You have two direct and obvious options:
Add a second commit that undoes the first cherry-pick. (Now you have a source snapshot that looks as if you never did the first cherry-pick.)
Remove the first cherry-pick, i.e., alter your commit history.
Which of these to use depends on many things, including whether you're of the school that says "never rewrite any history ever" (in which case you must use the first approach) or whether you have additional commits beyond the cherry-picked one, in graph order.
Remember that each Git commit is uniquely identified by its hash ID. If we use uppercase letters to represent those commits (instead of raw hash IDs) we can draw a clearer picture of what is going on. For instance, suppose we start out with this series of commits:
... <-F <-G <-H <--yourbranch (HEAD)
\
I <-J <--origin/theirbranch
You then decide that for whatever reason, you like their commit J
, so you run git cherry-pick <hash-of-J>
or git cherry-pick theirbranch
. This copies the effect of commit J
, making a new commit. We could call this commit K
but let's use J'
to indicate that it's a copy of J
:
...--F--G--H--J' <-- yourbranch (HEAD)
\
I--J <-- origin/theirbranch
At some point in the future, they—whoever they are—have discarded their commit J
in favor of their new and improved commit K
, so that after you run git fetch
you have:
...--F--G--H--J' <-- yourbranch (HEAD)
\
I--K <-- origin/theirbranch
(Commit J
has vanished entirely: new commit K
points back directly to I
. This is that "history rewriting" that people talk about.)
You can rewrite your own history, at this point, using git reset --hard
. Of course this throws out any work you're doing, but let's suppose you did not do any new work so that this is OK:
J' [abandoned]
/
...--F--G--H <-- yourbranch (HEAD)
\
I--K <-- origin/theirbranch
You can now cherry-pick their commit K
more easily since it does not conflict with your copy J'
of their original J
. This results in you having:
J' [abandoned]
/
...--F--G--H--K' <-- yourbranch (HEAD)
\
I--K <-- origin/theirbranch
But suppose, on the other hand, that you do have work that depends on your J'
copy of their J
. For instance, suppose you have made a half dozen more commits since you cherry-picked their J
to make your J'
? Then you have this right now:
...--F--G--H--J'-L--M--N--O--P--Q <-- yourbranch (HEAD)
\
I--K <-- origin/theirbranch
It's now much harder for you to get rid of your J'
: it's embedded in your history, which extends all the way back from Q
to F
.
You can use interactive rebase (git rebase -i
) to attempt to pluck J'
out of your history by copying commits L-M-N-O-P-Q
to new commits L'-M'-N'-O'-P'-Q'
that are a lot like the originals, but skip J'
:
J'-L--M--N--O--P--Q [abandoned]
/
...--F--G--H--L'-M'-N'-O'-P'-Q' <-- yourbranch (HEAD)
\
I--K <-- origin/theirbranch
(Notice that this looks a lot like repeated cherry-picking. That's because that's what git rebase
is, it's repeated cherry-picking!) But if that's too painful, or objectionable for other reasons, you can now make a new commit R
that reverses the effect of your copy J'
of their original J
, and add that to your branch:
...--F--G--H--J'-L--M--N--O--P--Q--R <-- yourbranch (HEAD)
\
I--K <-- origin/theirbranch
New commit R
may be easy to make (or may not), by running:
git revert <hash-of-J>
The git revert
command tells Git: Figure out what changed in the given commit, and make a new commit that has the effect of undoing those changes. For every line I added to some file, remove that line from that file. For every line I removed from some file, put that line back. For every file I deleted wholesale, bring that file back; for every file I created from scratch, remove that file entirely.
When this process completes, you have a snapshot in commit R
that looks as though commit J'
never happened. (If your changes in commits L
through Q
conflict with this attempt to back out J'
, you will generally see a merge conflict during the revert process. Note that if this is the case, you will generally see the same conflicts if you use an interactive rebase to discard commit J'
while copying L
-through-Q
to new commits.)