{"thread":{"id":"10233","subject":"Suggestions for improvements to post-receive hook","startedAt":"2007-10-10T14:32:14Z","lastAt":"2007-10-10T14:32:14Z","messageCount":1,"participants":["Bill Lear"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"55371","messageId":"18188.57966.215393.192741@lisa.zopyra.com","threadId":"10233","inReplyTo":null,"subject":"Suggestions for improvements to post-receive hook","fromName":"Bill Lear","fromEmail":"rael@zopyra.com","sentAt":"2007-10-10T14:32:14Z","receivedAt":"2007-10-10T14:32:14Z","isPatch":false,"sender":{"key":"rael@zopyra.com","avatar":"https://gravatar.com/avatar/c4f2d2790ca3828d3b4e7dfebabf61d2fe94fd82fa49cdac2a5295dd2d46a874?d=mp&s=160"},"body":"We have made a few changes to the post-receive hook for operation in\nour company.  I wanted to pass them on in case they are suitable for\nmore general use.  Below I mix description of the change, a\njustification of the change, along with a snippet of code and/or code\ncomments illustrating the change.  I'm sure there are more\nsophisticated ways to do what we've done, but it seems to work for us.\n\n1. Support per-branch mailing lists.  This allows me to collaborate\n   with several different people on different mini-projects from the\n   same \"hub\" repository without each of them getting mail they don't\n   care about.\n\n   Code:\n\n# hooks.{branch}mailinglist\n#   This is the list that all pushes to a given branch will go to;\n#   leave it blank to not send emails for every ref update to this branch.\n[...]\n        if [ -z \"$recipients\" ]; then\n                # Essentially an associative array,\n                # hooks.${short_refname}mailinglist: allows a separate mailing\n                # list for each branch/tag thingie.\n                recipients=$(git-repo-config hooks.${short_refname}mailinglist)\n        fi\n\n        # One last try ...\n        if [ -z \"$recipients\" ]; then\n                recipients=$(git repo-config hooks.mailinglist)\n        fi\n\n        # Check if we've got anyone to send to\n        if [ -z \"$recipients\" ]; then\n                echo >&2 \"*** hooks.recipients is not set so no email will be sent\"\n                echo >&2 \"*** for $refname update $oldrev->$newrev\"\n                exit 0\n        fi\n\n\n2. Allow specification of a prefix for the subject line.  This allows\n   us to better filter our email --- we use a mailing list for some\n   projects and the mailing list prefixes the subject with its own\n   string, for example.\n\n   Code:\n\n# hooks.prefix\n#   Set the email subject prefix to \"[$hooks.prefix]\".  Defaults to empty string.\n[...]\n# Notes\n# -----\n# All emails have their subjects prefixed with \"[$hooks.prefix]\"\n# to aid filtering, if hooks.prefix is set.  If it is not set, no\n# prefix is used.\n[...]\nprefix=$(git-repo-config hooks.prefix)\n\nif [ -n \"$prefix\" ]; then\n        EMAILPREFIX=\"[${prefix}] \"\nelse\n        EMAILPREFIX=\"\"\nfi\n[... elsewhere in the code]\ngenerate_email_header()\n{\n        # --- Email (all stdout will be the email)\n        # Generate header\n        cat <<-EOF\n        From: $committer\n        To: $recipients\n        Subject: ${EMAILPREFIX}$projectdesc $refname_type \"$short_refname\" ${change_type}d. $describe\n        X-Git-Refname: $refname\n        X-Git-Reftype: $refname_type\n[...]\n\n3.  Provide change content in email.  The current hook does not produce\n    enough information for us --- we like to see the content of the change\n    so we can code-review on-the-fly.  I hacked the code, in my ignorance,\n    to use git-whatchanged, as it seemed to provide what we wanted.  I\n    am not currently controlling this behavior with a config flag, though\n    presumably it could be, easily.\n\n    Code (simple diff):\n408,411c399,400\n<       echo \"Diffstat:\"\n<       git-diff-tree --no-color --stat -M -C --find-copies-harder $newrev ^$oldrev\n<       echo \"\"\n<       git-whatchanged --no-color -p --find-copies-harder $oldrev..$newrev\n---\n>       echo \"Summary of changes:\"\n>       git diff-tree --stat --summary --find-copies-harder $oldrev..$newrev\n\n4 and 5.\n\n    4: Log the content of the emails, if desired, to a log file.\n    Our email archiving system here is between lame and nonexistent,\n    so we like to be able to look back at the logs if a problem should\n    occur.\n\n    5: Allow sending only a certain number of lines maximum to email\n    recipients.  Some diffs are huge, so we don't want to overwhelm\n    recipients' inboxes.\n\n    Note: currently, specifying the maximum number of email lines\n    only works when you specify logging, though this is not necessary.\n    Also: this might only be useful if you have a flag that controls\n    the use of git-whatchanged (or whatever) to produce a fuller diff\n    set in the email.\n\n    Code:\n\n# hooks.maxemaillines\n#   Clip the email sent out to this number of lines.  If unset,\n#   defaults to 500 lines.\n[...]\n# hooks.logfile\n#   If set then the entire email content sent will be logged to this file.\n#   If unset, no logging is done.\n[...]\nmaxemaillines=$(git-repo-config --int hooks.maxemaillines)\nlogfile=$(git-repo-config hooks.logfile)\n[...]\n        # If a log file was specified, tee to the logfile, head, and then\n        # sendmail, else just send the mail directly.\n        if [ -n \"$logfile\" ]; then\n                while read oldrev newrev refname\n                do\n                        generate_email $oldrev $newrev $refname |\n                            /usr/bin/tee -a ${logfile} 2>/dev/null |\n                            /usr/bin/head -${maxemaillines} |\n                            /usr/sbin/sendmail -t $envelopesender\n                done\n        else\n                while read oldrev newrev refname\n                do\n                        generate_email $oldrev $newrev $refname |\n                            /usr/sbin/sendmail -t $envelopesender\n                done\n        fi\n\n6.  Support an author filter.  I don't have this running currently,\n    but I believe this feature was in the update script.  Basically\n    (and here follows most likely confusion on my part) it allows you\n    to filter your changes out of a change set so that when you\n    push a change set that includes your changes to a hub, it only\n    sends them once (here, I'm most certainly not expressing this\n    correctly).\n\n    Anyway, someone on the list suggested this, and it was in\n    the update hook, like this:\n\nauthorfilter=$(git-repo-config hooks.authorfilter)\ncase \"$refname_type\" in\n        \"tracking branch\"|branch)\n                if expr \"$oldrev\" : '0*$' >/dev/null\n                then\n                        # If the old reference is \"0000..0000\" then this is a new branch\n                        # and so oldrev is not valid\n                        echo \"  as a new  $refname_type\"\n                        echo \"        to  $newrev ($newrev_type)\"\n                        echo \"\"\n                        # This shows all log entries that are not already covered by\n                        # another ref - i.e. commits that are now accessible from this\n                        # ref that were previously not accessible\n                        git-whatchanged -p ${authorfilter:+--author=\"$authorfilter\"} --pretty $newref $(git-rev-parse --not --all)\n                else\n                        # oldrev is valid\n                        oldrev_type=$(git-cat-file -t \"$oldrev\")\n\n                        # Now the problem is for cases like this:\n                        #   * --- * --- * --- * (oldrev)\n                        #          \\\n                        #           * --- * --- * (newrev)\n                        # i.e. there is no guarantee that newrev is a strict subset\n                        # of oldrev - (would have required a force, but that's allowed).\n                        # So, we can't simply say rev-list $oldrev..$newrev.  Instead\n                        # we find the common base of the two revs and list from there\n                        baserev=$(git-merge-base $oldrev $newrev)\n\n                        # Commit with a parent\n                        for rev in $(git-rev-list ${authorfilter:+--author=\"$authorfilter\"} $newrev ^$baserev)\n                        do\n                                revtype=$(git-cat-file -t \"$rev\")\n                                echo \"       via  $rev ($revtype)\"\n                        done\n                        if [ \"$baserev\" = \"$oldrev\" ]; then\n                                echo \"      from  $oldrev ($oldrev_type)\"\n                        else\n                                echo \"  based on  $baserev\"\n                                echo \"      from  $oldrev ($oldrev_type)\"\n                                echo \"\"\n                                echo \"This ref update crossed a branch point; i.e. the old rev is not a strict subset\"\n                                echo \"of the new rev.  This occurs, when you --force push a change in a situation\"\n                                echo \"like this:\"\n                                echo \"\"\n                                echo \" * -- * -- B -- O -- O -- O ($oldrev)\"\n                                echo \"            \\\\\"\n                                echo \"             N -- N -- N ($newrev)\"\n                                echo \"\"\n                                echo \"Therefore, we assume that you've already had alert emails for all of the O\"\n                                echo \"revisions, and now give you all the revisions in the N branch from the common\"\n                                echo \"base, B ($baserev), up to the new revision.\"\n                        fi\n                        echo \"\"\n                        echo \"Diffstat:\"\n                        git-diff-tree --no-color --stat -M -C --find-copies-harder $newrev ^$baserev\n                        echo \"\"\n                        git-whatchanged -p ${authorfilter:+--author=\"$authorfilter\"} --pretty $newrev ^$baserev\n                fi\n                ;;\n        \"annotated tag\")\n\nOk, I think that's about it.  Andy, let me know if I can help\nclarify anything.\n\n\nBill\n"}]}