雷灵模板

一个人做项目,Git 分支到底怎么管?我踩过的坑和现在的做法

author
·
10
0
🤖AI摘要
本文探讨个人项目在Git管理中的分支策略和commit规范。作者指出,初期直接使用`main`分支会导致回滚困难。随后,他分享了自己的分支模型:`main`(线上代码),`develop`(日常开发)和`feature/xxx`(功能分支)。文章详细说明了何时进行分支切分、如何写有意义的commit message,以及rebase与merge的选择。作者强调保持历史简洁、分支清晰以及规范化的提交信息对个人项目的重要性。

刚开始自己写项目的时候,我的 Git 用法就是:main 分支一条线走到底,commit message 写"update"或者"fix bug",从来不建分支。觉得一个人开发嘛,搞分支不是给自己找事吗。

大概两三个月后,问题就来了。有一次改了一个功能,改到一半发现思路不对,想回到原来的版本。结果因为中间没有保存点——对,我甚至连 commit 都隔几天才做一次——根本回不去了。最后重新手写了两百多行代码。

从那以后我才慢慢建立起一套自己用的 Git 工作制度。不是什么大团队那套 Git Flow,就是适合个人和小项目的东西。用了几年了,挺顺手,写出来给还在裸奔的朋友参考。


一个我一直在用的分支模型

说实话,个人项目的分支结构真的不需要太复杂。我现在的做法就三条分支:

  • main — 线上跑的代码。不直接往上提交,只接受合并。
  • develop — 日常开发的主线。新功能从这切出去,做完合回来。
  • feature/xxx — 临时分支,做完一个功能就删掉。

就是这么简单。画出来大概这样:

main    ★───★────────────────★──── 线上版本
         \                  /
develop   ★──★──★────★──★──★── 日常开发
               \     /
feature/a       ★──★           临时(用完就删)

这个结构和 Git Flow 比少了一个 release 分支和一个 hotfix 分支。我之前试过加上,但一个人根本用不上——你不可能一边开发新功能一边修线上 bug 还要跑 release 流程。简化之后反而更顺手。


什么时候切分支?

这是我给自己定的规矩,不复杂:

  1. 开始一个新功能 → 从 developfeature/xxx 分支
  2. 修线上 bug → 从 mainhotfix/xxx,修完合回 maindevelop
  3. 实验性的东西 → 从 developexperiment/xxx,如果效果好就合,效果不好直接删

第三点很重要。很多时候你想试一个新方法,但不确定能不能跑通。在 experiment 分支上试,不污染 develop。我之前好几次在 develop 上直接试,结果整个分支被弄乱了,改回去花了不少时间。

分支命名也很关键。我以前用过 fix-bugnew-feature 这种名字,两个月后回来看完全不知道对应什么功能。现在的命名规则:

feature/article-editor    # 功能名能一眼看出是什么
hotfix/login-timeout      # 修的什么 bug
experiment/redis-session  # 试的什么东西

Commit message 怎么写才有用

这条是我被自己坑得最惨的地方。

以前我的 commit message 就是"update"、"fix"、"改了下"、"修bug"。三个月后看 git log 就像天书,完全不知道每个 commit 对应什么改动。

后来在网上看到一个 Angular 团队的规范,改得更松一点自己用了:

<type>(<scope>): <做了什么事>

type:    feat / fix / refactor / docs / style / test / chore
scope:   改了哪个模块(不写也行)

真实例子:

feat(editor): 文章编辑器支持 Markdown 实时预览
fix(auth): 修复 token 过期后前端不跳转登录页的问题
refactor(api): 把文章接口从 REST 拆分到独立路由文件
chore: 升级 axios 到 1.7,顺便删掉没用的 qs 依赖

关键是第一个词(feat/fix/refactor 这些)能让你在 git log --oneline 的时候快速扫出提交类型。scope 括号里的内容帮你定位改的是项目的哪一块。

还有一个很实用的习惯:每 commit 只做一件事。不要一个 commit 既"修复登录 bug"又"顺便重构了用户模型还改了样式"。以后 git bisect 找 bug 来源的时候,你就知道颗粒度粗有多要命了。


合代码(rebase 还是 merge?)

这个问题网上吵得很多。我的做法很简单:

公共分支(main、develop 这种多人共享的)用 merge,保持历史完整。自己的 feature 分支rebase,保持提交记录干净。

一个人的时候其实怎么都行,但养成好习惯没坏处。我一般是这样:

# 在 feature 分支干完活了
git fetch origin
git rebase origin/develop      # 把别人的改动垫到我下面
git checkout develop
git merge feature/xxx           # 合进来,一个 merge commit 就很干净
git branch -d feature/xxx       # 用完就删

rebase 让历史变成一条线,merge 保留一个"这里合并了"的标记。我自己感觉这样比两边都 merge 清爽不少。


我碰到过的几个坑

坑一:rebase 一半改主意了

有时候 rebase 到一半发现冲突太多,不想继续了。

git rebase --abort    # 回到 rebase 之前的状态

很多人不知道有这个命令,冲突了就硬着头皮一个一个解,解半天结果把代码搞乱了。

坑二:push 之后又 amend 了 commit

本地 git commit --amend 把上次的 commit message 改得更清楚,这没问题。但如果已经 push 过了:

git push --force-with-lease origin feature/xxx

--force-with-lease 而不是 --force。前者会先检查远程有没有其他人的提交,有的话会拒绝 force push,防止误覆盖别人的代码。一个人开发的时候无所谓,但这个习惯以后跟人合作能救命。

坑三:.gitignore 忘加了

每次都是在项目做了好一阵子了才想起来没加 .gitignore,然后 node_modules.env、编译产物全混在仓库里。我的习惯是 git init 完第一件事就是建 .gitignore,并且把下面这些默认写上:

node_modules/
.env
dist/
*.log
.DS_Store

Node.js 项目还可以用 npx gitignore node 自动生成。

坑四:把敏感信息 commit 进去了

比如 .env 里的密钥、API key。一旦 push 到 GitHub 就收不回来了——就算你删掉这行重新 commit,历史记录里还有。

这种情况用 git filter-branch 或者 BFG 能清理,但最省心的做法是 .gitignore 里先加 .env,然后项目里放一个 .env.example 给别人参考格式。


这套东西对我的实际帮助

总结一下,用上这套流程之后几个明显的变化:

  1. 回退变容易了。改崩了就切分支、回退 commit,不用重新手写代码。
  2. git log 能看懂了。规范 commit message 之后,翻历史记录就能知道三个月前改了什么。
  3. 试新东西没负担。反正是在 experiment 分支上试,搞不成就删掉。
  4. 不怕丢代码。commit 粒度细,即使 IDE 崩了也就丢最近十几分钟的改动。

小结

一个人开发 ≠ 不用规范。分支策略和 commit message 这些东西,看起来是给团队准备的,其实最受益的就是你自己——三个月后的你。

不用搞太复杂。三条分支、commit 前缀、遇到冲突用 rebase --abort,基本就够了。先把这个搞顺,以后再考虑 Git Flow 那一套也不迟。

Git 这种东西就是典型的"平时不用心,出事了才后悔"。我写这些也不是什么高深理论,就是踩坑踩多了自然养成的习惯。能帮到一个人,这篇就没白写。


文中提到的 Angular commit 规范(我把它简化了,原版更严格):https://github.com/angular/angular/blob/main/CONTRIBUTING.md#commit

评论 (0)