Split oversized runtime, sandbox, and interactive add files into focused helpers
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
package solutions.tretter.githugandroid
|
||||
|
||||
import android.content.Context
|
||||
import android.system.Os
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
internal class GitProcessRunner(
|
||||
private val context: Context?,
|
||||
private val sandboxesRoot: File,
|
||||
) {
|
||||
private companion object {
|
||||
val RequiredGitCommandAliases = listOf(
|
||||
"add",
|
||||
"branch",
|
||||
"checkout",
|
||||
"commit",
|
||||
"commit-tree",
|
||||
"config",
|
||||
"diff",
|
||||
"fetch",
|
||||
"index-pack",
|
||||
"merge",
|
||||
"merge-base",
|
||||
"pack-objects",
|
||||
"pull",
|
||||
"push",
|
||||
"read-tree",
|
||||
"rebase",
|
||||
"receive-pack",
|
||||
"rev-list",
|
||||
"rev-parse",
|
||||
"show-ref",
|
||||
"stash",
|
||||
"status",
|
||||
"symbolic-ref",
|
||||
"unpack-objects",
|
||||
"update-ref",
|
||||
"upload-pack",
|
||||
)
|
||||
}
|
||||
|
||||
fun shellExecutable(): String {
|
||||
return when {
|
||||
context != null -> "/system/bin/sh"
|
||||
File("/bin/sh").exists() -> "/bin/sh"
|
||||
else -> "sh"
|
||||
}
|
||||
}
|
||||
|
||||
fun runGit(
|
||||
binary: File,
|
||||
workingDir: File,
|
||||
arguments: List<String>,
|
||||
environment: Map<String, String> = emptyMap(),
|
||||
): ProcessExecutionResult {
|
||||
return runProcess(binary, workingDir, arguments, environment)
|
||||
}
|
||||
|
||||
fun runShellProcess(binary: File, workingDir: File, arguments: List<String>): ProcessExecutionResult {
|
||||
return try {
|
||||
val process = ProcessBuilder(listOf(binary.absolutePath) + arguments)
|
||||
.directory(workingDir)
|
||||
.redirectErrorStream(true)
|
||||
.apply {
|
||||
environment()["HOME"] = workingDir.absolutePath
|
||||
environment()["LC_ALL"] = "C"
|
||||
}
|
||||
.start()
|
||||
|
||||
val output = process.inputStream.bufferedReader().readLines()
|
||||
val exit = process.waitFor()
|
||||
ProcessExecutionResult(exitCode = exit, outputLines = output)
|
||||
} catch (error: Exception) {
|
||||
ProcessExecutionResult(exitCode = -1, outputLines = listOf("Shell execution failed: ${error.message ?: error::class.java.simpleName}"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun runProcess(
|
||||
binary: File,
|
||||
workingDir: File,
|
||||
arguments: List<String>,
|
||||
extraEnvironment: Map<String, String> = emptyMap(),
|
||||
): ProcessExecutionResult {
|
||||
return try {
|
||||
val gitExecPath = gitExecDirectory(binary)
|
||||
val process = ProcessBuilder(listOf(binary.absolutePath) + arguments)
|
||||
.directory(workingDir)
|
||||
.redirectErrorStream(true)
|
||||
.apply {
|
||||
environment()["HOME"] = workingDir.absolutePath
|
||||
environment()["GIT_EXEC_PATH"] = gitExecPath.absolutePath
|
||||
environment()["PATH"] = listOfNotNull(
|
||||
gitExecPath.absolutePath,
|
||||
System.getenv("PATH")?.takeIf { it.isNotBlank() },
|
||||
).joinToString(File.pathSeparator)
|
||||
environment()["GIT_CONFIG_NOSYSTEM"] = "1"
|
||||
environment()["GIT_AUTHOR_NAME"] = "GitHug"
|
||||
environment()["GIT_AUTHOR_EMAIL"] = "githug@example.com"
|
||||
environment()["GIT_COMMITTER_NAME"] = "GitHug"
|
||||
environment()["GIT_COMMITTER_EMAIL"] = "githug@example.com"
|
||||
environment()["LC_ALL"] = "C"
|
||||
environment().putAll(extraEnvironment)
|
||||
}
|
||||
.start()
|
||||
|
||||
val output = process.inputStream.bufferedReader().readLines()
|
||||
val exit = process.waitFor()
|
||||
ProcessExecutionResult(exitCode = exit, outputLines = output)
|
||||
} catch (error: Exception) {
|
||||
ProcessExecutionResult(exitCode = -1, outputLines = listOf("Native Git execution failed: ${error.message ?: error::class.java.simpleName}"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun gitExecDirectory(binary: File): File {
|
||||
val directory = if (context != null) {
|
||||
File(context.filesDir, "git-exec")
|
||||
} else {
|
||||
File(binary.parentFile ?: binary.absoluteFile.parentFile ?: sandboxesRoot, "git-exec")
|
||||
}
|
||||
refreshGitExecDirectoryIfNeeded(directory, binary)
|
||||
directory.mkdirs()
|
||||
|
||||
File(directory, "git").also { alias ->
|
||||
if (!alias.exists()) {
|
||||
createGitAlias(binary, alias)
|
||||
}
|
||||
}
|
||||
RequiredGitCommandAliases.forEach { command ->
|
||||
listOf("git-$command", command).forEach { aliasName ->
|
||||
val alias = File(directory, aliasName)
|
||||
if (!alias.exists()) {
|
||||
createGitAlias(binary, alias)
|
||||
}
|
||||
}
|
||||
}
|
||||
return directory
|
||||
}
|
||||
|
||||
private fun refreshGitExecDirectoryIfNeeded(directory: File, binary: File) {
|
||||
val fingerprint = buildString {
|
||||
append(binary.absolutePath)
|
||||
append('\n')
|
||||
append(binary.length())
|
||||
append('\n')
|
||||
append(binary.lastModified())
|
||||
}
|
||||
val stamp = File(directory, ".binary-fingerprint")
|
||||
val previousFingerprint = stamp.takeIf { it.isFile }?.readText()
|
||||
if (previousFingerprint != fingerprint) {
|
||||
directory.deleteRecursively()
|
||||
}
|
||||
directory.mkdirs()
|
||||
stamp.writeText(fingerprint)
|
||||
}
|
||||
|
||||
private fun createGitAlias(binary: File, alias: File) {
|
||||
if (alias.exists() || Files.isSymbolicLink(alias.toPath())) {
|
||||
alias.delete()
|
||||
}
|
||||
if (context != null) {
|
||||
try {
|
||||
Os.symlink(binary.absolutePath, alias.absolutePath)
|
||||
alias.setExecutable(true, false)
|
||||
return
|
||||
} catch (_: Exception) {
|
||||
// Fall through to the portable options below.
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Files.createSymbolicLink(alias.toPath(), binary.toPath())
|
||||
} catch (_: Exception) {
|
||||
binary.copyTo(alias, overwrite = true)
|
||||
}
|
||||
alias.setExecutable(true, false)
|
||||
}
|
||||
}
|
||||
|
||||
internal data class ProcessExecutionResult(
|
||||
val exitCode: Int,
|
||||
val outputLines: List<String>,
|
||||
)
|
||||
Reference in New Issue
Block a user