git-format-patch.shon commit combine-diff: better hunk splitting. (3ec1909)
   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    -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
  72done
  73
  74case "$keep_subject$numbered" in
  75tt)
  76        die '--keep-subject and --numbered are incompatible.' ;;
  77esac
  78
  79tmp=.tmp-series$$
  80trap 'rm -f $tmp-*' 0 1 2 3 15
  81
  82series=$tmp-series
  83commsg=$tmp-commsg
  84filelist=$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
  97case "$#,$1$2" in
  981,?*..?*)
  99        # single "rev1..rev2"
 100        ;;
 1011,?*..)
 102        # single "rev1.." should mean "rev1..HEAD"
 103        set x "$1"HEAD
 104        shift
 105        ;;
 1061,*)
 107        # single rev1
 108        set x "$1..HEAD"
 109        shift
 110        ;;
 1112,?*..?*)
 112        # not traditional "rev1" "rev2"
 113        ;;
 1142,*)
 115        set x "$1..$2"
 116        shift
 117        ;;
 118esac
 119
 120# Now we have what we want in $@
 121for revpair
 122do
 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
 149done >$series
 150
 151me=`git-var GIT_AUTHOR_IDENT | sed -e 's/>.*/>/'`
 152
 153case "$outdir" in
 154*/) ;;
 155*) outdir="$outdir/" ;;
 156esac
 157test -d "$outdir" || mkdir -p "$outdir" || exit
 158
 159titleScript='
 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
 174process_one () {
 175        perl -w -e '
 176my ($keep_subject, $num, $signoff, $commsg) = @ARGV;
 177my ($signoff_pattern, $done_header, $done_subject, $signoff_seen,
 178    $last_was_signoff);
 179
 180if ($signoff) {
 181        $signoff = "Signed-off-by: " . `git-var GIT_COMMITTER_IDENT`;
 182        $signoff =~ s/>.*/>/;
 183        $signoff_pattern = quotemeta($signoff);
 184}
 185
 186my @weekday_names = qw(Sun Mon Tue Wed Thu Fri Sat);
 187my @month_names = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
 188
 189sub 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
 205print "From nobody Mon Sep 17 00:00:00 2001\n";
 206open FH, "git stripspace <$commsg |" or die "open $commsg pipe";
 207while (<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}
 240if (!$signoff_seen && $signoff ne "") {
 241    if (!$last_was_signoff) {
 242        print "\n";
 243    }
 244    print "$signoff\n";
 245}
 246print "\n---\n\n";
 247close 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
 259total=`wc -l <$series | tr -dc "[0-9]"`
 260case "$total,$numbered" in
 2611,*)
 262        numfmt='' ;;
 263*,t)
 264        numfmt=`echo "$total" | wc -c`
 265        numfmt=$(($numfmt-1))
 266        numfmt=" %0${numfmt}d/$total"
 267esac
 268
 269i=1
 270while read commit
 271do
 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`
 298done <$series