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 -a|--a|--au|--aut|--auth|--autho|--author)
38 author=t ;;
39 -c|--c|--ch|--che|--chec|--check)
40 check=t ;;
41 -d|--d|--da|--dat|--date)
42 date=t ;;
43 -m|--m|--mb|--mbo|--mbox)
44 date=t author=t mbox=t ;;
45 -k|--k|--ke|--kee|--keep|--keep-|--keep-s|--keep-su|--keep-sub|\
46 --keep-subj|--keep-subje|--keep-subjec|--keep-subject)
47 keep_subject=t ;;
48 -n|--n|--nu|--num|--numb|--numbe|--number|--numbere|--numbered)
49 numbered=t ;;
50 -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
51 signoff=t ;;
52 --st|--std|--stdo|--stdou|--stdout)
53 stdout=t mbox=t date=t author=t ;;
54 -o=*|--o=*|--ou=*|--out=*|--outp=*|--outpu=*|--output=*|--output-=*|\
55 --output-d=*|--output-di=*|--output-dir=*|--output-dire=*|\
56 --output-direc=*|--output-direct=*|--output-directo=*|\
57 --output-director=*|--output-directory=*)
58 outdir=`expr "$1" : '-[^=]*=\(.*\)'` ;;
59 -o|--o|--ou|--out|--outp|--outpu|--output|--output-|--output-d|\
60 --output-di|--output-dir|--output-dire|--output-direc|--output-direct|\
61 --output-directo|--output-director|--output-directory)
62 case "$#" in 1) usage ;; esac; shift
63 outdir="$1" ;;
64 -h|--h|--he|--hel|--help)
65 usage
66 ;;
67 -*' '* | -*"$LF"* | -*' '*)
68 # Ignore diff option that has whitespace for now.
69 ;;
70 -*) diff_opts="$diff_opts$1 " ;;
71 *) break ;;
72 esac
73 shift
74done
75
76case "$keep_subject$numbered" in
77tt)
78 die '--keep-subject and --numbered are incompatible.' ;;
79esac
80
81tmp=.tmp-series$$
82trap 'rm -f $tmp-*' 0 1 2 3 15
83
84series=$tmp-series
85commsg=$tmp-commsg
86filelist=$tmp-files
87
88# Backward compatible argument parsing hack.
89#
90# Historically, we supported:
91# 1. "rev1" is equivalent to "rev1..HEAD"
92# 2. "rev1..rev2"
93# 3. "rev1" "rev2 is equivalent to "rev1..rev2"
94#
95# We want to take a sequence of "rev1..rev2" in general.
96# Also, "rev1.." should mean "rev1..HEAD"; git-diff users are
97# familiar with that syntax.
98
99case "$#,$1$2" in
1001,?*..?*)
101 # single "rev1..rev2"
102 ;;
1031,?*..)
104 # single "rev1.." should mean "rev1..HEAD"
105 set x "$1"HEAD
106 shift
107 ;;
1081,*)
109 # single rev1
110 set x "$1..HEAD"
111 shift
112 ;;
1132,?*..?*)
114 # not traditional "rev1" "rev2"
115 ;;
1162,*)
117 set x "$1..$2"
118 shift
119 ;;
120esac
121
122# Now we have what we want in $@
123for revpair
124do
125 case "$revpair" in
126 ?*..?*)
127 rev1=`expr "$revpair" : '\(.*\)\.\.'`
128 rev2=`expr "$revpair" : '.*\.\.\(.*\)'`
129 ;;
130 *)
131 rev1="$revpair^"
132 rev2="$revpair"
133 ;;
134 esac
135 git-rev-parse --verify "$rev1^0" >/dev/null 2>&1 ||
136 die "Not a valid rev $rev1 ($revpair)"
137 git-rev-parse --verify "$rev2^0" >/dev/null 2>&1 ||
138 die "Not a valid rev $rev2 ($revpair)"
139 git-cherry -v "$rev1" "$rev2" |
140 while read sign rev comment
141 do
142 case "$sign" in
143 '-')
144 echo >&2 "Merged already: $comment"
145 ;;
146 *)
147 echo $rev
148 ;;
149 esac
150 done
151done >$series
152
153me=`git-var GIT_AUTHOR_IDENT | sed -e 's/>.*/>/'`
154
155case "$outdir" in
156*/) ;;
157*) outdir="$outdir/" ;;
158esac
159test -d "$outdir" || mkdir -p "$outdir" || exit
160
161titleScript='
162 /./d
163 /^$/n
164 s/^\[PATCH[^]]*\] *//
165 s/[^-a-z.A-Z_0-9]/-/g
166 s/\.\.\.*/\./g
167 s/\.*$//
168 s/--*/-/g
169 s/^-//
170 s/-$//
171 s/$/./
172 p
173 q
174'
175
176whosepatchScript='
177/^author /{
178 s/'\''/'\''\\'\'\''/g
179 s/author \(.*>\) \(.*\)$/au='\''\1'\'' ad='\''\2'\''/p
180 q
181}'
182
183process_one () {
184 mailScript='
185 /./d
186 /^$/n'
187 case "$keep_subject" in
188 t) ;;
189 *)
190 mailScript="$mailScript"'
191 s|^\[PATCH[^]]*\] *||
192 s|^|[PATCH'"$num"'] |'
193 ;;
194 esac
195 mailScript="$mailScript"'
196 s|^|Subject: |'
197 case "$mbox" in
198 t)
199 echo 'From nobody Mon Sep 17 00:00:00 2001' ;# UNIX "From" line
200 ;;
201 esac
202
203 eval "$(sed -ne "$whosepatchScript" $commsg)"
204 test "$author,$au" = ",$me" || {
205 mailScript="$mailScript"'
206 a\
207From: '"$au"
208 }
209 test "$date,$au" = ",$me" || {
210 mailScript="$mailScript"'
211 a\
212Date: '"$ad"
213 }
214
215 mailScript="$mailScript"'
216 a\
217
218 : body
219 p
220 n
221 b body'
222
223 (cat $commsg ; echo; echo) |
224 sed -ne "$mailScript" |
225 git-stripspace
226
227 test "$signoff" = "t" && {
228 offsigner=`git-var GIT_COMMITTER_IDENT | sed -e 's/>.*/>/'`
229 line="Signed-off-by: $offsigner"
230 grep -q "^$line\$" $commsg || {
231 echo
232 echo "$line"
233 echo
234 }
235 }
236 echo
237 echo '---'
238 echo
239 git-diff-tree -p $diff_opts "$commit" | git-apply --stat --summary
240 echo
241 git-diff-tree -p $diff_opts "$commit"
242 echo "-- "
243 echo "@@GIT_VERSION@@"
244
245 case "$mbox" in
246 t)
247 echo
248 ;;
249 esac
250}
251
252total=`wc -l <$series | tr -dc "[0-9]"`
253case "$total,$numbered" in
2541,*)
255 numfmt='' ;;
256*,t)
257 numfmt=`echo "$total" | wc -c`
258 numfmt=$(($numfmt-1))
259 numfmt=" %0${numfmt}d/$total"
260esac
261
262i=1
263while read commit
264do
265 git-cat-file commit "$commit" | git-stripspace >$commsg
266 title=`sed -ne "$titleScript" <$commsg`
267 case "$numbered" in
268 '') num= ;;
269 *)
270 num=`printf "$numfmt" $i` ;;
271 esac
272
273 file=`printf '%04d-%stxt' $i "$title"`
274 if test '' = "$stdout"
275 then
276 echo "$file"
277 process_one >"$outdir$file"
278 if test t = "$check"
279 then
280 # This is slightly modified from Andrew Morton's Perfect Patch.
281 # Lines you introduce should not have trailing whitespace.
282 # Also check for an indentation that has SP before a TAB.
283 grep -n '^+\([ ]* .*\|.*[ ]\)$' "$outdir$file"
284 :
285 fi
286 else
287 echo >&2 "$file"
288 process_one
289 fi
290 i=`expr "$i" + 1`
291done <$series