这是 Git 手册系列的第二篇。初级篇解决了"能用",本篇解决"用得对":多人协作不打架、冲突不慌、历史能改得干净。
目录
- 分支模型:选一个然后坚持
- merge 与 rebase:到底用哪个
- 冲突的正确解法
- reset 的三种模式(soft / mixed / hard)
- stash:临时收摊子
- cherry-pick:挑一个提交带走
- 修改历史:amend 与交互式 rebase
- reflog:你的后悔药
- tag 与发布
- 中级常见错误
分支模型:选一个然后坚持
个人 / 小团队:Trunk-Based(主干开发)
main是唯一长期分支,随时可发布- 功能分支从 main 切出,活 1~3 天,合并后立即删除
- 配合 PR/MR 做代码评审
git switch -c feature/xxx main
# 开发、提交
git push -u origin feature/xxx # 提 PR,评审通过后合并
git switch main && git pull
git branch -d feature/xxx
需要同时维护多个发布版本:Git Flow
main:线上版本;develop:集成分支feature/*、release/*、hotfix/*各司其职- 重,只有确实需要多版本并行(如 SaaS + 私有化部署)才用
结论:90% 的团队用 Trunk-Based 就够了。分支模型越简单,协作成本越低。
merge 与 rebase:到底用哪个
两者都用来"把 B 分支的改动合进 A 分支",区别在于历史形态:
merge(保留真实历史,产生合并提交):
main: A---B-------M
\ /
feature: C---D
rebase(把 feature 的提交"搬家"到 main 最新处,历史成一条直线):
main: A---B
feature: C'--D' (C、D 被重放为新提交)
经验法则:
- 公共分支(main、develop)上用 merge,保留协作痕迹。
- 自己的功能分支同步 main 最新代码时用 rebase,保持历史干净:
git switch feature/xxx
git fetch origin
git rebase origin/main # 解决冲突后 git rebase --continue
⚠️ 黄金规则:永远不要 rebase 已经 push 且别人在用的分支。rebase 会改写提交 id,别人的本地历史会对不上。
冲突的正确解法
冲突的本质:同一文件的同一区域,两边都改了,Git 不知道该听谁的。
git merge feature/xxx
# CONFLICT (content): Merge conflict in src/app.js
处理流程:
git status # 1. 看哪些文件冲突
打开冲突文件,会看到标记:
<<<<<<< HEAD
const timeout = 3000;
=======
const timeout = 5000;
>>>>>>> feature/xxx
<<<<<<< HEAD 到 ======= 是当前分支的内容,======= 到 >>>>>>> 是合入方的内容。手动编辑成正确结果,删掉标记:
const timeout = 5000;
然后:
git add src/app.js # 2. 标记为已解决
git commit # 3. 完成合并提交
中途想放弃:git merge --abort(rebase 对应 git rebase --abort),一切回到合并前。
降低冲突概率的三个习惯:
- 功能分支短命,尽快合并
- 开始干活前先
git pull/git rebase origin/main - 公共文件(配置文件、路由表)的改动尽早沟通
reset 的三种模式(soft / mixed / hard)
git reset 把 HEAD 移动到某个提交,三种模式决定"工作区和暂存区怎么办":
| 模式 | 提交历史 | 暂存区 | 工作区 |
|---|---|---|---|
--soft | 回退 | 保留 | 保留 |
--mixed(默认) | 回退 | 清空 | 保留 |
--hard | 回退 | 清空 | 清空 |
git reset --soft HEAD~1 # 撤销上次提交,改动还在暂存区(改提交内容用)
git reset HEAD~1 # 撤销上次提交,改动回到工作区(重新组织提交用)
git reset --hard HEAD~1 # 撤销上次提交,改动全部丢弃(危险!)
HEAD~1 表示"上一条提交",HEAD~3 表示往前数 3 条。
安全准则:--hard 之前先 git stash 或确认改动有备份;只 reset 还没 push 的提交。
stash:临时收摊子
场景:正在 feature 分支改一半,线上突然有 bug 要你切分支修。
git stash # 把未提交的改动收起来,工作区变干净
git switch hotfix/urgent # 去修 bug
# …修完、提交…
git switch feature/xxx # 回来
git stash pop # 恢复刚才收起的改动
常用操作:
git stash list # 看有几个 stash
git stash show -p # 看最近一次 stash 的内容
git stash push -m "登录页改了一半" # 带备注存
git stash apply stash@{1} # 恢复指定的(pop 会删掉,apply 保留)
git stash drop stash@{0} # 删除
cherry-pick:挑一个提交带走
场景:在 develop 上修的一个 bug,main 上也需要,但不想合并整个 develop。
git log --oneline develop # 找到那个提交的 id,比如 a1b2c3d
git switch main
git cherry-pick a1b2c3d # 把它复制过来(生成新提交 id)
可以连续挑多个:git cherry-pick A B C,或挑一个区间 git cherry-pick A..B(不含 A)。
修改历史:amend 与交互式 rebase
amend:修补最近一次提交
# 刚提交完发现漏了个文件 / 提交信息写错了
git add forgotten.js
git commit --amend # 编辑器里顺便改 message
git commit --amend --no-edit # 只补内容不改 message
交互式 rebase:整理最近 N 条提交
git rebase -i HEAD~4
编辑器里列出最近 4 条提交,把每行开头的 pick 改成:
| 指令 | 作用 |
|---|---|
pick | 保留这条提交 |
reword | 保留,但改提交信息 |
squash | 合并进上一条(保留两条的信息供编辑) |
fixup | 合并进上一条(丢弃本条信息) |
drop | 删除这条提交 |
典型场景:PR 之前把 修复 typo、再改一下、真的改好了 这类碎提交压成一条干净的。
⚠️ 同样的黄金规则:只改写未 push 的历史。如果已 push 且确认只有自己在用,改写后需要 git push --force-with-lease(比 --force 安全:远程有别人新提交时会拒绝)。
reflog:你的后悔药
几乎所有"我完了"的瞬间,git reflog 都能救:
git reflog
a1b2c3d HEAD@{0}: reset: moving to HEAD~1 ← 手滑的操作
e4f5g6h HEAD@{1}: commit: 重要的提交 ← 以为丢了的提交
reflog 记录 HEAD 的每一次移动(commit、reset、rebase、切换分支……),保留约 90 天。找回方式:
git reset --hard e4f5g6h # 直接回到那个状态
# 或
git branch rescue e4f5g6h # 开个分支把那个提交捞回来
只要不是 git clean 删掉的未跟踪新文件,Git 里几乎没有真正丢失的提交。
tag 与发布
git tag v1.2.0 # 轻量标签
git tag -a v1.2.0 -m "发布 1.2.0:新增支付模块" # 附注标签(推荐)
git push origin v1.2.0 # 推送单个标签
git push --tags # 推送所有标签
git tag -d v1.2.0 # 删本地
git push origin :v1.2.0 # 删远程
版本号遵循语义化版本 主.次.修(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 与钩子——理解了"为什么",命令就再也背不丢。