format: Improve handling of commit authorship and mail "From:" headers

Josh Triplett created

Default to the behavior of git format-patch --from, which uses your
committer identity as the "From:" header for the patch mail, and
preserves commit authorship in an in-body "From:" header when formatting
commits you didn't write.  Add a --no-from option to use the commit
author as the "From:" address for each patch mail.

Change summary

git-series.1 | 10 ++++++++++
src/main.rs  | 14 +++++++++++++-
2 files changed, 23 insertions(+), 1 deletion(-)

Detailed changes

git-series.1 🔗

@@ -111,6 +111,7 @@ To start working on the branch again, use \fBgit series checkout\fR.
 
 .TP
 \fBgit series format\fR [\fB--in-reply-to=\fR\fIMessage-Id\fR] \
+[\fB--no-from\fR] \
 [\fB-v\fR \fIN\fR | \fB--reroll-count=\fR\fIN\fR] [\fB--stdout\fR]
 Prepare the patch series to send via email.
 This creates one file per patch in the series, plus one additional file for the
@@ -130,6 +131,15 @@ Make the first mail a reply to the specified Message-Id.
 The Message-Id may include or omit the surrounding angle brackets; git-series
 will add them if not present.
 .TP
+.B --no-from
+By default, \fBgit series format\fR includes a "From:" line in the mail body
+for the commit author when formatting commits you didn't write; this allows you
+to send the patches as emails from your own address while preserving authorship
+information for each patch.
+\fBgit series format --no-from\fR will instead use the commit author as the
+"From:" address for each patch mail.
+Use this when producing patch files for purposes other than email.
+.TP
 \fB-v\fR \fIN\fR | \fB--reroll-count=\fB\fIN\fR
 Mark the patch series as PATCH v\fIN\fR.
 The patch filenames and mail subjects will include the version number.

src/main.rs 🔗

@@ -1070,6 +1070,7 @@ fn ensure_nl(s: &str) -> &'static str {
 fn format(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> {
     let config = try!(repo.config());
     let to_stdout = m.is_present("stdout");
+    let no_from = m.is_present("no-from");
 
     let shead_commit = try!(peel_to_commit(try!(try!(repo.find_reference(SHEAD_REF)).resolve())));
     let stree = try!(shead_commit.tree());
@@ -1165,6 +1166,8 @@ fn format(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> {
         let (subject, body) = split_message(message);
         let commit_id = commit.id();
         let commit_author = commit.author();
+        let commit_author_name = commit_author.name().unwrap();
+        let commit_author_email = commit_author.email().unwrap();
         let summary_sanitized = sanitize_summary(&subject);
         let this_message_id = format!("<{}.{}>", commit_id, message_id_suffix);
         let parent = try!(commit.parent(0));
@@ -1183,9 +1186,17 @@ fn format(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> {
         if commit_num == 0 && cover_entry.is_none() {
             in_reply_to_message_id = Some(this_message_id);
         }
-        try!(writeln!(out, "From: {} <{}>", committer_name, committer_email));
+        if no_from {
+            try!(writeln!(out, "From: {} <{}>", commit_author_name, commit_author_email));
+        } else {
+            try!(writeln!(out, "From: {} <{}>", committer_name, committer_email));
+        }
         try!(writeln!(out, "Date: {}", date_822(commit_author.when())));
         try!(writeln!(out, "Subject: [{} {}/{}] {}\n", subject_patch, commit_num+1, commits.len(), subject));
+
+        if !no_from && (commit_author_name != committer_name || commit_author_email != committer_email) {
+            try!(writeln!(out, "From: {} <{}>\n", commit_author_name, commit_author_email));
+        }
         if !body.is_empty() {
             try!(write!(out, "{}{}", body, ensure_nl(&body)));
         }
@@ -1560,6 +1571,7 @@ fn main() {
                 SubCommand::with_name("format")
                     .about("Prepare patch series for email")
                     .arg_from_usage("--in-reply-to [Message-Id] 'Make the first mail a reply to the specified Message-Id'")
+                    .arg_from_usage("--no-from 'Don't include in-body \"From:\" headers when formatting patches authored by others'")
                     .arg_from_usage("-v, --reroll-count=[N] 'Mark the patch series as PATCH vN'")
                     .arg_from_usage("--stdout 'Write patches to stdout rather than files'"),
                 SubCommand::with_name("log")