aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2008-11-27 19:24:00 -0800
committerJunio C Hamano <gitster@pobox.com>2008-11-27 19:24:00 -0800
commit496db64202b11e66a080873ca9e32f1354dcfd3e (patch)
treed15f31e62f019aee02af3a99a7e4c453ee31857a
parent455d0f5c23a600ff8c94642ca5123f1bd2f1ec0d (diff)
parentbeece9dab8e26c98062351536ce0d871a066790e (diff)
downloadgit-496db64202b11e66a080873ca9e32f1354dcfd3e.tar.gz
git-496db64202b11e66a080873ca9e32f1354dcfd3e.tar.xz
Merge branch 'ph/send-email'
* 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.
-rw-r--r--Documentation/git-send-email.txt28
-rwxr-xr-xgit-send-email.perl246
-rwxr-xr-xt/t9001-send-email.sh8
3 files changed, 207 insertions, 75 deletions
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index 82f505686..acf8bf41d 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -8,7 +8,7 @@ git-send-email - Send a collection of patches as emails
SYNOPSIS
--------
-'git send-email' [options] <file|directory> [... file|directory]
+'git send-email' [options] <file|directory|rev-list options>...
DESCRIPTION
@@ -37,9 +37,23 @@ The --bcc option must be repeated for each user you want on the bcc list.
+
The --cc option must be repeated for each user you want on the cc list.
+--annotate::
+ Review each patch you're about to send in an editor. The setting
+ 'sendemail.multiedit' defines if this will spawn one editor per patch
+ or one for all of them at once.
+
--compose::
Use $GIT_EDITOR, core.editor, $VISUAL, or $EDITOR to edit an
introductory message for the patch series.
++
+When compose is in used, git send-email gets less interactive will use the
+values of the headers you set there. If the body of the email (what you type
+after the headers and a blank line) only contains blank (or GIT: prefixed)
+lines, the summary won't be sent, but git-send-email will still use the
+Headers values if you don't removed them.
++
+If it wasn't able to see a header in the summary it will ask you about it
+interactively after quitting your editor.
--from::
Specify the sender of the emails. This will default to
@@ -183,6 +197,12 @@ Administering
--[no-]validate::
Perform sanity checks on patches.
Currently, validation means the following:
+
+--[no-]format-patch::
+ When an argument may be understood either as a reference or as a file name,
+ choose to understand it as a format-patch argument ('--format-patch')
+ or as a file name ('--no-format-patch'). By default, when such a conflict
+ occurs, git send-email will fail.
+
--
* Warn of patches that contain lines longer than 998 characters; this
@@ -204,6 +224,12 @@ sendemail.aliasfiletype::
Format of the file(s) specified in sendemail.aliasesfile. Must be
one of 'mutt', 'mailrc', 'pine', or 'gnus'.
+sendemail.multiedit::
+ If true (default), a single editor instance will be spawned to edit
+ files you have to edit (patches when '--annotate' is used, and the
+ summary when '--compose' is used). If false, files will be edited one
+ after the other, spawning a new editor each time.
+
Author
------
diff --git a/git-send-email.perl b/git-send-email.perl
index 007e2c6ee..7508f8ff2 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
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 561ae7d0a..617e97d96 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -292,4 +292,12 @@ test_expect_success '--compose adds MIME for utf8 subject' '
grep "^Subject: =?utf-8?q?utf8-s=C3=BCbj=C3=ABct?=" msgtxt1
'
+test_expect_success 'detects ambiguous reference/file conflict' '
+ echo master > master &&
+ git add master &&
+ git commit -m"add master" &&
+ test_must_fail git send-email --dry-run master 2>errors &&
+ grep disambiguate errors
+'
+
test_done