1#!/bin/sh
2#
3# Copyright (c) 2005 Junio C Hamano
4#
5
6USAGE='[-n | -k] [-o <dir> | --stdout] [--signoff] [--check] [--mbox] [--diff-options] <upstream> [<our-head>]'
7LONG_USAGE='Prepare each commit with its patch since our-head forked from upstream,
8one file per patch, for e-mail submission. Each output file is
9numbered sequentially from 1, and uses the first line of the commit
10message (massaged for pathname safety) as the filename.
11
12There are three output modes. By default, output files are created in
13the current working directory; when -o is specified, they are created
14in that directory instead; when --stdout is specified, they are spit
15on standard output, and can be piped to git-am.
16
17When -n is specified, instead of "[PATCH] Subject", the first line is formatted
18as "[PATCH N/M] Subject", unless you have only one patch.
19
20When --mbox is specified, the output is formatted to resemble
21UNIX mailbox format, and can be concatenated together for processing
22with applymbox.'
23. git-sh-setup
24
25# Force diff to run in C locale.
26LANG=C LC_ALL=C
27export LANG LC_ALL
28
29diff_opts=
30LF='
31'
32
33outdir=./
34while case "$#" in 0) break;; esac
35do
36 case "$1" in
37 -c|--c|--ch|--che|--chec|--check)
38 check=t ;;
39 -a|--a|--au|--aut|--auth|--autho|--author|\
40 -d|--d|--da|--dat|--date|\
41 -m|--m|--mb|--mbo|--mbox) # now noop
42 ;;
43 -k|--k|--ke|--kee|--keep|--keep-|--keep-s|--keep-su|--keep-sub|\
44 --keep-subj|--keep-subje|--keep-subjec|--keep-subject)
45 keep_subject=t ;;
46 -n|--n|--nu|--num|--numb|--numbe|--number|--numbere|--numbered)
47 numbered=t ;;
48 -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
49 signoff=t ;;
50 --st|--std|--stdo|--stdou|--stdout)
51 stdout=t mbox=t date=t author=t ;;
52 -o=*|--o=*|--ou=*|--out=*|--outp=*|--outpu=*|--output=*|--output-=*|\
53 --output-d=*|--output-di=*|--output-dir=*|--output-dire=*|\
54 --output-direc=*|--output-direct=*|--output-directo=*|\
55 --output-director=*|--output-directory=*)
56 outdir=`expr "$1" : '-[^=]*=\(.*\)'` ;;
57 -o|--o|--ou|--out|--outp|--outpu|--output|--output-|--output-d|\
58 --output-di|--output-dir|--output-dire|--output-direc|--output-direct|\
59 --output-directo|--output-director|--output-directory)
60 case "$#" in 1) usage ;; esac; shift
61 outdir="$1" ;;
62 -h|--h|--he|--hel|--help)
63 usage
64 ;;
65 -*' '* | -*"$LF"* | -*' '*)
66 # Ignore diff option that has whitespace for now.
67 ;;
68 -*) diff_opts="$diff_opts$1 " ;;
69 *) break ;;
70 esac
71 shift
72done
73
74case "$keep_subject$numbered" in
75tt)
76 die '--keep-subject and --numbered are incompatible.' ;;
77esac
78
79tmp=.tmp-series$$
80trap 'rm -f $tmp-*' 0 1 2 3 15
81
82series=$tmp-series
83commsg=$tmp-commsg
84filelist=$tmp-files
85
86# Backward compatible argument parsing hack.
87#
88# Historically, we supported:
89# 1. "rev1" is equivalent to "rev1..HEAD"
90# 2. "rev1..rev2"
91# 3. "rev1" "rev2 is equivalent to "rev1..rev2"
92#
93# We want to take a sequence of "rev1..rev2" in general.
94# Also, "rev1.." should mean "rev1..HEAD"; git-diff users are
95# familiar with that syntax.
96
97case "$#,$1$2" in
981,?*..?*)
99 # single "rev1..rev2"
100 ;;
1011,?*..)
102 # single "rev1.." should mean "rev1..HEAD"
103 set x "$1"HEAD
104 shift
105 ;;
1061,*)
107 # single rev1
108 set x "$1..HEAD"
109 shift
110 ;;
1112,?*..?*)
112 # not traditional "rev1" "rev2"
113 ;;
1142,*)
115 set x "$1..$2"
116 shift
117 ;;
118esac
119
120# Now we have what we want in $@
121for revpair
122do
123 case "$revpair" in
124 ?*..?*)
125 rev1=`expr "$revpair" : '\(.*\)\.\.'`
126 rev2=`expr "$revpair" : '.*\.\.\(.*\)'`
127 ;;
128 *)
129 rev1="$revpair^"
130 rev2="$revpair"
131 ;;
132 esac
133 git-rev-parse --verify "$rev1^0" >/dev/null 2>&1 ||
134 die "Not a valid rev $rev1 ($revpair)"
135 git-rev-parse --verify "$rev2^0" >/dev/null 2>&1 ||
136 die "Not a valid rev $rev2 ($revpair)"
137 git-cherry -v "$rev1" "$rev2" |
138 while read sign rev comment
139 do
140 case "$sign" in
141 '-')
142 echo >&2 "Merged already: $comment"
143 ;;
144 *)
145 echo $rev
146 ;;
147 esac
148 done
149done >$series
150
151me=`git-var GIT_AUTHOR_IDENT | sed -e 's/>.*/>/'`
152
153case "$outdir" in
154*/) ;;
155*) outdir="$outdir/" ;;
156esac
157test -d "$outdir" || mkdir -p "$outdir" || exit
158
159titleScript='
160 /./d
161 /^$/n
162 s/^\[PATCH[^]]*\] *//
163 s/[^-a-z.A-Z_0-9]/-/g
164 s/\.\.\.*/\./g
165 s/\.*$//
166 s/--*/-/g
167 s/^-//
168 s/-$//
169 s/$/./
170 p
171 q
172'
173
174process_one () {
175 perl -w -e '
176my ($keep_subject, $num, $signoff, $commsg) = @ARGV;
177my ($signoff_pattern, $done_header, $done_subject, $signoff_seen,
178 $last_was_signoff);
179
180if ($signoff) {
181 $signoff = `git-var GIT_COMMITTER_IDENT`;
182 $signoff =~ s/>.*/>/;
183 $signoff_pattern = quotemeta($signoff);
184}
185
186my @weekday_names = qw(Sun Mon Tue Wed Thu Fri Sat);
187my @month_names = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
188
189sub show_date {
190 my ($time, $tz) = @_;
191 my $minutes = abs($tz);
192 $minutes = ($minutes / 100) * 60 + ($minutes % 100);
193 if ($tz < 0) {
194 $minutes = -$minutes;
195 }
196 my $t = $time + $minutes * 60;
197 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($t);
198 return sprintf("%s %s %d %02d:%02d:%02d %d %+05d",
199 $weekday_names[$wday],
200 $month_names[$mon],
201 $mday, $hour, $min, $sec,
202 $year+1900, $tz);
203}
204
205print "From nobody Mon Sep 17 00:00:00 2001\n";
206open FH, "git stripspace <$commsg |" or die "open $commsg pipe";
207while (<FH>) {
208 unless ($done_header) {
209 if (/^$/) {
210 $done_header = 1;
211 }
212 elsif (/^author (.*>) (.*)$/) {
213 my ($author_ident, $author_date) = ($1, $2);
214 my ($utc, $off) = ($author_date =~ /^(\d+) ([-+]?\d+)$/);
215 $author_date = show_date($utc, $off);
216
217 print "From: $author_ident\n";
218 print "Date: $author_date\n";
219 }
220 next;
221 }
222 unless ($done_subject) {
223 unless ($keep_subject) {
224 s/^\[PATCH[^]]*\]\s*//;
225 s/^/[PATCH$num] /;
226 }
227 print "Subject: $_";
228 $done_subject = 1;
229 next;
230 }
231
232 $last_was_signoff = 0;
233 if (/Signed-off-by:/i) {
234 if ($signoff ne "" && /Signed-off-by:\s*$signoff_pattern$/i) {
235 $signoff_seen = 1;
236 }
237 }
238 print $_;
239}
240if (!$signoff_seen && $signoff ne "") {
241 if (!$last_was_signoff) {
242 print "\n";
243 }
244 print "$signoff\n";
245}
246print "\n---\n\n";
247close FH or die "close $commsg pipe";
248' "$keep_subject" "$num" "$signoff" $commsg
249
250 git-diff-tree -p $diff_opts "$commit" | git-apply --stat --summary
251 echo
252 git-diff-tree -p $diff_opts "$commit"
253 echo "-- "
254 echo "@@GIT_VERSION@@"
255
256 echo
257}
258
259total=`wc -l <$series | tr -dc "[0-9]"`
260case "$total,$numbered" in
2611,*)
262 numfmt='' ;;
263*,t)
264 numfmt=`echo "$total" | wc -c`
265 numfmt=$(($numfmt-1))
266 numfmt=" %0${numfmt}d/$total"
267esac
268
269i=1
270while read commit
271do
272 git-cat-file commit "$commit" | git-stripspace >$commsg
273 title=`sed -ne "$titleScript" <$commsg`
274 case "$numbered" in
275 '') num= ;;
276 *)
277 num=`printf "$numfmt" $i` ;;
278 esac
279
280 file=`printf '%04d-%stxt' $i "$title"`
281 if test '' = "$stdout"
282 then
283 echo "$file"
284 process_one >"$outdir$file"
285 if test t = "$check"
286 then
287 # This is slightly modified from Andrew Morton's Perfect Patch.
288 # Lines you introduce should not have trailing whitespace.
289 # Also check for an indentation that has SP before a TAB.
290 grep -n '^+\([ ]* .*\|.*[ ]\)$' "$outdir$file"
291 :
292 fi
293 else
294 echo >&2 "$file"
295 process_one
296 fi
297 i=`expr "$i" + 1`
298done <$series