It happens all the time at work, that someone accidentally commits something to master instead of to the intended feature branch, the person then tries to resolve it, only to have the changes suddenly disappear. I have searched thick and thin, and can find no documentation to shed light on why this happens, or how to remedy the situation.
Here are steps to reproduce:
$ git init
$ echo hello world > testfile
$ git add testfile
$ git commit -m 'init'
$ git branch A
$ echo abc >> testfile
$ git commit -am 'append abc'
$ git revert HEAD --no-edit
$ git checkout A
$ echo fff > secondfile
$ git add secondfile
$ git commit -m 'Add second file'
$ git cherry-pick master^
At this point the git history looks like this:
$ git log --oneline --all --graph --decorate
* ac6f9b4 (HEAD -> A) append abc
* 54be952 Add second file
| * 9ba1f16 (master) Revert "append abc"
| * ef7c8d6 append abc
|/
* 65a885d init
Watch then what happens when I rebase branch A on top of master:
$ git rebase master
$ git log --oneline --all --graph --decorate
* 9d08739 (HEAD -> A) Add second file
* 9ba1f16 (master) Revert "append abc"
* ef7c8d6 append abc
* 65a885d init
What happened to the commit that was at the head of A, commit ac6f9b4? Where did it go? why wasn't it reapplied?
While this was just a small example with a single commit missing on the end, but sometimes we end up losing several commits from the middle of a long commit chain, and then they seem practically invisible :(
Rebase doesn't re-apply commits that have already been applied in the new base -- here, it's your append abc
change. It's already in the master history. This is so often right that git does this without comment. It's arguably at least worth mentioning in the presence of subsequent reverts.
To see whether anything you're rebasing is already part of the new base history (master
, here),
git log --oneline --cherry ...master
and look for =
-marked commits. Those are commits in master that match commits in your branch; rebase won't re-apply them. Swap master...
for ...master
to see the local equivalents.
If you want an effectively blinded rebase, a first cut is
git checkout -B A master
git cherry-pick ..A@{1} # < added the very important `..` by edit
which just moves A onto master and then cherry-picks everything you just left behind. To ignore merges (which you probably should), add --no-merges
to the cherry-pick, it turns out when you ask cherry-pick for a series it just passes the whole set to git rev-list
so you can use the machinery directly:
git cherry-pick --no-merges ..A@{1} # just learned now that cherry-pick takes this
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With