分支揉在一起怎么办

复制代码
# Git 线性历史处理手册

这份文档记录一个常见误判场景:

- 表面上看,`main.v4` 像是把 `main.v5` 合入了
- 实际上,代码只是在 `main.v4` 上 cherry-pick 了某一笔提交
- 之所以图上看着像合并,是因为后来又出现了一个 merge commit

这个仓库里对应的真实案例是:

- `main.v5` 上原始提交:`1e327c18`
- `main.v4` 上对应的 cherry-pick:`207ea35e`
- 后面生成的 merge commit:`12aa1738`
- 最后把历史重写成线性提交后,`main.v4` 变成了普通提交链

## 先判断是不是"真的合入"

先看分支图和提交父节点:

```bash
git log --oneline --decorate --graph --all --date-order
git show --no-patch --pretty=raw <commit>
```

重点看:

- 如果一个 commit 有两个 parent,它就是 merge commit
- 如果两边提交信息相同、diff 也相同,通常就是同一笔补丁的 cherry-pick

再用 patch-id 验证是不是同一个改动:

```bash
git show <commit-a> --pretty=format:%b --patch | git patch-id --stable
git show <commit-b> --pretty=format:%b --patch | git patch-id --stable
```

如果 patch-id 相同,说明代码改动本质一致,只是 commit hash 不同。

## 这个案例里发生了什么

当时的链路是:

```text
207ea35e  <- main.v4 上的 cherry-pick
1e327c18  <- main.v5 上的原始提交
12aa1738  <- merge commit,带着两个 parent
```

问题不在代码,而在历史结构:

- `207ea35e` 只是把 `1e327c18` 的改动复制到 `main.v4`
- `12aa1738` 又把 `1e327c18` 作为第二父提交接进来
- 所以图上看起来像 `main.v5` 合入 `main.v4`

## 如果你想把它改成"线性历史"

目标是:

- 保留代码改动
- 去掉 merge 结构
- 让 `main.v4` 只是一串普通提交

操作步骤:

### 1. 回到 merge 之前的基点

```bash
git reset --hard <merge-commit-之前的那个提交>
```

在这个案例里是:

```bash
git reset --hard 207ea35e
```

### 2. 把 merge commit 按第一父提交重放成普通提交

```bash
git cherry-pick -m 1 <merge-commit>
```

`-m 1` 的意思:

- 以第一个 parent 作为主线
- 把 merge commit 相对这条主线带来的改动,重新生成一个普通 commit

在这个案例里:

```bash
git cherry-pick -m 1 12aa1738
```

### 3. 如果还有后续提交,继续逐个 cherry-pick

```bash
git cherry-pick <commit>
```

在这个案例里,后面又补了:

```bash
git cherry-pick 8af8bfe9
```

## 如何确认结果正确

看分支链:

```bash
git log --oneline --decorate --first-parent -n 6 main.v4
```

你希望看到的是普通线性提交,而不是一个带两个 parent 的 merge commit。

再看当前 commit 的父节点:

```bash
git show --no-patch --pretty=raw HEAD
git rev-list --parents -n 1 HEAD
```

如果只显示一个 parent,就说明已经是线性历史。

## 推送到远端

如果本地历史重写了,远端通常需要强推:

```bash
git push --force-with-lease origin main.v4
```

为什么用 `--force-with-lease`:

- 比 `--force` 安全
- 它会检查远端分支有没有被别人更新
- 如果远端有新提交,它会拒绝覆盖

## 这次案例的最短命令序列

```bash
git reset --hard 207ea35e
git cherry-pick -m 1 12aa1738
git cherry-pick 8af8bfe9
git push --force-with-lease origin main.v4
```

## 以后遇到类似问题的判断顺序

1. 先看提交图,确认是不是 merge commit
2. 再看 `--pretty=raw`,确认 parent 数量
3. 再比 patch-id,确认是不是同一笔改动
4. 需要线性化时,用 `reset --hard` 回到基点,再 `cherry-pick -m 1`
5. 最后用 `--force-with-lease` 推远端