Open patch hunk editor for git add -p edit command

This commit is contained in:
Joe Tretter
2026-05-18 21:10:17 -05:00
parent 4c5d68c1cb
commit ed0b81e587
6 changed files with 121 additions and 4 deletions

View File

@@ -4,6 +4,7 @@ enum class GitEditorCommandKind {
COMMIT_MESSAGE,
REBASE_TODO,
TAG_MESSAGE,
PATCH_HUNK,
}
data class GitEditorInvocation(

View File

@@ -405,10 +405,15 @@ fun GitHugApp() {
fun openGitMessageEditor(invocation: GitEditorInvocation) {
val initialContent = runtime.gitEditorInitialContent(currentLevel, repo, invocation)
val openedMessage = when (invocation.kind) {
GitEditorCommandKind.REBASE_TODO -> "Opened Git rebase editor"
GitEditorCommandKind.PATCH_HUNK -> "Opened Git patch editor"
else -> "Opened Git message editor"
}
output = buildList {
addAll(output)
add("$ ${invocation.command}")
add(if (invocation.kind == GitEditorCommandKind.REBASE_TODO) "Opened Git rebase editor" else "Opened Git message editor")
add(openedMessage)
}
gitMessageEditorState = GitMessageEditorState(
invocation = invocation.copy(initialContent = initialContent),
@@ -474,6 +479,12 @@ fun GitHugApp() {
return
}
val patchHunkEditorInvocation = GitSandboxEngine.parsePatchHunkEditorInvocation(repo, raw)
if (patchHunkEditorInvocation != null) {
openGitMessageEditor(patchHunkEditorInvocation)
return
}
val (newRepo, lines) = runtime.execute(currentLevel, repo, raw)
applyCommandResult(raw, newRepo, lines, echoCommand = !isInteractiveInput)
}

View File

@@ -315,6 +315,21 @@ class GitRepositoryRuntime private constructor(
return executeInteractiveRebaseEditorCommand(level, currentRepo, nativeGit, sandboxRoot, workingDir, invocation, message)
}
if (invocation.kind == GitEditorCommandKind.PATCH_HUNK) {
val (updatedRepo, output) = GitSandboxEngine.applyPatchHunkEdit(currentRepo, message)
val newlyStagedPaths = updatedRepo.files.filter { updatedFile ->
updatedFile.staged && currentRepo.files.firstOrNull { it.name == updatedFile.name }?.staged != true
}.map { it.name }
if (newlyStagedPaths.isNotEmpty()) {
runGit(nativeGit, workingDir, listOf("add") + newlyStagedPaths)
}
val inspectedRepo = inspectSandbox(level).copy(
currentDir = updatedRepo.currentDir,
interactiveAddSession = updatedRepo.interactiveAddSession,
)
return inspectedRepo to output
}
val messageFile = File(sandboxRoot, ".git/GITHUG_ANDROID_EDITMSG").apply {
parentFile?.mkdirs()
writeText(message)

View File

@@ -8,6 +8,36 @@ object GitSandboxEngine {
val quoted: Boolean = false,
)
fun parsePatchHunkEditorInvocation(repo: RepoState, command: String): GitEditorInvocation? {
val session = repo.interactiveAddSession ?: return null
if (session.selectionAction != "patch-hunk") return null
if (command.trim().lowercase() != "e") return null
val target = session.target ?: return null
val file = repo.files.firstOrNull { it.name == target && !it.deleted } ?: return null
return GitEditorInvocation(
command = command,
kind = GitEditorCommandKind.PATCH_HUNK,
title = "Edit Patch Hunk",
initialContent = patchHunkLines(file)
.dropLastWhile { it == PatchHunkPrompt }
.joinToString("\n"),
)
}
fun applyPatchHunkEdit(repo: RepoState, content: String): Pair<RepoState, List<String>> {
val session = repo.interactiveAddSession ?: return repo to listOf("No patch hunk is active.")
val target = session.target ?: return repo.copy(interactiveAddSession = null) to listOf("No patch hunk is active.")
if (session.selectionAction != "patch-hunk") return repo to listOf("No patch hunk is active.")
if (content.isBlank()) return repo to listOf("Edited hunk was empty; patch was not applied.", PatchHunkPrompt)
val updatedFiles = repo.files.map { file ->
if (file.name == target && !file.deleted) file.copy(staged = true) else file
}
return repo.copy(files = updatedFiles, interactiveAddSession = null) to listOf(
"$PatchHunkPrompt e",
"Applied edited hunk.",
)
}
fun commandReferenceLines(): List<String> = listOf(
"Available sandbox commands:",
" git ",
@@ -368,7 +398,7 @@ object GitSandboxEngine {
}
}
"s" -> repo to listOf("$PatchHunkPrompt $answer", "Sorry, cannot split this hunk", PatchHunkPrompt)
"e" -> repo to listOf("$PatchHunkPrompt $answer", "Manual hunk editing is not available in this mobile sandbox.", PatchHunkPrompt)
"e" -> repo to listOf("$PatchHunkPrompt $answer", "Opening patch editor")
else -> repo to listOf("$PatchHunkPrompt $answer", "Unknown command '$answer'.", PatchHunkPrompt)
}
}