Merge branch 'ph/send-email'
authorJunio C Hamano <gitster@pobox.com>
Fri, 28 Nov 2008 03:24:00 +0000 (19:24 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 28 Nov 2008 03:24:00 +0000 (19:24 -0800)
* ph/send-email:
git send-email: ask less questions when --compose is used.
git send-email: add --annotate option
git send-email: interpret unknown files as revision lists
git send-email: make the message file name more specific.

1  2 
git-send-email.perl
diff --combined git-send-email.perl
index 007e2c6ee12db18152884c5ac9aa26eff224e36e,9039cfde06529847fe62f1fe97eb9240b5fdb1d6..7508f8ff242854462d0b5b505621dd840b551641
@@@ -22,8 -22,12 +22,12 @@@ use Term::ReadLine
  use Getopt::Long;
  use Data::Dumper;
  use Term::ANSIColor;
+ use File::Temp qw/ tempdir /;
+ use Error qw(:try);
  use Git;
  
+ Getopt::Long::Configure qw/ pass_through /;
  package FakeTerm;
  sub new {
        my ($class, $reason) = @_;
@@@ -38,7 -42,7 +42,7 @@@ package main
  
  sub usage {
        print <<EOT;
- git send-email [options] <file | directory>...
+ git send-email [options] <file | directory | rev-list options >
  
    Composing:
      --from                  <str>  * Email From:
@@@ -47,6 -51,7 +51,7 @@@
      --bcc                   <str>  * Email Bcc:
      --subject               <str>  * Email "Subject:"
      --in-reply-to           <str>  * Email "In-Reply-To:"
+     --annotate                     * Review each patch that will be sent in an editor.
      --compose                      * Open an editor for introduction.
  
    Sending:
@@@ -73,6 -78,8 +78,8 @@@
      --quiet                        * Output one line of info per email.
      --dry-run                      * Don't actually send the emails.
      --[no-]validate                * Perform patch sanity checks. Default on.
+     --[no-]format-patch            * understand any non optional arguments as
+                                      `git format-patch` ones.
  
  EOT
        exit(1);
@@@ -124,12 -131,10 +131,10 @@@ my $auth
  sub unique_email_list(@);
  sub cleanup_compose_files();
  
- # Constants (essentially)
- my $compose_filename = ".msg.$$";
  # Variables we fill in automatically, or via prompting:
  my (@to,@cc,@initial_cc,@bcclist,@xh,
-       $initial_reply_to,$initial_subject,@files,$author,$sender,$smtp_authpass,$compose,$time);
+       $initial_reply_to,$initial_subject,@files,
+       $author,$sender,$smtp_authpass,$annotate,$compose,$time);
  
  my $envelope_sender;
  
@@@ -149,6 -154,27 +154,27 @@@ if ($@) 
  
  # Behavior modification variables
  my ($quiet, $dry_run) = (0, 0);
+ my $format_patch;
+ my $compose_filename = $repo->repo_path() . "/.gitsendemail.msg.$$";
+ # Handle interactive edition of files.
+ my $multiedit;
+ my $editor = $ENV{GIT_EDITOR} || Git::config(@repo, "core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
+ sub do_edit {
+       if (defined($multiedit) && !$multiedit) {
+               map {
+                       system('sh', '-c', $editor.' "$@"', $editor, $_);
+                       if (($? & 127) || ($? >> 8)) {
+                               die("the editor exited uncleanly, aborting everything");
+                       }
+               } @_;
+       } else {
+               system('sh', '-c', $editor.' "$@"', $editor, @_);
+               if (($? & 127) || ($? >> 8)) {
+                       die("the editor exited uncleanly, aborting everything");
+               }
+       }
+ }
  
  # Variables with corresponding config settings
  my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc, $cc_cmd);
@@@ -179,6 -205,7 +205,7 @@@ my %config_settings = 
      "aliasesfile" => \@alias_files,
      "suppresscc" => \@suppress_cc,
      "envelopesender" => \$envelope_sender,
+     "multiedit" => \$multiedit,
  );
  
  # Handle Uncouth Termination
@@@ -221,6 -248,7 +248,7 @@@ my $rc = GetOptions("sender|from=s" => 
                    "smtp-ssl" => sub { $smtp_encryption = 'ssl' },
                    "smtp-encryption=s" => \$smtp_encryption,
                    "identity=s" => \$identity,
+                   "annotate" => \$annotate,
                    "compose" => \$compose,
                    "quiet" => \$quiet,
                    "cc-cmd=s" => \$cc_cmd,
                    "envelope-sender=s" => \$envelope_sender,
                    "thread!" => \$thread,
                    "validate!" => \$validate,
+                   "format-patch!" => \$format_patch,
         );
  
  unless ($rc) {
@@@ -345,13 -374,10 +374,13 @@@ my %parse_alias = 
                        # spaces delimit multiple addresses
                        $aliases{$1} = [ split(/\s+/, $2) ];
                }}},
 -      pine => sub { my $fh = shift; while (<$fh>) {
 -              if (/^(\S+)\t.*\t(.*)$/) {
 +      pine => sub { my $fh = shift; my $f='\t[^\t]*';
 +              for (my $x = ''; defined($x); $x = $_) {
 +                      chomp $x;
 +                      $x .= $1 while(defined($_ = <$fh>) && /^ +(.*)$/);
 +                      $x =~ /^(\S+)$f\t\(?([^\t]+?)\)?(:?$f){0,2}$/ or next;
                        $aliases{$1} = [ split(/\s*,\s*/, $2) ];
 -              }}},
 +              }},
        gnus => sub { my $fh = shift; while (<$fh>) {
                if (/\(define-mail-alias\s+"(\S+?)"\s+"(\S+?)"\)/) {
                        $aliases{$1} = [ $2 ];
@@@ -368,23 -394,52 +397,52 @@@ if (@alias_files and $aliasfiletype an
  
  ($sender) = expand_aliases($sender) if defined $sender;
  
+ # returns 1 if the conflict must be solved using it as a format-patch argument
+ sub check_file_rev_conflict($) {
+       my $f = shift;
+       try {
+               $repo->command('rev-parse', '--verify', '--quiet', $f);
+               if (defined($format_patch)) {
+                       print "foo\n";
+                       return $format_patch;
+               }
+               die(<<EOF);
+ File '$f' exists but it could also be the range of commits
+ to produce patches for.  Please disambiguate by...
+     * Saying "./$f" if you mean a file; or
+     * Giving --format-patch option if you mean a range.
+ EOF
+       } catch Git::Error::Command with {
+               return 0;
+       }
+ }
  # Now that all the defaults are set, process the rest of the command line
  # arguments and collect up the files that need to be processed.
- for my $f (@ARGV) {
-       if (-d $f) {
+ my @rev_list_opts;
+ while (my $f = pop @ARGV) {
+       if ($f eq "--") {
+               push @rev_list_opts, "--", @ARGV;
+               @ARGV = ();
+       } elsif (-d $f and !check_file_rev_conflict($f)) {
                opendir(DH,$f)
                        or die "Failed to opendir $f: $!";
  
                push @files, grep { -f $_ } map { +$f . "/" . $_ }
                                sort readdir(DH);
                closedir(DH);
-       } elsif (-f $f or -p $f) {
+       } elsif ((-f $f or -p $f) and !check_file_rev_conflict($f)) {
                push @files, $f;
        } else {
-               print STDERR "Skipping $f - not found.\n";
+               push @rev_list_opts, $f;
        }
  }
  
+ if (@rev_list_opts) {
+       push @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts);
+ }
  if ($validate) {
        foreach my $f (@files) {
                unless (-p $f) {
@@@ -403,6 -458,108 +461,108 @@@ if (@files) 
        usage();
  }
  
+ sub get_patch_subject($) {
+       my $fn = shift;
+       open (my $fh, '<', $fn);
+       while (my $line = <$fh>) {
+               next unless ($line =~ /^Subject: (.*)$/);
+               close $fh;
+               return "GIT: $1\n";
+       }
+       close $fh;
+       die "No subject line in $fn ?";
+ }
+ if ($compose) {
+       # Note that this does not need to be secure, but we will make a small
+       # effort to have it be unique
+       open(C,">",$compose_filename)
+               or die "Failed to open for writing $compose_filename: $!";
+       my $tpl_sender = $sender || $repoauthor || $repocommitter || '';
+       my $tpl_subject = $initial_subject || '';
+       my $tpl_reply_to = $initial_reply_to || '';
+       print C <<EOT;
+ From $tpl_sender # This line is ignored.
+ GIT: Lines beginning in "GIT: " will be removed.
+ GIT: Consider including an overall diffstat or table of contents
+ GIT: for the patch you are writing.
+ GIT:
+ GIT: Clear the body content if you don't wish to send a summary.
+ From: $tpl_sender
+ Subject: $tpl_subject
+ In-Reply-To: $tpl_reply_to
+ EOT
+       for my $f (@files) {
+               print C get_patch_subject($f);
+       }
+       close(C);
+       my $editor = $ENV{GIT_EDITOR} || Git::config(@repo, "core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
+       if ($annotate) {
+               do_edit($compose_filename, @files);
+       } else {
+               do_edit($compose_filename);
+       }
+       open(C2,">",$compose_filename . ".final")
+               or die "Failed to open $compose_filename.final : " . $!;
+       open(C,"<",$compose_filename)
+               or die "Failed to open $compose_filename : " . $!;
+       my $need_8bit_cte = file_has_nonascii($compose_filename);
+       my $in_body = 0;
+       my $summary_empty = 1;
+       while(<C>) {
+               next if m/^GIT: /;
+               if ($in_body) {
+                       $summary_empty = 0 unless (/^\n$/);
+               } elsif (/^\n$/) {
+                       $in_body = 1;
+                       if ($need_8bit_cte) {
+                               print C2 "MIME-Version: 1.0\n",
+                                        "Content-Type: text/plain; ",
+                                          "charset=utf-8\n",
+                                        "Content-Transfer-Encoding: 8bit\n";
+                       }
+               } elsif (/^MIME-Version:/i) {
+                       $need_8bit_cte = 0;
+               } elsif (/^Subject:\s*(.+)\s*$/i) {
+                       $initial_subject = $1;
+                       my $subject = $initial_subject;
+                       $_ = "Subject: " .
+                               ($subject =~ /[^[:ascii:]]/ ?
+                                quote_rfc2047($subject) :
+                                $subject) .
+                               "\n";
+               } elsif (/^In-Reply-To:\s*(.+)\s*$/i) {
+                       $initial_reply_to = $1;
+                       next;
+               } elsif (/^From:\s*(.+)\s*$/i) {
+                       $sender = $1;
+                       next;
+               } elsif (/^(?:To|Cc|Bcc):/i) {
+                       print "To/Cc/Bcc fields are not interpreted yet, they have been ignored\n";
+                       next;
+               }
+               print C2 $_;
+       }
+       close(C);
+       close(C2);
+       if ($summary_empty) {
+               print "Summary email is empty, skipping it\n";
+               $compose = -1;
+       }
+ } elsif ($annotate) {
+       do_edit(@files);
+ }
  my $prompting = 0;
  if (!defined $sender) {
        $sender = $repoauthor || $repocommitter || '';
@@@ -447,17 -604,6 +607,6 @@@ sub expand_aliases 
  @initial_cc = expand_aliases(@initial_cc);
  @bcclist = expand_aliases(@bcclist);
  
- if (!defined $initial_subject && $compose) {
-       while (1) {
-               $_ = $term->readline("What subject should the initial email start with? ", $initial_subject);
-               last if defined $_;
-               print "\n";
-       }
-       $initial_subject = $_;
-       $prompting++;
- }
  if ($thread && !defined $initial_reply_to && $prompting) {
        while (1) {
                $_= $term->readline("Message-ID to be used as In-Reply-To for the first email? ", $initial_reply_to);
@@@ -484,59 -630,6 +633,6 @@@ if (!defined $smtp_server) 
  }
  
  if ($compose) {
-       # Note that this does not need to be secure, but we will make a small
-       # effort to have it be unique
-       open(C,">",$compose_filename)
-               or die "Failed to open for writing $compose_filename: $!";
-       print C "From $sender # This line is ignored.\n";
-       printf C "Subject: %s\n\n", $initial_subject;
-       printf C <<EOT;
- GIT: Please enter your email below.
- GIT: Lines beginning in "GIT: " will be removed.
- GIT: Consider including an overall diffstat or table of contents
- GIT: for the patch you are writing.
- EOT
-       close(C);
-       my $editor = $ENV{GIT_EDITOR} || Git::config(@repo, "core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
-       system('sh', '-c', $editor.' "$@"', $editor, $compose_filename);
-       open(C2,">",$compose_filename . ".final")
-               or die "Failed to open $compose_filename.final : " . $!;
-       open(C,"<",$compose_filename)
-               or die "Failed to open $compose_filename : " . $!;
-       my $need_8bit_cte = file_has_nonascii($compose_filename);
-       my $in_body = 0;
-       while(<C>) {
-               next if m/^GIT: /;
-               if (!$in_body && /^\n$/) {
-                       $in_body = 1;
-                       if ($need_8bit_cte) {
-                               print C2 "MIME-Version: 1.0\n",
-                                        "Content-Type: text/plain; ",
-                                          "charset=utf-8\n",
-                                        "Content-Transfer-Encoding: 8bit\n";
-                       }
-               }
-               if (!$in_body && /^MIME-Version:/i) {
-                       $need_8bit_cte = 0;
-               }
-               if (!$in_body && /^Subject: ?(.*)/i) {
-                       my $subject = $1;
-                       $_ = "Subject: " .
-                               ($subject =~ /[^[:ascii:]]/ ?
-                                quote_rfc2047($subject) :
-                                $subject) .
-                               "\n";
-               }
-               print C2 $_;
-       }
-       close(C);
-       close(C2);
        while (1) {
                $_ = $term->readline("Send this email? (y|n) ");
                last if defined $_;
                exit(0);
        }
  
-       @files = ($compose_filename . ".final", @files);
+       if ($compose > 0) {
+               @files = ($compose_filename . ".final", @files);
+       }
  }
  
  # Variables we set as part of the loop over files