bigywater.tech
Git · 34

分支改名与删除对对象的影响

给分支改名、删除分支,.git 里到底改了什么?对象会不会跟着消失?这篇逐项验证:改名带走了什么、-M 覆盖掉的提交去了哪里、删分支后对象数怎么变、git branch -d 凭什么说“没合并”。

5 → 5
git branch -m 改名前后的对象总数,对象一个都没变
13 → 13 → 8
删除分支前后对象数不变,清 reflog 并 gc --prune=now 才降到 8
upstream 优先
git branch -d 判断“已合并”时,有 upstream 就看 upstream,没有才看 HEAD

改名只动本地的名字

实测 · 本机执行输出
  $ 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 仍指向旧名。

-M 覆盖

实测 · 本机执行输出
  $ 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 一篇。

-d 与已合并

实测 · 本机执行输出
  $ 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'

被任何工作树检出的分支不能删除,先切走。

分步演示

分支改名与删除
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。远程是本机的一个裸仓库;对象总数 = git count-objects -v 里的 count + in-pack;输出里的 hash 做了替换;第三节的 13 → 8 包含此前被覆盖分支留下的不可达对象
    • 没有验证:git branch -c(复制分支);branch --move 对 worktree 中分支的处理;git branch --delete --remotes;remote 端分支改名(git push origin :old new 的完整流程,只在 refspec 一篇涉及推送);branch.<名>.description 等其它 config 项随改名的处理
    京ICP备2026067502号-1京公网安备11010502063487号