summaryrefslogtreecommitdiff
path: root/git-send-email.perl
diff options
context:
space:
mode:
Diffstat (limited to 'git-send-email.perl')
-rwxr-xr-xgit-send-email.perl246
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