使用Git将最新提交移至新分支


4980

我想将我已承诺掌握的最后几个提交移动到新的分支,并在做出这些提交之前将master重新带回。不幸的是,我的Git-fu还不够坚固,有什么帮助吗?

即我该如何去

master A - B - C - D - E

对此吗?

newbranch     C - D - E
             /
master A - B 

113
注:我问对面的问题在这里
Benjol


6
这里的评论被清除了吗?我之所以问是因为,在我每两个月访问此问题期间,我总是滚动浏览该评论。
Tejas Kale

Answers:


6446

移至现有分支

如果要将提交移至现有分支,它将如下所示:

git checkout existingbranch
git merge master         # Bring the commits here
git checkout master
git reset --keep HEAD~3  # Move master back by 3 commits.
git checkout existingbranch

--keep选项将保留您可能在不相关的文件中进行的所有未提交的更改,或者如果必须覆盖这些更改,则中止该操作(与执行操作类似)git checkout。如果中止,则git stash您的更改将重试,或者用于--hard丢失更改(即使来自提交之间未更改的文件!)

移至新分支

此方法通过使用第一个命令创建新分支(git branch newbranch)但不切换到该。然后,我们回滚当前分支(主节点)并切换到新分支以继续工作。

git branch newbranch      # Create a new branch, containing all current commits
git reset --keep HEAD~3   # Move master back by 3 commits (Make sure you know how many commits you need to go back)
git checkout newbranch    # Go to the new branch that still has the desired commits
# Warning: after this it's not safe to do a rebase in newbranch without extra care.

但是,请确保要返回多少次提交。或者,HEAD~3您可以提供提交的哈希值(或类似origin/master要还原回),例如:

git reset --keep a1b2c3d4

警告:在Git 2.0版及更高版本中,如果稍后git rebase将新分支添加到原始(master)分支上,则--no-fork-point在改基期间可能需要显式选项,以避免丢失从master分支移走的提交。拥有branch.autosetuprebase always一套使这个可能性比较大。有关详细信息,请参见John Mellor的答案


249
特别是,不要试图回到上一次将提交推送到可能已从其他人撤回的另一个存储库的那一点。
Greg Hewgill

107
想知道您是否可以解释为什么这样做。对我来说,您正在创建一个新分支,从仍在使用的旧分支中删除3个提交,然后签出您创建的分支。那么,您删除的提交如何神奇地显示在新分支中?
乔纳森·杜马因

142
@Jonathan Dumaine:因为我在删除旧分支中的提交之前创建了新分支。他们仍然在新分支中。
sykora

86
git中的分支只是指向历史记录的标记,没有任何东西可以被克隆,创建或删除(标记除外)
knittl

218
另请注意:不要在工作副本中进行未提交的更改!这只是咬我!:(
亚当·塔特尔

1038

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

您想回到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

由于分支只是一个指针,因此master指向最后一次提交。当您创建newBranch时,您仅创建了指向最后一次提交的新指针。然后使用git reset您将指针移回两次提交。但是由于您没有移动newBranch,所以它仍然指向它最初执行的提交。


55
我还需要做一个git push origin master --force更改,以使更改显示在主存储库中。
南2014年

9
这个答案会导致提交丢失:下次,您git rebase的3个提交将被从中静默丢弃newbranch。请参阅我的答案以获取详细信息和更安全的替代方法。
约翰·梅勒

14
@约翰,这是胡扯。在不知道自己在做什么的情况下进行基础调整会导致提交丢失。如果您丢失了提交,对不起您,但是这个答案并没有丢失您的提交。请注意,origin/master上图中没有出现。如果您推动origin/master并进行上述更改,那么肯定会很有趣。但这是“医生,当我这样做时会很痛”这类问题。这超出了原始问题的范围。我建议您写自己的问题来探讨您的情况,而不是劫持这个问题。
Ryan Lundy

1
@John,在回答中,您说“不要这样做! git branch -t newbranch”。返回并再次阅读答案。 没有人建议这样做。
Ryan Lundy

1
@Kyralessa,当然可以,但是如果您查看问题中的图表,很显然他们希望newbranch基于现有的本地master分支。执行接受的答案后,当用户都绕运行git rebasenewbranch,git会提醒他们,他们忘了设定上游分支,所以他们会跑git branch --set-upstream-to=master,然后git rebase和有同样的问题。他们可能git branch -t newbranch首先使用。
约翰·梅勒

454

一般来说...

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


13
OP难道不是要从主服务器迁移到新分支吗?如果您在master上选择樱桃,那您将添加到master分支中-实际上添加了已经拥有的提交。而且它既不移回分支头又不移到B。还是我没有得到一些微妙而酷的东西?
RaveTheTadpole

6
如果您本应创建新的功能分支时不小心提交了错误的非主分支,则此方法非常有用。
julianc 2014年

5
+1是某些情况下的有用方法。如果您只想将自己的提交(散布在其他提交中)拉到新分支中,则很好。
泰勒五世

7
更好的答案。这样,您可以将提交移动到任何分支。
skywinder 2014年

8
采摘樱桃的顺序重要吗?
kon psych 2015年

328

另一种方法,仅使用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.是一个不错的把戏就知道了。


1
当前目录。我想这只有在顶层目录下才行。
aragaer 2014年

1
局部推动会引起咧嘴笑,但在反射时,它与git branch -f此处有何不同?
jthill 2014年

2
@GerardSexton .是现任导演。git可以推送到REMOTES或GIT URL。path to local directory支持的Git URL语法。请参阅中的GIT URLS部分git help clone
弱化2014年

31
我不知道为什么这个等级没有更高。死法简单,没有git reset的小但潜在的危险-很难。
Godsmith

4
@Godsmith我的猜测是人们喜欢三个简单的命令,而不是两个稍微模糊的命令。同样,投票最多的答案由于首先显示而获得更多的赞誉。
JS_Riddler 2015年

321

先前的大多数答案都是危险的错误提示!

不要这样做:

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例如--hard,但更安全,因为失败而不是丢弃未提交的更改)。
  • 然后它分叉newbranch
  • 然后,将这3个提交重选到newbranch。由于它们不再被分支引用,因此它使用git的reflog来实现HEAD@{2}HEAD以前用来引用2个操作的提交,即在我们1.检出newbranch和2. git reset丢弃3个提交之前。

警告:reflog默认是启用的,但是如果您手动禁用了reflog(例如,通过使用“裸露”的git仓库),则运行后将无法取回3次提交git reset --keep HEAD~3

不依赖引用日志的替代方法是:

# 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

但是随后有人通过强制按原点/原版来重写历史记录以删除M2:

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

使用本地reflog,git rebase可以看到您是从Origin / master分支的较早版本分支出来的,因此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-pointgit的底垫混帐合并基础文档。


14
这个回答说“不要这样做!” 以上是没人建议做的事情。
Ryan Lundy

3
大多数人不重写已出版的历史,尤其是在上master。所以不,它们不是危险的错误。
沃尔夫

4
@Kyralessa,如果您已设置,则-t您所指的内git branch隐发生git config --global branch.autosetuprebase always。即使您不这样做,我也已经向您解释过,如果您在执行这些命令后设置跟踪,则会出现相同的问题,因为OP可能打算针对他们的问题进行处理。
约翰·梅勒

2
@RockLee,是的,解决这种情况的一般方法是从一个安全的起点创建一个新分支(newbranch2),然后从樱桃中挑选您要保留的所有提交(从badnewbranch到newbranch2)。Cherry-picking将为提交赋予新的哈希,因此您将能够安全地为newbranch2重新设置基础(并且现在可以删除badnewbranch)。
约翰·梅勒

2
@Walf,您误会了:git rebase的设计宗旨是对上游的历史记录进行重写时具有鲁棒性。不幸的是,这种健壮性的副作用影响了每个人,即使他们或他们的上游都没有重写历史。
约翰·梅勒

150

使用git stash更简单的解决方案

这是提交错误分支的简单得多的解决方案。从master具有三个错误提交的分支开始:

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

什么时候使用?

  • 如果您的主要目的是回滚 master
  • 您想保留文件更改
  • 您不在乎有关错误提交的消息
  • 你还没推
  • 您希望这容易记住
  • 您不希望出现诸如临时/新分支,查找和复制提交散列以及其他麻烦之类的复杂情况

这是做什么的,按行号

  1. 撤消对的最后三次提交(及其消息)master,但所有工作文件保持不变
  2. 整理所有工作文件的更改,使master工作树与HEAD〜3 状态完全相等
  3. 切换到现有分支 newbranch
  4. 将隐藏的更改应用于工作目录并清除隐藏

您现在可以像平常一样使用git addgit commit。所有新的提交都将添加到中newbranch

这不行

  • 它不会使随机的临时树枝杂乱无章
  • 它不会保留错误的提交消息,因此您需要向此新提交添加新的提交消息。
  • 更新!使用向上箭头滚动查看命令缓冲区,以使用其提交消息重新应用先前的提交(感谢@ARK)

目标

OP表示,目标是在不丢失更改的情况下“使母带回到那些提交之前”,而此解决方案可以做到这一点。

当我不小心对master而不是进行新的提交时,我每周至少执行一次develop。通常我只有一次提交来回滚,在这种情况下,git reset HEAD^在第1行使用只是一种简单的方法来回滚一次提交。

如果您将主控的更改推到上游,请不要这样做

其他人可能已经撤消了这些更改。如果仅重写本地母版,将其推到上游不会有任何影响,但是将重写的历史推给合作者可能会引起头痛。


4
谢谢,很高兴我阅读了过去/通读本文,因为这对我来说也是一个很常见的用例。我们是如此不典型吗?
吉姆·麦克

6
我认为我们是完全典型的,“哎呀,我错误地决定要掌握的东西”是需要还原少量提交或更少提交的最常见用例。幸运的是,这种解决方案是如此简单,我现在已经记住了。
Slam

9
这应该是公认的答案。它简单明了,易于理解和易于记忆
Sina Madani

1
我不认为隐藏是必要的。我只是没有做,并且运行良好。
阿坎波斯

1
如果您碰巧在CLI(命令行)历史记录中添加了提交消息,则也可以轻松地将其返回。我碰巧同时使用了git addgit commit命令,所以我所要做的就是按下箭头并输入几次,然后开始!一切都回来了,但是现在在正确的分支上。
卢克·吉迪恩

30

从技术上讲,这并不是“移动”它们,但是具有相同的效果:

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)

1
你不能用rebase同一件事吗?
Bergi 2015年

是的,您也可以rebase在上述场景中的分离分支上使用。
Sukima

24

为此,无需重写历史记录(例如,如果您已经推送了提交):

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

然后可以不加力地推开两个分支!


但是然后您必须处理还原方案,根据您的情况,还原方案可能会非常棘手。如果您在分支上还原提交,则Git仍将看到已发生的那些提交,因此,要撤消该提交,您必须还原还原。这会烧掉很多人,特别是当他们还原合并并尝试将分支合并回去时,才发现Git认为它已经合并了该分支(完全正确)。
Makoto 2016年

1
这就是为什么我将提交内容最后挑选到新分支上的原因。这样git会将它们视为新提交,从而解决了您的问题。
teh_senaus

这比最初看起来要危险得多,因为您在更改存储库历史记录的状态时并未真正了解该状态的含义。
Makoto 2016年

5
我不同意您的说法-该答案的重点是您不会更改历史记录,而只是添加新的提交(实际上撤消了对所做更改的重做)。这些新的提交可以像往常一样被推送和合并。
teh_senaus

13

只是这种情况:

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

7

我该如何去

A - B - C - D - E 
                |
                master

对此吗?

A - B - C - D - E 
    |           |
    master      newbranch

用两个命令

  • git branch -m主newbranch

给予

A - B - C - D - E 
                |
                newbranch

  • git分支主管B

给予

A - B - C - D - E
    |           |
    master      newbranch

是的,这很有效而且很容易。Sourcetree GUI对git shell中所做的更改有些困惑,但是在提取之后,一切都很好。
lars k。

是的,它们和问题一样。前两张图旨在与问题中的图相同,只是重新绘制了我想为答案中说明目的的方式。基本上将master分支重命名为newbranch,并在需要的地方创建一个新的master分支。
伊万

4

如果您只需要将所有未推送的提交移动到新分支,则只需,

  1. 从当前分支创建一个新分支git branch new-branch-name

  2. 推送您的新分支git push origin new-branch-name

  3. 您的旧(当前)分支还原到上一推送/稳定状态:git reset --hard origin/old-branch-name

有些人还有其他upstreams而不是origin,他们应该使用适当的upstream


3

1)创建一个新分支,它将所有更改移到new_branch。

git checkout -b new_branch

2)然后回到旧分支。

git checkout master

3)做git rebase

git rebase -i <short-hash-of-B-commit>

4)然后打开的编辑器包含最后3个提交信息。

...
pick <C's hash> C
pick <D's hash> D
pick <E's hash> E
...

5)更改pickdrop所有这3个提交。然后保存并关闭编辑器。

...
drop <C's hash> C
drop <D's hash> D
drop <E's hash> E
...

6)现在,最后3个提交已从当前分支(master)中删除。现在,用力推动分支,+在分支名称前加符号。

git push origin +master

2

您可以做到这只是我使用的3个简单步骤。

1)在要提交最近更新的位置创建新分支。

git branch <branch name>

2)查找要在新分支上提交的最近提交ID。

git log

3)复制该提交ID,请注意,最新提交列表位于顶部。这样您就可以找到自己的提交。您也可以通过消息找到它。

git cherry-pick d34bcef232f6c...

您还可以提供一定范围的提交ID。

git cherry-pick d34bcef...86d2aec

现在您的工作完成了。如果您选择了正确的ID和正确的分支,那么您将成功。因此,在执行此操作之前请先小心。否则可能会发生另一个问题。

现在您可以推送代码

git push


0

另一种方法是:

[1]master分支重命名为您newbranch(假设您在master分支上):

git branch -m newbranch

[2]master根据您希望的提交创建分支:

git checkout -b master <seven_char_commit_id>

例如git checkout -b master a34bc22

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.