Technically you can't move commits at all, not in the sense that you mean. Moreover, "the stash" is actually just a single reference-name (refs/stash
). The git stash
script uses the reflog to hide multiple items under that single name. When you run git stash save
to make a new one, the script creates a new commit (different from every other commit so far) and makes the stash point to it.1 It would be possible to achieve your goal this way, I think, but this is the wrong way to try it.
Fortunately, that's not a problem anyway. What you really want is just a plain old git rebase
! The git rebase
command simply automates a series of git cherry-pick
operations with a git reset
somewhere along the way.
Here's how this works. You have your work, on your branch B
, which you start doing when your upstream team has upstream/B
pointing to the commit-chain I mark o
(for old) here:
..- * - o - o - o <-- upstream/B
\
x - y <-- B
Now your upstream team goes and "rewrites history" in upstream/B
, replacing the three old o
s with new n
s (maybe 4 n
s, even). The old ones are still there though, especially in your repo since your B
includes them:
n - n - n - n <-- upstream/B
/
..- * - o - o - o <-- upstream/B@{1}, in reflog
\
x - y <-- B
What I believe you want to do is to copy x
and y
(or a longer chain of 5 in your example) to new, slightly-different commits x'
and y'
, resulting in this graph:
x' - y' <-- B
/
n - n - n - n <-- upstream/B
/
..- * - o - o - o <-- upstream/B@{1}, in reflog
\
x - y <-- [reflog only]
To do this, you simply need to tell git rebase
to start with the commit right after upstream/B@{1}
(that's x
) when rebasing branch B
onto the new upstream/B
:
$ git rebase --onto upstream/B 'upstream/B@{1}' B
This is why I left the reflog label upstream/B@{1}
in the graph drawings: after a git fetch
updates upstream/B
with one of these rewrites, it's the label that lets you find x
easily. Note that if upstream/B
has been updated several times, the reflog number might have increased beyond 1
. (I also put the ...@{1}
into single quotes above, to protect it from shells—there are a few—that like to eat braces. Chances are yours doesn't and they're not needed.)
The latest versions of git (2.x, though 1.9 had it I think) have a new flag option to git merge-base
called --fork-point
that is meant to help automatically figure out where the o
-to-x
transition is, even if the reflog number is no longer just 1
. So if you have a new enough git you can totally automate the whole thing. If not, you can count manually (as you have been doing), or manually poke through the upstream/B
reflog to make sure @{1}
is the right suffix. In all cases, what you need is to locate that o
-to-x
transition, since that's the argument you must pass to git rebase
.
(You might not need the --onto
argument, but it's safe to include. Without --onto
git chooses the "on-to" from the branch's so-called "upstream". This is a terribly confusing term, especially since one of the rebase
arguments is actually called upstream
and it means something different! The upstream argument is where we are using upstream/B@{1}
; it identifies commits to exclude from the rebase cherry-pick copying process.)
1In fact, it's at least two new commits—two for plain git stash save
, three if you add -a
or -u
—and the one to which the refs/stash
reference points is a merge commit. It's not what one would normally think of as a merge; the script simply uses—one might say "abuses"—the multiple-parent-commits aspect of a merge commit to be able to store these commits and use a single reference-name to find all of them later.