Merge branch 'jc/diff'
[git.git] / git-format-patch.sh
1 #!/bin/sh
2 #
3 # Copyright (c) 2005 Junio C Hamano
4 #
5
6 USAGE='[-n | -k] [-o <dir> | --stdout] [--signoff] [--check] [--mbox] [--diff-options] <upstream> [<our-head>]'
7 LONG_USAGE='Prepare each commit with its patch since our-head forked from upstream,
8 one file per patch, for e-mail submission.  Each output file is
9 numbered sequentially from 1, and uses the first line of the commit
10 message (massaged for pathname safety) as the filename.
11
12 There are three output modes.  By default, output files are created in
13 the current working directory; when -o is specified, they are created
14 in that directory instead; when --stdout is specified, they are spit
15 on standard output, and can be piped to git-am.
16
17 When -n is specified, instead of "[PATCH] Subject", the first line is formatted
18 as "[PATCH N/M] Subject", unless you have only one patch.
19
20 When --mbox is specified, the output is formatted to resemble
21 UNIX mailbox format, and can be concatenated together for processing
22 with applymbox.'
23 . git-sh-setup
24
25 # Force diff to run in C locale.
26 LANG=C LC_ALL=C
27 export LANG LC_ALL
28
29 diff_opts=
30 LF='
31 '
32
33 outdir=./
34 while case "$#" in 0) break;; esac
35 do
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
72 done
73
74 case "$keep_subject$numbered" in
75 tt)
76         die '--keep-subject and --numbered are incompatible.' ;;
77 esac
78
79 tmp=.tmp-series$$
80 trap 'rm -f $tmp-*' 0 1 2 3 15
81
82 series=$tmp-series
83 commsg=$tmp-commsg
84 filelist=$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
97 case "$#,$1$2" in
98 1,?*..?*)
99         # single "rev1..rev2"
100         ;;
101 1,?*..)
102         # single "rev1.." should mean "rev1..HEAD"
103         set x "$1"HEAD
104         shift
105         ;;
106 1,*)
107         # single rev1
108         set x "$1..HEAD"
109         shift
110         ;;
111 2,?*..?*)
112         # not traditional "rev1" "rev2"
113         ;;
114 2,*)
115         set x "$1..$2"
116         shift
117         ;;
118 esac
119
120 # Now we have what we want in $@
121 for revpair
122 do
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
149 done >$series
150
151 me=`git-var GIT_AUTHOR_IDENT | sed -e 's/>.*/>/'`
152
153 case "$outdir" in
154 */) ;;
155 *) outdir="$outdir/" ;;
156 esac
157 test -d "$outdir" || mkdir -p "$outdir" || exit
158
159 titleScript='
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
174 process_one () {
175         perl -w -e '
176 my ($keep_subject, $num, $signoff, $commsg) = @ARGV;
177 my ($signoff_pattern, $done_header, $done_subject, $signoff_seen,
178     $last_was_signoff);
179
180 if ($signoff) {
181         $signoff = "Signed-off-by: " . `git-var GIT_COMMITTER_IDENT`;
182         $signoff =~ s/>.*/>/;
183         $signoff_pattern = quotemeta($signoff);
184 }
185
186 my @weekday_names = qw(Sun Mon Tue Wed Thu Fri Sat);
187 my @month_names = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
188
189 sub 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
205 print "From nobody Mon Sep 17 00:00:00 2001\n";
206 open FH, "git stripspace <$commsg |" or die "open $commsg pipe";
207 while (<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 }
240 if (!$signoff_seen && $signoff ne "") {
241     if (!$last_was_signoff) {
242         print "\n";
243     }
244     print "$signoff\n";
245 }
246 print "\n---\n\n";
247 close 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
259 total=`wc -l <$series | tr -dc "[0-9]"`
260 case "$total,$numbered" in
261 1,*)
262         numfmt='' ;;
263 *,t)
264         numfmt=`echo "$total" | wc -c`
265         numfmt=$(($numfmt-1))
266         numfmt=" %0${numfmt}d/$total"
267 esac
268
269 i=1
270 while read commit
271 do
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`
298 done <$series