#!/bin/sh # Sets: new_commits # Assumes: $oldrev $newrev $refname # # This is for use in post receive hooks, as it assumes the refname has moved and # is now newrev, we need to discard it. This is down with bash string replace, # as it will replace only the first match, compared to the canonical "grep -v" # approach which will throw out multiple matches if the same commit is referred # to by multiple branches. # # Excellent, excellent docs from Andy Parkin's email script # ################################################## # # Consider this: # 1 --- 2 --- O --- X --- 3 --- 4 --- N # # O is $oldrev for $refname # N is $newrev for $refname # X is a revision pointed to by some other ref, for which we may # assume that an email has already been generated. # In this case we want to issue an email containing only revisions # 3, 4, and N. Given (almost) by # # git rev-list N ^O --not --all # # The reason for the "almost", is that the "--not --all" will take # precedence over the "N", and effectively will translate to # # git rev-list N ^O ^X ^N # # So, we need to build up the list more carefully. git rev-parse # will generate a list of revs that may be fed into git rev-list. # We can get it to make the "--not --all" part and then filter out # the "^N" with: # # git rev-parse --not --all | grep -v N # # Then, using the --stdin switch to git rev-list we have effectively # manufactured # # git rev-list N ^O ^X # # This leaves a problem when someone else updates the repository # while this script is running. Their new value of the ref we're # working on would be included in the "--not --all" output; and as # our $newrev would be an ancestor of that commit, it would exclude # all of our commits. What we really want is to exclude the current # value of $refname from the --not list, rather than N itself. So: # # git rev-parse --not --all | grep -v $(git rev-parse $refname) # # Get's us to something pretty safe (apart from the small time # between refname being read, and git rev-parse running - for that, # I give up) # # # Next problem, consider this: # * --- B --- * --- O ($oldrev) # \ # * --- X --- * --- N ($newrev) # # That is to say, there is no guarantee that oldrev is a strict # subset of newrev (it would have required a --force, but that's # allowed). So, we can't simply say rev-list $oldrev..$newrev. # Instead we find the common base of the two revs and list from # there. # # As above, we need to take into account the presence of X; if # another branch is already in the repository and points at some of # the revisions that we are about to output - we don't want them. # The solution is as before: git rev-parse output filtered. # # Finally, tags: 1 --- 2 --- O --- T --- 3 --- 4 --- N # # Tags pushed into the repository generate nice shortlog emails that # summarise the commits between them and the previous tag. However, # those emails don't include the full commit messages that we output # for a branch update. Therefore we still want to output revisions # that have been output on a tag email. # # Luckily, git rev-parse includes just the tool. Instead of using # "--all" we use "--branches"; this has the added benefit that # "remotes/" will be ignored as well. # ################################################## function set_new_commits() { nl=$'\n' # Get all the current branches, not'd as we want only new ones new_commits=$(git rev-parse --not --branches) # Strip off the not current branch new_hash=$(git rev-parse $refname) new_commits=${new_commits/^$new_hash/} # Put back newrev without the not new_commits=${new_commits}${nl}${newrev} # Put in ^oldrev if it's not a new branch if [ "$oldrev" != "0000000000000000000000000000000000000000" ] ; then new_commits=${new_commits}${nl}^${oldrev} fi new_commits=${new_commits/$nl$nl/$nl} new_commits=${new_commits/#$nl/} } # Sets: $change_type # Assumes: $oldrev $newrev # # --- Interpret # 0000->1234 (create) # 1234->2345 (update) # 2345->0000 (delete) function set_change_type() { if [ "$oldrev" == "0000000000000000000000000000000000000000" ] ; then change_type="create" else if [ "$newrev" == "0000000000000000000000000000000000000000" ] ; then change_type="delete" else change_type="update" fi fi } # Sets: $newrev_type $oldrev_type $rev $rev_type # Assumes: $newrev $oldrev # --- Get the revision types function set_rev_types() { newrev_type=$(git cat-file -t "$newrev" 2> /dev/null) oldrev_type=$(git cat-file -t "$oldrev" 2> /dev/null) if [ "$newrev" == "0000000000000000000000000000000000000000" ] ; then rev_type="$oldrev_type" rev="$oldrev" else rev_type="$newrev_type" rev="$newrev" fi } # Sets: $describe # Assumes: $rev # # The email subject will contain the best description of the ref that we can build from the parameters function set_describe() { rev_to_describe="$rev" if [ "$1" != "" ] ; then rev_to_describe="$1" fi describe=$(git describe $rev_to_describe 2>/dev/null) if [ -z "$describe" ]; then describe=$rev_to_describe fi } # Sets: $describe_tags # Assumes: $rev # # The email subject will contain the best description of the ref that we can build from the parameters function set_describe_tags() { rev_to_describe="$rev" if [ "$1" != "" ] ; then rev_to_describe="$1" fi describe_tags=$(git describe --tags $rev_to_describe 2>/dev/null) if [ -z "$describe_tags" ]; then describe_tags=$rev_to_describe fi } # Takes a lockfile path and command to execute once the lock is acquired. # # Retries every second for 5 minutes. # # with_lock "foo.lock" "echo a" # Works # with_lock "foo.lock" "echo b1 ; echo b2" # Work # function several() { # echo several1 ; echo several2 # } # with_lock "foo.lock" several # Works # function with_lock() { lockfile="$1" block="$2" with_lock_has_lock=1 trap with_lock_unlock_if_held INT TERM EXIT lockfile -1 -r 300 "$lockfile" with_lock_has_lock=$? if [ $with_lock_has_lock -ne 0 ] ; then exit $? fi eval "$block" rm -f "$lockfile" with_lock_has_lock=1 } function with_lock_unlock_if_held() { if [[ "$with_lock_has_lock" -eq 0 ]] ; then rm -f "$lockfile" fi } function display_error_message() { echo "----------------------------------------------------" >&2 echo "" >&2 echo "" >&2 for ((i=1;i<=$#;i+=1)); do eval message="$"$i echo "$message" >&2 done echo "" >&2 echo "" >&2 echo "----------------------------------------------------" >&2 }