update-hook: Major overhaul (handling tags, mainly).
[git/repo.git] / git-format-patch.sh
blob7e67c4e403f48be282e934586faa93a36e5f1ad8
1 #!/bin/sh
3 # Copyright (c) 2005 Junio C Hamano
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.
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.
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.
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
25 # Force diff to run in C locale.
26 LANG=C LC_ALL=C
27 export LANG LC_ALL
29 diff_opts=
30 LF='
33 outdir=./
34 while case "$#" in 0) break;; esac
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
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
65 -*' '* | -*"$LF"* | -*' '*)
66 # Ignore diff option that has whitespace for now.
68 -*) diff_opts="$diff_opts$1 " ;;
69 *) break ;;
70 esac
71 shift
72 done
74 case "$keep_subject$numbered" in
75 tt)
76 die '--keep-subject and --numbered are incompatible.' ;;
77 esac
79 tmp=.tmp-series$$
80 trap 'rm -f $tmp-*' 0 1 2 3 15
82 series=$tmp-series
83 commsg=$tmp-commsg
84 filelist=$tmp-files
86 # Backward compatible argument parsing hack.
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"
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.
97 case "$#,$1$2" in
98 1,?*..?*)
99 # single "rev1..rev2"
101 1,?*..)
102 # single "rev1.." should mean "rev1..HEAD"
103 set x "$1"HEAD
104 shift
106 1,*)
107 # single rev1
108 set x "$1..HEAD"
109 shift
111 2,?*..?*)
112 # not traditional "rev1" "rev2"
114 2,*)
115 set x "$1..$2"
116 shift
118 esac
120 # Now we have what we want in $@
121 for revpair
123 case "$revpair" in
124 ?*..?*)
125 rev1=`expr "$revpair" : '\(.*\)\.\.'`
126 rev2=`expr "$revpair" : '.*\.\.\(.*\)'`
129 rev1="$revpair^"
130 rev2="$revpair"
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
140 case "$sign" in
141 '-')
142 echo >&2 "Merged already: $comment"
145 echo $rev
147 esac
148 done
149 done >$series
151 me=`git-var GIT_AUTHOR_IDENT | sed -e 's/>.*/>/'`
153 case "$outdir" in
154 */) ;;
155 *) outdir="$outdir/" ;;
156 esac
157 test -d "$outdir" || mkdir -p "$outdir" || exit
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/$/./
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);
180 if ($signoff) {
181 $signoff = `git-var GIT_COMMITTER_IDENT`;
182 $signoff =~ s/>.*/>/;
183 $signoff_pattern = quotemeta($signoff);
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);
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;
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);
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;
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);
217 print "From: $author_ident\n";
218 print "Date: $author_date\n";
220 next;
222 unless ($done_subject) {
223 unless ($keep_subject) {
224 s/^\[PATCH[^]]*\]\s*//;
225 s/^/[PATCH$num] /;
227 print "Subject: $_";
228 $done_subject = 1;
229 next;
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;
238 print $_;
240 if (!$signoff_seen && $signoff ne "") {
241 if (!$last_was_signoff) {
242 print "\n";
244 print "$signoff\n";
246 print "\n---\n\n";
247 close FH or die "close $commsg pipe";
248 ' "$keep_subject" "$num" "$signoff" $commsg
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@@"
256 echo
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
270 while read commit
272 git-cat-file commit "$commit" | git-stripspace >$commsg
273 title=`sed -ne "$titleScript" <$commsg`
274 case "$numbered" in
275 '') num= ;;
277 num=`printf "$numfmt" $i` ;;
278 esac
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"
293 else
294 echo >&2 "$file"
295 process_one
297 i=`expr "$i" + 1`
298 done <$series