From 9683d9d03bd07ca4fdf46549fc662c06870c4227 Mon Sep 17 00:00:00 2001 From: Matthew Cline Date: Sun, 1 Nov 2009 20:24:41 -0800 Subject: Information on our git-hooks Local copies of the git hooks, plus intructions on how to change them. --- crawl-ref/git-hooks/README.txt | 8 + crawl-ref/git-hooks/crawl-ref-cia | 289 +++++++++++++++ crawl-ref/git-hooks/crawl-ref-email | 677 ++++++++++++++++++++++++++++++++++++ crawl-ref/git-hooks/git_buildbot.py | 311 +++++++++++++++++ crawl-ref/git-hooks/post-receive | 13 + crawl-ref/git-hooks/update | 19 + 6 files changed, 1317 insertions(+) create mode 100644 crawl-ref/git-hooks/README.txt create mode 100755 crawl-ref/git-hooks/crawl-ref-cia create mode 100755 crawl-ref/git-hooks/crawl-ref-email create mode 100755 crawl-ref/git-hooks/git_buildbot.py create mode 100755 crawl-ref/git-hooks/post-receive create mode 100755 crawl-ref/git-hooks/update (limited to 'crawl-ref/git-hooks') diff --git a/crawl-ref/git-hooks/README.txt b/crawl-ref/git-hooks/README.txt new file mode 100644 index 0000000000..a189aae7f0 --- /dev/null +++ b/crawl-ref/git-hooks/README.txt @@ -0,0 +1,8 @@ +This directory contains copies of the git hook scripts. The actual hooks can +be edited by getting an SHH shell on the sourceforge machine: + + ssh -t USER,crawl-ref@shell.sourceforge.net create + +See http://sourceforge.net/apps/trac/sourceforge/wiki/Shell%20service + +Once there, cd to "/home/scm_git/c/cr/crawl-ref/crawl-ref/hooks" diff --git a/crawl-ref/git-hooks/crawl-ref-cia b/crawl-ref/git-hooks/crawl-ref-cia new file mode 100755 index 0000000000..fd542653f4 --- /dev/null +++ b/crawl-ref/git-hooks/crawl-ref-cia @@ -0,0 +1,289 @@ +#!/usr/bin/perl -w +# +# ciabot -- Mail a git log message to a given address, for the purposes of CIA +# +# Loosely based on cvslog by Russ Allbery +# Copyright 1998 Board of Trustees, Leland Stanford Jr. University +# +# Copyright 2001, 2003, 2004, 2005 Petr Baudis +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License version 2, as published by the +# Free Software Foundation. +# +# The master location of this file is in the Cogito repository +# (see http://www.kernel.org/git/). +# +# This program is designed to run as the .git/hooks/post-commit hook. It takes +# the commit information, massages it and mails it to the address given below. +# +# The calling convention of the post-commit hook is: +# +# .git/hooks/post-commit $commit_sha1 $branch_name +# +# If it does not work, try to disable $xml_rpc in the configuration section +# below. Also, remember to make the hook file executable. +# +# +# Note that you can (and it might be actually more desirable) also use this +# script as the GIT update hook: +# +# refname=${1#refs/heads/} +# [ "$refname" = "master" ] && refname= +# oldhead=$2 +# newhead=$3 +# for merged in $(git-rev-list $newhead ^$oldhead | tac); do +# /path/to/ciabot.pl $merged $refname +# done +# +# This is useful when you use a remote repository that you only push to. The +# update hook will be triggered each time you push into that repository, and +# the pushed commits will be reported through CIA. + +use strict; +use vars qw ($project $from_email $dest_email $noisy $rpc_uri $sendmail + $xml_rpc $ignore_regexp $alt_local_message_target); + + + + +### Configuration + +# Project name (as known to CIA). +$project = 'crawl-ref'; + +# The from address in generated mails. +$from_email = 'dshaligram@users.sourceforge.net'; + +# Mail all reports to this address. +$dest_email = 'cia@cia.vc'; + +# If using XML-RPC, connect to this URI. +$rpc_uri = 'http://cia.vc/RPC2'; + +# Path to your USCD sendmail compatible binary (your mailer daemon created this +# program somewhere). +$sendmail = '/usr/sbin/sendmail'; + +# If set, the script will send CIA the full commit message. If unset, only the +# first line of the commit message will be sent. +$noisy = 0; + +# This script can communicate with CIA either by mail or by an XML-RPC +# interface. The XML-RPC interface is faster and more efficient, however you +# need to have RPC::XML perl module installed, and some large CVS hosting sites +# (like Savannah or Sourceforge) might not allow outgoing HTTP connections +# while they allow outgoing mail. Also, this script will hang and eventually +# not deliver the event at all if CIA server happens to be down, which is +# unfortunately not an uncommon condition. +$xml_rpc = 0; + +# This variable should contain a regexp, against which each file will be +# checked, and if the regexp is matched, the file is ignored. This can be +# useful if you do not want auto-updated files, such as e.g. ChangeLog, to +# appear via CIA. +# +# The following example will make the script ignore all changes in two specific +# files in two different modules, and everything concerning module 'admin': +# +# $ignore_regexp = "^(gentoo/Manifest|elinks/src/bfu/inphist.c|admin/)"; +$ignore_regexp = ""; + +# It can be useful to also grab the generated XML message by some other +# programs and e.g. autogenerate some content based on it. Here you can specify +# a file to which it will be appended. +$alt_local_message_target = ""; + + + + +### The code itself + +use vars qw ($commit $tree @parent $author $committer); +use vars qw ($user $branch $rev @files $logmsg $message); +my $line; + + + +### Input data loading + + +# The commit stuff +$commit = $ARGV[0]; +$branch = $ARGV[1]; + +open COMMIT, "git-cat-file commit $commit|" or die "git-cat-file commit $commit: $!"; +my $state = 0; +$logmsg = ''; +while (defined ($line = )) { + if ($state == 1) { + $logmsg .= $line; + $noisy or $state++; + next; + } elsif ($state > 1) { + next; + } + + chomp $line; + unless ($line) { + $state = 1; + next; + } + + my ($key, $value) = split(/ /, $line, 2); + if ($key eq 'tree') { + $tree = $value; + } elsif ($key eq 'parent') { + push(@parent, $value); + } elsif ($key eq 'author') { + $author = $value; + } elsif ($key eq 'committer') { + $committer = $value; + } +} +close COMMIT; + + +open DIFF, "git-diff-tree -r $parent[0] $tree|" or die "git-diff-tree $parent[0] $tree: $!"; +while (defined ($line = )) { + chomp $line; + my @f; + (undef, @f) = split(/\t/, $line, 2); + push (@files, @f); +} +close DIFF; + + +# Figure out who is doing the update. +# XXX: Too trivial this way? +($user) = $author =~ /<(.*?)@/; + + +$rev = substr($commit, 0, 12); + + + + +### Remove to-be-ignored files + +@files = grep { $_ !~ m/$ignore_regexp/; } @files + if ($ignore_regexp); +exit unless @files; + + + +### Compose the mail message + + +my ($VERSION) = '1.0'; +my $ts = time; + +$message = < + + CIA Perl client for Git + $VERSION + + + $project +EM +; +$message .= " $branch" if ($branch); +$message .= < + + $ts + + + + $user + $rev + +EM +; + +foreach (@files) { + s/&/&/g; + s//>/g; + $message .= " $_\n"; +} + +$logmsg =~ s/&/&/g; +$logmsg =~ s//>/g; + +$message .= < + +$logmsg + + + + +EM +; + + + +### Write the message to an alt-target + +if ($alt_local_message_target and open (ALT, ">>$alt_local_message_target")) { + print ALT $message; + close ALT; +} + + + +### Send out the XML-RPC message + + +if ($xml_rpc) { + # We gotta be careful from now on. We silence all the warnings because + # RPC::XML code is crappy and works with undefs etc. + $^W = 0; + $RPC::XML::ERROR if (0); # silence perl's compile-time warning + + require RPC::XML; + require RPC::XML::Client; + + my $rpc_client = new RPC::XML::Client $rpc_uri; + my $rpc_request = RPC::XML::request->new('hub.deliver', $message); + my $rpc_response = $rpc_client->send_request($rpc_request); + + unless (ref $rpc_response) { + die "XML-RPC Error: $RPC::XML::ERROR\n"; + } + exit; +} + + + +### Send out the mail + + +# Open our mail program + +open (MAIL, "| $sendmail -t -oi -oem") or die "Cannot execute $sendmail : " . ($?>>8); + + +# The mail header + +print MAIL <> 8) . "\n" unless ($? == 0); + +# vi: set sw=2: + diff --git a/crawl-ref/git-hooks/crawl-ref-email b/crawl-ref/git-hooks/crawl-ref-email new file mode 100755 index 0000000000..31577275a0 --- /dev/null +++ b/crawl-ref/git-hooks/crawl-ref-email @@ -0,0 +1,677 @@ +#!/bin/sh +# +# Copyright (c) 2007 Andy Parkins +# +# An example hook script to mail out commit update information. This hook +# sends emails listing new revisions to the repository introduced by the +# change being reported. The rule is that (for branch updates) each commit +# will appear on one email and one email only. +# +# This hook is stored in the contrib/hooks directory. Your distribution +# will have put this somewhere standard. You should make this script +# executable then link to it in the repository you would like to use it in. +# For example, on debian the hook is stored in +# /usr/share/doc/git-core/contrib/hooks/post-receive-email: +# +# chmod a+x post-receive-email +# cd /path/to/your/repository.git +# ln -sf /usr/share/doc/git-core/contrib/hooks/post-receive-email hooks/post-receive +# +# This hook script assumes it is enabled on the central repository of a +# project, with all users pushing only to it and not between each other. It +# will still work if you don't operate in that style, but it would become +# possible for the email to be from someone other than the person doing the +# push. +# +# Config +# ------ +# hooks.mailinglist +# This is the list that all pushes will go to; leave it blank to not send +# emails for every ref update. +# hooks.announcelist +# This is the list that all pushes of annotated tags will go to. Leave it +# blank to default to the mailinglist field. The announce emails lists +# the short log summary of the changes since the last annotated tag. +# hooks.envelopesender +# If set then the -f option is passed to sendmail to allow the envelope +# sender address to be set +# hooks.emailprefix +# All emails have their subjects prefixed with this prefix, or "[SCM]" +# if emailprefix is unset, to aid filtering +# hooks.showrev +# The shell command used to format each revision in the email, with +# "%s" replaced with the commit id. Defaults to "git rev-list -1 +# --pretty %s", displaying the commit id, author, date and log +# message. To list full patches separated by a blank line, you +# could set this to "git show -C %s; echo". +# To list a gitweb/cgit URL *and* a full patch for each change set, use this: +# "t=%s; printf 'http://.../?id=%%s' \$t; echo;echo; git show -C \$t; echo" +# Be careful if "..." contains things that will be expanded by shell "eval" +# or printf. +# +# Notes +# ----- +# All emails include the headers "X-Git-Refname", "X-Git-Oldrev", +# "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and +# give information for debugging. +# + +# ---------------------------- Functions + +# +# Top level email generation function. This decides what type of update +# this is and calls the appropriate body-generation routine after outputting +# the common header +# +# Note this function doesn't actually generate any email output, that is +# taken care of by the functions it calls: +# - generate_email_header +# - generate_create_XXXX_email +# - generate_update_XXXX_email +# - generate_delete_XXXX_email +# - generate_email_footer +# +generate_email() +{ + # --- Arguments + 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 email generated." + exit 0 + ;; + *) + # Anything else (is there anything else?) + echo >&2 "*** Unknown type of update to $refname ($rev_type)" + echo >&2 "*** - no email generated" + exit 1 + ;; + esac + + # Check if we've got anyone to send to + if [ -z "$recipients" ]; then + case "$refname_type" in + "annotated tag") + config_name="hooks.announcelist" + ;; + *) + config_name="hooks.mailinglist" + ;; + esac + echo >&2 "*** $config_name is not set so no email will be sent" + echo >&2 "*** for $refname update $oldrev->$newrev" + exit 0 + fi + + # Email parameters + # The email subject will contain the best description of the ref + # that we can build from the parameters + describe=$(git describe $rev 2>/dev/null) + if [ -z "$describe" ]; then + describe=$rev + fi + + generate_email_header + + # Call the correct body generation function + fn_name=general + case "$refname_type" in + "tracking branch"|branch) + fn_name=branch + ;; + "annotated tag") + fn_name=atag + ;; + esac + generate_${change_type}_${fn_name}_email + + generate_email_footer +} + +generate_email_header() +{ + # --- Email (all stdout will be the email) + # Generate header + cat <<-EOF + To: $recipients + Subject: ${emailprefix} ($refname_type: $short_refname) ${change_type}d. $describe + X-Git-Refname: $refname + X-Git-Reftype: $refname_type + X-Git-Oldrev: $oldrev + X-Git-Newrev: $newrev + + EOF +} + +generate_email_footer() +{ + SPACE=" " + cat <<-EOF + + --${SPACE} + $projectdesc + EOF +} + +# --------------- Branches + +# +# Called for the creation of a branch +# +generate_create_branch_email() +{ + # This is a new branch and so oldrev is not valid + echo " at $newrev ($newrev_type)" + echo "" + + echo $LOGBEGIN + show_new_revisions + echo $LOGEND +} + +# +# Called for the change of a pre-existing branch +# +generate_update_branch_email() +{ + # 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. + + # List all of the revisions that were removed by this update, in a + # fast forward update, this list will be empty, because rev-list O + # ^N is empty. For a non fast forward, O ^N is the list of removed + # revisions + fast_forward="" + rev="" + for rev in $(git rev-list $newrev..$oldrev) + do + revtype=$(git cat-file -t "$rev") + echo " discards $rev ($revtype)" + done + if [ -z "$rev" ]; then + fast_forward=1 + fi + + # List all the revisions from baserev to newrev in a kind of + # "table-of-contents"; note this list can include revisions that + # have already had notification emails and is present to show the + # full detail of the change from rolling back the old revision to + # the base revision and then forward to the new revision + for rev in $(git rev-list $oldrev..$newrev) + do + revtype=$(git cat-file -t "$rev") + echo " via $rev ($revtype)" + done + + if [ "$fast_forward" ]; then + echo " from $oldrev ($oldrev_type)" + else + # 1. Existing revisions were removed. In this case newrev + # is a subset of oldrev - this is the reverse of a + # fast-forward, a rewind + # 2. New revisions were added on top of an old revision, + # this is a rewind and addition. + + # (1) certainly happened, (2) possibly. When (2) hasn't + # happened, we set a flag to indicate that no log printout + # is required. + + echo "" + + # Find the common ancestor of the old and new revisions and + # compare it with newrev + baserev=$(git merge-base $oldrev $newrev) + rewind_only="" + if [ "$baserev" = "$newrev" ]; then + echo "This update discarded existing revisions and left the branch pointing at" + echo "a previous point in the repository history." + echo "" + echo " * -- * -- N ($newrev)" + echo " \\" + echo " O -- O -- O ($oldrev)" + echo "" + echo "The removed revisions are not necessarilly gone - if another reference" + echo "still refers to them they will stay in the repository." + rewind_only=1 + else + echo "This update added new revisions after undoing existing revisions. That is" + echo "to say, the old revision is not a strict subset of the new revision. This" + echo "situation occurs when you --force push a change and generate a repository" + echo "containing something like this:" + echo "" + echo " * -- * -- B -- O -- O -- O ($oldrev)" + echo " \\" + echo " N -- N -- N ($newrev)" + echo "" + echo "When this happens we assume that you've already had alert emails for all" + echo "of the O revisions, and so we here report only the revisions in the N" + echo "branch from the common base, B." + fi + fi + + echo "" + if [ -z "$rewind_only" ]; then + echo $LOGBEGIN + show_new_revisions + + # XXX: Need a way of detecting whether git rev-list actually + # outputted anything, so that we can issue a "no new + # revisions added by this update" message + + echo $LOGEND + else + echo "No new revisions were added by this update." + fi + + # The diffstat is shown from the old revision to the new revision. + # This is to show the truth of what happened in this change. + # There's no point showing the stat from the base to the new + # revision because the base is effectively a random revision at this + # point - the user will be interested in what this revision changed + # - including the undoing of previous revisions in the case of + # non-fast forward updates. + echo "" + echo "Summary of changes:" + git diff-tree -u --stat --summary --find-copies-harder $oldrev..$newrev +} + +# +# Called for the deletion of a branch +# +generate_delete_branch_email() +{ + echo " was $oldrev" + echo "" + echo $LOGEND + git show -s --pretty=oneline $oldrev + echo $LOGEND +} + +# --------------- Annotated tags + +# +# Called for the creation of an annotated tag +# +generate_create_atag_email() +{ + echo " at $newrev ($newrev_type)" + + generate_atag_email +} + +# +# Called for the update of an annotated tag (this is probably a rare event +# and may not even be allowed) +# +generate_update_atag_email() +{ + echo " to $newrev ($newrev_type)" + echo " from $oldrev (which is now obsolete)" + + generate_atag_email +} + +# +# Called when an annotated tag is created or changed +# +generate_atag_email() +{ + # Use git for-each-ref to pull out the individual fields from the + # tag + eval $(git for-each-ref --shell --format=' + tagobject=%(*objectname) + tagtype=%(*objecttype) + tagger=%(taggername) + tagged=%(taggerdate)' $refname + ) + + echo " tagging $tagobject ($tagtype)" + case "$tagtype" in + commit) + + # If the tagged object is a commit, then we assume this is a + # release, and so we calculate which tag this tag is + # replacing + prevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null) + + if [ -n "$prevtag" ]; then + echo " replaces $prevtag" + fi + ;; + *) + echo " length $(git cat-file -s $tagobject) bytes" + ;; + esac + echo " tagged by $tagger" + echo " on $tagged" + + echo "" + echo $LOGBEGIN + + # Show the content of the tag message; this might contain a change + # log or release notes so is worth displaying. + git cat-file tag $newrev | sed -e '1,/^$/d' + + echo "" + case "$tagtype" in + commit) + # Only commit tags make sense to have rev-list operations + # performed on them + if [ -n "$prevtag" ]; then + # Show changes since the previous release + git rev-list --pretty=short "$prevtag..$newrev" | git shortlog + else + # No previous tag, show all the changes since time + # began + git rev-list --pretty=short $newrev | git shortlog + fi + ;; + *) + # XXX: Is there anything useful we can do for non-commit + # objects? + ;; + esac + + echo $LOGEND +} + +# +# Called for the deletion of an annotated tag +# +generate_delete_atag_email() +{ + echo " was $oldrev" + echo "" + echo $LOGEND + git show -s --pretty=oneline $oldrev + echo $LOGEND +} + +# --------------- General references + +# +# Called when any other type of reference is created (most likely a +# non-annotated tag) +# +generate_create_general_email() +{ + echo " at $newrev ($newrev_type)" + + generate_general_email +} + +# +# Called when any other type of reference is updated (most likely a +# non-annotated tag) +# +generate_update_general_email() +{ + echo " to $newrev ($newrev_type)" + echo " from $oldrev" + + generate_general_email +} + +# +# Called for creation or update of any other type of reference +# +generate_general_email() +{ + # Unannotated tags are more about marking a point than releasing a + # version; therefore we don't do the shortlog summary that we do for + # annotated tags above - we simply show that the point has been + # marked, and print the log message for the marked point for + # reference purposes + # + # Note this section also catches any other reference type (although + # there aren't any) and deals with them in the same way. + + echo "" + if [ "$newrev_type" = "commit" ]; then + echo $LOGBEGIN + git show --no-color --root -s --pretty=medium $newrev + echo $LOGEND + else + # What can we do here? The tag marks an object that is not + # a commit, so there is no log for us to display. It's + # probably not wise to output git cat-file as it could be a + # binary blob. We'll just say how big it is + echo "$newrev is a $newrev_type, and is $(git cat-file -s $newrev) bytes long." + fi +} + +# +# Called for the deletion of any other type of reference +# +generate_delete_general_email() +{ + echo " was $oldrev" + echo "" + echo $LOGEND + git show -s --pretty=oneline $oldrev + echo $LOGEND +} + + +# --------------- Miscellaneous utilities + +# +# Show new revisions as the user would like to see them in the email. +# +show_new_revisions() +{ + # This shows all log entries that are not already covered by + # another ref - i.e. commits that are now accessible from this + # ref that were previously not accessible + # (see generate_update_branch_email for the explanation of this + # command) + + # Revision range passed to rev-list differs for new vs. updated + # branches. + if [ "$change_type" = create ] + then + # Show all revisions exclusive to this (new) branch. + revspec=$newrev + else + # Branch update; show revisions not part of $oldrev. + revspec=$oldrev..$newrev + fi + + other_branches=$(git for-each-ref --format='%(refname)' refs/heads/ | + grep -F -v $refname) + git rev-parse --not $other_branches | + if [ -z "$custom_showrev" ] + then + git rev-list --pretty --stdin $revspec + else + git rev-list --stdin $revspec | + while read onerev + do + eval $(printf "$custom_showrev" $onerev) + done + fi +} + + +send_mail() +{ + if [ -n "$envelopesender" ]; then + /usr/sbin/sendmail -t -f "$envelopesender" + else + /usr/sbin/sendmail -t + fi +} + +# ---------------------------- main() + +# --- Constants +LOGBEGIN="-----------------------------------------------------------------------" +LOGEND="-----------------------------------------------------------------------" + +# --- Config +# Set GIT_DIR either from the working directory, or from the environment +# variable. +GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) +if [ -z "$GIT_DIR" ]; then + echo >&2 "fatal: post-receive: GIT_DIR not set" + exit 1 +fi + +projectdesc=$(sed -ne '1p' "$GIT_DIR/description") +# Check if the description is unchanged from it's default, and shorten it to +# a more manageable length if it is +if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null +then + projectdesc="UNNAMED PROJECT" +fi + +recipients=$(git config hooks.mailinglist) +announcerecipients=$(git config hooks.announcelist) +envelopesender=$(git config hooks.envelopesender) +emailprefix=$(git config hooks.emailprefix || echo '[SCM] ') +custom_showrev=$(git config hooks.showrev) + +# --- Main loop +# Allow dual mode: run from the command line just like the update hook, or +# if no arguments are given then run as a hook script +if [ -n "$1" -a -n "$2" -a -n "$3" ]; then + # Output to the terminal in command line mode - if someone wanted to + # resend an email; they could redirect the output to sendmail + # themselves + PAGER= generate_email $2 $3 $1 +else + while read oldrev newrev refname + do + generate_email $oldrev $newrev $refname | send_mail + done +fi diff --git a/crawl-ref/git-hooks/git_buildbot.py b/crawl-ref/git-hooks/git_buildbot.py new file mode 100755 index 0000000000..50d07f45d7 --- /dev/null +++ b/crawl-ref/git-hooks/git_buildbot.py @@ -0,0 +1,311 @@ +#! /usr/bin/env python + +# This script expects one line for each new revision on the form +# +# +# For example: +# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 +# 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master +# +# Each of these changes will be passed to the buildbot server along +# with any other change information we manage to extract from the +# repository. +# +# This script is meant to be run from hooks/post-receive in the git +# repository. It can also be run at client side with hooks/post-merge +# after using this wrapper: + +#!/bin/sh +# PRE=$(git rev-parse 'HEAD@{1}') +# POST=$(git rev-parse HEAD) +# SYMNAME=$(git rev-parse --symbolic-full-name HEAD) +# echo "$PRE $POST $SYMNAME" | git_buildbot.py +# +# Largely based on contrib/hooks/post-receive-email from git. + +import commands +import logging +import os +import re +import sys + +from twisted.spread import pb +from twisted.cred import credentials +from twisted.internet import reactor + +from buildbot.scripts import runner +from optparse import OptionParser + +# Modify this to fit your setup, or pass in --master server:host on the +# command line + +master = "xerxes.uplinklabs.net:9989" + +# When sending the notification, send this category iff +# it's set (via --category) + +category = None + + +# The GIT_DIR environment variable must have been set up so that any +# git commands that are executed will operate on the repository we're +# installed in. + +changes = [] + + +def connectFailed(error): + logging.error("Could not connect to %s: %s" + % (master, error.getErrorMessage())) + return error + + +def addChange(dummy, remote, changei): + logging.debug("addChange %s, %s" % (repr(remote), repr(changei))) + try: + c = changei.next() + except StopIteration: + remote.broker.transport.loseConnection() + return None + + logging.info("New revision: %s" % c['revision'][:8]) + for key, value in c.iteritems(): + logging.debug(" %s: %s" % (key, value)) + + d = remote.callRemote('addChange', c) + d.addCallback(addChange, remote, changei) + return d + + +def connected(remote): + return addChange(None, remote, changes.__iter__()) + + +def grab_commit_info(c, rev): + # Extract information about committer and files using git show + f = os.popen("git show --raw --pretty=full %s" % rev, 'r') + + files = [] + + while True: + line = f.readline() + if not line: + break + + m = re.match(r"^:.*[MAD]\s+(.+)$", line) + if m: + logging.debug("Got file: %s" % m.group(1)) + files.append(m.group(1)) + continue + + m = re.match(r"^Author:\s+(.+)$", line) + if m: + logging.debug("Got author: %s" % m.group(1)) + c['who'] = m.group(1) + + if re.match(r"^Merge: .*$", line): + files.append('merge') + + c['files'] = files + status = f.close() + if status: + logging.warning("git show exited with status %d" % status) + + +def gen_changes(input, branch): + while True: + line = input.readline() + if not line: + break + + logging.debug("Change: %s" % line) + + m = re.match(r"^([0-9a-f]+) (.*)$", line.strip()) + c = {'revision': m.group(1), + 'comments': m.group(2), + 'branch': branch, + } + if category: + c['category'] = category + grab_commit_info(c, m.group(1)) + changes.append(c) + + +def gen_create_branch_changes(newrev, refname, branch): + # A new branch has been created. Generate changes for everything + # up to `newrev' which does not exist in any branch but `refname'. + # + # Note that this may be inaccurate if two new branches are created + # at the same time, pointing to the same commit, or if there are + # commits that only exists in a common subset of the new branches. + + logging.info("Branch `%s' created" % branch) + + f = os.popen("git rev-parse --not --branches" + + "| grep -v $(git rev-parse %s)" % refname + + "| git rev-list --reverse --pretty=oneline --stdin %s" % newrev, + 'r') + + gen_changes(f, branch) + + status = f.close() + if status: + logging.warning("git rev-list exited with status %d" % status) + + +def gen_update_branch_changes(oldrev, newrev, refname, branch): + # A branch has been updated. If it was a fast-forward update, + # generate Change events for everything between oldrev and newrev. + # + # In case of a forced update, first generate a "fake" Change event + # rewinding the branch to the common ancestor of oldrev and + # newrev. Then, generate Change events for each commit between the + # common ancestor and newrev. + + logging.info("Branch `%s' updated %s .. %s" + % (branch, oldrev[:8], newrev[:8])) + + baserev = commands.getoutput("git merge-base %s %s" % (oldrev, newrev)) + logging.debug("oldrev=%s newrev=%s baserev=%s" % (oldrev, newrev, baserev)) + if baserev != oldrev: + c = {'revision': baserev, + 'comments': "Rewind branch", + 'branch': branch, + 'who': "dummy", + } + logging.info("Branch %s was rewound to %s" % (branch, baserev[:8])) + files = [] + f = os.popen("git diff --raw %s..%s" % (oldrev, baserev), 'r') + while True: + line = f.readline() + if not line: + break + + file = re.match(r"^:.*[MAD]\s*(.+)$", line).group(1) + logging.debug(" Rewound file: %s" % file) + files.append(file) + + status = f.close() + if status: + logging.warning("git diff exited with status %d" % status) + + if category: + c['category'] = category + + if files: + c['files'] = files + changes.append(c) + + if newrev != baserev: + # Not a pure rewind + f = os.popen("git rev-list --reverse --pretty=oneline %s..%s" + % (baserev, newrev), 'r') + gen_changes(f, branch) + + status = f.close() + if status: + logging.warning("git rev-list exited with status %d" % status) + + +def cleanup(res): + reactor.stop() + + +def process_changes(): + # Read branch updates from stdin and generate Change events + while True: + line = sys.stdin.readline() + if not line: + break + + [oldrev, newrev, refname] = line.split(None, 2) + + # We only care about regular heads, i.e. branches + m = re.match(r"^refs\/heads\/(.+)$", refname) + if not m: + logging.info("Ignoring refname `%s': Not a branch" % refname) + continue + + branch = m.group(1) + + # Find out if the branch was created, deleted or updated. Branches + # being deleted aren't really interesting. + if re.match(r"^0*$", newrev): + logging.info("Branch `%s' deleted, ignoring" % branch) + continue + elif re.match(r"^0*$", oldrev): + gen_create_branch_changes(newrev, refname, branch) + else: + gen_update_branch_changes(oldrev, newrev, refname, branch) + + # Submit the changes, if any + if not changes: + logging.warning("No changes found") + return + + host, port = master.split(':') + port = int(port) + + f = pb.PBClientFactory() + d = f.login(credentials.UsernamePassword("change", "changepw")) + reactor.connectTCP(host, port, f) + + d.addErrback(connectFailed) + d.addCallback(connected) + d.addBoth(cleanup) + + reactor.run() + + +def parse_options(): + parser = OptionParser() + parser.add_option("-l", "--logfile", action="store", type="string", + help="Log to the specified file") + parser.add_option("-v", "--verbose", action="count", + help="Be more verbose. Ignored if -l is not specified.") + master_help = ("Build master to push to. Default is %(master)s" % + { 'master' : master }) + parser.add_option("-m", "--master", action="store", type="string", + help=master_help) + parser.add_option("-c", "--category", action="store", + type="string", help="Scheduler category to notify.") + options, args = parser.parse_args() + return options + + +# Log errors and critical messages to stderr. Optionally log +# information to a file as well (we'll set that up later.) +stderr = logging.StreamHandler(sys.stderr) +fmt = logging.Formatter("git_buildbot: %(levelname)s: %(message)s") +stderr.setLevel(logging.ERROR) +stderr.setFormatter(fmt) +logging.getLogger().addHandler(stderr) +logging.getLogger().setLevel(logging.DEBUG) + +try: + options = parse_options() + level = logging.WARNING + if options.verbose: + level -= 10 * options.verbose + if level < 0: + level = 0 + + if options.logfile: + logfile = logging.FileHandler(options.logfile) + logfile.setLevel(level) + fmt = logging.Formatter("%(asctime)s %(levelname)s: %(message)s") + logfile.setFormatter(fmt) + logging.getLogger().addHandler(logfile) + + if options.master: + master=options.master + + if options.category: + category = options.category + + process_changes() +except SystemExit: + pass +except: + logging.exception("Unhandled exception") + sys.exit(1) diff --git a/crawl-ref/git-hooks/post-receive b/crawl-ref/git-hooks/post-receive new file mode 100755 index 0000000000..5125e73991 --- /dev/null +++ b/crawl-ref/git-hooks/post-receive @@ -0,0 +1,13 @@ +#!/bin/bash + +GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) +if [ -z "$GIT_DIR" ]; then + echo >&2 "fatal: post-receive: GIT_DIR not set" + exit 1 +fi + +$GIT_DIR/hooks/crawl-ref-email "$@" +#$GIT_DIR/hooks/crawl-ref-cia "$@" + +# BuildBot +#$GIT_DIR/hooks/git_buildbot.py "$@" diff --git a/crawl-ref/git-hooks/update b/crawl-ref/git-hooks/update new file mode 100755 index 0000000000..a38deb2499 --- /dev/null +++ b/crawl-ref/git-hooks/update @@ -0,0 +1,19 @@ +#!/bin/bash + +# Note that you can (and it might be actually more desirable) also use this +# script as the GIT update hook: +# + +GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) +if [ -z "$GIT_DIR" ]; then + echo >&2 "fatal: post-receive: GIT_DIR not set" + exit 1 +fi + +refname=${1#refs/heads/} +[ "$refname" = "master" ] && refname= +oldhead=$2 +newhead=$3 +for merged in $(git-rev-list $newhead ^$oldhead | tac); do + $GIT_DIR/hooks/crawl-ref-cia $merged $refname +done -- cgit v1.2.3-54-g00ecf