Remove brittle interactive rebase shortcuts

This commit is contained in:
Joe Tretter
2026-05-18 18:37:10 -05:00
parent 06b56010d0
commit d4e2c8714c
8 changed files with 1172 additions and 45 deletions

View File

@@ -127,7 +127,8 @@ class GitRepositoryRuntime private constructor(
.takeIf { it.path.startsWith(sandboxRoot.path) } ?: sandboxRoot
val shellTokens = GitSandboxEngine.tokenizeShellCommand(command)
val tokens = shellTokens.map { it.value }
val invocation = parseEnvironmentPrefixedCommand(shellTokens)
val tokens = invocation.command.map { it.value }
if (tokens.isEmpty()) return inspectSandbox(level).copy(currentDir = currentRepo.currentDir) to emptyList()
if (currentRepo.interactiveAddSession != null) {
val (updatedRepo, output) = GitSandboxEngine.execute(currentRepo, command)
@@ -146,13 +147,13 @@ class GitRepositoryRuntime private constructor(
val expandedTokens = if (tokens.firstOrNull() == "echo") {
tokens
} else {
expandShellPathspecs(currentRepo, shellTokens)
expandShellPathspecs(currentRepo, invocation.command)
}.normalizeGitStageAlias()
executeSyntheticGitCommand(currentRepo, command, expandedTokens)?.let { return it }
executeSyntheticGitCommand(currentRepo, command, expandedTokens, invocation.environment)?.let { return it }
val result = when (expandedTokens.first()) {
"git" -> currentRepo to runGit(nativeGit, workingDir, expandedTokens.drop(1)).outputLines
"git" -> currentRepo to runGit(nativeGit, workingDir, expandedTokens.drop(1), invocation.environment).outputLines
"help", "?" -> currentRepo to commandReferenceLines()
else -> executeHelperCommand(sandboxRoot, workingDir, currentRepo, expandedTokens)
}
@@ -551,7 +552,9 @@ class GitRepositoryRuntime private constructor(
currentRepo: RepoState,
command: String,
tokens: List<String>,
environment: Map<String, String> = emptyMap(),
): Pair<RepoState, List<String>>? {
if (environment.isNotEmpty()) return null
if (tokens.firstOrNull() != "git") return null
val gitCommand = tokens.getOrNull(1) ?: return null
if (gitCommand == "clone" && tokens.getOrNull(2)?.startsWith("https://github.com/Gazler/cloneme") == true) {
@@ -562,7 +565,7 @@ class GitRepositoryRuntime private constructor(
}
val shouldUseSandboxSemantics = when (gitCommand) {
"add" -> tokens.any { it == "-p" || it == "--patch" || it == "-i" || it == "--interactive" }
"rebase" -> "-i" in tokens || "--interactive" in tokens || "--onto" in tokens
"rebase" -> "--onto" in tokens
"merge" -> "--squash" in tokens || tokens.lastOrNull() == "mybranch" || tokens.lastOrNull() == "feature"
"revert", "stash" -> true
"checkout" -> tokens.any { it == "file3" } || tokens.takeLast(2) == listOf("--", "config.rb")
@@ -829,11 +832,47 @@ class GitRepositoryRuntime private constructor(
return (first + second).distinct()
}
private fun runGit(binary: File, workingDir: File, arguments: List<String>): ProcessExecutionResult {
return runProcess(binary, workingDir, arguments)
private data class EnvironmentPrefixedCommand(
val environment: Map<String, String>,
val command: List<GitSandboxEngine.ShellToken>,
)
private fun parseEnvironmentPrefixedCommand(tokens: List<GitSandboxEngine.ShellToken>): EnvironmentPrefixedCommand {
val environment = linkedMapOf<String, String>()
var commandStart = 0
while (commandStart < tokens.size) {
val value = tokens[commandStart].value
val assignmentIndex = value.indexOf('=')
if (assignmentIndex <= 0) break
val name = value.take(assignmentIndex)
if (!name.isShellEnvironmentName()) break
environment[name] = value.substring(assignmentIndex + 1)
commandStart += 1
}
return EnvironmentPrefixedCommand(environment, tokens.drop(commandStart))
}
private fun runProcess(binary: File, workingDir: File, arguments: List<String>): ProcessExecutionResult {
private fun String.isShellEnvironmentName(): Boolean {
if (isEmpty()) return false
if (first() != '_' && !first().isLetter()) return false
return all { it == '_' || it.isLetterOrDigit() }
}
private fun runGit(
binary: File,
workingDir: File,
arguments: List<String>,
environment: Map<String, String> = emptyMap(),
): ProcessExecutionResult {
return runProcess(binary, workingDir, arguments, environment)
}
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)
@@ -842,13 +881,17 @@ class GitRepositoryRuntime private constructor(
.apply {
environment()["HOME"] = workingDir.absolutePath
environment()["GIT_EXEC_PATH"] = gitExecPath.absolutePath
environment()["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()