format-patch: fix two-argument special case, and make it easier to pick single commits
[git] / git-format-patch.sh
1 #!/bin/sh
2 #
3 # Copyright (c) 2005 Junio C Hamano
4 #
5
6 . git-sh-setup || die "Not a git archive."
7
8 usage () {
9     echo >&2 "usage: $0"' [-n] [-o dir | --stdout] [--keep-subject] [--mbox]
10     [--check] [--signoff] [-<diff options>...]
11     [--help]
12     ( from..to ... | upstream [ our-head ] )
13
14 Prepare each commit with its patch since our-head forked from upstream,
15 one file per patch, for e-mail submission.  Each output file is
16 numbered sequentially from 1, and uses the first line of the commit
17 message (massaged for pathname safety) as the filename.
18
19 When -o is specified, output files are created in that directory; otherwise in
20 the current working directory.
21
22 When -n is specified, instead of "[PATCH] Subject", the first line is formatted
23 as "[PATCH N/M] Subject", unless you have only one patch.
24
25 When --mbox is specified, the output is formatted to resemble
26 UNIX mailbox format, and can be concatenated together for processing
27 with applymbox.
28 '
29     exit 1
30 }
31
32 diff_opts=
33 LF='
34 '
35
36 outdir=./
37 while case "$#" in 0) break;; esac
38 do
39     case "$1" in
40     -a|--a|--au|--aut|--auth|--autho|--author)
41     author=t ;;
42     -c|--c|--ch|--che|--chec|--check)
43     check=t ;;
44     -d|--d|--da|--dat|--date)
45     date=t ;;
46     -m|--m|--mb|--mbo|--mbox)
47     date=t author=t mbox=t ;;
48     -k|--k|--ke|--kee|--keep|--keep-|--keep-s|--keep-su|--keep-sub|\
49     --keep-subj|--keep-subje|--keep-subjec|--keep-subject)
50     keep_subject=t ;;
51     -n|--n|--nu|--num|--numb|--numbe|--number|--numbere|--numbered)
52     numbered=t ;;
53     -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
54     signoff=t ;;
55     --st|--std|--stdo|--stdou|--stdout)
56     stdout=t mbox=t date=t author=t ;;
57     -o=*|--o=*|--ou=*|--out=*|--outp=*|--outpu=*|--output=*|--output-=*|\
58     --output-d=*|--output-di=*|--output-dir=*|--output-dire=*|\
59     --output-direc=*|--output-direct=*|--output-directo=*|\
60     --output-director=*|--output-directory=*)
61     outdir=`expr "$1" : '-[^=]*=\(.*\)'` ;;
62     -o|--o|--ou|--out|--outp|--outpu|--output|--output-|--output-d|\
63     --output-di|--output-dir|--output-dire|--output-direc|--output-direct|\
64     --output-directo|--output-director|--output-directory)
65     case "$#" in 1) usage ;; esac; shift
66     outdir="$1" ;;
67     -h|--h|--he|--hel|--help)
68         usage
69         ;;
70     -*' '* | -*"$LF"* | -*'     '*)
71         # Ignore diff option that has whitespace for now.
72         ;;
73     -*) diff_opts="$diff_opts$1 " ;;
74     *) break ;;
75     esac
76     shift
77 done
78
79 case "$keep_subject$numbered" in
80 tt)
81         die '--keep-subject and --numbered are incompatible.' ;;
82 esac
83
84 tmp=.tmp-series$$
85 trap 'rm -f $tmp-*' 0 1 2 3 15
86
87 series=$tmp-series
88 commsg=$tmp-commsg
89 filelist=$tmp-files
90
91 # Backward compatible argument parsing hack.
92 #
93 # Historically, we supported:
94 # 1. "rev1"             is equivalent to "rev1..HEAD"
95 # 2. "rev1..rev2"
96 # 3. "rev1" "rev2       is equivalent to "rev1..rev2"
97 #
98 # We want to take a sequence of "rev1..rev2" in general.
99 # Also, "rev1.." should mean "rev1..HEAD"; git-diff users are
100 # familiar with that syntax.
101
102 case "$#,$1$2" in
103 1,?*..?*)
104         # single "rev1..rev2"
105         ;;
106 1,?*..)
107         # single "rev1.." should mean "rev1..HEAD"
108         set x "$1"HEAD
109         shift
110         ;;
111 1,*)
112         # single rev1
113         set x "$1..HEAD"
114         shift
115         ;;
116 2,?*..?*)
117         # not traditional "rev1" "rev2"
118         ;;
119 2,*)
120         set x "$1..$2"
121         shift
122         ;;
123 esac
124
125 # Now we have what we want in $@
126 for revpair
127 do
128         case "$revpair" in
129         ?*..?*)
130                 rev1=`expr "$revpair" : '\(.*\)\.\.'`
131                 rev2=`expr "$revpair" : '.*\.\.\(.*\)'`
132                 ;;
133         *)
134                 rev1="$revpair^"
135                 rev2="$revpair"
136                 ;;
137         esac
138         git-rev-parse --verify "$rev1^0" >/dev/null 2>&1 ||
139                 die "Not a valid rev $rev1 ($revpair)"
140         git-rev-parse --verify "$rev2^0" >/dev/null 2>&1 ||
141                 die "Not a valid rev $rev2 ($revpair)"
142         git-cherry -v "$rev1" "$rev2" |
143         while read sign rev comment
144         do
145                 case "$sign" in
146                 '-')
147                         echo >&2 "Merged already: $comment"
148                         ;;
149                 *)
150                         echo $rev
151                         ;;
152                 esac
153         done
154 done >$series
155
156 me=`git-var GIT_AUTHOR_IDENT | sed -e 's/>.*/>/'`
157
158 case "$outdir" in
159 */) ;;
160 *) outdir="$outdir/" ;;
161 esac
162 test -d "$outdir" || mkdir -p "$outdir" || exit
163
164 titleScript='
165         /./d
166         /^$/n
167         s/^\[PATCH[^]]*\] *//
168         s/[^-a-z.A-Z_0-9]/-/g
169         s/\.\.\.*/\./g
170         s/\.*$//
171         s/--*/-/g
172         s/^-//
173         s/-$//
174         s/$/./
175         p
176         q
177 '
178
179 whosepatchScript='
180 /^author /{
181         s/author \(.*>\) \(.*\)$/au='\''\1'\'' ad='\''\2'\''/p
182         q
183 }'
184
185 process_one () {
186         mailScript='
187         /./d
188         /^$/n'
189         case "$keep_subject" in
190         t)  ;;
191         *)
192             mailScript="$mailScript"'
193             s|^\[PATCH[^]]*\] *||
194             s|^|[PATCH'"$num"'] |'
195             ;;
196         esac
197         mailScript="$mailScript"'
198         s|^|Subject: |'
199         case "$mbox" in
200         t)
201             echo 'From nobody Mon Sep 17 00:00:00 2001' ;# UNIX "From" line
202             ;;
203         esac
204
205         eval "$(LANG=C LC_ALL=C sed -ne "$whosepatchScript" $commsg)"
206         test "$author,$au" = ",$me" || {
207                 mailScript="$mailScript"'
208         a\
209 From: '"$au"
210         }
211         test "$date,$au" = ",$me" || {
212                 mailScript="$mailScript"'
213         a\
214 Date: '"$ad"
215         }
216
217         mailScript="$mailScript"'
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-cat-file commit "$commit^" | sed -e 's/^tree /applies-to: /' -e q
242         git-diff-tree -p $diff_opts "$commit"
243         echo "---"
244         echo "@@GIT_VERSION@@"
245
246         case "$mbox" in
247         t)
248                 echo
249                 ;;
250         esac
251 }
252
253 total=`wc -l <$series | tr -dc "[0-9]"`
254 i=1
255 while read commit
256 do
257     git-cat-file commit "$commit" | git-stripspace >$commsg
258     title=`sed -ne "$titleScript" <$commsg`
259     case "$numbered" in
260     '') num= ;;
261     *)
262         case $total in
263         1) num= ;;
264         *) num=' '`printf "%d/%d" $i $total` ;;
265         esac
266     esac
267
268     file=`printf '%04d-%stxt' $i "$title"`
269     if test '' = "$stdout"
270     then
271             echo "* $file"
272             process_one >"$outdir$file"
273             if test t = "$check"
274             then
275                 # This is slightly modified from Andrew Morton's Perfect Patch.
276                 # Lines you introduce should not have trailing whitespace.
277                 # Also check for an indentation that has SP before a TAB.
278                 grep -n '^+\([  ]*      .*\|.*[         ]\)$' "$outdir$file"
279                 :
280             fi
281     else
282             echo >&2 "* $file"
283             process_one
284     fi
285     i=`expr "$i" + 1`
286 done <$series