使用 Git 将最近的提交移动到新分支
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1628563/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Move the most recent commit(s) to a new branch with Git
提问by Mark A. Nicolosi
I'd like to move the last several commits I've committed to master to a new branch and take master back to before those commits were made. Unfortunately, my Git-fu is not strong enough yet, any help?
我想将我已承诺掌握的最后几次提交移动到一个新分支,并在进行这些提交之前将 master 带回。不幸的是,我的 Git-fu 还不够强大,有什么帮助吗?
I.e. How can I go from this
即我怎么能从这个
master A - B - C - D - E
to this?
到这个?
newbranch C - D - E
/
master A - B
回答by sykora
Moving to an existing branch
转移到现有分支
If you want to move your commits to an existing branch, it will look like this:
如果您想将提交移动到现有分支,它将如下所示:
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
The --keep
option preserves any uncommitted changes that you might have in unrelated files, or aborts if those changes would have to be overwritten -- similarly to what git checkout
does. If it aborts, git stash
your changes and retry, or use --hard
to lose the changes (even from files that didn't change between the commits!)
该--keep
选项保留您在不相关文件中可能具有的任何未提交的更改,或者在必须覆盖这些更改时中止 - 与所做的类似git checkout
。如果它中止,git stash
您的更改并重试,或用于--hard
丢失更改(即使是在提交之间没有更改的文件!)
Moving to a new branch
搬到新的分行
This method works by creating a new branch with the first command (git branch newbranch
) but not switching to it. Then we roll back the current branch (master) and switch to the new branch to continue working.
此方法的工作原理是使用第一个命令 ( git branch newbranch
)创建一个新分支,但不切换到它。然后我们回滚当前分支(master)并切换到新分支继续工作。
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.
But do make sure how many commits to go back. Alternatively, instead of HEAD~3
, you can simply provide the hash of the commit (or the reference like origin/master
) you want to revert back to, e.g:
但是一定要确定要返回多少次提交。或者,HEAD~3
您可以简单地提供origin/master
要恢复的提交(或类似引用)的哈希值,而不是,例如:
git reset --keep a1b2c3d4
WARNING:With Git version 2.0 and later, if you later git rebase
the new branch upon the original (master
) branch, you may need an explicit --no-fork-point
option during the rebase to avoid losing the commits you moved from the master branch. Having branch.autosetuprebase always
set makes this more likely. See John Mellor's answerfor details.
警告:对于 Git 2.0 及更高版本,如果您稍后git rebase
在原始 ( master
) 分支上创建新分支,您可能需要--no-fork-point
在变基期间使用显式选项以避免丢失从主分支移动的提交。拥有branch.autosetuprebase always
一套使这个可能性比较大。有关详细信息,请参阅John Mellor 的回答。
回答by Ryan Lundy
For those wondering why it works (as I was at first):
对于那些想知道它为什么起作用的人(就像我一开始一样):
You want to go back to C, and move D and E to the new branch. Here's what it looks like at first:
您想返回 C,并将 D 和 E 移动到新分支。这是它最初的样子:
A-B-C-D-E (HEAD)
↑
master
After git branch newBranch
:
之后git branch newBranch
:
newBranch
↓
A-B-C-D-E (HEAD)
↑
master
After git reset --hard HEAD~2
:
之后git reset --hard HEAD~2
:
newBranch
↓
A-B-C-D-E (HEAD)
↑
master
Since a branch is just a pointer, masterpointed to the last commit. When you made newBranch, you simply made a new pointer to the last commit. Then using git reset
you moved the masterpointer back two commits. But since you didn't move newBranch, it still points to the commit it originally did.
由于分支只是一个指针,因此master指向最后一次提交。当您创建newBranch 时,您只需创建一个指向最后一次提交的新指针。然后使用git reset
您将主指针移回两次提交。但是由于您没有移动newBranch,它仍然指向它最初所做的提交。
回答by Ivan
In General...
一般来说...
The method exposed by sykora is the best option in this case. But sometimes is not the easiest and it's not a general method. For a general method use git cherry-pick:
在这种情况下,sykora 公开的方法是最好的选择。但有时不是最简单的,也不是通用的方法。对于一般方法,请使用git cherry-pick:
To achieve what OP wants, its a 2-step process:
为了实现 OP 想要的东西,它有两个步骤:
Step 1 - Note which commits from master you want on a newbranch
第 1 步 - 请注意您希望从 master 提交哪些 newbranch
Execute
执行
git checkout master
git log
Note the hashes of (say 3) commits you want on newbranch
. Here I shall use:
C commit: 9aa1233
D commit: 453ac3d
E commit: 612ecb3
注意你想要的(比如 3)提交的哈希值newbranch
。这里我将使用:
C 提交:9aa1233
D 提交:453ac3d
E 提交:612ecb3
Note:You can use the first seven characters or the whole commit hash
注意:您可以使用前七个字符或整个提交哈希
Step 2 - Put them on the newbranch
第 2 步 - 将它们放在 newbranch
git checkout newbranch
git cherry-pick 612ecb3
git cherry-pick 453ac3d
git cherry-pick 9aa1233
OR (on Git 1.7.2+, use ranges)
或(在 Git 1.7.2+ 上,使用范围)
git checkout newbranch
git cherry-pick 612ecb3~1..9aa1233
git cherry-pickapplies those three commits to newbranch.
git cherry-pick将这三个提交应用于 newbranch。
回答by aragaer
Yet another way to do this, using just 2 commands. Also keeps your current working tree intact.
另一种方法来做到这一点,只使用 2 个命令。还可以保持您当前的工作树完好无损。
git checkout -b newbranch # switch to a new branch
git branch -f master HEAD~3 # make master point to some older commit
Old version- before I learned about git branch -f
旧版本- 在我了解之前git branch -f
git checkout -b newbranch # switch to a new branch
git push . +HEAD~3:master # make master point to some older commit
Being able to push
to .
is a nice trick to know.
能够push
到.
是一个不错的把戏就知道了。
回答by John Mellor
Most previous answers are dangerously wrong!
以前的大多数答案都是危险的错误!
Do NOT do this:
不要这样做:
git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch
As the next time you run git rebase
(or git pull --rebase
) those 3 commits would be silently discarded from newbranch
! (see explanation below)
下次您运行git rebase
(或git pull --rebase
)时,这 3 次提交将从newbranch
! (见下面的解释)
Instead do this:
而是这样做:
git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
- First it discards the 3 most recent commits (
--keep
is like--hard
, but safer, as fails rather than throw away uncommitted changes). - Then it forks off
newbranch
. - Then it cherry-picks those 3 commits back onto
newbranch
. Since they're no longer referenced by a branch, it does that by using git's reflog:HEAD@{2}
is the commit thatHEAD
used to refer to 2 operations ago, i.e. before we 1. checked outnewbranch
and 2. usedgit reset
to discard the 3 commits.
- 首先它丢弃 3 个最近的提交(
--keep
就像--hard
,但更安全,因为失败而不是丢弃未提交的更改)。 - 然后它分叉了
newbranch
。 - 然后它将这 3 个提交挑选回
newbranch
. 由于它们不再被分支引用,它通过使用 git 的reflog来做到这一点:HEAD@{2}
是HEAD
用于引用前 2 个操作的提交,即在我们 1. 检出newbranch
和 2. 用于git reset
丢弃 3 个提交之前。
Warning: the reflog is enabled by default, but if you've manually disabled it (e.g. by using a "bare" git repository), you won't be able to get the 3 commits back after running git reset --keep HEAD~3
.
警告:默认情况下会启用 reflog,但如果您手动禁用它(例如,通过使用“裸”git 存储库),您将无法在运行后恢复 3 次提交git reset --keep HEAD~3
。
An alternative that doesn't rely on the reflog is:
不依赖于 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
(if you prefer you can write @{-1}
- the previously checked out branch - instead of oldbranch
).
(如果你愿意,你可以写@{-1}
- 之前签出的分支 - 而不是oldbranch
)。
Technical explanation
技术说明
Why would git rebase
discard the 3 commits after the first example? It's because git rebase
with no arguments enables the --fork-point
option by default, which uses the local reflog to try to be robust against the upstream branch being force-pushed.
为什么会git rebase
在第一个示例之后丢弃 3 个提交?这是因为默认情况下git rebase
没有参数启用该--fork-point
选项,它使用本地 reflog 尝试对被强制推送的上游分支保持健壮。
Suppose you branched off origin/master when it contained commits M1, M2, M3, then made three commits yourself:
假设您在 origin/master 包含提交 M1、M2、M3 时将其分支,然后自己进行了三个提交:
M1--M2--M3 <-- origin/master
\
T1--T2--T3 <-- topic
but then someone rewrites history by force-pushing origin/master to remove M2:
但是后来有人通过强制推动 origin/master 来删除 M2 来重写历史记录:
M1--M3' <-- origin/master
\
M2--M3--T1--T2--T3 <-- topic
Using your local reflog, git rebase
can see that you forked from an earlier incarnation of the origin/master branch, and hence that the M2 and M3 commits are not really part of your topic branch. Hence it reasonably assumes that since M2 was removed from the upstream branch, you no longer want it in your topic branch either once the topic branch is rebased:
使用您的本地 reflog,git rebase
可以看到您从 origin/master 分支的早期化身分叉,因此 M2 和 M3 提交实际上并不是您主题分支的一部分。因此,它合理地假设,由于 M2 已从上游分支中删除,一旦主题分支重新定位,您就不再需要它在您的主题分支中:
M1--M3' <-- origin/master
\
T1'--T2'--T3' <-- topic (rebased)
This behavior makes sense, and is generally the right thing to do when rebasing.
这种行为是有道理的,并且通常是在变基时正确的做法。
So the reason that the following commands fail:
所以以下命令失败的原因:
git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch
is because they leave the reflog in the wrong state. Git sees newbranch
as having forked off the upstream branch at a revision that includes the 3 commits, then the reset --hard
rewrites the upstream's history to remove the commits, and so next time you run git rebase
it discards them like any other commit that has been removed from the upstream.
是因为他们让 reflog 处于错误的状态。Git 认为newbranch
在包含 3 次提交的修订版中分叉了上游分支,然后reset --hard
重写上游的历史记录以删除提交,因此下次运行时,git rebase
它会像从上游删除的任何其他提交一样丢弃它们。
But in this particular case we want those 3 commits to be considered as part of the topic branch. To achieve that, we need to fork off the upstream at the earlier revision that doesn't include the 3 commits. That's what my suggested solutions do, hence they both leave the reflog in the correct state.
但是在这种特殊情况下,我们希望将这 3 个提交视为主题分支的一部分。为了实现这一点,我们需要在不包括 3 次提交的早期版本中分叉上游。这就是我建议的解决方案所做的,因此它们都使 reflog 处于正确的状态。
For more details, see the definition of --fork-point
in the git rebaseand git merge-basedocs.
回答by Slam
Much simpler solution using git stash
使用 git stash 更简单的解决方案
Here's a far simpler solution for commits to the wrong branch. Starting on branch master
that has three mistaken commits:
这是提交到错误分支的更简单的解决方案。从master
三个错误提交的分支开始:
git reset HEAD~3
git stash
git checkout newbranch
git stash pop
When to use this?
什么时候用这个?
- If your primary purpose is to roll back
master
- You want to keep file changes
- You don't care about the messages on the mistaken commits
- You haven't pushed yet
- You want this to be easy to memorize
- You don't want complications like temporary/new branches, finding and copying commit hashes, and other headaches
- 如果您的主要目的是回滚
master
- 您想保留文件更改
- 你不关心错误提交的消息
- 你还没推
- 你希望这很容易记住
- 您不希望出现临时/新分支、查找和复制提交哈希以及其他令人头疼的问题
What this does, by line number
这是做什么的,按行号
- Undoes the last three commits (and their messages) to
master
, yet leaves all working files intact - Stashes away all the working file changes, making the
master
working tree exactly equal to the HEAD~3 state - Switches to an existing branch
newbranch
- Applies the stashed changes to your working directory and clears the stash
- 撤消对 的最后三个提交(及其消息)
master
,但保持所有工作文件完好无损 - 隐藏所有工作文件更改,使
master
工作树完全等于 HEAD~3 状态 - 切换到现有分支
newbranch
- 将隐藏的更改应用到您的工作目录并清除存储
You can now use git add
and git commit
as you normally would. All new commits will be added to newbranch
.
您现在可以像往常一样使用git add
和git commit
。所有新提交都将添加到newbranch
.
What this doesn't do
这不做什么
- It doesn't leave random temporary branches cluttering your tree
- It doesn't preserve the mistaken commit messages,so you'll need to add a new commit message to this new commit
- Update! Use up-arrow to scroll through your command buffer to reapply the prior commit with its commit message (thanks @ARK)
- 它不会让随机的临时树枝弄乱你的树
- 它不会保留错误的提交消息,因此您需要向此新提交添加新的提交消息
- 更新!使用向上箭头滚动命令缓冲区以重新应用先前的提交及其提交消息(感谢@ARK)
Goals
目标
The OP stated the goal was to "take master back to before those commits were made" without losing changes and this solution does that.
OP 表示目标是“在做出这些提交之前将 master 带回”而不会丢失更改,而这个解决方案就是这样做的。
I do this at least once a week when I accidentally make new commits to master
instead of develop
. Usually I have only one commit to rollback in which case using git reset HEAD^
on line 1 is a simpler way to rollback just one commit.
当我不小心对master
而不是develop
. 通常我只有一次回滚提交,在这种情况下,git reset HEAD^
在第 1 行使用是回滚一次提交的一种更简单的方法。
Don't do this if you pushed master's changes upstream
如果您将 master 的更改推送到上游,请不要这样做
Someone else may have pulled those changes. If you are only rewriting your local master there's no impact when it's pushed upstream, but pushing a rewritten history to collaborators can cause headaches.
其他人可能已经取消了这些更改。如果你只是重写你的本地 master,当它被推到上游时没有影响,但是将重写的历史推送给合作者可能会让人头疼。
回答by Sukima
This doesn't "move" them in the technical sense but it has the same effect:
这不会在技术意义上“移动”它们,但具有相同的效果:
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)
回答by teh_senaus
To do this without rewriting history (i.e. if you've already pushed the commits):
要在不重写历史记录的情况下执行此操作(即,如果您已经推送了提交):
git checkout master
git revert <commitID(s)>
git checkout -b new-branch
git cherry-pick <commitID(s)>
Both branches can then be pushed without force!
然后可以不用力推动两个分支!
回答by Darkglow
Had just this situation:
刚好遇到这样的情况:
Branch one: A B C D E F J L M
\ (Merge)
Branch two: G I K N
I performed:
我表现:
git branch newbranch
git reset --hard HEAD~8
git checkout newbranch
I expected that commit I would be the HEAD, but commit L is it now...
我希望提交 I 将成为 HEAD,但现在提交 L 是...
To be sure to land on the right spot in the history its easier to work with the hash of the commit
为了确保落在历史记录中的正确位置,使用提交的哈希值更容易
git branch newbranch
git reset --hard #########
git checkout newbranch
回答by Ivan
How can I go from this
我怎样才能离开这里
A - B - C - D - E
|
master
to this?
到这个?
A - B - C - D - E
| |
master newbranch
With two commands
用两个命令
- git branch -m master newbranch
- git branch -m master newbranch
giving
给予
A - B - C - D - E
|
newbranch
and
和
- git branch master B
- git 分支主 B
giving
给予
A - B - C - D - E
| |
master newbranch