Files
zhuangang/topics/devops/git/02-merge-rebase-recovery.md
T

84 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: devops.git.merge-rebase-recovery
title: 合并、变基和撤销:merge / rebase / cherry-pick、revert / reset、reflog 找回
type: lesson
domain: devops
stack: [git]
tags: [merge, rebase, cherry-pick, revert, reset, reflog, stash]
level: intermediate
prerequisites: [devops.git.branch-commit]
est_hours: 2
related: [devops.git.scenario.hotfix-and-leaked-secret, devops.git.interview]
week: [8, 15]
order: 2
---
# 合并、变基和撤销:merge / rebase / cherry-pick、revert / reset、reflog 找回
## 解释说明
**merge vs rebase**
- `git merge feat`:把两个分支的历史合在一起,可能产生一个合并提交;历史真实反映“并行开发”,但图形会交错。`--no-ff` 强制生成合并提交,记录“这里合入了一个功能”;`--squash` 把功能分支压成一个提交。
- `git rebase main`:把当前分支的提交“搬到” main 最新提交之后重新应用,历史是一条直线;但**会生成新的提交(哈希变了)**。
- 原则:**只 rebase 自己的、还没被别人基于它工作的分支**;公共分支(main、已共享的分支)不要 rebase。
- `git rebase -i`:交互式整理提交(合并、改信息、删除、调整顺序),合并请求提交前整理用。
**cherry-pick**:把某个提交复制到当前分支,常用于把热修复同时带到 release 和 main。
**撤销**
| 场景 | 命令 | 说明 |
| --- | --- | --- |
| 工作区改乱了 | `git restore 文件` | 丢弃未暂存的修改 |
| 误 `add` 了 | `git restore --staged 文件` | 取消暂存,修改保留 |
| 最后一次提交信息写错 / 漏文件(未推送) | `git commit --amend` | 改写最后一个提交 |
| 撤销本地提交(未推送) | `git reset --soft HEAD~1`(保留改动)/ `--hard`(丢弃改动) | 改写历史 |
| 撤销**已推送**的提交 | `git revert 提交` | 生成一个反向提交,不改写历史,团队协作的安全做法 |
**reflog**:记录 HEAD 每次移动的位置(本地,默认保留约 90 天)。`reset --hard`、删分支、错误的 rebase 之后,都可以在 reflog 里找到原来的提交并恢复。**只要提交过,就几乎丢不了。**
**stash**:临时保存未提交的改动,`git stash push -m "说明"`、`git stash pop`。
## 案例
- 合并请求前,用 `rebase -i` 把“WIP”“修正拼写”这类提交合并成两三个有意义的提交,再 `rebase main` 解决冲突,评审者看到的历史清晰。
- 上线后发现某个提交引入 bug,main 已经被多人拉取:用 `git revert`,不要 reset 后强推。
- `reset --hard` 后发现删掉了还需要的提交:`git reflog` 找到哈希,`git reset --hard 哈希` 或 `git branch rescue 哈希` 恢复。
## 代码示例
下面这些操作在 [examples/git-lab/lab.py](examples/git-lab/lab.py) 里会在临时仓库中自动执行并验证:
```bash
# 功能分支同步最新 main(自己的分支,可以 rebase)
git switch feat/login
git fetch origin
git rebase origin/main # 有冲突:解决后 git add 文件 && git rebase --continue;放弃:git rebase --abort
git push --force-with-lease # 改写过历史的个人分支推送;比 --force 安全:远端有别人的新提交时会拒绝
# 撤销已推送的提交
git revert a1b2c3d
# 误 reset 之后找回
git reflog # 找到 “commit: feat: 重要改动” 那一行的哈希
git reset --hard 0ecd7f4
# 热修复带回 main
git switch main
git cherry-pick 9f8e7d6
```
## 易错点
- **对公共分支 rebase 后强推**:别人的历史和你的对不上,提交会“重复”或丢失。
- **用 `--force` 而不是 `--force-with-lease`**:可能覆盖同事刚推送的提交。
- **已推送的提交用 reset 撤销**:其他人再推送时又把它带回来。用 revert。
- **revert 一个合并提交**:要用 `-m 1` 指定保留哪一边;之后想再合入同一分支,需要先 revert 这个 revert。
- **以为 reflog 能找回从未提交过的改动**:没提交(也没 stash)的工作区修改被 `reset --hard` 后是真的没了。
- **解决冲突时直接全选“我的”或“他的”**:可能丢掉对方的修改,冲突解决后要运行测试。
## 面试常问
见 [Git 面试题](interview.md):merge 和 rebase 的区别、revert 和 reset 的区别、误删提交怎么找回。