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

如何将我最近在master上的提交移动到一个新的分支,并将master重置为在这些提交之前?例如:

master A - B - C - D - E

对此:

newbranch     C - D - E/master A - B
1535352 次浏览

迁移到现有分支

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

git checkout existingbranchgit merge branchToMoveCommitFromgit checkout branchToMoveCommitFromgit 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 commitsgit checkout master       # checkout master, this is the place you want to go backgit 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,而是简单地提供你想在大师(/当前)分支上“恢复到”的提交的哈希(或像来源/主这样的引用),例如:

git reset --hard a1b2c3d4

*1您将只有从master分支“丢失”提交,但不要担心,您将在new分支中获得这些提交!

最后,您可能需要强制将您的最新更改推送到主存储库:

git push origin master --force

警告:在Git版本2.0及更高版本中,如果您稍后在原始(master)分支上git rebase新分支,您可能需要在重基期间显式--no-fork-point选项以避免丢失结转提交。设置branch.autosetuprebase always使这种情况更有可能发生。有关详细信息,请参阅约翰·梅勒的回答

对于那些想知道它为什么有效的人(就像我起初一样):

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

由于分支只是一个指针,大师指向最后一次提交。当你做新分支时,你只是做了一个指向最后一次提交的新指针。然后使用git reset大师指针移回了两次提交。但是因为你没有移动新分支,它仍然指向它最初做的提交。

总的来说…

在这种情况下,sykora公开的方法是最好的选择。但有时不是最简单的,也不是通用方法。对于通用方法,请使用git樱桃选择

为了实现OP想要的,这是一个两步的过程:

步骤1-注意您想要在newbranch上从master提交的内容

执行

git checkout mastergit log

注意您想要在newbranch上提交的(例如3)提交的哈希值。这里我将使用:
C代码提交:9aa1233
D提交:453ac3d
代码执行:612ecb3

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

第2步-把它们放在newbranch

git checkout newbranchgit cherry-pick 612ecb3git cherry-pick 453ac3dgit cherry-pick 9aa1233

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

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

git樱桃选择将这三个提交应用于new分支。

刚刚遇到这种情况:

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

我执行:

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

我原以为我是头,但现在是L…

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

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

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

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 basicallylike 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)

还有另一种方法可以做到这一点,只需使用2个命令。还可以保持当前工作树的完整性。

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

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

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

能够push.是一个很好的技巧。

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

git checkout mastergit revert <commitID(s)>git checkout -b new-branchgit cherry-pick <commitID(s)>

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

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

不要这样做:

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

下次运行git rebase(或git pull --rebase)时,这3个提交将从newbranch中静默丢弃!(见下面的解释)

而是这样做:

git reset --keep HEAD~3git checkout -t -b newbranchgit cherry-pick ..HEAD@{2}
  • 首先,它丢弃3个最近的提交(--keep类似于--hard,但更安全,因为失败而不是丢弃未提交的更改)。
  • 然后它分叉newbranch
  • 然后它选择这3个提交回到newbranch。由于它们不再被分支引用,它通过使用git的重排来做到这一点:HEAD@{2}HEAD用来引用2个操作之前的提交,即在我们1.签出newbranch之前。

警告:默认情况下重新启动,但如果您手动禁用它(例如通过使用“裸”git存储库),您将无法在运行git reset --keep HEAD~3后恢复3次提交。

一个不依赖于reflg的替代方案是:

# newbranch will omit the 3 most recent commits.git checkout -b newbranch HEAD~3git 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选项,它使用本地重新启动来尝试对被强制推送的上游分支保持健壮。

假设您在包含提交M1、M2、M3时分支了原始/主,然后自己进行了三次提交:

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

但随后有人通过强制推动起源/主人删除M2来改写历史:

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

使用您的本地reflg,git rebase可以看到您从源/主分支的早期化身分叉,因此M2和M3提交并不是您的主题分支的真正一部分。因此,它合理地假设,由于M2已从上游分支中删除,一旦主题分支重新基于,您就不再希望它在您的主题分支中:

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

这种行为是有意义的,并且通常是重设基点时的正确做法。

因此,以下命令失败的原因:

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

Git认为newbranch在包含3个提交的修订中分叉了上游分支,然后reset --hard重写上游的历史记录以删除提交,因此下次运行git rebase时,它会像从上游删除的任何其他提交一样丢弃它们。

但在这种特殊情况下,我们希望这3次提交被视为主题分支的一部分。为了实现这一点,我们需要在不包括3次提交的早期版本中分叉上游。这就是我建议的解决方案所做的,因此它们都将reflg保持在正确的状态。

有关更多详细信息,请参阅gitrebasegit合并库文档中--fork-point的定义。

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

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

git reset HEAD~3git stashgit checkout newbranchgit stash pop

什么时候用这个?

  • 如果您的主要目的是回滚master
  • 您想保留文件更改
  • 你不关心错误提交上的消息
  • 你还没推呢
  • 你想让这个容易记住
  • 您不希望出现临时/新分支、查找和复制提交哈希以及其他令人头痛的问题

这是做什么的,按行号

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

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

这没什么用

  • 它不会在你的树上留下随机的临时树枝
  • 它不会保留错误的提交消息,所以你需要向这个新提交添加一个新的提交消息
  • 更新!使用向上箭头滚动您的命令缓冲区以重新应用先前的提交及其提交消息(感谢@ARK)

目标

OP表示,目标是“在这些提交之前将master带回”而不会丢失更改,而这个解决方案就是这样做的。

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

不要这样做,如果你把主人的变化推到上游

其他人可能已经取消了这些更改。如果您只是重写本地主服务器,则将其向上推时没有影响,但将重写的历史推送给协作者可能会引起麻烦。

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

1)创建一个新的分支,你想提交你最近的更新。

#0

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

git log

3)复制提交ID注意最近提交列表发生在顶部。所以你可以找到你的提交。你也可以通过消息找到这个。

#0

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

git cherry-pick d34bcef...86d2aec

现在你的工作完成了。如果你选择了正确的id和正确的分支,那么你就会成功。所以在这样做之前要小心。否则会发生另一个问题。

现在您可以推送您的代码

git push

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> Cpick <D's hash> Dpick <E's hash> E...

5)在所有这3次提交中将pick更改为drop。然后保存并关闭编辑器。

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

6)现在从当前分支(master)中删除了最后3次提交。现在强行推送分支,分支名称前有+符号。

git push origin +master

我该如何离开这一切

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

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

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

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

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

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

最简单的方法做到这一点:

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

git branch -m newbranch

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

git checkout -b master <seven_char_commit_id>

例如git check out-b master a34bc22

注意:newbranch的上游是origin/master

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

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

(注意见提交哈希)

为了避免这种情况:

1) git checkout master
2) git branch <feature branch> master
3) git reset --hard <commit hash>
4) git push -f origin master

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

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

之后,这两个分支都与我完成的7次提交有关。在git checkout new-branch之后,我在git loggit status做得很好,但是,当访问旧分支(git checkout old-branch)时,我收到了“git落后于7次提交,可以快进”的消息。对我来说,删除这条消息的有效方法是以下内容:

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

在这一步之后,最后7次提交仅被引用为新分支,而之前的提交在比特桶树中被引用为旧分支和新分支。

如果你和我一样是UI用户,并且正在使用Visual Studio。那么你可以执行以下操作:在我的情况下,我想把最新的提交到另一个分支。

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

输入图片描述

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

  2. 现在,隐藏您的更改

输入图片描述

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

输入图片描述

  1. 从“Git更改”中双击您的最新Stash。

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

输入图片描述

  1. 最后,提交您的更改。

使用Emacs的git瓷器Magit,您只需按b s(magit-分支-衍生)即可完成此操作。您将被要求输入新分支的名称,一旦您按回车键,瞧。

Magit留档

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

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

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

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

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

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

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

解释:

  1. 我们检查我们想要拆分的提交的步骤
  2. 然后从这个提交创建一个新分支
  3. 执行rebase将同步new_branch和master。所以现在我们有两个相同的分支,具有相同的提交
  4. 在master上重置,我们在拆分后清理最后的提交
  5. 列表项

TLDR

git checkout branch_to_remove_commitsgit reset --hard ${hash_of_new_tip}git checkout -b branch_to_store_commits# Move commits (single hash, list of hashes or range ffaa..ffoo)git cherry-pick ${commit_hash}git push --set-upstream origin branch_to_store_commits# Switch back to last branchgit checkout -git push -f

对我来说

git log --pretty=oneline -n ${NUMBER}

最适合识别有问题的提交哈希。