While the server pre-receive hook script is executing, suppose it receive a new branch called baz. Branch foo, qqaz and bar are existing branch. How to efficiently find the newest shared commit of baz(It's commit 3 in the example below. not commit 2 and 1)?
---0---1 foo
\
\ 8---9---10 qqaz
\ /
2---3---4 bar
\
5
\
6---7 baz
A bit complex, but should give the correct result:
git rev-list baz | grep -Ff <(git rev-list foo qqax bar) | head -1
If your shell does not understand the <(...)
operator, you can use a temp file:
git rev-list foo qqax bar > /tmp/commit-list.txt
git rev-list baz | grep -Ff /tmp/commit-list.txt | head -1
Another way can be to successively take the merge-base
of baz
and the target branches, and keep the latest of those commits.
To do this correctly in all cases is anywhere from "very tricky" to "impossible", because git push
can receive requests to change, add, and/or multiple branch names all in one go. That is, just because your receiving Git has branches named foo
, qqaz
, and bar
identifying commits 1, 10, and 4 respectively now does not mean that this will be the case when the push finishes: it might contain a request to set foo
to commit 0, to add a new commit 11 and set qqaz
to point to that commit, to add several new commits past 4 and update bar
, and so on.
If, however, you limit a git push
to a single branch-name update—which you can do if you control the various hooks—the problem becomes considerably more tractable. The trick is to recognize that the pre-receive hook itself runs before any changes to any branch names are made . If baz
is a new branch name , then there is no name yet, in all of the branch names in the repository, for commit 7. (There might already be a tag name for any of these commits, or a tag name creation or update in the list of references to be updated by this push: be sure to consider these when designing your algorithm.)
If we make use of that, then:
push
request has a line of the form 000...000 <hash-of-7> refs/heads/baz
in it, indicating that the branch name baz
will be added, if this push is accepted; git rev-list <hash-of-7> --not --branches --topo-order
will list commits 7, 6, and 5, in that order; Should there be any merge commits in the new commit sequence, things get more complicated:
---0---1 foo
\
\ 8---9---10 qqaz
\ /
2---3---4 bar
\ \
----5
\
6---7 baz
Here commit 5 is a merge, combining commits 2 and 3. I'm not sure which commit(s) you want to find in this case.
In general, when you wish to examine the commit graph, git rev-list
is the Git tool for doing so. The --branches
option tells it to use all branch names; --tags
tells it to use all tag names; --all
tells it to use all references. A --not
in the options causes subsequent references to be "negative references", as if you had listed, say, ^refs/heads/dev
to exclude branch dev
. 1 You can list particular patterns to include or exclude as well. See the git rev-list
documentation for details.
1 A later --not
cancels an earlier one, so that:
git rev-list HEAD --not br1 br2 br3 --not br4
means the same thing as:
git rev-list HEAD ^br1 ^br2 ^br3 br4
and in general the order of positive and negative references does not matter.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.