diff options
Diffstat (limited to 'git-send-email.perl')
-rwxr-xr-x | git-send-email.perl | 246 |
1 files changed, 172 insertions, 74 deletions
diff --git a/git-send-email.perl b/git-send-email.perl index 007e2c6ee1..7508f8ff24 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -22,8 +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 @@ 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 @@ git send-email [options] <file | directory>... --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 @@ git send-email [options] <file | directory>... --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 @@ 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 @@ 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 @@ my %config_settings = ( "aliasesfile" => \@alias_files, "suppresscc" => \@suppress_cc, "envelopesender" => \$envelope_sender, + "multiedit" => \$multiedit, ); # Handle Uncouth Termination @@ -221,6 +248,7 @@ my $rc = GetOptions("sender|from=s" => \$sender, "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, @@ -231,6 +259,7 @@ my $rc = GetOptions("sender|from=s" => \$sender, "envelope-sender=s" => \$envelope_sender, "thread!" => \$thread, "validate!" => \$validate, + "format-patch!" => \$format_patch, ); unless ($rc) { @@ -368,23 +397,52 @@ if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) { ($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 +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 +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 +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 $_; @@ -548,7 +644,9 @@ EOT 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 |