]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'dl/format-patch-cover-from-desc'
authorJunio C Hamano <gitster@pobox.com>
Sun, 10 Nov 2019 09:02:11 +0000 (18:02 +0900)
committerJunio C Hamano <gitster@pobox.com>
Sun, 10 Nov 2019 09:02:11 +0000 (18:02 +0900)
The branch description ("git branch --edit-description") has been
used to fill the body of the cover letters by the format-patch
command; this has been enhanced so that the subject can also be
filled.

* dl/format-patch-cover-from-desc:
  format-patch: teach --cover-from-description option
  format-patch: use enum variables
  format-patch: replace erroneous and condition

1  2 
Documentation/config/format.txt
Documentation/git-format-patch.txt
builtin/log.c
t/t4014-format-patch.sh
t/t9902-completion.sh

index 40cad9278fd1734ce5bc7445611dbeb70d00168d,735dfcf8275c1ba827012459e1f8bf1f1b3b2d4e..513fcd88d5b934bedfcb95909cd0593a39687b2e
@@@ -36,6 -36,12 +36,12 @@@ format.subjectPrefix:
        The default for format-patch is to output files with the '[PATCH]'
        subject prefix. Use this variable to change that prefix.
  
+ format.coverFromDescription::
+       The default mode for format-patch to determine which parts of
+       the cover letter will be populated using the branch's
+       description. See the `--cover-from-description` option in
+       linkgit:git-format-patch[1].
  format.signature::
        The default for format-patch is to output a signature containing
        the Git version number. Use this variable to change that default.
@@@ -81,7 -87,7 +87,7 @@@ format.coverLetter:
  
  format.outputDirectory::
        Set a custom directory to store the resulting files instead of the
 -      current working directory.
 +      current working directory. All directory components will be created.
  
  format.useAutoBase::
        A boolean value which lets you enable the `--base=auto` option of
index 2035d4d5d53df801186ee7a713a51634d6ac9fd9,6800e1ab9a3b34e2166813cbb65e1e0ab5dc7f49..00bdf9b1251a1df819545f3da758c51cc25d7bfa
@@@ -19,6 -19,7 +19,7 @@@ SYNOPSI
                   [--start-number <n>] [--numbered-files]
                   [--in-reply-to=<message id>] [--suffix=.<sfx>]
                   [--ignore-if-in-upstream]
+                  [--cover-from-description=<mode>]
                   [--rfc] [--subject-prefix=<subject prefix>]
                   [(--reroll-count|-v) <n>]
                   [--to=<email>] [--cc=<email>]
@@@ -66,8 -67,7 +67,8 @@@ they are created in the current workin
  can be set with the `format.outputDirectory` configuration option.
  The `-o` option takes precedence over `format.outputDirectory`.
  To store patches in the current working directory even when
 -`format.outputDirectory` points elsewhere, use `-o .`.
 +`format.outputDirectory` points elsewhere, use `-o .`. All directory
 +components will be created.
  
  By default, the subject of a single patch is "[PATCH] " followed by
  the concatenation of lines from the commit message up to the first blank
@@@ -172,6 -172,26 +173,26 @@@ will want to ensure that threading is d
        patches being generated, and any patch that matches is
        ignored.
  
+ --cover-from-description=<mode>::
+       Controls which parts of the cover letter will be automatically
+       populated using the branch's description.
+ +
+ If `<mode>` is `message` or `default`, the cover letter subject will be
+ populated with placeholder text. The body of the cover letter will be
+ populated with the branch's description. This is the default mode when
+ no configuration nor command line option is specified.
+ +
+ If `<mode>` is `subject`, the first paragraph of the branch description will
+ populate the cover letter subject. The remainder of the description will
+ populate the body of the cover letter.
+ +
+ If `<mode>` is `auto`, if the first paragraph of the branch description
+ is greater than 100 bytes, then the mode will be `message`, otherwise
+ `subject` will be used.
+ +
+ If `<mode>` is `none`, both the cover letter subject and body will be
+ populated with placeholder text.
  --subject-prefix=<subject prefix>::
        Instead of the standard '[PATCH]' prefix in the subject
        line, instead use '[<subject prefix>]'. This
@@@ -348,6 -368,7 +369,7 @@@ with configuration variables
        signOff = true
        outputDirectory = <directory>
        coverLetter = auto
+       coverFromDescription = auto
  ------------
  
  
diff --combined builtin/log.c
index 89873d2dc2a3dc1e0e50a97e14bf8b1894231593,04be559bd23f57563932deb0a9ac4feb7aa5dd57..a26f223ab4ad9ae8cfe9405fb5ff62dd48509775
@@@ -37,6 -37,7 +37,7 @@@
  #include "range-diff.h"
  
  #define MAIL_DEFAULT_WRAP 72
+ #define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100
  
  /* Set a default date-time format for git log ("log.date" config variable) */
  static const char *default_date_mode = NULL;
@@@ -627,7 -628,6 +628,7 @@@ int cmd_show(int argc, const char **arg
                        break;
                case OBJ_TAG: {
                        struct tag *t = (struct tag *)o;
 +                      struct object_id *oid = get_tagged_oid(t);
  
                        if (rev.shown_one)
                                putchar('\n');
                        rev.shown_one = 1;
                        if (ret)
                                break;
 -                      o = parse_object(the_repository, &t->tagged->oid);
 +                      o = parse_object(the_repository, oid);
                        if (!o)
                                ret = error(_("could not read object %s"),
 -                                          oid_to_hex(&t->tagged->oid));
 +                                          oid_to_hex(oid));
                        objects[i].item = o;
                        i--;
                        break;
@@@ -765,23 -765,51 +766,51 @@@ static void add_header(const char *valu
        item->string[len] = '\0';
  }
  
- #define THREAD_SHALLOW 1
- #define THREAD_DEEP 2
- static int thread;
+ enum cover_setting {
+       COVER_UNSET,
+       COVER_OFF,
+       COVER_ON,
+       COVER_AUTO
+ };
+ enum thread_level {
+       THREAD_UNSET,
+       THREAD_SHALLOW,
+       THREAD_DEEP
+ };
+ enum cover_from_description {
+       COVER_FROM_NONE,
+       COVER_FROM_MESSAGE,
+       COVER_FROM_SUBJECT,
+       COVER_FROM_AUTO
+ };
+ static enum thread_level thread;
  static int do_signoff;
  static int base_auto;
  static char *from;
  static const char *signature = git_version_string;
  static const char *signature_file;
- static int config_cover_letter;
+ static enum cover_setting config_cover_letter;
  static const char *config_output_directory;
+ static enum cover_from_description cover_from_description_mode = COVER_FROM_MESSAGE;
  
- enum {
-       COVER_UNSET,
-       COVER_OFF,
-       COVER_ON,
-       COVER_AUTO
- };
+ static enum cover_from_description parse_cover_from_description(const char *arg)
+ {
+       if (!arg || !strcmp(arg, "default"))
+               return COVER_FROM_MESSAGE;
+       else if (!strcmp(arg, "none"))
+               return COVER_FROM_NONE;
+       else if (!strcmp(arg, "message"))
+               return COVER_FROM_MESSAGE;
+       else if (!strcmp(arg, "subject"))
+               return COVER_FROM_SUBJECT;
+       else if (!strcmp(arg, "auto"))
+               return COVER_FROM_AUTO;
+       else
+               die(_("%s: invalid cover from description mode"), arg);
+ }
  
  static int git_format_config(const char *var, const char *value, void *cb)
  {
                        thread = THREAD_SHALLOW;
                        return 0;
                }
-               thread = git_config_bool(var, value) && THREAD_SHALLOW;
+               thread = git_config_bool(var, value) ? THREAD_SHALLOW : THREAD_UNSET;
                return 0;
        }
        if (!strcmp(var, "format.signoff")) {
                }
                return 0;
        }
+       if (!strcmp(var, "format.coverfromdescription")) {
+               cover_from_description_mode = parse_cover_from_description(value);
+               return 0;
+       }
  
        return git_log_config(var, value, cb);
  }
@@@ -994,20 -1026,6 +1027,6 @@@ static void print_signature(FILE *file
        putc('\n', file);
  }
  
- static void add_branch_description(struct strbuf *buf, const char *branch_name)
- {
-       struct strbuf desc = STRBUF_INIT;
-       if (!branch_name || !*branch_name)
-               return;
-       read_branch_desc(&desc, branch_name);
-       if (desc.len) {
-               strbuf_addch(buf, '\n');
-               strbuf_addbuf(buf, &desc);
-               strbuf_addch(buf, '\n');
-       }
-       strbuf_release(&desc);
- }
  static char *find_branch_name(struct rev_info *rev)
  {
        int i, positive = -1;
@@@ -1054,6 -1072,44 +1073,44 @@@ static void show_diffstat(struct rev_in
        fprintf(rev->diffopt.file, "\n");
  }
  
+ static void prepare_cover_text(struct pretty_print_context *pp,
+                              const char *branch_name,
+                              struct strbuf *sb,
+                              const char *encoding,
+                              int need_8bit_cte)
+ {
+       const char *subject = "*** SUBJECT HERE ***";
+       const char *body = "*** BLURB HERE ***";
+       struct strbuf description_sb = STRBUF_INIT;
+       struct strbuf subject_sb = STRBUF_INIT;
+       if (cover_from_description_mode == COVER_FROM_NONE)
+               goto do_pp;
+       if (branch_name && *branch_name)
+               read_branch_desc(&description_sb, branch_name);
+       if (!description_sb.len)
+               goto do_pp;
+       if (cover_from_description_mode == COVER_FROM_SUBJECT ||
+                       cover_from_description_mode == COVER_FROM_AUTO)
+               body = format_subject(&subject_sb, description_sb.buf, " ");
+       if (cover_from_description_mode == COVER_FROM_MESSAGE ||
+                       (cover_from_description_mode == COVER_FROM_AUTO &&
+                        subject_sb.len > COVER_FROM_AUTO_MAX_SUBJECT_LEN))
+               body = description_sb.buf;
+       else
+               subject = subject_sb.buf;
+ do_pp:
+       pp_title_line(pp, &subject, sb, encoding, need_8bit_cte);
+       pp_remainder(pp, &body, sb, 0);
+       strbuf_release(&description_sb);
+       strbuf_release(&subject_sb);
+ }
  static void make_cover_letter(struct rev_info *rev, int use_stdout,
                              struct commit *origin,
                              int nr, struct commit **list,
                              int quiet)
  {
        const char *committer;
-       const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
-       const char *msg;
        struct shortlog log;
        struct strbuf sb = STRBUF_INIT;
        int i;
        if (!branch_name)
                branch_name = find_branch_name(rev);
  
-       msg = body;
        pp.fmt = CMIT_FMT_EMAIL;
        pp.date_mode.type = DATE_RFC2822;
        pp.rev = rev;
        pp.print_email_subject = 1;
        pp_user_info(&pp, NULL, &sb, committer, encoding);
-       pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
-       pp_remainder(&pp, &msg, &sb, 0);
-       add_branch_description(&sb, branch_name);
+       prepare_cover_text(&pp, branch_name, &sb, encoding, need_8bit_cte);
        fprintf(rev->diffopt.file, "%s\n", sb.buf);
  
        strbuf_release(&sb);
@@@ -1249,9 -1300,9 +1301,9 @@@ static int output_directory_callback(co
  
  static int thread_callback(const struct option *opt, const char *arg, int unset)
  {
-       int *thread = (int *)opt->value;
+       enum thread_level *thread = (enum thread_level *)opt->value;
        if (unset)
-               *thread = 0;
+               *thread = THREAD_UNSET;
        else if (!arg || !strcmp(arg, "shallow"))
                *thread = THREAD_SHALLOW;
        else if (!strcmp(arg, "deep"))
@@@ -1542,6 -1593,7 +1594,7 @@@ int cmd_format_patch(int argc, const ch
        int use_patch_format = 0;
        int quiet = 0;
        int reroll_count = -1;
+       char *cover_from_description_arg = NULL;
        char *branch_name = NULL;
        char *base_commit = NULL;
        struct base_tree_info bases;
                { OPTION_CALLBACK, 0, "rfc", &rev, NULL,
                            N_("Use [RFC PATCH] instead of [PATCH]"),
                            PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback },
+               OPT_STRING(0, "cover-from-description", &cover_from_description_arg,
+                           N_("cover-from-description-mode"),
+                           N_("generate parts of a cover letter based on a branch's description")),
                { OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"),
                            N_("Use [<prefix>] instead of [PATCH]"),
                            PARSE_OPT_NONEG, subject_prefix_callback },
                             PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
                             PARSE_OPT_KEEP_DASHDASH);
  
+       if (cover_from_description_arg)
+               cover_from_description_mode = parse_cover_from_description(cover_from_description_arg);
        if (0 < reroll_count) {
                struct strbuf sprefix = STRBUF_INIT;
                strbuf_addf(&sprefix, "%s v%d",
                setup_pager();
  
        if (output_directory) {
 +              int saved;
                if (rev.diffopt.use_color != GIT_COLOR_ALWAYS)
                        rev.diffopt.use_color = GIT_COLOR_NEVER;
                if (use_stdout)
                        die(_("standard output, or directory, which one?"));
 +              /*
 +               * We consider <outdir> as 'outside of gitdir', therefore avoid
 +               * applying adjust_shared_perm in s-c-l-d.
 +               */
 +              saved = get_shared_repository();
 +              set_shared_repository(0);
 +              switch (safe_create_leading_directories_const(output_directory)) {
 +              case SCLD_OK:
 +              case SCLD_EXISTS:
 +                      break;
 +              default:
 +                      die(_("could not create leading directories "
 +                            "of '%s'"), output_directory);
 +              }
 +              set_shared_repository(saved);
                if (mkdir(output_directory, 0777) < 0 && errno != EEXIST)
                        die_errno(_("could not create directory '%s'"),
                                  output_directory);
diff --combined t/t4014-format-patch.sh
index b8969998b5090984e608391167531cfc771f0ad3,88db01308a3f09f20c764617d44418172b459cbf..69267b16f0baccc642ed3c7a4692b1a0c415d478
@@@ -1517,6 -1517,178 +1517,178 @@@ test_expect_success 'format patch ignor
        test_cmp expect actual
  '
  
+ test_expect_success 'cover letter with invalid --cover-from-description and config' '
+       test_config branch.rebuild-1.description "config subject
+ body" &&
+       test_must_fail git format-patch --cover-letter --cover-from-description garbage master &&
+       test_config format.coverFromDescription garbage &&
+       test_must_fail git format-patch --cover-letter master
+ '
+ test_expect_success 'cover letter with format.coverFromDescription = default' '
+       test_config branch.rebuild-1.description "config subject
+ body" &&
+       test_config format.coverFromDescription default &&
+       git checkout rebuild-1 &&
+       git format-patch --stdout --cover-letter master >actual &&
+       grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+       ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+       grep "^config subject$" actual &&
+       grep "^body$" actual
+ '
+ test_expect_success 'cover letter with --cover-from-description default' '
+       test_config branch.rebuild-1.description "config subject
+ body" &&
+       git checkout rebuild-1 &&
+       git format-patch --stdout --cover-letter --cover-from-description default master >actual &&
+       grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+       ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+       grep "^config subject$" actual &&
+       grep "^body$" actual
+ '
+ test_expect_success 'cover letter with format.coverFromDescription = none' '
+       test_config branch.rebuild-1.description "config subject
+ body" &&
+       test_config format.coverFromDescription none &&
+       git checkout rebuild-1 &&
+       git format-patch --stdout --cover-letter master >actual &&
+       grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+       grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+       ! grep "^config subject$" actual &&
+       ! grep "^body$" actual
+ '
+ test_expect_success 'cover letter with --cover-from-description none' '
+       test_config branch.rebuild-1.description "config subject
+ body" &&
+       git checkout rebuild-1 &&
+       git format-patch --stdout --cover-letter --cover-from-description none master >actual &&
+       grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+       grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+       ! grep "^config subject$" actual &&
+       ! grep "^body$" actual
+ '
+ test_expect_success 'cover letter with format.coverFromDescription = message' '
+       test_config branch.rebuild-1.description "config subject
+ body" &&
+       test_config format.coverFromDescription message &&
+       git checkout rebuild-1 &&
+       git format-patch --stdout --cover-letter master >actual &&
+       grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+       ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+       grep "^config subject$" actual &&
+       grep "^body$" actual
+ '
+ test_expect_success 'cover letter with --cover-from-description message' '
+       test_config branch.rebuild-1.description "config subject
+ body" &&
+       git checkout rebuild-1 &&
+       git format-patch --stdout --cover-letter --cover-from-description message master >actual &&
+       grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+       ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+       grep "^config subject$" actual &&
+       grep "^body$" actual
+ '
+ test_expect_success 'cover letter with format.coverFromDescription = subject' '
+       test_config branch.rebuild-1.description "config subject
+ body" &&
+       test_config format.coverFromDescription subject &&
+       git checkout rebuild-1 &&
+       git format-patch --stdout --cover-letter master >actual &&
+       grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
+       ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+       ! grep "^config subject$" actual &&
+       grep "^body$" actual
+ '
+ test_expect_success 'cover letter with --cover-from-description subject' '
+       test_config branch.rebuild-1.description "config subject
+ body" &&
+       git checkout rebuild-1 &&
+       git format-patch --stdout --cover-letter --cover-from-description subject master >actual &&
+       grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
+       ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+       ! grep "^config subject$" actual &&
+       grep "^body$" actual
+ '
+ test_expect_success 'cover letter with format.coverFromDescription = auto (short subject line)' '
+       test_config branch.rebuild-1.description "config subject
+ body" &&
+       test_config format.coverFromDescription auto &&
+       git checkout rebuild-1 &&
+       git format-patch --stdout --cover-letter master >actual &&
+       grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
+       ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+       ! grep "^config subject$" actual &&
+       grep "^body$" actual
+ '
+ test_expect_success 'cover letter with --cover-from-description auto (short subject line)' '
+       test_config branch.rebuild-1.description "config subject
+ body" &&
+       git checkout rebuild-1 &&
+       git format-patch --stdout --cover-letter --cover-from-description auto master >actual &&
+       grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
+       ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+       ! grep "^config subject$" actual &&
+       grep "^body$" actual
+ '
+ test_expect_success 'cover letter with format.coverFromDescription = auto (long subject line)' '
+       test_config branch.rebuild-1.description "this is a really long first line and it is over 100 characters long which is the threshold for long subjects
+ body" &&
+       test_config format.coverFromDescription auto &&
+       git checkout rebuild-1 &&
+       git format-patch --stdout --cover-letter master >actual &&
+       grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+       ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+       grep "^this is a really long first line and it is over 100 characters long which is the threshold for long subjects$" actual &&
+       grep "^body$" actual
+ '
+ test_expect_success 'cover letter with --cover-from-description auto (long subject line)' '
+       test_config branch.rebuild-1.description "this is a really long first line and it is over 100 characters long which is the threshold for long subjects
+ body" &&
+       git checkout rebuild-1 &&
+       git format-patch --stdout --cover-letter --cover-from-description auto master >actual &&
+       grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+       ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+       grep "^this is a really long first line and it is over 100 characters long which is the threshold for long subjects$" actual &&
+       grep "^body$" actual
+ '
+ test_expect_success 'cover letter with command-line --cover-from-description overrides config' '
+       test_config branch.rebuild-1.description "config subject
+ body" &&
+       test_config format.coverFromDescription none &&
+       git checkout rebuild-1 &&
+       git format-patch --stdout --cover-letter --cover-from-description subject master >actual &&
+       grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
+       ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+       ! grep "^config subject$" actual &&
+       grep "^body$" actual
+ '
  test_expect_success 'cover letter using branch description (1)' '
        git checkout rebuild-1 &&
        test_config branch.rebuild-1.description hello &&
@@@ -1606,32 -1778,6 +1778,32 @@@ test_expect_success 'From line has expe
        test_cmp from filtered
  '
  
 +test_expect_success 'format-patch -o with no leading directories' '
 +      rm -fr patches &&
 +      git format-patch -o patches master..side &&
 +      count=$(git rev-list --count master..side) &&
 +      ls patches >list &&
 +      test_line_count = $count list
 +'
 +
 +test_expect_success 'format-patch -o with leading existing directories' '
 +      rm -rf existing-dir &&
 +      mkdir existing-dir &&
 +      git format-patch -o existing-dir/patches master..side &&
 +      count=$(git rev-list --count master..side) &&
 +      ls existing-dir/patches >list &&
 +      test_line_count = $count list
 +'
 +
 +test_expect_success 'format-patch -o with leading non-existing directories' '
 +      rm -rf non-existing-dir &&
 +      git format-patch -o non-existing-dir/patches master..side &&
 +      count=$(git rev-list --count master..side) &&
 +      test_path_is_dir non-existing-dir &&
 +      ls non-existing-dir/patches >list &&
 +      test_line_count = $count list
 +'
 +
  test_expect_success 'format-patch format.outputDirectory option' '
        test_config format.outputDirectory patches &&
        rm -fr patches &&
diff --combined t/t9902-completion.sh
index 54f8ce18cb9e510edaf7d21f70ddb075a55304e3,5187e2ede581f12c1ebe840943cb48f889b9ad44..e90ac565e13d9432b5fe5c762a63adfc8181b9fd
@@@ -28,10 -28,10 +28,10 @@@ complete (
  #
  # (2) A test makes sure that common subcommands are included in the
  #     completion for "git <TAB>", and a plumbing is excluded.  "add",
 -#     "filter-branch" and "ls-files" are listed for this.
 +#     "rebase" and "ls-files" are listed for this.
  
 -GIT_TESTING_ALL_COMMAND_LIST='add checkout check-attr filter-branch ls-files'
 -GIT_TESTING_PORCELAIN_COMMAND_LIST='add checkout filter-branch'
 +GIT_TESTING_ALL_COMMAND_LIST='add checkout check-attr rebase ls-files'
 +GIT_TESTING_PORCELAIN_COMMAND_LIST='add checkout rebase'
  
  . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash"
  
@@@ -1392,12 -1392,12 +1392,12 @@@ test_expect_success 'basic' 
        # built-in
        grep -q "^add \$" out &&
        # script
 -      grep -q "^filter-branch \$" out &&
 +      grep -q "^rebase \$" out &&
        # plumbing
        ! grep -q "^ls-files \$" out &&
  
 -      run_completion "git f" &&
 -      ! grep -q -v "^f" out
 +      run_completion "git r" &&
 +      ! grep -q -v "^r" out
  '
  
  test_expect_success 'double dash "git" itself' '
@@@ -1548,7 -1548,10 +1548,10 @@@ test_expect_success 'complete tree file
  '
  
  test_expect_success PERL 'send-email' '
-       test_completion "git send-email --cov" "--cover-letter " &&
+       test_completion "git send-email --cov" <<-\EOF &&
+       --cover-from-description=Z
+       --cover-letter Z
+       EOF
        test_completion "git send-email ma" "master "
  '
  
        '
  done
  
 +test_expect_success 'git config - section' '
 +      test_completion "git config br" <<-\EOF
 +      branch.Z
 +      browser.Z
 +      EOF
 +'
 +
 +test_expect_success 'git config - variable name' '
 +      test_completion "git config log.d" <<-\EOF
 +      log.date Z
 +      log.decorate Z
 +      EOF
 +'
 +
 +test_expect_success 'git config - value' '
 +      test_completion "git config color.pager " <<-\EOF
 +      false Z
 +      true Z
 +      EOF
 +'
 +
 +test_expect_success 'git -c - section' '
 +      test_completion "git -c br" <<-\EOF
 +      branch.Z
 +      browser.Z
 +      EOF
 +'
 +
 +test_expect_success 'git -c - variable name' '
 +      test_completion "git -c log.d" <<-\EOF
 +      log.date=Z
 +      log.decorate=Z
 +      EOF
 +'
 +
 +test_expect_success 'git -c - value' '
 +      test_completion "git -c color.pager=" <<-\EOF
 +      false Z
 +      true Z
 +      EOF
 +'
 +
 +test_expect_success 'git clone --config= - section' '
 +      test_completion "git clone --config=br" <<-\EOF
 +      branch.Z
 +      browser.Z
 +      EOF
 +'
 +
 +test_expect_success 'git clone --config= - variable name' '
 +      test_completion "git clone --config=log.d" <<-\EOF
 +      log.date=Z
 +      log.decorate=Z
 +      EOF
 +'
 +
 +test_expect_success 'git clone --config= - value' '
 +      test_completion "git clone --config=color.pager=" <<-\EOF
 +      false Z
 +      true Z
 +      EOF
 +'
 +
  test_expect_success 'sourcing the completion script clears cached commands' '
        __git_compute_all_commands &&
        verbose test -n "$__git_all_commands" &&