I'm trying to get all the commit hashes that are coming in from a merge.
If you are controlling whether or not the merge gets performed, you have an ideal situation, because you can easily enumerate the commits that would be reachable after the merge that are not currently reachable before the merge begins. (Since the merge does not alter the earlier part of the graph, you can still get the same result after the merge.)
Let's say that you are on the commit whose hash is H
at the tip of branch mainline
, and you are about to—but have not yet—issued the command:
git merge feature
to bring in commits from feature
that are not already from mainline
, as in:
...--o--*--o--o--H <-- mainline
\
E--F----G <-- feature
The list of commits that this merge would bring in—which is not really as interesting as the changes that it will merge since it's possible that, for instance, F
is simply a direct revert of E
so that only commit G
really matters—is simply those enumerated by:
git log mainline..feature
You may want to control the order of enumeration. See below, after the next quoted bit.
Note that after the merge, you have:
...--o--*--o--o--H--M <-- mainline
\ /
E--F----G <-- feature
and the commits that were brought in are those enumerated by M^1..M^2
. (This assumes a simple two-tip merge that results in a true merge commit. Octopus merges are enumerable but need fancier syntax, and fast-forward merges do not record the previous tip of mainline
and are problematic to analyze after-the-fact.)
One approach I've tried is possibly finding the commits between the merge commit and the last commit added to master. However, apparently these seem to be meshed by timestamps.
The git log
command walks the commit graph using a priority queue. Your "meshed by timestamps" observation is due to the priority of items in the queue. You may simply want to control this priority, probably with --topo-order
.
The overall loop is structured this way:
All inserts obey command line negations, e.g., --not foo
excludes all commits reachable from the commit specified by foo
including commit foo
itself. (This affects both initialization, in the next point, and iteration, below.)
git log
will initialize the queue by inserting as starting points all commits mentioned directly / positively on the command line. If no commits are mentioned, insert HEAD
. ("Positively" here refers to the fact that B ^A
has a positive reference to B
and a negative reference to A
. So A..B
excludes the commits reachable from A
, just like --not A
does.)
Now, while the queue is non-empty, git log
runs:
- Remove the front commit off the queue and work with it.
- Insert into the queue any parent(s) of that commit, as modified by any selected History Simplification or other options (including parent rewriting), that are not in the queue and have not yet been visited.
The overall process quits once the queue is empty.
Given that a merge has—by definition—two or more parent commits, any git log
step that traverses a merge has the potential to insert two or more commits into the queue. Of course, the queue can also start with two or more commits in it. Whenever the queue does have two or more commits, it's the priority that determines which commit is in which position in the queue.
The default priority is by committer date, with higher values (later dates) moving to the front of the queue. However:
--date-order
makes sure that children come ahead of parents. Otherwise it is the same as the default.
--author-date-order
uses the author date instead of the committer date, and is otherwise the same as --date-order
(parents come out after all of their descendants).
--topo-order
avoids interleaving the legs of merges.
See the git log
documentation for details. Note that --graph
implies --topo-order
.