{"thread":{"id":"7365","subject":"[PATCH] Convert emailing part of hooks--update to hooks--post-receive","startedAt":"2007-03-23T10:23:58Z","lastAt":"2007-03-31T11:36:37Z","messageCount":11,"participants":["Andy Parkins","Junio C Hamano","Linus Torvalds","Jakub Narebski"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"37804","messageId":"200703231023.58911.andyparkins@gmail.com","threadId":"7365","inReplyTo":null,"subject":"[PATCH] Convert emailing part of hooks--update to hooks--post-receive","fromName":"Andy Parkins","fromEmail":"andyparkins@gmail.com","sentAt":"2007-03-23T10:23:58Z","receivedAt":"2007-03-23T10:23:58Z","isPatch":true,"sender":{"key":"andyparkins@gmail.com","avatar":null},"body":"The update hook is no longer the correct place to generate emails; there\nis now the hooks/post-receive script which is run automatically after a\nref has been updated.\n\nThis patch is to make use of that new location, and to address some\nfaults in the old update hook.\n\nThe primary problem in the conversion was that in the update hook, the\nref has not actually been changed, but is about to be.  In the\npost-receive hook the ref has already been updated.  That meant that\nwhere we previously had lines like:\n\n git rev-list --not --all\n\nwould now fail because \"--all\" in the post-receive hook includes the ref\nthat we are making the email for.  This made it more difficult to show\nonly the new revisions added by this update.\n\nThe solution is not pretty; however it does work and doesn't need any\nchanges to git-rev-list itself.  It also fixes (more accurately: reduces\nthe likelihood of) a nasty race when another update occurs while this\nscript is running.  The solution, in short, looks like this (see the\nsource code for a longer explanation)\n\n git rev-parse --not --all | grep -v $(<$refname) |\n git rev-list --pretty --stdin $newrev --not $baserev\n\nThis uses git-rev-parse followed by grep to filter out the revision of\nthe ref in question before it gets to rev-list and inhibits the output\nof itself.  By using $(<$refname) rather than $newrev as the filter, it\nalso takes care of the situation where another update to the same ref\nhas been made since $refname was $newrev.\n\nThe second problem that is addressed is that of tags inhibiting the\ncorrect output of an update email.  Consider this, with somebranch and\nsometag pointing at the same revision:\n\n git push origin somebranch\n git push origin sometag\n\nThat would work fine; the push of the branch would generate an email\ncontaining all the new commits introduced by the update, then the push\nof the tag would generate the shortlog formatted tag email.  Now\nconsider:\n\n git push origin sometag\n git push origin somebranch\n\nWhen some branch comes to run its \"--not --all\" line, it will find\nsometag, and filter those commits from the email - leaving nothing.\nThat meant that those commits would not show (in full) on any email.\nThe fix is to not use \"--all\", and instead use \"--branches\" in the\ngit-rev-parse command.\n\nOther changes\n * Lose the monstrous one-giant-script layout and put things in easy to\n   digest functions.  This makes it much easier to find the place you\n   need to change if you wanted to customise the output.  I've also\n   tried to write more verbose comments for the same reason.  The hook\n   script is big, mainly because of all the different cases that it has\n   to handle, so being easy to navigate is important.\n * All uses of \"git-command\" changed to \"git command\", to cope better\n   if a user decided not to install all the hard links to git;\n * Cleaned up some of the English in the email\n * The fact that the receive hook makes the ref available also allows me\n   to use Shawn Pearce's fantastic suggestion that an annotated tag can\n   be parsed with git-for-each-ref.  This removes the potentially\n   non-portable use of \"<<<\" heredocs and the nasty messing around with\n   \"date\" to convert numbers of seconds UTC to a real date\n * Deletions are now caught and notified (briefly)\n * To help with debugging, I've retained the command line mode from the\n   update hook; but made it so that the output is not emailed, it's just\n   printed to the screen.  This could then be redirected if the user\n   wanted\n * Removed the \"Hello\" from the beginning of the email - it's just\n   noise, and no one seriously has their day made happier by \"friendly\"\n   programs\n * The fact that it doesn't rely on repository state as an indicator any\n   more means that it's far more stable in its output; hopefully the\n   same arguments will always generate the same email - even if the\n   repository changes in the future.  This means you can easily recreate\n   an email should you want to.\n\nSigned-off-by: Andy Parkins <andyparkins@gmail.com>\n---\n\nI'm sorry this is so big and nasty Junio; but that's mostly because the main\nbody of it is a cut and paste from the old update hook.\n\nIt includes a whole set of little fixes as well as a reorganisation though,\nso I don't think the content tracking would have detected it as a rename.\n\n templates/hooks--post-receive |  554 +++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 554 insertions(+), 0 deletions(-)\n create mode 100644 templates/hooks--post-receive\n\ndiff --git a/templates/hooks--post-receive b/templates/hooks--post-receive\nnew file mode 100644\nindex 0000000..1e988ee\n--- /dev/null\n+++ b/templates/hooks--post-receive\n@@ -0,0 +1,554 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2007 Andy Parkins\n+#\n+# An example hook script to mail out commit update information.\n+#\n+# To enable this hook, make this file executable by \"chmod +x update\".\n+#\n+# Config\n+# ------\n+# hooks.mailinglist\n+#   This is the list that all pushes will go to; leave it blank to not send\n+#   emails for every ref update.\n+# hooks.announcelist\n+#   This is the list that all pushes of annotated tags will go to.  Leave it\n+#   blank to default to the mailinglist field.  The announce emails lists the\n+#   short log summary of the changes since the last annotated tag.\n+#\n+# Notes\n+# -----\n+# All emails have their subjects prefixed with \"[SCM]\" to aid filtering.\n+# All emails include the headers \"X-Git-Refname\", \"X-Git-Oldrev\",\n+# \"X-Git-Newrev\", and \"X-Git-Reftype\" to enable fine tuned filtering and\n+# give information for debugging.\n+#\n+\n+# ---------------------------- Functions\n+\n+#\n+# Top level email generation function.  This decides what type of update\n+# this is and calls the appropriate body-generation routine after outputting\n+# the common header\n+#\n+# Note this function doesn't actually generate any email output, that is taken\n+# care of by the functions it calls:\n+#  - generate_email_header\n+#  - generate_create_XXXX_email\n+#  - generate_update_XXXX_email\n+#  - generate_delete_XXXX_email\n+#  - generate_email_footer\n+#\n+generate_email()\n+{\n+\t# --- Arguments\n+\toldrev=$(git rev-parse $1)\n+\tnewrev=$(git rev-parse $2)\n+\trefname=\"$3\"\n+\n+\t# --- Interpret\n+\t# 0000->1234 (create)\n+\t# 1234->2345 (update)\n+\t# 2345->0000 (delete)\n+\tif expr \"$oldrev\" : '0*$' >/dev/null\n+\tthen\n+\t\tchange_type=\"create\"\n+\telse\n+\t\tif expr \"$newrev\" : '0*$' >/dev/null\n+\t\tthen\n+\t\t\tchange_type=\"delete\"\n+\t\telse\n+\t\t\tchange_type=\"update\"\n+\t\tfi\n+\tfi\n+\n+\t# --- Get the revision types\n+\tnewrev_type=$(git cat-file -t $newrev 2> /dev/null)\n+\toldrev_type=$(git cat-file -t \"$oldrev\" 2> /dev/null)\n+\tcase \"$change_type\" in\n+\tcreate|update)\n+\t\trev=\"$newrev\"\n+\t\trev_type=\"$newrev_type\"\n+\t\t;;\n+\tdelete)\n+\t\trev=\"$oldrev\"\n+\t\trev_type=\"$oldrev_type\"\n+\t\t;;\n+\tesac\n+\n+\t# The revision type tells us what type the commit is, combined with\n+\t# the location of the ref we can decide between\n+\t#  - working branch\n+\t#  - tracking branch\n+\t#  - unannoted tag\n+\t#  - annotated tag\n+\tcase \"$refname\",\"$rev_type\" in\n+\t\trefs/tags/*,commit)\n+\t\t\t# un-annotated tag\n+\t\t\trefname_type=\"tag\"\n+\t\t\tshort_refname=${refname##refs/tags/}\n+\t\t\t;;\n+\t\trefs/tags/*,tag)\n+\t\t\t# annotated tag\n+\t\t\trefname_type=\"annotated tag\"\n+\t\t\tshort_refname=${refname##refs/tags/}\n+\t\t\t# change recipients\n+\t\t\tif [ -n \"$announcerecipients\" ]; then\n+\t\t\t\trecipients=\"$announcerecipients\"\n+\t\t\tfi\n+\t\t\t;;\n+\t\trefs/heads/*,commit)\n+\t\t\t# branch\n+\t\t\trefname_type=\"branch\"\n+\t\t\tshort_refname=${refname##refs/heads/}\n+\t\t\t;;\n+\t\trefs/remotes/*,commit)\n+\t\t\t# tracking branch\n+\t\t\trefname_type=\"tracking branch\"\n+\t\t\tshort_refname=${refname##refs/remotes/}\n+\t\t\techo \"*** Push-update of tracking branch, $refname\"\n+\t\t\techo \"***  - no email generated.\" >&2\n+\t\t\texit 0\n+\t\t\t;;\n+\t\t*)\n+\t\t\t# Anything else (is there anything else?)\n+\t\t\techo \"*** Unknown type of update to $refname ($rev_type)\" >&2\n+\t\t\techo \"***  - no email generated\"\n+\t\t\texit 1\n+\t\t\t;;\n+\tesac\n+\n+\t# Check if we've got anyone to send to\n+\tif [ -z \"$recipients\" ]; then\n+\t\techo \"*** hooks.recipients is not set so no email will be sent\" >&2\n+\t\techo \"*** for $refname update $oldrev->$newrev\" >&2\n+\t\texit 0\n+\tfi\n+\n+\t# Email parameters\n+\t# The committer will be obtained from the latest existing rev; so\n+\t# for a deletion it will be the oldrev, for the others, then newrev\n+\tcommitter=$(git show --pretty=full -s $rev | grep \"^Commit: \" | sed -e \"s/^Commit: //\")\n+\t# The email subject will contain the best description of the ref\n+\t# that we can build from the parameters\n+\tdescribe=$(git describe $rev 2>/dev/null)\n+\tif [ -z \"$describe\" ]; then\n+\t\tdescribe=$rev\n+\tfi\n+\n+\tgenerate_email_header\n+\n+\t# Call the correct body generation function - object oriented programming\n+\t# in a script - overkill perhaps?\n+\tfn_name=general\n+\tcase \"$refname_type\" in\n+\t\t# --- Branches\n+\t\t\"tracking branch\"|branch)\n+\t\t\tfn_name=branch\n+\t\t\t;;\n+\t\t# --- Annotated tags\n+\t\t\"annotated tag\")\n+\t\t\tfn_name=atag\n+\t\t\t;;\n+\tesac\n+\tgenerate_${change_type}_${fn_name}_email\n+\n+\tgenerate_email_footer\n+}\n+\n+generate_email_header()\n+{\n+\t# --- Email (all stdout will be the email)\n+\t# Generate header\n+\tcat <<-EOF\n+\tFrom: $committer\n+\tTo: $recipients\n+\tSubject: ${EMAILPREFIX}$projectdesc $refname_type, $short_refname, ${change_type}d. $describe\n+\tX-Git-Refname: $refname\n+\tX-Git-Reftype: $refname_type\n+\tX-Git-Oldrev: $oldrev\n+\tX-Git-Newrev: $newrev\n+\n+\tThis is an automated email from the git hooks/post-receive script. It was\n+\tgenerated because a ref change was pushed to the repository containing\n+\tthe project \"$projectdesc\".\n+\n+\tThe $refname_type, $short_refname has been ${change_type}d\n+\tEOF\n+}\n+\n+generate_email_footer()\n+{\n+\tcat <<-EOF\n+\n+\thooks/post-receive\n+\t--\n+\tGit Source Code Management System\n+\tEOF\n+}\n+\n+# --------------- Branches\n+\n+#\n+# Called for the creation of a branch\n+#\n+generate_create_branch_email()\n+{\n+\t# This is a new branch and so oldrev is not valid\n+\techo \"        at  $newrev ($newrev_type)\"\n+\techo \"\"\n+\n+\techo $LOGBEGIN\n+\t# This shows all log entries that are not already covered by\n+\t# another ref - i.e. commits that are now accessible from this\n+\t# ref that were previously not accessible (see generate_update_branch_email\n+\t# for the explanation of this command)\n+\tgit rev-parse --not --branches | grep -v $(<${GIT_DIR}/$refname) |\n+\tgit rev-list --pretty --stdin $newrev --not $baserev\n+\techo $LOGEND\n+}\n+\n+#\n+# Called for the change of a pre-existing branch\n+#\n+generate_update_branch_email()\n+{\n+\t# Consider this:\n+\t#   1 --- 2 --- O --- X --- 3 --- 4 --- N\n+\t#\n+\t# O is $oldrev for $refname\n+\t# N is $newrev for $refname\n+\t# X is a revision pointed to by some other ref, for which we may\n+\t#   assume that an email has already been generated.\n+\t# In this case we want to issue an email containing only revisions\n+\t# 3, 4, and N.  Given (almost) by\n+\t#\n+\t#  git-rev-list N ^O --not --all\n+\t#\n+\t# The reason for the \"almost\", is that the \"--not --all\" will take\n+\t# precedence over the \"N\", and effectively will translate to\n+\t#\n+\t#  git-rev-list N ^O ^X ^N\n+\t#\n+\t# So, we need to build up the list more carefully.  git-rev-parse will\n+\t# generate a list of revs that may be fed into git-rev-list.  We can get\n+\t# it to make the \"--not --all\" part and then filter out the \"^N\" with:\n+\t#\n+\t#  git-rev-parse --not --all | grep -v N\n+\t#\n+\t# Then, using the --stdin switch to git-rev-list we have effectively\n+\t# manufactured\n+\t#\n+\t#  git-rev-list N ^O ^X\n+\t#\n+\t# This leaves a problem when someone else updates the repository\n+\t# while this script is running.  Their new value of the ref we're working\n+\t# on would be included in the \"--not --all\" output; and as our $newrev\n+\t# would be an ancestor of that commit, it would exclude all of our\n+\t# commits.  What we really want is to exclude the current value of\n+\t# $refname from the --not list, rather than N itself.  So:\n+\t#\n+\t#  git-rev-parse --not --all | grep -v $(< $refname)\n+\t#\n+\t# Get's us to something pretty safe (apart from the small time between\n+\t# refname being read, and git-rev-parse running - for that, I give up)\n+\t#\n+\t#\n+\t# Next problem, consider this:\n+\t#   * --- B --- * --- O ($oldrev)\n+\t#          \\\n+\t#           * --- X --- * --- N ($newrev)\n+\t#\n+\t# That is to say, there is no guarantee that newrev is a strict subset of\n+\t# oldrev (it would have required a --force, but that's allowed).  So, we\n+\t# can't simply say rev-list $oldrev..$newrev.  Instead we find the common\n+\t# base of the two revs and list from there.\n+\t#\n+\t# As above, we need to take into account the presence of X; if another\n+\t# branch is already in the repository and points at some of the revisions\n+\t# that we are about to output - we don't want them.  The solution is as\n+\t# before: git-rev-parse output filtered.\n+\t#\n+\t# Finally, tags:\n+\t#   1 --- 2 --- O --- T --- 3 --- 4 --- N\n+\t#\n+\t# Tags pushed into the repository generate nice shortlog emails that\n+\t# summarise the commits between them and the previous tag.  However,\n+\t# those emails don't include the full commit messages that we output\n+\t# for a branch update.  Therefore we still want to output revisions\n+\t# that have been output on a tag email.\n+\t#\n+\t# Luckily, git-rev-parse includes just the tool.  Instead of using \"--all\"\n+\t# we use \"--branches\"; this has the added benefit that \"remotes/\" will\n+\t# be ignored as well.\n+\n+\tbaserev=$(git merge-base $oldrev $newrev)\n+\n+\t# List all the revisions from baserev to newrev in a kind of\n+\t# \"table-of-contents\"; note this list can include revisions that have\n+\t# already had notification emails and is present to show the full detail\n+\t# of the change from rolling back the old revision to the base revision and\n+\t# then forward to the new revision\n+\tfor rev in $(git rev-list $newrev --not $baserev)\n+\tdo\n+\t\trevtype=$(git cat-file -t \"$rev\")\n+\t\techo \"       via  $rev ($revtype)\"\n+\tdone\n+\n+\t# If the baserev is equal to the oldrev, then we know that this was a\n+\t# fast forward update; otherwise \"--force\" was used, and we give a more\n+\t# detailed explanation of what happened.\n+\tif [ \"$baserev\" = \"$oldrev\" ]; then\n+\t\techo \"      from  $oldrev ($oldrev_type)\"\n+\telse\n+\t\techo \"      from  $baserev\"\n+\t\techo \"  discards  $oldrev ($oldrev_type) and its branch\"\n+\t\techo \"\"\n+\t\techo \"This update added new revisions after undoing old revisions.  That is to\"\n+\t\techo \"say, the old revision is not a strict subset of the new revision.  This\"\n+\t\techo \"situation occurs when you --force push a change and generate a\"\n+\t\techo \"repository containing something like this:\"\n+\t\techo \"\"\n+\t\techo \" * -- * -- B -- O -- O -- O ($oldrev)\"\n+\t\techo \"            \\\\\"\n+\t\techo \"             N -- N -- N ($newrev)\"\n+\t\techo \"\"\n+\t\techo \"When this happens we assume that you've already had alert emails for all\"\n+\t\techo \"of the O revisions, and so we here report only the revisions in the N\"\n+\t\techo \"branch from the common base, B ($baserev),\"\n+\t\techo \"up to the new branch endpoint.\"\n+\tfi\n+\n+\techo \"\"\n+\techo \"Those revisions listed above that are new to this repository have\"\n+\techo \"not appeared on any other notification email; so we list those\"\n+\techo \"revisions in full, below.\"\n+\n+\techo \"\"\n+\techo $LOGBEGIN\n+\tgit rev-parse --not --branches | grep -v $(<${GIT_DIR}/$refname) |\n+\tgit rev-list --pretty --stdin $newrev --not $baserev\n+\n+\t# XXX: Need a way of detecting whether git rev-list actually outputted\n+\t# anything, so that we can issue a \"no new revisions added by this\n+\t# update\" message\n+\n+\techo $LOGEND\n+\n+\t# The diffstat is shown from the old revision to the new revision.  This\n+\t# is to show the truth of what happened in this change.  There's no point\n+\t# showing the stat from the base to the new revision because the base\n+\t# is effectively a random revision at this point - the user will be\n+\t# interested in what this revision changed - including the undoing of\n+\t# previous revisions in the case of non-fast forward updates.\n+\techo \"\"\n+\techo \"Summary of changes:\"\n+\tgit diff-tree --no-color --stat -M -C --find-copies-harder $baserev..$newrev\n+}\n+\n+#\n+# Called for the deletion of a branch\n+#\n+generate_delete_branch_email()\n+{\n+\techo \"       was  $oldrev\"\n+\techo \"\"\n+}\n+\n+# --------------- Annotated tags\n+\n+#\n+# Called for the creation of an annotated tag\n+#\n+generate_create_atag_email()\n+{\n+\techo \"        at  $newrev ($newrev_type)\"\n+\n+\tgenerate_atag_email\n+}\n+\n+#\n+# Called for the update of an annotated tag (this is probably a rare event\n+# and may not even be allowed)\n+#\n+generate_update_atag_email()\n+{\n+\techo \"        to  $newrev ($newrev_type)\"\n+\techo \"      from  $oldrev (which is now obsolete)\"\n+\n+\tgenerate_atag_email\n+}\n+\n+#\n+# Called when an annotated tag is created or changed\n+#\n+generate_atag_email()\n+{\n+\t# Use git-for-each-ref to pull out the individual fields from the tag\n+\teval $(git-for-each-ref --format='\n+\ttagobject=\"%(*objectname)\"\n+\ttagtype=\"%(*objecttype)\"\n+\ttagger=\"%(taggername)\"\n+\ttagged=\"%(taggerdate)\"' $refname\n+\t)\n+\n+\techo \"   tagging  $tagobject ($tagtype)\"\n+\tcase \"$tagtype\" in\n+\tcommit)\n+\t\t# If the tagged object is a commit, then we assume this is a\n+\t\t# release, and so we calculate which tag this tag is replacing\n+\t\tprevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null)\n+\n+\t\tif [ -n \"$prevtag\" ]; then\n+\t\t\techo \"  replaces  $prevtag\"\n+\t\tfi\n+\t\t;;\n+\t*)\n+\t\techo \"    length  $(git cat-file -s $tagobject) bytes\"\n+\t\t;;\n+\tesac\n+\techo \" tagged by  $tagger\"\n+\techo \"        on  $tagged\"\n+\n+\techo \"\"\n+\techo $LOGBEGIN\n+\n+\t# Show the content of the tag message; this might contain a change log\n+\t# or release notes so is worth displaying.  \"tail -n +5\" is there to\n+\t# remove the first 4 lines of the tag object, those details have\n+\t# already been summarised above\n+\tgit cat-file tag $newrev | tail -q -n +5\n+\n+\techo \"\"\n+\tcase \"$tagtype\" in\n+\tcommit)\n+\t\t# Only commit tags make sense to have rev-list operations performed\n+\t\t# on them\n+\t\tif [ -n \"$prevtag\" ]; then\n+\t\t\t# Show changes since the previous release\n+\t\t\tgit rev-list --pretty=short \"$prevtag..$newrev\" | git shortlog\n+\t\telse\n+\t\t\t# No previous tag, show all the changes since time began\n+\t\t\tgit rev-list --pretty=short $newrev | git shortlog\n+\t\tfi\n+\t\t;;\n+\t*)\n+\t\t# XXX: Is there anything useful we can do for non-commit objects?\n+\t\t;;\n+\tesac\n+\n+\techo $LOGEND\n+\techo \"\"\n+}\n+\n+#\n+# Called for the deletion of an annotated tag\n+#\n+generate_delete_atag_email()\n+{\n+\techo \"       was  $oldrev\"\n+\techo \"\"\n+}\n+\n+# --------------- General references\n+\n+#\n+# Called when any other type of reference is created (most likely a\n+# non-annotated tag)\n+#\n+generate_create_general_email()\n+{\n+\techo \"        at  $newrev ($newrev_type)\"\n+\n+\tgenerate_general_email\n+}\n+\n+#\n+# Called when any other type of reference is updated (most likely a\n+# non-annotated tag)\n+#\n+generate_update_general_email()\n+{\n+\techo \"        to  $newrev ($newrev_type)\"\n+\techo \"      from  $oldrev\"\n+\n+\tgenerate_general_email\n+}\n+\n+#\n+# Called for creation or update of any other type of reference\n+#\n+generate_general_email()\n+{\n+\t# Unannotated tags are more about marking a point than releasing a version;\n+\t# therefore we don't do the shortlog summary that we do for annotated tags\n+\t# above - we simply show that the point has been marked, and print the log\n+\t# message for the marked point for reference purposes\n+\t#\n+\t# Note this section also catches any other reference type (although there\n+\t# aren't any) and deals with them in the same way.\n+\n+\techo \"\"\n+\tif [ \"$newrev_type\" = \"commit\" ]; then\n+\t\techo $LOGBEGIN\n+\t\tgit show --no-color --root -s $newrev\n+\t\techo $LOGEND\n+\telse\n+\t\t# What can we do here?  The tag marks an object that is not a commit,\n+\t\t# so there is no log for us to display.  It's probably not wise to\n+\t\t# output git-cat-file as it could be a binary blob.  We'll just say how\n+\t\t# big it is\n+\t\techo \"$newrev is a $newrev_type, and is $(git cat-file -s $newrev) bytes long.\"\n+\tfi\n+\techo \"\"\n+}\n+\n+#\n+# Called for the deletion of any other type of reference\n+#\n+generate_delete_general_email()\n+{\n+\techo \"       was  $oldrev\"\n+\techo \"\"\n+}\n+\n+# ---------------------------- main()\n+\n+# --- Constants\n+EMAILPREFIX=\"[SCM] \"\n+LOGBEGIN=\"- Log -----------------------------------------------------------------\"\n+LOGEND=\"-----------------------------------------------------------------------\"\n+\n+# --- Config\n+# Set GIT_DIR either from the working directory, or from the environment\n+# variable.\n+GIT_DIR=$(git-rev-parse --git-dir 2>/dev/null)\n+if [ -z \"$GIT_DIR\" ]; then\n+\techo \"fatal: post-receive: GIT_DIR not set\"\n+\texit 1\n+fi\n+\n+projectdesc=$(cat $GIT_DIR/description)\n+# Check if the description is unchanged from it's default, and shorten it to a\n+# more manageable length if it is\n+if expr \"$projectdesc\" : \"Unnamed repository.*$\" >/dev/null\n+then\n+\tprojectdesc=\"UNNAMED PROJECT\"\n+fi\n+\n+recipients=$(git repo-config hooks.mailinglist)\n+announcerecipients=$(git repo-config hooks.announcelist)\n+\n+# --- Main loop\n+# Allow dual mode: run from the command line just like the update hook, or if\n+# no arguments are given then run as a hook script\n+if [ -n \"$1\" -a -n \"$2\" -a -n \"$3\" ]; then\n+\t# Output to the terminal in command line mode - if someone wanted to\n+\t# resend an email; they could redirect the output to sendmail themselves\n+\tgenerate_email $2 $3 $1\n+else\n+\twhile read oldrev newrev refname\n+\tdo\n+\t\tgenerate_email $oldrev $newrev $refname | /usr/sbin/sendmail -t\n+\tdone\n+fi\n-- \n1.5.1.rc1.28.gedbf0\n"},{"id":"37877","messageId":"7vlkhmg6c3.fsf@assigned-by-dhcp.cox.net","threadId":"7365","inReplyTo":"200703231023.58911.andyparkins@gmail.com","subject":"Re: [PATCH] Convert emailing part of hooks--update to hooks--post-receive","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-03-24T12:43:24Z","receivedAt":"2007-03-24T12:43:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Andy Parkins <andyparkins@gmail.com> writes:\n\n>  git rev-parse --not --all | grep -v $(<$refname) |\n>  git rev-list --pretty --stdin $newrev --not $baserev\n>\n> This uses git-rev-parse followed by grep to filter out the revision of\n> the ref in question before it gets to rev-list and inhibits the output\n> of itself.  By using $(<$refname) rather than $newrev as the filter, it\n> also takes care of the situation where another update to the same ref\n> has been made since $refname was $newrev.\n\nIs $(<$refname) even POSIX?  Are we sure $refname is always a\nfile (not a packed ref)?\n\nIf the other push updates the same ref you are filtering out,\nthat would make \"grep -v\" to remove it from the exclusion set,\nwhich is a neat trick.  However, wouldn't that cause the\nrev-list to report the effect of the other push, and not your\npush?  I am wondering if the mail would say \"Alice pushed and\nthis branch has these commits as the result\" and ends up\nincluding Bob's commits in it when that happens.\n\n>  * All uses of \"git-command\" changed to \"git command\", to cope better\n>    if a user decided not to install all the hard links to git;\n\nI think that is a good discipline in general, but I wonder if it\nis strictly necessary.  Doesn't hook already get invoked with\nenvironment that has GIT_EXEC_PATH at the beginning of the PATH?\n\n> +generate_email()\n> +{\n> ...\n> +\t\trefs/remotes/*,commit)\n> +\t\t\t# tracking branch\n> +\t\t\trefname_type=\"tracking branch\"\n> +\t\t\tshort_refname=${refname##refs/remotes/}\n> +\t\t\techo \"*** Push-update of tracking branch, $refname\"\n> +\t\t\techo \"***  - no email generated.\" >&2\n\nHuh?  Did you mean to send the first one also to stderr?\n\n> +\t\t\texit 0\n> +\t\t\t;;\n> +\t\t*)\n> +\t\t\t# Anything else (is there anything else?)\n> +\t\t\techo \"*** Unknown type of update to $refname ($rev_type)\" >&2\n> +\t\t\techo \"***  - no email generated\"\n\nHuh?  Did you mean to send the second one also to stderr?\n\nIt usually is easier to spot this kind of gotchas if you\nconsistently write like this instead:\n\n\techo >&2 \"your message comes here...\"\n\techo >&2 \"... and the second line is here\"\n\n> +\t\t\texit 1\n> +\t\t\t;;\n> +\tesac\n> +\n> +\t# Check if we've got anyone to send to\n> +\tif [ -z \"$recipients\" ]; then\n> +\t\techo \"*** hooks.recipients is not set so no email will be sent\" >&2\n> +\t\techo \"*** for $refname update $oldrev->$newrev\" >&2\n> +\t\texit 0\n> +\tfi\n> +\n> +\t# Email parameters\n> +\t# The committer will be obtained from the latest existing rev; so\n> +\t# for a deletion it will be the oldrev, for the others, then newrev\n> +\tcommitter=$(git show --pretty=full -s $rev | grep \"^Commit: \" | sed -e \"s/^Commit: //\")\n\nPlease never pipe grep into sed, or the other way around.  It\nmakes the script look amateurish.\n\nI think you meant:\n\n\t.... | sed -ne 's/^Commit: //p'\n\nIs it assumed that pushing developers do not cross-pull,\nbypassing the repository this hook is in?  Otherwise, if Alice\ndoes a lot of work, Bob pulls from her (bypassing this\nrepository), makes a tiny fix, and Alice pulls (bypassing this\nrepository) back from Bob, the last pull by Alice would result\nin a fast-forward, and when Alice pushes the results back, this\nrepository will see the Bob's tiny fix at the tip, the script\ntakes that commit as the representative, and From: line would\nhave Bob's name on it.\n\nIt would not be a problem as long as you use this repository as\nthe _ONLY_ place developers meet, but that's a big assumption:\ncentralized repo with CVS-mentality.\n\nThe assumption is not wrong per-se, but I think that needs to be\ndocumented at one place, near the top: \"if you subscribe to this\nkind of workflow, then this e-mail script is for you, otherwise,\nit is not\".\n\nSimilarly the other assumption your script makes should be\ndocumented: the \"list only new commits to the entire repository\"\ndesign.  I do not think that behaviour is for everybody, but you\nalready know that, and I would not make further comments on this\npoint, other than saying that I do agree it is good for some\npeople.  It all depends on the workflow and recipients'\ninterests.\n\n> +\t# The email subject will contain the best description of the ref\n> +\t# that we can build from the parameters\n> +\tdescribe=$(git describe $rev 2>/dev/null)\n> +\tif [ -z \"$describe\" ]; then\n> +\t\tdescribe=$rev\n> +\tfi\n> +\n> +\tgenerate_email_header\n> +\n> +\t# Call the correct body generation function - object oriented programming\n> +\t# in a script - overkill perhaps?\n\nI do not think so.  It is a good style as long as you do not\nbotch it and leave the variables empty -- even in that case the\nruntime would notice and say \"generate___email: no such command\"\n;-).\n\n> +\tfn_name=general\n> +\tcase \"$refname_type\" in\n> +\t\t# --- Branches\n> +\t\t\"tracking branch\"|branch)\n> +\t\t\tfn_name=branch\n> +\t\t\t;;\n> +\t\t# --- Annotated tags\n> +\t\t\"annotated tag\")\n> +\t\t\tfn_name=atag\n> +\t\t\t;;\n> +\tesac\n\nPlease indent the case arms at the same level as \"case/esac\",\nlike this:\n\n\tcase $var in\n        foo | bar)\n        \tdo this\n                ;;\n\tesac\n\nwhich is in line with:\n\n\tswitch (x) {\n        case 1:\n        \tfoo;\n                break;\n\t}\n\n> +\tgenerate_${change_type}_${fn_name}_email\n> +\n> +\tgenerate_email_footer\n> +}\n> +\n> +generate_email_header()\n> +{\n> +\t# --- Email (all stdout will be the email)\n> +\t# Generate header\n> +\tcat <<-EOF\n> +\tFrom: $committer\n> +\tTo: $recipients\n> +\tSubject: ${EMAILPREFIX}$projectdesc $refname_type, $short_refname, ${change_type}d. $describe\n> +\tX-Git-Refname: $refname\n> +\tX-Git-Reftype: $refname_type\n> +\tX-Git-Oldrev: $oldrev\n> +\tX-Git-Newrev: $newrev\n> +\n> +\tThis is an automated email from the git hooks/post-receive script. It was\n> +\tgenerated because a ref change was pushed to the repository containing\n> +\tthe project \"$projectdesc\".\n> +\n> +\tThe $refname_type, $short_refname has been ${change_type}d\n> +\tEOF\n> +}\n\nMicronit.  Some committer names (GIT_COMMITTER_NAME) that are\nnot quoted may need to be quoted to appear on the From: header\n(most notable is a name with dot after middle initial).\n\n> +\n> +generate_email_footer()\n> +{\n> +\tcat <<-EOF\n> +\n> +\thooks/post-receive\n> +\t--\n> +\tGit Source Code Management System\n> +\tEOF\n> +}\n\nA signature separator line is usually \"^-- $\", not \"^--$\".\n\nI'd rather not to have name Git there, for two reasons.  If you\nare the lead of the development group who is also contact to the\nQA group, you would probably get e-mail notifications from your\ndevelopment repo and QA group repo.  Instead of the generic\n'Git' name, which does not add any information of real value, it\nwould be better to have an identifier of the repository (you\nwould configure it in description perhaps).  Granted, the same\ninformation is on Subject: line, but the body of the message may\nbe saved in a separate file for later perusal.\n\nAlso, this is (as described in the first part of the script)\nonly an example script, and although I am willing to help making\nsure this is bug-free (like spending time writing this message\nright now -- wow, I've been sitting here typing this e-mail for\nalmost two hours), I do not want to be held responsible for any\nbugs in it, nor I do not want anybody to mistakenly think that\nwhat this script does is the way to run things recommended by\nthe Git community.  It really is just \"a useful script to use\nWITH git Andy wrote\", not \"THE hook Git gives us\".\n\n> +# --------------- Branches\n> +\n> +#\n> +# Called for the creation of a branch\n> +#\n> +generate_create_branch_email()\n> +{\n> +\t# This is a new branch and so oldrev is not valid\n> +\techo \"        at  $newrev ($newrev_type)\"\n> +\techo \"\"\n\nI kind of like the attention to the detail of giving an empty\nargv[1] to echo (it used to be customary to do this for\nportability), but I wonder if it is still needed...\n\n> +\n> +\techo $LOGBEGIN\n> +\t# This shows all log entries that are not already covered by\n> +\t# another ref - i.e. commits that are now accessible from this\n> +\t# ref that were previously not accessible (see generate_update_branch_email\n> +\t# for the explanation of this command)\n> +\tgit rev-parse --not --branches | grep -v $(<${GIT_DIR}/$refname) |\n> +\tgit rev-list --pretty --stdin $newrev --not $baserev\n> +\techo $LOGEND\n> +}\n\nWhere does $baserev come from?  Random leftover garbage from\ninvocation for the branch the main while loop handled before\nthis branch?\n\nI think \"--not $baserev\" is useless for a new branch ;-).\n\n> +\n> +#\n> +# Called for the change of a pre-existing branch\n> +#\n> +generate_update_branch_email()\n> +{\n> +\t# Consider this:\n> +\t#   1 --- 2 --- O --- X --- 3 --- 4 --- N\n> +\t#\n> +\t# O is $oldrev for $refname\n> +\t# N is $newrev for $refname\n> +\t# X is a revision pointed to by some other ref, for which we may\n> +\t#   assume that an email has already been generated.\n\nYou are doing this one branch at a time even though a single\npush may update more than one branch.  I wonder what happens if\nbranch A and B are pushed, and they both contain shared new\nmaterial?  When this is run for A, the entire contribution of B\n(after push happened) is excluded.  Then when this is run for\nB, the entire contribution of A is excluded.  Doesn't that mean\nthe commits new to this repository that are shared between A and\nB appear for neither branches?  There must be something obvious\nI am missing...\n\n> +\t# Next problem, consider this:\n> +\t#   * --- B --- * --- O ($oldrev)\n> +\t#          \\\n> +\t#           * --- X --- * --- N ($newrev)\n> +\t#\n> +\t# That is to say, there is no guarantee that newrev is a strict subset of\n> +\t# oldrev (it would have required a --force, but that's allowed).  So, we\n> +\t# can't simply say rev-list $oldrev..$newrev.  Instead we find the common\n> +\t# base of the two revs and list from there.\n\nThis comment and logic is wrong.  In the above graph, if you say\n\"rev-list O..N\" (which is \"rev-list N ^O\"), you would get\ncommits that are reachable from N, excluding the ones that are\nalso reachable from O, so you would get N, * before it, X and *\nbefore it.\n\n> +\t#\n> +\t# As above, we need to take into account the presence of X; if another\n> +\t# branch is already in the repository and points at some of the revisions\n> +\t# that we are about to output - we don't want them.  The solution is as\n> +\t# before: git-rev-parse output filtered.\n\nSo, I think \"rev-list N ^O ^X\" is what you would want here,\nwhich is the same as the first \"single strand of pearls\" case.\nAnd notice that I did not have to say anything about base?  I do\nnot think it is necessary to compute base if you are only\ninterested in showing which commits are new.\n\nIt is however useful to compute it for another reason, namely\nthat you would want to give different messages depending on\nfast-forwardness of O->N transition.  However, even for that\npurpose, merge-base may not be necessarily the best way to see\nif it is a fast forward for this application.\n\n> +\t#\n> +\t# Finally, tags:\n> +\t#   1 --- 2 --- O --- T --- 3 --- 4 --- N\n> +\t#\n> +\t# Tags pushed into the repository generate nice shortlog emails that\n> +\t# summarise the commits between them and the previous tag.  However,\n> +\t# those emails don't include the full commit messages that we output\n> +\t# for a branch update.  Therefore we still want to output revisions\n> +\t# that have been output on a tag email.\n> +\t#\n> +\t# Luckily, git-rev-parse includes just the tool.  Instead of using \"--all\"\n> +\t# we use \"--branches\"; this has the added benefit that \"remotes/\" will\n> +\t# be ignored as well.\n\nOk.\n\n> +\n> +\tbaserev=$(git merge-base $oldrev $newrev)\n> +\n> +\t# List all the revisions from baserev to newrev in a kind of\n> +\t# \"table-of-contents\"; note this list can include revisions that have\n> +\t# already had notification emails and is present to show the full detail\n> +\t# of the change from rolling back the old revision to the base revision and\n> +\t# then forward to the new revision\n> +\tfor rev in $(git rev-list $newrev --not $baserev)\n> +\tdo\n> +\t\trevtype=$(git cat-file -t \"$rev\")\n> +\t\techo \"       via  $rev ($revtype)\"\n> +\tdone\n> +\n> +\t# If the baserev is equal to the oldrev, then we know that this was a\n> +\t# fast forward update; otherwise \"--force\" was used, and we give a more\n> +\t# detailed explanation of what happened.\n\nThat's bog-standard way we use to see if O->N transition is a\nfast forward, but for this application, since it is far more\nlikely to be than it isn't, I think computing \"rev-list N..O\"\nand checking if it reports anything is a better check\n(especially because its results can be used in a way I'll\ndescribe later).\n\nN..O lists commits that are reachable from O but not from N, in\nother words, the commits lost by moving head from O to N.  If\nthat is not an empty set, it is not a fast-forward.\n\n> +\tif [ \"$baserev\" = \"$oldrev\" ]; then\n> +\t\techo \"      from  $oldrev ($oldrev_type)\"\n> +\telse\n> +\t\techo \"      from  $baserev\"\n> +\t\techo \"  discards  $oldrev ($oldrev_type) and its branch\"\n> +\t\techo \"\"\n> +\t\techo \"This update added new revisions after undoing old revisions.  That is to\"\n> +\t\techo \"say, the old revision is not a strict subset of the new revision.  This\"\n> +\t\techo \"situation occurs when you --force push a change and generate a\"\n> +\t\techo \"repository containing something like this:\"\n> +\t\techo \"\"\n> +\t\techo \" * -- * -- B -- O -- O -- O ($oldrev)\"\n> +\t\techo \"            \\\\\"\n> +\t\techo \"             N -- N -- N ($newrev)\"\n> +\t\techo \"\"\n> +\t\techo \"When this happens we assume that you've already had alert emails for all\"\n> +\t\techo \"of the O revisions, and so we here report only the revisions in the N\"\n> +\t\techo \"branch from the common base, B ($baserev),\"\n> +\t\techo \"up to the new branch endpoint.\"\n> +\tfi\n\nI suspect the recipients would want to know which ones are\ndiscarded, so perhaps list O with:\n\n\t\"rev-list N..O\"\n\nNotice you do not need to use B to compute this set either.  I\nthink you can replace \"--not $baserev\" with \"--not $oldrev\" in\nall these \"rev-list --pretty --stdin $newrev\" invocations.\n\n> +\n> +\techo \"\"\n> +\techo \"Those revisions listed above that are new to this repository have\"\n> +\techo \"not appeared on any other notification email; so we list those\"\n> +\techo \"revisions in full, below.\"\n> +\n> +\techo \"\"\n> +\techo $LOGBEGIN\n> +\tgit rev-parse --not --branches | grep -v $(<${GIT_DIR}/$refname) |\n> +\tgit rev-list --pretty --stdin $newrev --not $baserev\n\nI am recommending to get rid of merge-base, which means you\nwon't have $baserev, but it can be replaced with $oldrev.\n\n\"O..N\" looks more gittish than \"$(merge-base O N)..N\"\n\n> +\n> +\t# XXX: Need a way of detecting whether git rev-list actually outputted\n> +\t# anything, so that we can issue a \"no new revisions added by this\n> +\t# update\" message\n> +\n> +\techo $LOGEND\n> +\n> +\t# The diffstat is shown from the old revision to the new revision.  This\n> +\t# is to show the truth of what happened in this change.  There's no point\n> +\t# showing the stat from the base to the new revision because the base\n> +\t# is effectively a random revision at this point - the user will be\n> +\t# interested in what this revision changed - including the undoing of\n> +\t# previous revisions in the case of non-fast forward updates.\n\n> +\techo \"\"\n> +\techo \"Summary of changes:\"\n> +\tgit diff-tree --no-color --stat -M -C --find-copies-harder $baserev..$newrev\n> +}\n\nComment and code mismatch between $baserev and $oldrev?\n\n\"-M -C\" is redundant.  Just \"-C\" should do.  If you really want\nto spend extra cycles, then \"--find-copies-harder\" is fine, but\nI think that implies \"-C\".\n\nDo you even need --no-color?  I thought we specifically tried\nnot to do the color or any UI stuff for the lower-level\ndiff-{tree,files,index} brothers.\n\nI would add --summary to the list, though.\n\n> +\n> +#\n> +# Called for the deletion of a branch\n> +#\n> +generate_delete_branch_email()\n> +{\n> +\techo \"       was  $oldrev\"\n> +\techo \"\"\n> +}\n\nPerhaps with a one-line?\n\n> +\n> +# --------------- Annotated tags\n> +\n> +#\n> +# Called for the creation of an annotated tag\n> +#\n> +generate_create_atag_email()\n> +{\n> +\techo \"        at  $newrev ($newrev_type)\"\n> +\n> +\tgenerate_atag_email\n> +}\n\nPerhaps with a one-line?\n\n> +\n> +#\n> +# Called for the update of an annotated tag (this is probably a rare event\n> +# and may not even be allowed)\n> +#\n> +generate_update_atag_email()\n> +{\n> +\techo \"        to  $newrev ($newrev_type)\"\n> +\techo \"      from  $oldrev (which is now obsolete)\"\n> +\n> +\tgenerate_atag_email\n> +}\n> +\n> +#\n> +# Called when an annotated tag is created or changed\n> +#\n> +generate_atag_email()\n> +{\n> +\t# Use git-for-each-ref to pull out the individual fields from the tag\n> +\teval $(git-for-each-ref --format='\n> +\ttagobject=\"%(*objectname)\"\n> +\ttagtype=\"%(*objecttype)\"\n> +\ttagger=\"%(taggername)\"\n> +\ttagged=\"%(taggerdate)\"' $refname\n> +\t)\n\nPerfect example of using for-each-ref and nicely done.  Contrary\nto what you said earlier, I see a hyphen between git and\nfor-each-ref, though.\n\n> +\n> +\techo \"   tagging  $tagobject ($tagtype)\"\n> +\tcase \"$tagtype\" in\n> +\tcommit)\n> +\t\t# If the tagged object is a commit, then we assume this is a\n> +\t\t# release, and so we calculate which tag this tag is replacing\n> +\t\tprevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null)\n\nWhat if $newrev is a merge, I wonder...\n\n> +\n> +\t\tif [ -n \"$prevtag\" ]; then\n> +\t\t\techo \"  replaces  $prevtag\"\n> +\t\tfi\n> +\t\t;;\n> +\t*)\n> +\t\techo \"    length  $(git cat-file -s $tagobject) bytes\"\n> +\t\t;;\n> +\tesac\n> +\techo \" tagged by  $tagger\"\n> +\techo \"        on  $tagged\"\n> +\n> +\techo \"\"\n> +\techo $LOGBEGIN\n> +\n> +\t# Show the content of the tag message; this might contain a change log\n> +\t# or release notes so is worth displaying.  \"tail -n +5\" is there to\n> +\t# remove the first 4 lines of the tag object, those details have\n> +\t# already been summarised above\n> +\tgit cat-file tag $newrev | tail -q -n +5\n\nDidn't you mean to update this with:\n\n\t... | sed -e '1,/^$/d'\n\n?\n\n> +\n> +\techo \"\"\n> +\tcase \"$tagtype\" in\n> +\tcommit)\n> +\t\t# Only commit tags make sense to have rev-list operations performed\n> +\t\t# on them\n> +\t\tif [ -n \"$prevtag\" ]; then\n> +\t\t\t# Show changes since the previous release\n> +\t\t\tgit rev-list --pretty=short \"$prevtag..$newrev\" | git shortlog\n> +\t\telse\n> +\t\t\t# No previous tag, show all the changes since time began\n> +\t\t\tgit rev-list --pretty=short $newrev | git shortlog\n> +\t\tfi\n> +\t\t;;\n> +\t*)\n> +\t\t# XXX: Is there anything useful we can do for non-commit objects?\n> +\t\t;;\n\nYou won't get a tag to another tag with %(*objecttype), so you\nonly have to worry about trees and blobs.  But in addition to\nthe \"length XX bytes\" you already said the type of what it tags\nupfront, so I do not think of anything else to say here.\n\n> +# --- Config\n> +# Set GIT_DIR either from the working directory, or from the environment\n> +# variable.\n> +GIT_DIR=$(git-rev-parse --git-dir 2>/dev/null)\n> +if [ -z \"$GIT_DIR\" ]; then\n> +\techo \"fatal: post-receive: GIT_DIR not set\"\n> +\texit 1\n> +fi\n> +\n> +projectdesc=$(cat $GIT_DIR/description)\n\nThis goes on the Subject: header; you'd better make sure the\nrepository is not misconfigured to have more than one lines in\nit.\n\n> +# Check if the description is unchanged from it's default, and shorten it to a\n> +# more manageable length if it is\n> +if expr \"$projectdesc\" : \"Unnamed repository.*$\" >/dev/null\n> +then\n> +\tprojectdesc=\"UNNAMED PROJECT\"\n> +fi\n\nI vaguely recall you made this die instead of leaving unnamed...\n"},{"id":"37885","messageId":"200703241550.05590.andyparkins@gmail.com","threadId":"7365","inReplyTo":"7vlkhmg6c3.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] Convert emailing part of hooks--update to hooks--post-receive","fromName":"Andy Parkins","fromEmail":"andyparkins@gmail.com","sentAt":"2007-03-24T15:44:51Z","receivedAt":"2007-03-24T15:44:51Z","isPatch":true,"sender":{"key":"andyparkins@gmail.com","avatar":null},"body":"On Saturday 2007, March 24, Junio C Hamano wrote:\n\n> Is $(<$refname) even POSIX?  Are we sure $refname is always a\n> file (not a packed ref)?\n\nOh dear; I had no idea that packed refs made the ref file itself \ndisappear.  Would\n\n git-rev-parse $refname\n\nBe a better way of getting the revision?\n\n> If the other push updates the same ref you are filtering out,\n> that would make \"grep -v\" to remove it from the exclusion set,\n> which is a neat trick.  However, wouldn't that cause the\n> rev-list to report the effect of the other push, and not your\n> push?  I am wondering if the mail would say \"Alice pushed and\n> this branch has these commits as the result\" and ends up\n> including Bob's commits in it when that happens.\n\nI hope not, \n\n  o --- O --- n --- n --- N --- m --- m --- M (refname)\n\nThe revs O and N come from stdin so git-receive-pack will pass (with \nrefname pointing at M in both cases)\n  O N refname\n  N M refname\non stdin.  In case 1 (Alice),\n\n  git rev-parse --not --all | grep -v $(git rev-parse $refname) |\n  git rev-list --pretty --stdin $newrev\n\nfilters out the \"^M\" from the git-rev-parse output, and the git-rev-list \nadds N in.  So although \"^M\" isn't the \"^N\" that the grep is normally \nthere to remove, it doesn't matter because the $newrev makes the \nrev-list start at \"N\", ignoring the \"m\" commits.\n\n> I think that is a good discipline in general, but I wonder if it\n> is strictly necessary.  Doesn't hook already get invoked with\n> environment that has GIT_EXEC_PATH at the beginning of the PATH?\n\nIt's certainly not necessary; just an idea I picked up off the mailing \nlist, but I forget from whom I got it.\n\n> Huh?  Did you mean to send the first one also to stderr?\n> Huh?  Did you mean to send the second one also to stderr?\n>\n> It usually is easier to spot this kind of gotchas if you\n> consistently write like this instead:\n>\n> \techo >&2 \"your message comes here...\"\n> \techo >&2 \"... and the second line is here\"\n\nDone, done and done.\n\n> Please never pipe grep into sed, or the other way around.  It\n> makes the script look amateurish.\n\nOkay; my unfamiliarity with sed is showing up; I don't use it a lot so \nI'd say I am an amateur.  Fixed.\n\n> Is it assumed that pushing developers do not cross-pull,\n> bypassing the repository this hook is in?  Otherwise, if Alice\n\nI did think about that problem, but I couldn't think of a truely \"right\" \nsolution.  It seemed strange to have emails appearing to be from \nsomeone who wasn't mentioned in the commits.  Also; I couldn't see that \nthere was any sensible way of finding the email address of the person \ndoing the push.\n\n> It would not be a problem as long as you use this repository as\n> the _ONLY_ place developers meet, but that's a big assumption:\n> centralized repo with CVS-mentality.\n\nI thought it was safe to make that assumption because the use of this \nhook script implies a centralised-repo mentality.\n\n> The assumption is not wrong per-se, but I think that needs to be\n> documented at one place, near the top: \"if you subscribe to this\n\nDone.\n\n> Similarly the other assumption your script makes should be\n> documented: the \"list only new commits to the entire repository\"\n\nDone.\n\n> I do not think so.  It is a good style as long as you do not\n> botch it and leave the variables empty -- even in that case the\n> runtime would notice and say \"generate___email: no such command\"\n> ;-).\n\nI can't see a path through the script that leaves either $change_type or \n$fn_name unset; so I hope that won't be a problem.\n\n> Please indent the case arms at the same level as \"case/esac\",\n> like this:\n\nDone.\n\n> Micronit.  Some committer names (GIT_COMMITTER_NAME) that are\n> not quoted may need to be quoted to appear on the From: header\n> (most notable is a name with dot after middle initial).\n\nHow does\n\n  committer=$(git show --pretty=full -s $rev | sed -ne \"s/^Commit: //\" |\n\t\tsed -e 's/\\(.*\\) </\"\\1\" </')\n\nLook?\n\n> A signature separator line is usually \"^-- $\", not \"^--$\".\n\nDone.\n\n> I'd rather not to have name Git there, for two reasons.  If you\n\nDone.  $projectdesc used instead.\n\n> Also, this is (as described in the first part of the script)\n> only an example script, and although I am willing to help making\n> sure this is bug-free (like spending time writing this message\n> right now -- wow, I've been sitting here typing this e-mail for\n> almost two hours), I do not want to be held responsible for any\n\nWow; that is a long time.\n\n> bugs in it, nor I do not want anybody to mistakenly think that\n> what this script does is the way to run things recommended by\n> the Git community.  It really is just \"a useful script to use\n> WITH git Andy wrote\", not \"THE hook Git gives us\".\n\nNo problem at all.\n\n> I kind of like the attention to the detail of giving an empty\n> argv[1] to echo (it used to be customary to do this for\n> portability), but I wonder if it is still needed...\n\nI don't know were I picked that habbit up from; I think some early \nscripts I wrote needed it, so from then on I just put it in whenever I \nneed a blank.\n\nOn ocassion it's also helped for find and replace operations.\n\n> Where does $baserev come from?  Random leftover garbage from\n> invocation for the branch the main while loop handled before\n> this branch?\n\nOops.  Done; baserev removed.\n\n> You are doing this one branch at a time even though a single\n> push may update more than one branch.  I wonder what happens if\n> branch A and B are pushed, and they both contain shared new\n> material?  When this is run for A, the entire contribution of B\n> (after push happened) is excluded.  Then when this is run for\n> B, the entire contribution of A is excluded.  Doesn't that mean\n> the commits new to this repository that are shared between A and\n> B appear for neither branches?  There must be something obvious\n> I am missing...\n\nHmmm, that's a good point.  That's an assumption from the old update \nhook that is no longer true.  I have no answer for that.\n\n> This comment and logic is wrong.  In the above graph, if you say\n> \"rev-list O..N\" (which is \"rev-list N ^O\"), you would get\n> commits that are reachable from N, excluding the ones that are\n> also reachable from O, so you would get N, * before it, X and *\n> before it.\n\nThe comment was indeed the wrong way round; but I got the baserev stuff \nfrom the old hook script:\n\n$ git-blame v1.4.4 -- templates/hooks--update\n(Junio C Hamano     75)  base=$(git-merge-base \"$2\" \"$3\")\n(Junio C Hamano     86)  git-rev-list --pretty \"$3\" \"^$base\"\n\nI just assumed there was good reason for it and copied it.  If rev-list \nO..N is fine, I'll do that.\n\n> That's bog-standard way we use to see if O->N transition is a\n> fast forward, but for this application, since it is far more\n> likely to be than it isn't, I think computing \"rev-list N..O\"\n> and checking if it reports anything is a better check\n> (especially because its results can be used in a way I'll\n> describe later).\n\nI like it.\n\nI've thrown away all the baserev stuff and used the N..O notation to \nlist the discarded revisions.  This also makes it easy to detect the \nfast forward as you say.\n\n> > +\techo \"\"\n> > +\techo \"Summary of changes:\"\n> > +\tgit diff-tree --no-color --stat -M -C --find-copies-harder\n> > $baserev..$newrev +}\n>\n> Comment and code mismatch between $baserev and $oldrev?\n\nComment was right; what I was actually trying to show was the difference \nbetween oldrev and newrev, whatever the path it took to get from old to \nnew.\n\n> \"-M -C\" is redundant.  Just \"-C\" should do.  If you really want\n> to spend extra cycles, then \"--find-copies-harder\" is fine, but\n\nDone.  I think wasting the extra cycles is okay, because no one is going \nto notice if the notification email is a bit delayed in its generation, \nbut they might appreciate the extra detail in the email that will be \nthere to refer to forever.\n\n> Do you even need --no-color?  I thought we specifically tried\n\nParanoia on my part.  Removed.\n\n> I would add --summary to the list, though.\n\nDone; but I think the interesting extra information in the summary (i.e. \nthe creations/deletions) would be better if it were on the --stat and \nthe --summary can go again.\n\n> > +\n> > +#\n> > +# Called for the deletion of a branch\n> > +#\n> > +generate_delete_branch_email()\n> > +{\n> > +\techo \"       was  $oldrev\"\n> > +\techo \"\"\n> > +}\n>\n> Perhaps with a one-line?\n\nDon't understand.  Do you mean throw the excessive size?  I made it a \nfunction on purpose to keep the \"example\" quality.  Makes it obvious \nwere to put customisations.\n\n> Perfect example of using for-each-ref and nicely done.  Contrary\n> to what you said earlier, I see a hyphen between git and\n> for-each-ref, though.\n\nFixed.  I wish I could take credit, but it was Shawn's idea :-)\n\n> > +\t\t# If the tagged object is a commit, then we assume this is a\n> > +\t\t# release, and so we calculate which tag this tag is replacing\n> > +\t\tprevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null)\n>\n> What if $newrev is a merge, I wonder...\n\nOh yes; but that one is Andreas's fault:\n\n(Andreas Ericsson   54)  prev=$(git describe \"$3^\" | sed 's/-g.*//')\n\nCan we hope that even if it is a merge, that either parent would be on \nthe same previous tag?\n\n X --- * -- * --- * --- * -- Y\n        \\                   /\n         * --- * --- * --- *\n\nNot guaranteed, but what else is valid?\n\n> Didn't you mean to update this with:\n>\n> \t... | sed -e '1,/^$/d'\n\nI did; and could swear I had.  Wonder how I managed to undo that one.\n\n> You won't get a tag to another tag with %(*objecttype), so you\n> only have to worry about trees and blobs.  But in addition to\n> the \"length XX bytes\" you already said the type of what it tags\n> upfront, so I do not think of anything else to say here.\n\nI did think that when gitattributes arrive, they might offer some \ninteresting possibilities for that section.\n\n> This goes on the Subject: header; you'd better make sure the\n> repository is not misconfigured to have more than one lines in\n> it.\n\nprojectdesc=$(cat $GIT_DIR/description | sed -ne '1p')\n\n?\n\n> I vaguely recall you made this die instead of leaving unnamed...\n\nI did for the update hook; when that would prevent the update; the \npost-receive hook can't stop the update so we /have/ to issue the \nemail - there is no second chance.\n\nAndy\n-- \nDr Andy Parkins, M Eng (hons), MIET\nandyparkins@gmail.com\n"},{"id":"37888","messageId":"Pine.LNX.4.64.0703240858370.6730@woody.linux-foundation.org","threadId":"7365","inReplyTo":"200703241550.05590.andyparkins@gmail.com","subject":"Re: [PATCH] Convert emailing part of hooks--update to hooks--post-receive","fromName":"Linus Torvalds","fromEmail":"torvalds@linux-foundation.org","sentAt":"2007-03-24T16:12:28Z","receivedAt":"2007-03-24T16:12:28Z","isPatch":true,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Sat, 24 Mar 2007, Andy Parkins wrote:\n> \n> Oh dear; I had no idea that packed refs made the ref file itself \n> disappear.\n\nEverybody should do\n\n\tgit pack-refs --all --prune\n\noccasionally to see this. In fact, I think we should probably make that \npart of \"git gc\" (which currently doesn't pack branches at all, just tags, \nsince it omits the \"--all\").\n\n> Would\n> \n>  git-rev-parse $refname\n> \n> Be a better way of getting the revision?\n\nThat's always the correct way to get a revision, although you should use\n\n\tgit-rev-parse --verify \"$refname\"^0\n\nto make sure that you get a commit (of course, if you don't want a \ncommit, but any random ref-SHA1, remove the \"^0\" from the end!).\n\nHOWEVER. \"git-rev-parse\" will take any arbitrary SHA1-expression, which \nmay or may not be what you want. If you actually also want to verify that \nit's strictly a branch name (or other ref-name), rather than just a random \nSHA1 expression, you should do\n\n\tgit show-ref [--verify] refname\n\nwhere the \"--verify\" again enables strict checking. HOWEVER, it will not \ncheck that it's a commit, so if you need the resulting SHA1 to be of a \nspecific type, you need to do that separately.\n\nSide note: those two \"--verify\" calls do two different kinds of strict \nchecking, because \"show-ref\" and \"rev-parse\" are different things. In \n\"git-rev-parse\" it checks that the argument is exactly *one* SHA1 value, \nand not some flag or a SHA1 range. Because \"git rev-parse\" can take a lot \nof different input formats. In contrast, in \"git show-ref\", the stricter \nchecking enabled by \"--verify\" will just force it to not do any \"pattern\" \nfor the ref, but it wants an *exact* refname.\n\n\t\tLinus\n"},{"id":"37914","messageId":"7vfy7tajiz.fsf@assigned-by-dhcp.cox.net","threadId":"7365","inReplyTo":"200703241550.05590.andyparkins@gmail.com","subject":"Re: [PATCH] Convert emailing part of hooks--update to hooks--post-receive","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-03-25T07:07:16Z","receivedAt":"2007-03-25T07:07:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Andy Parkins <andyparkins@gmail.com> writes:\n\n> On Saturday 2007, March 24, Junio C Hamano wrote:\n> ...\n>> You are doing this one branch at a time even though a single\n>> push may update more than one branch.  I wonder what happens if\n>> branch A and B are pushed, and they both contain shared new\n>> material?  When this is run for A, the entire contribution of B\n>> (after push happened) is excluded.  Then when this is run for\n>> B, the entire contribution of A is excluded.  Doesn't that mean\n>> the commits new to this repository that are shared between A and\n>> B appear for neither branches?  There must be something obvious\n>> I am missing...\n>\n> Hmmm, that's a good point.  That's an assumption from the old update \n> hook that is no longer true.  I have no answer for that.\n\nI think you can do something along the following line.\n\n (1) You say \"for-each-ref --all\" to get the ref information\n     that is after update.\n\n (2) Instead of looping and processing one at a time, grab all\n     information upfront.  replace the object name you obtained\n     in (1) for the ref being updated with the oldrev info you\n     discovered.\n\n (3) Handle the branch one at a time, but instead of using --all,\n     use the result of (2) as the \"state of the repository and\n     its refs before this update\".\n\nIn other words, you have enough information to reconstruct the\nview of the repository before your push took place.  After\nrunning \"for-each-ref --all\" once upfront, you reconstruct such\na view, and after that you do not ever look at where the real\nrefs are in the repository.  That would perhaps alleviate the\nissue of racing pushers as a side effect.\n\n>> > +\n>> > +#\n>> > +# Called for the deletion of a branch\n>> > +#\n>> > +generate_delete_branch_email()\n>> > +{\n>> > +\techo \"       was  $oldrev\"\n>> > +\techo \"\"\n>> > +}\n>>\n>> Perhaps with a one-line?\n>\n> Don't understand.  Do you mean throw the excessive size?\n\nSorry, what I meant was:\n\n\tgit show -s --pretty=oneline \"$oldrev\"\n\n>> Perfect example of using for-each-ref and nicely done.  Contrary\n>> to what you said earlier, I see a hyphen between git and\n>> for-each-ref, though.\n>\n> Fixed.  I wish I could take credit, but it was Shawn's idea :-)\n\nActually it was not quite \"nicely done\".  If taggername has an\nunusual character then dq pair you hard coded there may not\nquote the string correctly.\n\n> +\t# Use git-for-each-ref to pull out the individual fields from the tag\n> +\teval $(git-for-each-ref --format='\n> +\ttagobject=\"%(*objectname)\"\n> +\ttagtype=\"%(*objecttype)\"\n> +\ttagger=\"%(taggername)\"\n> +\ttagged=\"%(taggerdate)\"' $refname\n> +\t)\n\nI even suspect that a malicious tagger could do:\n\n\tGIT_COMMITTER_NAME='Andy `rm -fr .` Parkins' \\\n        git tag -a my-malicious-tag\n\nand push it to you.  Telling for-each-ref to quote properly for\nthe host language, like this:\n\n\tgit-for-each-ref --shell --format='\n        \to=%(*objectname)\n                t=%(*objecttype)\n                n=%(taggername)\n                d=%(taggerdate)' $refname\n\nwould be safer.\n\n>> > +\t\t# If the tagged object is a commit, then we assume this is a\n>> > +\t\t# release, and so we calculate which tag this tag is replacing\n>> > +\t\tprevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null)\n>>\n>> What if $newrev is a merge, I wonder...\n>\n> Oh yes; but that one is Andreas's fault:\n>\n> (Andreas Ericsson   54)  prev=$(git describe \"$3^\" | sed 's/-g.*//')\n>\n> Can we hope that even if it is a merge, that either parent would be on \n> the same previous tag?\n>\n>  X --- * -- * --- * --- * -- Y\n>         \\                   /\n>          * --- * --- * --- *\n>\n> Not guaranteed, but what else is valid?\n\nYou could describe all the parents and see if they differ.  If\nthey reach different tag, we could see which one is newer.  Or\nsomething like that.  In the special (but usual) case of a\nsingle parent commit, \"describing all the parents\" is what you\nare already doing, so it is not any more expensive in the normal\ncase.\n\n>> This goes on the Subject: header; you'd better make sure the\n>> repository is not misconfigured to have more than one lines in\n>> it.\n>\n> projectdesc=$(cat $GIT_DIR/description | sed -ne '1p')\n>\n> ?\n\nPlease do not have cat on either side of a pipe.  That makes a\nshell script look ... eh, you know the word ;-).\n\n\tsed -e 1q \"$GIT_DIR/description\"\n"},{"id":"37915","messageId":"7vaby1aj86.fsf@assigned-by-dhcp.cox.net","threadId":"7365","inReplyTo":"Pine.LNX.4.64.0703240858370.6730@woody.linux-foundation.org","subject":"Re: [PATCH] Convert emailing part of hooks--update to hooks--post-receive","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-03-25T07:13:45Z","receivedAt":"2007-03-25T07:13:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Torvalds <torvalds@linux-foundation.org> writes:\n\n> On Sat, 24 Mar 2007, Andy Parkins wrote:\n>> \n>> Oh dear; I had no idea that packed refs made the ref file itself \n>> disappear.\n>\n> Everybody should do\n>\n> \tgit pack-refs --all --prune\n>\n> occasionally to see this. In fact, I think we should probably make that \n> part of \"git gc\" (which currently doesn't pack branches at all, just tags, \n> since it omits the \"--all\").\n\n*Warning*\n\nAs described in 1.5.0 release notes, packed and then pruned refs\nare not visible by commit walkers older than 1.5.0.  So\neverybody should refrain from doing the above on a public\n\"distribution point\" repository that can be accessed over http\nuntil everybody upgrades.\n"},{"id":"37917","messageId":"7vircp92z9.fsf@assigned-by-dhcp.cox.net","threadId":"7365","inReplyTo":"7vfy7tajiz.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] Convert emailing part of hooks--update to hooks--post-receive","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-03-25T07:50:02Z","receivedAt":"2007-03-25T07:50:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <junkio@cox.net> writes:\n\n> I think you can do something along the following line.\n>\n>  (1) You say \"for-each-ref --all\" to get the ref information\n>      that is after update.\n>\n>  (2) Instead of looping and processing one at a time, grab all\n>      information upfront.  replace the object name you obtained\n>      in (1) for the ref being updated with the oldrev info you\n>      discovered.\n>\n>  (3) Handle the branch one at a time, but instead of using --all,\n>      use the result of (2) as the \"state of the repository and\n>      its refs before this update\".\n>\n> In other words, you have enough information to reconstruct the\n> view of the repository before your push took place.  After\n> running \"for-each-ref --all\" once upfront, you reconstruct such\n> a view, and after that you do not ever look at where the real\n> refs are in the repository.  That would perhaps alleviate the\n> issue of racing pushers as a side effect.\n\nA couple of issues around the above suggestion.\n\nIncidentally, the above fixes the \"pushing tag and then commit,\nand pushing commit and then tag, would give different results\"\nproblem, as \"what's new\" computation would not consider the\ncommits the updated tag brought in as \"old\" commits anymore.\n\nThe above fixes \"when pushing two branches that share new\ncommits, the shared commits will not be shown on any reports\"\nproblem, but if you literally implement it the way I suggested,\nyou would instead list such shared commits that are new to the\nrepository in reports for both branches.  This may or may not be\na problem, depending on your definition of \"new to the\nrepository\".  On one hand, they *are* new commits introduced by\nthis push (that updates both branches), so in that sense,\nshowing them on reports for both branches may be the right thing\nto do.  On the other hand, you would not see these shared\ncommits on the report for the latter branch if you did the same\npush as two separate \"git push $URL A; git push $URL B\"\ninvocations, so showing them only on the report for one of the\nbranches may be the right thing to do, even when they are pushed\ntogether (by the way, this is one of the reasons that I think\n\"new to the repository\" is wrong.  I do not think its semantics\nis well-defined).\n\nIf you want to implement the latter semantics, then after you\nhandle one branch in step (3), you can replace the internal view\nof \"what the repository looked like before this push\" by\nreplacing the commit associated with the branch you just have\ndealt with (which you replaced with its $oldrev in step (2) in\nthe data read from 'for-each-ref --all') with its $newrev.  Then\nthe shared commits would be part of the old state of the\nrepository when you handle the other ref, and would not show up\nin its report.  If you do this, you would not want to replace\nthe data after handing a tag, in order to preserve the fix for\n\"new tag hides new commits\" problem.\n"},{"id":"37922","messageId":"200703250951.04503.andyparkins@gmail.com","threadId":"7365","inReplyTo":"7vfy7tajiz.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] Convert emailing part of hooks--update to hooks--post-receive","fromName":"Andy Parkins","fromEmail":"andyparkins@gmail.com","sentAt":"2007-03-25T08:51:02Z","receivedAt":"2007-03-25T08:51:02Z","isPatch":true,"sender":{"key":"andyparkins@gmail.com","avatar":null},"body":"On Sunday 2007, March 25, Junio C Hamano wrote:\n\n> I think you can do something along the following line.\n>\n>  (1) You say \"for-each-ref --all\" to get the ref information\n>      that is after update.\n\nWould you mind if I delayed that to a separate patch?  It's a \nsignificant difference from the original update hook, so I think it's \nworth having that change separately documented.\n\n> Sorry, what I meant was:\n>\n> \tgit show -s --pretty=oneline \"$oldrev\"\n\nAh - no problem.  Done.\n\n> Actually it was not quite \"nicely done\".  If taggername has an\n> unusual character then dq pair you hard coded there may not\n> quote the string correctly.\n\nDevious.  Fixed as you suggest.\n\n> > (Andreas Ericsson   54)  prev=$(git describe \"$3^\" | sed\n> > 's/-g.*//')\n>\n> You could describe all the parents and see if they differ.  If\n> they reach different tag, we could see which one is newer.  Or\n> something like that.  In the special (but usual) case of a\n> single parent commit, \"describing all the parents\" is what you\n> are already doing, so it is not any more expensive in the normal\n> case.\n\nAgain; I think I'll fix that one as a separate patch as it is a change \nfrom the behaviour of hooks/update.\n\n> Please do not have cat on either side of a pipe.  That makes a\n> shell script look ... eh, you know the word ;-).\n>\n> \tsed -e 1q \"$GIT_DIR/description\"\n\nDone.  I've taken the liberty of adding a pipe-to-cat, that I hope \nyou'll forgive...\n\n# Output to the terminal in command line mode - if someone wanted to\n# resend an email; they could redirect the output to sendmail themselves\n  generate_email $2 $3 $1 | cat\n\nThis is to force the deactivation of the pager for all the git commands \nthat generate_email calls.\n\n\nAndy\n-- \nDr Andy Parkins, M Eng (hons), MIET\nandyparkins@gmail.com\n"},{"id":"37923","messageId":"7v7it54rfj.fsf@assigned-by-dhcp.cox.net","threadId":"7365","inReplyTo":"200703250951.04503.andyparkins@gmail.com","subject":"Re: [PATCH] Convert emailing part of hooks--update to hooks--post-receive","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-03-25T09:13:04Z","receivedAt":"2007-03-25T09:13:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Andy Parkins <andyparkins@gmail.com> writes:\n\n> On Sunday 2007, March 25, Junio C Hamano wrote:\n> ...\n>> I think you can do something along the following line.\n>>\n>>  (1) You say \"for-each-ref --all\" to get the ref information\n>>      that is after update.\n>\n> Would you mind if I delayed that to a separate patch?\n\nSurely.\n\nAs we discussed, I would prefer a patch to create this as a new\nfile in\n\n\tcontrib/examples/hooks/\n\ndirectory.  A single-liner \n\n\t# see contrib/example/hooks for examples.\n\nin another new file \"templates/hooks--post-receive\" would be\nalso nice.\n\n>> Actually it was not quite \"nicely done\".  If taggername has an\n>> unusual character then dq pair you hard coded there may not\n>> quote the string correctly.\n>\n> Devious.\n\nand funny, isn't it?\n\n>   generate_email $2 $3 $1 | cat\n>\n> This is to force the deactivation of the pager for all the git commands \n> that generate_email calls.\n\nI think you could probably do the same with:\n\n\tPAGER= generate_email \"$2\" \"$3\" \"$1\"\n"},{"id":"38409","messageId":"200703302016.27303.andyparkins@gmail.com","threadId":"7365","inReplyTo":"7v7it54rfj.fsf@assigned-by-dhcp.cox.net","subject":"[PATCH v2] Reimplement emailing part of hooks--update in contrib/hooks/post-receive-email","fromName":"Andy Parkins","fromEmail":"andyparkins@gmail.com","sentAt":"2007-03-30T19:16:26Z","receivedAt":"2007-03-30T19:16:26Z","isPatch":true,"sender":{"key":"andyparkins@gmail.com","avatar":null},"body":"The update hook is no longer the correct place to generate emails; there\nis now the hooks/post-receive script which is run automatically after a\nref has been updated.\n\nThis patch is to make use of that new location, and to address some\nfaults in the old update hook.\n\nThe primary problem in the conversion was that in the update hook, the\nref has not actually been changed, but is about to be.  In the\npost-receive hook the ref has already been updated.  That meant that\nwhere we previously had lines like:\n\n git rev-list --not --all\n\nwould now give the wrong list because \"--all\" in the post-receive hook\nincludes the ref that we are making the email for.  This made it more\ndifficult to show only the new revisions added by this update.\n\nThe solution is not pretty; however it does work and doesn't need any\nchanges to git-rev-list itself.  It also fixes (more accurately: reduces\nthe likelihood of) a nasty race when another update occurs while this\nscript is running.  The solution, in short, looks like this (see the\nsource code for a longer explanation)\n\n git rev-parse --not --all | grep -v $(git rev-parse $refname) |\n git rev-list --pretty --stdin $oldrev..$newrev\n\nThis uses git-rev-parse followed by grep to filter out the revision of\nthe ref in question before it gets to rev-list and inhibits the output\nof itself.  By using $(git rev-parse $revname) rather than $newrev as the\nfilter, it also takes care of the situation where another update to the\nsame ref has been made since $refname was $newrev.\n\nThe second problem that is addressed is that of tags inhibiting the\ncorrect output of an update email.  Consider this, with somebranch and\nsometag pointing at the same revision:\n\n git push origin somebranch\n git push origin sometag\n\nThat would work fine; the push of the branch would generate an email\ncontaining all the new commits introduced by the update, then the push\nof the tag would generate the shortlog formatted tag email.  Now\nconsider:\n\n git push origin sometag\n git push origin somebranch\n\nWhen some branch comes to run its \"--not --all\" line, it will find\nsometag, and filter those commits from the email - leaving nothing.\nThat meant that those commits would not show (in full) on any email.\nThe fix is to not use \"--all\", and instead use \"--branches\" in the\ngit-rev-parse command.\n\nOther changes\n * Lose the monstrous one-giant-script layout and put things in easy to\n   digest functions.  This makes it much easier to find the place you\n   need to change if you wanted to customise the output.  I've also\n   tried to write more verbose comments for the same reason.  The hook\n   script is big, mainly because of all the different cases that it has\n   to handle, so being easy to navigate is important.\n * All uses of \"git-command\" changed to \"git command\", to cope better\n   if a user decided not to install all the hard links to git;\n * Cleaned up some of the English in the email\n * The fact that the receive hook makes the ref available also allows me\n   to use Shawn Pearce's fantastic suggestion that an annotated tag can\n   be parsed with git-for-each-ref.  This removes the potentially\n   non-portable use of \"<<<\" heredocs and the nasty messing around with\n   \"date\" to convert numbers of seconds UTC to a real date\n * Deletions are now caught and notified (briefly)\n * To help with debugging, I've retained the command line mode from the\n   update hook; but made it so that the output is not emailed, it's just\n   printed to the screen.  This could then be redirected if the user\n   wanted\n * Removed the \"Hello\" from the beginning of the email - it's just\n   noise, and no one seriously has their day made happier by \"friendly\"\n   programs\n * The fact that it doesn't rely on repository state as an indicator any\n   more means that it's far more stable in its output; hopefully the\n   same arguments will always generate the same email - even if the\n   repository changes in the future.  This means you can easily recreate\n   an email should you want to.\n * Included Jim Meyering's envelope sender option for the sendmail call\n * The hook is now so big that it was inappropriate to copy it\n   to every repository by keeping it in the templates directory.\n   Instead, I've put a comment saying to look in contrib/hooks, and\n   given an example of calling the script from that template hook.  The\n   advantage of calling the script residing at some fixed location is\n   that if a future package of git included a bug fixed version of the\n   script, that would be picked up automatically, and the user would not\n   have to notice and manually copy the new hook to every repository\n   that uses it.\n\nSigned-off-by: Andy Parkins <andyparkins@gmail.com>\n---\n contrib/hooks/post-receieve-email |  588 +++++++++++++++++++++++++++++++++++++\n templates/hooks--post-receive     |   17 +\n 2 files changed, 605 insertions(+), 0 deletions(-)\n create mode 100644 contrib/hooks/post-receieve-email\n create mode 100644 templates/hooks--post-receive\n\ndiff --git a/contrib/hooks/post-receieve-email b/contrib/hooks/post-receieve-email\nnew file mode 100644\nindex 0000000..6516015\n--- /dev/null\n+++ b/contrib/hooks/post-receieve-email\n@@ -0,0 +1,588 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2007 Andy Parkins\n+#\n+# An example hook script to mail out commit update information.  This hook sends emails\n+# listing new revisions to the repository introduced by the change being reported.  The\n+# rule is that (for branch updates) each commit will appear on one email and one email\n+# only.\n+#\n+# This hook is stored in the contrib/hooks directory.  Your distribution will have put\n+# this somewhere standard.  You should make this script executable then link to it in\n+# the repository you would like to use it in.  For example, on debian the hook is stored\n+# in /usr/share/doc/git-core/contrib/hooks/post-receive-email:\n+#\n+#  chmod a+x post-receive-email\n+#  cd /path/to/your/repository.git\n+#  ln -sf /usr/share/doc/git-core/contrib/hooks/post-receive-email hooks/post-receive\n+#\n+# This hook script assumes it is enabled on the central repository of a project, with\n+# all users pushing only to it and not between each other.  It will still work if you\n+# don't operate in that style, but it would become possible for the email to be from\n+# someone other than the person doing the push.\n+#\n+# Config\n+# ------\n+# hooks.mailinglist\n+#   This is the list that all pushes will go to; leave it blank to not send\n+#   emails for every ref update.\n+# hooks.announcelist\n+#   This is the list that all pushes of annotated tags will go to.  Leave it\n+#   blank to default to the mailinglist field.  The announce emails lists the\n+#   short log summary of the changes since the last annotated tag.\n+# hook.envelopesender\n+#   If set then the -f option is passed to sendmail to allow the envelope sender\n+#   address to be set\n+#\n+# Notes\n+# -----\n+# All emails have their subjects prefixed with \"[SCM]\" to aid filtering.\n+# All emails include the headers \"X-Git-Refname\", \"X-Git-Oldrev\",\n+# \"X-Git-Newrev\", and \"X-Git-Reftype\" to enable fine tuned filtering and\n+# give information for debugging.\n+#\n+\n+# ---------------------------- Functions\n+\n+#\n+# Top level email generation function.  This decides what type of update\n+# this is and calls the appropriate body-generation routine after outputting\n+# the common header\n+#\n+# Note this function doesn't actually generate any email output, that is taken\n+# care of by the functions it calls:\n+#  - generate_email_header\n+#  - generate_create_XXXX_email\n+#  - generate_update_XXXX_email\n+#  - generate_delete_XXXX_email\n+#  - generate_email_footer\n+#\n+generate_email()\n+{\n+\t# --- Arguments\n+\toldrev=$(git rev-parse $1)\n+\tnewrev=$(git rev-parse $2)\n+\trefname=\"$3\"\n+\n+\t# --- Interpret\n+\t# 0000->1234 (create)\n+\t# 1234->2345 (update)\n+\t# 2345->0000 (delete)\n+\tif expr \"$oldrev\" : '0*$' >/dev/null\n+\tthen\n+\t\tchange_type=\"create\"\n+\telse\n+\t\tif expr \"$newrev\" : '0*$' >/dev/null\n+\t\tthen\n+\t\t\tchange_type=\"delete\"\n+\t\telse\n+\t\t\tchange_type=\"update\"\n+\t\tfi\n+\tfi\n+\n+\t# --- Get the revision types\n+\tnewrev_type=$(git cat-file -t $newrev 2> /dev/null)\n+\toldrev_type=$(git cat-file -t \"$oldrev\" 2> /dev/null)\n+\tcase \"$change_type\" in\n+\tcreate|update)\n+\t\trev=\"$newrev\"\n+\t\trev_type=\"$newrev_type\"\n+\t\t;;\n+\tdelete)\n+\t\trev=\"$oldrev\"\n+\t\trev_type=\"$oldrev_type\"\n+\t\t;;\n+\tesac\n+\n+\t# The revision type tells us what type the commit is, combined with\n+\t# the location of the ref we can decide between\n+\t#  - working branch\n+\t#  - tracking branch\n+\t#  - unannoted tag\n+\t#  - annotated tag\n+\tcase \"$refname\",\"$rev_type\" in\n+\t\trefs/tags/*,commit)\n+\t\t\t# un-annotated tag\n+\t\t\trefname_type=\"tag\"\n+\t\t\tshort_refname=${refname##refs/tags/}\n+\t\t\t;;\n+\t\trefs/tags/*,tag)\n+\t\t\t# annotated tag\n+\t\t\trefname_type=\"annotated tag\"\n+\t\t\tshort_refname=${refname##refs/tags/}\n+\t\t\t# change recipients\n+\t\t\tif [ -n \"$announcerecipients\" ]; then\n+\t\t\t\trecipients=\"$announcerecipients\"\n+\t\t\tfi\n+\t\t\t;;\n+\t\trefs/heads/*,commit)\n+\t\t\t# branch\n+\t\t\trefname_type=\"branch\"\n+\t\t\tshort_refname=${refname##refs/heads/}\n+\t\t\t;;\n+\t\trefs/remotes/*,commit)\n+\t\t\t# tracking branch\n+\t\t\trefname_type=\"tracking branch\"\n+\t\t\tshort_refname=${refname##refs/remotes/}\n+\t\t\techo >&2 \"*** Push-update of tracking branch, $refname\"\n+\t\t\techo >&2 \"***  - no email generated.\"\n+\t\t\texit 0\n+\t\t\t;;\n+\t\t*)\n+\t\t\t# Anything else (is there anything else?)\n+\t\t\techo >&2 \"*** Unknown type of update to $refname ($rev_type)\"\n+\t\t\techo >&2 \"***  - no email generated\"\n+\t\t\texit 1\n+\t\t\t;;\n+\tesac\n+\n+\t# Check if we've got anyone to send to\n+\tif [ -z \"$recipients\" ]; then\n+\t\techo >&2 \"*** hooks.recipients is not set so no email will be sent\"\n+\t\techo >&2 \"*** for $refname update $oldrev->$newrev\"\n+\t\texit 0\n+\tfi\n+\n+\t# Email parameters\n+\t# The committer will be obtained from the latest existing rev; so\n+\t# for a deletion it will be the oldrev, for the others, then newrev\n+\tcommitter=$(git show --pretty=full -s $rev | sed -ne \"s/^Commit: //p\" |\n+\t\tsed -ne 's/\\(.*\\) </\"\\1\" </p')\n+\t# The email subject will contain the best description of the ref\n+\t# that we can build from the parameters\n+\tdescribe=$(git describe $rev 2>/dev/null)\n+\tif [ -z \"$describe\" ]; then\n+\t\tdescribe=$rev\n+\tfi\n+\n+\tgenerate_email_header\n+\n+\t# Call the correct body generation function\n+\tfn_name=general\n+\tcase \"$refname_type\" in\n+\t\"tracking branch\"|branch)\n+\t\tfn_name=branch\n+\t\t;;\n+\t\"annotated tag\")\n+\t\tfn_name=atag\n+\t\t;;\n+\tesac\n+\tgenerate_${change_type}_${fn_name}_email\n+\n+\tgenerate_email_footer\n+}\n+\n+generate_email_header()\n+{\n+\t# --- Email (all stdout will be the email)\n+\t# Generate header\n+\tcat <<-EOF\n+\tFrom: $committer\n+\tTo: $recipients\n+\tSubject: ${EMAILPREFIX}$projectdesc $refname_type, $short_refname, ${change_type}d. $describe\n+\tX-Git-Refname: $refname\n+\tX-Git-Reftype: $refname_type\n+\tX-Git-Oldrev: $oldrev\n+\tX-Git-Newrev: $newrev\n+\n+\tThis is an automated email from the git hooks/post-receive script. It was\n+\tgenerated because a ref change was pushed to the repository containing\n+\tthe project \"$projectdesc\".\n+\n+\tThe $refname_type, $short_refname has been ${change_type}d\n+\tEOF\n+}\n+\n+generate_email_footer()\n+{\n+\tcat <<-EOF\n+\n+\n+\thooks/post-receive\n+\t-- \n+\t$projectdesc\n+\tEOF\n+}\n+\n+# --------------- Branches\n+\n+#\n+# Called for the creation of a branch\n+#\n+generate_create_branch_email()\n+{\n+\t# This is a new branch and so oldrev is not valid\n+\techo \"        at  $newrev ($newrev_type)\"\n+\techo \"\"\n+\n+\techo $LOGBEGIN\n+\t# This shows all log entries that are not already covered by\n+\t# another ref - i.e. commits that are now accessible from this\n+\t# ref that were previously not accessible (see generate_update_branch_email\n+\t# for the explanation of this command)\n+\tgit rev-parse --not --branches | grep -v $(git rev-parse $refname) |\n+\tgit rev-list --pretty --stdin $newrev\n+\techo $LOGEND\n+}\n+\n+#\n+# Called for the change of a pre-existing branch\n+#\n+generate_update_branch_email()\n+{\n+\t# Consider this:\n+\t#   1 --- 2 --- O --- X --- 3 --- 4 --- N\n+\t#\n+\t# O is $oldrev for $refname\n+\t# N is $newrev for $refname\n+\t# X is a revision pointed to by some other ref, for which we may\n+\t#   assume that an email has already been generated.\n+\t# In this case we want to issue an email containing only revisions\n+\t# 3, 4, and N.  Given (almost) by\n+\t#\n+\t#  git-rev-list N ^O --not --all\n+\t#\n+\t# The reason for the \"almost\", is that the \"--not --all\" will take\n+\t# precedence over the \"N\", and effectively will translate to\n+\t#\n+\t#  git-rev-list N ^O ^X ^N\n+\t#\n+\t# So, we need to build up the list more carefully.  git-rev-parse will\n+\t# generate a list of revs that may be fed into git-rev-list.  We can get\n+\t# it to make the \"--not --all\" part and then filter out the \"^N\" with:\n+\t#\n+\t#  git-rev-parse --not --all | grep -v N\n+\t#\n+\t# Then, using the --stdin switch to git-rev-list we have effectively\n+\t# manufactured\n+\t#\n+\t#  git-rev-list N ^O ^X\n+\t#\n+\t# This leaves a problem when someone else updates the repository\n+\t# while this script is running.  Their new value of the ref we're working\n+\t# on would be included in the \"--not --all\" output; and as our $newrev\n+\t# would be an ancestor of that commit, it would exclude all of our\n+\t# commits.  What we really want is to exclude the current value of\n+\t# $refname from the --not list, rather than N itself.  So:\n+\t#\n+\t#  git-rev-parse --not --all | grep -v $(git-rev-parse $refname)\n+\t#\n+\t# Get's us to something pretty safe (apart from the small time between\n+\t# refname being read, and git-rev-parse running - for that, I give up)\n+\t#\n+\t#\n+\t# Next problem, consider this:\n+\t#   * --- B --- * --- O ($oldrev)\n+\t#          \\\n+\t#           * --- X --- * --- N ($newrev)\n+\t#\n+\t# That is to say, there is no guarantee that oldrev is a strict subset of\n+\t# newrev (it would have required a --force, but that's allowed).  So, we\n+\t# can't simply say rev-list $oldrev..$newrev.  Instead we find the common\n+\t# base of the two revs and list from there.\n+\t#\n+\t# As above, we need to take into account the presence of X; if another\n+\t# branch is already in the repository and points at some of the revisions\n+\t# that we are about to output - we don't want them.  The solution is as\n+\t# before: git-rev-parse output filtered.\n+\t#\n+\t# Finally, tags:\n+\t#   1 --- 2 --- O --- T --- 3 --- 4 --- N\n+\t#\n+\t# Tags pushed into the repository generate nice shortlog emails that\n+\t# summarise the commits between them and the previous tag.  However,\n+\t# those emails don't include the full commit messages that we output\n+\t# for a branch update.  Therefore we still want to output revisions\n+\t# that have been output on a tag email.\n+\t#\n+\t# Luckily, git-rev-parse includes just the tool.  Instead of using \"--all\"\n+\t# we use \"--branches\"; this has the added benefit that \"remotes/\" will\n+\t# be ignored as well.\n+\n+\t# List all of the revisions that were removed by this update, in a fast forward\n+\t# update, this list will be empty, because rev-list O ^N is empty.  For a non\n+\t# fast forward, O ^N is the list of removed revisions\n+\tfastforward=\"\"\n+\trev=\"\"\n+\tfor rev in $(git rev-list $newrev..$oldrev)\n+\tdo\n+\t\trevtype=$(git cat-file -t \"$rev\")\n+\t\techo \"  discards  $rev ($revtype)\"\n+\tdone\n+\tif [ -z \"$rev\" ]; then\n+\t\tfast_forward=1\n+\tfi\n+\n+\t# List all the revisions from baserev to newrev in a kind of\n+\t# \"table-of-contents\"; note this list can include revisions that have\n+\t# already had notification emails and is present to show the full detail\n+\t# of the change from rolling back the old revision to the base revision and\n+\t# then forward to the new revision\n+\tfor rev in $(git rev-list $oldrev..$newrev)\n+\tdo\n+\t\trevtype=$(git cat-file -t \"$rev\")\n+\t\techo \"       via  $rev ($revtype)\"\n+\tdone\n+\n+\tif [ -z \"$fastforward\" ]; then\n+\t\techo \"      from  $oldrev ($oldrev_type)\"\n+\telse\n+\t\techo \"\"\n+\t\techo \"This update added new revisions after undoing old revisions.  That is to\"\n+\t\techo \"say, the old revision is not a strict subset of the new revision.  This\"\n+\t\techo \"situation occurs when you --force push a change and generate a\"\n+\t\techo \"repository containing something like this:\"\n+\t\techo \"\"\n+\t\techo \" * -- * -- B -- O -- O -- O ($oldrev)\"\n+\t\techo \"            \\\\\"\n+\t\techo \"             N -- N -- N ($newrev)\"\n+\t\techo \"\"\n+\t\techo \"When this happens we assume that you've already had alert emails for all\"\n+\t\techo \"of the O revisions, and so we here report only the revisions in the N\"\n+\t\techo \"branch from the common base, B.\"\n+\tfi\n+\n+\techo \"\"\n+\techo \"Those revisions listed above that are new to this repository have\"\n+\techo \"not appeared on any other notification email; so we list those\"\n+\techo \"revisions in full, below.\"\n+\n+\techo \"\"\n+\techo $LOGBEGIN\n+\tgit rev-parse --not --branches | grep -v $(git rev-parse $refname) |\n+\tgit rev-list --pretty --stdin $oldrev..$newrev\n+\n+\t# XXX: Need a way of detecting whether git rev-list actually outputted\n+\t# anything, so that we can issue a \"no new revisions added by this\n+\t# update\" message\n+\n+\techo $LOGEND\n+\n+\t# The diffstat is shown from the old revision to the new revision.  This\n+\t# is to show the truth of what happened in this change.  There's no point\n+\t# showing the stat from the base to the new revision because the base\n+\t# is effectively a random revision at this point - the user will be\n+\t# interested in what this revision changed - including the undoing of\n+\t# previous revisions in the case of non-fast forward updates.\n+\techo \"\"\n+\techo \"Summary of changes:\"\n+\tgit diff-tree --stat --summary --find-copies-harder $oldrev..$newrev\n+}\n+\n+#\n+# Called for the deletion of a branch\n+#\n+generate_delete_branch_email()\n+{\n+\techo \"       was  $oldrev\"\n+\techo \"\"\n+\techo $LOGEND\n+\tgit show -s --pretty=oneline $oldrev\n+\techo $LOGEND\n+}\n+\n+# --------------- Annotated tags\n+\n+#\n+# Called for the creation of an annotated tag\n+#\n+generate_create_atag_email()\n+{\n+\techo \"        at  $newrev ($newrev_type)\"\n+\n+\tgenerate_atag_email\n+}\n+\n+#\n+# Called for the update of an annotated tag (this is probably a rare event\n+# and may not even be allowed)\n+#\n+generate_update_atag_email()\n+{\n+\techo \"        to  $newrev ($newrev_type)\"\n+\techo \"      from  $oldrev (which is now obsolete)\"\n+\n+\tgenerate_atag_email\n+}\n+\n+#\n+# Called when an annotated tag is created or changed\n+#\n+generate_atag_email()\n+{\n+\t# Use git-for-each-ref to pull out the individual fields from the tag\n+\teval $(git for-each-ref --shell --format='\n+\ttagobject=%(*objectname)\n+\ttagtype=%(*objecttype)\n+\ttagger=%(taggername)\n+\ttagged=%(taggerdate)' $refname\n+\t)\n+\n+\techo \"   tagging  $tagobject ($tagtype)\"\n+\tcase \"$tagtype\" in\n+\tcommit)\n+\t\t# If the tagged object is a commit, then we assume this is a\n+\t\t# release, and so we calculate which tag this tag is replacing\n+\t\tprevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null)\n+\n+\t\tif [ -n \"$prevtag\" ]; then\n+\t\t\techo \"  replaces  $prevtag\"\n+\t\tfi\n+\t\t;;\n+\t*)\n+\t\techo \"    length  $(git cat-file -s $tagobject) bytes\"\n+\t\t;;\n+\tesac\n+\techo \" tagged by  $tagger\"\n+\techo \"        on  $tagged\"\n+\n+\techo \"\"\n+\techo $LOGBEGIN\n+\n+\t# Show the content of the tag message; this might contain a change log\n+\t# or release notes so is worth displaying.\n+\tgit cat-file tag $newrev | sed -e '1,/^$/d'\n+\n+\techo \"\"\n+\tcase \"$tagtype\" in\n+\tcommit)\n+\t\t# Only commit tags make sense to have rev-list operations performed\n+\t\t# on them\n+\t\tif [ -n \"$prevtag\" ]; then\n+\t\t\t# Show changes since the previous release\n+\t\t\tgit rev-list --pretty=short \"$prevtag..$newrev\" | git shortlog\n+\t\telse\n+\t\t\t# No previous tag, show all the changes since time began\n+\t\t\tgit rev-list --pretty=short $newrev | git shortlog\n+\t\tfi\n+\t\t;;\n+\t*)\n+\t\t# XXX: Is there anything useful we can do for non-commit objects?\n+\t\t;;\n+\tesac\n+\n+\techo $LOGEND\n+}\n+\n+#\n+# Called for the deletion of an annotated tag\n+#\n+generate_delete_atag_email()\n+{\n+\techo \"       was  $oldrev\"\n+\techo \"\"\n+\techo $LOGEND\n+\tgit show -s --pretty=oneline $oldrev\n+\techo $LOGEND\n+}\n+\n+# --------------- General references\n+\n+#\n+# Called when any other type of reference is created (most likely a\n+# non-annotated tag)\n+#\n+generate_create_general_email()\n+{\n+\techo \"        at  $newrev ($newrev_type)\"\n+\n+\tgenerate_general_email\n+}\n+\n+#\n+# Called when any other type of reference is updated (most likely a\n+# non-annotated tag)\n+#\n+generate_update_general_email()\n+{\n+\techo \"        to  $newrev ($newrev_type)\"\n+\techo \"      from  $oldrev\"\n+\n+\tgenerate_general_email\n+}\n+\n+#\n+# Called for creation or update of any other type of reference\n+#\n+generate_general_email()\n+{\n+\t# Unannotated tags are more about marking a point than releasing a version;\n+\t# therefore we don't do the shortlog summary that we do for annotated tags\n+\t# above - we simply show that the point has been marked, and print the log\n+\t# message for the marked point for reference purposes\n+\t#\n+\t# Note this section also catches any other reference type (although there\n+\t# aren't any) and deals with them in the same way.\n+\n+\techo \"\"\n+\tif [ \"$newrev_type\" = \"commit\" ]; then\n+\t\techo $LOGBEGIN\n+\t\tgit show --no-color --root -s $newrev\n+\t\techo $LOGEND\n+\telse\n+\t\t# What can we do here?  The tag marks an object that is not a commit,\n+\t\t# so there is no log for us to display.  It's probably not wise to\n+\t\t# output git-cat-file as it could be a binary blob.  We'll just say how\n+\t\t# big it is\n+\t\techo \"$newrev is a $newrev_type, and is $(git cat-file -s $newrev) bytes long.\"\n+\tfi\n+}\n+\n+#\n+# Called for the deletion of any other type of reference\n+#\n+generate_delete_general_email()\n+{\n+\techo \"       was  $oldrev\"\n+\techo \"\"\n+\techo $LOGEND\n+\tgit show -s --pretty=oneline $oldrev\n+\techo $LOGEND\n+}\n+\n+# ---------------------------- main()\n+\n+# --- Constants\n+EMAILPREFIX=\"[SCM] \"\n+LOGBEGIN=\"- Log -----------------------------------------------------------------\"\n+LOGEND=\"-----------------------------------------------------------------------\"\n+\n+# --- Config\n+# Set GIT_DIR either from the working directory, or from the environment\n+# variable.\n+GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)\n+if [ -z \"$GIT_DIR\" ]; then\n+\techo >&2 \"fatal: post-receive: GIT_DIR not set\"\n+\texit 1\n+fi\n+\n+projectdesc=$(sed -e '1p' \"$GIT_DIR/description\")\n+# Check if the description is unchanged from it's default, and shorten it to a\n+# more manageable length if it is\n+if expr \"$projectdesc\" : \"Unnamed repository.*$\" >/dev/null\n+then\n+\tprojectdesc=\"UNNAMED PROJECT\"\n+fi\n+\n+recipients=$(git repo-config hooks.mailinglist)\n+announcerecipients=$(git repo-config hooks.announcelist)\n+envelopesender=$(git-repo-config hooks.envelopesender)\n+\n+# --- Main loop\n+# Allow dual mode: run from the command line just like the update hook, or if\n+# no arguments are given then run as a hook script\n+if [ -n \"$1\" -a -n \"$2\" -a -n \"$3\" ]; then\n+\t# Output to the terminal in command line mode - if someone wanted to\n+\t# resend an email; they could redirect the output to sendmail themselves\n+\tPAGER= generate_email $2 $3 $1\n+else\n+\tif [ -n \"$envelopesender\" ]; then\n+\t\tenvelopesender=\"-f '$envelopesender'\"\n+\tfi\n+\n+\twhile read oldrev newrev refname\n+\tdo\n+\t\tgenerate_email $oldrev $newrev $refname |\n+\t\t/usr/sbin/sendmail -t $envelopesender\n+\tdone\n+fi\ndiff --git a/templates/hooks--post-receive b/templates/hooks--post-receive\nnew file mode 100644\nindex 0000000..190de26\n--- /dev/null\n+++ b/templates/hooks--post-receive\n@@ -0,0 +1,17 @@\n+#!/bin/sh\n+#\n+# An example hook script for the post-receive event\n+#\n+# This script is run after receive-pack has accepted a pack and the\n+# repository has been updated.  It is passed arguments in through stdin\n+# in the form\n+#  <oldrev> <newrev> <refname>\n+# For example:\n+#  aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master\n+#\n+# see contrib/hooks/ for an sample, or uncomment the next line (on debian)\n+#\n+\n+\n+#. /usr/share/doc/git-core/contrib/hooks/post-receive-email\n+\n-- \n1.5.1.rc1.27.g1d848\n"},{"id":"38426","messageId":"eulgu1$u2m$1@sea.gmane.org","threadId":"7365","inReplyTo":"200703302016.27303.andyparkins@gmail.com","subject":"Re: [PATCH v2] Reimplement emailing part of hooks--update in contrib/hooks/post-receive-email","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2007-03-31T11:36:37Z","receivedAt":"2007-03-31T11:36:37Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Andy Parkins wrote:\n\n>  git rev-list --not --all\n> \n> would now give the wrong list because \"--all\" in the post-receive hook\n> includes the ref that we are making the email for.  This made it more\n> difficult to show only the new revisions added by this update.\n\nWhat about \"git rev-list --not --all ^included\" ?\n-- \nJakub Narebski\nWarsaw, Poland\nShadeHawk on #git\n"}]}