git-staging

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Non-interactive Git Staging

非交互式 Git 暂存

When to use this skill

什么时候使用本技巧

Use this skill when you need to:
  • Stage only specific hunks from a file (not the entire file)
  • Stage only specific lines within a hunk
  • Avoid interactive git commands (
    git add -p
    ,
    git add -i
    , etc.)
  • Programmatically control exactly what gets staged
当你需要完成以下操作时使用本技巧:
  • 仅暂存文件中的特定 hunk(而非整个文件)
  • 仅暂存某个 hunk 内的特定行
  • 避免使用交互式 Git 命令(
    git add -p
    git add -i
    等)
  • 通过编程方式精准控制要暂存的内容

Step 1: Assess the changes

步骤 1:评估变更

Before choosing a staging method, determine what changes exist:
bash
git status                    # See which files have changes
git diff --no-ext-diff        # See all unstaged changes
git diff --cached --no-ext-diff  # See already-staged changes
Decision tree:
  • If only ONE file has changes and you want ALL of them staged → use
    git add <file>
  • If ONE file has multiple unrelated hunks and you want only SOME → use Method 2 or 3
  • If MULTIPLE files need staging → stage per-file with
    git add
    for each, or selectively with patch method
选择暂存方法前,先确认存在哪些变更:
bash
git status                    # 查看哪些文件有改动
git diff --no-ext-diff        # 查看所有未暂存的变更
git diff --cached --no-ext-diff  # 查看已暂存的变更
决策树:
  • 如果只有 1 个文件有变更且你需要暂存它的所有改动 → 使用
    git add <file>
  • 如果 1 个文件有多个不相关的 hunk 且你只需要暂存部分 → 使用方法 2 或 3
  • 如果有多个文件需要暂存 → 对每个文件单独执行
    git add
    暂存,或使用补丁方法选择性暂存

Why not use
git add -p
?

为什么不使用
git add -p

Interactive git commands require a TTY and human input. AI agents cannot reliably fake interactive sessions - attempts using
echo "y"
or
yes
are fragile and often fail. Instead, construct patches programmatically and apply them directly to the index.
交互式 Git 命令需要 TTY 和人工输入。AI Agent 无法可靠地模拟交互式会话——使用
echo "y"
yes
的尝试稳定性差,经常失败。更好的做法是通过编程方式构造补丁,直接将其应用到索引(index)。

Core technique

核心技术

The key insight is that
git apply --cached
applies a patch directly to the staging area (index) without modifying the working tree. This lets you stage precise changes non-interactively.
核心思路是
git apply --cached
可以直接将补丁应用到暂存区(index),无需修改工作树。这让你可以非交互式地暂存精确的变更。

Temporary files

临时文件

All temporary patch files should be written to
tmp/
within the repository (not
/tmp
) to avoid permission prompts. Ensure the directory exists first:
bash
mkdir -p tmp
所有临时补丁文件都应该写入代码仓库内的
tmp/
目录(而非系统的
/tmp
),避免权限提示。首先确保该目录存在:
bash
mkdir -p tmp

Methods

方法

Method 1: Stage entire file

方法 1:暂存整个文件

When you want to stage all changes in a file:
bash
git add <file>
当你需要暂存某个文件的所有变更时:
bash
git add <file>

Method 2: Stage specific hunks via patch

方法 2:通过补丁暂存特定 hunk

When you need to stage only certain hunks from a file:
  1. Generate the full diff for the file:
    bash
    git diff <file> > tmp/full.patch
  2. Edit the patch to keep only the hunks you want to stage (use the Edit tool or create a new file with only the desired hunks)
  3. Apply the edited patch to the index:
    bash
    git apply --cached tmp/selected.patch
当你只需要暂存文件中的部分 hunk 时:
  1. 生成该文件的完整 diff:
    bash
    git diff <file> > tmp/full.patch
  2. 编辑补丁,仅保留你想要暂存的 hunk(使用编辑工具,或创建仅包含目标 hunk 的新文件)
  3. 将编辑后的补丁应用到索引:
    bash
    git apply --cached tmp/selected.patch

Method 3: Stage specific lines within a hunk

方法 3:暂存 hunk 内的特定行

When you need to stage only certain lines within a hunk, you must carefully edit the patch to maintain validity:
  1. Generate the diff:
    bash
    git diff <file> > tmp/full.patch
  2. Edit the patch, following these rules for the hunk you're modifying:
    • Keep the
      @@
      hunk header but adjust the line counts
    • To exclude an added line (
      +
      ): remove the entire line from the patch
    • To exclude a removed line (
      -
      ): change
      -
      to a space (
       
      ) to make it context
    • Adjust the line counts in the
      @@ -X,Y +X,Z @@
      header to match
  3. Apply:
    bash
    git apply --cached tmp/selected.patch
当你需要暂存某个 hunk 内的特定行时,必须仔细编辑补丁以保证其有效性:
  1. 生成 diff:
    bash
    git diff <file> > tmp/full.patch
  2. 编辑补丁,对于你要修改的 hunk 遵循以下规则:
    • 保留
      @@
      hunk 头,但调整行数计数
    • 排除某条新增行
      +
      开头):从补丁中删除整行
    • 排除某条删除行
      -
      开头):将
      -
      改为空格(
       
      ),使其成为上下文行
    • 调整
      @@ -X,Y +X,Z @@
      头中的行数计数,使其匹配实际内容
  3. 应用:
    bash
    git apply --cached tmp/selected.patch

Patch format reference

补丁格式参考

A unified diff patch has this structure:
diff
diff --git a/file.txt b/file.txt
index abc123..def456 100644
--- a/file.txt
+++ b/file.txt
@@ -10,6 +10,8 @@ optional context label
 context line (unchanged)
-removed line
+added line
 context line (unchanged)
统一 diff 补丁的结构如下:
diff
diff --git a/file.txt b/file.txt
index abc123..def456 100644
--- a/file.txt
+++ b/file.txt
@@ -10,6 +10,8 @@ optional context label
 context line (unchanged)
-removed line
+added line
 context line (unchanged)

Hunk header format

Hunk 头格式

@@ -START,COUNT +START,COUNT @@
  • First pair: original file (lines being removed or used as context)
  • Second pair: new file (lines being added or used as context)
  • COUNT = number of lines in that side of the hunk (context + changes)
@@ -START,COUNT +START,COUNT @@
  • 第一组:原始文件(被删除或用作上下文的行)
  • 第二组:新文件(被新增或用作上下文的行)
  • COUNT = hunk 对应侧的行数(上下文 + 变更)

Line prefixes

行前缀

  •  
    (space): context line (unchanged, appears in both versions)
  • -
    : line only in original (will be removed)
  • +
    : line only in new version (will be added)
  •  
    (空格):上下文行(无改动,两个版本都存在)
  • -
    :仅存在于原始版本的行(会被删除)
  • +
    :仅存在于新版本的行(会被新增)

Example: Staging only the second hunk

示例:仅暂存第二个 hunk

Given a file with two hunks of changes:
bash
undefined
假设某个文件有两个 hunk 的变更:
bash
undefined

Generate full diff

生成完整 diff

git diff --no-ext-diff myfile.py > tmp/full.patch

The patch might look like:

```diff
diff --git a/myfile.py b/myfile.py
index abc123..def456 100644
--- a/myfile.py
+++ b/myfile.py
@@ -5,6 +5,7 @@ import os
 def foo():
     pass
+    # Added comment in first hunk

 def bar():
@@ -20,6 +21,7 @@ def bar():
 def baz():
     pass
+    # Added comment in second hunk
To stage only the second hunk, create a new patch with just that hunk:
diff
diff --git a/myfile.py b/myfile.py
index abc123..def456 100644
--- a/myfile.py
+++ b/myfile.py
@@ -20,6 +21,7 @@ def bar():
 def baz():
     pass
+    # Added comment in second hunk
Then apply:
bash
git apply --cached tmp/second-hunk.patch
git diff --no-ext-diff myfile.py > tmp/full.patch

补丁内容可能如下:

```diff
diff --git a/myfile.py b/myfile.py
index abc123..def456 100644
--- a/myfile.py
+++ b/myfile.py
@@ -5,6 +5,7 @@ import os
 def foo():
     pass
+    # Added comment in first hunk

 def bar():
@@ -20,6 +21,7 @@ def bar():
 def baz():
     pass
+    # Added comment in second hunk
要仅暂存第二个 hunk,创建仅包含该 hunk 的新补丁:
diff
diff --git a/myfile.py b/myfile.py
index abc123..def456 100644
--- a/myfile.py
+++ b/myfile.py
@@ -20,6 +21,7 @@ def bar():
 def baz():
     pass
+    # Added comment in second hunk
然后应用:
bash
git apply --cached tmp/second-hunk.patch

Example: Excluding specific added lines

示例:排除特定新增行

If you have a hunk with multiple additions but only want to stage some:
Original hunk:
diff
@@ -10,4 +10,7 @@
 existing line
+line I want to stage
+line I do NOT want to stage
+another line I want to stage
 more context
Edit to exclude the unwanted line (remove it entirely and adjust count):
diff
@@ -10,4 +10,6 @@
 existing line
+line I want to stage
+another line I want to stage
 more context
Note: The
+10,7
became
+10,6
because we removed one added line.
如果你有一个包含多条新增内容的 hunk,但只想暂存其中一部分:
原始 hunk:
diff
@@ -10,4 +10,7 @@
 existing line
+line I want to stage
+line I do NOT want to stage
+another line I want to stage
 more context
编辑补丁排除不需要的行(整行删除并调整计数):
diff
@@ -10,4 +10,6 @@
 existing line
+line I want to stage
+another line I want to stage
 more context
注意:
+10,7
变成了
+10,6
,因为我们删除了一条新增行。

Verification

验证

After applying, verify what was staged:
bash
git diff --cached          # Show staged changes
git diff                   # Show unstaged changes (should include excluded hunks)
git status                 # Overview of staged/unstaged state
应用补丁后,验证暂存的内容:
bash
git diff --cached          # 查看已暂存的变更
git diff                   # 查看未暂存的变更(应该包含被排除的hunk)
git status                 # 暂存/未暂存状态概览

Common pitfalls

常见陷阱

  1. Invalid line counts: If the
    @@
    header counts don't match the actual lines in the hunk,
    git apply
    will fail. Always recount after editing.
  2. Missing newline at EOF: Patches are sensitive to trailing newlines. Watch for
    \ No newline at end of file
    markers.
  3. Whitespace corruption: Ensure context lines start with a space, not an empty prefix. Some editors strip trailing spaces.
  4. Index mismatch: The
    index abc123..def456
    line is optional for
    git apply --cached
    . If you have issues, try removing it.
  1. 行数计数无效:如果
    @@
    头中的计数和 hunk 内的实际行数不匹配,
    git apply
    会失败。编辑后务必重新计数。
  2. 文件末尾缺少换行符:补丁对末尾换行符非常敏感。留意
    \ No newline at end of file
    标记。
  3. 空白符损坏:确保上下文行以空格开头,而非空前缀。部分编辑器会删除行尾空格。
  4. 索引不匹配
    git apply --cached
    不需要
    index abc123..def456
    行。如果你遇到问题,可以尝试删除该行。

Troubleshooting

故障排查

If
git apply --cached
fails:
  1. Test the patch first without
    --cached
    :
    bash
    git apply --check tmp/selected.patch
  2. Use verbose mode to see what's happening:
    bash
    git apply --cached -v tmp/selected.patch
  3. Common error messages:
    • "patch does not apply": Line counts are wrong or context doesn't match
    • "patch fragment without header": Missing the
      diff --git
      or
      ---/+++
      lines
如果
git apply --cached
失败:
  1. 先不带
    --cached
    参数测试补丁:
    bash
    git apply --check tmp/selected.patch
  2. 使用 verbose 模式查看具体情况:
    bash
    git apply --cached -v tmp/selected.patch
  3. 常见错误信息:
    • "patch does not apply":行数计数错误或上下文不匹配
    • "patch fragment without header":缺少
      diff --git
      ---/+++