From 95aca0f365dd6aa258fb71c447788120a0e3a8f2 Mon Sep 17 00:00:00 2001 From: "Kyle J. McKay" Date: Fri, 3 Jun 2016 20:22:27 -0700 Subject: [PATCH] mail.sh: various minor updates Show the diff for the first revision on a new branch. Improve rename detection in diffs (add the -B option). Show a summary of changes for new branches just like updated branches get. Include a stat and summary for each individual diff that's not a merge commit. Limit the stat width to 72 for better email compatibility. Show diffs for "evil" merges (add the --cc option). Abbreviate merge commit hashes a bit to avoid overflowing email lines so much. Avoid error message if .git/description is not present. Send output to stdout instead of mailing with --stdout as the first argument. Update a few comments to match the actual behavior. Show dates using rfc2822 format since output is a mail message. Fully peel tag objects when handling them. Include embedded tag name in tag emails. Produce more concise URLs and move them into the commit header area. Add URLs for non-branch create/update events. Force rev-parse to interpret its arguments as revisions. Support hooks.summaryonly boolean to suppress actual diff itself. Signed-off-by: Kyle J. McKay --- taskd/mail.sh | 122 +++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 95 insertions(+), 27 deletions(-) diff --git a/taskd/mail.sh b/taskd/mail.sh index cbd7bfb..c71c5e1 100755 --- a/taskd/mail.sh +++ b/taskd/mail.sh @@ -43,6 +43,9 @@ # hooks.mailinglist # This is the list that all pushes will go to; leave it blank to not send # emails for every ref update. +# hooks.summaryonly +# If true do not include the actual diff when showing new commits (the +# summary information will still be shown). Default is false. # hooks.announcelist # This is the list that all pushes of annotated tags will go to. Leave it # blank to default to the mailinglist field. The announce emails lists @@ -88,8 +91,10 @@ echol() prep_for_email() { # --- Arguments - oldrev=$(git rev-parse $1) - newrev=$(git rev-parse $2) + oldrev=$(git rev-parse --revs-only "$1" --) + newrev=$(git rev-parse --revs-only "$2" --) + scratch="${newrev#????????????????}" + newrev16="${newrev%$scratch}" refname="$3" # --- Interpret @@ -293,6 +298,14 @@ generate_create_branch_email() echol $LOGBEGIN show_new_revisions echol $LOGEND + + # The diffstat is shown from an empty tree to the new revision. + # This is to show the truth of what happened in this change. + # Note that since Git 1.5.5 the empty tree object is ALWAYS available + # whether or not it's actually present in the repository. + echol "" + echol "Summary of changes:" + git diff-tree --no-color --stat=72 --summary -B --find-copies-harder 4b825dc642cb6eb9a060e54bf8d69288fbee4904 $newrev } # @@ -471,7 +484,7 @@ generate_update_branch_email() # non-fast-forward updates. echol "" echol "Summary of changes:" - git diff-tree --no-color --stat --summary --find-copies-harder $oldrev..$newrev + git diff-tree --no-color --stat=72 --summary -B --find-copies-harder $oldrev..$newrev } # @@ -482,7 +495,7 @@ generate_delete_branch_email() echol " was $oldrev" echol "" echol $LOGBEGIN - git diff-tree --no-color -s --always --encoding=UTF-8 --pretty=oneline $oldrev + git diff-tree --no-color --date=$datefmt -s --abbrev-commit --abbrev=$habbr --always --encoding=UTF-8 --pretty=oneline $oldrev echol $LOGEND } @@ -516,14 +529,26 @@ generate_update_atag_email() generate_atag_email() { # Use git for-each-ref to pull out the individual fields from the - # tag - eval $(git for-each-ref --shell --format=' - tagobject=%(*objectname) - tagtype=%(*objecttype) + # tag and git cat-file --batch-check to peel the tag + tagname= + tagger= + tagged= + eval $(git for-each-ref --shell --format=" + tagname=%(tag) tagger=%(taggername) - tagged=%(taggerdate)' $refname + tagged=%(taggerdate:$datefmt)" $refname ) + tagobject= + tagtype= + info="$(echo "$newrev^{}" | git cat-file --batch-check=' + tagobject=%(objectname) + tagtype=%(objecttype)' + )" + case "$info" in *type=*) eval "$info";; *) + tagtype=missing + esac + echol " tag $tagname" echol " tagging $tagobject ($tagtype)" case "$tagtype" in commit) @@ -538,11 +563,12 @@ generate_atag_email() fi ;; *) - echol " length $(git cat-file -s $tagobject) bytes" + echol " length $(git cat-file -s "$tagobject" 2>/dev/null) bytes" ;; esac echol " tagged by $tagger" echol " on $tagged" + echol " URL <$projurl/$newrev16>" echol "" echol $LOGBEGIN @@ -582,7 +608,7 @@ generate_delete_atag_email() echol " was $oldrev" echol "" echol $LOGBEGIN - git diff-tree --no-color -s --always --encoding=UTF-8 --pretty=oneline $oldrev + git diff-tree --no-color --date=$datefmt -s --abbrev-commit --abbrev=$habbr --always --encoding=UTF-8 --pretty=oneline $oldrev echol $LOGEND } @@ -628,7 +654,7 @@ generate_general_email() echol "" if [ "$newrev_type" = "commit" ]; then echol $LOGBEGIN - git diff-tree --no-color --root -s --always --encoding=UTF-8 --pretty=medium $newrev + git diff-tree --no-color --date=$datefmt --root -s --always --encoding=UTF-8 --format="$pfmt1$projurlesc/$newrev16$pfmt2" $newrev echol $LOGEND else # What can we do here? The tag marks an object that is not @@ -647,7 +673,7 @@ generate_delete_general_email() echol " was $oldrev" echol "" echol $LOGBEGIN - git diff-tree --no-color -s --always --encoding=UTF-8 --pretty=oneline $oldrev + git diff-tree --no-color --date=$datefmt -s --abbrev-commit --abbrev=$habbr --always --encoding=UTF-8 --pretty=oneline $oldrev echol $LOGEND } @@ -683,15 +709,29 @@ show_new_revisions() # then # git rev-list --pretty --stdin $revspec # else - git rev-list --stdin $revspec | - while read onerev + git --no-pager log --stdin --no-color --format=tformat:"%H %p" $revspec | + LC_ALL=C awk '{print $1 " " NF-1}' | + while read onerev pcnt do if [ -n "$custom_showrev" ]; then eval $(printf "$custom_showrev" $onerev) else - echol "$cfg_gitweburl/$projectname/commit/$onerev" - echo - git diff-tree --no-color -p --always --encoding=UTF-8 --pretty=medium -C $onerev + if [ ${summaryonly:-false} = false ]; then + if [ ${pcnt:-1} -gt 1 ]; then + opts="-p --cc" + else + opts="-p --stat=72 --summary" + fi + else + if [ ${pcnt:-1} -gt 1 ]; then + opts="-s" + else + opts="--stat=72 --summary" + fi + fi + scratch="${onerev#????????????????}" + onerev16="${onerev%$scratch}" + git diff-tree --no-color --date=$datefmt $opts --always --encoding=UTF-8 --format="$pfmt1$projurlesc/$onerev16$pfmt2" --abbrev=$habbr -B -C --root $onerev echo fi done @@ -714,6 +754,12 @@ size_limit() } +sendmail_to_stdout() +{ + cat +} + + send_mail() { if [ -n "$cfg_sender" ]; then @@ -732,6 +778,16 @@ LOGEND="-----------------------------------------------------------------------" # --- Config . @basedir@/shlib.sh +# Git formatting to use +datefmt=rfc2822 +habbr=12 + +# This is --pretty=medium in two parts with a URL: line added +pfmt1='format:commit %H%nAuthor: %an <%ae>%nDate: %ad%nURL: <' +pfmt2='>%n%n%w(0,4,4)%s%n%n%b' + +git_add_config core.abbrev=$habbr + # Set GIT_DIR either from the working directory, or from the environment # variable. GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) @@ -740,28 +796,37 @@ if [ -z "$GIT_DIR" ]; then exit 1 fi -projectdesc=$(LC_ALL=C sed -ne '1p' "$GIT_DIR/description") +projectdesc= +! [ -s "$GIT_DIR/description" ] || projectdesc=$(LC_ALL=C sed -ne '1p' "$GIT_DIR/description") # Check if the description is unchanged from it's default, and shorten it to # a more manageable length if it is -if LC_ALL=C expr "$projectdesc" : "Unnamed repository.*$" >/dev/null +if [ -z "$projectdesc" ] || LC_ALL=C expr "$projectdesc" : "Unnamed repository.*$" >/dev/null then projectdesc="UNNAMED PROJECT" fi -projectname="$4" +# If --stdout is first argument send all output there instead of mailing +if [ "$1" = "--stdout" ]; then + shift + cfg_sendmail_bin="sendmail_to_stdout" +fi + +projectname="${4%.git}" if [ -n "$projectname" ]; then projectname="$projectname.git" - projectboth="$projectname (\"$projectdesc\")" projectowner="$(config_get owner)" else - projectname="$projectdesc" - projectboth="$projectdesc" + projectname="$(basename "$PWD")" fi +projectboth="$projectname (\"$projectdesc\")" +projurl="$cfg_gitweburl/$projectname" +projurlesc="$(printf '%s\n' "$projurl" | sed -e 's/%/%%/g')" emailsender="$5" emailextraheader="$6" recipients=$(git config hooks.mailinglist) +summaryonly=$(git config --bool hooks.summaryonly 2>/dev/null || :) announcerecipients=$(git config hooks.announcelist) envelopesender=$(git config hooks.envelopesender) emailprefix=$(git config hooks.emailprefix || echol "[$cfg_name] ") @@ -770,14 +835,17 @@ custom_showrev=$(git config hooks.showrev) # --- Main loop # Allow dual mode: run from the command line just like the update hook, or # if no arguments are given then run as a hook script +# If --stdout is first argument send all output there instead (handled above) +# Optional 4th (projectname), 5th (sender) and 6th (extra header) arguments if [ -n "$1" -a -n "$2" -a -n "$3" ]; then - # Output to the terminal in command line mode - if someone wanted to - # resend an email; they could redirect the output to sendmail - # themselves + # Handle a single update rather than a batch on stdin + # Output will still be sent to sendmail unless --stdout is used + # Same 3 args as update hook ( ) if prep_for_email $2 $3 $1; then PAGER= generate_email | size_limit $((256*1024)) | send_mail fi else + # Same input as pre-receive hook (each line is ) while read oldrev newrev refname do prep_for_email $oldrev $newrev $refname || continue -- 2.11.4.GIT