最近在维护一个持续迭代的项目时,分支数量越堆越多,很多功能分支合并完之后并没有及时清理,本地残留了大量旧分支。想快速找出来“哪些分支已经几个月没人动”,结果发现git branch默认只按字母序显示分支名,完全看不出时间信息,甚至很难判断这些分支是活跃还是已经废弃。于是我把 Git 分支按最后提交日期排序的方法系统整理了一遍,这篇文章就是这次梳理的完整记录。
本文会覆盖三种主流做法:
git branch --sort=-committerdate:最简单直接的排序命令。git for-each-ref --sort=-committerdate --format=...:更灵活的完整解决方案。- 用
git log自行拼接排序:了解原理,方便自定义。
同时会讲清楚committerdate、authordate、creatordate的区别,以及如何结合筛选条件定位长时间未更新的“僵尸分支”。新手可以跟着从准备演示仓库开始操作,有经验的开发者可以直接跳到命令示例和最佳实践部分。
1. 为什么需要按提交日期排序分支?
1.1 分支多了之后的“失控感”
在多人协作的项目中,每个需求、每个 Bug 修复都可能开一个分支。随着迭代次数增加,分支列表会越来越长。
默认的git branch输出长这样:
$ git branch bugfix/payment feature/login feature/order * master如果项目里只有 5 个分支,这样看没有问题。但当分支数量到达 30 个、50 个甚至更多时,仅凭分支名很难判断哪些分支还在活跃开发,哪些分支已经合并完但忘了删除。
尤其到了版本发布的节点,需要确认“哪些分支还没合并”“哪些分支最近已经废弃”,这时候最直观的指标就是“这个分支最后一次提交是什么时候”。按最后提交日期排序,本质上是在回答一个问题:这个分支到底还有没有人在维护。
1.2 “最后提交日期”到底指什么
Git 里的时间并不只有一种。
在git log输出中,每次提交至少有两个时间概念:
- author date:作者创建这次提交的时间。
- committer date:提交者把这次提交写入当前分支/仓库的时间。
平时git log默认显示的时间,通常是作者时间。而在分支排序场景下,我们更关心的是“这个分支最后被更新到哪一刻”,对应的是committer date,因为通过 rebase、cherry-pick、apply patch 等操作后,committer date 会更新,更能反映这个分支当前指向的提交实际进入仓库的时间。
还需要注意一个容易混淆的概念:分支本身并不是一个“容器”,它只是一个指向某个提交的指针。所以“按分支最后提交日期排序”,本质是查看“每个分支指针指向的那次提交的 committer date”,然后按这个时间排序。这个理解对后面排查问题很有帮助。
还有一个概念是creatordate,它表示引用(ref)本身的创建或最后更新时间。针对分支这种引用,creatordate和它指向的提交关系比较密切,在多数使用场景下,我们直接按提交时间排序即可。
2. 环境准备与版本说明
2.1 本教程的验证环境
本文的命令在 Git 2.x 环境下验证通过,具体场景如下:
- 操作系统:Linux / macOS 均可;Windows 用户建议使用 Git Bash 执行命令。
- Git 版本:任意较新的 2.x 版本;
git branch --sort和git for-each-ref --sort都属于长期存在的功能。 - 终端编码:建议使用 UTF-8,避免中文分支名显示异常。
如果你还不确定自己电脑的 Git 版本,可以执行:
git --version我本机测试时使用 Git 2.39 系列,如果你的项目环境版本较老,建议先升级再继续操作。
如果你的电脑还没有安装 Git,需要先完成安装和基础配置。这里简单提醒一下:Windows 可以从 Git 官网下载安装包,安装时保持默认选项即可;macOS 可以使用 Homebrew 安装:
brew install git安装完成后,至少配置用户名和邮箱,否则 commit 操作会失败或提示缺失身份信息:
git config --global user.name "your name" git config --global user.email "you@example.com"2.2 准备一个便于演示的仓库
为了能够看清楚排序效果,这里先创建一个包含多个分支、且不同分支提交时间有差异的测试仓库。
git init demo-branch-sort cd demo-branch-sort git config user.name "dev" git config user.email "dev@example.com" echo "# demo" > README.md git add . git commit -m "init project"接着创建几个分支,并在每个分支上进行独立提交:
git checkout -b feature/login echo "login feature" > login.txt git add . git commit -m "add login feature" git checkout master git checkout -b feature/order echo "order feature" > order.txt git add . git commit -m "add order feature" git checkout master git checkout -b bugfix/payment echo "fix payment" > payment.txt git add . git commit -m "fix payment bug" git checkout master echo "update readme" >> README.md git add . git commit -m "update readme"最终仓库里会有 4 个本地分支:
- master:最后一次提交是 “update readme”。
- feature/login:最后一次提交是 “add login feature”。
- feature/order:最后一次提交是 “add order feature”。
- bugfix/payment:最后一次提交是 “fix payment bug”。
由于每个分支都只提交了一次且时间点不同,方便后续观察排序结果。
3. 核心命令拆解:三种排序方式
下面我们一层一层把按最后提交日期排序的方案讲透。先看最省事的,再看最灵活的,最后讲一个“理解原理”的做法。
3.1 最简单的git branch --sort
这是日常使用中最直接的排序方式。
写法:
git branch --sort=-committerdate在这个输出中,分支会按照“最后一次提交时间”倒序排列,最新提交的分支排在最上面:
$ git branch --sort=-committerdate * master bugfix/payment feature/order feature/login如果去掉前面的减号,就变成正序排列:
git branch --sort=committerdate在实际使用中,我们绝大部分场景都希望“越新的分支排越前面”,所以-committerdate是更常用的写法。
配合-v参数,可以同时看到每个分支的最新提交哈希和提交信息:
git branch -v --sort=-committerdate输出示例:
* master a1b2c3d update readme bugfix/payment b2c3d4e fix payment bug feature/order c3d4e5f add order feature feature/login d4e5f6a add login feature这里的哈希和提交信息只是演示,真实执行时以你仓库中的结果为准。git branch -v --sort特别适合日常快速浏览,因为它一行就能看出分支最后改了什么。
3.2 更灵活的git for-each-ref
git branch --sort虽然简单,但输出字段固定,无法自由控制显示内容、时间格式和排序键。如果需要把分支和时间信息输出为表格,或者方便脚本处理,推荐使用git for-each-ref。
基础用法:
git for-each-ref --sort=-committerdate \ --format='%(committerdate:iso8601) | %(refname:short) | %(subject)' \ refs/heads其中:
refs/heads表示只遍历本地分支引用。%(committerdate:iso8601)是提交时间的 ISO8601 格式。%(refname:short)是去掉了refs/heads前缀的短分支名。%(subject)是该分支最新一次提交的第一行说明。
输出示例:
2025-01-06 10:23:45 +0800 | master | update readme 2025-01-06 10:12:30 +0800 | bugfix/payment | fix payment bug 2025-01-06 10:02:11 +0800 | feature/order | add order feature 2025-01-05 18:20:09 +0800 | feature/login | add login feature注意:上面示例里显示的时间是我为了演示补充的说明文字,实际执行时看到的时间取决于你仓库里的真实提交时间。
如果你想让人一眼看出分支多久没更新,可以把时间格式改成相对时间:
git for-each-ref --sort=-committerdate \ --format='%(committerdate:relative) | %(refname:short) | %(subject)' \ refs/heads输出示例:
2 hours ago | master | update readme 4 hours ago | bugfix/payment | fix payment bug 5 hours ago | feature/order | add order feature 2 days ago | feature/login | add login feature这种格式阅读起来非常直观,特别适合分支清理前的“体检”环节。
3.3 用git log自己拼接排序
如果不想记忆for-each-ref的格式化字段,也可以用git log配合 shell 循环手动拼接分支列表:
git branch | while read branch; do branch=$(echo "$branch" | sed 's/\* //') last_commit=$(git log -1 --format='%ci %s' "$branch" 2>/dev/null) echo "$branch $last_commit" done | sort -k2,2r这段脚本的大致逻辑是:
git branch列出所有本地分支,带*的是当前分支。- 用
sed去掉行首的*。 git log -1 --format='%ci %s'取该分支最后一次提交的提交时间和提交说明。- 最后用
sort -k2,2r按时间列倒序排序。
这个方案比较绕,而且每执行一个分支都要调用一次git log,分支很多时速度会慢。推荐只是在理解“排序本质”时参考,生产环境直接用git for-each-ref。
3.4 排序字段的选择
-committerdate并不是唯一可用的排序字段,Git 还支持按多个时间维度排序:
committerdate:提交者时间,最常见,适合判断分支最后的活跃时间。authordate:作者时间,适合追溯原作者是谁、原始提交时间是什么。creatordate:引用创建时间,适合查看分支创建的时间。
在绝大多数分支清理场景里,我们只需要关注committerdate。因为 rebase、cherry-pick、fast-forward 合并等操作都会更新提交者时间,用它判断分支“最后被更新”最准确。
如果你还想看一下当前 HEAD 指向哪个分支,可以用:
git for-each-ref --sort=-committerdate \ --format='%(HEAD) %(refname:short) | %(committerdate:relative)' \ refs/heads%(HEAD)如果以*开头,说明那个分支就是当前所在分支。
4. 完整实战:按最后提交日期管理本地分支
这一节把上面的命令串起来,做一次完整的实战演示,覆盖查看、筛选、清理准备整个流程。
4.1 查看默认分支列表
先回到前面创建的演示仓库:
cd demo-branch-sort不添加任何参数时,分支按名称排序列出:
$ git branch bugfix/payment feature/login feature/order * master这个列表在分支数量少的时候还可以接受,但信息量仅限于名字。
4.2 按提交日期倒序查看
执行:
git branch -v --sort=-committerdate输出大致为:
* master a1b2c3d update readme bugfix/payment b2c3d4e fix payment bug feature/order c3d4e5f add order feature feature/login d4e5f6a add login feature排序结果从最新提交到最旧提交排列。在真实项目中,这种视图能立刻发现哪些分支最近还在活跃,哪些已经停了很久。
4.3 用for-each-ref输出更完整的信息
如果觉得-v的信息还不够,可以用下面的命令,把时间精确到秒,并且显示提交人和提交说明:
git for-each-ref --sort=-committerdate \ --format='%(committerdate:iso8601) | %(refname:short) | %(authorname) | %(subject)' \ refs/heads在团队协作中,这种格式非常适合直接导出给同事确认:“这个分支是你负责的吗?最后提交时间是 X,还要继续保留吗?”
4.4 只看远程仓库分支
本地分支往往和远程分支对应。如果只想看远程分支,把refs/heads换成refs/remotes:
git for-each-ref --sort=-committerdate \ --format='%(committerdate:iso8601) | %(refname:short) | %(subject)' \ refs/remotes也可以直接使用简短写法:
git branch -r --sort=-committerdate这里有一点要注意:远程分支refname默认显示为origin/feature/login这样的形式。如果想去掉origin/前缀,可以用%(refname:lstrip=2)代替%(refname:short):
git for-each-ref --sort=-committerdate \ --format='%(committerdate:iso8601) | %(refname:lstrip=2) | %(subject)' \ refs/remoteslstrip=2的意思是去掉引用路径的前两段,也就是去掉refs/remotes和origin。
4.5 结合相对时间定位不活跃分支
分支清理前,最常问的问题是:“哪些分支超过 30 天没有更新了?”
先把所有分支按相对时间展示出来:
git for-each-ref --sort=-committerdate \ --format='%(committerdate:relative) | %(refname:short) | %(subject)' \ refs/heads输出中你会看到类似2 days ago、6 weeks ago、3 months ago这样的相对时间。相对时间是动态计算的,适合人工阅读,但它不是固定字段,拿去做精确的日期过滤时不方便。
如果需要精确过滤超过 30 天的分支,可以借助 unix 时间戳写一段小脚本:
git for-each-ref --sort=-committerdate \ --format='%(committerdate:unix) %(refname:short)' refs/heads | while read ts branch; do now=$(date +%s) diff=$(( (now - ts) / 86400 )) if [ "$diff" -gt 30 ]; then echo "$branch : $diff days" fi done这段脚本的思路:
%(committerdate:unix)输出 Unix 时间戳。date +%s获取当前时间戳。- 两者相减得到秒数,再除以 86400 换算成天数。
diff -gt 30表示超过 30 天未更新的分支会被打印出来。
注意:这段脚本需要 bash 或 Git Bash 环境,Windows 的 CMD/PowerShell 直接执行可能不兼容。
5. 常用组合:筛选、统计与脚本化
5.1 找出已经合并到主分支的旧分支
除了按时间排序,还可以结合--merged参数,只显示已经合并到指定分支的旧分支:
git branch --merged master --sort=-committerdate这种命令非常适合清理场景:已经合并到 master 的分支,如果没有特殊原因,通常就是可以删除的候选对象。再配合最后提交时间,就能把“已合并 + 长时间未更新”的分支一次性列出来。如果要列出未合并分支,把--merged改成--no-merged:
git branch --no-merged master --sort=-committerdate未合并分支即使旧,也可能包含尚未合入的功能或专属环境的修复,删除前必须二次确认。
5.2 统计每个分支的最后提交人和提交邮箱
团队维护时,如果想知道“某个分支最近是谁在提交”,用committeremail和authorname字段:
git for-each-ref --sort=-committerdate \ --format='%(committerdate:iso8601) | %(refname:short) | %(committeremail) | %(subject)' \ refs/heads输出后可以按提交人过滤,例如查看某个开发者的最近分支提交情况:
git for-each-ref --sort=-committerdate \ --format='%(committerdate:iso8601) | %(refname:short) | %(committeremail)' \ refs/heads | grep "zhangsan@example.com"这里grep只是简单的字符串过滤,实际上这种方式比在浏览器里翻 GitLab/GitHub 分支列表要快得多。
5.3 把命令固化成 Git 别名
每次输入一大段for-each-ref命令比较麻烦,可以把它配置成 Git 别名。
在终端执行:
git config --global alias.recent "for-each-ref --sort=-committerdate --format='%(committerdate:relative) | %(refname:short) | %(authorname) | %(subject)' refs/heads"配置好之后,直接执行:
git recent就能看到所有本地分支的最后提交时间和提交信息。
也可以配置查看远程分支的别名:
git config --global alias.remote-recent "for-each-ref --sort=-committerdate --format='%(committerdate:relative) | %(refname:lstrip=2) | %(authorname) | %(subject)' refs/remotes"日常使用git remote-recent即可。
在 Windows 环境如果使用 Git Bash,别名里的单引号不会出问题;如果你在 PowerShell