修改基础分支并一次性重新派生所有分支

7

我有类似这样的代码:

A //branch 1
 \
  B - C //branch 2
       \
        D //branch 3

我希望你能为我提供类似这样的内容:

 A - E //branch_1
      \
       B' - C' //branch_2
             \
              D' //branch_3

我想执行一个命令,一次性地重新定位所有分支,这样我就不用一个一个地重新定位它们了。这个有可能吗?

4个回答

6
简短回答:不行,但你可以最小化你需要做的工作量。关于如何最小化工作量的长回答如下。
要理解这一点的关键是 Git 的分支名称——例如你的示例中的名称 branch_1、branch_2 和 branch_3 ——仅仅是指向一个特定提交的标识符。实际上形成分支的是提交本身。有关详细信息,请参见“分支”到底是什么意思?。与此同时,git rebase 所做的是复制一些提交,新的副本通常是在一个新的基础上创建的(因此称为“re-base”)。
在你的特定情况下,只有一个需要复制的提交链,即 B-C-D 链。如果我们去掉所有标签(分支名称),我们可以这样绘制图形片段:
A--E
 \
  B--C--D

你的任务是将 B--C--D 复制到 B'--C'--D',它们类似于 BD,但是在 A 后面而不是在 E 后面。我会把它们放在顶部,这样我们就可以在图片中保留原始的 B--C--D 链。
     B'-C'-D'
    /
A--E
 \
  B--C--D

一旦你复制完文件,你可以更改标签,使其指向复制品,而不是原件;现在我们需要将 D' 上移,以便我们可以将 branch_2 指向 C'
          D'   <-- branch_3
         /
     B'-C'     <-- branch_2
    /
A--E           <-- branch_1

这需要至少两个Git命令才能完成:
  1. git rebase, to copy B-C-D to B'-C'-D' and move branch_3 to point to D'. Normally this would be two commands:

    git checkout branch_3 && git rebase branch_1
    

    but you can actually do this with one Git command as git rebase has the option of doing the initial git checkout:

    git rebase branch_1 branch_3
    
  2. git branch -f, to re-point branch_2.

    We know (from our careful graph drawing that showed us that we could do a single git rebase of branch_3 to copy all the commits) that branch_2 points to the commit "one step back" from the commit to which branch_3 points. That is, at the start, branch_2 names commit C and branch_3 names commit D. Hence, once we're all done, branch_2 needs to name commit C'.

    Since it was one step back from the tip of the old branch_3, it must be one step back from the tip of the new branch_3 afterward.1 So now that we have done the rebase and have the B'-C'-D' chain, we simply direct Git to move the label branch_2 to point one step back from wherever branch_3 points:

    git branch -f branch_2 branch_3~1
    
因此,在这种情况下,至少需要两个Git命令(如果您喜欢单独使用git checkout则需要三个命令)。
请注意,即使我们只是移动/复制两个分支名称,也有可能需要更多或不同的命令。例如,如果我们从以下内容开始:
F--J        <-- br1
 \
  G--H--K   <-- br2
      \
       I    <-- br3

如果我们想要复制 G-H-(K;I) 中的所有内容,我们无法通过一条git rebase 命令完成。但是,我们可以先对 br2 或者 br3 进行 rebase 操作,将其中三个提交复制过去;然后使用 git rebase --onto <target> <upstream> <branch> 命令来对剩余的分支进行操作,以复制最后一个提交。

事实上,git rebase --onto 是最通用的形式:我们总是可以只使用一系列 git rebase --onto <target> <upstream> <branch> 命令完成整个工作。这是因为 git rebase 真正做了两件事情:(1) 复制一些提交,可能为空;(2) 移动分支标签,就像 git branch -f 一样。复制的集合是通过 <upstream>..HEAD 的结果确定的——参见 gitrevisions 和下面的脚注1——复制位置由 --onto 设置;分支的目的地是在执行复制后 HEAD 所在的位置。如果要复制的集合为空,则 HEAD 直接到达 --onto 目标。因此,看起来一个简单的脚本就可以完成所有工作...但请参阅脚注1。

1然而,这假设git rebase实际上复制了所有提交。(这种假设的安全级别因所涉及的变基而异。)

实际上,尽管要复制的最初一组提交是通过运行git rev-list --no-merges <upstream>..HEAD(或其等价物)确定的,但该最初的集合会立即通过计算“要复制”和“不需要复制,因为现在将成为上游”的范围中每个提交的git patch-id进一步缩小。也就是说,重写代码使用<upstream>...HEAD结合--right-only --cherry-pick,而不仅仅是省略合并提交,还省略已经在上游的提交。

我们可以编写一个脚本来定位我们想要变基的分支集合中每个分支名称的相对位置,以此来实现这一目标。(我之前尝试过一些实验性的工作。) 但是还有一个问题:在拣选过程中,由于冲突解决,有些提交可能会变为空,你需要使用git rebase --skip跳过它们。这将改变其余已复制提交的相对位置。
最终意味着,除非对git rebase进行一些增强,记录哪些提交映射到哪些新提交以及哪些提交被完全删除,否则无法创建完全正确、完全可靠的多次变基脚本。我认为只使用HEAD reflog就足够接近了,而不需要修改Git本身:这仅在某人开始这种复杂的变基,但在其中间做一些git reset --soft等操作然后恢复变基时才会出错。

2对于某些形式的git rebase而言,这是字面上的真相,而对于其他形式则不然。生成列表的代码因使用--interactive和/或--keep-empty和/或--preserve-merges而有所不同。非交互式git am基础的rebase使用以下代码:

if test -n "$keep_empty"
then
    # we have to do this the hard way.  git format-patch completely squashes
    # empty commits and even if it didn't the format doesn't really lend
    # itself well to recording empty patches.  fortunately, cherry-pick
    # makes this easy
    git cherry-pick ${gpg_sign_opt:+"$gpg_sign_opt"} --allow-empty \
        --right-only "$revisions" \
        ${restrict_revision+^$restrict_revision}
    ret=$?
else
    rm -f "$GIT_DIR/rebased-patches"

    git format-patch -k --stdout --full-index --cherry-pick --right-only \
        --src-prefix=a/ --dst-prefix=b/ --no-renames --no-cover-letter \
        "$revisions" ${restrict_revision+^$restrict_revision} \
        >"$GIT_DIR/rebased-patches"
    ret=$?

[snip]

    git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" \
        ${gpg_sign_opt:+"$gpg_sign_opt"} <"$GIT_DIR/rebased-patches"
    ret=$?

[snip]

"

--cherry-pick --right-only 是通过任何使用的命令(cherry-pick或format-patch)传递给git rev-list代码的,以便它可以从对称差异中获取右侧的提交ID列表,并删除“预选樱桃”。

交互式变基要复杂得多!

"

澄清一下:您在结尾处包含的脚本是所提到的实验的一部分,还是从Git源代码中获取的?(如果是的话,最好提及/链接它们。) - mkrieger1
1
@mkrieger1:最后的脚本片段直接来自于git-rebase--am.sh。我会添加一个链接。感谢ptyo ixfes。 :-) - torek

2
这个问题是 如何将本地git分支的链进行变基 的超集。
今天我使用的解决方案是一个名为git-chain的工具,旨在解决这个问题。
在你的例子中,你可以指定一个链。
git chain setup -c myfeature branch1 branch2 branch3

一旦这个链条被设置好了,你可以对branch1进行新的提交,然后运行git chain rebase -c myfeature,这个工具会为你处理所有引用并将它们很好地合并。

作为额外的好处,它还会计算和处理branch1branch2branch3的任何修改或新提交。


0
为了至少找出在初始变基之后需要更新的分支,您可以运行以下命令:

哪些分支

$ git branch --contains <commit A>

0

git-branchless 工具套件可以很好地解决这个问题:

$ git checkout branch1
$ git commit -m 'E'

$ git move -s B -d branch1

# the above is the same as the following,
# because `branch1` is already checked out:
$ git move -s B

# or, if you want to address branches instead of commits
# (this will also move all descendant commits/branches):
$ git move -b branch2 -d branch1

声明:我是作者。

请查看 git move文档


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接