Reputation: 1508
I need to run a post-commit git server side hook that si supposed to check if there has been a merge and do some automation if there was a merge.
I tried using git reflog -1
and it works great but only on the client side.
I also tried implementing a post-merge hook but that did not work.
when I run git reflog -1
on the server side I get no output.
any ideas how I can accomplish this?
Upvotes: 1
Views: 473
Reputation: 487755
ElpieKay's comment has the key to the answer, but it's worth a bit more detail. Since the server did not actually run git commit
, you must use a post-receive hook to watch for reference updates. Unfortunately, post-receive hooks are not simple. Here is a simple framework that is generally useful, expressed in POSIX shell:
#! /bin/sh
# sample post-receive hook
# return true if the argument ($1) is the null hash (all-0s)
is_nullhash() {
expr "$1" : '0*$' >/dev/null
}
while read oldhash newhash ref; do
# If the old hash is all 0s, the reference was just created.
# If the new hash is all 0s, the reference was just deleted.
# Otherwise, the reference was updated, from $oldhash to $newhash.
if is_nullhash $oldhash; then
op=create
elif is_nullhash $newhash; then
op=delete
else
op=update
fi
# If the reference begins with refs/heads/, the rest of it is
# a branch name. If it starts with refs/tags, the rest is a tag.
# Otherwise it's some other type of reference (not decoded here).
case $ref in
refs/heads/*) reftype=branch; shortref=${ref#refs/heads/};;
refs/tags/*) reftype=tag; shortref=${ref#refs/tags/};;
*) reftype=other; shortref=$ref;; # NB: not shortened!
esac
... insert code here ...
done
The code that goes in the "insert code here" section must:
For instance, if you want to watch specifically for a push to master
only:
if [ $reftype = branch -a $shortref = master ]; then
if [ $op = update ]; then
handle_master_update $oldhash $newhash
else
... do something different if the op is create or delete ...
fi
fi
where handle_master_update
is your function to deal with an update to master
. If you don't want to handle creation and deletion of the master
branch, you can simplify this further to:
case $ref,$op in
refs/heads/master,update) handle_master_update $oldhash $newhash;;
esac
in which case you can delete the boilerplate section that decodes the reference type.
Now we get to the heart of the update-handling:
# Do something with update to master branch.
# The old hash is $1 and the new hash is $2,
# so commits in $1..$2 now appear on `master` but
# were not part of `master` before (they may have
# been on some other branch, and may still be).
# Meanwhile, commits in $2..$1 used to be on `master`
# but have just been removed via force-push. If this
# list is empty, the push did not have to be forced,
# and maybe was not.
handle_master_update() {
local rev parents
git rev-list $2..$1 | while read rev; do ...; done # deal with removed commits
git rev-list --parents $1..$2 | while read rev parents; do
...
done
}
The first section (dealing with removed commits) is of course optional. The second section is the one where you wanted to detect merges, so we use git rev-list
(not git log
—rev-list is the more useful workhorse here) to enumerate all the added commits along with their parent hash IDs. The read
puts all the parent hashes into the variable parents
, so we can easily count them in the ...
section. For instance:
set -- $parents
case $# in
0) ...;; # $rev is a root commit
1) ...;; # $rev is an ordinary commit
*) ...;; # $rev is a merge, its parents are $1, $2, ... through $#
esac
The zero case is highly unusual—for it to happen as an update, we must have added a root commit to the branch, e.g., going from:
A--B--C <-- master
to:
A--B--C--D--H <-- master
/
F---------G
The git rev-list
, given C..H
(well, their hash IDs), will list H
, D
, G
, and F
in some order. The hash for H
will come first, but everything after that depends on any sorting options you supply to git rev-list
. Use --topo-order
to guarantee that you receive a topological sort, if that's important to how your code works (e.g., you might want --reverse --topo-sort
so as to always be working forwards, getting one of D, F, G, H
or F, G, D, H
as your inputs).
Note that if you do use a POSIX-compatible sh
to implement your hook, there's an annoyance with git rev-list ... | while read ...
: the while
loop runs in a subshell which means any variables set here are lost when the loop exits. You can work around this several ways; see While-loop subshell dilemma in Bash.
Upvotes: 2