Add display_error_message to reduce duplication.
[git-central.git] / server / functions
index 533bbd5..5040ef7 100644 (file)
@@ -1,13 +1,90 @@
 #!/bin/sh
 
 # Sets: new_commits
-# Assumes: $oldrev, $newrev, and $refname
+# 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'
 
@@ -30,3 +107,115 @@ function set_new_commits() {
        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
+}
+