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


我想将我已经承诺掌握的最后几个提交移动到一个新的分支,然后在完成这些提交之前将主人带回去。不幸的是,我的Git-fu还不够强大,有什么帮助吗?

即我该怎么做呢?

master A - B - C - D - E

这个?

newbranch     C - D - E
             /
master A - B 

3763
2017-10-27 03:07


起源


注意:我问了相反的问题 这里 - Benjol
可能重复 使用git将提交从master移到分支上 - Chris Moschini
eddmann.com/posts/... 这一个有效 - Sagar Naliyapara
这里的评论被清除了吗?我问,因为在我每两个月访问这个问题时,我总是按照这个评论滚动。 - Tejas Kale


答案:


搬到新的分支机构

警告: 此方法有效,因为您使用第一个命令创建新分支: git branch newbranch。如果你想将提交移动到 现有分支 您需要在执行之前将更改合并到现有分支中 git reset --hard HEAD~3 (看到 转移到现有分支 下面)。 如果您不先合并更改,它们将会丢失。

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

# 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,只需提供提交的哈希值(或类似的引用) 产地/主)你想“恢复到”  (/ current)分支,例如:

git reset --hard a1b2c3d4

* 1你会的 只要 从主分支“失去”提交,但不要担心,你将在新分支中提交这些提交!

警告: 使用Git 2.0及更高版本,如果您以后再使用 git rebase 原始的新分支(master)分支,你可能需要一个明确的 --no-fork-point 在rebase期间的选项,以避免丢失结转提交。有 branch.autosetuprebase always set使这更有可能。看到 John Mellor的回答 详情。

转移到现有分支

如果你想将你的提交移动到 现有分支,它看起来像这样:

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

4796
2017-07-22 22:37



特别是, 别 尝试回到上次将提交推送到另一个存储库的位置,而另一个存储库可能是其他人可能已经提取的。 - Greg Hewgill
想知道你是否可以解释为什么这有效。对我来说,你正在创建一个新的分支,从你仍在的旧分支中删除3个提交,然后检查你所创建的分支。那么你删除的提交如何神奇地出现在新的分支中? - Jonathan Dumaine
@Jonathan Dumaine:因为我在从旧分支中删除提交之前创建了新分支。他们仍然在新的分支机构。 - sykora
git中的分支只是指向历史提交的标记,没有任何克隆,创建或删除(标记除外) - knittl
另请注意:请勿对工作副本中未提交的更改执行此操作!这只是我的意思! :( - Adam Tuttle


对于那些想知道它为什么起作用的人(就像我最初的那样):

您想要返回C,并将D和E移动到新分支。这是它最初的样子:

A-B-C-D-E (HEAD)
        ↑
      master

git branch newBranch

    newBranch
        ↓
A-B-C-D-E (HEAD)
        ↑
      master

git reset --hard HEAD~2

    newBranch
        ↓
A-B-C-D-E (HEAD)
    ↑
  master

由于分支只是一个指针,  指向最后一次提交。当你做的时候 newBranch,你只是创建了一个指向最后一次提交的新指针。然后用 git reset 你感动了  指针返回两个提交。但既然你没动 newBranch,它仍然指向它最初的提交。


848
2018-02-07 16:58



你需要--hard,因为否则git会从工作目录中的重置提交中留下更改(或者至少对我而言)。 - Andrew
我还需要做一个 git push origin master --force 用于在主存储库中显示的更改。 - Dženan
这个答案导致提交丢失: 下次你 git rebase,3个提交将被默默地丢弃 newbranch。看到 我的答案 详情和更安全的替代品。 - John Mellor
@John,那是胡说八道。在不知道你正在做什么的情况下进行重新定位会导致丢失。如果你丢失了提交,我很抱歉,但这个答案并没有丢失你的提交。注意 origin/master 没有出现在上图中。如果你推到 origin/master 然后做出上面的修改,当然,事情会变得有趣。但这是一个“博士,当我这样做时会受到伤害”这样的问题。它超出了原始问题的范围。我建议你写一些自己的问题来探索你的场景而不是劫持这个场景。 - Ryan Lundy
@John,在你的回答中,你说“不要这样做! git branch -t newbranch“。回去再看看答案。 没有人 建议这样做。 - Ryan Lundy


一般来说...

在这种情况下,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

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

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

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


343
2018-03-26 08:13



OP不是试图移动 从 主 至 newbranch?如果你在master上进行挑选,那么你将添加到master分支 - 实际上是添加它已经拥有的提交。并且它不会将任何一个分支头移回B.或者是否有一些微妙而酷的东西我没有得到? - RaveTheTadpole
如果您在创建新功能分支时意外提交了错误的非主分支,则此方法非常有效。 - julianc
在某些情况下,+1是有用的方法。如果您只想将自己的提交(与其他人一起穿插)放入新分支,这是很好的。 - Tyler V.
这是更好的答案。这样您就可以将提交移动到任何分支。 - skywinder
樱桃采摘的顺序是否重要? - kon psych


另一种方法是使用2个命令。同时保持当前工作树的完整性。

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

旧版  - 在我了解之前 git branch -f

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

能够 push 至 . 知道这是一个很好的技巧。


252
2018-04-06 22:38



当前目录。我想这只有在你位于顶级目录时才有效。 - aragaer
当地的推动是诱人的,但经过反思,它有何不同 git branch -f 这里? - jthill
@GerardSexton . 现任董事。 git可以推送到REMOTES或GIT URL。 path to local directory 支持Git URL语法。请参阅中的GIT URLS部分 git help clone。 - weakish
我不知道为什么这个评级不高。死简单,没有git重置的小但潜在的危险 - 硬。 - Godsmith
@Godsmith我的猜测是人们更喜欢三个简单的命令,而不是两个稍微模糊的命令。此外,最高投票的答案通过首先显示的性质得到更多的赞成。 - JS_Riddler


以前的大多数答案都是错误的!

不要这样做:

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

下次你跑的时候 git rebase (要么 git pull --rebase)这三个提交将被默默地丢弃 newbranch! (见下面的解释)

而是这样做:

git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
  • 首先它丢弃最近的3个提交(--keep 就好像 --hard,但更安全,因为失败而不是丢弃未提交的更改)。
  • 然后它分叉了 newbranch
  • 然后樱桃挑选那3个提交回来 newbranch。因为它们不再被分支引用,所以它通过使用git来实现 引用日志HEAD@{2} 是提交 HEAD 用于指代之前的2个操作,即在我们签出之前 newbranch 和2.使用 git reset 丢弃3个提交。

警告:默认情况下启用了reflog,但是如果你手动禁用它(例如通过使用“裸”git存储库),你将无法在运行后恢复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)。


技术说明

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

假设你在包含提交M1,M2,M3的情况下对origin / master进行分支,然后自己进行三次提交:

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

然后有人通过强制推动origin / master来删除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保持在正确的状态。

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


210
2017-10-19 14:12



这个答案说“不要这样做!”高于没有人建议的事情。 - Ryan Lundy
@Kyralessa, -t 你指的是 git branch 如果你有,就会隐含地发生 git config --global branch.autosetuprebase always 组。即使你不这样做,我 已经解释过了 如果您在执行这些命令后设置跟踪,则会出现同样的问题,因为OP可能会打算提出问题。 - John Mellor
@RockLee,是的,修复这种情况的方法是从安全的起点创建一个新的分支(newbranch2),然后挑选你想要保留的所有提交(从badnewbranch到newbranch2)。采摘樱桃会给提交新的哈希,所以你将能够安全地改变newbranch2(现在可以删除badnewbranch)。 - John Mellor
@Walf,你误会了: git rebase 被设计为对重写其历史的上游具有强大的功能。不幸的是,这种鲁棒性的副作用会影响每个人,即使他们和他们的上游都没有重写历史。 - John Mellor
你也可以使用 --no-fork-point 当你跑步 git rebase。 - torek


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

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)

27
2018-01-21 16:10



你不能用 rebase 为了同样的事情? - Bergi
是的你可以选择使用 rebase 在上面的场景中的分离分支上。 - Sukima


要在不重写历史记录的情况下执行此操作(即,如果您已经推送了提交):

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

然后可以无力推动两个分支!


19
2017-09-20 10:17



但是你必须处理恢复情况,这取决于你的情况,可能会更加棘手。如果你在分支上恢复提交,Git仍然会看到这些提交已经发生,所以为了撤消它,你必须恢复恢复。这会烧掉很多人,特别是当他们恢复合并并尝试合并分支时,才发现Git认为它已经合并了那个分支(这是完全正确的)。 - Makoto
这就是为什么我在最后挑选提交到新分支的原因。这样git认为它们是新的提交,它解决了你的问题。 - teh_senaus
这比最初看起来更危险,因为你正在改变存储库历史的状态而不真正理解这种状态的含义。 - Makoto
我不遵循你的论点 - 这个答案的要点是你不是在改变历史,只是添加新的提交(有效地撤消重做的改变)。这些新提交可以正常推送和合并。 - teh_senaus


只是这种情况:

Branch one: A B C D E F     J   L M  
                       \ (Merge)
Branch two:             G I   K     N

我表演了:

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

我期望提交我将成为HEAD,但提交L现在是......

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

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

12
2018-05-01 07:50





更简单的解决方案

如果:

  1. 您的主要目的是回滚 master,和
  2. 你想保持变化,但不要特别关心 个人承诺,和
  3. 你还没推,

然后以下更简单(从分支开始) master 错误提交):

git reset HEAD~3
git stash
git checkout newbranch
git stash pop

这是做什么的:

  1. 撤消最后三次提交(及其消息) master,但保留所有工作文件
  2. 存储所有工作文件的变化,使得 master 工作树等于HEAD~3状态。
  3. 切换到现有分支 newbranch
  4. 将已更改的文件从存储中拉出并进入工作目录。

你现在可以使用了 git add 和 git commit 像往常一样。所有更改都将在 newbranch

这不做什么:

  • 它不会让随机的临时分支混乱你的树。
  • 它不会保留错误的提交和提交消息。 您需要向此新提交添加新的提交消息。然而,OP表示目标是“在做出这些提交之前将主人带回来”而不会丢失更改,而这个解决方案就是这样做的。

我每周至少做一次,当时我不小心做了新的提交 master 代替 develop。通常我只有一个提交回滚在这种情况下 git reset HEAD^ 只能回滚一次提交。


8
2018-06-01 05:40