大禹记。

首页 / 文章 / 正文

一个人写代码时,我用这套 Git 工作流

这个站的仓库只有我一个人:没有评审,没有分支保护,没人等我合并,所以那套「功能分支加 pull request」的流程对我只意味着多两步操作。下面四个习惯是 2026 年上半年慢慢固定的。环境是 Git 2.47、Windows 11 上的 Git Bash,仓库就是那几十个 HTML 加一个 CSS。

四个每天在用的习惯

一、小提交:改完一个「能跑的最小单元」就提交

我现在的粒度是一个页面或一条规则一次提交,比如「归档页加 2026 年这一节」。以前我爱攒,攒到两百多行一起提,结果提交信息只能写「更新」,想回滚某一处就得把另外九处一起回滚。小提交的收益不是安全,而是可回退:改动越小回滚代价越小,我反而更敢改。

提交信息我固定用五个前缀,全小写:

前缀表示什么例子
feat新增内容或新页面feat: 归档页增加 2026 年条目
fix修错误链接、坏掉的样式fix: 修正笔记列表的相对路径
docs只改文字,不动结构docs: 补充静态站取舍那段
style纯样式、间距、颜色变量style: 卡片圆角统一到 10px
chore杂活,删文件、调目录chore: 删掉没用的临时脚本

前缀的作用不是好看,是让我能用 git log --oneline | grep fix 一秒找出修过的坑。

二、勤用 stash,但要清楚它什么时候会丢东西

写到一半发现要改别的,我不开新分支,直接塞进 stash。默认 git stash 不管未跟踪的新文件,这点坑过我一次:新写的 html 明明不在工作区了,git stash list 里却看不见它。所以我一律带 -u,并且一定写说明:

git stash push -u -m "wip: 侧栏还没写完"
git stash list              # 看栈里都有什么
git stash pop               # 取栈顶,并删掉记录
git stash apply stash@{2}   # 取指定一条,记录保留

会丢东西的场景我踩过两种。一是 pop 时和工作区冲突:stash 记录不会被删,改动带着冲突标记留在文件里,很容易以为已经处理完,我确认全部解对才手动 git stash drop。二是 stash@{n} 的编号会变——每压入一条后面的全部往前挪一位,所以我不靠裸 pop 猜位置,先 git stash list 按说明文字认一次。

三、solo 分支上我敢 rebase,而不是 merge

我自己的分支上没有任何别人,历史只有我在读。所以攒两三个小提交之后,我会做一次交互 rebase,把明显的「写错了重来」压掉:

git rebase -i HEAD~4      # 整理最近四个提交
git rebase --continue     # 解完冲突继续
git rebase --abort        # 改乱了,退回 rebase 之前

规则只有一条:已经推到远端、被人拉过的提交绝不 rebase。所以实际情况变成推之前随便整理,推完就当它不可变。真要保留分叉证据时我直接 git merge --no-ff。

四、先备份:一行命令把整个仓库变成一份文件

远端仓库不等于备份,它只是同一个账户下的另一份拷贝。我会隔一段时间做一次 bundle,把仓库连分支带标签压成单个文件,拷到移动硬盘:

git bundle create ../web-backup.bundle --all
git bundle verify ../web-backup.bundle   # 恢复前先验一次

恢复时把它当成普通的远端 clone 就行。好处是不依赖任何服务:一个 bundle 加一份 style.css,任何电脑上都能还原。

一次事故:我把未提交的改动清空了

今年五月那次印象很深。当时我在改导航,改乱了,脑子里只想「退回上一个提交再重写」。我敲了 git reset --hard HEAD,敲完才反应过来:工作区里还有四十多行没提交。这条命令的杀伤力在于它同时清掉工作区和暂存区,而且不带确认。

接下来的顺序我记到现在,因为弄反了找不回来。第一步,别再往这个仓库写新东西;第二步,用 reflog 确认提交层面的状态:

git reflog --date=iso | head -20
# 3f2a1c8 HEAD@{0}: reset: moving to HEAD
# 8c4d102 HEAD@{1}: commit: feat: 导航加上笔记栏目

reflog 里能看到提交记录,但 reset --hard 丢掉的未提交内容不在提交层面,所以下一步去对象库里捞——前提是我那天确实 git add -A 过一次,被 add 的内容会以临时对象的形式进过库:

git fsck --lost-found --no-reflogs --unreachable
# 在 .git/lost-found/other/ 下得到若干散对象
git show 4b9e1f7 > ../recovered-nav.css
git diff --no-index web/assets/style.css ../recovered-nav.css

靠这几条我把 add 过的部分基本拼了回来。但有一小块是 add 之后新写的,从没进过索引,git fsck 也查不到,属于彻底没了,最后靠编辑器自己的本地历史翻出来。这里有个我当时没意识到的区别:

  • add 过的内容:进过对象库,git fsck --lost-found 有机会捞回来。
  • stash 过的内容:在 refs/stash 里有记录,误 drop 也能靠 reflog 找回。
  • 从没 add、也没 stash 的内容:Git 不知道它存在过,任何命令都救不了。

从那以后我加了一条硬规矩:git reset --hard 和 git checkout -- . 之前,先无脑来一句 git stash push -u -m "保险"。它只花五秒钟,换来的是一份完整的还原点。

小结

一个人写代码时,Git 的价值不在流程规范,而在「让我敢改」:小提交为了可回退,stash 为了随手存,rebase 为了历史干净,bundle 为了不把拷贝放在同一个篮子里。那次事故教会我最后一件事——破坏性操作之前,永远先留一个还原点再敲回车。


这套习惯不适合多人协作的仓库,分支保护和 rebase 的取舍在那边是另一道题,我只记录对自己这个站有效的部分。