简体   繁体   中英

How to efficiently find the newest shared commit of a branch while running server pre-receive hook?

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:

  • the 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;
  • a git rev-list <hash-of-7> --not --branches --topo-order will list commits 7, 6, and 5, in that order;
  • the parent of 5 is 3.

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM