Merge branch 'jk/submodule-c-credential'
authorJunio C Hamano <gitster@pobox.com>
Wed, 6 Apr 2016 18:39:12 +0000 (11:39 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 6 Apr 2016 18:39:12 +0000 (11:39 -0700)
"git -c credential.<var>=<value> submodule" can now be used to
propagate configuration variables related to credential helper
down to the submodules.

* jk/submodule-c-credential:
git_config_push_parameter: handle empty GIT_CONFIG_PARAMETERS
git: submodule honor -c credential.* from command line
quote: implement sq_quotef()
submodule: fix segmentation fault in submodule--helper clone
submodule: fix submodule--helper clone usage
submodule: check argc count for git submodule--helper clone
submodule: don't pass empty string arguments to submodule--helper clone

1  2 
builtin/submodule--helper.c
config.c
git-submodule.sh
t/t1300-repo-config.sh
index 72e804ed4fa23b4c9b1bb0db1e0f59e9964eba45,3d37c3f1822db14d376a19afd443c3082e8c5a8e..d36e8a0ec43af81a9c55d772dc99b54de7f7a58d
@@@ -22,12 -22,17 +22,12 @@@ static int module_list_compute(int argc
                               struct module_list *list)
  {
        int i, result = 0;
 -      char *max_prefix, *ps_matched = NULL;
 -      int max_prefix_len;
 +      char *ps_matched = NULL;
        parse_pathspec(pathspec, 0,
                       PATHSPEC_PREFER_FULL |
                       PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
                       prefix, argv);
  
 -      /* Find common prefix for all pathspec's */
 -      max_prefix = common_prefix(pathspec);
 -      max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
 -
        if (pathspec->nr)
                ps_matched = xcalloc(pathspec->nr, 1);
  
@@@ -37,9 -42,9 +37,9 @@@
        for (i = 0; i < active_nr; i++) {
                const struct cache_entry *ce = active_cache[i];
  
 -              if (!S_ISGITLINK(ce->ce_mode) ||
 -                  !match_pathspec(pathspec, ce->name, ce_namelen(ce),
 -                                  max_prefix_len, ps_matched, 1))
 +              if (!match_pathspec(pathspec, ce->name, ce_namelen(ce),
 +                                  0, ps_matched, 1) ||
 +                  !S_ISGITLINK(ce->ce_mode))
                        continue;
  
                ALLOC_GROW(list->entries, list->nr + 1, list->alloc);
@@@ -52,6 -57,7 +52,6 @@@
                         */
                        i++;
        }
 -      free(max_prefix);
  
        if (ps_matched && report_path_error(ps_matched, pathspec, prefix))
                result = -1;
@@@ -118,6 -124,55 +118,55 @@@ static int module_name(int argc, const 
  
        return 0;
  }
+ /*
+  * Rules to sanitize configuration variables that are Ok to be passed into
+  * submodule operations from the parent project using "-c". Should only
+  * include keys which are both (a) safe and (b) necessary for proper
+  * operation.
+  */
+ static int submodule_config_ok(const char *var)
+ {
+       if (starts_with(var, "credential."))
+               return 1;
+       return 0;
+ }
+ static int sanitize_submodule_config(const char *var, const char *value, void *data)
+ {
+       struct strbuf *out = data;
+       if (submodule_config_ok(var)) {
+               if (out->len)
+                       strbuf_addch(out, ' ');
+               if (value)
+                       sq_quotef(out, "%s=%s", var, value);
+               else
+                       sq_quote_buf(out, var);
+       }
+       return 0;
+ }
+ static void prepare_submodule_repo_env(struct argv_array *out)
+ {
+       const char * const *var;
+       for (var = local_repo_env; *var; var++) {
+               if (!strcmp(*var, CONFIG_DATA_ENVIRONMENT)) {
+                       struct strbuf sanitized_config = STRBUF_INIT;
+                       git_config_from_parameters(sanitize_submodule_config,
+                                                  &sanitized_config);
+                       argv_array_pushf(out, "%s=%s", *var, sanitized_config.buf);
+                       strbuf_release(&sanitized_config);
+               } else {
+                       argv_array_push(out, *var);
+               }
+       }
+ }
  static int clone_submodule(const char *path, const char *gitdir, const char *url,
                           const char *depth, const char *reference, int quiet)
  {
        argv_array_push(&cp.args, path);
  
        cp.git_cmd = 1;
-       cp.env = local_repo_env;
+       prepare_submodule_repo_env(&cp.env_array);
        cp.no_stdin = 1;
  
        return run_command(&cp);
@@@ -180,14 -235,18 +229,18 @@@ static int module_clone(int argc, cons
  
        const char *const git_submodule_helper_usage[] = {
                N_("git submodule--helper clone [--prefix=<path>] [--quiet] "
-                  "[--reference <repository>] [--name <name>] [--url <url>]"
-                  "[--depth <depth>] [--] [<path>...]"),
+                  "[--reference <repository>] [--name <name>] [--depth <depth>] "
+                  "--url <url> --path <path>"),
                NULL
        };
  
        argc = parse_options(argc, argv, prefix, module_clone_options,
                             git_submodule_helper_usage, 0);
  
+       if (argc || !url || !path)
+               usage_with_options(git_submodule_helper_usage,
+                                  module_clone_options);
        strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name);
        sm_gitdir = strbuf_detach(&sb, NULL);
  
        return 0;
  }
  
+ static int module_sanitize_config(int argc, const char **argv, const char *prefix)
+ {
+       struct strbuf sanitized_config = STRBUF_INIT;
+       if (argc > 1)
+               usage(_("git submodule--helper sanitize-config"));
+       git_config_from_parameters(sanitize_submodule_config, &sanitized_config);
+       if (sanitized_config.len)
+               printf("%s\n", sanitized_config.buf);
+       strbuf_release(&sanitized_config);
+       return 0;
+ }
 +struct submodule_update_clone {
 +      /* index into 'list', the list of submodules to look into for cloning */
 +      int current;
 +      struct module_list list;
 +      unsigned warn_if_uninitialized : 1;
 +
 +      /* update parameter passed via commandline */
 +      struct submodule_update_strategy update;
 +
 +      /* configuration parameters which are passed on to the children */
 +      int quiet;
 +      const char *reference;
 +      const char *depth;
 +      const char *recursive_prefix;
 +      const char *prefix;
 +
 +      /* Machine-readable status lines to be consumed by git-submodule.sh */
 +      struct string_list projectlines;
 +
 +      /* If we want to stop as fast as possible and return an error */
 +      unsigned quickstop : 1;
 +};
 +#define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
 +      SUBMODULE_UPDATE_STRATEGY_INIT, 0, NULL, NULL, NULL, NULL, \
 +      STRING_LIST_INIT_DUP, 0}
 +
 +/**
 + * Determine whether 'ce' needs to be cloned. If so, prepare the 'child' to
 + * run the clone. Returns 1 if 'ce' needs to be cloned, 0 otherwise.
 + */
 +static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
 +                                         struct child_process *child,
 +                                         struct submodule_update_clone *suc,
 +                                         struct strbuf *out)
 +{
 +      const struct submodule *sub = NULL;
 +      struct strbuf displaypath_sb = STRBUF_INIT;
 +      struct strbuf sb = STRBUF_INIT;
 +      const char *displaypath = NULL;
 +      char *url = NULL;
 +      int needs_cloning = 0;
 +
 +      if (ce_stage(ce)) {
 +              if (suc->recursive_prefix)
 +                      strbuf_addf(&sb, "%s/%s", suc->recursive_prefix, ce->name);
 +              else
 +                      strbuf_addf(&sb, "%s", ce->name);
 +              strbuf_addf(out, _("Skipping unmerged submodule %s"), sb.buf);
 +              strbuf_addch(out, '\n');
 +              goto cleanup;
 +      }
 +
 +      sub = submodule_from_path(null_sha1, ce->name);
 +
 +      if (suc->recursive_prefix)
 +              displaypath = relative_path(suc->recursive_prefix,
 +                                          ce->name, &displaypath_sb);
 +      else
 +              displaypath = ce->name;
 +
 +      if (suc->update.type == SM_UPDATE_NONE
 +          || (suc->update.type == SM_UPDATE_UNSPECIFIED
 +              && sub->update_strategy.type == SM_UPDATE_NONE)) {
 +              strbuf_addf(out, _("Skipping submodule '%s'"), displaypath);
 +              strbuf_addch(out, '\n');
 +              goto cleanup;
 +      }
 +
 +      /*
 +       * Looking up the url in .git/config.
 +       * We must not fall back to .gitmodules as we only want
 +       * to process configured submodules.
 +       */
 +      strbuf_reset(&sb);
 +      strbuf_addf(&sb, "submodule.%s.url", sub->name);
 +      git_config_get_string(sb.buf, &url);
 +      if (!url) {
 +              /*
 +               * Only mention uninitialized submodules when their
 +               * path have been specified
 +               */
 +              if (suc->warn_if_uninitialized) {
 +                      strbuf_addf(out,
 +                              _("Submodule path '%s' not initialized"),
 +                              displaypath);
 +                      strbuf_addch(out, '\n');
 +                      strbuf_addstr(out,
 +                              _("Maybe you want to use 'update --init'?"));
 +                      strbuf_addch(out, '\n');
 +              }
 +              goto cleanup;
 +      }
 +
 +      strbuf_reset(&sb);
 +      strbuf_addf(&sb, "%s/.git", ce->name);
 +      needs_cloning = !file_exists(sb.buf);
 +
 +      strbuf_reset(&sb);
 +      strbuf_addf(&sb, "%06o %s %d %d\t%s\n", ce->ce_mode,
 +                      sha1_to_hex(ce->sha1), ce_stage(ce),
 +                      needs_cloning, ce->name);
 +      string_list_append(&suc->projectlines, sb.buf);
 +
 +      if (!needs_cloning)
 +              goto cleanup;
 +
 +      child->git_cmd = 1;
 +      child->no_stdin = 1;
 +      child->stdout_to_stderr = 1;
 +      child->err = -1;
 +      argv_array_push(&child->args, "submodule--helper");
 +      argv_array_push(&child->args, "clone");
 +      if (suc->quiet)
 +              argv_array_push(&child->args, "--quiet");
 +      if (suc->prefix)
 +              argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL);
 +      argv_array_pushl(&child->args, "--path", sub->path, NULL);
 +      argv_array_pushl(&child->args, "--name", sub->name, NULL);
 +      argv_array_pushl(&child->args, "--url", url, NULL);
 +      if (suc->reference)
 +              argv_array_push(&child->args, suc->reference);
 +      if (suc->depth)
 +              argv_array_push(&child->args, suc->depth);
 +
 +cleanup:
 +      free(url);
 +      strbuf_reset(&displaypath_sb);
 +      strbuf_reset(&sb);
 +
 +      return needs_cloning;
 +}
 +
 +static int update_clone_get_next_task(struct child_process *child,
 +                                    struct strbuf *err,
 +                                    void *suc_cb,
 +                                    void **void_task_cb)
 +{
 +      struct submodule_update_clone *suc = suc_cb;
 +
 +      for (; suc->current < suc->list.nr; suc->current++) {
 +              const struct cache_entry *ce = suc->list.entries[suc->current];
 +              if (prepare_to_clone_next_submodule(ce, child, suc, err)) {
 +                      suc->current++;
 +                      return 1;
 +              }
 +      }
 +      return 0;
 +}
 +
 +static int update_clone_start_failure(struct strbuf *err,
 +                                    void *suc_cb,
 +                                    void *void_task_cb)
 +{
 +      struct submodule_update_clone *suc = suc_cb;
 +      suc->quickstop = 1;
 +      return 1;
 +}
 +
 +static int update_clone_task_finished(int result,
 +                                    struct strbuf *err,
 +                                    void *suc_cb,
 +                                    void *void_task_cb)
 +{
 +      struct submodule_update_clone *suc = suc_cb;
 +
 +      if (!result)
 +              return 0;
 +
 +      suc->quickstop = 1;
 +      return 1;
 +}
 +
 +static int update_clone(int argc, const char **argv, const char *prefix)
 +{
 +      const char *update = NULL;
 +      int max_jobs = -1;
 +      struct string_list_item *item;
 +      struct pathspec pathspec;
 +      struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
 +
 +      struct option module_update_clone_options[] = {
 +              OPT_STRING(0, "prefix", &prefix,
 +                         N_("path"),
 +                         N_("path into the working tree")),
 +              OPT_STRING(0, "recursive-prefix", &suc.recursive_prefix,
 +                         N_("path"),
 +                         N_("path into the working tree, across nested "
 +                            "submodule boundaries")),
 +              OPT_STRING(0, "update", &update,
 +                         N_("string"),
 +                         N_("rebase, merge, checkout or none")),
 +              OPT_STRING(0, "reference", &suc.reference, N_("repo"),
 +                         N_("reference repository")),
 +              OPT_STRING(0, "depth", &suc.depth, "<depth>",
 +                         N_("Create a shallow clone truncated to the "
 +                            "specified number of revisions")),
 +              OPT_INTEGER('j', "jobs", &max_jobs,
 +                          N_("parallel jobs")),
 +              OPT__QUIET(&suc.quiet, N_("don't print cloning progress")),
 +              OPT_END()
 +      };
 +
 +      const char *const git_submodule_helper_usage[] = {
 +              N_("git submodule--helper update_clone [--prefix=<path>] [<path>...]"),
 +              NULL
 +      };
 +      suc.prefix = prefix;
 +
 +      argc = parse_options(argc, argv, prefix, module_update_clone_options,
 +                           git_submodule_helper_usage, 0);
 +
 +      if (update)
 +              if (parse_submodule_update_strategy(update, &suc.update) < 0)
 +                      die(_("bad value for update parameter"));
 +
 +      if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0)
 +              return 1;
 +
 +      if (pathspec.nr)
 +              suc.warn_if_uninitialized = 1;
 +
 +      /* Overlay the parsed .gitmodules file with .git/config */
 +      gitmodules_config();
 +      git_config(submodule_config, NULL);
 +
 +      if (max_jobs < 0)
 +              max_jobs = parallel_submodules();
 +
 +      run_processes_parallel(max_jobs,
 +                             update_clone_get_next_task,
 +                             update_clone_start_failure,
 +                             update_clone_task_finished,
 +                             &suc);
 +
 +      /*
 +       * We saved the output and put it out all at once now.
 +       * That means:
 +       * - the listener does not have to interleave their (checkout)
 +       *   work with our fetching.  The writes involved in a
 +       *   checkout involve more straightforward sequential I/O.
 +       * - the listener can avoid doing any work if fetching failed.
 +       */
 +      if (suc.quickstop)
 +              return 1;
 +
 +      for_each_string_list_item(item, &suc.projectlines)
 +              utf8_fprintf(stdout, "%s", item->string);
 +
 +      return 0;
 +}
 +
  struct cmd_struct {
        const char *cmd;
        int (*fn)(int, const char **, const char *);
@@@ -509,20 -333,20 +578,21 @@@ static struct cmd_struct commands[] = 
        {"list", module_list},
        {"name", module_name},
        {"clone", module_clone},
+       {"sanitize-config", module_sanitize_config},
 +      {"update-clone", update_clone}
  };
  
  int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
  {
        int i;
        if (argc < 2)
 -              die(_("fatal: submodule--helper subcommand must be "
 +              die(_("submodule--helper subcommand must be "
                      "called with a subcommand"));
  
        for (i = 0; i < ARRAY_SIZE(commands); i++)
                if (!strcmp(argv[1], commands[i].cmd))
                        return commands[i].fn(argc - 1, argv + 1, prefix);
  
 -      die(_("fatal: '%s' is not a valid submodule--helper "
 +      die(_("'%s' is not a valid submodule--helper "
              "subcommand"), argv[1]);
  }
diff --combined config.c
index 9ba40bc1b039b9b65425dc4fa1bd9c7f1fcb0868,8570eec51dcc8a23e4219762b04eef73411cb613..8f66519e616ad9ebb6ee2540028cd2f04ce46103
+++ b/config.c
@@@ -24,7 -24,6 +24,7 @@@ struct config_source 
                        size_t pos;
                } buf;
        } u;
 +      const char *origin_type;
        const char *name;
        const char *path;
        int die_on_error;
@@@ -162,7 -161,7 +162,7 @@@ void git_config_push_parameter(const ch
  {
        struct strbuf env = STRBUF_INIT;
        const char *old = getenv(CONFIG_DATA_ENVIRONMENT);
-       if (old) {
+       if (old && *old) {
                strbuf_addstr(&env, old);
                strbuf_addch(&env, ' ');
        }
@@@ -472,9 -471,9 +472,9 @@@ static int git_parse_source(config_fn_
                        break;
        }
        if (cf->die_on_error)
 -              die(_("bad config file line %d in %s"), cf->linenr, cf->name);
 +              die(_("bad config line %d in %s %s"), cf->linenr, cf->origin_type, cf->name);
        else
 -              return error(_("bad config file line %d in %s"), cf->linenr, cf->name);
 +              return error(_("bad config line %d in %s %s"), cf->linenr, cf->origin_type, cf->name);
  }
  
  static int parse_unit_factor(const char *end, uintmax_t *val)
@@@ -589,9 -588,9 +589,9 @@@ static void die_bad_number(const char *
        if (!value)
                value = "";
  
 -      if (cf && cf->name)
 -              die(_("bad numeric config value '%s' for '%s' in %s: %s"),
 -                  value, name, cf->name, reason);
 +      if (cf && cf->origin_type && cf->name)
 +              die(_("bad numeric config value '%s' for '%s' in %s %s: %s"),
 +                  value, name, cf->origin_type, cf->name, reason);
        die(_("bad numeric config value '%s' for '%s': %s"), value, name, reason);
  }
  
@@@ -1062,13 -1061,11 +1062,13 @@@ static int do_config_from(struct config
  }
  
  static int do_config_from_file(config_fn_t fn,
 -              const char *name, const char *path, FILE *f, void *data)
 +              const char *origin_type, const char *name, const char *path, FILE *f,
 +              void *data)
  {
        struct config_source top;
  
        top.u.file = f;
 +      top.origin_type = origin_type;
        top.name = name;
        top.path = path;
        top.die_on_error = 1;
  
  static int git_config_from_stdin(config_fn_t fn, void *data)
  {
 -      return do_config_from_file(fn, "<stdin>", NULL, stdin, data);
 +      return do_config_from_file(fn, "standard input", "", NULL, stdin, data);
  }
  
  int git_config_from_file(config_fn_t fn, const char *filename, void *data)
        f = fopen(filename, "r");
        if (f) {
                flockfile(f);
 -              ret = do_config_from_file(fn, filename, filename, f, data);
 +              ret = do_config_from_file(fn, "file", filename, filename, f, data);
                funlockfile(f);
                fclose(f);
        }
        return ret;
  }
  
 -int git_config_from_buf(config_fn_t fn, const char *name, const char *buf,
 -                      size_t len, void *data)
 +int git_config_from_mem(config_fn_t fn, const char *origin_type,
 +                      const char *name, const char *buf, size_t len, void *data)
  {
        struct config_source top;
  
        top.u.buf.buf = buf;
        top.u.buf.len = len;
        top.u.buf.pos = 0;
 +      top.origin_type = origin_type;
        top.name = name;
        top.path = NULL;
        top.die_on_error = 0;
@@@ -1136,7 -1132,7 +1136,7 @@@ static int git_config_from_blob_sha1(co
                return error("reference '%s' does not point to a blob", name);
        }
  
 -      ret = git_config_from_buf(fn, name, buf, size, data);
 +      ret = git_config_from_mem(fn, "blob", name, buf, size, data);
        free(buf);
  
        return ret;
@@@ -1853,26 -1849,15 +1853,26 @@@ contline
        return offset;
  }
  
 -int git_config_set_in_file(const char *config_filename,
 -                      const char *key, const char *value)
 +int git_config_set_in_file_gently(const char *config_filename,
 +                                const char *key, const char *value)
  {
 -      return git_config_set_multivar_in_file(config_filename, key, value, NULL, 0);
 +      return git_config_set_multivar_in_file_gently(config_filename, key, value, NULL, 0);
  }
  
 -int git_config_set(const char *key, const char *value)
 +void git_config_set_in_file(const char *config_filename,
 +                          const char *key, const char *value)
  {
 -      return git_config_set_multivar(key, value, NULL, 0);
 +      git_config_set_multivar_in_file(config_filename, key, value, NULL, 0);
 +}
 +
 +int git_config_set_gently(const char *key, const char *value)
 +{
 +      return git_config_set_multivar_gently(key, value, NULL, 0);
 +}
 +
 +void git_config_set(const char *key, const char *value)
 +{
 +      git_config_set_multivar(key, value, NULL, 0);
  }
  
  /*
@@@ -1917,7 -1902,7 +1917,7 @@@ static int git_config_parse_key_1(cons
         * Validate the key and while at it, lower case it for matching.
         */
        if (store_key)
 -              *store_key = xmalloc(strlen(key) + 1);
 +              *store_key = xmallocz(strlen(key));
  
        dot = 0;
        for (i = 0; key[i]; i++) {
                if (store_key)
                        (*store_key)[i] = c;
        }
 -      if (store_key)
 -              (*store_key)[i] = 0;
  
        return 0;
  
@@@ -1987,10 -1974,9 +1987,10 @@@ int git_config_key_is_valid(const char 
   * - the config file is removed and the lock file rename()d to it.
   *
   */
 -int git_config_set_multivar_in_file(const char *config_filename,
 -                              const char *key, const char *value,
 -                              const char *value_regex, int multi_replace)
 +int git_config_set_multivar_in_file_gently(const char *config_filename,
 +                                         const char *key, const char *value,
 +                                         const char *value_regex,
 +                                         int multi_replace)
  {
        int fd = -1, in_fd = -1;
        int ret;
@@@ -2217,27 -2203,11 +2217,27 @@@ write_err_out
  
  }
  
 -int git_config_set_multivar(const char *key, const char *value,
 -                      const char *value_regex, int multi_replace)
 +void git_config_set_multivar_in_file(const char *config_filename,
 +                                   const char *key, const char *value,
 +                                   const char *value_regex, int multi_replace)
 +{
 +      if (git_config_set_multivar_in_file_gently(config_filename, key, value,
 +                                                 value_regex, multi_replace) < 0)
 +              die(_("Could not set '%s' to '%s'"), key, value);
 +}
 +
 +int git_config_set_multivar_gently(const char *key, const char *value,
 +                                 const char *value_regex, int multi_replace)
  {
 -      return git_config_set_multivar_in_file(NULL, key, value, value_regex,
 -                                             multi_replace);
 +      return git_config_set_multivar_in_file_gently(NULL, key, value, value_regex,
 +                                                    multi_replace);
 +}
 +
 +void git_config_set_multivar(const char *key, const char *value,
 +                           const char *value_regex, int multi_replace)
 +{
 +      git_config_set_multivar_in_file(NULL, key, value, value_regex,
 +                                      multi_replace);
  }
  
  static int section_name_match (const char *buf, const char *name)
@@@ -2439,13 -2409,3 +2439,13 @@@ int parse_config_key(const char *var
  
        return 0;
  }
 +
 +const char *current_config_origin_type(void)
 +{
 +      return cf && cf->origin_type ? cf->origin_type : "command line";
 +}
 +
 +const char *current_config_name(void)
 +{
 +      return cf && cf->name ? cf->name : "";
 +}
diff --combined git-submodule.sh
index 03222821209bc63dff80ff4a4ecdcbe1d120b22f,1f132b489b81eb0bb7a4a6a5b7d3a5b88a77f53a..cd749f473c5fc8bdd16018c5b735a215176230bb
@@@ -192,6 -192,16 +192,16 @@@ isnumber(
        n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1"
  }
  
+ # Sanitize the local git environment for use within a submodule. We
+ # can't simply use clear_local_git_env since we want to preserve some
+ # of the settings from GIT_CONFIG_PARAMETERS.
+ sanitize_submodule_env()
+ {
+       sanitized_config=$(git submodule--helper sanitize-config)
+       clear_local_git_env
+       GIT_CONFIG_PARAMETERS=$sanitized_config
+ }
  #
  # Add a new submodule to the working tree, .gitmodules and the index
  #
@@@ -347,9 -357,9 +357,9 @@@ Use -f if you really want to add it." >
                                echo "$(eval_gettext "Reactivating local git directory for submodule '\$sm_name'.")"
                        fi
                fi
-               git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" "$reference" "$depth" || exit
+               git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${depth:+"$depth"} || exit
                (
-                       clear_local_git_env
+                       sanitize_submodule_env
                        cd "$sm_path" &&
                        # ash fails to wordsplit ${branch:+-b "$branch"...}
                        case "$branch" in
@@@ -418,7 -428,7 +428,7 @@@ cmd_foreach(
                        name=$(git submodule--helper name "$sm_path")
                        (
                                prefix="$prefix$sm_path/"
-                               clear_local_git_env
+                               sanitize_submodule_env
                                cd "$sm_path" &&
                                sm_path=$(relative_path "$sm_path") &&
                                # we make $path available to scripts ...
@@@ -591,24 -601,6 +601,24 @@@ cmd_deinit(
        done
  }
  
-       clear_local_git_env
 +is_tip_reachable () (
-       clear_local_git_env
++      sanitize_submodule_env &&
 +      cd "$1" &&
 +      rev=$(git rev-list -n 1 "$2" --not --all 2>/dev/null) &&
 +      test -z "$rev"
 +)
 +
 +fetch_in_submodule () (
++      sanitize_submodule_env &&
 +      cd "$1" &&
 +      case "$2" in
 +      '')
 +              git fetch ;;
 +      *)
 +              git fetch $(get_default_remote) "$2" ;;
 +      esac
 +)
 +
  #
  # Update each submodule path to correct revision, using clone and checkout as needed
  #
@@@ -663,14 -655,6 +673,14 @@@ cmd_update(
                --depth=*)
                        depth=$1
                        ;;
 +              -j|--jobs)
 +                      case "$2" in '') usage ;; esac
 +                      jobs="--jobs=$2"
 +                      shift
 +                      ;;
 +              --jobs=*)
 +                      jobs=$1
 +                      ;;
                --)
                        shift
                        break
                cmd_init "--" "$@" || return
        fi
  
 -      cloned_modules=
 -      git submodule--helper list --prefix "$wt_prefix" "$@" | {
 +      {
 +      git submodule--helper update-clone ${GIT_QUIET:+--quiet} \
 +              ${wt_prefix:+--prefix "$wt_prefix"} \
 +              ${prefix:+--recursive-prefix "$prefix"} \
 +              ${update:+--update "$update"} \
 +              ${reference:+--reference "$reference"} \
 +              ${depth:+--depth "$depth"} \
 +              ${jobs:+$jobs} \
 +              "$@" || echo "#unmatched"
 +      } | {
        err=
 -      while read mode sha1 stage sm_path
 +      while read mode sha1 stage just_cloned sm_path
        do
                die_if_unmatched "$mode"
 -              if test "$stage" = U
 -              then
 -                      echo >&2 "Skipping unmerged submodule $prefix$sm_path"
 -                      continue
 -              fi
 +
                name=$(git submodule--helper name "$sm_path") || exit
                url=$(git config submodule."$name".url)
                branch=$(get_submodule_config "$name" branch master)
  
                displaypath=$(relative_path "$prefix$sm_path")
  
 -              if test "$update_module" = "none"
 -              then
 -                      echo "Skipping submodule '$displaypath'"
 -                      continue
 -              fi
 -
 -              if test -z "$url"
 -              then
 -                      # Only mention uninitialized submodules when its
 -                      # path have been specified
 -                      test "$#" != "0" &&
 -                      say "$(eval_gettext "Submodule path '\$displaypath' not initialized
 -Maybe you want to use 'update --init'?")"
 -                      continue
 -              fi
 -
 -              if ! test -d "$sm_path"/.git && ! test -f "$sm_path"/.git
 +              if test $just_cloned -eq 1
                then
 -                      git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$prefix" --path "$sm_path" --name "$name" --url "$url" ${reference:+"$reference"} ${depth:+"$depth"} || exit
 -                      cloned_modules="$cloned_modules;$name"
                        subsha1=
 +                      update_module=checkout
                else
-                       subsha1=$(clear_local_git_env; cd "$sm_path" &&
+                       subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
                                git rev-parse --verify HEAD) ||
                        die "$(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
                fi
                        if test -z "$nofetch"
                        then
                                # Fetch remote before determining tracking $sha1
-                               (clear_local_git_env; cd "$sm_path" && git-fetch) ||
+                               (sanitize_submodule_env; cd "$sm_path" && git-fetch) ||
                                die "$(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
                        fi
-                       remote_name=$(clear_local_git_env; cd "$sm_path" && get_default_remote)
-                       sha1=$(clear_local_git_env; cd "$sm_path" &&
+                       remote_name=$(sanitize_submodule_env; cd "$sm_path" && get_default_remote)
+                       sha1=$(sanitize_submodule_env; cd "$sm_path" &&
                                git rev-parse --verify "${remote_name}/${branch}") ||
                        die "$(eval_gettext "Unable to find current ${remote_name}/${branch} revision in submodule path '\$sm_path'")"
                fi
                        then
                                # Run fetch only if $sha1 isn't present or it
                                # is not reachable from a ref.
 -                              (sanitize_submodule_env; cd "$sm_path" &&
 -                                      ( (rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) &&
 -                                       test -z "$rev") || git-fetch)) ||
 +                              is_tip_reachable "$sm_path" "$sha1" ||
 +                              fetch_in_submodule "$sm_path" ||
                                die "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'")"
 -                      fi
  
 -                      # Is this something we just cloned?
 -                      case ";$cloned_modules;" in
 -                      *";$name;"*)
 -                              # then there is no local change to integrate
 -                              update_module=checkout ;;
 -                      esac
 +                              # Now we tried the usual fetch, but $sha1 may
 +                              # not be reachable from any of the refs
 +                              is_tip_reachable "$sm_path" "$sha1" ||
 +                              fetch_in_submodule "$sm_path" "$sha1" ||
 +                              die "$(eval_gettext "Fetched in submodule path '\$displaypath', but it did not contain $sha1. Direct fetching of that commit failed.")"
 +                      fi
  
                        must_die_on_failure=
                        case "$update_module" in
                                die "$(eval_gettext "Invalid update mode '$update_module' for submodule '$name'")"
                        esac
  
-                       if (clear_local_git_env; cd "$sm_path" && $command "$sha1")
+                       if (sanitize_submodule_env; cd "$sm_path" && $command "$sha1")
                        then
                                say "$say_msg"
                        elif test -n "$must_die_on_failure"
                then
                        (
                                prefix="$prefix$sm_path/"
-                               clear_local_git_env
+                               sanitize_submodule_env
                                cd "$sm_path" &&
                                eval cmd_update
                        )
  
  set_name_rev () {
        revname=$( (
-               clear_local_git_env
+               sanitize_submodule_env
                cd "$1" && {
                        git describe "$2" 2>/dev/null ||
                        git describe --tags "$2" 2>/dev/null ||
@@@ -1136,7 -1135,7 +1146,7 @@@ cmd_status(
                else
                        if test -z "$cached"
                        then
-                               sha1=$(clear_local_git_env; cd "$sm_path" && git rev-parse --verify HEAD)
+                               sha1=$(sanitize_submodule_env; cd "$sm_path" && git rev-parse --verify HEAD)
                        fi
                        set_name_rev "$sm_path" "$sha1"
                        say "+$sha1 $displaypath$revname"
                then
                        (
                                prefix="$displaypath/"
-                               clear_local_git_env
+                               sanitize_submodule_env
                                cd "$sm_path" &&
                                eval cmd_status
                        ) ||
@@@ -1220,7 -1219,7 +1230,7 @@@ cmd_sync(
                        if test -e "$sm_path"/.git
                        then
                        (
-                               clear_local_git_env
+                               sanitize_submodule_env
                                cd "$sm_path"
                                remote=$(get_default_remote)
                                git config remote."$remote".url "$sub_origin_url"
diff --combined t/t1300-repo-config.sh
index 3d6f1db9da95cefa02391792500b9577d4ca9902,c96774030475ff2006477106946946107ead39ed..d934a2441724332d9e8cebd7679788ee3a660fce
@@@ -699,13 -699,11 +699,13 @@@ test_expect_success 'invalid unit' 
        echo 1auto >expect &&
        git config aninvalid.unit >actual &&
        test_cmp expect actual &&
 -      cat >expect <<-\EOF &&
 -      fatal: bad numeric config value '\''1auto'\'' for '\''aninvalid.unit'\'' in .git/config: invalid unit
 -      EOF
        test_must_fail git config --int --get aninvalid.unit 2>actual &&
 -      test_i18ncmp expect actual
 +      test_i18ngrep "bad numeric config value .1auto. for .aninvalid.unit. in file .git/config: invalid unit" actual
 +'
 +
 +test_expect_success 'invalid stdin config' '
 +      echo "[broken" | test_must_fail git config --list --file - >output 2>&1 &&
 +      test_i18ngrep "bad config line 1 in standard input" output
  '
  
  cat > expect << EOF
@@@ -959,15 -957,13 +959,15 @@@ Qsection.sub=section.val
  Qsection.sub=section.val5Q
  EOF
  test_expect_success '--null --list' '
 -      git config --null --list | nul_to_q >result &&
 +      git config --null --list >result.raw &&
 +      nul_to_q <result.raw >result &&
        echo >>result &&
        test_cmp expect result
  '
  
  test_expect_success '--null --get-regexp' '
 -      git config --null --get-regexp "val[0-9]" | nul_to_q >result &&
 +      git config --null --get-regexp "val[0-9]" >result.raw &&
 +      nul_to_q <result.raw >result &&
        echo >>result &&
        test_cmp expect result
  '
@@@ -1087,6 -1083,20 +1087,20 @@@ test_expect_success 'git -c complains a
        test_must_fail git -c "" rev-parse
  '
  
+ test_expect_success 'multiple git -c appends config' '
+       test_config alias.x "!git -c x.two=2 config --get-regexp ^x\.*" &&
+       cat >expect <<-\EOF &&
+       x.one 1
+       x.two 2
+       EOF
+       git -c x.one=1 x >actual &&
+       test_cmp expect actual
+ '
+ test_expect_success 'git -c is not confused by empty environment' '
+       GIT_CONFIG_PARAMETERS="" git -c x.one=1 config --list
+ '
  test_expect_success 'git config --edit works' '
        git config -f tmp test.value no &&
        echo test.value=yes >expect &&
@@@ -1144,9 -1154,6 +1158,9 @@@ test_expect_success 'urlmatch' 
                cookieFile = /tmp/cookie.txt
        EOF
  
 +      test_expect_code 1 git config --bool --get-urlmatch doesnt.exist https://good.example.com >actual &&
 +      test_must_be_empty actual &&
 +
        echo true >expect &&
        git config --bool --get-urlmatch http.SSLverify https://good.example.com >actual &&
        test_cmp expect actual &&
@@@ -1208,154 -1215,4 +1222,154 @@@ test_expect_success POSIXPERM,PERL 'pre
          "die q(badrename) if ((stat(q(.git/config)))[2] & 07777) != 0600"
  '
  
 +! test_have_prereq MINGW ||
 +HOME="$(pwd)" # convert to Windows path
 +
 +test_expect_success 'set up --show-origin tests' '
 +      INCLUDE_DIR="$HOME/include" &&
 +      mkdir -p "$INCLUDE_DIR" &&
 +      cat >"$INCLUDE_DIR"/absolute.include <<-\EOF &&
 +              [user]
 +                      absolute = include
 +      EOF
 +      cat >"$INCLUDE_DIR"/relative.include <<-\EOF &&
 +              [user]
 +                      relative = include
 +      EOF
 +      cat >"$HOME"/.gitconfig <<-EOF &&
 +              [user]
 +                      global = true
 +                      override = global
 +              [include]
 +                      path = "$INCLUDE_DIR/absolute.include"
 +      EOF
 +      cat >.git/config <<-\EOF
 +              [user]
 +                      local = true
 +                      override = local
 +              [include]
 +                      path = ../include/relative.include
 +      EOF
 +'
 +
 +test_expect_success '--show-origin with --list' '
 +      cat >expect <<-EOF &&
 +              file:$HOME/.gitconfig   user.global=true
 +              file:$HOME/.gitconfig   user.override=global
 +              file:$HOME/.gitconfig   include.path=$INCLUDE_DIR/absolute.include
 +              file:$INCLUDE_DIR/absolute.include      user.absolute=include
 +              file:.git/config        user.local=true
 +              file:.git/config        user.override=local
 +              file:.git/config        include.path=../include/relative.include
 +              file:.git/../include/relative.include   user.relative=include
 +              command line:   user.cmdline=true
 +      EOF
 +      git -c user.cmdline=true config --list --show-origin >output &&
 +      test_cmp expect output
 +'
 +
 +test_expect_success '--show-origin with --list --null' '
 +      cat >expect <<-EOF &&
 +              file:$HOME/.gitconfigQuser.global
 +              trueQfile:$HOME/.gitconfigQuser.override
 +              globalQfile:$HOME/.gitconfigQinclude.path
 +              $INCLUDE_DIR/absolute.includeQfile:$INCLUDE_DIR/absolute.includeQuser.absolute
 +              includeQfile:.git/configQuser.local
 +              trueQfile:.git/configQuser.override
 +              localQfile:.git/configQinclude.path
 +              ../include/relative.includeQfile:.git/../include/relative.includeQuser.relative
 +              includeQcommand line:Quser.cmdline
 +              trueQ
 +      EOF
 +      git -c user.cmdline=true config --null --list --show-origin >output.raw &&
 +      nul_to_q <output.raw >output &&
 +      # The here-doc above adds a newline that the --null output would not
 +      # include. Add it here to make the two comparable.
 +      echo >>output &&
 +      test_cmp expect output
 +'
 +
 +test_expect_success '--show-origin with single file' '
 +      cat >expect <<-\EOF &&
 +              file:.git/config        user.local=true
 +              file:.git/config        user.override=local
 +              file:.git/config        include.path=../include/relative.include
 +      EOF
 +      git config --local --list --show-origin >output &&
 +      test_cmp expect output
 +'
 +
 +test_expect_success '--show-origin with --get-regexp' '
 +      cat >expect <<-EOF &&
 +              file:$HOME/.gitconfig   user.global true
 +              file:.git/config        user.local true
 +      EOF
 +      git config --show-origin --get-regexp "user\.[g|l].*" >output &&
 +      test_cmp expect output
 +'
 +
 +test_expect_success '--show-origin getting a single key' '
 +      cat >expect <<-\EOF &&
 +              file:.git/config        local
 +      EOF
 +      git config --show-origin user.override >output &&
 +      test_cmp expect output
 +'
 +
 +test_expect_success 'set up custom config file' '
 +      CUSTOM_CONFIG_FILE="file\" (dq) and spaces.conf" &&
 +      cat >"$CUSTOM_CONFIG_FILE" <<-\EOF
 +              [user]
 +                      custom = true
 +      EOF
 +'
 +
 +test_expect_success !MINGW '--show-origin escape special file name characters' '
 +      cat >expect <<-\EOF &&
 +              file:"file\" (dq) and spaces.conf"      user.custom=true
 +      EOF
 +      git config --file "$CUSTOM_CONFIG_FILE" --show-origin --list >output &&
 +      test_cmp expect output
 +'
 +
 +test_expect_success '--show-origin stdin' '
 +      cat >expect <<-\EOF &&
 +              standard input: user.custom=true
 +      EOF
 +      git config --file - --show-origin --list <"$CUSTOM_CONFIG_FILE" >output &&
 +      test_cmp expect output
 +'
 +
 +test_expect_success '--show-origin stdin with file include' '
 +      cat >"$INCLUDE_DIR"/stdin.include <<-EOF &&
 +              [user]
 +                      stdin = include
 +      EOF
 +      cat >expect <<-EOF &&
 +              file:$INCLUDE_DIR/stdin.include include
 +      EOF
 +      echo "[include]path=\"$INCLUDE_DIR\"/stdin.include" \
 +              | git config --show-origin --includes --file - user.stdin >output &&
 +      test_cmp expect output
 +'
 +
 +test_expect_success !MINGW '--show-origin blob' '
 +      cat >expect <<-\EOF &&
 +              blob:a9d9f9e555b5c6f07cbe09d3f06fe3df11e09c08   user.custom=true
 +      EOF
 +      blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") &&
 +      git config --blob=$blob --show-origin --list >output &&
 +      test_cmp expect output
 +'
 +
 +test_expect_success !MINGW '--show-origin blob ref' '
 +      cat >expect <<-\EOF &&
 +              blob:"master:file\" (dq) and spaces.conf"       user.custom=true
 +      EOF
 +      git add "$CUSTOM_CONFIG_FILE" &&
 +      git commit -m "new config file" &&
 +      git config --blob=master:"$CUSTOM_CONFIG_FILE" --show-origin --list >output &&
 +      test_cmp expect output
 +'
 +
  test_done