Route interactive Git through native terminal sessions
Remove Kotlin interactive-add emulation and run patch add via the native Git runtime with PTY-backed session IO.
This commit is contained in:
@@ -3,12 +3,18 @@ package solutions.tretter.githugandroid
|
||||
import android.content.Context
|
||||
import android.system.Os
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.nio.file.Files
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
internal class GitProcessRunner(
|
||||
private val context: Context?,
|
||||
private val sandboxesRoot: File,
|
||||
) {
|
||||
private val nextHostSessionId = AtomicInteger(1)
|
||||
private val hostSessions = ConcurrentHashMap<Int, HostGitSession>()
|
||||
|
||||
private companion object {
|
||||
val RequiredGitCommandAliases = listOf(
|
||||
"add",
|
||||
@@ -77,6 +83,29 @@ internal class GitProcessRunner(
|
||||
return result
|
||||
}
|
||||
|
||||
fun startGitSession(
|
||||
binary: File,
|
||||
workingDir: File,
|
||||
arguments: List<String>,
|
||||
environment: Map<String, String> = emptyMap(),
|
||||
): GitSessionResult {
|
||||
val gitExecPath = gitExecDirectory(binary)
|
||||
val fullEnvironment = gitEnvironment(binary, workingDir, environment, gitExecPath)
|
||||
return if (context != null) {
|
||||
NativeGitBridge.startGitSession(binary, workingDir, arguments, fullEnvironment)
|
||||
} else {
|
||||
startHostGitSession(binary, workingDir, arguments, fullEnvironment)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeGitSession(sessionId: Int, input: String): GitSessionResult {
|
||||
return if (context != null) {
|
||||
NativeGitBridge.writeGitSession(sessionId, input)
|
||||
} else {
|
||||
writeHostGitSession(sessionId, input)
|
||||
}
|
||||
}
|
||||
|
||||
fun runShellProcess(binary: File, workingDir: File, arguments: List<String>): ProcessExecutionResult {
|
||||
return try {
|
||||
val process = ProcessBuilder(listOf(binary.absolutePath) + arguments)
|
||||
@@ -119,6 +148,67 @@ internal class GitProcessRunner(
|
||||
}
|
||||
}
|
||||
|
||||
private fun startHostGitSession(
|
||||
binary: File,
|
||||
workingDir: File,
|
||||
arguments: List<String>,
|
||||
environment: Map<String, String>,
|
||||
): GitSessionResult {
|
||||
return try {
|
||||
val process = ProcessBuilder(listOf(binary.absolutePath) + arguments)
|
||||
.directory(workingDir)
|
||||
.redirectErrorStream(true)
|
||||
.apply { environment().putAll(environment) }
|
||||
.start()
|
||||
val sessionId = nextHostSessionId.getAndIncrement()
|
||||
hostSessions[sessionId] = HostGitSession(process)
|
||||
readHostGitSessionResult(sessionId, process)
|
||||
} catch (error: Exception) {
|
||||
GitSessionResult(
|
||||
sessionId = 0,
|
||||
running = false,
|
||||
exitCode = -1,
|
||||
outputLines = listOf("Native Git session failed: ${error.message ?: error::class.java.simpleName}"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeHostGitSession(sessionId: Int, input: String): GitSessionResult {
|
||||
val session = hostSessions[sessionId]
|
||||
?: return GitSessionResult(sessionId, running = false, exitCode = -1, outputLines = listOf("Native Git session is not running."))
|
||||
return try {
|
||||
session.process.outputStream.write(input.toByteArray())
|
||||
session.process.outputStream.flush()
|
||||
readHostGitSessionResult(sessionId, session.process)
|
||||
} catch (error: Exception) {
|
||||
hostSessions.remove(sessionId)
|
||||
GitSessionResult(
|
||||
sessionId = sessionId,
|
||||
running = false,
|
||||
exitCode = -1,
|
||||
outputLines = listOf("Native Git session failed: ${error.message ?: error::class.java.simpleName}"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readHostGitSessionResult(sessionId: Int, process: Process): GitSessionResult {
|
||||
val output = StringBuilder()
|
||||
val deadline = System.nanoTime() + 1_000_000_000L
|
||||
do {
|
||||
output.append(process.inputStream.readAvailableText())
|
||||
if (!process.isAlive || output.isNotEmpty()) break
|
||||
Thread.sleep(25)
|
||||
} while (System.nanoTime() < deadline)
|
||||
|
||||
output.append(process.inputStream.readAvailableText())
|
||||
return if (process.isAlive) {
|
||||
GitSessionResult(sessionId = sessionId, running = true, exitCode = null, outputLines = output.toString().toOutputLines())
|
||||
} else {
|
||||
hostSessions.remove(sessionId)
|
||||
GitSessionResult(sessionId = sessionId, running = false, exitCode = process.exitValue(), outputLines = output.toString().toOutputLines())
|
||||
}
|
||||
}
|
||||
|
||||
private fun gitExecDirectory(binary: File): File {
|
||||
val directory = if (context != null) {
|
||||
File(context.filesDir, "git-exec")
|
||||
@@ -166,6 +256,7 @@ internal class GitProcessRunner(
|
||||
put("GIT_COMMITTER_NAME", "GitHug")
|
||||
put("GIT_COMMITTER_EMAIL", "githug@example.com")
|
||||
put("LC_ALL", "C")
|
||||
put("TERM", "xterm-256color")
|
||||
putAll(extraEnvironment)
|
||||
}
|
||||
}
|
||||
@@ -232,3 +323,32 @@ internal data class ProcessExecutionResult(
|
||||
val exitCode: Int,
|
||||
val outputLines: List<String>,
|
||||
)
|
||||
|
||||
internal data class GitSessionResult(
|
||||
val sessionId: Int,
|
||||
val running: Boolean,
|
||||
val exitCode: Int?,
|
||||
val outputLines: List<String>,
|
||||
)
|
||||
|
||||
private data class HostGitSession(
|
||||
val process: Process,
|
||||
)
|
||||
|
||||
private fun InputStream.readAvailableText(): String {
|
||||
val output = StringBuilder()
|
||||
val buffer = ByteArray(4096)
|
||||
while (available() > 0) {
|
||||
val count = read(buffer)
|
||||
if (count <= 0) break
|
||||
output.append(String(buffer, 0, count))
|
||||
}
|
||||
return output.toString()
|
||||
}
|
||||
|
||||
private fun String.toOutputLines(): List<String> =
|
||||
replace("\r\n", "\n")
|
||||
.replace('\r', '\n')
|
||||
.lineSequence()
|
||||
.toList()
|
||||
.dropLastWhile { it.isEmpty() }
|
||||
|
||||
Reference in New Issue
Block a user