我想将我已经承诺掌握的最后几个提交移动到一个新的分支,然后在完成这些提交之前将主人带回去。不幸的是,我的Git-fu还不够强大,有什么帮助吗?
即我该怎么做呢?
master A - B - C - D - E
这个?
newbranch C - D - E
/
master A - B
我想将我已经承诺掌握的最后几个提交移动到一个新的分支,然后在完成这些提交之前将主人带回去。不幸的是,我的Git-fu还不够强大,有什么帮助吗?
即我该怎么做呢?
master A - B - C - D - E
这个?
newbranch C - D - E
/
master A - B
警告: 此方法有效,因为您使用第一个命令创建新分支: 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 (GONE from master)
git checkout newbranch # Go to the new branch that still has the desired commits
但要确保要返回多少次提交。或者,你可以代替 HEAD~3
,只需提供提交的哈希值(或类似的引用) 产地/主)你想“恢复到” 主 (/ current)分支,例如:
git reset --hard a1b2c3d4
* 1你会的 只要 从主分支“失去”提交,但不要担心,你将在新分支中提交这些提交!
警告: 使用Git 2.0及更高版本,如果您以后再使用 git rebase
原始的新分支(master
)分支,你可能需要一个明确的 --no-fork-point
在rebase期间的选项,以避免丢失结转提交。有 branch.autosetuprebase always
set使这更有可能。看到 John Mellor的回答 详情。
如果你想将你的提交移动到 现有分支,它看起来像这样:
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
对于那些想知道它为什么起作用的人(就像我最初的那样):
您想要返回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
由于分支只是一个指针, 主 指向最后一次提交。当你做的时候 newBranch,你只是创建了一个指向最后一次提交的新指针。然后用 git reset
你感动了 主 指针返回两个提交。但既然你没动 newBranch,它仍然指向它最初的提交。
在这种情况下,sykora公开的方法是最佳选择。但有时并不是最容易的,也不是一般的方法。对于一般方法使用 git cherry-pick:
要实现OP想要的,它需要两个步骤:
newbranch
执行
git checkout master
git log
注意你想要的哈希(比如3)提交 newbranch
。我将在这里使用:
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。
另一种方法是使用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
至 .
知道这是一个很好的技巧。
不要这样做:
git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch
下次你跑的时候 git rebase
(要么 git pull --rebase
)这三个提交将被默默地丢弃 newbranch
! (见下面的解释)
而是这样做:
git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
--keep
就好像 --hard
,但更安全,因为失败而不是丢弃未提交的更改)。newbranch
。newbranch
。因为它们不再被分支引用,所以它通过使用git来实现 引用日志: HEAD@{2}
是提交 HEAD
用于指代之前的2个操作,即在我们签出之前 newbranch
和2.使用 git reset
丢弃3个提交。警告:默认情况下启用了reflog,但是如果你手动禁用它(例如通过使用“裸”git存储库),你将无法在运行后恢复3次提交 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尝试对强制推送的上游分支进行强制。
假设你在包含提交M1,M2,M3的情况下对origin / master进行分支,然后自己进行三次提交:
M1--M2--M3 <-- origin/master
\
T1--T2--T3 <-- topic
然后有人通过强制推动origin / master来删除M2来重写历史记录:
M1--M3' <-- origin/master
\
M2--M3--T1--T2--T3 <-- topic
使用您的本地reflog, git rebase
可以看到你从源/主分支的早期版本分叉,因此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-point
在里面 git rebase 和 git merge-base文档。
这不会在技术意义上“移动”它们,但它具有相同的效果:
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)
要在不重写历史记录的情况下执行此操作(即,如果您已经推送了提交):
git checkout master
git revert <commitID(s)>
git checkout -b new-branch
git cherry-pick <commitID(s)>
然后可以无力推动两个分支!
只是这种情况:
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
更简单的解决方案
如果:
master
,和 然后以下更简单(从分支开始) master
错误提交):
git reset HEAD~3
git stash
git checkout newbranch
git stash pop
这是做什么的:
master
,但保留所有工作文件master
工作树等于HEAD~3状态。newbranch
你现在可以使用了 git add
和 git commit
像往常一样。所有更改都将在 newbranch
。
这不做什么:
我每周至少做一次,当时我不小心做了新的提交 master
代替 develop
。通常我只有一个提交回滚在这种情况下 git reset HEAD^
只能回滚一次提交。