这是 Git 手册系列的第二篇。初级篇解决了"能用",本篇解决"用得对":多人协作不打架、冲突不慌、历史能改得干净。
目录
- 分支模型:选一个然后坚持
- merge 与 rebase:到底用哪个
- 冲突的正确解法
- reset 的三种模式(soft / mixed / hard)
- stash:临时收摊子
- cherry-pick:挑一个提交带走
- 修改历史:amend 与交互式 rebase
- reflog:你的后悔药
- tag 与发布
- 中级常见错误
分支模型:选一个然后坚持
个人 / 小团队:Trunk-Based(主干开发)
main是唯一长期分支,随时可发布- 功能分支从 main 切出,活 1~3 天,合并后立即删除
- 配合 PR/MR 做代码评审
| |
需要同时维护多个发布版本:Git Flow
main:线上版本;develop:集成分支feature/*、release/*、hotfix/*各司其职- 重,只有确实需要多版本并行(如 SaaS + 私有化部署)才用
结论:90% 的团队用 Trunk-Based 就够了。分支模型越简单,协作成本越低。
merge 与 rebase:到底用哪个
两者都用来"把 B 分支的改动合进 A 分支",区别在于历史形态:
| |
经验法则:
- 公共分支(main、develop)上用 merge,保留协作痕迹。
- 自己的功能分支同步 main 最新代码时用 rebase,保持历史干净:
| |
⚠️ 黄金规则:永远不要 rebase 已经 push 且别人在用的分支。rebase 会改写提交 id,别人的本地历史会对不上。
冲突的正确解法
冲突的本质:同一文件的同一区域,两边都改了,Git 不知道该听谁的。
| |
处理流程:
| |
打开冲突文件,会看到标记:
| |
<<<<<<< HEAD 到 ======= 是当前分支的内容,======= 到 >>>>>>> 是合入方的内容。手动编辑成正确结果,删掉标记:
| |
然后:
| |
中途想放弃:git merge --abort(rebase 对应 git rebase --abort),一切回到合并前。
降低冲突概率的三个习惯:
- 功能分支短命,尽快合并
- 开始干活前先
git pull/git rebase origin/main - 公共文件(配置文件、路由表)的改动尽早沟通
reset 的三种模式(soft / mixed / hard)
git reset 把 HEAD 移动到某个提交,三种模式决定"工作区和暂存区怎么办":
| 模式 | 提交历史 | 暂存区 | 工作区 |
|---|---|---|---|
--soft | 回退 | 保留 | 保留 |
--mixed(默认) | 回退 | 清空 | 保留 |
--hard | 回退 | 清空 | 清空 |
| |
HEAD~1 表示"上一条提交",HEAD~3 表示往前数 3 条。
安全准则:--hard 之前先 git stash 或确认改动有备份;只 reset 还没 push 的提交。
stash:临时收摊子
场景:正在 feature 分支改一半,线上突然有 bug 要你切分支修。
| |
常用操作:
| |
cherry-pick:挑一个提交带走
场景:在 develop 上修的一个 bug,main 上也需要,但不想合并整个 develop。
| |
可以连续挑多个:git cherry-pick A B C,或挑一个区间 git cherry-pick A..B(不含 A)。
修改历史:amend 与交互式 rebase
amend:修补最近一次提交
| |
交互式 rebase:整理最近 N 条提交
| |
编辑器里列出最近 4 条提交,把每行开头的 pick 改成:
| 指令 | 作用 |
|---|---|
pick | 保留这条提交 |
reword | 保留,但改提交信息 |
squash | 合并进上一条(保留两条的信息供编辑) |
fixup | 合并进上一条(丢弃本条信息) |
drop | 删除这条提交 |
典型场景:PR 之前把 修复 typo、再改一下、真的改好了 这类碎提交压成一条干净的。
⚠️ 同样的黄金规则:只改写未 push 的历史。如果已 push 且确认只有自己在用,改写后需要 git push --force-with-lease(比 --force 安全:远程有别人新提交时会拒绝)。
reflog:你的后悔药
几乎所有"我完了"的瞬间,git reflog 都能救:
| |
| |
reflog 记录 HEAD 的每一次移动(commit、reset、rebase、切换分支……),保留约 90 天。找回方式:
| |
只要不是 git clean 删掉的未跟踪新文件,Git 里几乎没有真正丢失的提交。
tag 与发布
| |
版本号遵循语义化版本 主.次.修(semver):破坏性变更升主版本,新功能升次版本,修 bug 升修订号。
中级常见错误
1. 在公共分支上 rebase / amend 了已 push 的提交 队友 pull 时会历史错乱。解:公共分支只用 merge;改写前先确认没有别人基于它工作。
2. 解决冲突时直接用"接受我方/接受对方"整文件覆盖 可能把别人无关的改动一起吞掉。逐块(hunk)检查。
3. git reset --hard 之后发现误删了改动
先别慌,git reflog 找回。养成 hard 之前 git stash 的习惯。
4. stash 堆成山从来不清理
git stash list 定期看一眼,用完就 pop/drop。
5. cherry-pick 之后又 merge 原分支,出现重复改动 偶尔会发生,合并时 Git 通常能识别;冲突了耐心解一次即可。
本篇速查表
| 目的 | 命令 |
|---|---|
| 同步 main 到功能分支 | git rebase origin/main |
| 放弃合并 | git merge --abort |
| 撤销提交保留改动 | git reset --soft/--mixed HEAD~1 |
| 彻底回退(危险) | git reset --hard <id> |
| 暂存半成品 | git stash / git stash pop |
| 复制单个提交 | git cherry-pick <id> |
| 改最近提交 | git commit --amend |
| 整理历史 | git rebase -i HEAD~n |
| 找回丢失提交 | git reflog |
| 安全强推 | git push --force-with-lease |
| 打标签 | git tag -a v1.0.0 -m "..." |
到这一层,你已经能应付团队协作的全部日常。下一篇《高级篇》进入 Git 的内部世界:对象模型、引用、HEAD 的本质、bisect 二分排错、worktree、submodule/subtree 与钩子——理解了"为什么",命令就再也背不丢。