git add -p 是怎么把一个文件切成一块一块让你选的?hunk 的边界怎么定、s 什么时候能拆、e 编辑之后行数怎么重算、最后暂存区里到底是什么?这篇用脚本喂答案跑真实的 git add -p,一项项对账。
$ git diff | grep -E '^@@' @@ -1,8 +1,8 @@ @@ -17,7 +17,7 @@ @@ -35,6 +35,6 @@ 第 3 行与第 5 行相隔一行,各带 3 行上下文,上下文重叠,于是合成了一个 hunk
git diff 把连续的改动连同上下文(默认 3 行)切成 hunk,两处改动的上下文重叠就合并成一个。所以 4 处改动只切出 3 个 hunk。
答案 n、y、y(第一个 hunk 不要,后两个要);add -p 每个 hunk 问一次(提示里的 s 只在可拆分时才出现): $ printf 'n\ny\ny\n' | git add -p f.txt 2>&1 | grep -E '^(@@|\()' | sed -E 's/^(\([0-9]\/[0-9]\) Stage this hunk \[[^]]*\]\?) .*/\1/' @@ -1,8 +1,8 @@ (1/3) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,s,e,p,P,?]? (2/3) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? (3/3) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,P,?]? 暂存区里(git diff --cached)和工作区里剩下的(git diff): $ git diff --cached | grep -E '^(@@|[+-][^+-])' @@ -17,7 +17,7 @@ -行 20 +行 20 改了 @@ -35,6 +35,6 @@ -行 38 +行 38 改了 $ git diff | grep -E '^(@@|[+-][^+-])' @@ -1,8 +1,8 @@ -行 03 +行 03 改了 -行 05 +行 05 改了
git add -p 对每个 hunk 问一次:y 暂存、n 跳过。暂存区和工作区于是按 hunk 为单位分开,git diff --cached 与 git diff 各看到一部分。
对第一个 hunk 输入 s:git 回答 Split into 2 hunks.(第 3 行与第 5 行的改动被分开),再对拆出的第一块输 y、第二块输 n、然后 q:
,?]? Split into 2 hunks.
$ git diff --cached | grep -E '^(@@|[+-][^+-])'
@@ -1,6 +1,6 @@
-行 03
+行 03 改了一个 hunk 里有多处改动、且它们之间隔着未改动的行时,可以用 s 拆成更小的 hunk,分别决定取舍。只有一处改动的 hunk 没有 s 选项。
$ git diff --cached | grep -E '^(@@|[+-][^+-])' @@ -1,8 +1,7 @@ -行 03 +行 03 我手工改的 -行 05 $ git show :f.txt | sed -n '3p;5p'; echo '--- 工作区:'; sed -n '3p;5p' f.txt 行 03 我手工改的 行 06 --- 工作区: 行 03 改了 行 05 改了
e 把这个 hunk 当补丁交给编辑器。改 + 行就是改要暂存的内容;删掉一个 + 行相当于“只暂存对应的删除”;保存后 git 重算 @@ 头里的行数。工作区文件不受影响。
add -p(n、y、n)暂存的 blob: a5d5288f9632 手工只取第 2 个 hunk 的补丁 git apply --cached: a5d5288f9632 相同: True 工作区文件没动: True
暂存的内容 = HEAD 版本加上被选中的 hunk,和 git apply --cached 一个补丁得到的 blob 完全相同。
$ printf "y\n" | git add -p 2>&1 | head -2; git status --short No changes. ?? new.txt $ git add -N new.txt; git status --short; printf "y\n" | git add -p new.txt 2>&1 | grep -E "^(@@|\(1/1\))" | head -2; git diff --cached --stat | tail -1 A new.txt @@ -0,0 +1,6 @@ (1/1) Stage addition [y,n,q,a,d,e,p,P,?]? 1 file changed, 6 insertions(+) $ printf 'n\ny\nn\n' | git reset -p f.txt >/dev/null 2>&1; git diff --cached | grep -E '^(@@|[+-][^+-])' @@ -1,8 +1,8 @@ -行 03 +行 03 改了 -行 05 +行 05 改了 @@ -35,6 +35,6 @@ -行 38 +行 38 改了
untracked 文件没有 hunk,要先 git add -N(intent-to-add);reset -p、checkout -p、stash -p 用同一套选择机制,方向相反。