Git 工作流与速查手册:日常开发实用参考

Git 有上百个子命令,但日常开发其实只靠其中十五个左右。这篇文章讲的是我真正在用的工作流——再附一份值得放在手边的命令速查表。不讲内部原理,不讲理论,只讲你真正要敲的那些键。

心智模型

在 Git 里,一处改动可以待在三个地方,几乎每个命令都是在这三者之间搬运工作成果:

text
工作区              →   暂存区(Index)       →   版本库(提交)
(你在这里改文件)      (git add)                (git commit)

还有第四个地方——远程仓库,它是版本库在服务器上的一份副本。push 把提交推上去,fetch 把提交拉下来。把这张图记在脑子里,Git 的大部分神秘感就消失了。

一次性配置

bash
git config --global user.name  "Charles Tang"
git config --global user.email "you@example.com"

# 新仓库默认使用 main 作为分支名
git config --global init.defaultBranch main

# 更好的差异显示和一个顺手的默认编辑器
git config --global core.editor "code --wait"
git config --global pull.rebase true   # pull 时用 rebase 而非 merge

# 查看当前配置
git config --list

设置 pull.rebase true 是一个有倾向性的选择,但值得:它能让历史保持线性,而不是被一堆 “Merge branch ‘main’” 的噪音塞满。

每天的循环

九成的日子都长这样:

bash
git status                  # 改了什么?
git pull                    # 拉取最新
git checkout -b feat/login  # 为这次工作开分支

# ……改文件……

git add -p                  # 交互式地按代码块暂存
git commit -m "Add login form validation"
git push -u origin feat/login

git add -p 被严重低估了。它会带你逐个代码块地过一遍改动,让你提交的恰好是你想提交的内容——而不是那行你忘了删的 console.log

暂存与提交

bash
git add file.js             # 暂存单个文件
git add .                   # 暂存当前目录下的所有改动
git add -p                  # 交互式暂存,逐块进行
git restore --staged file.js  # 取消暂存(保留改动)

git commit -m "Message"     # 提交暂存区的改动
git commit -am "Message"    # 暂存已跟踪文件 + 提交,一步到位
git commit --amend          # 重写上一次提交
git commit --amend --no-edit  # 把改动追加到上次提交,保留原消息

--amend 是「糟糕,漏了个文件」时的好帮手——但只在 push 之前用。修改已发布的历史会逼着其他人一起处理这次重写。

写一条好的提交消息

text
Add rate limiting to login endpoint

Brute-force attempts were unthrottled. Cap at 5 tries
per IP per minute, returning 429 after that.

标题用祈使句(“Add”,不是 “Added”),控制在 50 个字符以内,然后空一行,再写为什么做了什么,diff 已经说清楚了。

分支

bash
git branch                  # 列出本地分支
git branch -a               # 包含远程分支
git checkout -b feat/x      # 创建并切换
git switch feat/x           # 切换(更现代、更清晰)
git switch -c feat/x        # 创建并切换(现代写法)
git branch -d feat/x        # 删除(安全——未合并会拒绝)
git branch -D feat/x        # 删除(强制)
git branch -m old new       # 重命名

switchrestore 是把功能过载的 checkout 拆开后的现代命令。switch 切分支,restore 改文件。用它们——更不容易误操作。

查看历史

bash
git log --oneline --graph --all    # 我一直开着的视图
git log -p file.js                 # 单个文件的历史,带 diff
git log --since="2 weeks ago"
git show <commit>                  # 某次提交的完整细节
git diff                           # 工作区 vs 暂存区
git diff --staged                  # 暂存区 vs 上次提交
git diff main..feat/x              # 两个分支之间的差异
git blame file.js                  # 每一行最后是谁改的

给上面第一条命令配个别名吧——你会反复敲它:

bash
git config --global alias.lg "log --oneline --graph --all --decorate"

与远程同步

bash
git fetch                   # 下载远程改动,但不应用
git pull                    # fetch + 整合进当前分支
git push                    # 上传提交
git push -u origin feat/x   # 推送并设置上游(第一次)
git push --force-with-lease # rebase 之后的安全强推

永远不要用裸的 git push --force 改用 --force-with-lease:如果别人推了你还没看到的提交,它会拒绝推送,这样你就不会悄无声息地覆盖掉队友的工作。

撤销操作

这是人人都怕的部分。其实大多数操作都是可逆的。

bash
# 丢弃某个文件未提交的改动
git restore file.js

# 取消暂存某个文件(保留改动)
git restore --staged file.js

# 撤销上一次提交,改动保留在暂存区
git reset --soft HEAD~1

# 撤销上一次提交,改动退回工作区(未暂存)
git reset HEAD~1

# 抹掉上一次提交连同它的改动——破坏性操作
git reset --hard HEAD~1

# 通过新建一个反向提交来撤销(在共享历史上是安全的)
git revert <commit>

经验法则:reset 重写历史,revert 追加历史。 在共享分支上,永远用 revert。只有 reset --hard 会真正丢掉成果——对它保持敬畏。

安全网:reflog

即便执行了 reset --hard,你的提交在大约 30 天内也不会真的消失。reflog 会列出 HEAD 到过的每一个位置:

bash
git reflog
# 挑出犯错之前那个位置的哈希
git reset --hard HEAD@{2}

这东西救我的次数多到我都不好意思承认。如果你觉得自己弄丢了一个提交,先查 reflog,别急着慌。

储藏(Stash)

适用于手头工作只做了一半、却需要切换上下文的时候:

bash
git stash                   # 把改动收起来,工作区变干净
git stash -u                # 连未跟踪文件一起收
git stash list              # 查看所有储藏
git stash pop               # 重新应用最近一条,并从栈中移除
git stash apply             # 重新应用,但保留在栈中
git stash drop              # 删除某条储藏

Stash 是一个临时抽屉,不是档案柜。如果某样东西在 stash 里待了超过一天,它就该是一个分支。

合并 vs 变基

把一个分支更新到与 main 同步,有两种方式:

bash
# 合并——原样保留历史,多一个合并提交
git switch feat/x
git merge main

# 变基——把你的提交重放到 main 之上,历史保持线性
git switch feat/x
git rebase main

合并忠实记录了真实发生的事,代价是图谱会变得错综复杂。变基给你一条干净的直线,代价是重写了你的提交哈希。

我的原则:对自己的特性分支变基,让它在评审前保持整洁;绝不对别人已经拉取过的东西变基。重写共享历史,是毁掉一个周五的标准做法。

评审前整理提交

交互式变基让你能压缩、重排、改写提交:

bash
git rebase -i HEAD~4

你会进入一个编辑器,里面是你最近 4 次提交:

text
pick a1b2c3d Add login form
squash e4f5g6h Fix typo
squash h7i8j9k Fix another typo
reword l0m1n2o Wire up validation

squash 把一个提交并入上面那个;reword 让你修改提交消息。最终交付一个干净的提交,而不是五个乱糟糟的。

解决冲突

冲突不是错误——是 Git 在请你做一个它替你做不了的决定:

text
<<<<<<< HEAD
const timeout = 3000;
=======
const timeout = 5000;
>>>>>>> feat/x

把文件改成你想要的版本,删掉 <<<===>>> 这些标记,然后:

bash
git add file.js             # 标记这处冲突已解决
git rebase --continue       # 或者:git merge --continue
git rebase --abort          # 放弃,回到起点

拿不准的时候,--abort 永远是安全的。它会把一切恢复原状。

团队特性分支工作流

我在真实团队里遵循的流程:

bash
# 1. 从 main 全新开始
git switch main
git pull

# 2. 为这次工作开分支
git switch -c feat/user-export

# 3. 以小提交推进工作
git add -p && git commit -m "Add CSV export service"

# 4. 推送并发起 PR
git push -u origin feat/user-export

# 5. 工作期间保持与 main 同步
git fetch origin
git rebase origin/main

# 6. 评审前整理历史
git rebase -i origin/main

# 7. PR 合并后清理
git switch main
git pull
git branch -d feat/user-export

短命的分支、小颗粒的提交、用变基保持同步、合并前压缩。分支活得越久,冲突越糟糕——让它们以「天」计,而不是以「周」计。

常见的坑

提交到了错误的分支。 别慌。git reset --soft HEAD~1 取消提交,git stash,切到正确的分支,git stash pop,再提交一次。

把密钥推上去了。 在后面的提交里删掉是不够的——它还在历史里。你需要 git filter-repo(或 BFG)把它彻底擦掉、强推一次,而且无论如何都该轮换这个密钥。就当它已经泄露了。

detached HEAD(游离 HEAD)。 你检出的是一个提交而不是分支。如果只是看看,无害。如果你在那里做了提交,先 git switch -c rescue-branch 把它们保住,再切走。

pull 老是产生合并提交。 你没有设置 pull.rebase true。设上它,或者用 git pull --rebase

不小心提交了大文件。 一旦进了历史,它会让之后每一次克隆都永久变臃肿。要么趁早用 reset 拦下,要么用 filter-repo 擦掉。更好的做法:从第一个提交起就有一份正确的 .gitignore

.gitignore 必备项

gitignore
# 依赖
node_modules/

# 构建产物
dist/
public/

# 环境变量与密钥
.env
.env.local

# 编辑器与系统垃圾文件
.vscode/
.DS_Store

第一个提交里就把它加上。事后取消跟踪(git rm --cached)也行,但文件仍然会留在历史里。

速查表(浓缩版)

任务 命令
查看状态 git status
交互式暂存 git add -p
提交 git commit -m "..."
修补上次提交 git commit --amend
新建分支 git switch -c feat/x
切换分支 git switch main
从远程更新 git pull
首次推送 git push -u origin feat/x
安全强推 git push --force-with-lease
漂亮的历史图 git log --oneline --graph --all
丢弃文件改动 git restore file.js
取消暂存 git restore --staged file.js
撤销提交、保留成果 git reset --soft HEAD~1
撤销共享分支上的提交 git revert <commit>
临时储藏 git stash / git stash pop
找回丢失的提交 git reflog
变基到 main git rebase main
整理提交 git rebase -i HEAD~N

写在最后

比起对 Git 底层管道的深入了解,它更回报一小撮稳定一致的习惯。频繁提交、颗粒小一点。任何工作都开分支。动手前先 pull。写未来的自己读得懂的提交消息。还要记得 reflog 的存在——Git 里几乎没有什么是真正丢得掉的,这也意味着几乎没有哪个错误值得你慌张。