gitgit-cherry

"git cherry" shows an already cherry-picked commit as "ready to be applied"


I've been working on branch align and want to cherry-pick some commits from it to master. I use git cherry to look for candidate commits:

C:\Users\me\Documents\repo>git cherry -v master align | head -1
+ c2bbb3d99440be7524673702c92ad65e6522d2b1 Made RW_assert() work on 64-bit.

My understanding is the '+' means there's no equivalent commit on master. But I remember cherry-picking this commit to master already. In fact, it was the most recent change to file rw-fwd.h on branch align:

C:\Users\me\Documents\repo>git log align -1 rw-fwd.h
commit c2bbb3d99440be7524673702c92ad65e6522d2b1
Author: Me
Date:   Thu Jun 8 10:41:01 2017 +1000

    Made RW_assert() work on 64-bit.

And is still the most recent change to rw-fwd.h on branch master:

C:\Users\me\Documents\repo>git log master -1 rw-fwd.h
commit 5bc790b9b0adfcdc6c0a07b679155e33974e343a
Author: Me
Date:   Thu Jun 8 10:41:01 2017 +1000

    Made RW_assert() work on 64-bit.

The documentation for git cherry states: "The equivalence test is based on the diff, after removing whitespace and line numbers. git-cherry therefore detects when commits have been "copied" by means of git-cherry-pick[1], git-am[1] or git-rebase[1]." Visually, using git show, the commits look the same, but let's be absolutely sure:

C:\Users\me\Documents\repo>git show c2bbb3d | git patch-id
f6ad0912fd71f694b6b00c6ea93c87af7cf4ab98 c2bbb3d99440be7524673702c92ad65e6522d2b1

C:\Users\me\Documents\repo>git show 5bc790b | git patch-id
f6ad0912fd71f694b6b00c6ea93c87af7cf4ab98 5bc790b9b0adfcdc6c0a07b679155e33974e343a

The patch-ids are identical. Why has git cherry not detected that this commit was already cherry-picked?

Edit: below is a sanitized git log --oneline --decorate --graph. I've added this in response to the answer by @torek, not because I have any doubt the answer's correct, but because I'd value some help in interpreting the real commit log in light of that answer.

master is at a52f43e (first line) and align is at 9ffa94e (third line). align is the rightmost line on the graph all the way down, and is not merged from.

* a52f43e ######## ############ #####
* 507e731 ##### ##### ### ## ###
| *   9ffa94e (origin/align) Merge branch 'master' of https://ghe/repo/repo into align
| |\
| |/
|/|
* |   657d6c8 Merge pull request #8 from repo/test_system
|\ \
| * \   679739b Merged origin master into local branch.
| |\ \
| |/ /
|/| |
* | |   c7a5944 Merge branch 'master' of https://ghe/repo/repo
|\ \ \
| * \ \   e12016c Merge pull request #7 from repo/lean-mean
| |\ \ \
| | * | | 144fefd ##### ####### ##### ## #######
| | * | | 87e0e23 ####### ############## ## ####### ####### ...
| * | | | 8613f4e ######## ####### ######### ## ###### #####...
| |/ / /
* | | | 8f70a8d ######### #### ##### ### ###### ### ########...
* | | |   aedb306 Merge branch 'master' of https://ghe/repo/repo
|\ \ \ \
* \ \ \ \   0a4fd59 Merge branch 'master' of https://ghe/repo/repo
|\ \ \ \ \
* \ \ \ \ \   e1cfbfd Merge branch 'PBA_Experiment' of https://ghe/repo/repo
|\ \ \ \ \ \
| * | | | | | 7f1bc5b (######/##############) ##########
| | | | | * | 323d965 ######## ######### ####### ########.##...
| | | | | * | 5839749 ##### #######-#### ###### ## ### ## ##...
| | | | | | * d96ba71 ##-####### ############, ## ##### ####...
| | | | | | * 680354b #### ##### ######### #####, ##-###### ...
| | | | | | * a066e66 #####.  ### ### ###### ########## ####...
| | | | | | * afb7945 (###: ####-##-##-#######, ###: ####-##...
| | | | | | *   a14e769 Merge remote-tracking branch 'origin/master' into align
| | | | | | |\
| | | | | |_|/
| | | | |/| |
| | | | * | |   4354de6 Merge pull request #5 from repo/tech-bug-fixes
| | | | |\ \ \
| | | | | * | | c084aa8 (######/####-###-#####) ##### ######...
| | | | | * | | aea8a4f ######### ###-######## # ###### ### ...
| | | | | * | | 7857c95 ### #### #############, ### ### ####...
| | | |_|/ / /
| | |/| | | |
| | | | * | | 0b65102 .
| | | | * | |   2448af9 Merge branch 'master' of https://ghe/repo/repo
| | | | |\ \ \
| | | | * | | | 04c03da ##### ##### ###### ## ###### #######...
| | | | | | | * e05b167 ###### ### ## ######### ##########, ...
| | | | | | | * c3422d5 ##### ###############() ###### ## ##...
| | | | | | | * 92f4eff ##### ### ## ######/######### ####-#...
| | | | | | | * 35fb291 ### ################## (##### ######...
| | | | | | | * 6d12b2c ##### # ####### ## ######### #-###-#...
| | | | | | | *   aa84d37 Merge branch 'master' into align
| | | | | | | |\
| | | | | | |_|/
| | | | | |/| |
| | | | | * | | a96cb9d ##### #### ##### ######## ### ##### ...
| | | | |/ / /
| | | | * | | 911e5d2 ######## ### ##### #########.
| | | |/ / /
| | | * | |   d32ef9a Merge pull request #4 from repo/tech
| | | |\ \ \
| | |/ / / /
| | | * | | 092858f (######/####) ######## #### ## ######.
| | | * | | 1d862dd ##### ############ ## #### ### ### #### ...
| | | * | |   7eae069 Merge branch 'master' into tech
| | | |\ \ \
| | | |/ / /
| | |/| | |
| | | * | | f639a00 ##### ##### ## ### ########
| | | * | | ac794d9 ##### ################ ###### #### #####...
| | | * | | b0296ac ####### ########## ## ####### #### ## ##...
| | | * | | 91f2adf ###########
| | | * | | 47ae5eb ######
| | | * | | a975c46 ####### ##### ## #########
| | | * | |   041a552 Merge branch 'tech' of https://ghe/repo/repo into tech
| | | |\ \ \
| | | | * | | 908b8f7 ######
| | | | * | |   85ff704 Merge branch 'tech' of https://ghe/repo/repo into tech
| | | | |\ \ \
| | | | * | | | 6351c83 ######
| | | * | | | | 1486296 ###### #### ########.
| | | | |/ / /
| | | |/| | |
| | | * | | | 89cefd9 ######### ############# #### ### #####...
| | | |/ / /
| | | * | | 401b346 ### ########## ## ######### ######### ##...
| | | * | |   0e36458 Merge branch 'tech' of https://ghe/repo/repo into tech
| | | |\ \ \
| | | | * | | 7029347 ######
| | | | * | |   d819996 Merge branch 'tech' of https://ghe/repo/repo into tech
| | | | |\ \ \
| | | | * | | | 1d11d8a ######
| | | | * | | |   0f52244 Merge branch 'tech' of https://ghe/repo/repo into tech
| | | | |\ \ \ \
| | | | * | | | | 63f49c3 ######
| | | * | | | | | 15e0017 ######
| | | | |_|/ / /
| | | |/| | | |
| | | * | | | | a7c00b8 ######### ## ### #### ### #### ####
| | | | |/ / /
| | | |/| | |
| | | * | | | 5074fe5 ####### ## #### ########## ###### ## #...
| | | |/ / /
| | | * | | 989e68c ####### ## ### #### ### #### #### ####, ...
| | | * | |   9b3545c Merge branch 'tech' of https://ghe/repo/repo into tech
| | | |\ \ \
| | | | * | | 2a9f6b2 ##### #### ## ######## ######### #####...
| | | * | | | 519ac87 ######
| | | |/ / /
| | | * | | e850bac ######
| | | * | | 7e920c4 ######### ## ### #### ### ####### ### ##...
| | | * | | ac4608d ######## ## #### #### ########### ## ###...
| | | * | | f7f4e0c ###### ##-######### ## ###### ## ## ####...
| | | * | | 7ed258f ##### ######## ## ###### ##### ##### ###...
| | | | | * 67c8f71 ##### ### ## ######### ######## ########...
| | | | | * 2280a88 ##### ######### ### #### ### ##########
| | | | | * 3bd5b85 ##### # ### ## ##-######; ## ##### #####...
| | | | | *   7919cc3 Merge branch 'master' into align
| | | | | |\
| | | |_|_|/
| | |/| | |
| | * | | | 7278053 ##### #### ####### ##########.
| |/ / / /
|/| | | |
| | | | * 31d1e44 ##### # ### ### ### ######### ## ###### ##...
| | | | * 87f8195 ######## ### ### ###### ##### ####### ####...
| | | | * 379bd4a ### '#' ### ## #### # #### ### ####### ###...
| | | | * fe30105 ##### ######### ###### ##### ## ###### ###...
| | | | *   3b50f20 Merge branch 'master' of https://ghe/repo/repo into align
| | | | |\
| |_|_|_|/
|/| | | |
* | | | |   48ca2d6 Merge branch 'master' of https://ghe/repo/repo
|\ \ \ \ \
| |/ / / /
| * | | |   edbf46e Merged from align
| |\ \ \ \
| * | | | | af282b8 ##### ###### ### ######### ### ## .#####...
| * | | | | 2237855 ####### ### ######### ### #### ##### ## ...
| * | | | | f0711d9 ##### ###################() ### ########...
| * | | | | 5bc790b Made RW_assert() work on 64-bit.
* | | | | | c53aad5 ##### ######## ### ######## ####### ### ...
* | | | | | 1b4f135 ##### ####### #### #### ### ######-#####...
| | | | | * 49730f3 #####, ### ######, ## ###### ######## ##...
| | | | | * 8efc11e ##### ###### ### ######### ### ## .#####...
| | | | | * e0f583a ####### ### ######### ### #### ##### ## ...
| | | | | * 3b1a263 ##### ###################() ### ########...
| | | | | * c2bbb3d Made RW_assert() work on 64-bit.
| | | |_|/
| | |/| |
| | * | | 67154d6 ##### ##### ####### ### ####. ######### ##...

Looking at the bottom of the log above, c2bbb3d is the initial commit to align, 5bc790b is the cherry-pick of that commit to master. 67154d6 I expect is the common ancestor, that's where the output of git cherry stops.

The question I still can't answer is, how could I get a list of commits made to align, which are not already on master? It seems like any commit reachable from align, but not from master, is a candidate, as long as it hasn't already been cherry-picked to master. Any branch which feeds into master would have to be checked for cherry-picks -- that includes every branch in the graph except the rightmost one, align.

NB: we've been using git for just a few months so I'm pretty sure much of what we're doing is not best practice. We're learning a lot from trial and error.


Solution

  • Let me put the newly added extra question at the top, since it's actually kind of more important, and easy to answer:

    The question I still can't answer is, how could I get a list of commits made to align, which are not already on master?

    You get that with the output of git rev-list (which is meant for computers) or git log (meant for humans) when given:

    align ^master
    

    as the two constraints. (Minor aside as a reminder: git rev-list and git log are essentially the same command, with the difference being that git rev-list is aimed at further processing, so that it just produces full hash IDs by default, while git log is aimed at human reading, so it shows commits by default. There are a bunch of small-but-important details about the way git log shows commits, but for --oneline output they don't matter here.)

    These constraints tell the Git revision-list walker: Find me commits that are "on"—meaning "reachable in a walk through history starting from"—the commit specified by the name align, but exclude from that list all commits that are "on" master. Given the graph above, this list of commits is fairly long.

    This particular form, yes ^no, can be spelled no..yes instead, giving the more familiar short form:

    git log --decorate --oneline --graph master..align
    

    (note that there are two dots in this form). I've included the DOG, Decorate Oneline Graph, options, since for this particular purpose the friendly dog really is often helpful.1

    If you reverse the names—git log align..master—you get the, in this case two, commits that are on master that are not on align.

    This is also what the three-dot or symmetric difference form deals with: when we write master...align or align...master, as seen in the original answer below, we tell Git to produce both of these walks. One finds commits that are on master that are not on align (just two), and the other finds commits that are on align that are not on master (many).

    When we use this three-dot symmetric difference form with git rev-list (or git log), we can tell Git to mark up the output to show which side the commits come from. That mark-up can even use git patch-id to find commits in the two sides that are identical. But it doesn't look at any commits that were not selected by both sides, and in particular, when you strip it down to --left-only or --right-only, you can drop a lot of data. (Those data points are often useless ones and dropping them is good, but they're not always useless.)


    1For git log, I think it's actually a good idea to have --decorate turned on pretty much all the time. In fact, it's such a good idea that Linus Torvalds himself got it added to Git back in v2.1.0, but nobody remembered to document it until Git v2.9.0.


    Note that the graph you have added above is somewhat complicated (many squirrelly little lines running around), so that there is more than one commit at which various lines fork and join. When you use master...align or align...master, the revision-walking code prunes away any commits reachable from both starting-points. Imagine printing out the log and taking two highlighters to it. With one color, you start marking at master and color all the lines and stars you can get to from there. With the other color, you start marking at align and color all the lines and stars you can reach from there. Wherever the two highlightings overlap, those commits are on both; where they don't, those commits are on just one branch.

    Original answer, regarding git cherry

    The patch-ids are identical. Why has git cherry not detected that this commit was already cherry-picked?

    The git cherry command looks only at a limited subset of the commit graph. Looking at the whole thing would take too long for too little gain.

    Exactly how you got into this particular situation, I am not sure (there is more than one way to get here), but here are two different [Edit: and simple] situations as they might be shown by git log --oneline --decorate --graph align master:

    * c2bbb3d (align) Made RW_assert() work on 64-bit.
    * xxxxxxx some other commit here
    | * 5bc790b (master) Made RW_assert() work on 64-bit.
    |/
    * yyyyyyy whatever commit here
    * zzzzzzz yet another commit
       ... snip ...
    

    Or:

    * c2bbb3d (align) Made RW_assert() work on 64-bit.
    * xxxxxxx some other commit here
    | * zzzzzzz (master) yet another commit
    |/
    * yyyyyyy whatever commit here
    * 5bc790b Made RW_assert() work on 64-bit.
       ... snip ...
    

    Running git cherry master align will show the commit as redundant, i.e., not needing cherry-picking, only for the first graph. The reason for this is that git cherry looks at only the portion of the graph that "extends up" from the point where the two graph lines fork.

    Since the two graph lines rejoin at yyyyyyy whatever commit here, the command never looks at any commit "at or below" that point. Given that you got a claim that c2bbb3d was "new", your graph must resemble the second graph, not the first (and your comment reply says that it does).

    A more precise definition

    More precisely, git cherry master align uses git rev-list --left-right master...align to identify commits that are on either the align branch, or the master branch, but are not on both branches. For simple graphs like the ones above, this cuts off the merge base and all its parent commits. If the graph has a more complex topology, there may be multiple merge bases; all get excluded; exactly those commits that are reachable from just one of the two specific commits is included, and each such commit is marked, internally, as whether it was reached from the left-side argument (master in master...align) or the right-side argument (align in master...align).

    The git cherry command—or even the git rev-list command itself, with the right arguments—then computes the git patch-id for each commit on the two sides. This lets it detect which commits are the same, and which are different. But it never computes a patch ID for any of the commits excluded by the symmetric-difference three-dot syntax. In a large repository, that excludes most of the commits, and normally it does not matter for those since those commits are already on both branches anyway.