如何使两个 git 分支(具有共同的历史)在重新设置其中一个分支后彼此一致?

How to make two git branches (with common history) coherent with each other after rebasing one of them?

我觉得我的标题不够清晰,所以我描述一下问题:

在我的 git 项目中,我有 3 个分支:masterb1b2。这是历史树:

 A---B---C  master
          \
           D---E---F  b1
                    \
                     G---H---I  b2

假设我想变基 b1 分支并编辑 E 提交。变基后,提交 EF 将被具有不同提交 SHA 的新提交 E'F' 替换。因此,b1 中的历史将不同于 b2 中的历史:

 A---B---C  master
           \
             D---E'---F'  b1
                     
 A---B---C---D---E---F---G---H---I  b2

所以我的问题是:在变基 b1 之后如何确保 b2 遵循 b1(自动获得与 b1 相同的新提交)以便他们的各自的历史保持连贯。

第一次变基后,不是这样的:

 A---B---C  master
           \
             D---E'---F'  b1
                     
 A---B---C---D---E---F---G---H---I  b2

而是这个:

A---B---C  master
         \
          D---Ea---F'  b1
           \      
            E---F---G---H---I  b2

这里,Ea表示修改后的E提交。 你想要这个,如果我没理解错的话:

A---B---C  master
         \
          D---Ea---F'  b1
                    \      
                     G'---H'---I'  b2

您可以使用交互式变基实现此目的:

git checkout b2
git rebase -i b1

在变基编辑中,您注释掉提交 E:

的行
# pick df8efe6 E
pick a7fcbed G
pick 936b51a H
pick c77ca69 I

# ...
# Commands:
# p, pick = use commit
# ...
# If you remove a line here THAT COMMIT WILL BE LOST.

回复您的评论,如果您想从起始位置 (ABCDEFGHI) 一路走到所需的结束位置:

git checkout b2
git rebase -i master

在编辑器中:

pick 1234567 D
edit a7fcbed E
pick 936b51a F
pick c77ca69 G
pick 1e8d614 H
pick 8daafa7 I

# ...
# p, pick = use commit
# e, edit = use commit, but stop for amending
# ...

完成后,更正 b1 分支:

git checkout b1
git reset --hard 936b51a

您还可以查看其他答案以获取灵感。我不确定你为什么要在一个命令中实现所有这些;您仍然必须在此过程中的某处修改提交 E。这样做可以为您节省一个交互式变基。

如果您只有两个分支,具有如图所示的线性历史:

我会变基 b2 而不是 b1 :

 A---B---C  master
          \
           D---E'---F'
                     \
                      G'---H'---I'  b2

然后更新 b1 :

git branch -f b1 F'

@Han-KwangNienhuys 的回答完全有效,如果b2 已经重写

需要更新b2 时可以应用它。

So my question is: how to make sure that b2 follows b1 (automatically gets the same new commits as b1).

您也需要重写 b2 的历史记录,您正在重新设置所有 master..b2 提交的基址并重新挂起 b1b2 标签。

Interactive rebase 允许您在每个步骤后执行任意命令,特别是包括 git branch。也一样

git rebase -i master b2

并在当前 b1 提交后的选择列表中说 exec git branch -f b1

您可以将其自动化,将选择列表缓冲区通过

输送
while read command commit rest; do case $command in 
*) printf %s\n "$command${commit:+ $commit${rest:+ $rest}}" ;;&
pick) git for-each-ref refs/heads --points-at $commit \
        --format='exec git branch -f %(refname:short)' ;;
esac; done

并且它会在每次选择分支提示后添加 exec git branch -f。我刚刚构建并尝试了这个。当然要调味。

如果你要深入到一个分支历史中,最好只构建你想要的精确选择和分支序列; rebase 是一个方便的命令,旨在简化一项常见任务。像所有好的工具一样,它可以比您预期的更进一步,但它也有其局限性。