我如何将在主分支上的最近提交移到一个新的分支,并将主分支重置为这些提交之前的状态?例如,从这个状态:
master A - B - C - D - E
转化为:
newbranch C - D - E
/
master A - B
我如何将在主分支上的最近提交移到一个新的分支,并将主分支重置为这些提交之前的状态?例如,从这个状态:
master A - B - C - D - E
转化为:
newbranch C - D - E
/
master A - B
如果您想将您的提交移动到一个现有分支,操作如下:
git checkout existingbranch
git merge branchToMoveCommitFrom
git checkout branchToMoveCommitFrom
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 checkout master # checkout master, this is the place you want to go back
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
,而是直接提供要在master(/当前)分支上“还原回去”的提交的哈希值(或引用,如origin/master),例如:git reset --hard a1b2c3d4
git push origin master --force
警告:在Git版本2.0及更高版本中,如果您稍后将新分支(git rebase
)放在原始(master
)分支上,可能需要在重新基于操作期间使用显式的--no-fork-point
选项,以避免丢失已传递的提交。设置了branch.autosetuprebase always
会增加这种情况发生的可能性。有关详细信息,请参阅John Mellor的答案。
对于那些想知道它为什么有效(就像我一开始那样)的人:
你想要回到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
命令将 master 指针向后移动了两个提交。但是由于你没有移动 newBranch,所以它仍然指向最初的提交。
git push origin master --force
的操作。 - Dženangit rebase
时,这3个提交记录将会在 newbranch
中被无声地删除。详情和更安全的替代方法请查看我的答案。 - John Mellororigin/master
。如果你推送到origin/master
,然后再进行上述更改,当然会出现问题。但这是一种“医生,我这样做时很疼”的问题。而且这已经超出了原始问题所问的范围。我建议你写下你自己的问题来探索你的情况,而不是挟持这个问题。 - Ryan Lundygit branch -t newbranch
”。回去再读一遍答案。没有人建议这样做。 - Ryan Lundygit rebase
而没有参数会启用--fork-point
选项,这可能会导致提交丢失。在我看来,这是git rebase
的一个缺陷。尽管如此,它与我上面的答案无关。许多情况下,使用没有参数的git rebase
都会引起麻烦。不幸的是,故事的寓意是即使该分支已经是上游分支,也不要调用git rebase
而不传入分支。Git的可用性在过去的八年左右有了很大的改进,但这是一个需要更多工作的领域。 - Ryan Lundy在这种情况下,sykora公开的方法是最好的选择。但有时它并不是最简单的方法,也不是通用的方法。对于通用方法,请使用git cherry-pick:
要实现OP想要的效果,需要进行两个步骤:
newbranch
上的哪些提交来自master分支执行以下命令:
git checkout master
git log
注意要在newbranch
上放置(比如说3个)指定的提交记录哈希值。在这里我将使用:
C提交记录: 9aa1233
D提交记录: 453ac3d
E提交记录: 612ecb3
注意: 您可以使用前七个字符或整个提交记录哈希值
newbranch
上git checkout newbranch
git cherry-pick 612ecb3
git cherry-pick 453ac3d
git cherry-pick 9aa1233
git checkout newbranch
git cherry-pick 612ecb3~1..9aa1233
git cherry-pick 将这三个提交应用到 newbranch 分支。
不要这样做:
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}
--keep
类似于--hard
,但更安全,因为它会失败而不是放弃未提交的更改)。newbranch
。newbranch
。由于它们不再被引用于一个分支上,所以使用git的reflog: HEAD@{2}
是HEAD
在2个操作之前所指向的提交记录,即在我们检出newbranch
并使用git reset
丢弃这3个提交记录之前。警告:reflog默认启用,但如果您手动禁用了它(例如使用“裸”的git存储库),则运行git reset --keep HEAD~3
后将无法找回这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
丢弃?这是因为git rebase
没有参数时默认启用--fork-point
选项,它使用本地reflog尝试针对上游分支的强制推送保持稳健。
假设当origin/master包含提交M1、M2、M3时,你从中创建了一个分支,然后自己进行了三次提交:
M1--M2--M3 <-- origin/master
\
T1--T2--T3 <-- topic
M1--M3' <-- origin/master
\
M2--M3--T1--T2--T3 <-- topic
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
newbranch
视为从上游分支的一个修订版本分叉出来,该修订版本包括3个提交,然后执行reset --hard
命令重写上游的历史以删除这些提交,所以下一次运行git rebase
时会像任何其他已从上游中删除的提交一样将它们丢弃。--fork-point
定义。git branch
命令中,你所提到的-t
选项是隐含的,如果你设置了git config --global branch.autosetuprebase always
,即使你没有设置它也是如此。即使你没有设置,我已经向你解释过,在执行这些命令后设置跟踪会导致相同的问题,因为OP可能会按照他们的问题意图进行设置。 - John Mellor另一种只使用两个命令的方法,同时保持您当前的工作树不变。
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
到.
是一个不错的技巧。
git
可以推送到远程仓库或 Git URL。本地目录路径也支持 Git URL 语法。请参阅 git help clone
中的 GIT URL 部分。 - weakish对于提交到错误分支的情况,这里提供了一个更简单的解决方案。假设当前位于分支master
,而该分支有三个错误提交:
git reset HEAD~3
git stash
git checkout newbranch
git stash pop
master
master
的工作树状态恢复到 HEAD~3 时的状态newbranch
现在您可以像平常一样使用 git add
和 git commit
。所有新的提交都将添加到 newbranch
中。
原帖作者的目标是“将 master 回退到在这些提交之前的状态”,而不会丢失更改,这个方法可以达成此目的。
当我每周一次意外地将新提交提交到 master
而不是 develop
时,我通常会使用此方法。通常情况下,如果只需要回退一个提交,则可以使用第 1 行的 git reset HEAD^
方法更简单。
如果您已推送了 master 的更改,请不要这样做
其他人可能已经拉取了这些更改。如果您只会重写本地的 master 分支,则将其推送到上游时不影响任何内容,但是将重写的历史记录推送给协作者可能会导致问题。
git add
和git commit
命令,所以我只需要按上箭头并输入几次,就可以恢复所有内容了!现在,一切都回来了,但是在正确的分支上。 - Luke Gedeongit checkout -b newbranch
- Marianna S.1. 将 master
分支重命名为你的 newbranch
(假设你当前在 master
分支上):
git branch -m newbranch
git checkout -b master <seven_char_commit_id>
git branch -u origin/master master
newbranch
的上游将是origin/master
。在进行任何更改之前,请谨慎行事并确保您完全理解解决方案。
newbranch
是不是指向origin/master
了? - kraxororigin/master
的 master
副本。使用以下命令可以恢复正常:git branch -u origin/master master
。 - Wild PottokA--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)>
现在两个分支都可以被推送而不需要强制操作!
我该如何从这里开始
A - B - C - D - E
|
master
转化为这个样子?
A - B - C - D - E
| |
master newbranch
通过两个命令
得到
A - B - C - D - E
|
newbranch
并且
得到
A - B - C - D - E
| |
master newbranch