I have a problem creating a pre-receive
hook on a git remote branch, doing what I want.
What's the problem?
Direct commits to the master branch should be not allowed. Only merges into the master branch should be allowed.
Solution
My solution until now is to check, if there are changes in the push from a user, where the master is affected. But the problem is that I can't differentiate if the change is a direct commit or a merge.
#!/bin/sh
while read oldrefid newrefid refname
do
if [ "$refname" = "refs/heads/master" ]; then
echo $(git merge-base $oldrefid $newrefid)
echo "---- Direct commit to master branch is not allowed ----"
echo "Changes only with a merge from another branch"
exit 1
fi
done
Has anyone an idea, how to check if the change is a merge?
Thank you!
Just run git commit . You don't have to add anything before doing this, hence in the end you get the message no changes added to commit .
Pre-receive hooks enforce rules for contributions before commits may be pushed to a repository. Pre-receive hooks run tests on code pushed to a repository to ensure contributions meet repository or organization policy. If the commit contents pass the tests, the push will be accepted into the repository.
Not committing to master prevents colliding commits and having to merge each time 2 people change the same file.
Here's the short answer: look at the value produced by:
git rev-list --count --max-parents=1 $oldrefid..$newrefid
You want this to be zero. Read on for the explanation (and caveats).
Your loop has the right outline:
The trick lies in performing the check. Consider the two other pieces of information you receive, namely the old and new SHA-1 IDs, and that in these hooks, one (but not both) of these two SHA-1 IDs may be all 0
s (meaning the ref is being created or deleted).
To insist that the change not be a creation or deletion, your test should ensure that neither SHA-1 is all-zeros. (If you're willing to assume that only deletion need to be checked you can just check that the new SHA-1 is not all-zero. But if creation could occur—which is only the case if somehow the master
branch gets deleted after all, e.g., by someone logging on to the server receiving pushes, and manually deleting it—you'll still need to make sure that the old SHA-1 is not all-zero for the final test. Clearly this kind of deletion is possible, the question is whether you want to write code to handle the case.)
In any case, the most typical push simply updates the reference. Note that any new commits have already been written to the repository (they'll be garbage-collected if you refuse the push), so your task at this point is to:
To find these two sets of commits, you should use git rev-list
, since that's precisely its job: to produce a list of SHA-1s specified by some expression. The two expressions you want here are "all commit IDs find-able from one revision, that are not already find-able from some other ID". In git rev-list
terms these are git rev-list $r1 ^$r2
,1 or equivalently, git rev-list $r2..$r1
, for two revision-specifiers $r1
and $r2
. The two revspecs are, of course, just the old and new IDs for the proposed push
.
The order of these two IDs determines which set of commits git rev-list
lists: the ones that would be removed—this set is empty for a fast-forward operation—and the ones that would be added.
In this particular case, your goal is not to produce these lists of commits themselves (although that would work), but rather, to select something from these lists.
You may wish to prevent commit deletions (i.e., to enforce fast-forward-ness even if the user doing the push
specified a force flag). In this case, simply verifying that the "to be removed" list is empty suffices. You can do that by making sure that the list is actually empty, or—simpler in a shell script—having git rev-list
count them for you and check that the resulting number is zero.
You definitely wish to prevent additions that are not merges, but allow additions that are. In this case, adding --max-parents=1
(which can also be spelled --no-merges
) tells git rev-list
to suppress commits that have two or more parents, i.e., merges. Adding --count
gets you a count of commits that meet this "not a merge because zero or one parent" constraint. If this count is zero, then any commits being added must by definition be merges.
Hence:
n=$(git rev-list --count --max-parents=1 $oldrefid..$newrefid)
if [ $n -gt 0 ]; then
echo "disallowed: push adds $n non-merge commit(s)" 1>&2
exit 1
fi
for instance, will suffice to enforce this particular constraint.
1Almost but not quite equivalent, you can write git rev-list $r1 --not $r2
: the difference is that the effect of --not
lingers, so that if you were to add yet another revision ID $r3
the --not
will apply to r3
. That is, git rev-list A ^B C
means yes-A, not-B, yes-C
but A --not B C
means yes-A, not-B, not-C
. Note that in rev-list
syntax, B..A
means A ^B
, i.e., exactly B
is inverted.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With