git cherry
is superior to git log
for getting the difference between two branches. The problem is that its output is limited.
I'm trying to extract the email address (or user) associated with a commit. Here is what I'm doing.
git cherry firstbranch secondbranch | awk '/^+/ {print $2}' | awk '{ system("git show $1"); }'
All I get is the details of one commit. Instead of every commit that I do get with:
git cherry firstbranch secondbranch | awk '/^+/ {print $2}'
Something is going wrong with the second pipe operation.
My question is: How do I use git cherry to get committer email?
Steven Penny's approach of piping to git log --no-walk --stdin
is a correct method, and is required in the most complex cases (complex filtering of selected commit-IDs).
There are a couple of tricks that one can use for this particular case though. If you check the (extensive, albeit confusing) documentation for git rev-list
you will find the options --left-right
, --left-only
, --right-only
, --cherry-mark
, --cherry-pick
, and --cherry
.
The --left-right
option works with any symmetric difference, i.e., commits selected by A...B
, which means "all commits that are on branch A
or branch B
, excluding any commits that are on both branches." This is how git cherry
does the first part of its thing: it finds commits that are on the symmetric difference. However, git cherry
goes on to throw out all the commits that are "the same"1 on both branches, then marks those on one side with -
and those on the other with +
. The --left-right
option marks all commits as either <
(left side) or >
(right side).
Since rev-list
is the Swiss Army Chainsaw command, it also has the ability to throw out commits that are the same. It has even more abilities too: it can throw out commits on one side entirely, same-or-different. This is what we want in this case: throw out "commits that are the same" (so that we keep only "different" ones), then throw out all "left side" commits (so that we keep only those that are in the right side but not the left). We get the former with --cherry-pick
: it keeps only commits that are considered "different". Then we add --right-only
to keep only those on the right side, i.e., when we say firstbranch...secondbranch
, keep only those that are both different and in secondbranch
... which is exactly what git cherry
does.
Hence:
git rev-list --right-only --cherry-pick firstbranch...secondbranch
produces the same commit ID list as:
git cherry firstbranch secondbranch
(with the single difference being that instead of + face0ff...
, it prints +face0ff
, i.e., no space after the +
mark).
This seems pretty silly: instead of just the four words git cherry
and two branch names, we need git rev-list
and a bunch of options and two branch names and three periods. We've typed in lots more letters to get pretty much the same thing. So we could just use the shorter command and pipe it to git log
, but now we get to the tricky bit.
git log
and git rev-list
are very nearly the same commandIn fact, they are built from the same source code, they just set some internal options differently (and git log
will pretend you said HEAD
if you don't name any other starting points). Since we're about to pipe the output of git rev-list
to git log --pretty='%h %ce'
, maybe we can just do the whole thing in git log
directly.
Sure enough, we can. We need all those same options as with git rev-list
, but now we can just run:
git log --cherry-pick --right-only --pretty='%h %ce' firstbranch...secondbranch
(you may want --no-merges
here as well—I'm not sure offhand what git cherry
does about merges!).
1"Sameness", in this case, is determined by the output of git patch-id
, which basically strips identifying line numbers off diffs (and also strips certain white space details). Thus, if one commit was cherry-picked into another branch, both such commits will usually have the same patch-ID. (The patch-IDs will differ if someone had to resolve merge conflicts.)