5861

我想将我已经提交给 master 的最后几个提交移动到一个新分支,并将 master 带回这些提交之前。不幸的是,我的 Git-fu 还不够强大,有什么帮助吗?

即我怎么能从这个

master A - B - C - D - E

到这个?

newbranch     C - D - E
             /
master A - B 
4

20 回答 20

7592

移动到现有分支

如果您想将提交移动到现有分支,它将如下所示:

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

在执行此操作之前,您可以将未提交的编辑存储到您的存储中,使用git stash. 完成后,您可以使用以下命令检索隐藏的未提交编辑git stash pop

搬到新的分支

警告:此方法有效,因为您正在使用第一个命令创建一个新分支: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 (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

但一定要确保有多少次提交要返回。或者,您可以代替,只需提供要在(/当前)分支上“恢复”HEAD~3的提交的哈希(或引用,如origin/master ),例如:

git reset --hard a1b2c3d4

*1 你只会从主分支“丢失”提交,但别担心,你会在新分支中拥有这些提交!

警告:使用 Git 2.0 及更高版本,如果您稍后git rebase在原始 ( master) 分支上创建新分支,您可能需要--no-fork-point在 rebase 期间显式选择以避免丢失结转提交。设置branch.autosetuprebase always使这更有可能。有关详细信息,请参阅John Mellor 的答案

于 2009-10-27T03:15:15.320 回答
1176

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

您想回到 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,它仍然指向它最初所做的提交。

于 2011-07-22T22:37:41.890 回答
538

一般来说...

sykora 公开的方法是这种情况下的最佳选择。但有时不是最简单的,也不是通用的方法。对于一般方法,请使用git cherry-pick

为了实现 OP 想要的,它有两个步骤:

第 1 步 - 记下您想要从 master 提交的哪些提交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。

于 2012-02-07T16:58:43.387 回答
411

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

不要这样做:

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,但如果您手动禁用它(例如通过使用“裸”git 存储库),您将无法在运行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 来尝试对上游分支被强制推送的鲁棒性。

假设您在 origin/master 包含提交 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可以看到您从 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 保持在正确的状态。

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

于 2016-04-06T22:38:26.490 回答
371

还有另一种方法,只使用 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做到.是一个很好的技巧。

于 2014-03-26T08:13:49.977 回答
236

使用 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 addand了。git commit所有新的提交都将添加到newbranch.

这不做什么

  • 它不会让随机的临时树枝把你的树弄得乱七八糟
  • 它不会保留错误的提交消息,因此您需要向此新提交添加新的提交消息
  • 更新!使用向上箭头滚动命令缓冲区以重新应用先前的提交及其提交消息(感谢@ARK)

目标

OP 表示目标是在不丢失更改的情况下“让 master 回到提交之前”,而这个解决方案就是这样做的。

当我不小心做出新的提交master而不是develop. 通常我只有一个提交回滚,在这种情况下,使用git reset HEAD^第 1 行是一种更简单的方法来回滚一个提交。

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

其他人可能已经取消了这些更改。如果你只是重写你的本地master,那么当它被推送到上游时没有影响,但是将重写的历史推送给合作者可能会让人头疼。

于 2018-05-01T07:50:07.320 回答
32

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

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)
于 2013-10-19T14:12:19.860 回答
26

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

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

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

于 2016-01-21T16:10:36.243 回答
18

最简单的方法:

1.将分支重命名master为您的newbranch(假设您在master分支上):

git branch -m newbranch

2.master从您希望的提交创建分支:

git checkout -b master <seven_char_commit_id>

例如 git checkout -b master a34bc22

于 2020-03-21T07:24:50.123 回答
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
于 2013-09-20T10:17:35.893 回答
11

我怎么能从这个

A - B - C - D - E 
                |
                master

到这个?

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

有两个命令

  • git 分支 -m 主新分支

给予

A - B - C - D - E 
                |
                newbranch

  • git 分支主 B

给予

A - B - C - D - E
    |           |
    master      newbranch
于 2019-05-16T12:00:50.470 回答
9

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

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

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

  3. 旧(当前)分支恢复到上次推送/稳定状态:git reset --hard origin/old-branch-name

有的人也有otherupstreams而不是origin,应该用适当的upstream

于 2019-06-17T07:38:10.773 回答
4

你可以做到这只是我使用的 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

于 2018-05-25T14:14:27.140 回答
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)在所有这 3 个提交中更改pick为。drop然后保存并关闭编辑器。

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

6) 现在从当前分支 ( master) 中删除最后 3 个提交。现在强制推送分支,+在分支名称前加上符号。

git push origin +master
于 2018-06-01T05:40:11.607 回答
3

这里的大多数解决方案都会计算您想要返回的提交数量。我认为这是一种容易出错的方法。计数需要重新计数。

您可以简单地传递您希望在 HEAD 的提交的提交哈希,或者换句话说,您希望通过以下方式成为最后一次提交的提交:

(注意见提交哈希)

为了避免这种情况:

1) git checkout master

2) git branch <feature branch> master

3) git reset --hard <commit hash>

4) git push -f origin master
于 2020-11-24T21:49:27.363 回答
2

我很惊讶没有人推荐这种方式:

git checkout master
git checkout <commit hash from which you want to split>
git checkout -b new_branch
git rebase master
git checkout master
git reset --hard <commit hash you splitted>

解释:

  1. 步骤我们检查我们想要拆分的提交
  2. 然后从这个提交创建一个新分支
  3. 做 rebase 将同步 new_branch 和 master。所以现在我们有两个具有相同提交的相同分支
  4. 在 master 上重置,我们在拆分后清理最后一次提交
  5. 项目清单
于 2022-01-19T19:17:05.147 回答
1

如果您是像我这样的 UI 人员并且您正在使用 Visual Studio。然后您可以执行以下操作:就我而言,我想将最新的提交提交到另一个分支。

  1. 右键单击之前的一个(提交)。

在此处输入图像描述

  1. 因此,所有提交更改都将显示在Git 更改窗格中。

  2. 现在,隐藏您的更改

在此处输入图像描述

  1. 转到您的目标分支或从右下角创建一个新分支。

在此处输入图像描述

  1. 在“Git Changes”中双击你最新的 Stash。

  2. “存储详细信息”窗格将打开。单击“Pop”,然后解决冲突(如果存在)。

在此处输入图像描述

  1. 最后,提交您的更改。
于 2021-10-10T07:48:17.803 回答
1

我必须将 7 个提交从一个旧分支移动到一个新分支。

git checkout old-branch     # in the example, master
git reset --hard h4sh       # h4sh is the hash for the commit 
git checkout -b new-branch  
git push origin new-branch

之后,这两个分支都与我所做的 7 次提交有关。之后git checkout new-branch,我变得很好git loggit status但是,当访问旧分支 ( git checkout old-branch) 时,我收到消息“git 落后 7 次提交并且可以快进”。对我有用的删除此消息的是以下内容:

git checkout old-branch
git status 
> git is behind by 7 commits and can be fast-forwarded
git push origin old-branch -f

在该步骤之后,最后 7 个提交仅被引用为新分支,而之前的提交在 Bitbucket 树中被引用为旧分支和新分支。

于 2021-09-19T00:15:14.710 回答
1

使用 Emacs 的 git瓷器 Magit,您只需点击b s(magit-branch-spinoff) 即可完成此操作。系统会要求您输入新分支的名称,然后按 Enter 键,瞧。

来自Magit 文档

此命令创建并签出一个从当前分支开始并跟踪的新分支。该分支又被重置为它与上游共享的最后一个提交。如果当前分支没有上游或未推送的提交,则无论如何都会创建新分支,并且不会触及先前的当前分支。

这对于在旧分支(可能但不一定是“主”)上已经开始工作后创建功能分支很有用。

于 2021-12-03T10:33:15.837 回答
-1

从其他帖子中获取一些想法,避免与重置有关,并且极度偏执,我的解决方案是:

  1. git branch # 更改在新分支中可用
  2. git push # 上传,你可能需要弄乱“--set-upstream”,例如 git push --set-upstream https:///
  3. 通过 GUI 检查新分支是否在 git 中
  4. 销毁当前目录
  5. 从 git 仓库重新克隆

我并不骄傲,但我保留了我的数据;)

于 2022-01-15T16:02:05.647 回答