将放错位置的提交移动到新分支

时间:2018-09-12 08:23:08

标签: git git-revert git-cherry-pick

设置: 我已将代码更改提交到本地和远程,但是都在错误的分支中。

我的解决方案:

  1. 已检出master并创建了一个分支,代码应更改为
  2. Cherry从错误的分支签入中选择了提交并提交到新分支。
  3. 签出错误的分支。重置为第一个正确的提交 然后再次强制将其删除以删除远程上的错误提交 分支。

问题:这是要走的路吗?如果我用google搜索,就会发现人们使用了还原功能,但我不明白为什么会如此,因为它看起来更复杂,更危险。为什么我应该使用还原功能?

3 个答案:

答案 0 :(得分:1)

当您revert进行某些提交时,您将创建否定目标提交的提交。结果与reset相同,但您不必force push,可以执行简单的push,因为您添加到历史记录中不会将其删除。您可以revert提交的另一个历史上没有的差异。在这种情况下,您不能使用reset,因为它会导致失去目标提交后的所有提交。

您可能还会看到以下问题:What's the difference between Git Revert, Checkout and Reset?

答案 1 :(得分:1)

我建议您改用为基础,而不是3

git rebase --onto COMMIT_BEFORE_WRONG WRONG_COMMIT branch_with_wrong_commit
git push --force-with-lease

这“削减”了错误的提交。

添加-i(对于 interactive )时,您可以检查正确的提交是否已移动。


相同的结果将带有:

git rebase -i COMMIT_BEFORE_WRONG

,然后在显示的“ todo”文件的第一行中将单词pick更改为drop

答案 2 :(得分:1)

  

我[看到]人们使用还原,但我为什么不理解为什么它看起来更复杂,更危险。为什么我应该使用还原?

应该是一个很强的词。 (不如 shall must 那么强,但至少相当强。:-))

  

...重置为第一个正确的提交,然后再次强制将其删除以删除远程分支上的错误提交

每当必须使用git push --force或同等名称时,您就会以其他人可能期望的方式移动分支名称。 (在部分顺序中,力量应该弱于:我会说 may 。)特别是, Git分支名称自然会以一种渐进的方式移动,在该过程中,提交会被添加到分支,这是由于分支通过提交而增长的方式。

考虑:

$ git checkout <somebranch>
... work ...
$ git add <files>  # or --all or . or whatever
$ git commit

git checkout步骤的作用是将HEAD附加到分支,并将分支名称指向的 tip commit 复制到您的Git索引中和工作树,以便您可以对其进行处理:

... <-F  <-G  <-H   <--branch (HEAD)

名为 branch 的分支(全名是refs/heads/branch的引用)存储某些提交H的原始哈希ID。提交H本身存储其父提交G的原始哈希ID,该原始哈希ID存储某些提交F的原始哈希ID,依此类推。因此,我们说名称​​指向尖端提交H,它指向较早的提交G,依此类推。

git add步骤将更新索引/暂存区,以便可以进行提交,而git commit步骤将创建一个新的提交。新提交将当前签出的提交H的哈希ID作为其父哈希ID存储。 (当然,它将从当前索引/暂存区域创建的冻结快照存储为快照。)然后,作为最后一步,git commit将新提交的哈希ID写入到附加了HEAD分支名称:

... <-F  <-G  <-H  <-I   <--branch (HEAD)

这是人们进行提交时分支增长的方式,一次提交一次。当您合并一系列提交时,无论是作为真正的合并,还是作为一种快进而不是全部合并的操作,分支也将同时获取新的提交,也许一次很多,也许以一种非线性的方式,但是重要的是新的提交总是导致回到现有的提交:

...--F--G--H--I---M   <-- master (HEAD)
         \       /
          J--K--L   <-- develop

将合并提交M添加到master会使H的提交Imaster reachable 生效,因为我们可以按照向后指向内部的提交箭头(在这里以线条显示,因为现在箭头太难绘制文本了)使用M中的最上一行箭头。 (从ML的向左箭头也使我们也可以从M骑到KJ。{{ 3}}与波特兰的公交系统有一个很好的比喻,尽管任何都市火车系统都是相似的。)

但是假设我们改为这样做:

...--F--G--H--X  <-- master (HEAD)
         \
          J--K   <-- develop

然后意识到,哎呀,我们打算将提交X放在develop上。我们使用任何适合将复制 X到新提交的方法,例如cherry-pick或git rebase --onto(两者都做相同的工作)。然后,我们使用git checkout master; git reset --hard master~1X推开,这样它就不再位于master上:

             X
            /
...--F--G--H  <-- master (HEAD)
         \
          J--K--L   <-- develop

(这里LX的副本,放在我们想要的位置。)这种分支名称运动会使提交X悬空而无法找到它,至少,在 our 存储库中没有办法。但是,如果我们已经使用git push将提交X发送到其他地方,则某些 other Git会为其命名。实际上,我们也是如此:

             X   <-- origin/master
            /
...--F--G--H  <-- master (HEAD)
         \
          J--K--L   <-- develop

我们的origin/master是Git记住master上的origin的方式,仍然记得存在X的提交。这意味着origin的Git会记住X在他们的master上。

实际上,这是为什么,我们必须使用git push --force origin master:告诉origin上的Git它应该放弃 X。如果我们在其他任何人(有权访问该Git的人)之前执行此操作,X复制到 Git存储库中,我们很好:没有人看到X,因此移除X不会伤害任何人。

如果别人 did 从另一个Git上抢了一份副本,问题就开始堆积起来。现在,第三个Git存储库仍具有提交X,也许在它们的 master中。也许他们在X上方(他们的副本)建立了新的提交,而他们希望保留这些提交:

...--F--G--H--X--Y--Z   <-- master (HEAD)

我们现在要告诉他们:哦,忘记X,也将其从您的存储库中删除。这需要他们自己做git rebase --onto或类似的事情,将其YZ复制到不再导致回到X的新提交中。

简而言之,通过从我们的Git和X的Git中删除origin,我们为所有共享这些Git存储库的其他人增加了负担:他们也,必须全部删除其X,并处理所有后果。

在某些项目中,每个人都同意这种情况可能发生–在任何时间到任何分支,或在任何时候到分支的某些特定子集。在这些项目中,重设分支机构和强制推动就可以了。在某些项目中,没有其他用户,或者您可以在任何人有机会发现错误之前进行强制推送;在这种情况下,也可以在其中进行复位和强制推动。当您开始为不准备这样做的人做大量工作时,就会出现问题。在这种情况下,只需提交 new 提交而只是撤消 X中的工作,就可以为他们提供一种以<他们准备接受。