我有类似这样的代码:
A //branch 1
\
B - C //branch 2
\
D //branch 3
我希望你能为我提供类似这样的内容:
A - E //branch_1
\
B' - C' //branch_2
\
D' //branch_3
我想执行一个命令,一次性地重新定位所有分支,这样我就不用一个一个地重新定位它们了。这个有可能吗?
我有类似这样的代码:
A //branch 1
\
B - C //branch 2
\
D //branch 3
我希望你能为我提供类似这样的内容:
A - E //branch_1
\
B' - C' //branch_2
\
D' //branch_3
我想执行一个命令,一次性地重新定位所有分支,这样我就不用一个一个地重新定位它们了。这个有可能吗?
A--E
\
B--C--D
B--C--D
复制到 B'--C'--D'
,它们类似于 B
到 D
,但是在 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 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
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 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 chain setup -c myfeature branch1 branch2 branch3
一旦这个链条被设置好了,你可以对branch1
进行新的提交,然后运行git chain rebase -c myfeature
,这个工具会为你处理所有引用并将它们很好地合并。
作为额外的好处,它还会计算和处理branch1
、branch2
和branch3
的任何修改或新提交。
哪些分支
$ git branch --contains <commit A>
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
文档。
git-rebase--am.sh
。我会添加一个链接。感谢ptyo ixfes。 :-) - torek