仓库里的分支和标签,大概是每个写代码的人每天都要碰、却又最容易含糊带过的两个东西。git branch和git tag看起来只是两条简单的命令,一个切来切去,一个打上标记,但真正在多人协作、版本发布、线上回滚这些场景里出问题的时候,八成都能追到这两块没管清楚。我自己就经历过把热修分支误删、把发布标签删掉又重打、在 detached HEAD 上提交完切走导致提交"消失"这些事,踩一次记一辈子。
这篇内容围绕git branch与git tag的操作展开,把这两个东西的底层定位、常用命令的参数取舍、合并与变基的边界、标签的创建推送与校验、以及误操作之后的补救路径,全部拆开讲一遍。核心解决的问题是:让分支和标签从"随手用的命令"变成一套有规则、可回溯、能放心交给团队用的版本管理习惯。适合刚上手 Git 不久、只会add/commit/push三件套的同学,也适合带团队、需要制定分支规范的人对照着看。下面所有命令都基于 Git 2.23 以上版本,switch和restore这两个新命令可以直接用。
1. 先弄清分支和标签到底在做什么
1.1 两者本质都是指针,区别在于会不会跟着提交往前走
Git 的整个数据结构里,提交对象是一张有向无环图,每个提交记录着父提交的哈希。而分支和标签,干的其实是同一件事——给某个提交起一个好记的名字。翻一下.git目录你就明白了:.git/refs/heads/下面放的是分支,.git/refs/tags/下面放的是标签,里面的内容就是一行四十位的提交哈希。仓库跑久了,这些散文件会被打包进.git/packed-refs,但逻辑不变。
真正的差别在于可变性。分支是一个会移动的指针,你在分支上提交一次,这个指针就自动前移到新提交;标签一旦创建,就死死钉在那个提交上,除非你手动删掉重打。用生活里的东西类比:分支像书签夹在书里,你继续往后读,书签就跟着往后挪;标签像在某一页盖了个骑缝章,那一页写死了,后面怎么翻都不影响它。
这个差异直接决定了它们的用途。分支是"工作线",承载的是"接下来要做什么";标签是"里程碑",承载的是"曾经做过什么"。把这两件事混在一起,用分支去标记版本,最后仓库里会堆出几十条release-1.0、release-1.1的长命分支,谁也不敢删;反过来用标签做并行开发,那就更离谱了,标签根本没法承载后续提交。
1.2 什么时候必须用分支,什么时候必须用标签
判断标准其实很简单,问自己一句:这个东西后面还会不会继续往前走?
会往前走的,用分支。功能开发、bug 修复、实验性重构、临时的技术验证,全都属于这一类。分支的价值在于隔离——你在feature/payment上把代码改烂了,main上一行都不受影响;等你改好了,再合回去。它是并行工作的基础设施,没有分支,一个团队只能排队改代码。
不会往前走的,用标签。版本发布、线上事故的锚点、某个重要节点需要长期留存的快照,用标签。标签的价值在于可追溯且不可漂移。三个月后线上出问题,你说"回滚到 2.4.1",运维执行git checkout v2.4.1拿到的代码,和你三个月前发布时打的标签指向的代码,是同一个哈希,一个字符都不差。换成用分支标记版本,谁敢保证那条分支上没人在发布后又推了一次提交。
我在实际项目里见过一种很典型的混用:某个团队每次发版就打一条release/20240315分支,然后在上面继续改小问题,改完再发。结果三条发布分支之间代码不一致,谁也说不清线上跑的到底是哪份。后来改成打附注标签,发布分支发完立刻删掉,仓库立刻清爽了,回滚也有了唯一确定的锚点。
1.3 长命分支越少越好,短命分支越多越好
顺带说一个设计层面的判断。一个健康的仓库,长命分支一般就那么两三条,比如main加一条develop,可能再加一条当前正在维护的旧版本分支。剩下的分支都应该是短命的,活几天、最多一两周,合完就删。
原因在于合并成本是随着分支存活时间指数级上升的。一条分支活一个月,主干上可能已经进了两百个提交,合并的时候冲突能堆满一屏,解决冲突的时间比写功能还长。我也试过让分支活很久,最后发现每次 rebase 都要花半小时处理冲突,纯属自找麻烦。
至于标签,数量上没有上限,历史标签越多越好,它们是版本演进的完整档案。仓库里保留十年份的标签都没问题,因为标签几乎不占空间,一个附注标签也就是几百字节。
2. 分支操作的核心细节与实操要点
2.1 创建与切换:switch 和 checkout 该怎么选
老一点的教程里全是git checkout -b,这个命令本身没错,但它是个"万能命令",既能切分支,又能恢复文件,还能检出提交,参数一多就容易搞混。Git 2.23 把职责拆开了:切分支用git switch,恢复工作区文件用git restore。这不是玩花活,而是减少误操作的实招——你想想,git checkout -- .是丢弃所有工作区改动,手一抖打成git checkout .在某些版本里行为还不一样,损失是不可逆的。
日常我用得最多的三条:
# 从当前 HEAD 创建并切到新分支 git switch -c feature/order-export # 从指定的远端分支创建本地跟踪分支 git switch -c hotfix/login-timeout origin/release/2.4 # 从某个标签创建分支,用于基于历史版本修问题 git switch -c hotfix/2.4.1 v2.4.1这里有个细节值得说一下:git switch -c的-c是 create 的缩写,等价于老写法git checkout -b。但switch有个明确的好处,它只接受分支名,你要是手滑传个文件名进去,它会直接报错而不是默默去切提交,安全不少。
查看分支这件事,很多人只会git branch,其实它的参数能挖出不少信息:
git branch # 本地分支 git branch -r # 远端分支 git branch -a # 全部 git branch -vv # 带上游跟踪信息和 ahead/behind 计数 git branch --merged # 已经合进当前分支的分支 git branch --no-merged # 还没合进来的git branch -vv是我最推荐养成习惯的一条。输出长这样:
* main a1b2c3d [origin/main: ahead 2, behind 1] 修订单号校验 feature/order-export 9f8e7d6 [origin/feature/order-export] 导出接口初版方括号里的ahead 2, behind 1直接告诉你本地比远端多两个提交、少一个提交。少了这个信息,你根本不知道自己的分支是不是已经落后于主干,push的时候才发现被拒绝,白耽误时间。--merged和--no-merged则是清理分支时的利器,先看一眼哪些已经合完了,心里有数再动手删。
注意:
git branch只是列本地分支,远端已经被别人删掉的分支,你本地仍然会显示为origin/xxx。这些是"过期的远端引用",需要定时清理。
2.2 清理过期远端引用与设置上游跟踪
远端分支被人删了,本地还留着一堆origin/feature/xxx的过期引用,时间长了git branch -a能刷出几十行,看着就烦。解决办法是带 prune 参数拉取:
git fetch --prune # 或者简写 git fetch -p这条命令会在拉取的同时,把远端已经不存在的分支引用从本地清掉。我习惯把它设成默认行为,省得每次手动加:
git config --global fetch.prune true再说上游跟踪。新建的本地分支默认没有 upstream,第一次git push会提示你加--set-upstream。这一步别偷懒,宁可多敲一次:
git push -u origin feature/order-export设好之后,后续git push、git pull、git status全都知道该和哪个远端分支对比。git status会告诉你"你的分支比 origin/xxx 领先 3 个提交",这个提示在日常协作里非常有用。如果分支已经建好了但忘了设,可以补:
git branch --set-upstream-to=origin/feature/order-export还有一种情况,别人的分支你想在本地跟一下,不用自己建,直接切同名分支 Git 会自动建立跟踪:
git switch feature/order-export前提是origin/feature/order-export存在且本地没有同名分支。这个"自动猜"的行为很方便,但要注意别在已有本地同名分支的情况下用,会直接切到本地那条,容易搞混。
2.3 合并策略:merge 与 rebase 的边界在哪
这是分歧最大的一块,我先给结论:主干上用 merge,个人分支整理用 rebase,公共分支绝对不 rebase。
git merge会生成一个合并提交,保留完整的分叉历史。默认情况下如果 Git 发现可以快进(目标分支是当前分支的直系后代),它会直接把指针前移,不产生合并提交。这在历史记录上看起来像是一条直线,但丢失了"这里曾经有一个功能分支"的信息。团队协作场景里,我更倾向强制生成合并提交:
git merge --no-ff feature/order-export--no-ff强制创建一个合并节点,好处是将来用git log --graph看历史,能清楚看到每个功能是从哪里进来、包含了哪几个提交。要回滚整个功能,直接 revert 那个合并提交就行,一次搞定。如果用快进,功能分支上的五个提交混进主干历史,想整体回滚就得 revert 五次,中间还可能被别的提交隔开。
git rebase则是把当前分支的提交"摘下来",重新接到目标分支的最新提交之后。效果是历史变成一条直线,没有分叉。它的适用场景是:你的个人分支落后主干太多,想在合并前先跟上最新的主干,把冲突在自己这边解决掉。
# 在 feature 分支上,把基线更新到最新 main git switch feature/order-export git rebase main如果中间有冲突,Git 会停下来让你解决,解决完git add,然后git rebase --continue。想放弃就git rebase --abort,回到 rebase 之前的状态。
注意:rebase 会重写提交哈希。一条已经推送到远端、别人可能基于它工作的分支,绝对不能 rebase。你 rebase 完强推上去,同事本地的历史就对不上了,下次 pull 会拉出一堆重复提交,非常难收拾。
如果确实在自己分支上 rebase 过并且需要推上去,用git push --force-with-lease而不是--force。前者会先检查远端有没有你不知道的新提交,有就拒绝推送;后者是无脑覆盖,能把你同事刚推的东西直接抹掉。我吃过一次--force的亏,虽然最后从 reflog 里捞回来了,但那半小时心跳加速的感觉不想要第二次。
还有一个常被忽略的操作是交互式 rebase,用来整理自己分支上的提交历史:
git rebase -i HEAD~3它会打开编辑器,列出最近三个提交,你可以把pick改成squash合并、改成reword改提交信息、改成drop删掉。合并前把"修一下"、"再改改"、"改回来了"这种垃圾提交压成一个干净的提交,是对 reviewer 的基本尊重。
2.4 删除分支与误删之后的补救
合完的分支要删,这是纪律。本地删:
git branch -d feature/order-export # 已合并才允许删 git branch -D feature/order-export # 强制删,未合并也删-d会检查这个分支是否已经合并进当前分支,没合并就拒绝,这是保护机制。-D是大写,强行删,用在"这个分支的实验彻底失败了"的场景。远端删:
git push origin --delete feature/order-export现在说误删。假设你手滑-D删了一条还有三个提交没合的分支,别慌,git reflog是救命的:
git reflog输出里会有一行类似a1b2c3d HEAD@{5}: checkout: moving from feature/order-export to main,找到那个提交哈希,直接建回来:
git branch feature/order-export a1b2c3dreflog 默认保留 90 天的可达记录,30 天的不可达记录,日常误操作基本都能捞回来。远端分支被误删也一样,只要本地还有那个提交,重新推上去就行:
git push origin a1b2c3d:refs/heads/feature/order-export这条命令的意思是"把哈希 a1b2c3d 推到远端的 feature/order-export 分支"。注意冒号右边要写完整的refs/heads/前缀,否则 Git 可能理解成别的意思。
提示:reflog 只存在于本地,不会随 clone 或 push 传给别人。所以指望别人帮你找回误删的提交是不现实的,出事只能在自己这台机器上捞。
3. 标签操作全流程
3.1 轻量标签和附注标签,务必要选后者
Git 支持两种标签。轻量标签就是git tag v1.0.0,本质上只是在refs/tags/下写一个文件,指向某个提交,别的什么都没有。附注标签是git tag -a v1.0.0 -m "发布说明",它会在 Git 对象库里创建一个完整的 tag 对象,记录打标签的人、邮箱、时间、说明文字,还能做 GPG 签名。
我的建议很明确:发布用的标签一律用附注标签,轻量标签只在临时本地标记时用。
理由有几条。第一,附注标签能回答"这个版本是谁、什么时候发的",这在追责和审计时是硬信息,轻量标签完全给不了。第二,git describe默认只认附注标签,你打一堆轻量标签,它一个都看不见。第三,附注标签可以被签名,git tag -s v1.0.0 -m "..."加-s参数,用 GPG 私钥签一下,别人git tag -v v1.0.0就能验证这个标签确实是你打的,没被篡改。发布到生产环境的版本,这个保障值得花那几秒钟。
一个规范的附注标签长这样:
git tag -a v2.4.1 -m "修复订单导出超时问题 - 导出任务改为异步队列 - 单次导出上限调整为 5 万条 - 修复分页参数越界导致的空结果"-m支持多行,用换行符分隔就行。这种写法让标签本身就带了一份简版变更日志,git show v2.4.1的时候直接能看到,不用再去翻提交记录。
命名上我推荐语义化版本v主版本.次版本.修订号,前缀加不加v团队内部统一就行,但要一致。见过一个仓库里同时有1.0.0、v1.1、release-1.2、V1.3.0四种风格,纯属给脚本添堵。
3.2 打标签、推送、删除与校验的完整动作
标签有一个极其容易踩的坑:git push默认不推标签。
很多人打完标签本地git tag看着有,以为完事了,结果 CI 拉代码时找不到标签,构建号对不上。正确的推送方式有两种:
# 推单个标签 git push origin v2.4.1 # 推所有本地标签(包括轻量的) git push origin --tags # 只推附注标签(推荐) git push origin --follow-tags--follow-tags是我最推荐的,它只推送附注标签,而且是在推送分支的同时顺带推,不会把本地那些乱七八糟的轻量标签一股脑甩到远端。如果团队里有人习惯打轻量标签,用--tags会把它们全推上去,远端标签列表很快就乱了。
把默认行为改掉也行:
git config --global push.followTags true删除标签要分本地和远端两步,很多人只做了第一步:
# 删本地 git tag -d v2.4.1 # 删远端 git push origin --delete v2.4.1 # 等价的旧写法 git push origin :refs/tags/v2.4.1只删本地不删远端,下次fetch一拉,远端那个标签又回来了,白忙一场。
校验这块有几个实用命令:
git tag # 列出全部标签 git tag -l "v2.*" # 通配符筛选,注意引号不能省 git show v2.4.1 # 查看标签详情和指向的提交 git show-ref --tags # 列出标签及其哈希 git ls-remote --tags origin # 直接查远端有哪些标签 git tag --contains a1b2c3d # 哪些标签包含这个提交 git describe --tags --long # 找到最近的标签并给出距离git tag -l "v2.*"这条命令里的引号千万别省。不写引号的话,shell 会先做文件名通配,当前目录下有v2.txt之类的文件时,命令就变成了git tag -l v2.txt,结果自然是空的,你会以为标签不存在。
git describe --tags --long的输出格式是v2.4.1-12-ga1b2c3d,意思是"距离 v2.4.1 这个标签还有 12 个提交,当前提交是 a1b2c3d"。这个字符串非常适合拿来做构建版本号,比单纯的2.4.1信息量大得多,能精确定位到某次构建对应的代码状态。CI 脚本里常见这种用法:
VERSION=$(git describe --tags --long --dirty)末尾的--dirty表示如果工作区有未提交改动,就在版本号后面加个-dirty后缀,能有效防止"从带脏改动的工作区构建出来的产物被当成正式版发布"这种情况。
3.3 检出标签与 detached HEAD 的处理
git checkout v2.4.1之后,Git 会提示你处于 "detached HEAD" 状态。这个提示吓过不少人,其实它没那么可怕,只是需要理解发生了什么。
正常情况下 HEAD 指向一个分支,分支指向一个提交。检出标签时,HEAD 绕过分支直接指向那个提交,所以叫"游离"。在这个状态下你当然可以看代码、编译、跑测试,但如果你在这里做了提交,这些提交不属于任何分支,一旦切走,它们就成了"孤儿提交",只能靠 reflog 找回来。
正确的处理方式分两种。
只读不改,看完就走:
git switch v2.4.1 # 看看代码,跑跑测试 git switch main需要在这个版本上改东西,就基于标签开一条新分支:
git switch -c hotfix/2.4.2 v2.4.1这样 HEAD 就挂到新分支上了,后续提交都有归属,不存在丢失风险。这是我认为最应该养成的习惯:看到 detached HEAD 的提示,第一反应就是开分支,而不是直接在上面改。
万一手快已经在游离状态下提交了才发现,也别急,直接原地建分支就能把提交接住:
git switch -c rescue/20240315此时新分支会指向你刚才的提交,一切正常。要是已经切走了,用git reflog找到那个提交哈希,再git branch rescue/xxx <hash>建回来。
还有一个细节:如果标签名和分支名重名,git checkout xxx会有歧义。Git 默认优先当分支处理,找不到才当标签。想明确指定标签,写全路径:
git switch --detach refs/tags/v2.4.14. 几个真实场景的完整演练
4.1 线上紧急修复:从标签拉分支再合回去
这是最典型的组合用法。假设线上跑的是v2.4.1,主干main已经推进到了v2.5.0的开发中期,现在线上爆出一个严重问题,必须基于 2.4.1 修。
第一步,从标签拉热修分支:
git fetch --tags git switch -c hotfix/2.4.2 v2.4.1注意先fetch --tags,确保本地标签是最新的。我遇到过同事本地标签停留在半年前,从旧标签拉分支,改完发现改的地方在新版本里早就重构过了,白干一天。
第二步,改代码、提交、打新标签:
git add . git commit -m "修复导出任务并发数超限" git tag -a v2.4.2 -m "紧急修复:导出任务并发控制" git push origin hotfix/2.4.2 git push origin v2.4.2第三步,把修复同步回主干。这一步很多人会漏,导致下次发版问题重现:
git switch main git cherry-pick <修复提交的哈希>用cherry-pick而不是merge,原因是hotfix/2.4.2这条分支是从旧版本拉出来的,直接合进main会把大量旧代码一起带回来,等于把主干退回了 2.4.1 的状态。cherry-pick只挑那一个提交,干净利落。
如果中间有冲突,解决完git add,再git cherry-pick --continue。整个链路走完,记得把热修分支删掉:
git branch -d hotfix/2.4.2 git push origin --delete hotfix/2.4.24.2 发布流水线里的标签动作
在自动构建里,标签往往是版本的唯一来源。一个典型的发布流程是这样:开发合完代码到main,打一个附注标签推上去,CI 监听到标签推送事件,自动构建、自动打包、自动上传,产物名里带上标签名。
这种设计的好处是版本号和代码状态严格绑定。你看到产物叫app-v2.5.0.tar.gz,就能确定它对应的是v2.5.0这个标签指向的那份代码,出问题回滚也简单——重新构建v2.4.1的产物即可。
CI 里的版本号提取一般这么写:
# 拿最近的标签 + 提交数 + 短哈希,保证唯一 VERSION=$(git describe --tags --always --long) # v2.5.0-7-ga1b2c3d # 如果只想要干净的版本号,且必须打在标签上才发布 if git describe --exact-match --tags HEAD > /dev/null 2>&1; then VERSION=$(git describe --exact-match --tags HEAD) else echo "当前提交没有标签,跳过发布" exit 0 fi--exact-match的意思是"HEAD 必须正好在一个标签上",不满足就退出来。这条判断能挡住"随手推个分支就触发发布"的情况。我在一个项目里就吃过这个亏,某次误把 feature 分支推到了远端,触发了发布流程,虽然产物没上线,但把版本号占掉了。
再补一个标签顺序的细节。发布时是先推代码还是先推标签?顺序上没强制要求,但实践上我建议先推标签,再推分支。因为标签指向的提交必须先在远端存在(其实不是必须,推送标签会连带把提交传上去),但如果分支先推了、标签推送失败,就会出现"代码已经在那了但没有版本标记"的空窗。先推标签,失败了还能重试,不会留下不一致。
4.3 分支治理:命名规范和合并前的检查清单
分支一多,命名混乱带来的沟通成本会非常大。我现在用的规范是带前缀的三段式:
| 前缀 | 用途 | 示例 | 生命周期 |
|---|---|---|---|
feature/ | 新功能开发 | feature/order-export | 数天到两周 |
fix/ | 普通缺陷修复 | fix/login-timeout | 数小时到数天 |
hotfix/ | 线上紧急修复 | hotfix/2.4.2-export | 数小时 |
release/ | 发布准备(可选) | release/2.5.0 | 一两天 |
chore/ | 构建、依赖、配置 | chore/upgrade-node | 数小时 |
这个规范的价值不在于好看,而在于可以用脚本批量处理。比如月底清理一遍长期不动的手工分支:
# 列出三个月没动过的分支(按提交日期) git for-each-ref --sort=committerdate \ --format='%(committerdate:short) %(refname:short) %(authorname)' \ refs/heads/ | head -20合并前的检查清单,我一般跑这三条:
# 1. 我的分支是不是落后主干太多 git fetch origin git branch -vv # 2. 我的分支包含哪些提交,有没有垃圾提交要压 git log --oneline main..HEAD # 3. 有没有把不该提交的文件带上去 git diff --stat main...HEAD第一条看 ahead/behind,如果落后太多,先 rebase 跟上;第二条看提交信息,把"改一下""测试"这种压掉;第三条看改动文件,防止误提交node_modules、.env、本地配置这类东西。
关于分支保护,如果用的是托管平台,主干分支应该开保护:禁止直接推送、必须走 Pull Request、必须通过 CI、必须至少一个人 approve。这些不是流程负担,而是把"误推到主干"这种低级事故从物理上杜绝。我自己有次半夜改代码忘了切分支,直接在main上提交并推了上去,幸好有保护规则拦住了。
5. 常见问题与排查速查
5.1 高频问题速查表
| 现象 | 大概率原因 | 解决方式 |
|---|---|---|
推标签报already exists | 远端已有同名标签 | 先删远端再推,或用新版本号 |
| 删了本地标签,fetch 后又出现 | 只删了本地没删远端 | git push origin --delete vX |
git tag -l "v2.*"返回空 | 少了引号被 shell 展开 | 加上引号重试 |
| 切分支后文件没变化 | 工作区有未提交改动被带过去 | 先stash或提交再切 |
| 分支误删,提交找不到 | 分支指针没了 | git reflog找到哈希重建 |
| detached HEAD 上的提交消失了 | 提交不属于任何分支 | git reflog+git branch恢复 |
git describe看不到标签 | 用的是轻量标签 | 改用-a打附注标签 |
| 分支名大小写和远端不一致 | 大小写不敏感的文件系统 | 用git branch -m重命名后重推 |
| rebase 后同事拉取出现重复提交 | 公共分支被改写了历史 | 让同事git reset --hard origin/xxx |
5.2 几个容易翻车的点,逐个拆开说
同名标签重复打。这是最常见的。假设v2.4.1已经推到远端,你本地删了重打,然后git push origin v2.4.1,会收到! [rejected] v2.4.1 (already exists)的报错。Git 默认拒绝覆盖已存在的标签,这个保护是对的——如果允许随便覆盖,标签的可追溯性就没了。确实需要重打,先删远端:
git push origin --delete v2.4.1 git push origin v2.4.1但我更建议的做法是换个版本号。已经发出去的标签就是公开承诺,覆盖它等于告诉所有人"昨天那个 2.4.1 不算数",配合方会疯掉。宁可发v2.4.2。
切分支时工作区改动被带走。这是新手最容易懵的。你在feature/a上改了一堆文件没提交,直接git switch main,Git 会把能带的改动一起带过去。如果改的文件在两个分支之间没有差异,切换是成功的,但改动跑到main上了;如果有差异,Git 会拒绝切换并报错。
正确做法是切换前先处理掉:
# 存起来,切过去处理完事再回来 git stash push -m "调试到一半" # 或者干脆提交一个临时提交(WIP 提交) git commit -am "WIP: 导出接口临时调试"stash更干净,但要注意stash是不分分支的,列表是全局的。多个分支同时 stash 的时候,用-m加说明,不然过两天你自己都分不清哪个是哪个。查看用git stash list,恢复用git stash pop(弹出并删除)或git stash apply(保留)。
rebase 之后上游分支对不上。你在自己分支上 rebase 了主干,然后git push,被拒绝。这时候用--force-with-lease:
git push --force-with-lease origin feature/order-export如果还是被拒绝,说明远端有你没拉下来的提交,先git fetch看看是什么,别硬来。--force-with-lease的检查逻辑是比较远端的当前值和你的本地记录值,如果你fetch过,本地记录就更新了,保护就失效了。所以要在 rebase 之后、fetch 之前推,顺序很重要。
大文件误入历史,标签和分支都受影响。这是最麻烦的一类。有人提交了一个几百兆的二进制文件,推上去之后仓库体积爆炸,所有人都要拉这份历史。删掉文件再提交是没用的,历史里还在。这时候要改写历史,用git filter-repo之类的工具(旧版的filter-branch已经废弃了)。改写之后所有提交哈希全变,所有分支和标签都要重新推。这种操作我建议直接开个新仓库重建,或者找有经验的人来做,风险太高。
5.3 三条我踩过坑之后固化的习惯
第一条,推送前必跑git branch -vv。这一步能看出两件事:当前分支有没有设置上游,以及本地和远端差了多少。大部分"推不上去"和"拉不下来"的问题,在这一步就能发现。
第二条,打标签前必跑git status和git fetch --tags。前者确认工作区干净(避免从一个带改动的工作区打标签),后者确认标签列表是最新的。这两条加起来不到十秒钟,能挡掉一大半发布事故。
第三条,任何要删除的东西,先确认它是不是已经在远端存在,以及本地是否有备份。删分支、删标签、--force推送之前,我现在的固定动作是先git branch -a和git tag -l看一遍,心里确认"这个东西远端有,删了能找回来"。找不到的就先建个备份分支backup/xxx放着,确认没问题了再删。
这三条听起来像是废话,但真到了手忙脚乱的时候,能想起这三条的人不多。我现在把前两条直接写进了 shell 的git push别名里,每次推送自动先跑一遍检查,用了几次就回不去了。