Rebase 分支保留基于它的另一个分支上的提交
Rebase branch preserving commits on another branch based on it
抱歉,如果标题有误导性,但我不太确定如何描述我遇到的情况。
我已经像这样提交和分支了
A --- B --- C --- D (master)
\
E (another)
我想从 master
分支中删除提交 B
和 C
(保留 D
),但将它们保留在 another
分支中在主人身上。
所以在转换之后我的树应该是这样的:
A --- D (master)
\
B --- C --- E (another)
我想,我可能应该只变基 master
,但是我不确定 B
和 C
是否仍会包含在 another
中提到从中删除/省略 D
。
我应该如何进行才能达到上述效果?
假设您要移动变更集,应该没有那么难:
git rebase --onto A C master
这会将分支 master
移动到 A 之上,放弃直到 C 的修订(因此我只会移动 D 以及分支指针)。那么:
git rebase --onto C D another
这会将 E 变基到 C 之上,放弃直到 D 的修订(换句话说,仅移动 C 之上的 E...同时移动分支指针)。
应该可以。
实现它的另一种方法是简单地使用交互式变基:
https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History
从 master 创建另一个分支并手动重写它们:
$ git checkout -b another
$ git rebase -i
git rebase
没有参数会给你一个列表,其中包含该分支上的所有提交。然后只需在提交列表中用 'd' 标记要删除的那些。例如对于大师,你想要 "remove" B、C 和 E。
d b72a395 E
pick 5bca8d9 D
d 15aab26 C
d 25aab26 B
pick 35aab26 A
对于 "another" 分支,标记 "D" 将被删除。
要得到你想要的结果——好吧,可能是你想要的结果,至少——你必须停止使用现有的提交D
和 E
完全,原因是没有人——不是你或 Git 本身——可以改变 任何 关于 任何 现有提交, 提交之间的连接实际上是哈希 ID 存储在内部 parent/child 对的子项中。
也就是说,给定第一张图,提交 A
是根提交:它没有父项。没有箭头说 A
之前的提交是 _____ 因为 A
之前没有提交。但是提交B
确实有一个箭头,从它指向提交A
:我之前的提交是提交A
。提交 C
包含指向 B
的箭头; D
包含指向 C
的箭头; E
包含指向 D
:
的箭头
A <-B <-C <-D <-E
不同于提交,分支名称可以更改:它们充当指向您选择的任何一个提交的箭头。因此 master
当前指向现有提交 D
,并且 another
指向现有提交 E
。 Git可以从another
开始找E
,用E
找D
,用D
找C
,还有很快;或者Git可以从master
开始找D
,这样就可以找到C
和B
和A
.
您想要的结果有一个提交 B
指向 A
,C
指向 B
,因此现有提交通过 C
都很好。但是您想要 D
的新改进变体,它不是指向 C
,而是直接指向 A
.
这个新的和改进的 D'
大概有一个现有提交没有的快照。要为 D'
制作快照,您希望 Git 获取 C
和 D
中快照之间的差异,并将该差异应用于 [=] 中的快照23=].
Git 可以自动执行此操作。执行此操作的基本 Git 命令是 git cherry-pick
。我们稍后会看到如何使用 git rebase
到 运行(正确的一组)git cherry-pick
命令,但让我们从 cherry- 开始选择自己。
同样,你想要一个 E
的新的和改进的 copy,我们可以称之为 E'
,其中的改进是:
- 指向
C
,而不是D
;和
- 有一个快照是通过将快照
D
和 E
之间的差异应用于 C
中的快照而制作的。
同样,这是 git cherry-pick
的工作。那么让我们看看如何做到这一点。
使用git cherry-pick
要创建父级为 A
的新的和改进的 D'
,我们必须首先 git checkout
提交 A
本身,最好还在那里附加一个临时分支名称以免混淆。 (在内部,使用 git rebase
,Git 使用 no 临时分支名称完成所有这些。)所以我们将 运行:
git checkout -b temp <hash-of-A>
这给了我们:
A <-- temp (HEAD)
\
B--C--D <-- master
\
E <-- another
现在我们像这样使用git cherry-pick
:
git cherry-pick <hash-of-D>
# or: git cherry-pick master
这会复制提交 D
,master
指向的那个——我们可以通过它的哈希 ID 或名称 master
给它——到新的提交 D
',temp
现在指向它。 (任何时候我们进行新的提交,Git 将新提交的哈希 ID 存储在 current 分支中:HEAD
附加到。所以 temp
现在指向复制 D'
.)
A--D' <-- temp (HEAD)
\
B--C--D <-- master
\
E <-- another
现在我们需要另一个新的临时分支,指向提交 C
,所以我们 运行 git checkout -b temp2 <em>hash- of-C</em>
。 (除了原始哈希,我们可以使用 Git 必须找到提交 C
的任何其他方式,例如 master~1
,但是原始哈希可以剪切和粘贴,因为只要你剪对了。)这给了我们:
A--D' <-- temp
\
B--C <-- temp2 (HEAD)
\
D <-- master
\
E <-- another
(注意 HEAD
现在是如何附加到 temp2
,因为 git checkout -b
。)现在我们挑选提交 E
来制作 E'
:
git cherry-pick another
会成功,因为 another
指向提交 E
。如果一切顺利,Git 自己进行新的提交,我们有:
A--D' <-- temp
\
B--C--E' <-- temp2 (HEAD)
\
D <-- master
\
E <-- another
我们现在需要做的是强制名称 master
引用提交 D'
,名称 another
引用提交 E'
。现在要做到这一点,我们可以使用 git branch -f
:
git branch -f master temp
git branch -f another temp2
这给了我们:
A--D' <-- master, temp
\
B--C--E' <-- another, temp2 (HEAD)
\
D [abandoned]
\
E [abandoned]
尽管提交 D
和 E
没有 名称——这使得它们很难找到——它们会在你的 Git 存储库中相当长一段时间,通常至少 30 天。 (这可以通过各种 reflog 过期设置来控制。)如果你已经将他们的哈希 ID 保存在某个地方(并且你已经 - 或者更确切地说,Git 已经将哈希 ID 保存在一些reflogs),您仍然可以在这段时间内取回它们。
您现在可以 git checkout
任一原始分支名称并删除两个 temp
名称。
使用 git rebase
执行此操作
git rebase
所做的本质上是1运行[=63=的系列 ] 命令,并通过 运行ning 等效于 git branch -f
来完成所有操作,以强制分支名称指向 last 复制的提交,并且 [=77= 】 那个分支。 git rebase
将复制的提交集来自 rebase 所称的 upstream 参数。 rebase 将它们复制到的位置,就像 git cherry-pick
一样,来自 rebase 调用它的 onto 参数。
也就是你运行:
git rebase --onto <target> <upstream>
其中 target
是您想要在第一个复制提交之前的提交,并且 upstream
告诉 Git 什么提交 而不是 复制。这个 "what not to copy" 一开始看起来很奇怪,但你会习惯它。2 它也允许你在大多数时候省略 --onto
(虽然不是在你的具体情况)。
Git 所做的是枚举 <em>upstream</em>..HEAD
中的提交,排除某些通常不受欢迎的提交。3 这会产生一个应该复制/挑选的提交哈希 ID 列表。此列表被保存到一个临时文件中。4 然后,Git 运行 是 git checkout
的 HEAD 分离变体以检查 target
提交 --onto
,或者 upstream
如果您没有指定 --onto
。然后,Git 对保存的哈希 ID 进行挑选。最后,如果一切顺利,Git 强制将分支及其 HEAD
重新附加到 rebase 操作中最后复制的提交。
对于您的特殊情况,,比我快 20 分钟得到这个答案。 :-) 这只是对实际情况的长篇解释。
1我在这里说 好像 一样,因为有些 rebase 方法使用其他方法,有些 rebases 字面意思 运行 git cherry-pick
,或者——在最现代的 Git 中——直接构建在 Git 内部调用的 sequencer 中,它实现了 cherry -采摘。
2由于Git的A..B
限制语法,这实际上是自然的。这告诉Git:找到可从到达的提交B
,排除那些可从到达的提交A
. 有关可达性的(更多)信息,请参阅 Think Like (a) Git.
3不受欢迎的是现有的合并提交,以及任何已经被挑选出来的提交。 Git 使用 git patch-id
程序找到后者。描述得当有点棘手,这里就不赘述了。
4它在 .git
之下,但在 Git 的开发过程中位置已经移动。根据其他情况,有时您可以在 .git/rebase-todo
或类似的名称中找到它们,如果您好奇的话。
抱歉,如果标题有误导性,但我不太确定如何描述我遇到的情况。
我已经像这样提交和分支了
A --- B --- C --- D (master)
\
E (another)
我想从 master
分支中删除提交 B
和 C
(保留 D
),但将它们保留在 another
分支中在主人身上。
所以在转换之后我的树应该是这样的:
A --- D (master)
\
B --- C --- E (another)
我想,我可能应该只变基 master
,但是我不确定 B
和 C
是否仍会包含在 another
中提到从中删除/省略 D
。
我应该如何进行才能达到上述效果?
假设您要移动变更集,应该没有那么难:
git rebase --onto A C master
这会将分支 master
移动到 A 之上,放弃直到 C 的修订(因此我只会移动 D 以及分支指针)。那么:
git rebase --onto C D another
这会将 E 变基到 C 之上,放弃直到 D 的修订(换句话说,仅移动 C 之上的 E...同时移动分支指针)。
应该可以。
实现它的另一种方法是简单地使用交互式变基:
https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History
从 master 创建另一个分支并手动重写它们:
$ git checkout -b another
$ git rebase -i
git rebase
没有参数会给你一个列表,其中包含该分支上的所有提交。然后只需在提交列表中用 'd' 标记要删除的那些。例如对于大师,你想要 "remove" B、C 和 E。
d b72a395 E
pick 5bca8d9 D
d 15aab26 C
d 25aab26 B
pick 35aab26 A
对于 "another" 分支,标记 "D" 将被删除。
要得到你想要的结果——好吧,可能是你想要的结果,至少——你必须停止使用现有的提交D
和 E
完全,原因是没有人——不是你或 Git 本身——可以改变 任何 关于 任何 现有提交, 提交之间的连接实际上是哈希 ID 存储在内部 parent/child 对的子项中。
也就是说,给定第一张图,提交 A
是根提交:它没有父项。没有箭头说 A
之前的提交是 _____ 因为 A
之前没有提交。但是提交B
确实有一个箭头,从它指向提交A
:我之前的提交是提交A
。提交 C
包含指向 B
的箭头; D
包含指向 C
的箭头; E
包含指向 D
:
A <-B <-C <-D <-E
不同于提交,分支名称可以更改:它们充当指向您选择的任何一个提交的箭头。因此 master
当前指向现有提交 D
,并且 another
指向现有提交 E
。 Git可以从another
开始找E
,用E
找D
,用D
找C
,还有很快;或者Git可以从master
开始找D
,这样就可以找到C
和B
和A
.
您想要的结果有一个提交 B
指向 A
,C
指向 B
,因此现有提交通过 C
都很好。但是您想要 D
的新改进变体,它不是指向 C
,而是直接指向 A
.
这个新的和改进的 D'
大概有一个现有提交没有的快照。要为 D'
制作快照,您希望 Git 获取 C
和 D
中快照之间的差异,并将该差异应用于 [=] 中的快照23=].
Git 可以自动执行此操作。执行此操作的基本 Git 命令是 git cherry-pick
。我们稍后会看到如何使用 git rebase
到 运行(正确的一组)git cherry-pick
命令,但让我们从 cherry- 开始选择自己。
同样,你想要一个 E
的新的和改进的 copy,我们可以称之为 E'
,其中的改进是:
- 指向
C
,而不是D
;和 - 有一个快照是通过将快照
D
和E
之间的差异应用于C
中的快照而制作的。
同样,这是 git cherry-pick
的工作。那么让我们看看如何做到这一点。
使用git cherry-pick
要创建父级为 A
的新的和改进的 D'
,我们必须首先 git checkout
提交 A
本身,最好还在那里附加一个临时分支名称以免混淆。 (在内部,使用 git rebase
,Git 使用 no 临时分支名称完成所有这些。)所以我们将 运行:
git checkout -b temp <hash-of-A>
这给了我们:
A <-- temp (HEAD)
\
B--C--D <-- master
\
E <-- another
现在我们像这样使用git cherry-pick
:
git cherry-pick <hash-of-D>
# or: git cherry-pick master
这会复制提交 D
,master
指向的那个——我们可以通过它的哈希 ID 或名称 master
给它——到新的提交 D
',temp
现在指向它。 (任何时候我们进行新的提交,Git 将新提交的哈希 ID 存储在 current 分支中:HEAD
附加到。所以 temp
现在指向复制 D'
.)
A--D' <-- temp (HEAD)
\
B--C--D <-- master
\
E <-- another
现在我们需要另一个新的临时分支,指向提交 C
,所以我们 运行 git checkout -b temp2 <em>hash- of-C</em>
。 (除了原始哈希,我们可以使用 Git 必须找到提交 C
的任何其他方式,例如 master~1
,但是原始哈希可以剪切和粘贴,因为只要你剪对了。)这给了我们:
A--D' <-- temp
\
B--C <-- temp2 (HEAD)
\
D <-- master
\
E <-- another
(注意 HEAD
现在是如何附加到 temp2
,因为 git checkout -b
。)现在我们挑选提交 E
来制作 E'
:
git cherry-pick another
会成功,因为 another
指向提交 E
。如果一切顺利,Git 自己进行新的提交,我们有:
A--D' <-- temp
\
B--C--E' <-- temp2 (HEAD)
\
D <-- master
\
E <-- another
我们现在需要做的是强制名称 master
引用提交 D'
,名称 another
引用提交 E'
。现在要做到这一点,我们可以使用 git branch -f
:
git branch -f master temp
git branch -f another temp2
这给了我们:
A--D' <-- master, temp
\
B--C--E' <-- another, temp2 (HEAD)
\
D [abandoned]
\
E [abandoned]
尽管提交 D
和 E
没有 名称——这使得它们很难找到——它们会在你的 Git 存储库中相当长一段时间,通常至少 30 天。 (这可以通过各种 reflog 过期设置来控制。)如果你已经将他们的哈希 ID 保存在某个地方(并且你已经 - 或者更确切地说,Git 已经将哈希 ID 保存在一些reflogs),您仍然可以在这段时间内取回它们。
您现在可以 git checkout
任一原始分支名称并删除两个 temp
名称。
使用 git rebase
执行此操作
git rebase
所做的本质上是1运行[=63=的系列 ] 命令,并通过 运行ning 等效于 git branch -f
来完成所有操作,以强制分支名称指向 last 复制的提交,并且 [=77= 】 那个分支。 git rebase
将复制的提交集来自 rebase 所称的 upstream 参数。 rebase 将它们复制到的位置,就像 git cherry-pick
一样,来自 rebase 调用它的 onto 参数。
也就是你运行:
git rebase --onto <target> <upstream>
其中 target
是您想要在第一个复制提交之前的提交,并且 upstream
告诉 Git 什么提交 而不是 复制。这个 "what not to copy" 一开始看起来很奇怪,但你会习惯它。2 它也允许你在大多数时候省略 --onto
(虽然不是在你的具体情况)。
Git 所做的是枚举 <em>upstream</em>..HEAD
中的提交,排除某些通常不受欢迎的提交。3 这会产生一个应该复制/挑选的提交哈希 ID 列表。此列表被保存到一个临时文件中。4 然后,Git 运行 是 git checkout
的 HEAD 分离变体以检查 target
提交 --onto
,或者 upstream
如果您没有指定 --onto
。然后,Git 对保存的哈希 ID 进行挑选。最后,如果一切顺利,Git 强制将分支及其 HEAD
重新附加到 rebase 操作中最后复制的提交。
对于您的特殊情况,
1我在这里说 好像 一样,因为有些 rebase 方法使用其他方法,有些 rebases 字面意思 运行 git cherry-pick
,或者——在最现代的 Git 中——直接构建在 Git 内部调用的 sequencer 中,它实现了 cherry -采摘。
2由于Git的A..B
限制语法,这实际上是自然的。这告诉Git:找到可从到达的提交B
,排除那些可从到达的提交A
. 有关可达性的(更多)信息,请参阅 Think Like (a) Git.
3不受欢迎的是现有的合并提交,以及任何已经被挑选出来的提交。 Git 使用 git patch-id
程序找到后者。描述得当有点棘手,这里就不赘述了。
4它在 .git
之下,但在 Git 的开发过程中位置已经移动。根据其他情况,有时您可以在 .git/rebase-todo
或类似的名称中找到它们,如果您好奇的话。