Merge branch 'fc/send-email-annotate'
authorJunio C Hamano <gitster@pobox.com>
Thu, 18 Apr 2013 18:49:11 +0000 (11:49 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 18 Apr 2013 18:49:11 +0000 (11:49 -0700)
Allows format-patch --cover-letter to be configurable; the most
notable is the "auto" mode to create cover-letter only for multi
patch series.

* fc/send-email-annotate:
rebase-am: explicitly disable cover-letter
format-patch: trivial cleanups
format-patch: add format.coverLetter configuration variable
log: update to OPT_BOOL
format-patch: refactor branch name calculation
format-patch: improve head calculation for cover-letter
send-email: make annotate configurable

Documentation/config.txt
Documentation/git-format-patch.txt
Documentation/git-send-email.txt
builtin/log.c
git-rebase--am.sh
git-send-email.perl
t/t4014-format-patch.sh
index 3d750e0452308a4d3bf15053bfc5131d43c8c913..42b0f3ba42d60d8f321808a0f3f02d7195ecc5c0 100644 (file)
@@ -1109,6 +1109,11 @@ format.signoff::
     the rights to submit this work under the same open source license.
     Please see the 'SubmittingPatches' document for further discussion.
 
+format.coverLetter::
+       A boolean that controls whether to generate a cover-letter when
+       format-patch is invoked, but in addition can be set to "auto", to
+       generate a cover-letter only when there's more than one patch.
+
 filter.<driver>.clean::
        The command which is used to convert the content of a worktree
        file to a blob upon checkin.  See linkgit:gitattributes[5] for
@@ -2016,6 +2021,7 @@ sendemail.<identity>.*::
 
 sendemail.aliasesfile::
 sendemail.aliasfiletype::
+sendemail.annotate::
 sendemail.bcc::
 sendemail.cc::
 sendemail.cccmd::
index 3a62f50edae9cdc772cb1009cf839b18e0079a9c..39118774afbbd730e4464f44e24a05af91432c30 100644 (file)
@@ -20,7 +20,7 @@ SYNOPSIS
                   [--ignore-if-in-upstream]
                   [--subject-prefix=Subject-Prefix] [(--reroll-count|-v) <n>]
                   [--to=<email>] [--cc=<email>]
-                  [--cover-letter] [--quiet] [--notes[=<ref>]]
+                  [--[no-]cover-letter] [--quiet] [--notes[=<ref>]]
                   [<common diff options>]
                   [ <since> | <revision range> ]
 
@@ -195,7 +195,7 @@ will want to ensure that threading is disabled for `git send-email`.
        `Cc:`, and custom) headers added so far from config or command
        line.
 
---cover-letter::
+--[no-]cover-letter::
        In addition to the patches, generate a cover letter file
        containing the shortlog and the overall diffstat.  You can
        fill in a description in the file before sending it out.
@@ -260,6 +260,7 @@ attachments, and sign off patches with configuration variables.
        cc = <email>
        attach [ = mime-boundary-string ]
        signoff = true
+       coverletter = auto
 ------------
 
 
index 0cffef8aa51cc5c2f34393e39e45e84c4997b195..40a9a9abc14f175738986b244207655b296cc31d 100644 (file)
@@ -45,8 +45,9 @@ Composing
 ~~~~~~~~~
 
 --annotate::
-       Review and edit each patch you're about to send. See the
-       CONFIGURATION section for 'sendemail.multiedit'.
+       Review and edit each patch you're about to send. Default is the value
+       of 'sendemail.annotate'. See the CONFIGURATION section for
+       'sendemail.multiedit'.
 
 --bcc=<address>::
        Specify a "Bcc:" value for each email. Default is the value of
index 0f318107e5732a08bc6f979633829ea958d8f9b0..ad46f72950f3e4fda292da25f936e48c59705410 100644 (file)
@@ -100,9 +100,9 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
        int quiet = 0, source = 0, mailmap = 0;
 
        const struct option builtin_log_options[] = {
-               OPT_BOOLEAN(0, "quiet", &quiet, N_("suppress diff output")),
-               OPT_BOOLEAN(0, "source", &source, N_("show source")),
-               OPT_BOOLEAN(0, "use-mailmap", &mailmap, N_("Use mail map file")),
+               OPT_BOOL(0, "quiet", &quiet, N_("suppress diff output")),
+               OPT_BOOL(0, "source", &source, N_("show source")),
+               OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")),
                { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
                  PARSE_OPT_OPTARG, decorate_callback},
                OPT_END()
@@ -622,6 +622,14 @@ static void add_header(const char *value)
 static int thread;
 static int do_signoff;
 static const char *signature = git_version_string;
+static int config_cover_letter;
+
+enum {
+       COVER_UNSET,
+       COVER_OFF,
+       COVER_ON,
+       COVER_AUTO
+};
 
 static int git_format_config(const char *var, const char *value, void *cb)
 {
@@ -683,6 +691,14 @@ static int git_format_config(const char *var, const char *value, void *cb)
        }
        if (!strcmp(var, "format.signature"))
                return git_config_string(&signature, var, value);
+       if (!strcmp(var, "format.coverletter")) {
+               if (value && !strcasecmp(value, "auto")) {
+                       config_cover_letter = COVER_AUTO;
+                       return 0;
+               }
+               config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
+               return 0;
+       }
 
        return git_log_config(var, value, cb);
 }
@@ -794,9 +810,37 @@ static void add_branch_description(struct strbuf *buf, const char *branch_name)
        }
 }
 
+static char *find_branch_name(struct rev_info *rev)
+{
+       int i, positive = -1;
+       unsigned char branch_sha1[20];
+       const unsigned char *tip_sha1;
+       const char *ref;
+       char *full_ref, *branch = NULL;
+
+       for (i = 0; i < rev->cmdline.nr; i++) {
+               if (rev->cmdline.rev[i].flags & UNINTERESTING)
+                       continue;
+               if (positive < 0)
+                       positive = i;
+               else
+                       return NULL;
+       }
+       if (positive < 0)
+               return NULL;
+       ref = rev->cmdline.rev[positive].name;
+       tip_sha1 = rev->cmdline.rev[positive].item->sha1;
+       if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) &&
+           !prefixcmp(full_ref, "refs/heads/") &&
+           !hashcmp(tip_sha1, branch_sha1))
+               branch = xstrdup(full_ref + strlen("refs/heads/"));
+       free(full_ref);
+       return branch;
+}
+
 static void make_cover_letter(struct rev_info *rev, int use_stdout,
                              struct commit *origin,
-                             int nr, struct commit **list, struct commit *head,
+                             int nr, struct commit **list,
                              const char *branch_name,
                              int quiet)
 {
@@ -810,6 +854,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
        struct diff_options opts;
        int need_8bit_cte = 0;
        struct pretty_print_context pp = {0};
+       struct commit *head = list[0];
 
        if (rev->commit_format != CMIT_FMT_EMAIL)
                die(_("Cover letter needs email format"));
@@ -827,6 +872,9 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
                if (has_non_ascii(list[i]->buffer))
                        need_8bit_cte = 1;
 
+       if (!branch_name)
+               branch_name = find_branch_name(rev);
+
        msg = body;
        pp.fmt = CMIT_FMT_EMAIL;
        pp.date_mode = DATE_RFC2822;
@@ -1033,45 +1081,6 @@ static int cc_callback(const struct option *opt, const char *arg, int unset)
        return 0;
 }
 
-static char *find_branch_name(struct rev_info *rev)
-{
-       int i, positive = -1;
-       unsigned char branch_sha1[20];
-       const unsigned char *tip_sha1;
-       const char *ref;
-       char *full_ref, *branch = NULL;
-
-       for (i = 0; i < rev->cmdline.nr; i++) {
-               if (rev->cmdline.rev[i].flags & UNINTERESTING)
-                       continue;
-               if (positive < 0)
-                       positive = i;
-               else
-                       return NULL;
-       }
-       if (0 <= positive) {
-               ref = rev->cmdline.rev[positive].name;
-               tip_sha1 = rev->cmdline.rev[positive].item->sha1;
-       } else if (!rev->cmdline.nr && rev->pending.nr == 1 &&
-                  !strcmp(rev->pending.objects[0].name, "HEAD")) {
-               /*
-                * No actual ref from command line, but "HEAD" from
-                * rev->def was added in setup_revisions()
-                * e.g. format-patch --cover-letter -12
-                */
-               ref = "HEAD";
-               tip_sha1 = rev->pending.objects[0].item->sha1;
-       } else {
-               return NULL;
-       }
-       if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) &&
-           !prefixcmp(full_ref, "refs/heads/") &&
-           !hashcmp(tip_sha1, branch_sha1))
-               branch = xstrdup(full_ref + strlen("refs/heads/"));
-       free(full_ref);
-       return branch;
-}
-
 int cmd_format_patch(int argc, const char **argv, const char *prefix)
 {
        struct commit *commit;
@@ -1083,10 +1092,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        int start_number = -1;
        int just_numbers = 0;
        int ignore_if_in_upstream = 0;
-       int cover_letter = 0;
+       int cover_letter = -1;
        int boundary_count = 0;
        int no_binary_diff = 0;
-       struct commit *origin = NULL, *head = NULL;
+       struct commit *origin = NULL;
        const char *in_reply_to = NULL;
        struct patch_ids ids;
        struct strbuf buf = STRBUF_INIT;
@@ -1101,12 +1110,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                { OPTION_CALLBACK, 'N', "no-numbered", &numbered, NULL,
                            N_("use [PATCH] even with multiple patches"),
                            PARSE_OPT_NOARG, no_numbered_callback },
-               OPT_BOOLEAN('s', "signoff", &do_signoff, N_("add Signed-off-by:")),
-               OPT_BOOLEAN(0, "stdout", &use_stdout,
+               OPT_BOOL('s', "signoff", &do_signoff, N_("add Signed-off-by:")),
+               OPT_BOOL(0, "stdout", &use_stdout,
                            N_("print patches to standard out")),
-               OPT_BOOLEAN(0, "cover-letter", &cover_letter,
+               OPT_BOOL(0, "cover-letter", &cover_letter,
                            N_("generate a cover letter")),
-               OPT_BOOLEAN(0, "numbered-files", &just_numbers,
+               OPT_BOOL(0, "numbered-files", &just_numbers,
                            N_("use simple number sequence for output file names")),
                OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"),
                            N_("use <sfx> instead of '.patch'")),
@@ -1280,28 +1289,36 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        }
 
        if (rev.pending.nr == 1) {
+               int check_head = 0;
+
                if (rev.max_count < 0 && !rev.show_root_diff) {
                        /*
                         * This is traditional behaviour of "git format-patch
                         * origin" that prepares what the origin side still
                         * does not have.
                         */
-                       unsigned char sha1[20];
-                       const char *ref;
-
                        rev.pending.objects[0].item->flags |= UNINTERESTING;
                        add_head_to_pending(&rev);
-                       ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL);
-                       if (ref && !prefixcmp(ref, "refs/heads/"))
-                               branch_name = xstrdup(ref + strlen("refs/heads/"));
-                       else
-                               branch_name = xstrdup(""); /* no branch */
+                       check_head = 1;
                }
                /*
                 * Otherwise, it is "format-patch -22 HEAD", and/or
                 * "format-patch --root HEAD".  The user wants
                 * get_revision() to do the usual traversal.
                 */
+
+               if (!strcmp(rev.pending.objects[0].name, "HEAD"))
+                       check_head = 1;
+
+               if (check_head) {
+                       unsigned char sha1[20];
+                       const char *ref;
+                       ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL);
+                       if (ref && !prefixcmp(ref, "refs/heads/"))
+                               branch_name = xstrdup(ref + strlen("refs/heads/"));
+                       else
+                               branch_name = xstrdup(""); /* no branch */
+               }
        }
 
        /*
@@ -1310,29 +1327,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
         */
        rev.show_root_diff = 1;
 
-       if (cover_letter) {
-               /*
-                * NEEDSWORK:randomly pick one positive commit to show
-                * diffstat; this is often the tip and the command
-                * happens to do the right thing in most cases, but a
-                * complex command like "--cover-letter a b c ^bottom"
-                * picks "c" and shows diffstat between bottom..c
-                * which may not match what the series represents at
-                * all and totally broken.
-                */
-               int i;
-               for (i = 0; i < rev.pending.nr; i++) {
-                       struct object *o = rev.pending.objects[i].item;
-                       if (!(o->flags & UNINTERESTING))
-                               head = (struct commit *)o;
-               }
-               /* There is nothing to show; it is not an error, though. */
-               if (!head)
-                       return 0;
-               if (!branch_name)
-                       branch_name = find_branch_name(&rev);
-       }
-
        if (ignore_if_in_upstream) {
                /* Don't say anything if head and upstream are the same. */
                if (rev.pending.nr == 2) {
@@ -1364,11 +1358,21 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                list = xrealloc(list, nr * sizeof(list[0]));
                list[nr - 1] = commit;
        }
+       if (nr == 0)
+               /* nothing to do */
+               return 0;
        total = nr;
        if (!keep_subject && auto_number && total > 1)
                numbered = 1;
        if (numbered)
                rev.total = total + start_number - 1;
+       if (cover_letter == -1) {
+               if (config_cover_letter == COVER_AUTO)
+                       cover_letter = (total > 1);
+               else
+                       cover_letter = (config_cover_letter == COVER_ON);
+       }
+
        if (in_reply_to || thread || cover_letter)
                rev.ref_message_ids = xcalloc(1, sizeof(struct string_list));
        if (in_reply_to) {
@@ -1381,7 +1385,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                if (thread)
                        gen_message_id(&rev, "cover");
                make_cover_letter(&rev, use_stdout,
-                                 origin, nr, list, head, branch_name, quiet);
+                                 origin, nr, list, branch_name, quiet);
                total++;
                start_number--;
        }
index 97f31dc7af43bafe36d3222ba2f78da861a4e620..f84854f09a14b92790bad543cbe78c2662def9f7 100644 (file)
@@ -31,8 +31,8 @@ else
        rm -f "$GIT_DIR/rebased-patches"
 
        git format-patch -k --stdout --full-index --ignore-if-in-upstream \
-               --src-prefix=a/ --dst-prefix=b/ \
-               --no-renames $root_flag "$revisions" >"$GIT_DIR/rebased-patches"
+               --src-prefix=a/ --dst-prefix=b/ --no-renames --no-cover-letter \
+               $root_flag "$revisions" >"$GIT_DIR/rebased-patches"
        ret=$?
 
        if test 0 != $ret
index 729747281864155bee7ef009de3ab92cc7643a89..bd13cc812d2a0115edcdd2c3ec146665ddfc5e29 100755 (executable)
@@ -54,7 +54,7 @@ sub usage {
     --[no-]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.
+    --[no-]annotate                * Review each patch that will be sent in an editor.
     --compose                      * Open an editor for introduction.
     --compose-encoding      <str>  * Encoding to assume for introduction.
     --8bit-encoding         <str>  * Encoding to assume 8bit mails if undeclared
@@ -212,7 +212,8 @@ sub do_edit {
     "signedoffbycc" => [\$signed_off_by_cc, undef],
     "signedoffcc" => [\$signed_off_by_cc, undef],      # Deprecated
     "validate" => [\$validate, 1],
-    "multiedit" => [\$multiedit, undef]
+    "multiedit" => [\$multiedit, undef],
+    "annotate" => [\$annotate, undef]
 );
 
 my %config_settings = (
@@ -304,7 +305,7 @@ sub signal_handler {
                    "smtp-debug:i" => \$debug_net_smtp,
                    "smtp-domain:s" => \$smtp_domain,
                    "identity=s" => \$identity,
-                   "annotate" => \$annotate,
+                   "annotate!" => \$annotate,
                    "compose" => \$compose,
                    "quiet" => \$quiet,
                    "cc-cmd=s" => \$cc_cmd,
index 86ee0771077a9da0404a276873c81dd17600ceb4..58d418098d793b284bb4a7222cf9709c45b26c90 100755 (executable)
@@ -1284,4 +1284,37 @@ test_expect_success 'cover letter using branch description (6)' '
        grep hello actual >/dev/null
 '
 
+test_expect_success 'cover letter with nothing' '
+       git format-patch --stdout --cover-letter >actual &&
+       test_line_count = 0 actual
+'
+
+test_expect_success 'cover letter auto' '
+       mkdir -p tmp &&
+       test_when_finished "rm -rf tmp;
+               git config --unset format.coverletter" &&
+
+       git config format.coverletter auto &&
+       git format-patch -o tmp -1 >list &&
+       test_line_count = 1 list &&
+       git format-patch -o tmp -2 >list &&
+       test_line_count = 3 list
+'
+
+test_expect_success 'cover letter auto user override' '
+       mkdir -p tmp &&
+       test_when_finished "rm -rf tmp;
+               git config --unset format.coverletter" &&
+
+       git config format.coverletter auto &&
+       git format-patch -o tmp --cover-letter -1 >list &&
+       test_line_count = 2 list &&
+       git format-patch -o tmp --cover-letter -2 >list &&
+       test_line_count = 3 list &&
+       git format-patch -o tmp --no-cover-letter -1 >list &&
+       test_line_count = 1 list &&
+       git format-patch -o tmp --no-cover-letter -2 >list &&
+       test_line_count = 2 list
+'
+
 test_done