使用Git将最近的提交移到新的分支

我想把我承诺的最后几个提交到一个新的分支,并且在提交之前把master提交回去。 不幸的是,我的Git-fu还不够强大,有什么帮助吗?

也就是说,我怎么能从这个

master A - B - C - D - E 

这个?

 newbranch C - D - E / master A - B 

搬到一个新的分支

除非涉及其他情况,否则可以通过分支和回滚来轻松完成。

 # Note: Any changes not committed will be lost. git branch newbranch # Create a new branch, saving the desired commits git reset --hard HEAD~3 # Move master back by 3 commits (GONE from master) git checkout newbranch # Go to the new branch that still has the desired commits 

但是确保有多少次提交返回。 或者,你可以不用HEAD~3 ,只需在 (/当前)分支上提供你想要“还原回去”的提交(或起源/主引用)的散列,例如:

 git reset --hard a1b2c3d4 

* 1你只会 “失去”主分支的提交,但不用担心,你将会在newbranch提交这些提交。

警告:使用Git 2.0及更高版本,如果您稍后在原始( master )分支上重新git rebase新分支,则可能需要在重新--no-fork-point期间显式指定--no-fork-point选项,以避免遗失提交。 有了branch.autosetuprebase always会使得这个更可能。 有关详细信息,请参阅John Mellor的答案 。

转移到现有的分支

警告:上面的方法工作,因为你正在创建一个新的分支与第一个命令: git branch newbranch 。 如果你想使用现有的分支,你需要在执行git reset --hard HEAD~3之前将你的修改合并到现有的分支中 。 如果您不先合并您的更改,他们将会丢失。 所以,如果您正在使用现有的分支,它将如下所示:

 git checkout existingbranch git merge master git checkout master git reset --hard HEAD~3 # Go back 3 commits. You *will* lose uncommitted work. git checkout existingbranch 

对于那些想知道为什么它起作用的人(正如我起初):

你想回到C,然后把D和E移到新的分支。 首先是这样的:

 ABCDE (HEAD) ↑ master 

git branch newBranch

  newBranch ↓ ABCDE (HEAD) ↑ master 

git reset --hard HEAD~2

  newBranch ↓ ABCDE (HEAD) ↑ master 

由于分支只是一个指针,所以master指向最后一个提交。 当你创建newBranch时 ,你只需要创建一个新的指针,指向最后一个提交。 然后使用git reset指针移回两个提交。 但是既然你没有移动newBranch ,它仍然指向它原来的提交。

一般来说…

sykora暴露的方法在这种情况下是最好的选择。 但有时并不是最简单的方法,也不是一个通用的方法。 对于一般的方法使用git cherry-pick

为了实现OP的目标,需要两个步骤:

第1步 – 请注意,你需要在newbranch上提交的主人

执行

 git checkout master git log 

注意(如3)的散列提交你想在newbranch 。 在这里,我将使用:
C提交: 9aa1233
D提交: 453ac3d
E提交: 612ecb3

注意:您可以使用前七个字符或整个提交哈希

第2步 – 把它们放在新newbranch

 git checkout newbranch git cherry-pick 612ecb3 git cherry-pick 453ac3d git cherry-pick 9aa1233 

OR(在Git 1.7.2+上,使用范围)

 git checkout newbranch git cherry-pick 612ecb3..9aa1233 

git cherry-pick将这三个提交应用于newbranch。

还有另一种方法来做到这一点,只使用2个命令。 也保持你目前的工作树完好无损。

 git checkout -b newbranch # switch to a new branch git push . +HEAD~3:master # make master point to some older commit 

能够push. 是一个很好的把戏知道。

晚编辑:现在我知道git branch -f正确的方法是:

 git checkout -b newbranch # switch to a new branch git branch -f master HEAD~3 # make master point to some older commit 

这是一样的,但少一些“魔术”

大多数以前的答案是非常危险的!

不要这样做:

 git branch -t newbranch git reset --hard HEAD~3 git checkout newbranch 

当你下次运行git rebase (或者git pull --rebase )的时候,这3个提交会被从newbranch默默丢弃! (见下面的解释)

而是这样做:

 git reset --keep HEAD~3 git checkout -t -b newbranch git cherry-pick ..HEAD@{2} 
  • 首先丢弃最近的3个提交( --keep就像 – --keep ,但更安全,因为失败而不是扔掉未提交的更改)。
  • 然后它newbranch
  • 然后,樱桃newbranch 3次提交回新的newbranch 。 因为它们不再被分支引用,所以它通过使用git的reflog来实现 : HEAD@{2}HEAD用于引用2个操作前的提交,即在我们之前1.检出newbranch和2.使用git reset放弃3提交。

警告:reflog是默认启用的,但是如果你已经手动禁用它(例如使用“裸”的git仓库),那么在运行git reset --keep HEAD~3之后,你将不能得到3提交git reset --keep HEAD~3

不依赖于reflog的替代方案是:

 # newbranch will omit the 3 most recent commits. git checkout -b newbranch HEAD~3 git branch --set-upstream-to=oldbranch # Cherry-picks the extra commits from oldbranch. git cherry-pick ..oldbranch # Discards the 3 most recent commits from oldbranch. git branch --force oldbranch oldbranch~3 

(如果你喜欢,你可以写@{-1} – 先前签出的分支 – 而不是oldbranch )。


技术说明

为什么会在第一个例子之后放弃3次提交? 这是因为没有参数的git rebase默认启用了--fork-point选项,它使用本地reflog来强制上游分支被强制推送。

假设你在包含提交M1,M2,M3的情况下分支了原始/主机,然后自己做了三个提交:

 M1--M2--M3 <-- origin/master \ T1--T2--T3 <-- topic 

但有人通过强制推动起源/主人重写历史M2:

 M1--M3' <-- origin/master \ M2--M3--T1--T2--T3 <-- topic 

使用你的本地reflog, git rebase可以看到你从原始/主分支的早期版本中分离出来,因此M2和M3的提交并不是你的主题分支的真正组成部分。 因此,可以合理地假设,既然M2从上游分支中被移除了,那么一旦主题分支被重新设置,你不再需要在你的主题分支中:

 M1--M3' <-- origin/master \ T1'--T2'--T3' <-- topic (rebased) 

这种行为是有道理的,而且一般来说在重新绑定时是正确的。

所以下面的命令失败的原因是:

 git branch -t newbranch git reset --hard HEAD~3 git checkout newbranch 

是因为他们将reflog置于错误的状态。 Git认为newbranch在包含3个提交的版本中分支了上游分支,然后reset --hard重写了上游的历史记录以删除提交,所以下次运行git rebase它将丢弃它们,就像任何其他提交已从上游移除。

但在这个特殊情况下,我们希望这3个提交被视为主题分支的一部分。 为了达到这个目的,我们需要在不包含3个提交的早期版本中分离上游。 这就是我的建议解决方案,因此他们都将reflog保持在正确的状态。

有关更多详细信息,请参阅git rebase和git merge-base文档中的--fork-point的定义。

在技​​术意义上这不会“移动”它们,但它具有相同的效果:

 A--B--C (branch-foo) \ ^-- I wanted them here! \ D--E--F--G (branch-bar) ^--^--^-- Opps wrong branch! While on branch-bar: $ git reset --hard D # remember the SHAs for E, F, G (or E and G for a range) A--B--C (branch-foo) \ \ D-(E--F--G) detached ^-- (branch-bar) Switch to branch-foo $ git cherry-pick E..G A--B--C--E'--F'--G' (branch-foo) \ E--F--G detached (This can be ignored) \ / D--H--I (branch-bar) Now you won't need to worry about the detached branch because it is basically like they are in the trash can waiting for the day it gets garbage collected. Eventually some time in the far future it will look like: A--B--C--E'--F'--G'--L--M--N--... (branch-foo) \ \ D--H--I--J--K--.... (branch-bar) 

要做到这一点,而不重写历史(即如果你已经推动了提交):

 git checkout master git revert <commitID(s)> git checkout -b new-branch git cherry-pick <commitID(s)> 

两个分支都可以毫不费力地推动!

刚刚出现这种情况:

 Branch one: ABCDEFJLM \ (Merge) Branch two: GIKN 

我表演了:

 git branch newbranch git reset --hard HEAD~8 git checkout newbranch 

我希望承诺我会是头,但现在提交L …

为了确保在历史上正确的位置,更容易使用提交的散列

 git branch newbranch git reset --hard ######### git checkout newbranch