Don't include ../README in git.txt - make a local copy
[git.git] / git-format-patch.sh
index 5b93ff8..7e67c4e 100755 (executable)
@@ -3,30 +3,28 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
 # Copyright (c) 2005 Junio C Hamano
 #
 
-. git-sh-setup || die "Not a git archive."
-
-usage () {
-    echo >&2 "usage: $0"' [-n] [-o dir | --stdout] [--keep-subject] [--mbox]
-    [--check] [--signoff] [-<diff options>...]
-    ( from..to ... | upstream [ our-head ] )
-
-Prepare each commit with its patch since our-head forked from upstream,
+USAGE='[-n | -k] [-o <dir> | --stdout] [--signoff] [--check] [--mbox] [--diff-options] <upstream> [<our-head>]'
+LONG_USAGE='Prepare each commit with its patch since our-head forked from upstream,
 one file per patch, for e-mail submission.  Each output file is
 numbered sequentially from 1, and uses the first line of the commit
 message (massaged for pathname safety) as the filename.
 
 one file per patch, for e-mail submission.  Each output file is
 numbered sequentially from 1, and uses the first line of the commit
 message (massaged for pathname safety) as the filename.
 
-When -o is specified, output files are created in that directory; otherwise in
-the current working directory.
+There are three output modes.  By default, output files are created in
+the current working directory; when -o is specified, they are created
+in that directory instead; when --stdout is specified, they are spit
+on standard output, and can be piped to git-am.
 
 When -n is specified, instead of "[PATCH] Subject", the first line is formatted
 as "[PATCH N/M] Subject", unless you have only one patch.
 
 When --mbox is specified, the output is formatted to resemble
 UNIX mailbox format, and can be concatenated together for processing
 
 When -n is specified, instead of "[PATCH] Subject", the first line is formatted
 as "[PATCH N/M] Subject", unless you have only one patch.
 
 When --mbox is specified, the output is formatted to resemble
 UNIX mailbox format, and can be concatenated together for processing
-with applymbox.
-'
-    exit 1
-}
+with applymbox.'
+. git-sh-setup
+
+# Force diff to run in C locale.
+LANG=C LC_ALL=C
+export LANG LC_ALL
 
 diff_opts=
 LF='
 
 diff_opts=
 LF='
@@ -36,14 +34,12 @@ outdir=./
 while case "$#" in 0) break;; esac
 do
     case "$1" in
 while case "$#" in 0) break;; esac
 do
     case "$1" in
-    -a|--a|--au|--aut|--auth|--autho|--author)
-    author=t ;;
     -c|--c|--ch|--che|--chec|--check)
     check=t ;;
     -c|--c|--ch|--che|--chec|--check)
     check=t ;;
-    -d|--d|--da|--dat|--date)
-    date=t ;;
-    -m|--m|--mb|--mbo|--mbox)
-    date=t author=t mbox=t ;;
+    -a|--a|--au|--aut|--auth|--autho|--author|\
+    -d|--d|--da|--dat|--date|\
+    -m|--m|--mb|--mbo|--mbox) # now noop
+    ;;
     -k|--k|--ke|--kee|--keep|--keep-|--keep-s|--keep-su|--keep-sub|\
     --keep-subj|--keep-subje|--keep-subjec|--keep-subject)
     keep_subject=t ;;
     -k|--k|--ke|--kee|--keep|--keep-|--keep-s|--keep-su|--keep-sub|\
     --keep-subj|--keep-subje|--keep-subjec|--keep-subject)
     keep_subject=t ;;
@@ -63,6 +59,9 @@ do
     --output-directo|--output-director|--output-directory)
     case "$#" in 1) usage ;; esac; shift
     outdir="$1" ;;
     --output-directo|--output-director|--output-directory)
     case "$#" in 1) usage ;; esac; shift
     outdir="$1" ;;
+    -h|--h|--he|--hel|--help)
+        usage
+       ;;
     -*' '* | -*"$LF"* | -*'    '*)
        # Ignore diff option that has whitespace for now.
        ;;
     -*' '* | -*"$LF"* | -*'    '*)
        # Ignore diff option that has whitespace for now.
        ;;
@@ -95,13 +94,13 @@ filelist=$tmp-files
 # Also, "rev1.." should mean "rev1..HEAD"; git-diff users are
 # familiar with that syntax.
 
 # Also, "rev1.." should mean "rev1..HEAD"; git-diff users are
 # familiar with that syntax.
 
-case "$#,$1" in
+case "$#,$1$2" in
 1,?*..?*)
        # single "rev1..rev2"
        ;;
 1,?*..)
        # single "rev1.." should mean "rev1..HEAD"
 1,?*..?*)
        # single "rev1..rev2"
        ;;
 1,?*..)
        # single "rev1.." should mean "rev1..HEAD"
-       set x "$1"HEAD"
+       set x "$1"HEAD
        shift
        ;;
 1,*)
        shift
        ;;
 1,*)
@@ -127,7 +126,8 @@ do
                rev2=`expr "$revpair" : '.*\.\.\(.*\)'`
                ;;
        *)
                rev2=`expr "$revpair" : '.*\.\.\(.*\)'`
                ;;
        *)
-               usage
+               rev1="$revpair^"
+               rev2="$revpair"
                ;;
        esac
        git-rev-parse --verify "$rev1^0" >/dev/null 2>&1 ||
                ;;
        esac
        git-rev-parse --verify "$rev1^0" >/dev/null 2>&1 ||
@@ -171,81 +171,101 @@ titleScript='
        q
 '
 
        q
 '
 
-whosepatchScript='
-/^author /{
-       s/author \(.*>\) \(.*\)$/au='\''\1'\'' ad='\''\2'\''/p
-       q
-}'
-
 process_one () {
 process_one () {
-       mailScript='
-       /./d
-       /^$/n'
-       case "$keep_subject" in
-       t)  ;;
-       *)
-           mailScript="$mailScript"'
-           s|^\[PATCH[^]]*\] *||
-           s|^|[PATCH'"$num"'] |'
-           ;;
-       esac
-       mailScript="$mailScript"'
-       s|^|Subject: |'
-       case "$mbox" in
-       t)
-           echo 'From nobody Mon Sep 17 00:00:00 2001' ;# UNIX "From" line
-           ;;
-       esac
+       perl -w -e '
+my ($keep_subject, $num, $signoff, $commsg) = @ARGV;
+my ($signoff_pattern, $done_header, $done_subject, $signoff_seen,
+    $last_was_signoff);
 
 
-       eval "$(sed -ne "$whosepatchScript" $commsg)"
-       test "$author,$au" = ",$me" || {
-               mailScript="$mailScript"'
-       a\
-From: '"$au"
-       }
-       test "$date,$au" = ",$me" || {
-               mailScript="$mailScript"'
-       a\
-Date: '"$ad"
-       }
+if ($signoff) {
+       $signoff = `git-var GIT_COMMITTER_IDENT`;
+       $signoff =~ s/>.*/>/;
+       $signoff_pattern = quotemeta($signoff);
+}
 
 
-       mailScript="$mailScript"'
-       : body
-       p
-       n
-       b body'
+my @weekday_names = qw(Sun Mon Tue Wed Thu Fri Sat);
+my @month_names = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
+
+sub show_date {
+    my ($time, $tz) = @_;
+    my $minutes = abs($tz);
+    $minutes = ($minutes / 100) * 60 + ($minutes % 100);
+    if ($tz < 0) {
+        $minutes = -$minutes;
+    }
+    my $t = $time + $minutes * 60;
+    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($t);
+    return sprintf("%s %s %d %02d:%02d:%02d %d %+05d",
+                  $weekday_names[$wday],
+                  $month_names[$mon],
+                  $mday, $hour, $min, $sec,
+                  $year+1900, $tz);
+}
 
 
-       (cat $commsg ; echo; echo) |
-       sed -ne "$mailScript" |
-       git-stripspace
+print "From nobody Mon Sep 17 00:00:00 2001\n";
+open FH, "git stripspace <$commsg |" or die "open $commsg pipe";
+while (<FH>) {
+    unless ($done_header) {
+       if (/^$/) {
+           $done_header = 1;
+       }
+       elsif (/^author (.*>) (.*)$/) {
+           my ($author_ident, $author_date) = ($1, $2);
+           my ($utc, $off) = ($author_date =~ /^(\d+) ([-+]?\d+)$/);
+           $author_date = show_date($utc, $off);
 
 
-       test "$signoff" = "t" && {
-               offsigner=`git-var GIT_COMMITTER_IDENT | sed -e 's/>.*/>/'`
-               line="Signed-off-by: $offsigner"
-               grep -q "^$line\$" $commsg || {
-                       echo
-                       echo "$line"
-                       echo
-               }
+           print "From: $author_ident\n";
+           print "Date: $author_date\n";
        }
        }
-       echo
-       echo '---'
-       echo
+       next;
+    }
+    unless ($done_subject) {
+       unless ($keep_subject) {
+           s/^\[PATCH[^]]*\]\s*//;
+           s/^/[PATCH$num] /;
+       }
+        print "Subject: $_";
+       $done_subject = 1;
+       next;
+    }
+
+    $last_was_signoff = 0;
+    if (/Signed-off-by:/i) {
+        if ($signoff ne "" && /Signed-off-by:\s*$signoff_pattern$/i) {
+           $signoff_seen = 1;
+       }
+    }
+    print $_;
+}
+if (!$signoff_seen && $signoff ne "") {
+    if (!$last_was_signoff) {
+        print "\n";
+    }
+    print "$signoff\n";
+}
+print "\n---\n\n";
+close FH or die "close $commsg pipe";
+' "$keep_subject" "$num" "$signoff" $commsg
+
        git-diff-tree -p $diff_opts "$commit" | git-apply --stat --summary
        echo
        git-diff-tree -p $diff_opts "$commit" | git-apply --stat --summary
        echo
-       git-cat-file commit "$commit^" | sed -e 's/^tree /applies-to: /' -e q
        git-diff-tree -p $diff_opts "$commit"
        git-diff-tree -p $diff_opts "$commit"
-       echo "---"
+       echo "-- "
        echo "@@GIT_VERSION@@"
 
        echo "@@GIT_VERSION@@"
 
-       case "$mbox" in
-       t)
-               echo
-               ;;
-       esac
+       echo
 }
 
 total=`wc -l <$series | tr -dc "[0-9]"`
 }
 
 total=`wc -l <$series | tr -dc "[0-9]"`
+case "$total,$numbered" in
+1,*)
+       numfmt='' ;;
+*,t)
+       numfmt=`echo "$total" | wc -c`
+       numfmt=$(($numfmt-1))
+       numfmt=" %0${numfmt}d/$total"
+esac
+
 i=1
 while read commit
 do
 i=1
 while read commit
 do
@@ -254,16 +274,13 @@ do
     case "$numbered" in
     '') num= ;;
     *)
     case "$numbered" in
     '') num= ;;
     *)
-       case $total in
-       1) num= ;;
-       *) num=' '`printf "%d/%d" $i $total` ;;
-       esac
+        num=`printf "$numfmt" $i` ;;
     esac
 
     file=`printf '%04d-%stxt' $i "$title"`
     if test '' = "$stdout"
     then
     esac
 
     file=`printf '%04d-%stxt' $i "$title"`
     if test '' = "$stdout"
     then
-           echo "$file"
+           echo "$file"
            process_one >"$outdir$file"
            if test t = "$check"
            then
            process_one >"$outdir$file"
            if test t = "$check"
            then
@@ -274,7 +291,7 @@ do
                :
            fi
     else
                :
            fi
     else
-           echo >&2 "$file"
+           echo >&2 "$file"
            process_one
     fi
     i=`expr "$i" + 1`
            process_one
     fi
     i=`expr "$i" + 1`