3

I have the following Git commits tree:

R - A - B - C - D (master)

I would like:

R - A - B - D' (master)
        \ - C  (new-branch)

How could I do that (assuming it is even possible)?

I started using git cherry-pick but I didn't manage to remove the commit from the master branch.

Thanks.

3
  • 1
    You might need to follow that cherry-pick with a rebase -i in order to remove the other commit, but I wouldn't recommend deleting it entirely.
    – Nic
    Commented Jan 24, 2012 at 18:31
  • 2
    In the "after" diagram, does D represent the same changeset as the first D, or does it represent the same tree (files) as the first D? It seems your intention is to preserve only the changeset because you wish to remove the contribution made by C. In this case, the convention is to call it D' (D prime) to indicate that it does not have the same data (tree/parent/commit info) as the original D.
    – tcovo
    Commented Jan 25, 2012 at 15:02
  • Thank you! I wasn't aware of this convention, yet I understand the ambiguity of the situation.
    – Thomas
    Commented Jan 25, 2012 at 17:39

4 Answers 4

3

You cannot get exactly what you want since there will be differences in the commits, but the content of the files will be as you want if you do:

$ git checkout master
$ git reset --hard sha1-of-B  # set master back to B
$ git cherry-pick sha1-of-D   # set master to R-A-B-D'
$ git checkout -b new-branch sha1-of-B   # set new-branch to B
$ git cherry-pick sha1-of-C   # set new-branch to R-A-B-C'

Note that you do not really need to cherry-pick to set new-branch, and you could just as easily do:

$ git reset sha1-of-C

while you have new-branch checked out. This will actually give you R-A-B-C instead of R-A-B-C'. Note that the only difference between C and C' is the commit time.

Once you do the reset, you may have a hard time locating the sha1 hashes of the various commits (they are available from the reflog), so you may want to put tags on everything before you begin, or store the history somewhere.

2
  • Thank you very much! It did the trick, although I had to fix some conflicts when I cherry-picked D.
    – Thomas
    Commented Jan 24, 2012 at 19:30
  • As Raman noted, a quicker way to make new-branch point to C is to just do git branch new-branch sha1-of-C at any point (this command does not care what HEAD is, i.e. it does not care what is checkout out).
    – tcovo
    Commented Jan 25, 2012 at 15:15
3

Avoid unnecessary cherry-picks by creating new-branch directly at C, and then simply rebase master to remove commit C:

On master:

git branch new-branch sha1-of-C
git rebase --onto sha1-of-C^ sha1-of-C master

The second command deletes C from master by rebasing master ("branch" in the git-rebase man page) commits starting from sha1-of-C ("upstream") onto the commit before sha1-of-C.

Or you can do the rebase interactively if that second command is too confusing:

git rebase -i sha1-of-C^

On the interactive rebase, simply delete commit C, save and quit.

During rebasing, if git complains about merge conflicts, resolve them, git add, and git rebase --continue. You would have to resolve these conflicts regardless of the method you choose.

1

On master:

git branch new-branch
git reset --hard B
git cherry-pick D
git checkout new-branch
git reset --hard C
0

A recipe that does not involve variables like B or sha1-of-C:

git checkout master if not already checked out.

git branch tempD              # create temporary branch, pointing to D
git branch new-branch HEAD^   # create new-branch, pointing to C
git reset --hard HEAD^^       # reset master to B
git cherry-pick tempD         # apply D's changeset onto B
git branch -D tempD           # remove temporary branch

Not the answer you're looking for? Browse other questions tagged or ask your own question.