修改基础分支并立即重新设定所有子项

Kre*_*ver 7 git

我有这样的事情:

A //branch 1
 \
  B - C //branch 2
       \
        D //branch 3
Run Code Online (Sandbox Code Playgroud)

我想得到这样的东西:

 A - E //branch_1
      \
       B' - C' //branch_2
             \
              D' //branch_3
Run Code Online (Sandbox Code Playgroud)

我想一次执行一个命令并重新设置所有分支,所以我不必一一重新设置它们。这可能吗?

tor*_*rek 5

简短回答:不,这是不可能的……但是您可以最大限度地减少必须做的工作量。下面是关于最小化工作的长答案。

理解这一点的关键是 Git 的分支名称——在你的例子中,名称branch_1,branch_2branch_3只是“指向”一个特定提交的标识符。形成实际分支的是提交本身。有关详细信息,请参阅我们所说的“分支”究竟是什么意思? 同时,git rebase所做的是复制一些提交,新副本通常是在新的基础上制作的(因此是“re-base”)。

在您的特定情况下,只有一个提交链需要复制。那就是B--C--D链条。如果我们去掉所有标签(分支名称),我们可以这样绘制图形片段:

A--E
 \
  B--C--D
Run Code Online (Sandbox Code Playgroud)

你的任务是复制B--C--DB'--C'--D',这就像B通过D但是来E而不是来之后A。我会把它们放在上面,这样我们也可以B--C--D在图片中保留原始链:

     B'-C'-D'
    /
A--E
 \
  B--C--D
Run Code Online (Sandbox Code Playgroud)

制作副本后,您可以更改标签,使它们指向副本,而不是指向原件;现在我们需要移动D'起来,这样我们可以指出branch_2C'

          D'   <-- branch_3
         /
     B'-C'     <-- branch_2
    /
A--E           <-- branch_1
Run Code Online (Sandbox Code Playgroud)

这至少需要两个 Git 命令来完成:

  1. git rebase, 复制B-C-DB'-C'-D'并移动branch_3到指向D'。通常这将是两个命令:

    git checkout branch_3 && git rebase branch_1
    
    Run Code Online (Sandbox Code Playgroud)

    但是您实际上可以使用一个 Git 命令来执行此操作,也可以git rebase选择执行初始操作git checkout

    git rebase branch_1 branch_3
    
    Run Code Online (Sandbox Code Playgroud)
  2. git branch -f,重新点branch_2

    我们知道,(从我们这向我们表明,我们可以做一个仔细的图形图像git rebasebranch_3复制所有的提交),其branch_2指向承诺从“退一步”的承诺,这branch_3点。也就是说,在开始时,branch_2名称 commitCbranch_3名称 commit D。因此,一旦我们都完成了,branch_2需要命名 commit C'

    既然旧的尖端后退一步branch_3,那么以后必定是新的尖端后退一步branch_31 既然我们已经完成rebase并拥有了B'-C'-D'链,我们只需指示 Git 将标签移动branch_2到从任何branch_3点后退一步:

    git branch -f branch_2 branch_3~1
    
    Run Code Online (Sandbox Code Playgroud)

因此,对于这种情况,它至少需要两个 Git 命令(如果您喜欢单独的 ,则需要三个git checkout)。

请注意,有些情况下需要更多或不同的命令,即使我们只移动/复制两个分支名称。例如,如果我们开始:

F--J        <-- br1
 \
  G--H--K   <-- br2
      \
       I    <-- br3
Run Code Online (Sandbox Code Playgroud)

并且想要复制所有G-H-(K;I),我们不能用一个git rebase命令来做到这一点。我们可以做的是重新设置基数br2br3首先,复制四个提交中的三个;然后git rebase --onto <target> <upstream> <branch>与剩余的分支一起使用,复制剩余的一个提交。

(实际上,这git rebase --onto是最通用的形式:我们总是可以只用一系列git rebase --onto <target> <upstream> <branch>命令来完成整个工作,每个分支一个。这是因为它git rebase确实做了件事:(1)复制一些提交,可能是空的; (2) 移动一个分支标签 a la git branch -f. 复制的集合是根据<upstream>..HEAD- 参见gitrevisions下面的脚注 1 -的结果确定的,复制位置由--onto; 和分支的目的地是HEAD在进行复制后结束的地方。如果to-copy 集是空的,HEAD--onto目标处结束。所以看起来一个简单的(ish)脚本可以完成所有工作......但请参阅脚注 1。)


1然而,这假设git rebase实际上最终复制了所有提交。(此假设的安全级别因所讨论的变基而有很大差异。)

事实上,虽然要复制的初始提交集是通过运行确定的git rev-list --no-merges <upstream>..HEAD——或者它的等价物,真的——通过计算git patch-id“复制”和“不需要”中的每个提交,立即进一步削减初始集复制,因为现在将是上游”范围。也就是说,<upstream>..HEADrebase 代码使用<upstream>...HEAD--right-only --cherry-pick. 2 所以我们不仅省略了合并提交,还省略了已经在上游的提交。

我们可以编写一个自己执行此操作的脚本,以便我们可以在我们希望变基的分支集中定位每个分支名称的相对位置。(我前段时间做了大部分实验。)但还有另一个问题:在挑选过程中,由于冲突解决,一些提交可能会变成空的,你会git rebase --skip。这会更改剩余复制提交的相对位置。

这最终意味着除非git rebase稍微增加一点,以记录哪些提交映射到哪些新提交以及哪些提交被完全删除,否则不可能制作一个完全正确、完全可靠的多 rebase 脚本。我认为一个人可以在不修改 Git 本身的情况下使用HEADreflog获得足够的接近:如果有人开始这种复杂的变基,这只会出错,但是在它的中间,做一些git reset --softs 之类的,然后恢复变基.

2这确实适用于某些形式,git rebase而不适用于其他形式。生成列表的代码取决于您是否使用--interactive和/或--keep-empty和/或--preserve-merges。一个非交互式git am基于底垫的用途:

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]
Run Code Online (Sandbox Code Playgroud)

--cherry-pick --right-only是通过使用任何一个命令传递(樱桃挑或格式补丁)的git rev-list代码,使之能得到提交ID的右手侧列表中,用“预拾取樱桃”去掉,从对称差.

交互式变基要复杂得多(!)。