#!/bin/sh # # Copyright (c) 2005 Junio C Hamano # USAGE='[-n | -k] [-o | --stdout] [--signoff] [--check] [--mbox] [--diff-options] []' LONG_USAGE='Prepare each commit with its patch since our-head forked from upstream, one file per patch, for e-mail submission. Each output file is numbered sequentially from 1, and uses the first line of the commit message (massaged for pathname safety) as the filename. There are three output modes. By default, output files are created in the current working directory; when -o is specified, they are created in that directory instead; when --stdout is specified, they are spit on standard output, and can be piped to git-am. When -n is specified, instead of "[PATCH] Subject", the first line is formatted as "[PATCH N/M] Subject", unless you have only one patch. When --mbox is specified, the output is formatted to resemble UNIX mailbox format, and can be concatenated together for processing with applymbox.' . git-sh-setup # Force diff to run in C locale. LANG=C LC_ALL=C export LANG LC_ALL diff_opts= LF=' ' outdir=./ while case "$#" in 0) break;; esac do case "$1" in -c|--c|--ch|--che|--chec|--check) check=t ;; -a|--a|--au|--aut|--auth|--autho|--author|\ -d|--d|--da|--dat|--date|\ -m|--m|--mb|--mbo|--mbox) # now noop ;; -k|--k|--ke|--kee|--keep|--keep-|--keep-s|--keep-su|--keep-sub|\ --keep-subj|--keep-subje|--keep-subjec|--keep-subject) keep_subject=t ;; -n|--n|--nu|--num|--numb|--numbe|--number|--numbere|--numbered) numbered=t ;; -s|--s|--si|--sig|--sign|--signo|--signof|--signoff) signoff=t ;; --st|--std|--stdo|--stdou|--stdout) stdout=t mbox=t date=t author=t ;; -o=*|--o=*|--ou=*|--out=*|--outp=*|--outpu=*|--output=*|--output-=*|\ --output-d=*|--output-di=*|--output-dir=*|--output-dire=*|\ --output-direc=*|--output-direct=*|--output-directo=*|\ --output-director=*|--output-directory=*) outdir=`expr "$1" : '-[^=]*=\(.*\)'` ;; -o|--o|--ou|--out|--outp|--outpu|--output|--output-|--output-d|\ --output-di|--output-dir|--output-dire|--output-direc|--output-direct|\ --output-directo|--output-director|--output-directory) case "$#" in 1) usage ;; esac; shift outdir="$1" ;; -h|--h|--he|--hel|--help) usage ;; -*' '* | -*"$LF"* | -*' '*) # Ignore diff option that has whitespace for now. ;; -*) diff_opts="$diff_opts$1 " ;; *) break ;; esac shift done case "$keep_subject$numbered" in tt) die '--keep-subject and --numbered are incompatible.' ;; esac tmp=.tmp-series$$ trap 'rm -f $tmp-*' 0 1 2 3 15 series=$tmp-series commsg=$tmp-commsg filelist=$tmp-files # Backward compatible argument parsing hack. # # Historically, we supported: # 1. "rev1" is equivalent to "rev1..HEAD" # 2. "rev1..rev2" # 3. "rev1" "rev2 is equivalent to "rev1..rev2" # # We want to take a sequence of "rev1..rev2" in general. # Also, "rev1.." should mean "rev1..HEAD"; git-diff users are # familiar with that syntax. case "$#,$1$2" in 1,?*..?*) # single "rev1..rev2" ;; 1,?*..) # single "rev1.." should mean "rev1..HEAD" set x "$1"HEAD shift ;; 1,*) # single rev1 set x "$1..HEAD" shift ;; 2,?*..?*) # not traditional "rev1" "rev2" ;; 2,*) set x "$1..$2" shift ;; esac # Now we have what we want in $@ for revpair do case "$revpair" in ?*..?*) rev1=`expr "$revpair" : '\(.*\)\.\.'` rev2=`expr "$revpair" : '.*\.\.\(.*\)'` ;; *) rev1="$revpair^" rev2="$revpair" ;; esac git-rev-parse --verify "$rev1^0" >/dev/null 2>&1 || die "Not a valid rev $rev1 ($revpair)" git-rev-parse --verify "$rev2^0" >/dev/null 2>&1 || die "Not a valid rev $rev2 ($revpair)" git-cherry -v "$rev1" "$rev2" | while read sign rev comment do case "$sign" in '-') echo >&2 "Merged already: $comment" ;; *) echo $rev ;; esac done done >$series me=`git-var GIT_AUTHOR_IDENT | sed -e 's/>.*/>/'` case "$outdir" in */) ;; *) outdir="$outdir/" ;; esac test -d "$outdir" || mkdir -p "$outdir" || exit titleScript=' /./d /^$/n s/^\[PATCH[^]]*\] *// s/[^-a-z.A-Z_0-9]/-/g s/\.\.\.*/\./g s/\.*$// s/--*/-/g s/^-// s/-$// s/$/./ p q ' process_one () { perl -w -e ' my ($keep_subject, $num, $signoff, $commsg) = @ARGV; my ($signoff_pattern, $done_header, $done_subject, $signoff_seen, $last_was_signoff); if ($signoff) { $signoff = "Signed-off-by: " . `git-var GIT_COMMITTER_IDENT`; $signoff =~ s/>.*/>/; $signoff_pattern = quotemeta($signoff); } my @weekday_names = qw(Sun Mon Tue Wed Thu Fri Sat); my @month_names = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); sub show_date { my ($time, $tz) = @_; my $minutes = abs($tz); $minutes = ($minutes / 100) * 60 + ($minutes % 100); if ($tz < 0) { $minutes = -$minutes; } my $t = $time + $minutes * 60; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($t); return sprintf("%s %s %d %02d:%02d:%02d %d %+05d", $weekday_names[$wday], $month_names[$mon], $mday, $hour, $min, $sec, $year+1900, $tz); } print "From nobody Mon Sep 17 00:00:00 2001\n"; open FH, "git stripspace <$commsg |" or die "open $commsg pipe"; while () { unless ($done_header) { if (/^$/) { $done_header = 1; } elsif (/^author (.*>) (.*)$/) { my ($author_ident, $author_date) = ($1, $2); my ($utc, $off) = ($author_date =~ /^(\d+) ([-+]?\d+)$/); $author_date = show_date($utc, $off); print "From: $author_ident\n"; print "Date: $author_date\n"; } next; } unless ($done_subject) { unless ($keep_subject) { s/^\[PATCH[^]]*\]\s*//; s/^/[PATCH$num] /; } print "Subject: $_"; $done_subject = 1; next; } $last_was_signoff = 0; if (/Signed-off-by:/i) { if ($signoff ne "" && /Signed-off-by:\s*$signoff_pattern$/i) { $signoff_seen = 1; } } print $_; } if (!$signoff_seen && $signoff ne "") { if (!$last_was_signoff) { print "\n"; } print "$signoff\n"; } print "\n---\n\n"; close FH or die "close $commsg pipe"; ' "$keep_subject" "$num" "$signoff" $commsg git-diff-tree -p $diff_opts "$commit" | git-apply --stat --summary echo git-diff-tree -p $diff_opts "$commit" echo "-- " echo "@@GIT_VERSION@@" echo } total=`wc -l <$series | tr -dc "[0-9]"` case "$total,$numbered" in 1,*) numfmt='' ;; *,t) numfmt=`echo "$total" | wc -c` numfmt=$(($numfmt-1)) numfmt=" %0${numfmt}d/$total" esac i=1 while read commit do git-cat-file commit "$commit" | git-stripspace >$commsg title=`sed -ne "$titleScript" <$commsg` case "$numbered" in '') num= ;; *) num=`printf "$numfmt" $i` ;; esac file=`printf '%04d-%stxt' $i "$title"` if test '' = "$stdout" then echo "$file" process_one >"$outdir$file" if test t = "$check" then # This is slightly modified from Andrew Morton's Perfect Patch. # Lines you introduce should not have trailing whitespace. # Also check for an indentation that has SP before a TAB. grep -n '^+\([ ]* .*\|.*[ ]\)$' "$outdir$file" : fi else echo >&2 "$file" process_one fi i=`expr "$i" + 1` done <$series