Git进阶工作流

zheng头像
zheng 2026-07-16 11:28
楼主

Git 进阶工作流:从 add/commit/push 到团队协作的正确姿势

Git 入门只需要三条命令:addcommitpush。但用了三年还是这三条命令,遇到合并冲突就删库重来,看到 git log --graph 像地铁线路图一样乱。这篇文章讲的是这三条命令之后的东西——分支策略、合并方式、rebase 的正确用法、以及一套真正能落地的团队工作流。


你的 Git 在哪个阶段?

阶段 特征
入门 add、commit、push、pull 循环
进阶 会用 branch、merge、stash
熟练 rebase、cherry-pick、交互式 rebase
精通 reflog 救场、bisect 找 bug、submodule/subtree

这篇文章的目标是把你从"入门"拉到"熟练"。


分支策略:Git Flow vs GitHub Flow

Git Flow(重流程,适合版本发布型项目)

main        ●──●────────────────────●──  v1.0    v2.0
              \                    /
develop        ●──●──●──●──●──●──●
                  \     /
feature/login      ●──●
                        \
release/1.0               ●──●
                              \
hotfix/bug                    ●──●

适合:有明确版本号的软件(App、桌面软件),发布周期以周或月计。

GitHub Flow(轻流程,适合持续部署)

main ──●──●──●──●──●──●──● (随时可部署)
         \    \    \    \
feature1   ●──●  ●    ●
feature2        ●──●──●

适合:Web 应用、API 服务,每天部署多次。规则只有一条:所有功能开发在 feature 分支,合并前走 Pull Request。

怎么选?

个人项目或小团队 Web 项目,用 GitHub Flow。你想简单,分支越少越好。


Merge 的三种方式:你一直用错的那个按钮

GitHub PR 页面有三个合并按钮:

1. Merge Commit(默认)

main:    A──B──C
               \
feature:        D──E

merge 后:
main:    A──B──C────M
               \  /
                D──E

保留完整历史,但多人协作会产生大量 merge commit,"地铁线路图"就是这么来的。

2. Squash and Merge(推荐)

main:    A──B──C
               \
feature:        D──E

squash merge 后:
main:    A──B──C──S (S 是 D+E 的压缩)

一条 feature 分支的所有 commit 压缩成一个,main 分支历史是一条直线。缺点:丢失了 feature 分支上的中间 commit 细节。

3. Rebase and Merge

main:    A──B──C
               \
feature:        D──E

rebase merge 后:
main:    A──B──C──D'──E'

把 feature 分支的 commit 逐个搬到 main 顶部,保留每个 commit,但改变了 commit hash。

推荐策略

场景 方式
功能开发 PR Squash and Merge
Bug 修复(单 commit) Rebase and Merge
跨团队协作的大功能 Merge Commit(保留完整上下文)

我个人偏好:日常开发一律 Squash and Merge。main 分支历史干净,回滚一个 PR 就是回滚一个 commit。


Rebase 的正确姿势

Rebase 是最容易被用错的 Git 命令。记住核心原则:只 rebase 还没 push 过的本地 commit

场景一:同步上游变更(最常用)

# 你在 feature 分支上开发了 3 天,main 已经被同事更新了 20 个 commit
git checkout feature
git fetch origin
git rebase origin/main

# 如果有冲突,解决后:
git add .
git rebase --continue

等价于把你 feature 分支上的 commit "摘下来",等 main 更新完再"贴回去"。效果是 history 变成一条直线。

如果用 merge:

git merge origin/main

会产生一个 "Merge branch 'main' into feature" 的 commit,纯粹噪音。

场景二:交互式 Rebase —— 整理本地 commit

你在本地开发时 commit 很随意:

WIP: 开始写
fix typo
增加接口
WIP: 临时保存
又改了个 bug
完成功能

提交 PR 前,用交互式 rebase 整理:

git rebase -i HEAD~6

进入编辑器:

pick a1b2c3d WIP: 开始写
pick e4f5g6h fix typo
pick i7j8k9l 增加接口
pick m0n1o2p WIP: 临时保存
pick q3r4s5t 又改了个 bug
pick u6v7w8x 完成功能

改成:

pick a1b2c3d 开始写
fixup e4f5g6h fix typo          # 合并到上一个
pick i7j8k9l 增加接口
fixup m0n1o2p WIP: 临时保存     # 合并到上一个
fixup q3r4s5t 又改了个 bug      # 合并到上一个
reword u6v7w8x 完成功能         # 保留但重新写 commit 消息

保存退出后,6 个零散的 commit 变成 3 个清晰的 commit。再 push 到远端。

fixup 的作用是把该 commit 的内容合并到上一个 commit,但丢弃它的 commit 消息。squash 会合并内容并让你重新编辑消息。reword 只改消息不改内容。


Git Stash:临时的后悔药

正在 feature 分支上写了一堆代码,突然线上出了 bug 要切到 hotfix 分支:

git stash                    # 暂存所有修改
git checkout main
git checkout -b hotfix/crash
# ... 修复、提交、合并 ...
git checkout feature
git stash pop                # 恢复之前的工作

常用 stash 命令:

git stash                    # 暂存所有改动
git stash save "描述"        # 附说明
git stash list               # 查看 stash 列表
git stash pop                # 恢复最近一个并删除
git stash apply stash@{1}    # 恢复指定 stash,不删除
git stash drop stash@{2}     # 删除指定 stash


Cherry-pick:精准搬运单个 commit

测试环境上的某个修复需要立刻搬到线上,但线上分支和测试分支差异很大,不能直接 merge。

# 找到那个 commit 的 hash
git log --oneline

# 切到目标分支
git checkout main

# 只搬运这一个 commit
git cherry-pick abc1234

如果是搬运一个范围:

git cherry-pick abc1234..def5678

Cherry-pick 会重新生成 commit,hash 会变。不要对已经 push 到公共分支的 commit 做 cherry-pick,会造成重复提交。


Git Reflog:救回"被删除"的 commit

git reset --hard 之后发现删错了,欲哭无泪?Reflog 是你最后的救命稻草。

git reflog
abc1234 HEAD@{0}: reset: moving to HEAD~3
def5678 HEAD@{1}: commit: 重要的功能
ghi9012 HEAD@{2}: commit: 另一个修改

看到那个 def5678 了吗?它还在 repo 里,只是没有分支指向它。

git checkout -b recovery def5678

被"删除"的 commit 复活了。Reflog 默认保留 90 天,足够你从任何灾难中恢复。


Git Bisect:自动定位引入 bug 的 commit

线上突然出 bug,你知道 2 周前是好的,但中间有 200 个 commit,不知道哪个引出的 bug。

git bisect start
git bisect bad HEAD               # 当前版本有问题
git bisect good abc1234           # 2 周前版本没问题

Git 自动 checkout 到中间某个 commit。你测试:

git bisect good                   # 如果这个版本没问题
# 或
git bisect bad                    # 如果这个版本有问题

重复几次,Git 二分法快速定位到出问题的那一次 commit。200 个 commit,只需要测 log2(200) ≈ 8 次。

有自动化测试的话更快:

git bisect run python test_bug.py

全自动二分定位。


一套可落地的团队工作流

总结成每天的操作:

# 早上来,同步最新代码
git checkout main
git pull --rebase

# 开始新功能
git checkout -b feature/xxx
# ... 写代码,多次 commit ...

# 提交 PR 前整理 commit
git fetch origin
git rebase -i origin/main

# push
git push -u origin feature/xxx

# 创建 PR,等待 Code Review
# Review 意见修改后,继续 commit + push
# 最后选择 "Squash and Merge"

# 切回 main,拉最新,开始下一个功能
git checkout main
git pull --rebase
git branch -d feature/xxx


常见问题

Q:push 被拒绝,提示 "non-fast-forward"?

因为远端有新 commit 你本地没有。正确做法:

git fetch origin
git rebase origin/main
git push

千万别:git push -f(除非你完全确定自己在做什么)。

Q:冲突了怎么办?

Rebase 过程中冲突:

# Git 提示冲突
# 编辑冲突文件,解决冲突
git add .
git rebase --continue

# 如果解决不了,放弃本次 rebase
git rebase --abort

Merge 过程中冲突也一样,但 --abort 换成 git merge --abort

Q:.git 目录太大了怎么办?

历史久远的仓库,git clone 会下载全部历史。清理大文件:

# 找出大文件
git rev-list --objects --all | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | awk '/^blob/ {print $3, $4}' | sort -rn | head -10

# 用 BFG Repo-Cleaner 或 git filter-branch 清理(谨慎操作)


Git 的精髓不在于记住多少命令,而在于理解每个操作对历史的改变。Squash 让 main 分支干净、Rebase 让 feature 分支跟上游同步、Reflog 让一切误操作可逆。掌握这三件武器,Git 就从"能用的版本管理"变成"好用的版本管理"。

Docker Compose 搭建服务全家桶:一个文件搞定 NAS 常用服务 rsync 文件同步与增量备份教程:比 scp 快 10 倍

最新回复

暂无回复,来说两句吧

发表评论

站点总访问量:2775