Tiana随笔

Git 分支工作流的基本习惯

· Python

标签:#工具 #工作流 #工程

Git 分支工作流的基本习惯

这个博客项目到现在都没建 git 仓库,但分支工作流我在上一家用得很多,踩过坑。这篇记分支命名、提交粒度、rebase 和 merge 怎么选、合并前的四个动作,以及怎么避免直接 push 到 main。

我这个项目至今没建 git 仓库

说出来有点不好意思:部署上线的博客本身,没有 git。我没有用版本控制管理这个项目。原因后来单独写了一篇,这里不展开。

但分支工作流这事儿我以前在上一家公司用得很多,踩过坑,记一份常用做法。

分支存在的意义

Git 的分支本质是「一次从某个提交开始的独立开发线」。它的最大价值是把「还没确认要不要」的变化隔离出去,等稳定了再回到主分支。

我以前同时维护一段电商后台的代码。一个新人加了搜索功能,结果搞坏了一个老接口,因为他在 main 上直接改。我接手后让他回退到 main,新开 feature/search 分支,等搜索 PR 上来 review 之后再合并。

这就是分支的价值。分支不是仪式感,而是给「未完成的变化」一个抽屉。

分支命名前缀看团队习惯

常见的前缀有 feature/、fix/、refactor/、docs/、chore/。我个人偏好前缀能看出意图,不然后面一排 dev、test、tmp 出来,鬼知道是干嘛的。

feature/post-search
feature/login-remember-me
fix/admin-403-after-deploy
refactor/api-service-decomposition
docs/deploy-readme

仓库大起来之后,靠前缀做筛选非常省事。前缀写错了就 git branch -m 改名。

提交要小而完整

我第一次用 git 时被前辈敲打过一个习惯:「一次提交表达一个完整的变化」。这个词的关键是「完整」两字,单文件改一行不算完整,得是一个能被看懂意图的小单元。

举个反例。一个 PR 里既改登录逻辑又加注册接口还顺手换了图标,commit message 写着「update」。这种 commit 在 git log --oneline 里全是一条,肉眼找不到任何线索。

我后来养成的习惯是,写完一段改完一段、跑一遍相关测试、确认 OK 再 commit。这样 git log 才是真正的项目历史,而不是个仓库备份。

提交信息别敷衍

git commit -m "update" 是真正的反面教材。没有时间信息、没有改动范围、读起来跟没写一样。我现在每条提交都带一行意图描述,前面再加一段简短的「为什么这样改」,最后让 git 自己 diff 出来的事实说话。

refactor: 把权限检查从路由里抽到 service

原因是路由函数越写越长,每个接口都要重复 try/except。
新增 require_admin 函数统一处理,复用到管理端 17 个接口。

提交信息写好一点,git blame 出来的每一行都能帮上忙。我自己有几个老项目,都靠 git log -S "关键词" 找当时的决定。

rebase vs merge 的取舍

团队里吵过最多的就是 rebase 和 merge。我观察到的实际情况是:主干要保持线性,个人分支可以用 merge。

意思是,feature 分支本地开发时,我先 git rebase main 把自己分支的提交压在 main 之上,避免后续 PR 里有「merge main」这种混线。合 PR 时走 squash merge,把整段 feature 压成一个 commit 进 main,避免主分支历史长得没法看。

git checkout feature/post-search
git rebase main

rebase 时如果冲突,不会自动 stash,我得手动解决。这种时候我直接 git rebase --abort 重来,因为强行解决冲突往往引入更难复盘的 bug。

合并前的四个动作

合任何分支前,我必做四件事:

  • 看 git diff main 检查这次合并会带什么
  • 跑一遍 main 上最新的测试,确认基线干净
  • 跑一遍这条分支上的测试,确保要合并的代码自己稳
  • 看一眼 git log 这次分支的提交历史,确认没有临时提交的 debug 痕迹

第四点经常被忽略。我见过有人写代码时加了 print('debug'),最后忘记删,提交一起合并进了生产,事故之后查日志看得脑壳疼。

一个常用的工作钩子

我此前用过一条 pre-push 钩子,禁止把带 print( 或 console.log( 的提交直接推到主分支:

#!/usr/bin/env bash
# .git/hooks/pre-push
set -e

while read local_ref local_sha remote_ref remote_sha; do
  if [[ "$local_ref" =~ ^refs/heads/main$ ]]; then
    echo "拒绝直接 push 到 main,请走 PR 流程"
    exit 1
  fi

  diff_text=$(git diff "$remote_sha..$local_sha" -- ':!*.md' ':!*.lock')
  if echo "$diff_text" | grep -nE '^\+.*\b(print|console\.log)\b' >/dev/null; then
    echo "本次提交带 console.log / print,请清理后再推"
    exit 1
  fi
done

把它放进 .git/hooks/pre-push 并 chmod +x,能挡住大部分「忘了删调试代码」的合并。钩子文件本身不进 git,所以项目里用 scripts/install-hooks.sh 把模板复制过去:

#!/usr/bin/env bash
# scripts/install-hooks.sh
set -e

if [ ! -d ".git" ]; then
  echo "当前目录不是 git 仓库"
  exit 1
fi

cp scripts/hooks/pre-push .git/hooks/pre-push
chmod +x .git/hooks/pre-push
echo "已安装 pre-push 钩子"

新同事 clone 下来跑一次 bash scripts/install-hooks.sh,整套规则就生效了,不用每个人靠记忆背规范。

怎么避免直接 push 到 main

服务器端的 main 分支可以加规则保护。GitHub 上叫 protected branch,自托管 git 叫 gitolite 或 gitea 的 branch protection。开启后,直接 push 进 main 会被服务器拒绝,必须走 PR 流程。

我上一家公司给 main 加了 required review 至少 1 人 + required CI status check。新人提交 PR 后,CI 自动跑测试,挂了就不让合并。这个习惯在团队里一旦建立起来,几乎不用花额外的协调成本。

git 本身就是个工具

我见过有人把 git 工作流搞得很复杂,pre-push 钩子、commit lint、blame 警察,结果新人来了要学一礼拜才能提交代码。工作流服务于团队,不反过来。一个人维护的小项目,分支简单就行,仪式感别太重。

我现在的态度是:基本规则定了(分支前缀、小提交、PR review),剩下的能省就省。当团队规模超过五个人,再加自动化测试和 lint,不要提前建巴别塔。

相关文章