给分支改名、删除分支,.git 里到底改了什么?对象会不会跟着消失?这篇逐项验证:改名带走了什么、-M 覆盖掉的提交去了哪里、删分支后对象数怎么变、git branch -d 凭什么说“没合并”。
$ git config --get-regexp "^branch.old-name"; ls .git/refs/heads .git/logs/refs/heads | tr "\n" " "; echo
branch.old-name.remote origin
branch.old-name.merge refs/heads/old-name
.git/logs/refs/heads: main old-name .git/refs/heads: main old-name
$ git branch -m old-name new-name; ls .git/refs/heads .git/logs/refs/heads | tr "\n" " "; echo; git config --get-regexp "^branch\.(old|new)-name"; cat .git/HEAD
.git/logs/refs/heads: main new-name .git/refs/heads: main new-name
branch.new-name.remote origin
branch.new-name.merge refs/heads/old-name
ref: refs/heads/new-name
对象数 5 → 5;分支指向的提交没变: True
$ git reflog show new-name --format='%gd: %gs' | head -3
new-name@{0}: Branch: renamed refs/heads/old-name to refs/heads/new-name
new-name@{1}: commit: 分支上的提交
new-name@{2}: branch: Created from HEAD
upstream 仍是 origin/old-name(改名不会去改远程):
$ git branch -vv | grep new-name | sed -E 's/[0-9a-f]{40}/<sha40>/g; s/\b[0-9a-f]{7}\b/<sha>/g'
* new-name af81252 [origin/old-name] 分支上的提交git branch -m 重命名 .git/refs/heads/ 下的 ref 文件和 logs 下的 reflog 文件,更新 HEAD 与 config 里的 branch.<名>.*。reflog 被带走并多一条 Branch: renamed;对象一个都没变。远程上的分支名没改,upstream 仍指向旧名。
$ git branch -m w victim 2>&1 fatal: a branch named 'victim' already exists (退出码 128) $ git branch -M w victim 2>&1; git log -1 --format=%s victim 只在w上 被覆盖的 victim 原来指向的提交还在对象库里: True;有分支包含它吗: False(没有:它已不可达)
目标分支已存在时 -m 拒绝,-M 强制覆盖。被覆盖的分支原来的提交没有被删,但不再有分支包含它,成为不可达对象。
$ git branch -d gone-soon 2>&1 | head -3
error: the branch 'gone-soon' is not fully merged
hint: If you are sure you want to delete it, run 'git branch -D gone-soon'
hint: Disable this message with "git config set advice.forceDeleteBranch false"
$ ls .git/logs/refs/heads | tr "\n" " "; echo; git branch -D gone-soon | sed -E "s/[0-9a-f]{7}/<sha>/"; ls .git/logs/refs/heads | tr "\n" " "; echo
gone-soon main new-name victim
Deleted branch gone-soon (was <sha>).
main new-name victim
对象数 13 → 13(分支删了,对象一个都没少);提交是否还在: True
$ git reflog expire --expire=now --all; git gc -q --prune=now; echo "(清空 reflog 并 gc --prune=now 之后)"
(清空 reflog 并 gc --prune=now 之后)
对象数 8;提交是否还在: False删除分支只删 ref 和它的 reflog。提交、tree、blob 都还在对象库里,对象总数不变,直到 reflog 清空、gc --prune 之后才真正消失。想找回误删的分支见前面的 fsck 一篇。
$ git checkout -q -b feat1 && echo f > f1 && git add . && git commit -q -m feat1提交 && git push -q -u origin feat1 && git checkout -q main
情形 A:feat1 已经推送(合并进了自己的 upstream origin/feat1),但没合并进 HEAD:
$ git branch -d feat1 2>&1 | sed -E "s/[0-9a-f]{7}/<sha>/"; git branch --list feat1 | wc -l
warning: deleting branch 'feat1' that has been merged to
'refs/remotes/origin/feat1', but not yet merged to HEAD
Deleted branch feat1 (was <sha>).
0
情形 B:feat2 合并进了 HEAD,但它的 upstream 是 origin/main,里面没有 feat2 的提交:
$ git checkout -q -b feat2 && echo g > f2 && git add . && git commit -q -m feat2提交 && git checkout -q main && git merge -q --no-edit feat2 && git branch -u origin/main feat2 >/dev/null; git branch -d feat2 2>&1 | head -2; git branch --list feat2 | wc -l
warning: not deleting branch 'feat2' that is not yet merged to
'refs/remotes/origin/main', even though it is merged to HEAD
1
$ git branch -D feat2 | sed -E "s/[0-9a-f]{7}/<sha>/"
Deleted branch feat2 (was <sha>).-d 只删“已合并”的分支。参照物:有 upstream 的看 upstream,没有看 HEAD。与 upstream 和 HEAD 的关系不一致时,会给警告(仍删除)或拒绝,并明确说出是针对哪个 ref。
$ git branch -D main 2>&1 | head -2 error: cannot delete branch 'main' used by worktree at '/work/l30'
被任何工作树检出的分支不能删除,先切走。