我想将我已承诺掌握的最后几次提交移动到一个新分支,并在进行这些提交之前将 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
我想将我已承诺掌握的最后几次提交移动到一个新分支,并在进行这些提交之前将 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
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)
Instead do this:
git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
--keep
is like --hard
, but safer, as fails rather than throw away uncommitted changes).newbranch
.newbranch
. Since they're no longer referenced by a branch, it does that by using git's reflog: HEAD@{2}
is the commit that HEAD
used to refer to 2 operations ago, i.e. before we 1. checked out newbranch
and 2. used git reset
to discard the 3 commits.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
.
An alternative that doesn't rely on the reflog is:
# 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
).
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.
Suppose you branched off origin/master when it contained commits M1, M2, M3, then made three commits yourself:
M1--M2--M3 <-- origin/master
\
T1--T2--T3 <-- topic
but then someone rewrites history by force-pushing origin/master to remove 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:
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.
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.
For more details, see the definition of --fork-point
in the git rebase and git merge-base docs.
Here's a far simpler solution for commits to the wrong branch. Starting on branch master
that has three mistaken commits:
git reset HEAD~3
git stash
git checkout newbranch
git stash pop
master
master
, yet leaves all working files intactmaster
working tree exactly equal to the HEAD~3 statenewbranch
You can now use git add
and git commit
as you normally would. All new commits will be added to newbranch
.
The OP stated the goal was to "take master back to before those commits were made" without losing changes and this solution does that.
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.
Don't do this if you pushed master's changes upstream
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.
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…GA–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)
</div>
You can do this is just 3 simple step that i used.
1) make new branch where you want to commit you recent update.
git branch <branch name>
2) Find Recent Commit Id for commit on new branch.
git log
3) Copy that commit id note that Most Recent commit list take place on top. so you can find your commit. you also find this via message.
git cherry-pick d34bcef232f6c...
you can also provide some rang of commit id.
git cherry-pick d34bcef...86d2aec
Now your job done. If you picked correct id and correct branch then you will success. So before do this be careful. else another problem can occur.
Now you can push your code
git push