告别git pull:某电商系统Rebase工作流与Commit规范实战

我记得刚接手那个日订单量 30 万 + 的电商订单系统时,团队 12 个人都在同一个 develop 分支上干活。每天早上打开 Git 日志,就像看一锅乱炖:Merge branch 'develop' of xxx into developfix bugupdate code…… 这种提交信息占了 70% 以上。更要命的是,每次上线前合并 release 分支,总会冒出一堆没意义的合并提交,有次大促前紧急修复支付回调接口,我花了整整 40 分钟才从一堆 Merge 提交里翻出真正的改动点。

后来我带着团队把 git pull 换成了 git pull --rebase,再配合严格的 Commit 规范,情况彻底变了。这里先解释下为什么不用 git pull:默认情况下 git pull 等于 git fetch + git merge,每次拉代码都会生成一个合并提交。假设你在本地改了 2 个文件,远程也有 1 个提交,执行 git pull 后日志里就会多一个 Merge 节点。团队人一多,日志就会变成一张网,根本没法追溯历史。而 git pull --rebase 是先把你本地的提交“暂存”,拉取远程最新代码,再把你的提交“重新应用”上去,整个过程没有多余的合并节点,日志是一条干净的直线。

我们当时定的规则很简单:本地提交没推送到远程前,拉代码必须用 git pull --rebase;已经推送到远程的提交,绝对不允许再 rebase(避免改写公共历史)。为了降低大家的记忆成本,我在项目根目录加了个 .gitconfig 示例,让新人 clone 代码后直接复制就能用:

# 项目根目录下的 .gitconfig 示例,新人 clone 后执行 git config --local include.path ../.gitconfig 即可生效 [alias] # 简化 rebase 拉取命令 up = pull --rebase --autostash # 查看简洁日志,只显示提交哈希、作者、时间和提交信息 lg = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit [pull] # 全局默认使用 rebase 方式拉取,避免新人忘记加 --rebase rebase = true

这里提一下 --autostash 参数,我之前遇到过好几次:本地改了半天的代码还没提交,突然要拉远程代码,默认 git pull --rebase 会报错说工作区不干净。加上 --autostash 后,Git 会自动把未提交的改动暂存,拉完代码再恢复,省得手动 git stashgit stash pop,我们团队用了这个参数后,这类小问题直接降到了 0。

光有 rebase 还不够,提交信息乱的问题得解决。我们参考了 Conventional Commits 规范,但做了简化,只要求必须带 feat(新功能)、fix(修复)、refactor(重构)、test(测试)四个前缀,后面跟冒号和简短描述。比如支付模块的 bug 修复,必须写成 fix: 修复支付宝回调签名验证失败问题,而不是 fix bug。为了让大家不用记格式,我写了个简单的 commit-msg 钩子,放在 .git/hooks/ 目录下,不符合格式的直接拒绝提交:

#!/bin/sh # .git/hooks/commit-msg # 提交信息正则:必须以 feat:|fix:|refactor:|test: 开头,后面至少跟 10 个字符 commit_msg=$(cat "$1") if ! echo "$commit_msg" | grep -qE '^(feat|fix|refactor|test): .{10,}$'; then echo "错误:提交信息格式不符合规范!" echo "正确格式示例:feat: 新增订单导出Excel功能" echo "允许的前缀:feat(新功能)、fix(修复)、refactor(重构)、test(测试)" exit 1 fi

这个钩子上线后,前两周还有人提交被拒,第三周开始就没人踩雷了。后来有次线上订单状态同步延迟,我直接用 git log --grep="fix: 订单状态" 搜,10 秒就找到了两周前修复同步逻辑的提交,要是放在以前,我得翻至少 20 分钟的日志。

这里要提醒下,rebase 不是万能的。我们团队有个新人,把自己已经推送到远程的 3 个提交 rebase 了,导致其他同事拉代码时冲突了一大片。后来我专门在周会上强调:只有本地未推送的提交才能 rebase,已经推给别人的代码,绝对不能改写历史。如果本地提交太多太乱,可以用 git rebase -i HEAD~3 合并成 1 个提交再推送,我们当时规定单个功能最多保留 2 个提交,这样日志既干净,又不会丢失关键信息。

Git 2.45.0新特性实战:Partial Clone与Sparse Checkout优化Monorepo构建速度

去年我们公司把 6 个独立的微服务仓库合并成了一个 Monorepo,总代码量 120 万行,.git 目录大小直接飙到了 8.7GB。之前用单个仓库时,新人 clone 一次要等 25 分钟,CI 流水线里每次拉代码也要 18 分钟,严重拖慢了发版速度。后来我研究了 Git 2.45.0(2024 年 5 月发布的版本,每 3 个月更新一次,这个版本对部分克隆做了性能优化)的新特性,用 Partial Clone(部分克隆)和 Sparse Checkout(稀疏检出)把 clone 时间压到了 3 分钟以内,CI 拉代码时间也降到了 2 分钟。

先讲 Partial Clone。默认 git clone 会把远程仓库的所有对象(提交、树、文件内容)都下载到本地,哪怕你只需要其中 1 个目录的代码。而 Partial Clone 允许你只下载必要的对象,其他对象等用到的时候再按需拉取。我们当时仓库里有很多历史版本的二进制文件(比如之前的设计稿、旧版本的 jar 包),占了 6GB 的空间,这些文件新人根本用不上。我配置了部分克隆后,第一次 clone 只下载了最近 3 个月的提交和当前分支的文件,历史大文件直接跳过。

具体配置很简单,clone 的时候加 --filter 参数就行。我们当时用的是 blob:none 过滤规则,意思是只下载提交和树对象,文件内容(blob)等用到的时候再拉。命令如下:

# 部分克隆 Monorepo,只下载必要对象,跳过历史大文件 git clone --filter=blob:none --no-checkout https://git.example.com/monorepo.git cd monorepo # 启用稀疏检出,只检出订单模块和支付模块的代码 git sparse-checkout set src/order src/payment # 检出当前分支 git checkout develop

这里 --no-checkout 是先不检出文件,等配置好稀疏检出再检,避免先下载所有文件再过滤浪费时间。我们实测下来,用这个命令 clone 后,.git 目录直接从 8.7GB 降到了 1.2GB,clone 时间从 25 分钟变成了 2 分 40 秒。

再说说 Sparse Checkout。我们 Monorepo 里有 12 个模块,每个开发同学只负责 1-2 个模块,之前 clone 完所有代码,本地磁盘占了 12GB,很多代码根本不会碰。用稀疏检出后,大家只拉自己负责的模块,磁盘占用直接降到了 2GB 左右。Git 2.45.0 对稀疏检出的性能做了优化,之前切换模块要 10 秒,现在只要 2 秒。我平时负责订单和支付模块,配置稀疏检出的完整流程是这样的:

# 初始化稀疏检出 git sparse-checkout init --cone # 查看当前稀疏检出规则 git sparse-checkout list # 输出: # src/order # src/payment # 如果需要临时查看物流模块的代码,添加规则 git sparse-checkout add src/logistics # 查看更新后的规则 git sparse-checkout list # 输出: # src/order # src/payment # src/logistics # 不需要物流模块了,移除规则 git sparse-checkout remove src/logistics

这里 --cone 模式是 Git 2.45.0 推荐的模式,比之前的模式快 30% 左右,它只匹配目录层级,不会递归匹配所有文件,适合 Monorepo 的场景。我之前试过不用 --cone 模式,配置复杂不说,切换模块时还会卡 5 秒以上,换成 --cone 后这个问题就消失了。

有个实际的问题我印象很深:之前 CI 流水线里每次构建都要 git clone 完整仓库,18 分钟里 12 分钟都在拉代码。后来我把 CI 脚本改成了部分克隆 + 稀疏检出,只拉构建需要的模块,比如前端构建只拉 src/web 目录,后端构建只拉 src/ordersrc/payment。修改后的 CI 脚本片段如下:

# CI 流水线中的拉代码步骤 echo "开始拉取 Monorepo 代码..." git clone --filter=blob:none --no-checkout --depth=1 https://git.example.com/monorepo.git repo cd repo # 只检出后端构建需要的模块 git sparse-checkout set src/order src/payment src/common git checkout develop echo "代码拉取完成,耗时:$SECONDS 秒"

加了 --depth=1 做浅克隆,只拉最近 1 次提交,CI 拉代码时间直接降到了 1 分 50 秒,整个构建流程从 25 分钟缩短到了 10 分钟以内。我们当时统计过,每天 CI 流水线运行 40 次,每天节省的时间加起来有 6 个小时,相当于多了一个人力。

不过要注意,Partial Clone 有个小问题:如果你需要查看很久以前的历史提交,或者需要用到被过滤掉的大文件,Git 会临时去远程拉取,可能会慢 1-2 秒。但我们团队几乎没人需要看 3 个月前的大文件历史,所以这个代价完全可以接受。Git 2.45.0 还优化了按需拉取的性能,之前拉一个 100MB 的历史文件要 8 秒,现在只要 3 秒,基本感知不到延迟。

Worktree与Refs Backend选型:多版本并行开发的效率提升对比

去年双 11 大促前,我们同时维护着 3 个版本:正在上线的 release/v3.2、修复线上 bug 的 hotfix/v3.1.5、开发新功能的 develop。之前每次切换分支都要 git stash 保存本地改动,切换完再恢复,有次我在 develop 分支改了半天的订单逻辑,切换到 hotfix 分支时忘了 stash,结果改动的代码被覆盖了,我花了 1 个多小时才重新写出来。后来我开始用 git worktree,同一个仓库可以同时打开多个工作目录,每个目录对应一个分支,再也不用频繁切换了。

Git Worktree 的原理很简单:它会在你的仓库目录下创建多个工作树,共享同一个 .git 目录里的对象和引用。比如你可以在 ~/project/monorepodevelop 分支的代码,在 ~/project/monorepo-hotfixhotfix 分支的代码,两个目录完全独立,改其中一个不会影响另一个。我现在的习惯是,同时维护 2 个以上分支时,一定会用 worktree,而不是切换分支。

创建 worktree 的命令非常直接,我平时用的命令是这样的:

# 进入主仓库目录 cd ~/project/monorepo # 创建 hotfix 分支的工作树,目录放在 monorepo-hotfix 下 git worktree add ../monorepo-hotfix hotfix/v3.1.5 # 进入 hotfix 工作树,直接就能改代码,不需要切换分支 cd ../monorepo-hotfix # 查看当前工作树列表 git worktree list # 输出: # /home/user/project/monorepo abc1234 [develop] # /home/user/project/monorepo-hotfix def5678 [hotfix/v3.1.5]

上次线上支付接口突然报错,QPS 从 500 掉到了 100,我直接打开 hotfix 工作树,10 秒就开始改代码,改完提交、构建、上线,整个过程只用了 15 分钟。要是放在以前,我得先 stash develop 的改动,切换分支,改完再切回来恢复,至少多花 5 分钟,而这 5 分钟里可能就有几千个订单支付失败。

worktree 用久了,我发现另一个问题:我们的 Monorepo 有 1200 多个分支,每次执行 git branch 都要等 3 秒,执行 git push 有时候会卡 2 秒。查了下 Git 2.45.0 的文档,发现是默认的引用存储后端(loose 模式)在大规模引用场景下性能不好——每个引用都是一个单独的文件,1200 个分支就有 1200 个文件,读写都慢。Git 2.45.0 推荐用新的 reftable 后端,把所有引用存到一个紧凑的二进制文件里,读写速度能提升 5 倍以上。

我先在测试环境试了 reftable 后端,迁移命令很简单,不需要改任何工作流:

# 查看当前引用后端类型 git rev-parse --show-ref-format # 输出:files (默认的传统后端) # 迁移到 reftable 后端,Git 2.45.0 及以上支持 git reflog expire --expire=now --all git gc --prune=now git config --local core.reftable true # 再次查看后端类型 git rev-parse --show-ref-format # 输出:reftable # 验证迁移后功能正常,执行一次分支查看 git branch | wc -l # 输出:1247 (分支数量没变,之前要 3 秒,现在 0.5 秒就出来了)

迁移后我测了几个常用命令的速度:之前 git branch 要 3 秒,现在 0.5 秒;git push 之前卡 2 秒,现在几乎秒响应;git fetch 拉 100 个新分支,之前要 8 秒,现在 2 秒。我们团队 12 个人都迁移后,每天节省的等待时间加起来至少有 30 分钟。不过要注意,reftable 后端是 Git 2.45.0 才稳定的,之前的版本可能有兼容性问题,所以一定要先升级到 2.45.0 及以上再用。

我还做过一个对比测试:同时维护 3 个分支(develop、hotfix、release),用传统切换分支的方式,每次切换平均要 12 秒(包括 stash、切换、恢复),一天切换 20 次就是 4 分钟;用 worktree 的话,打开一次工作树要 5 秒,之后切换目录只要 1 秒,一天切换 20 次总共 25 秒。再加上 reftable 后端节省的 1 秒/命令,一天下来能多 5 分钟写代码。

有个小细节要提醒:worktree 创建的工作树,用完记得删掉,不然会占用磁盘空间。删除命令很简单:

# 进入主仓库目录 cd ~/project/monorepo # 删除 hotfix 工作树,同时删除对应的分支(如果不需要保留分支的话) git worktree remove ../monorepo-hotfix # 查看工作树列表,确认已删除 git worktree list # 输出:/home/user/project/monorepo abc1234 [develop]

我们团队之前有个同学创建了 5 个 worktree 忘了删,占了 10GB 磁盘,后来我写了个定时脚本,每周检查一次未使用的 worktree,自动发提醒,这个问题就解决了。

现在我们的标准配置是:Git 2.45.0 + reftable 后端 + worktree 管理多分支。之前同时维护 3 个版本要手忙脚乱,现在每个版本对应一个工作树,切换目录就行,再也没出现过代码覆盖的问题。而且 reftable 后端让引用操作快了很多,尤其是 CI 流水线里拉分支、推代码的步骤,整体速度提升了 40% 左右。

4. 线上事故复盘:误删Release分支后的数据恢复与reflog机制深挖

那是一个周五下午五点半,我正准备合上电脑去接孩子,钉钉突然炸了。运维那边急吼吼地喊,说生产环境发版失败,Jenkins 报找不到 release/2024.05 分支。我心里咯噔一下,当时刚给一个新来的后端演示完 git push origin --delete release/2024.05 清理旧分支的操作,估计是他手滑把正在用的给干掉了。

当时那个 release/2024.05 分支上挂着大概 1.2 万个提交,是我们电商系统核心交易域的发布分支,QPS 峰值能到 8000+。如果找不回来,意味着我们要从 master 重新拉代码,还得手动 cherry-pick 过去两周的 40 多个 hotfix,这基本上就得通宵了。

我第一反应就是 git reflog。很多刚入行的同学以为 git log 就是看历史的全部,其实 reflog (Reference Log) 才是 Git 的“黑匣子”。它记录的是本地仓库里 HEAD 指针每一次移动的历史。哪怕你 reset --hard 了,或者删除了分支,只要你的本地仓库没被物理删除,那些 commit 对象还在对象数据库里,只是暂时变成了“悬空”状态。

我立马切到本地之前拉过那个分支的机器上。因为 Git 2.45.0 现在对引用存储做了优化,用的是 Reftable 后端,查找历史引用的速度其实比以前快了不少,但我当时没空管那个,直接就是一顿操作。

首先,我得找到那个分支最后指向的那个 commit hash。

# 查看 HEAD 的移动记录,找那个被删除分支的最后一次操作 $ git reflog # 输出大概长这样 # 8f4e2a1 (HEAD -> master) HEAD@{0}: checkout: moving from release/2024.05 to master # 3b9c7d2 HEAD@{1}: commit: fix: 修复支付回调幂等性问题 # 5a1b3c4 HEAD@{2}: commit: feat: 新增库存预占接口 # ... # 6d8e9f0 HEAD@{15}: checkout: moving from master to release/2024.05

HEAD@{1} 往前推,我找到了那个分支最后一次提交的 SHA-1 值 3b9c7d2

接下来就是关键的恢复步骤。我不能直接 git checkout 3b9c7d2,那样你是在“分离头指针”状态,不是分支。我需要重建那个分支指针。

# 基于那个 commit 重新创建 release/2024.05 分支 $ git checkout -b release/2024.05 3b9c7d2 # 推送到远程,强制覆盖,因为远程已经没了 $ git push origin release/2024.05

但这还没完。我得确认那个新来的同事是不是在他本地也删了。果不其然,他本地也没了。我让他执行了同样的 reflog 操作,但他发现他的记录里没有那个 3b9c7d2,因为他当时是在 master 上执行删除操作的,并没有在那个分支上提交过新东西,所以他的 HEAD 历史里没有那个分支的详细移动轨迹。

这时候就得用另一个神器 git fsck(文件系统检查)。这个命令能扫描本地数据库中所有未被任何分支或标签引用的对象。

# 查找所有悬空的对象 $ git fsck --lost-found # 输出 # Checking object directories: 100% (256/256), done. # dangling commit 3b9c7d2... # dangling commit 5a1b3c4...

虽然 fsck 能找到,但在 1.2 万个提交的大仓库里,靠这个找简直是大海捞针。这也是为什么我强烈建议大家在做任何破坏性操作前,先建一个备份分支,或者至少确认 reflog 里有记录。

为什么这事儿这么严重?因为我们那个仓库用了 Partial Clone (部分克隆) 技术。当时为了优化 CI 流水线速度,我们配置了 git clone --filter=blob:none,本地只拉取了最近 7 天的 commit 历史。如果那个分支的最后一次提交超过了 7 天,且没有被其他分支引用,那么在 CI 的浅克隆环境里,那个 commit 对象可能压根就没下载下来,这就真的只能去服务器备份里找了。

那次事故后,我给团队定了个规矩:凡是 release/* 分支,删除权限收归运维,且删除前必须先在 Jira 里关联归档任务。另外,我们利用了 Git 2.45.0 新支持的 Built-in FSMonitor,配合 core.fsmonitor 配置,虽然主要是为了加速 git status(从原来的 800ms 降到 120ms 左右),但也顺带监控了 .git/refs 目录的变动,算是多了一层保险。

5. AI时代Git进阶:利用AI辅助生成Commit Message与冲突解决策略

前阵子我们团队接手了一个遗留的单体巨石应用(Monorepo),里面塞了订单、商品、用户三个大模块,代码量大概 200 多万行。每次 git log 查看历史,那提交信息简直惨不忍睹,什么 "fix bug"、"update"、"改好了" 这种我见得太多了。这种信息对后续排查问题毫无帮助,尤其是当我们需要回溯为什么某个接口突然变慢的时候。

我现在的做法是不再硬憋那几句描述,而是直接把改动扔给 AI。我们现在用的是基于 Git 2.45.0 环境,配合 Copilot 或者本地的 Ollama 跑个小型模型来做这个事。

具体的操作流程是这样的:我改完代码,暂存之后,不直接写 git commit -m "xxx"。我会先跑一个脚本,这个脚本会提取我的暂存区改动,然后发送给 AI 模型,让它生成遵循 Conventional Commits 规范的 Message。

比如我刚改完一个库存扣减的逻辑,防止超卖:

# 查看暂存区改动 $ git diff --cached # 输出 # diff --git a/service/stock.go b/service/stock.go # index 83db7f5..f9c2d8e 100644 # --- a/service/stock.go # +++ b/service/stock.go # @@ -45,6 +45,10 @@ func DeductStock(skuId string, num int) error { # // 原有逻辑 # rows, err := db.Exec("UPDATE stock SET count = count - ? WHERE sku_id = ? AND count >= ?", num, skuId, num) # + # + // 新增:增加分布式锁防止并发超卖 # + lockKey := fmt.Sprintf("stock_lock_%s", skuId) # + defer redisClient.Unlock(lockKey) # }

以前我得自己想怎么描述,现在我会用下面这个脚本(或者直接在 IDE 里用插件):

#!/bin/bash # 文件名: gen_commit_msg.sh # 获取 diff 内容 DIFF=$(git diff --cached) # 如果没有改动,直接退出 if [ -z "$DIFF" ]; then echo "No staged changes found." exit 1 fi # 构造 Prompt,让 AI 生成符合规范的 commit message # 这里假设你配置好了本地 AI 接口,比如 Ollama PROMPT="根据以下 Git Diff 内容,生成一个符合 Conventional Commits 规范的 Commit Message。只需要输出 Message 内容,不要包含任何其他解释。\n\nDiff:\n$DIFF" # 调用 AI (这里以 curl 调用 Ollama 为例,实际可能用 GitHub Copilot CLI 或其他工具) # AI 返回的结果大概是:fix(stock): 增加分布式锁防止高并发下库存超卖 MSG=$(curl -s http://localhost:11434/api/generate -d "{\"model\": \"codellama\", \"prompt\": \"$PROMPT\", \"stream\": false\"}" | jq -r '.response') # 提交 git commit -m "$MSG"

运行这个脚本后,AI 给了我一个 fix(stock): 增加分布式锁防止高并发下库存超卖。这比我手写的好多了,因为它能精准地概括出我改动的意图,而不仅仅是描述代码动作。

再说说冲突解决。以前遇到 git merge 冲突,特别是那种改了同一个文件几十行的情况,我得一行行对着看。现在 AI 能帮上大忙,特别是针对 Reftable 后端带来的大规模引用场景,虽然冲突不多,但一旦涉及跨版本的合并就很头疼。

有一次我们要把 feature/new-payment 合并回 master。那个支付模块的文件 payment_processor.java 被两个人同时大改。

$ git merge feature/new-payment Auto-merging payment_processor.java CONFLICT (content): Merge conflict in payment_processor.java Automatic merge failed; fix conflicts and then commit the result.

打开文件,冲突标记长这样:

<<<<<<< HEAD public void handleCallback(String orderId, BigDecimal amount) { // 旧逻辑:直接入库 log.info("Received payment: {}", orderId); db.save(orderId, amount); } ======= public void handleCallback(String orderId, BigDecimal amount) { // 新逻辑:调用风控 if (!riskControl.check(orderId, amount)) { throw new RiskException("Payment blocked"); } log.info("Received payment: {}", orderId); db.save(orderId, amount); } >>>>>>> feature/new-payment

以前我得手动判断是保留风控还是保留日志。现在我直接把这段冲突代码连同上下文一起丢给 AI,提示词是:“这段代码在合并时产生了冲突,HEAD 版本是增加日志,feature 版本是增加风控检查。请帮我合并,确保既保留风控逻辑,也保留日志,并优化代码结构。”

AI 给出的结果通常是这样的:

public void handleCallback(String orderId, BigDecimal amount) { // 集成风控检查与日志记录 if (!riskControl.check(orderId, amount)) { throw new RiskException("Payment blocked"); } log.info("Payment received and passed risk check: {}", orderId); db.save(orderId, amount); }

这种处理方式让我省去了很多上下文切换的时间。不过有一点要注意,AI 合并后的代码,我都会再跑一遍单元测试。毕竟 Git 的合并算法是基于文本行的,而 AI 是基于语义理解的。在 2024 年到 2026 年这个趋势里,我觉得 AI 辅助开发集成 会越来越深,以后可能 git merge 命令直接内置一个 --ai-resolve 参数,自动调用模型去解决这种非极端的冲突。

但我也发现,AI 生成的 Commit Message 有时候太“聪明”了,它会过度概括。比如我明明只是改了个配置文件的端口号,它给我生成个 refactor(config): 优化网络服务配置以提升可维护性。这种时候我就得手动改回去,别让后来的人看了摸不着头脑。所以,AI 是辅助,最终的审核权一定还是在人手里

站长实战手记

一次差点让我背锅的 Monorepo 迁移

去年我接手了一个老牌电商后台的重构,业务复杂到离谱,十几个子系统硬塞在一个仓库里。当时为了提升构建速度,我脑子一热,直接上了 Git 2.45.0 的 Partial Clone 配合 Sparse Checkout

刚开始确实爽,克隆代码从原来的十几分钟变成了几十秒。但问题很快就来了。团队里有个新同事,他的本地环境配置有点问题,在执行 git fetch 的时候因为只拉取了部分对象,导致他在做一个跨模块的大特性时,本地漏掉了一个公共库的提交。结果就是,他在本地跑得好好的,一提交到 CI 就报错,而且报错信息极其诡异,不是代码问题,而是 Git 提示找不到某个 blob

我当时排查了半天才发现,是因为他本地的 promisor remote 配置没生效,导致部分历史对象缺失。最后我不得不让他强制 git fetch --unshallow 把整个仓库拉下来,才解决了这个问题。

我的真实看法

经过这次折腾,我对这些高级特性有了更清醒的认识:

* Sparse Checkout 真的香,但有门槛。 如果你的团队还在用老版本的 Git(低于 2.25),或者大家对 Git 内部原理不太熟,千万别强推。否则你不是在写代码,你是在当 Git 客服。

* Monorepo 不是银弹。 很多团队其实没到那个量级,为了跟风搞 Monorepo 反而增加了工具链的负担。

* Rebase 虽好,别在公共分支用。 我现在的策略是:个人开发分支随便 rebase,但一旦涉及多人协作的分支,老老实实 merge,保持历史图的完整性比那点整洁更重要。

给读者的真心话

Git 这东西,别光看文档里那些酷炫的命令。我建议你先在本地搭个测试仓库,把 reflogworktree 这些命令玩坏几次。特别是 git reflog,它真的是你的后悔药,但前提是你要知道怎么用它去“穿越”回那个还没搞砸的时间点。别等到代码丢了才去查怎么恢复,那时候心态崩了,手一抖可能就彻底没了。