4

I am writing a pre-receive hook for Git. This is the one where if multiple commits are pushed, and any one of them fail, then the whole push fails. Which is what I want.

My problem is that not all the hashes from all commits are passed in. Only the most recent commit hash is, e.g.

2 commits are being pushed to a repo:

Commit 1 - 4b5w<br>
Commit 2 - 6gh7 -------------> passed in to pre-receive hook, 
                               but I want the previous hash too.

I can't use the update hook which is called for each ref, because I don't want any commits to go through if any one of them fails, e.g. commit 1 passing and commit 2 failing is not acceptable, as I would have to somehow rollback commit 1 when commit 2 fails.

How do I get the hashes from ALL commits being passed to the pre-receive hook?

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
noobcoder
  • 6,089
  • 2
  • 18
  • 34

2 Answers2

2

You can use a pre-receive hook and still list all pushed commits.
See this answer which includes:

chomp(my @commits = `git rev-list $old..$new`);
if ($?) {
  warn "git rev-list $old..$new failed\n";
  ++$errors, next;
}

foreach my $sha1 (@commits) {
  // validate some policy
}

As commented by torek, this is only for the master branch.

You can deal with multiple branches:

#!/bin/bash
while read oldrev newrev refname
do
    branch=$(git rev-parse --symbolic --abbrev-ref $refname)
    if [ "master" == "$branch" ]; then
        # Do something
    fi
done
Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • so obvious I feel like kicking myself, completely forgot about the old ref that is passed in, thanks. – noobcoder Aug 17 '16 at 21:55
  • [jthill's answer](http://stackoverflow.com/a/39006852/1256452) is a bit better as it handles multiple reference updates that share objects. The Perl script in the original answer looks only at an update to branch `master`. Of course, most of the real problem usually consists of defining precisely when and how to examine which objects in the first place: do you really want to validate every object on every push, or only certain ones on certain branches? What about notes? – torek Aug 18 '16 at 00:43
2
while read old new ref; do 
    [[ $new = *[^0]* ]] && news="$news $new"
done
git rev-list $news --not --all

This will avoid things like fastforwards over previously-pushed commits triggering wasted revalidation of unchanged content.

jthill
  • 55,082
  • 5
  • 77
  • 137
  • Nice, that seems more precise. +1 – VonC Aug 18 '16 at 05:20
  • Better to use `--not $olds` where `$olds` is accumulated as well; but watch out for null hashes in either `$new` *or* `$old`, which indicate newly-created references (where you need `--not --all` despite its imperfections) or deleted references (where you don't want to scan anything in this particular case). – torek Oct 30 '18 at 23:30
  • @torek agreed on the `$new` check for deletes, thanks! I disagree on the old-value though, everything incoming in `$old` was by definition already in `--all`, why revalidate commits in marching-ant fastforwards like pushing stable up to testing? – jthill Oct 31 '18 at 00:40
  • (oops, replaced comment, this is about *pre* receive, not post-receive) Yeah, that's reasonable enough for a pre-receive hook. I came across this in a link from someone talking about a post-receive hook, though. In the case of a post-receive hook, one must consider that the hook runs after Git drops the lock, so the references that `--all` sees may have changed since the post-receive hook was first invoked. – torek Oct 31 '18 at 00:45