7

I'm writing a pre-receive hook to do some validation before accepting commits on the push. It works fine with existing branches since I use following git command to get a list of incoming commits:

git rev-list $old_sha1..$new_sha1

However, above command fails if user pushing a new branch into a shared repository because old_sha1 is all zeros. So the question is how do I get a list of incoming commits for a newly created branch. Doing git rev-list $new_sha1 does not work since it gives all revisions till the beginning of times. Is there a way to specify something like this:

git rev-list $branching_sha1..$new_sha1
KiRPiCH
  • 379
  • 3
  • 6
  • Possibly related and possibly helpful: http://stackoverflow.com/questions/1549146/git-find-common-ancestor-of-two-branches (`git merge-base`) – Tyler Dec 24 '10 at 22:57
  • MatrixFrog it's related but not exactly it. I need to figure out a list of commits that are being pushed to central repo inside the server side hook. I was able to kind do it with `git rev-list master..$new_sha1`, but that works only when new branch was done from master. Have not tried with new branch of another branch. – KiRPiCH Dec 25 '10 at 02:07

3 Answers3

11

Try this:

git rev-list $new_sha1 $(git for-each-ref --format '^%(refname:short)' refs/heads/)

This uses git for-each-ref to print known refs; specifies in --format that the short name be output along with a preceding caret; and says with refs/heads/ to selecting just local heads. You end up running something like this:

git rev-list $new_sha1 ^master ^foo ^bar ^baz

The caret means “exclude commits reachable from here”. So this will give you all commits that are reachable from $new_sha1, but aren’t from any of the existing refs.

To handle the new-ref and existing-ref cases at once, you can use bash’s array support:

if ! [ $old_sha1 = 0000000000000000000000000000000000000000 ] ; then
    excludes=( ^$old_sha1 )
else
    excludes=( $(git for-each-ref --format '^%(refname:short)' refs/heads/) )
fi

git rev-list $new_sha1 "${excludes[@]}"
Aristotle Pagaltzis
  • 112,955
  • 23
  • 98
  • 97
  • Why do you exclude only `$old_sha1`? If the push was a merge, all commits reachable from the merged branch would be processed again, even though the server already knew them. – Michal Rus Jun 19 '15 at 23:30
  • The syntax `git rev-list $new_sha1 ^$old_sha1` is exactly equivalent to `git rev-list $old_sha1..$new_sha1`, which the OP was already using and satisfied with. I never gave any thought to whether that part is correct. – Aristotle Pagaltzis Jun 21 '15 at 22:34
  • That said, I think you are right and I think in that case the answer is to always do the `for-each` song and dance – that is, make the whole code simply this: `git rev-list $new_sha1 ^$old_sha1 $(git for-each-ref --format '^%(refname:short)' refs/heads/)` But I’m not sure, so I’m not updating the answer at this time. – Aristotle Pagaltzis Jun 21 '15 at 22:38
3

More simply, you can use this:

git rev-list $new_sha1 --not --all

It's quite the same principle as in Aristotle Pagaltzis' answer, but it just uses rev-list's built-in options (which I guess were added after this question was answered...).
The --all parameter is equivalent to the for-each-ref part: it adds all known refs to the command args. The --not parameter, in this particular case, is like adding the ^ prefix to all refs added by --all.
Thus this command will print commits reachable from the new SHA1 except all those reachable from the known (i.e. already pushed) refs.

Community
  • 1
  • 1
fbastien
  • 762
  • 1
  • 9
  • 20
1

The following helped in my case. I personally think it's very simple.

OLD_REV=$2
NEW_REV=$3

if [ $OLD_REV == "0000000000000000000000000000000000000000" ]; then
    OLD_REV=$(git merge-base master $NEW_REV)
fi;

git diff --name-only $OLD_REV $NEW_REV
Kashif Nazar
  • 20,775
  • 5
  • 29
  • 46