修改基础分支并一次重新设置所有子级的基础 [英] Modify base branch and rebase all children at once
问题描述
我有这样的东西:
A //branch 1
\
B - C //branch 2
\
D //branch 3
我想得到这样的东西:
A - E //branch_1
\
B' - C' //branch_2
\
D' //branch_3
我想执行一个命令并立即为所有分支重新设置基准,因此我不必一个接一个地对其进行重新基准化.这可能吗?
I would like to perform a command and rebase all branches at once, so I don't have to rebase them one by one. Is this possible?
推荐答案
简短的回答:不,这不可能...但是您可以尽量减少您要做的工作.接下来是关于最小化工作的长答案.
Short answer: no, it's not possible ... but you can minimize the amount of work you have to do. Long answer on minimizing work follows.
了解这一点的关键是Git的分支名称(在您的示例中为名称branch_1
,branch_2
和branch_3
)仅仅是指向"一个特定提交的标识符.正是提交本身构成了实际的分支.有关详细信息,请参见分支"到底是什么意思?同时,git rebase
的作用是 copy 进行一些提交,新副本通常在新的基础上创建(因此称为"re-base").
The key to understanding this is that Git's branch names—the names branch_1
, branch_2
, and branch_3
in your example—are merely identifiers that "point to" one specific commit. It's the commits themselves that form the actual branches. For details, see What exactly do we mean by "branch"? Meanwhile, what git rebase
does is to copy some commits, with the new copies normally being made on a new base (hence "re-base").
在您的特定情况下,只有一个提交链需要复制.这就是B--C--D
链.如果我们剥离所有标签(分支名称),则可以通过以下方式绘制图形片段:
In your particular case, there's only one chain of commits that requires copying. That's the B--C--D
chain. If we strip off all the labels (branch names) we can draw the graph fragment this way:
A--E
\
B--C--D
您的任务是将B--C--D
复制到B'--C'--D'
,就像B
到D
一样,但是要放在E
之后,而不是放在A
之后.我将它们放在顶部,以便我们也可以在图片中保留原始的B--C--D
链:
Your task is to copy B--C--D
to B'--C'--D'
, which are like B
through D
but come after E
instead of coming after A
. I'll put them on top so that we can keep the original B--C--D
chain in the picture too:
B'-C'-D'
/
A--E
\
B--C--D
制作完副本后,就可以更改标签,以使它们指向副本,而不是指向原始文档.现在我们需要向上移动D'
以便将branch_2
指向C'
:
Once you've made the copies, you can then change the labels so that they point to the copies, rather than pointing to the originals; and now we need to move D'
up so that we can point branch_2
to C'
:
D' <-- branch_3
/
B'-C' <-- branch_2
/
A--E <-- branch_1
这至少需要两个Git命令来完成:
This takes a minimum of two Git commands to accomplish:
-
git rebase
,将B-C-D
复制到B'-C'-D'
并移动branch_3
指向D'
.通常这是两个命令:
git rebase
, to copyB-C-D
toB'-C'-D'
and movebranch_3
to point toD'
. Normally this would be two commands:
git checkout branch_3 && git rebase branch_1
,但是实际上您可以使用一个Git命令执行此操作,因为git rebase
可以选择执行初始的git checkout
:
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
git branch -f
,以重新指向branch_2
.
git branch -f
, to re-point branch_2
.
我们知道(从我们仔细的图形图中可以看出,我们可以对branch_3
进行单个git rebase
复制所有提交),branch_2
指向从提交到提交退后一步"的提交. branch_3
指向哪个.也就是说,开始时,branch_2
名称提交C
,而branch_3
名称提交D
.因此,完成所有操作后,branch_2
需要将提交命名为C'
.
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'
.
由于它是 从旧branch_3
的尖端向后退一步,因此它必须又从新branch_3
的尖端向后退一步. 1 因此,既然我们已经完成了rebase
并拥有了B'-C'-D'
链,我们只需指示Git将标签branch_2
移动到从branch_3
指向的任何位置后退一步:
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
,则需要三个).
Thus, for this case, it takes at least two Git commands (three if you prefer a separate git checkout
).
请注意,在某些情况下,即使我们仅移动/复制两个分支名称,也需要使用更多或不同的命令.例如,如果我们从以下内容开始:
Note that there are cases where more or different commands are required, even if we are moving / copying just two branch names. For instance, if we started with:
F--J <-- br1
\
G--H--K <-- br2
\
I <-- br3
并想复制所有G-H-(K;I)
,我们不能用一个git rebase
命令来完成.我们 所要做的是首先重新设置br2
或br3
的基准,复制四个提交中的三个;然后将git rebase --onto <target> <upstream> <branch>
与其余分支一起使用,以复制剩下的一个提交.
and wanted to copy all of G-H-(K;I)
, we cannot do this with one git rebase
command. What we can do is rebase either br2
or br3
first, copying three of the four commits; then use git rebase --onto <target> <upstream> <branch>
with the remaining branch, to copy the one remaining commit.
(实际上,git rebase --onto
是最通用的形式:我们总是可以只用一系列git rebase --onto <target> <upstream> <branch>
命令来完成整个工作,每个分支一个.这是因为git rebase
确实是两件事:(1)复制一些提交集,可能为空;(2)移动分支标签la git branch -f
.复制的集是根据<upstream>..HEAD
的结果确定的,请参见 gitrevisions
和下面的脚注1-复制位置为由--onto
设置;分支的目的地是完成复制后HEAD
结束的任何地方;如果要复制的集为空,则HEAD
恰好在--onto
目标处结束. 就像一个简单的(ish)脚本可以完成所有工作……但请参见脚注1.)
(In fact, though, git rebase --onto
is the most general form: we can always do the entire job with just a series of git rebase --onto <target> <upstream> <branch>
commands, one per branch. This is because git rebase
really does two things: (1) copy some set of commits, possibly empty; (2) move a branch label a la git branch -f
. The copied set is determined from the result of <upstream>..HEAD
—see gitrevisions
and footnote 1 below—and with the copy location being set by --onto
; and the branch's destination is wherever HEAD
winds up after doing the copying. If the to-copy set is empty, HEAD
winds up right at the --onto
target. So it seems like a simple(ish) script could do all the work ... but see footnote 1.)
1 但是,这假设git rebase
实际上结束了复制 all 提交. (此假设的安全级别根据所讨论的基准而有很大差异.)
1This assumes, however, that git rebase
actually winds up copying all the commits. (The safety level of this assumption varies a lot depending on the rebase in question.)
实际上,虽然要复制的初始提交集是通过运行git rev-list --no-merges <upstream>..HEAD
或它的等效值来确定的,但是通过在两个复制"和不需要复制,因为现在将是上游"范围.也就是说,rebase代码将<upstream>...HEAD
与--right-only --cherry-pick
结合使用,而不是<upstream>..HEAD
. 2 因此,我们不仅忽略合并提交,而且还忽略已经在上游的提交.
In fact, while the initial set of commits to copy is determined by running git rev-list --no-merges <upstream>..HEAD
—or its equivalent, really—that initial set is immediately further whittled-down by computing the git patch-id
for each commit in both the "to copy" and "don't need to copy because now will be upstream" ranges. That is, instead of <upstream>..HEAD
, the rebase code uses <upstream>...HEAD
combined with --right-only --cherry-pick
.2 So we omit not just merge commits, but also commits that are already upstream.
我们可以编写一个自己执行的脚本,以便可以在希望重新建立基础的分支集中找到每个分支名称的相对位置. (我之前做过大部分实验).但是还有另一个问题:在挑选樱桃的过程中,由于冲突解决,有些提交可能会变为空,您可以git rebase --skip
进行处理.这会更改其余复制的提交的相对位置.
We could write a script that does this ourselves, so that we can locate the relative position of each branch-name in the set of branches we wish to rebase. (I did most of this as an experiment some time ago.) But there is another problem: during the cherry-picking process, it's possible that some commits will become empty due to conflict resolution, and you will git rebase --skip
them. This changes the relative position of the remaining copied commits.
这最终意味着,除非对git rebase
进行一点扩展,否则要记录哪些提交映射到哪些新提交以及哪些提交被完全删除,就不可能制作出完全正确,完全可靠的多重新设置脚本.我认为可以使用HEAD
reflog而不修改Git本身就足够接近:如果有人启动这种复杂的变基,这只会出错,但是在中间执行一些git reset --soft
和喜欢,然后恢复变基.
What this means in the end is that unless git rebase
is augmented a bit, to record which commits map to which new commits and which commits were dropped entirely, it's impossible to make a completely-correct, completely-reliable multi-rebase script. I think one could get sufficiently close, without modifying Git itself, using the HEAD
reflog: this would only go awry if someone starts this kind of complex rebase, but then in the middle of it, does some git reset --soft
s and the like and then resumes the rebase.
2 对于某些形式的git rebase
而言,这确实是正确的,而对于另一些形式而言,则不是.生成列表的代码取决于您是否使用--interactive
和/或--keep-empty
和/或--preserve-merges
. 基于非交互式git am
的rebase 用途:
2This is literally true for some forms of git rebase
and not for others. The code to generate the list varies depends on whether you're using --interactive
and/or --keep-empty
and/or --preserve-merges
. A non-interactive git am
based rebase uses:
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
代码,以便它可以使用"pre-采摘的樱桃",从对称差异中删除.
The --cherry-pick --right-only
are passed through whichever command is used (cherry-pick or format-patch) to the git rev-list
code, so that it can get the right-hand-side list of commit IDs, with "pre-picked cherries" removed, from the symmetric difference.
交互式rebase要复杂得多(!).
Interactive rebase is considerably more complicated (!).
这篇关于修改基础分支并一次重新设置所有子级的基础的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!