adapton 0.3.18

programming abstractions for general-purpose incremental computations
Documentation
#!/bin/bash
#
# Slack (slack.com) notification post-receive hook.
#
# Based on: https://github.com/joemiller/git-hooks Campfire notification post-receive hook. Author: Joe Miller
# (http://joemiller.me)
#
# Based on post-receive.irc by Mikael Fridh <frimik@gmail.com> https://gist.github.com/1821358
#
# Settings needed:
#  git config hooks.slack.webhook-url "https://hooks.slack.com/services/..."
#  git config hooks.slack.channel "general"
#
# - The Slack webhook URL can be found in:
#   https://my.slack.com/services/new/incoming-webhook
#
function help() {
  echo "Required config settings:"
  echo " git config hooks.slack.webhook-url 'https://hooks.slack.com/services/...'"
  echo " git config hooks.slack.channel 'general'"
  echo " git config hooks.slack.show-only-last-commit true #optional"
  echo " git config hooks.slack.show-full-commit true #optional"
  echo " git config hooks.slack.username 'git' #optional"
  echo " git config hooks.slack.icon-url 'http://imgur/icon.png' #optional"
  echo " git config hooks.slack.icon-emoji ':twisted_rightwards_arrows:' #optional"
  echo " git config hooks.slack.repo-nice-name 'MyRepo' #optional"
  echo " git config hooks.slack.repos-root '/path/to/repos' #optional"
  echo " git config hooks.slack.changeset-url-pattern 'http://yourserver/%repo_path%/changeset/%rev_hash%' #optional"
  echo " git config hooks.slack.compare-url-pattern 'http://yourserver/%repo_path%/changeset/%old_rev_hash%..%new_rev_hash%' #optional"
  echo " git config hooks.slack.branch-regexp 'regexp'  #optional"
}

function replace_variables() {
	sed "s|%repo_path%|$repopath|g;s|%old_rev_hash%|$oldrev|g;s|%new_rev_hash%|$newrev|g;s|%rev_hash%|$newrev|g;s|%repo_prefix%|$repoprefix|g"
}

function notify() {
  oldrev=$(git rev-parse $1)
  newrev=$(git rev-parse $2)
  refname="$3"

  # --- Interpret
  # 0000->1234 (create)
  # 1234->2345 (update)
  # 2345->0000 (delete)
  if expr "$oldrev" : '0*$' >/dev/null
  then
    change_type="create"
  else
    if expr "$newrev" : '0*$' >/dev/null
    then
      change_type="delete"
    else
      change_type="update"
    fi
  fi

  # --- Get the revision types
  newrev_type=$(git cat-file -t $newrev 2> /dev/null)
  oldrev_type=$(git cat-file -t "$oldrev" 2> /dev/null)
  case "$change_type" in
    create|update)
      rev="$newrev"
      rev_type="$newrev_type"
      ;;
    delete)
      rev="$oldrev"
      rev_type="$oldrev_type"
      ;;
  esac

  # The revision type tells us what type the commit is, combined with
  # the location of the ref we can decide between
  #  - working branch
  #  - tracking branch
  #  - unannoted tag
  #  - annotated tag
  case "$refname","$rev_type" in
    refs/tags/*,commit)
      # un-annotated tag
      refname_type="tag"
      short_refname=${refname##refs/tags/}
      ;;
    refs/tags/*,tag)
      # annotated tag
      refname_type="annotated tag"
      short_refname=${refname##refs/tags/}
      # change recipients
      if [ -n "$announcerecipients" ]; then
        recipients="$announcerecipients"
      fi
      ;;
    refs/heads/*,commit)
      # branch
      refname_type="branch"
      short_refname=${refname##refs/heads/}
      ;;
    refs/remotes/*,commit)
      # tracking branch
      refname_type="tracking branch"
      short_refname=${refname##refs/remotes/}
      echo >&2 "*** Push-update of tracking branch, $refname"
      echo >&2 "***  - no notification generated."
      return 0
      ;;
    *)
      # Anything else (is there anything else?)
      echo >&2 "*** Unknown type of update to $refname ($rev_type)"
      echo >&2 "***  - no notification generated"
      return 0
      ;;
  esac

  branchregexp=$(git config --get hooks.slack.branch-regexp)
  if [ -n "$branchregexp" ]; then
    if [[ ! $short_refname =~ $branchregexp ]]; then
      exit 0;
    fi
  fi

  #channels=$(git config hooks.irc.channel)

  # plural suffix, default "", changed to "s" if commits > 1
  s=""

  # Repo name, either Gitolite or normal repo.
  if [ -n "$GL_REPO" ]; then
    # it's a gitolite repo
    repodir=$(basename $(pwd))
    repo=$GL_REPO
  else
    repodir=$(basename $(pwd))
    if [ "$repodir" == ".git" ]; then
      repodir=$(dirname $PWD)
      repodir=$(basename $repodir)
    fi
    repo=${repodir%.git}
  fi

  repoprefix=$(git config hooks.slack.repo-nice-name || git config hooks.irc.prefix || git config hooks.emailprefix || echo "$repo")
  onlylast=$(git config --get hooks.slack.show-only-last-commit)
  onlylast=$onlylast && [ -n "$onlylast" ]
  fullcommit=$(git config --get hooks.slack.show-full-commit)

  # Get the user information
  # If $GL_USER is set we're running under gitolite.
  if [ -n "$GL_USER" ]; then
    user=$GL_USER
  else
    user=$USER
  fi

  case ${change_type} in
    "create")
      header="New ${refname_type} *${short_refname}* has been created in ${repoprefix}"
      single_commit_suffix="commit"
      ;;
    "delete")
      header="$(tr '[:lower:]' '[:upper:]' <<< ${refname_type:0:1})${refname_type:1} *$short_refname* has been deleted from ${repoprefix}"
      single_commit_suffix="commit"
      ;;
    "update")
      num=$(git log --pretty=oneline ${1}..${2}|wc -l|tr -d ' ')
      branch=${3/refs\/heads\//}

      if [ ${num} -gt 1 ]; then
        header="${num} new commits *pushed* to *${short_refname}* in ${repoprefix}"
        single_commit_suffix="one"
        s="s"
      else
        header="A new commit has been *pushed* to *${short_refname}* in ${repoprefix}"
        single_commit_suffix="one"
      fi

      ;;
    *)
      # most weird ... this should never happen
      echo >&2 "*** Unknown type of update to $refname ($rev_type)"
      echo >&2 "***  - notifications will probably screw up."
      ;;
  esac

  if $onlylast && [[ "${change_type}" != "delete" ]]; then
    header="$header, showing last $single_commit_suffix:"
  fi


  if [[ "${change_type}" != "delete" && "${refname_type}" == "branch" ]]; then
    changeseturlpattern=$(git config --get hooks.slack.changeset-url-pattern)
    compareurlpattern=$(git config --get hooks.slack.compare-url-pattern)
    reporoot=$(git config --get hooks.slack.repos-root)

    urlformat=
    if [ -n "$changeseturlpattern" -a -n "$reporoot" ]; then
      if [[ $PWD == ${reporoot}* ]]; then
        repopath=$PWD
        base=$(basename $PWD)
        if [ "$base" == ".git" ]; then
          repopath=$(dirname $repopath)
        fi
        idx=$(echo $reporoot | wc -c | tr -d ' ')
        repopath=$(echo $repopath | cut -c$idx-)
        urlformat=$(echo $changeseturlpattern | replace_variables)

        if [ -n "$compareurlpattern" ]; then
          comparelink=$(echo $compareurlpattern | replace_variables)
          header=$(echo $header | sed -e "s|\([a-zA-Z0-9]\{1,\} new commit[s]\{0,1\}\)|\<$comparelink\|\\1\>|")
        fi
      else
        echo >&2 "$PWD is not in $reporoot. Not creating hyperlinks."
      fi
    fi

    formattedurl=""
    if [ -n "$urlformat" ]; then
      formattedurl="<${urlformat}|%h> "
    fi


    nl="\\\\n"

    if [[ "${change_type}" == "update" ]]; then
      start="${1}"
    else
      start="HEAD"
    fi

    end="${2}"


    # merge `git log` output with $header
    if $onlylast; then
      countarg="-n 1"
    else
      countarg=""
    fi

    # show the full commit message
    if [ "$fullcommit" == "true" ]; then
      commitformat="%s%n%n%b"
    else
      commitformat="%s"
    fi

    # Process the log and escape double quotes and backslashes; assuming commit names/messages don't have five of the following: & ; @
    log_out=$( git log --pretty=format:"&&&&&%cN;;;;;${formattedurl}${commitformat}@@@@@" $countarg ${start}..${end} \
        | perl -p -e 's/@@@@@\n+/@@@@@/mg' \
        | sed -e 's/\\/\\\\/g' \
        | sed -e 's/"/\\"/g' \
        | sed -e 's/&&&&&\(.*\);;;;;/{ \"fallback\" : \"\", \"color\" : \"good\", \"fields\" : [{"title":"\1","value":"/g' \
        | sed -e 's/@@@@@/","short":false},]},/g' \
        | sed -e 's/,\]/]/' )

    attachments="[${log_out%?}]"

  fi

  if [ -n "${attachments}" ] && [[ "${attachments}" != "" ]]; then
    msg=$(echo -e "\"text\":\"${header}\", \"attachments\" : $attachments")
  else
    msg=$(echo -e "\"text\":\"${header}\"")
  fi

  # slack API uses \n substitution for newlines
  msg=$(echo -n "${msg}" | perl -p -e 's/\n/\\n/mg')

  webhook_url=$(git config --get hooks.slack.webhook-url)
  channel=$(git config --get hooks.slack.channel)
  username=$(git config --get hooks.slack.username)
  iconurl=$(git config --get hooks.slack.icon-url)
  iconemoji=$(git config --get hooks.slack.icon-emoji)

  if [ -z "$webhook_url" ]; then
    echo "ERROR: config settings not found"
    help
    exit 1
  fi

  payload="{${msg}"

  if [ -n "$channel" ]; then
    payload="$payload, \"channel\": \"$channel\""
  fi

  if [ -n "$username" ]; then
    payload="$payload, \"username\": \"$username\""
  fi

  if [ -n "$iconurl" ]; then
    payload="$payload, \"icon_url\": \"$iconurl\""
  elif [ -n "$iconemoji" ]; then
    payload="$payload, \"icon_emoji\": \"$iconemoji\""
  fi

  payload="$payload}"

  if [ -n "$DEBUG" ]; then
    echo "POST $webhook_url"
    echo "payload=$payload"
    return
  fi

  curl -s \
      -d "payload=$payload" \
      "$webhook_url" \
      >/dev/null

}

# MAIN PROGRAM
# Read all refs from stdin, notify slack for each
while read line; do
  set -- $line
  notify $*
  RET=$?
done

exit $RET