强制推送后重新设置

时间:2020-03-26 10:47:07

标签: git branch rebase

我有一个特征分支A。然后我开始开发依赖于A的第二个特征,因此我将新的特征分支B基于A:

git checkout A
git checkout -B B

我在B上做了一些工作,所以现在在B上我有提交1(来自A)和新的提交2。 我们公司总是尽可能地将一个PR的所有提交压缩在一起,所以在某一点上我要强制A,以便A仅提交1'。现在,我想将B重新设置为A(或在A合并后为主),但是由于我强制按下A,因此git尝试应用commit 1,这显然失败了。

2种解决方法,但都不是很好:

使用git cherry-pick:

git checkout B
git checkout -B B2
git log // copy latest commit id
git checkout B
git reset --hard A
git cherry-pick <commit-id>

使用软重置:

git checkout B
git reset --soft HEAD~1
git stash
git reset --hard A
git stash pop
git commit -a -m "msg"

是否存在用于解决此问题的“ git方法”?我知道始终压缩提交可能不是最佳实践,但是我不能改变。还是有更好的方法将一个分支建立在另一个分支上?

1 个答案:

答案 0 :(得分:1)

最终,您需要git rebase --onto。有时候,您不需要做任何特别的事情。

设置

让我们来介绍一下您的初始情况:

...--A--B   <-- master
         \
          C   <-- feature/A
           \
            D   <-- feature/B

也就是说,在某条主线上有一系列提交(我在这里将其称为master,但可能是develop或其他),然后在您的feature/A上进行一次提交,然后对您的feature/B进行一次提交。 D上的提交feature/B的父级是Cfeature/B上的提交feature/A

稍后,您已经向feature/A添加了第二个提交,得到:

...--A--B   <-- master
         \
          C--E   <-- feature/A
           \
            D   <-- feature/B

最终,feature/A将被合并到master,并且根据某些策略规则,您已经进行了一次新的提交F,它是CE,以便您现在拥有:

          F   <-- feature/A
         /
...--A--B   <-- master
         \
          C--E   [abandoned]
           \
            D   <-- feature/B

在这一点上,您想将D复制到一些新的提交D'上,该提交与D与其父项的区别完全相同,但其中{{1} }的父级是D',而不是F

Git提供了一种简单的方法来获取您想要的东西:

C

问题是git checkout feature/B git rebase --onto feature/A something-goes-here 部分。那里有什么?

复制一些提交

实际上,something-goes-here命令只是一系列git rebase命令,后跟一个分支标签动作。正如您已经发现的那样,git cherry-pick执行您想要的操作:它复制一个提交。实际上,它可以复制多个提交(使用Git内部称为 sequencer 的东西)。

也就是说,它会将要复制的每个提交与提交的 parent 进行比较,以查看更改的内容。然后,它对 current 提交进行相同的更改,如果一切顺利,则提交结果。

例如,让我们从这种情况开始。目前,我使用一个新标签git cherry-pick来记住提交saved-A,并添加了名称E并在括号中添加了new-B显示当前分支HEAD,而当前提交是提交new-B

F

我们现在可以运行 F <-- feature/A, new-B (HEAD) / ...--A--B <-- master \ C--E <-- saved-A \ D <-- feature/B 。我们告诉Git:比较提交git cherry-pick feature/B与它的父D,然后对现在的位置进行相同的更改,提交C,然后提交结果。 / em>如果一切顺利,我们将得到:

F

我们现在要做的就是将名称 D' <-- new-B (HEAD) / F <-- feature/A / ...--A--B <-- master \ C--E <-- saved-A \ D <-- feature/B 移至指向提交feature/B的位置,然后删除名称D'

new-B

同样,第一部分就是 D' <-- feature/B (HEAD) / F <-- feature/A / ...--A--B <-- master \ C--E <-- saved-A \ D [abandoned] 所做的:复制一个提交。其中的 last 部分是git cherry-pick所做的:移动分支标签,例如git rebase

此处的关键是feature/B复制 some 个提交。 哪个? 默认答案对您来说是错误的答案!

git rebase的作用简而言之

让我们看一下稍有不同的图形:

git rebase

在这里,我们在分支...--A--B <-- target \ C--D--E <-- current (HEAD) 上,即current会说git statuson branch current的最先提交是提交currentE的哈希ID是存储在名称E中的哈希ID。

如果我们现在运行:

refs/heads/current

Git会将git rebase target 提交复制到新提交C-D-E并将新提交放置在C'-D'-E'之上,然后移动分支名称,如下所示:

target

通常这就是我们想要的。但是: C'-D'-E' <-- current (HEAD) / ...--A--B <-- target \ C--D--E [abandoned] 怎么知道要复制git rebase但也不知道要复制C-D-E

答案是A使用Git内部的“列出一些提交”操作git rebase,并带有停止点。重新编制文档声称git rev-list所做的是运行的:

git rebase

这是一个白色的谎言:它足够接近且具有说明性。确切的细节比较棘手,我们稍后将解决。现在,让我们看一下git rev-list target..HEAD 的{​​{1}}部分。这告诉Git:不要列出从目标开始并向后工作可以找到的任何提交。

由于target..名提交target..HEAD,这意味着:请勿复制提交target 。好吧,我们已经不打算复制commit B,所以没什么大不了的。但这 的意思是:不要复制提交B 。为什么不?因为提交B指向提交A。提交B在两个分支AA上。所以我们复制了target,但我们没有复制,因为它在请勿复制列表中。也有之前 current的提交,但是它们都在请勿复制部分中,因此都不会被复制。

因此,将A的提交复制到此处:它们在“要复制”列表中,而不是由于不在“复制”列表中而终止。

因此,A概括地说是这样的:

  1. 记住C-D-E附加到哪个分支。
  2. 列出一些要复制的提交哈希ID。
  3. 从当前分支中分离git rebase
  4. 一次复制一次列出的提交,就像HEAD一样。
  5. HEAD所附加的分支名称
  6. 移动到我们现在的位置。
  7. git cherry-pick重新连接到已移动的分支。

请注意,在第4步中可能会出错。特别是,复制提交,就像通过HEAD一样(无论是否实际使用HEAD)都可能发生合并冲突< / em>。如果是这样,则重新定位将在中间停止,带有分离的HEAD。因此,了解步骤3很重要。但是,我们会将其留给其他问题和答案(以及有关重新设置是否确实确实使用“自动选择”的详细信息:有时会使用,有时会伪造)。

关于哪些提交被复制的真相

我们提到上面的git cherry-pick是一个谎言:一种简化,旨在使人们更容易理解要复制哪些提交。现在是时候了。

  • 首先,git cherry-pick通常忽略合并完全提交。如果是合并(具有两个或多个父级),则由以上target..HEAD生成的所有提交都将被剔除。只要列表中没有合并提交,就没关系了。

  • 第二,git rebase 也忽略了与某些其他提交 patch-ID等效的提交。这使用git rev-list程序。除了观察要获得“其他提交”部分之外,我们在这里不做任何详细介绍,Git实际上必须使用带有三个点的git rebase。这将产生一个对称差异列表,该列表可从git patch-id到达,但不能成为目标,并且也可以从git rev-list target...HEAD而到达,但不能到达HEAD。有关可达性的更多信息,请参见Think Like (a) Git。然后,rebase命令在两个列表中的每个提交上使用target(它是内部生成的,因此它知道哪个提交哈希与哪个列表一起使用),并剔除具有匹配补丁ID的列表。这样做的效果是,例如,如果提交HEAD与提交git patch-id已经已经相同(樱桃选择),而不是复制{{1} },我们只需复制B,即可获得:

    D

    因为提交C-D-EC-E“做同样的事情”。

  • 最后,对我们来说最重要的是, C'-E' <-- current (HEAD) / ...--A--B <-- target \ C--D--E [abandoned] 使我们可以使用其他 target

在上面的示例中,我们运行了:

B

D既是--onto stop参数,也是我们Git放置副本的目标的目标。但是我们可以运行:

git rebase target

,现在target将对git rev-list stop..HEAD的{​​{1}}部分使用我们的 stop 参数,同时继续使用我们的 target 副本所在位置的参数。

所以,假设我们现在得到了 this

git rebase --onto target stop

然后我们运行:

git rebase

我们现在告诉Git,我们的基准站的 stop 参数是stop,它选择提交git rev-list。我们的基准将在...--A--B <-- target \ C <-- another \ D--E <-- current (HEAD) git rebase --onto target another 上使用another,这意味着要复制的提交列表将仅由C组成。

该列表将通过patch-id和no-merges规则进一步过滤,但是只要git rev-list another..HEAD不同,我们最终会得到:

C..E

也就是说,我们将仅复制{em> 从{{1}可以访问的两个提交D-E,而忽略{{1}可以访问的提交B }}。

将它们放在一起

这是您要进行提交复制时的设置:

D

请注意,我们添加了名称 D'-E' <-- current (HEAD) / ...--A--B <-- target \ C <-- another \ D--E [abandoned] ,以记住要复制的 not 。我们不想复制提交D-Ecurrent。我们还是不会复制C,但这是记住所有内容不复制的简单方法。

我们目前有another个签出(提交 F <-- feature/A / ...--A--B <-- master \ C--E <-- saved-A \ D <-- feature/B (HEAD) )。我们不需要创建名称saved-A,所以我们不需要这样做。现在我们运行:

C

Git现在将列出要复制的提交:当前分支E上的每个提交,除了E上的每个提交。这就是提交feature/B

Git现在分离HEAD,移动到提交D(我们的new-B目标)并复制git rebase --onto feature/A saved-A 来产生feature/B。这样就完成了要复制的提交列表,因此,在成功将saved-A复制到D之后,Git强制将名称F移到--onto并重新附加{{1 }},给我们:

D

这正是我们想要的。

我们现在可以删除名称D'

如果您不保存姓名怎么办?

如果您已经重新建立了D的基础,但是忘记将提交D'的提交哈希ID保存在某个地方怎么办?

幸运的是,您没有保存了feature/BD'的哈希ID。您可以:

  • 使用HEAD查找他们,或
  • 使用 D' <-- feature/B (HEAD) / F <-- feature/A / ...--A--B <-- master \ C--E <-- saved-A \ D [abandoned] 查找saved-A用来命名的哈希ID,或者
  • 做您想找到的其他任何事情。

原始哈希ID有效,因此您可以运行:

feature/A

找到哈希ID之后。 (使用剪切和粘贴或类似方法正确获得哈希ID;手工输入它甚至是它的唯一前缀是导致错误的方法。)

引用名称也可以,因此通常您可以这样做:

E

其中E是提交C的哈希ID时您看到的引用日志名称,当您运行git log列出git reflog的先前哈希ID时。 (feature/A可能会提交git rebase --onto feature/A <hash-ID-of-E-or-C> ,因此也可以使用。)

关键是找到要忽略的提交,并将其与git rebase --onto feature/A feature/A@{1} 一起使用。根据副本应放置的位置设置 target ,并将停止点(the git rebase documentation调用 upstream 参数的对象)设置为哈希ID,停止您不想要复制的提交。

什么时候不需要什么特别的东西?

如果压缩的提交具有与原始提交相同的补丁ID,则feature/A@{1}省去具有匹配补丁ID的提交将为您完成所有工作。通常,只有在您将一个提交压榨合并到其他分支的情况下,才会发生这种情况。

E技巧始终有效,因此您实际上不必担心这种情况,但是如果发生的次数很多,很高兴知道。