Merge branch 'ma/parse-maybe-bool' into next
authorJunio C Hamano <gitster@pobox.com>
Fri, 18 Aug 2017 20:52:49 +0000 (13:52 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 18 Aug 2017 20:52:50 +0000 (13:52 -0700)
Code clean-up.

* ma/parse-maybe-bool:
parse_decoration_style: drop unused argument `var`
treewide: deprecate git_config_maybe_bool, use git_parse_maybe_bool
config: make git_{config,parse}_maybe_bool equivalent
config: introduce git_parse_maybe_bool_text
t5334: document that git push --signed=1 does not work
Doc/git-{push,send-pack}: correct --sign= to --signed=

12 files changed:
1  2 
Documentation/git-push.txt
Documentation/git-send-pack.txt
builtin/log.c
builtin/merge.c
builtin/pull.c
builtin/push.c
builtin/remote.c
builtin/send-pack.c
config.c
pager.c
submodule-config.c
t/t5534-push-signed.sh
index 0a639664fd67f497b1597cf903015a284947509d,766673434a4aa00f54513f4974ab8df24eaffb83..3e76e99f38f67a530d43e2da3b3129c2a99e3366
@@@ -12,7 -12,7 +12,7 @@@ SYNOPSI
  'git push' [--all | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
           [--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-v | --verbose]
           [-u | --set-upstream] [--push-option=<string>]
-          [--[no-]signed|--sign=(true|false|if-asked)]
+          [--[no-]signed|--signed=(true|false|if-asked)]
           [--force-with-lease[=<refname>[:<expect>]]]
           [--no-verify] [<repository> [<refspec>...]]
  
@@@ -141,7 -141,7 +141,7 @@@ already exists on the remote side
        information, see `push.followTags` in linkgit:git-config[1].
  
  --[no-]signed::
- --sign=(true|false|if-asked)::
+ --signed=(true|false|if-asked)::
        GPG-sign the push request to update refs on the receiving
        side, to allow it to be checked by the hooks and/or be
        logged.  If `false` or `--no-signed`, no signing will be
@@@ -217,47 -217,6 +217,47 @@@ with this feature
  +
  "--no-force-with-lease" will cancel all the previous --force-with-lease on the
  command line.
 ++
 +A general note on safety: supplying this option without an expected
 +value, i.e. as `--force-with-lease` or `--force-with-lease=<refname>`
 +interacts very badly with anything that implicitly runs `git fetch` on
 +the remote to be pushed to in the background, e.g. `git fetch origin`
 +on your repository in a cronjob.
 ++
 +The protection it offers over `--force` is ensuring that subsequent
 +changes your work wasn't based on aren't clobbered, but this is
 +trivially defeated if some background process is updating refs in the
 +background. We don't have anything except the remote tracking info to
 +go by as a heuristic for refs you're expected to have seen & are
 +willing to clobber.
 ++
 +If your editor or some other system is running `git fetch` in the
 +background for you a way to mitigate this is to simply set up another
 +remote:
 ++
 +      git remote add origin-push $(git config remote.origin.url)
 +      git fetch origin-push
 ++
 +Now when the background process runs `git fetch origin` the references
 +on `origin-push` won't be updated, and thus commands like:
 ++
 +      git push --force-with-lease origin-push
 ++
 +Will fail unless you manually run `git fetch origin-push`. This method
 +is of course entirely defeated by something that runs `git fetch
 +--all`, in that case you'd need to either disable it or do something
 +more tedious like:
 ++
 +      git fetch              # update 'master' from remote
 +      git tag base master    # mark our base point
 +      git rebase -i master   # rewrite some commits
 +      git push --force-with-lease=master:base master:master
 ++
 +I.e. create a `base` tag for versions of the upstream code that you've
 +seen and are willing to overwrite, then rewrite history, and finally
 +force push changes to `master` if the remote version is still at
 +`base`, regardless of what your local `remotes/origin/master` has been
 +updated to in the background.
  
  -f::
  --force::
index 966abb0df807c79714a18d8b106390caae45cf89,d32841f7a0318bc883580fe24a40a1efcc704de7..f51c64939b48b7b082752a294b17aee6d92c4fa0
@@@ -11,7 -11,7 +11,7 @@@ SYNOPSI
  [verse]
  'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>]
                [--verbose] [--thin] [--atomic]
-               [--[no-]signed|--sign=(true|false|if-asked)]
+               [--[no-]signed|--signed=(true|false|if-asked)]
                [<host>:]<directory> [<ref>...]
  
  DESCRIPTION
@@@ -71,7 -71,7 +71,7 @@@ be in a separate packet, and the list m
        refs.
  
  --[no-]signed::
- --sign=(true|false|if-asked)::
+ --signed=(true|false|if-asked)::
        GPG-sign the push request to update refs on the receiving
        side, to allow it to be checked by the hooks and/or be
        logged.  If `false` or `--no-signed`, no signing will be
        will also fail if the actual call to `gpg --sign` fails.  See
        linkgit:git-receive-pack[1] for the details on the receiving end.
  
 +--push-option=<string>::
 +      Pass the specified string as a push option for consumption by
 +      hooks on the server side.  If the server doesn't support push
 +      options, error out.  See linkgit:git-push[1] and
 +      linkgit:githooks[5] for details.
 +
  <host>::
        A remote host to house the repository.  When this
        part is specified, 'git-receive-pack' is invoked via
diff --combined builtin/log.c
index 725c7b8a1a40f3dd943e4f42c801121d9c8ea78f,8a46cec39971ecfcdf5dac5e16f575032a5957a9..25c82410923f7b7e33760b31362e0f665a66abda
@@@ -5,7 -5,6 +5,7 @@@
   *             2006 Junio Hamano
   */
  #include "cache.h"
 +#include "config.h"
  #include "refs.h"
  #include "color.h"
  #include "commit.h"
@@@ -53,14 -52,9 +53,14 @@@ struct line_opt_callback_data 
        struct string_list args;
  };
  
- static int parse_decoration_style(const char *var, const char *value)
 +static int auto_decoration_style(void)
 +{
 +      return (isatty(1) || pager_in_use()) ? DECORATE_SHORT_REFS : 0;
 +}
 +
+ static int parse_decoration_style(const char *value)
  {
-       switch (git_config_maybe_bool(var, value)) {
+       switch (git_parse_maybe_bool(value)) {
        case 1:
                return DECORATE_SHORT_REFS;
        case 0:
@@@ -73,7 -67,7 +73,7 @@@
        else if (!strcmp(value, "short"))
                return DECORATE_SHORT_REFS;
        else if (!strcmp(value, "auto"))
 -              return (isatty(1) || pager_in_use()) ? DECORATE_SHORT_REFS : 0;
 +              return auto_decoration_style();
        return -1;
  }
  
@@@ -82,7 -76,7 +82,7 @@@ static int decorate_callback(const stru
        if (unset)
                decoration_style = 0;
        else if (arg)
-               decoration_style = parse_decoration_style("command line", arg);
+               decoration_style = parse_decoration_style(arg);
        else
                decoration_style = DECORATE_SHORT_REFS;
  
@@@ -111,8 -105,6 +111,8 @@@ static void init_log_defaults(void
  {
        init_grep_defaults();
        init_diff_ui_defaults();
 +
 +      decoration_style = auto_decoration_style();
  }
  
  static void cmd_log_init_defaults(struct rev_info *rev)
@@@ -372,14 -364,11 +372,14 @@@ static int cmd_log_walk(struct rev_inf
                         */
                        rev->max_count++;
                if (!rev->reflog_info) {
 -                      /* we allow cycles in reflog ancestry */
 +                      /*
 +                       * We may show a given commit multiple times when
 +                       * walking the reflogs.
 +                       */
                        free_commit_buffer(commit);
 +                      free_commit_list(commit->parents);
 +                      commit->parents = NULL;
                }
 -              free_commit_list(commit->parents);
 -              commit->parents = NULL;
                if (saved_nrl < rev->diffopt.needed_rename_limit)
                        saved_nrl = rev->diffopt.needed_rename_limit;
                if (rev->diffopt.degraded_cc_to_c)
@@@ -412,7 -401,7 +412,7 @@@ static int git_log_config(const char *v
        if (!strcmp(var, "log.date"))
                return git_config_string(&default_date_mode, var, value);
        if (!strcmp(var, "log.decorate")) {
-               decoration_style = parse_decoration_style(var, value);
+               decoration_style = parse_decoration_style(value);
                if (decoration_style < 0)
                        decoration_style = 0; /* maybe warn? */
                return 0;
@@@ -487,20 -476,16 +487,20 @@@ static int show_blob_object(const struc
            !DIFF_OPT_TST(&rev->diffopt, ALLOW_TEXTCONV))
                return stream_blob_to_fd(1, oid, NULL, 0);
  
 -      if (get_sha1_with_context(obj_name, 0, oidc.hash, &obj_context))
 +      if (get_oid_with_context(obj_name, GET_OID_RECORD_PATH,
 +                               &oidc, &obj_context))
                die(_("Not a valid object name %s"), obj_name);
 -      if (!obj_context.path[0] ||
 -          !textconv_object(obj_context.path, obj_context.mode, &oidc, 1, &buf, &size))
 +      if (!obj_context.path ||
 +          !textconv_object(obj_context.path, obj_context.mode, &oidc, 1, &buf, &size)) {
 +              free(obj_context.path);
                return stream_blob_to_fd(1, oid, NULL, 0);
 +      }
  
        if (!buf)
                die(_("git show %s: bad file"), obj_name);
  
        write_or_die(1, buf, size);
 +      free(obj_context.path);
        return 0;
  }
  
@@@ -604,7 -589,7 +604,7 @@@ int cmd_show(int argc, const char **arg
                        rev.shown_one = 1;
                        if (ret)
                                break;
 -                      o = parse_object(t->tagged->oid.hash);
 +                      o = parse_object(&t->tagged->oid);
                        if (!o)
                                ret = error(_("Could not read object %s"),
                                            oid_to_hex(&t->tagged->oid));
@@@ -824,7 -809,7 +824,7 @@@ static int git_format_config(const cha
                return 0;
        }
        if (!strcmp(var, "format.from")) {
-               int b = git_config_maybe_bool(var, value);
+               int b = git_parse_maybe_bool(value);
                free(from);
                if (b < 0)
                        from = xstrdup(value);
@@@ -850,10 -835,8 +850,10 @@@ static int open_next_file(struct commi
        if (output_directory) {
                strbuf_addstr(&filename, output_directory);
                if (filename.len >=
 -                  PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len)
 +                  PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len) {
 +                      strbuf_release(&filename);
                        return error(_("name of output directory is too long"));
 +              }
                strbuf_complete(&filename, '/');
        }
  
        if (!quiet)
                printf("%s\n", filename.buf + outdir_offset);
  
 -      if ((rev->diffopt.file = fopen(filename.buf, "w")) == NULL)
 -              return error(_("Cannot open patch file %s"), filename.buf);
 +      if ((rev->diffopt.file = fopen(filename.buf, "w")) == NULL) {
 +              error_errno(_("Cannot open patch file %s"), filename.buf);
 +              strbuf_release(&filename);
 +              return -1;
 +      }
  
        strbuf_release(&filename);
        return 0;
@@@ -891,8 -871,8 +891,8 @@@ static void get_patch_ids(struct rev_in
        o2 = rev->pending.objects[1].item;
        flags1 = o1->flags;
        flags2 = o2->flags;
 -      c1 = lookup_commit_reference(o1->oid.hash);
 -      c2 = lookup_commit_reference(o2->oid.hash);
 +      c1 = lookup_commit_reference(&o1->oid);
 +      c2 = lookup_commit_reference(&o2->oid);
  
        if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
                die(_("Not a range."));
  static void gen_message_id(struct rev_info *info, char *base)
  {
        struct strbuf buf = STRBUF_INIT;
 -      strbuf_addf(&buf, "%s.%lu.git.%s", base,
 -                  (unsigned long) time(NULL),
 +      strbuf_addf(&buf, "%s.%"PRItime".git.%s", base,
 +                  (timestamp_t) time(NULL),
                    git_committer_info(IDENT_NO_NAME|IDENT_NO_DATE|IDENT_STRICT));
        info->message_id = strbuf_detach(&buf, NULL);
  }
@@@ -1009,7 -989,8 +1009,7 @@@ static void make_cover_letter(struct re
            open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet))
                return;
  
 -      log_write_email_headers(rev, head, &pp.subject, &pp.after_subject,
 -                              &need_8bit_cte);
 +      log_write_email_headers(rev, head, &pp.after_subject, &need_8bit_cte);
  
        for (i = 0; !need_8bit_cte && i < nr; i++) {
                const char *buf = get_commit_buffer(list[i], NULL);
        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);
  
        diff_setup_done(&opts);
  
 -      diff_tree_sha1(origin->tree->object.oid.hash,
 -                     head->tree->object.oid.hash,
 -                     "", &opts);
 +      diff_tree_oid(&origin->tree->object.oid,
 +                    &head->tree->object.oid,
 +                    "", &opts);
        diffcore_std(&opts);
        diff_flush(&opts);
  
@@@ -1104,7 -1083,8 +1104,7 @@@ static const char *set_outdir(const cha
        if (!output_directory)
                return prefix;
  
 -      return xstrdup(prefix_filename(prefix, outdir_offset,
 -                                     output_directory));
 +      return prefix_filename(prefix, output_directory);
  }
  
  static const char * const builtin_format_patch_usage[] = {
@@@ -1276,7 -1256,7 +1276,7 @@@ static struct commit *get_base_commit(c
  
                        if (get_oid(upstream, &oid))
                                die(_("Failed to resolve '%s' as a valid ref."), upstream);
 -                      commit = lookup_commit_or_die(oid.hash, "upstream base");
 +                      commit = lookup_commit_or_die(&oid, "upstream base");
                        base_list = get_merge_bases_many(commit, total, list);
                        /* There should be one and only one merge base. */
                        if (!base_list || base_list->next)
  
                if (rev_nr % 2)
                        rev[i] = rev[2 * i];
 -              rev_nr = (rev_nr + 1) / 2;
 +              rev_nr = DIV_ROUND_UP(rev_nr, 2);
        }
  
        if (!in_merge_bases(base, rev[0]))
@@@ -1367,7 -1347,7 +1367,7 @@@ static void prepare_bases(struct base_t
                struct object_id *patch_id;
                if (commit->util)
                        continue;
 -              if (commit_patch_id(commit, &diffopt, oid.hash, 0))
 +              if (commit_patch_id(commit, &diffopt, &oid, 0))
                        die(_("cannot get patch id"));
                ALLOC_GROW(bases->patch_id, bases->nr_patch_id + 1, bases->alloc_patch_id);
                patch_id = bases->patch_id + bases->nr_patch_id;
@@@ -1832,7 -1812,7 +1832,7 @@@ static int add_pending_commit(const cha
  {
        struct object_id oid;
        if (get_oid(arg, &oid) == 0) {
 -              struct commit *commit = lookup_commit_reference(oid.hash);
 +              struct commit *commit = lookup_commit_reference(&oid);
                if (commit) {
                        commit->object.flags |= flags;
                        add_pending_object(revs, &commit->object, arg);
diff --combined builtin/merge.c
index d5797b8fe77c99c89323348f3a0bd1d68ae9b453,f9fe5c9d78ae0758a7c005e99f3e5c63309c4715..e15f00895043081441ed3a64128515d6a7251e22
@@@ -7,7 -7,6 +7,7 @@@
   */
  
  #include "cache.h"
 +#include "config.h"
  #include "parse-options.h"
  #include "builtin.h"
  #include "lockfile.h"
@@@ -45,6 -44,7 +45,6 @@@ struct strategy 
  
  static const char * const builtin_merge_usage[] = {
        N_("git merge [<options>] [<commit>...]"),
 -      N_("git merge [<options>] <msg> HEAD <commit>"),
        N_("git merge --abort"),
        N_("git merge --continue"),
        NULL
@@@ -244,7 -244,7 +244,7 @@@ static void drop_save(void
        unlink(git_path_merge_mode());
  }
  
 -static int save_state(unsigned char *stash)
 +static int save_state(struct object_id *stash)
  {
        int len;
        struct child_process cp = CHILD_PROCESS_INIT;
        else if (!len)          /* no changes */
                return -1;
        strbuf_setlen(&buffer, buffer.len-1);
 -      if (get_sha1(buffer.buf, stash))
 +      if (get_oid(buffer.buf, stash))
                die(_("not a valid object: %s"), buffer.buf);
        return 0;
  }
@@@ -305,18 -305,18 +305,18 @@@ static void reset_hard(unsigned const c
                die(_("read-tree failed"));
  }
  
 -static void restore_state(const unsigned char *head,
 -                        const unsigned char *stash)
 +static void restore_state(const struct object_id *head,
 +                        const struct object_id *stash)
  {
        struct strbuf sb = STRBUF_INIT;
        const char *args[] = { "stash", "apply", NULL, NULL };
  
 -      if (is_null_sha1(stash))
 +      if (is_null_oid(stash))
                return;
  
 -      reset_hard(head, 1);
 +      reset_hard(head->hash, 1);
  
 -      args[2] = sha1_to_hex(stash);
 +      args[2] = oid_to_hex(stash);
  
        /*
         * It is OK to ignore error here, for example when there was
@@@ -376,10 -376,10 +376,10 @@@ static void squash_message(struct commi
  
  static void finish(struct commit *head_commit,
                   struct commit_list *remoteheads,
 -                 const unsigned char *new_head, const char *msg)
 +                 const struct object_id *new_head, const char *msg)
  {
        struct strbuf reflog_message = STRBUF_INIT;
 -      const unsigned char *head = head_commit->object.oid.hash;
 +      const struct object_id *head = &head_commit->object.oid;
  
        if (!msg)
                strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
                else {
                        const char *argv_gc_auto[] = { "gc", "--auto", NULL };
                        update_ref(reflog_message.buf, "HEAD",
 -                              new_head, head, 0,
 +                              new_head->hash, head->hash, 0,
                                UPDATE_REFS_DIE_ON_ERR);
                        /*
                         * We ignore errors in 'gc --auto', since the
                        DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
                opts.detect_rename = DIFF_DETECT_RENAME;
                diff_setup_done(&opts);
 -              diff_tree_sha1(head, new_head, "", &opts);
 +              diff_tree_oid(head, new_head, "", &opts);
                diffcore_std(&opts);
                diff_flush(&opts);
        }
  static void merge_name(const char *remote, struct strbuf *msg)
  {
        struct commit *remote_head;
 -      unsigned char branch_head[20];
 +      struct object_id branch_head;
        struct strbuf buf = STRBUF_INIT;
        struct strbuf bname = STRBUF_INIT;
        const char *ptr;
        char *found_ref;
        int len, early;
  
 -      strbuf_branchname(&bname, remote);
 +      strbuf_branchname(&bname, remote, 0);
        remote = bname.buf;
  
 -      memset(branch_head, 0, sizeof(branch_head));
 +      oidclr(&branch_head);
        remote_head = get_merge_parent(remote);
        if (!remote_head)
                die(_("'%s' does not point to a commit"), remote);
  
 -      if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) {
 +      if (dwim_ref(remote, strlen(remote), branch_head.hash, &found_ref) > 0) {
                if (starts_with(found_ref, "refs/heads/")) {
                        strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
 -                                  sha1_to_hex(branch_head), remote);
 +                                  oid_to_hex(&branch_head), remote);
                        goto cleanup;
                }
                if (starts_with(found_ref, "refs/tags/")) {
                        strbuf_addf(msg, "%s\t\ttag '%s' of .\n",
 -                                  sha1_to_hex(branch_head), remote);
 +                                  oid_to_hex(&branch_head), remote);
                        goto cleanup;
                }
                if (starts_with(found_ref, "refs/remotes/")) {
                        strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n",
 -                                  sha1_to_hex(branch_head), remote);
 +                                  oid_to_hex(&branch_head), remote);
                        goto cleanup;
                }
        }
@@@ -537,7 -537,7 +537,7 @@@ static void parse_branch_merge_options(
                die(_("Bad branch.%s.mergeoptions string: %s"), branch,
                    split_cmdline_strerror(argc));
        REALLOC_ARRAY(argv, argc + 2);
 -      memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
 +      MOVE_ARRAY(argv + 1, argv, argc + 1);
        argc++;
        argv[0] = "branch.*.mergeoptions";
        parse_options(argc, argv, NULL, builtin_merge_options,
@@@ -566,7 -566,7 +566,7 @@@ static int git_merge_config(const char 
        else if (!strcmp(k, "merge.renormalize"))
                option_renormalize = git_config_bool(k, v);
        else if (!strcmp(k, "merge.ff")) {
-               int boolval = git_config_maybe_bool(k, v);
+               int boolval = git_parse_maybe_bool(v);
                if (0 <= boolval) {
                        fast_forward = boolval ? FF_ALLOW : FF_NO;
                } else if (v && !strcmp(v, "only")) {
        return git_diff_ui_config(k, v, cb);
  }
  
 -static int read_tree_trivial(unsigned char *common, unsigned char *head,
 -                           unsigned char *one)
 +static int read_tree_trivial(struct object_id *common, struct object_id *head,
 +                           struct object_id *one)
  {
        int i, nr_trees = 0;
        struct tree *trees[MAX_UNPACK_TREES];
        return 0;
  }
  
 -static void write_tree_trivial(unsigned char *sha1)
 +static void write_tree_trivial(struct object_id *oid)
  {
 -      if (write_cache_as_tree(sha1, 0, NULL))
 +      if (write_cache_as_tree(oid->hash, 0, NULL))
                die(_("git write-tree failed to write a tree"));
  }
  
  static int try_merge_strategy(const char *strategy, struct commit_list *common,
                              struct commit_list *remoteheads,
 -                            struct commit *head, const char *head_arg)
 +                            struct commit *head)
  {
        static struct lock_file lock;
 +      const char *head_arg = "HEAD";
  
        hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
        refresh_cache(REFRESH_QUIET);
@@@ -782,7 -781,7 +782,7 @@@ static void prepare_to_commit(struct co
  
  static int merge_trivial(struct commit *head, struct commit_list *remoteheads)
  {
 -      unsigned char result_tree[20], result_commit[20];
 +      struct object_id result_tree, result_commit;
        struct commit_list *parents, **pptr = &parents;
        static struct lock_file lock;
  
                return error(_("Unable to write index."));
        rollback_lock_file(&lock);
  
 -      write_tree_trivial(result_tree);
 +      write_tree_trivial(&result_tree);
        printf(_("Wonderful.\n"));
        pptr = commit_list_append(head, pptr);
        pptr = commit_list_append(remoteheads->item, pptr);
        prepare_to_commit(remoteheads);
 -      if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents,
 -                      result_commit, NULL, sign_commit))
 +      if (commit_tree(merge_msg.buf, merge_msg.len, result_tree.hash, parents,
 +                      result_commit.hash, NULL, sign_commit))
                die(_("failed to write commit object"));
 -      finish(head, remoteheads, result_commit, "In-index merge");
 +      finish(head, remoteheads, &result_commit, "In-index merge");
        drop_save();
        return 0;
  }
@@@ -810,12 -809,12 +810,12 @@@ static int finish_automerge(struct comm
                            int head_subsumed,
                            struct commit_list *common,
                            struct commit_list *remoteheads,
 -                          unsigned char *result_tree,
 +                          struct object_id *result_tree,
                            const char *wt_strategy)
  {
        struct commit_list *parents = NULL;
        struct strbuf buf = STRBUF_INIT;
 -      unsigned char result_commit[20];
 +      struct object_id result_commit;
  
        free_commit_list(common);
        parents = remoteheads;
                commit_list_insert(head, &parents);
        strbuf_addch(&merge_msg, '\n');
        prepare_to_commit(remoteheads);
 -      if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents,
 -                      result_commit, NULL, sign_commit))
 +      if (commit_tree(merge_msg.buf, merge_msg.len, result_tree->hash, parents,
 +                      result_commit.hash, NULL, sign_commit))
                die(_("failed to write commit object"));
        strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
 -      finish(head, remoteheads, result_commit, buf.buf);
 +      finish(head, remoteheads, &result_commit, buf.buf);
        strbuf_release(&buf);
        drop_save();
        return 0;
@@@ -840,7 -839,9 +840,7 @@@ static int suggest_conflicts(void
        struct strbuf msgbuf = STRBUF_INIT;
  
        filename = git_path_merge_msg();
 -      fp = fopen(filename, "a");
 -      if (!fp)
 -              die_errno(_("Could not open '%s' for writing"), filename);
 +      fp = xfopen(filename, "a");
  
        append_conflicts_hint(&msgbuf);
        fputs(msgbuf.buf, fp);
        return 1;
  }
  
 -static struct commit *is_old_style_invocation(int argc, const char **argv,
 -                                            const unsigned char *head)
 -{
 -      struct commit *second_token = NULL;
 -      if (argc > 2) {
 -              unsigned char second_sha1[20];
 -
 -              if (get_sha1(argv[1], second_sha1))
 -                      return NULL;
 -              second_token = lookup_commit_reference_gently(second_sha1, 0);
 -              if (!second_token)
 -                      die(_("'%s' is not a commit"), argv[1]);
 -              if (hashcmp(second_token->object.oid.hash, head))
 -                      return NULL;
 -      }
 -      return second_token;
 -}
 -
  static int evaluate_result(void)
  {
        int cnt = 0;
@@@ -940,7 -959,7 +940,7 @@@ static int default_edit_option(void
                return 0;
  
        if (e) {
-               int v = git_config_maybe_bool(name, e);
+               int v = git_parse_maybe_bool(e);
                if (v < 0)
                        die(_("Bad value '%s' in environment '%s'"), e, name);
                return v;
@@@ -1019,7 -1038,7 +1019,7 @@@ static void handle_fetch_head(struct co
                die_errno(_("could not close '%s'"), filename);
  
        for (pos = 0; pos < merge_names->len; pos = npos) {
 -              unsigned char sha1[20];
 +              struct object_id oid;
                char *ptr;
                struct commit *commit;
  
                else
                        npos = merge_names->len;
  
 -              if (npos - pos < 40 + 2 ||
 -                  get_sha1_hex(merge_names->buf + pos, sha1))
 +              if (npos - pos < GIT_SHA1_HEXSZ + 2 ||
 +                  get_oid_hex(merge_names->buf + pos, &oid))
                        commit = NULL; /* bad */
 -              else if (memcmp(merge_names->buf + pos + 40, "\t\t", 2))
 +              else if (memcmp(merge_names->buf + pos + GIT_SHA1_HEXSZ, "\t\t", 2))
                        continue; /* not-for-merge */
                else {
 -                      char saved = merge_names->buf[pos + 40];
 -                      merge_names->buf[pos + 40] = '\0';
 +                      char saved = merge_names->buf[pos + GIT_SHA1_HEXSZ];
 +                      merge_names->buf[pos + GIT_SHA1_HEXSZ] = '\0';
                        commit = get_merge_parent(merge_names->buf + pos);
 -                      merge_names->buf[pos + 40] = saved;
 +                      merge_names->buf[pos + GIT_SHA1_HEXSZ] = saved;
                }
                if (!commit) {
                        if (ptr)
@@@ -1098,9 -1117,12 +1098,9 @@@ static struct commit_list *collect_pare
  
  int cmd_merge(int argc, const char **argv, const char *prefix)
  {
 -      unsigned char result_tree[20];
 -      unsigned char stash[20];
 -      unsigned char head_sha1[20];
 +      struct object_id result_tree, stash, head_oid;
        struct commit *head_commit;
        struct strbuf buf = STRBUF_INIT;
 -      const char *head_arg;
        int i, ret = 0, head_subsumed;
        int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
        struct commit_list *common = NULL;
         * Check if we are _not_ on a detached HEAD, i.e. if there is a
         * current branch.
         */
 -      branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, NULL);
 +      branch = branch_to_free = resolve_refdup("HEAD", 0, head_oid.hash, NULL);
        if (branch && starts_with(branch, "refs/heads/"))
                branch += 11;
 -      if (!branch || is_null_sha1(head_sha1))
 +      if (!branch || is_null_oid(&head_oid))
                head_commit = NULL;
        else
 -              head_commit = lookup_commit_or_die(head_sha1, "HEAD");
 +              head_commit = lookup_commit_or_die(&head_oid, "HEAD");
  
        init_diff_ui_defaults();
        git_config(git_merge_config, NULL);
                 * to forbid "git merge" into a branch yet to be born.
                 * We do the same for "git pull".
                 */
 -              unsigned char *remote_head_sha1;
 +              struct object_id *remote_head_oid;
                if (squash)
                        die(_("Squash commit into empty head not supported yet"));
                if (fast_forward == FF_NO)
                        die(_("%s - not something we can merge"), argv[0]);
                if (remoteheads->next)
                        die(_("Can merge only exactly one commit into empty head"));
 -              remote_head_sha1 = remoteheads->item->object.oid.hash;
 -              read_empty(remote_head_sha1, 0);
 -              update_ref("initial pull", "HEAD", remote_head_sha1,
 +              remote_head_oid = &remoteheads->item->object.oid;
 +              read_empty(remote_head_oid->hash, 0);
 +              update_ref("initial pull", "HEAD", remote_head_oid->hash,
                           NULL, 0, UPDATE_REFS_DIE_ON_ERR);
                goto done;
        }
  
        /*
 -       * This could be traditional "merge <msg> HEAD <commit>..."  and
 -       * the way we can tell it is to see if the second token is HEAD,
 -       * but some people might have misused the interface and used a
 -       * commit-ish that is the same as HEAD there instead.
 -       * Traditional format never would have "-m" so it is an
 -       * additional safety measure to check for it.
 +       * All the rest are the commits being merged; prepare
 +       * the standard merge summary message to be appended
 +       * to the given message.
         */
 -      if (!have_message &&
 -          is_old_style_invocation(argc, argv, head_commit->object.oid.hash)) {
 -              warning("old-style 'git merge <msg> HEAD <commit>' is deprecated.");
 -              strbuf_addstr(&merge_msg, argv[0]);
 -              head_arg = argv[1];
 -              argv += 2;
 -              argc -= 2;
 -              remoteheads = collect_parents(head_commit, &head_subsumed,
 -                                            argc, argv, NULL);
 -      } else {
 -              /* We are invoked directly as the first-class UI. */
 -              head_arg = "HEAD";
 -
 -              /*
 -               * All the rest are the commits being merged; prepare
 -               * the standard merge summary message to be appended
 -               * to the given message.
 -               */
 -              remoteheads = collect_parents(head_commit, &head_subsumed,
 -                                            argc, argv, &merge_msg);
 -      }
 +      remoteheads = collect_parents(head_commit, &head_subsumed,
 +                                    argc, argv, &merge_msg);
  
        if (!head_commit || !argc)
                usage_with_options(builtin_merge_usage,
        if (verify_signatures) {
                for (p = remoteheads; p; p = p->next) {
                        struct commit *commit = p->item;
 -                      char hex[GIT_SHA1_HEXSZ + 1];
 +                      char hex[GIT_MAX_HEXSZ + 1];
                        struct signature_check signature_check;
                        memset(&signature_check, 0, sizeof(signature_check));
  
                        goto done;
                }
  
 -              if (checkout_fast_forward(head_commit->object.oid.hash,
 -                                        commit->object.oid.hash,
 +              if (checkout_fast_forward(&head_commit->object.oid,
 +                                        &commit->object.oid,
                                          overwrite_ignore)) {
                        ret = 1;
                        goto done;
                }
  
 -              finish(head_commit, remoteheads, commit->object.oid.hash, msg.buf);
 +              finish(head_commit, remoteheads, &commit->object.oid, msg.buf);
                drop_save();
                goto done;
        } else if (!remoteheads->next && common->next)
                        /* See if it is really trivial. */
                        git_committer_info(IDENT_STRICT);
                        printf(_("Trying really trivial in-index merge...\n"));
 -                      if (!read_tree_trivial(common->item->object.oid.hash,
 -                                             head_commit->object.oid.hash,
 -                                             remoteheads->item->object.oid.hash)) {
 +                      if (!read_tree_trivial(&common->item->object.oid,
 +                                             &head_commit->object.oid,
 +                                             &remoteheads->item->object.oid)) {
                                ret = merge_trivial(head_commit, remoteheads);
                                goto done;
                        }
            /*
             * Stash away the local changes so that we can try more than one.
             */
 -          save_state(stash))
 -              hashclr(stash);
 +          save_state(&stash))
 +              oidclr(&stash);
  
        for (i = 0; i < use_strategies_nr; i++) {
                int ret;
                if (i) {
                        printf(_("Rewinding the tree to pristine...\n"));
 -                      restore_state(head_commit->object.oid.hash, stash);
 +                      restore_state(&head_commit->object.oid, &stash);
                }
                if (use_strategies_nr != 1)
                        printf(_("Trying merge strategy %s...\n"),
  
                ret = try_merge_strategy(use_strategies[i]->name,
                                         common, remoteheads,
 -                                       head_commit, head_arg);
 +                                       head_commit);
                if (!option_commit && !ret) {
                        merge_was_ok = 1;
                        /*
                }
  
                /* Automerge succeeded. */
 -              write_tree_trivial(result_tree);
 +              write_tree_trivial(&result_tree);
                automerge_was_ok = 1;
                break;
        }
        if (automerge_was_ok) {
                ret = finish_automerge(head_commit, head_subsumed,
                                       common, remoteheads,
 -                                     result_tree, wt_strategy);
 +                                     &result_tree, wt_strategy);
                goto done;
        }
  
         * it up.
         */
        if (!best_strategy) {
 -              restore_state(head_commit->object.oid.hash, stash);
 +              restore_state(&head_commit->object.oid, &stash);
                if (use_strategies_nr > 1)
                        fprintf(stderr,
                                _("No merge strategy handled the merge.\n"));
                ; /* We already have its result in the working tree. */
        else {
                printf(_("Rewinding the tree to pristine...\n"));
 -              restore_state(head_commit->object.oid.hash, stash);
 +              restore_state(&head_commit->object.oid, &stash);
                printf(_("Using the %s to prepare resolving by hand.\n"),
                        best_strategy);
                try_merge_strategy(best_strategy, common, remoteheads,
 -                                 head_commit, head_arg);
 +                                 head_commit);
        }
  
        if (squash)
diff --combined builtin/pull.c
index 9b86e519b19a6180e52fda3342dec4f080e1f9e9,1c0eb27c3769019968131d2ff7aa1fff6906bde6..7fe281414eceaae64926caca2b6194c9fcfef299
@@@ -6,7 -6,6 +6,7 @@@
   * Fetch one or more remote refs and merge it/them into the current HEAD.
   */
  #include "cache.h"
 +#include "config.h"
  #include "builtin.h"
  #include "parse-options.h"
  #include "exec_cmd.h"
@@@ -16,8 -15,6 +16,8 @@@
  #include "dir.h"
  #include "refs.h"
  #include "revision.h"
 +#include "submodule.h"
 +#include "submodule-config.h"
  #include "tempfile.h"
  #include "lockfile.h"
  #include "wt-status.h"
@@@ -39,7 -36,7 +39,7 @@@ enum rebase_type 
  static enum rebase_type parse_config_rebase(const char *key, const char *value,
                int fatal)
  {
-       int v = git_config_maybe_bool("pull.rebase", value);
+       int v = git_parse_maybe_bool(value);
  
        if (!v)
                return REBASE_FALSE;
@@@ -80,7 -77,6 +80,7 @@@ static const char * const pull_usage[] 
  /* Shared options */
  static int opt_verbosity;
  static char *opt_progress;
 +static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
  
  /* Options passed to git-merge or git-rebase */
  static enum rebase_type opt_rebase = -1;
@@@ -105,6 -101,7 +105,6 @@@ static char *opt_upload_pack
  static int opt_force;
  static char *opt_tags;
  static char *opt_prune;
 -static char *opt_recurse_submodules;
  static char *max_children;
  static int opt_dry_run;
  static char *opt_keep;
@@@ -119,10 -116,6 +119,10 @@@ static struct option pull_options[] = 
        OPT_PASSTHRU(0, "progress", &opt_progress, NULL,
                N_("force progress reporting"),
                PARSE_OPT_NOARG),
 +      { OPTION_CALLBACK, 0, "recurse-submodules",
 +                 &recurse_submodules, N_("on-demand"),
 +                 N_("control for recursive fetching of submodules"),
 +                 PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules },
  
        /* Options passed to git-merge or git-rebase */
        OPT_GROUP(N_("Options related to merging")),
        OPT_PASSTHRU('p', "prune", &opt_prune, NULL,
                N_("prune remote-tracking branches no longer on remote"),
                PARSE_OPT_NOARG),
 -      OPT_PASSTHRU(0, "recurse-submodules", &opt_recurse_submodules,
 -              N_("on-demand"),
 -              N_("control recursive fetching of submodules"),
 -              PARSE_OPT_OPTARG),
        OPT_PASSTHRU('j', "jobs", &max_children, N_("n"),
                N_("number of submodules pulled in parallel"),
                PARSE_OPT_OPTARG),
@@@ -274,7 -271,7 +274,7 @@@ static const char *config_get_ff(void
        if (git_config_get_value("pull.ff", &value))
                return NULL;
  
-       switch (git_config_maybe_bool("pull.ff", value)) {
+       switch (git_parse_maybe_bool(value)) {
        case 0:
                return "--no-ff";
        case 1:
@@@ -333,20 -330,21 +333,20 @@@ static int git_pull_config(const char *
   * Appends merge candidates from FETCH_HEAD that are not marked not-for-merge
   * into merge_heads.
   */
 -static void get_merge_heads(struct sha1_array *merge_heads)
 +static void get_merge_heads(struct oid_array *merge_heads)
  {
 -      const char *filename = git_path("FETCH_HEAD");
 +      const char *filename = git_path_fetch_head();
        FILE *fp;
        struct strbuf sb = STRBUF_INIT;
 -      unsigned char sha1[GIT_SHA1_RAWSZ];
 +      struct object_id oid;
  
 -      if (!(fp = fopen(filename, "r")))
 -              die_errno(_("could not open '%s' for reading"), filename);
 +      fp = xfopen(filename, "r");
        while (strbuf_getline_lf(&sb, fp) != EOF) {
 -              if (get_sha1_hex(sb.buf, sha1))
 +              if (get_oid_hex(sb.buf, &oid))
                        continue;  /* invalid line: does not start with SHA1 */
                if (starts_with(sb.buf + GIT_SHA1_HEXSZ, "\tnot-for-merge\t"))
                        continue;  /* ref is not-for-merge */
 -              sha1_array_append(merge_heads, sha1);
 +              oid_array_append(merge_heads, &oid);
        }
        fclose(fp);
        strbuf_release(&sb);
@@@ -486,20 -484,8 +486,20 @@@ static int run_fetch(const char *repo, 
                argv_array_push(&args, opt_tags);
        if (opt_prune)
                argv_array_push(&args, opt_prune);
 -      if (opt_recurse_submodules)
 -              argv_array_push(&args, opt_recurse_submodules);
 +      if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT)
 +              switch (recurse_submodules) {
 +              case RECURSE_SUBMODULES_ON:
 +                      argv_array_push(&args, "--recurse-submodules=on");
 +                      break;
 +              case RECURSE_SUBMODULES_OFF:
 +                      argv_array_push(&args, "--recurse-submodules=no");
 +                      break;
 +              case RECURSE_SUBMODULES_ON_DEMAND:
 +                      argv_array_push(&args, "--recurse-submodules=on-demand");
 +                      break;
 +              default:
 +                      BUG("submodule recursion option not understood");
 +              }
        if (max_children)
                argv_array_push(&args, max_children);
        if (opt_dry_run)
  /**
   * "Pulls into void" by branching off merge_head.
   */
 -static int pull_into_void(const unsigned char *merge_head,
 -              const unsigned char *curr_head)
 +static int pull_into_void(const struct object_id *merge_head,
 +              const struct object_id *curr_head)
  {
        /*
         * Two-way merge: we treat the index as based on an empty tree,
         * index/worktree changes that the user already made on the unborn
         * branch.
         */
 -      if (checkout_fast_forward(EMPTY_TREE_SHA1_BIN, merge_head, 0))
 +      if (checkout_fast_forward(&empty_tree_oid, merge_head, 0))
                return 1;
  
 -      if (update_ref("initial pull", "HEAD", merge_head, curr_head, 0, UPDATE_REFS_DIE_ON_ERR))
 +      if (update_ref("initial pull", "HEAD", merge_head->hash, curr_head->hash, 0, UPDATE_REFS_DIE_ON_ERR))
                return 1;
  
        return 0;
  }
  
 +static int rebase_submodules(void)
 +{
 +      struct child_process cp = CHILD_PROCESS_INIT;
 +
 +      cp.git_cmd = 1;
 +      cp.no_stdin = 1;
 +      argv_array_pushl(&cp.args, "submodule", "update",
 +                                 "--recursive", "--rebase", NULL);
 +
 +      return run_command(&cp);
 +}
 +
 +static int update_submodules(void)
 +{
 +      struct child_process cp = CHILD_PROCESS_INIT;
 +
 +      cp.git_cmd = 1;
 +      cp.no_stdin = 1;
 +      argv_array_pushl(&cp.args, "submodule", "update",
 +                                 "--recursive", "--checkout", NULL);
 +
 +      return run_command(&cp);
 +}
 +
  /**
   * Runs git-merge, returning its exit status.
   */
@@@ -685,7 -647,7 +685,7 @@@ static const char *get_tracking_branch(
   * current branch forked from its remote tracking branch. Returns 0 on success,
   * -1 on failure.
   */
 -static int get_rebase_fork_point(unsigned char *fork_point, const char *repo,
 +static int get_rebase_fork_point(struct object_id *fork_point, const char *repo,
                const char *refspec)
  {
        int ret;
        if (ret)
                goto cleanup;
  
 -      ret = get_sha1_hex(sb.buf, fork_point);
 +      ret = get_oid_hex(sb.buf, fork_point);
        if (ret)
                goto cleanup;
  
@@@ -729,16 -691,16 +729,16 @@@ cleanup
   * Sets merge_base to the octopus merge base of curr_head, merge_head and
   * fork_point. Returns 0 if a merge base is found, 1 otherwise.
   */
 -static int get_octopus_merge_base(unsigned char *merge_base,
 -              const unsigned char *curr_head,
 -              const unsigned char *merge_head,
 -              const unsigned char *fork_point)
 +static int get_octopus_merge_base(struct object_id *merge_base,
 +              const struct object_id *curr_head,
 +              const struct object_id *merge_head,
 +              const struct object_id *fork_point)
  {
        struct commit_list *revs = NULL, *result;
  
        commit_list_insert(lookup_commit_reference(curr_head), &revs);
        commit_list_insert(lookup_commit_reference(merge_head), &revs);
 -      if (!is_null_sha1(fork_point))
 +      if (!is_null_oid(fork_point))
                commit_list_insert(lookup_commit_reference(fork_point), &revs);
  
        result = reduce_heads(get_octopus_merge_bases(revs));
        if (!result)
                return 1;
  
 -      hashcpy(merge_base, result->item->object.oid.hash);
 +      oidcpy(merge_base, &result->item->object.oid);
        return 0;
  }
  
   * fork point calculated by get_rebase_fork_point(), runs git-rebase with the
   * appropriate arguments and returns its exit status.
   */
 -static int run_rebase(const unsigned char *curr_head,
 -              const unsigned char *merge_head,
 -              const unsigned char *fork_point)
 +static int run_rebase(const struct object_id *curr_head,
 +              const struct object_id *merge_head,
 +              const struct object_id *fork_point)
  {
        int ret;
 -      unsigned char oct_merge_base[GIT_SHA1_RAWSZ];
 +      struct object_id oct_merge_base;
        struct argv_array args = ARGV_ARRAY_INIT;
  
 -      if (!get_octopus_merge_base(oct_merge_base, curr_head, merge_head, fork_point))
 -              if (!is_null_sha1(fork_point) && !hashcmp(oct_merge_base, fork_point))
 +      if (!get_octopus_merge_base(&oct_merge_base, curr_head, merge_head, fork_point))
 +              if (!is_null_oid(fork_point) && !oidcmp(&oct_merge_base, fork_point))
                        fork_point = NULL;
  
        argv_array_push(&args, "rebase");
                warning(_("ignoring --verify-signatures for rebase"));
  
        argv_array_push(&args, "--onto");
 -      argv_array_push(&args, sha1_to_hex(merge_head));
 +      argv_array_push(&args, oid_to_hex(merge_head));
  
 -      if (fork_point && !is_null_sha1(fork_point))
 -              argv_array_push(&args, sha1_to_hex(fork_point));
 +      if (fork_point && !is_null_oid(fork_point))
 +              argv_array_push(&args, oid_to_hex(fork_point));
        else
 -              argv_array_push(&args, sha1_to_hex(merge_head));
 +              argv_array_push(&args, oid_to_hex(merge_head));
  
        ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
        argv_array_clear(&args);
  int cmd_pull(int argc, const char **argv, const char *prefix)
  {
        const char *repo, **refspecs;
 -      struct sha1_array merge_heads = SHA1_ARRAY_INIT;
 -      unsigned char orig_head[GIT_SHA1_RAWSZ], curr_head[GIT_SHA1_RAWSZ];
 -      unsigned char rebase_fork_point[GIT_SHA1_RAWSZ];
 +      struct oid_array merge_heads = OID_ARRAY_INIT;
 +      struct object_id orig_head, curr_head;
 +      struct object_id rebase_fork_point;
 +      int autostash;
  
        if (!getenv("GIT_REFLOG_ACTION"))
                set_reflog_message(argc, argv);
        if (read_cache_unmerged())
                die_resolve_conflict("pull");
  
 -      if (file_exists(git_path("MERGE_HEAD")))
 +      if (file_exists(git_path_merge_head()))
                die_conclude_merge();
  
 -      if (get_sha1("HEAD", orig_head))
 -              hashclr(orig_head);
 +      if (get_oid("HEAD", &orig_head))
 +              oidclr(&orig_head);
  
        if (!opt_rebase && opt_autostash != -1)
                die(_("--[no-]autostash option is only valid with --rebase."));
  
 +      autostash = config_autostash;
        if (opt_rebase) {
 -              int autostash = config_autostash;
                if (opt_autostash != -1)
                        autostash = opt_autostash;
  
 -              if (is_null_sha1(orig_head) && !is_cache_unborn())
 +              if (is_null_oid(&orig_head) && !is_cache_unborn())
                        die(_("Updating an unborn branch with changes added to the index."));
  
                if (!autostash)
                        require_clean_work_tree(N_("pull with rebase"),
                                _("please commit or stash them."), 1, 0);
  
 -              if (get_rebase_fork_point(rebase_fork_point, repo, *refspecs))
 -                      hashclr(rebase_fork_point);
 +              if (get_rebase_fork_point(&rebase_fork_point, repo, *refspecs))
 +                      oidclr(&rebase_fork_point);
        }
  
        if (run_fetch(repo, refspecs))
        if (opt_dry_run)
                return 0;
  
 -      if (get_sha1("HEAD", curr_head))
 -              hashclr(curr_head);
 +      if (get_oid("HEAD", &curr_head))
 +              oidclr(&curr_head);
  
 -      if (!is_null_sha1(orig_head) && !is_null_sha1(curr_head) &&
 -                      hashcmp(orig_head, curr_head)) {
 +      if (!is_null_oid(&orig_head) && !is_null_oid(&curr_head) &&
 +                      oidcmp(&orig_head, &curr_head)) {
                /*
                 * The fetch involved updating the current branch.
                 *
  
                warning(_("fetch updated the current branch head.\n"
                        "fast-forwarding your working tree from\n"
 -                      "commit %s."), sha1_to_hex(orig_head));
 +                      "commit %s."), oid_to_hex(&orig_head));
  
 -              if (checkout_fast_forward(orig_head, curr_head, 0))
 +              if (checkout_fast_forward(&orig_head, &curr_head, 0))
                        die(_("Cannot fast-forward your working tree.\n"
                                "After making sure that you saved anything precious from\n"
                                "$ git diff %s\n"
                                "output, run\n"
                                "$ git reset --hard\n"
 -                              "to recover."), sha1_to_hex(orig_head));
 +                              "to recover."), oid_to_hex(&orig_head));
        }
  
        get_merge_heads(&merge_heads);
        if (!merge_heads.nr)
                die_no_merge_candidates(repo, refspecs);
  
 -      if (is_null_sha1(orig_head)) {
 +      if (is_null_oid(&orig_head)) {
                if (merge_heads.nr > 1)
                        die(_("Cannot merge multiple branches into empty head."));
 -              return pull_into_void(*merge_heads.sha1, curr_head);
 +              return pull_into_void(merge_heads.oid, &curr_head);
        }
        if (opt_rebase && merge_heads.nr > 1)
                die(_("Cannot rebase onto multiple branches."));
  
        if (opt_rebase) {
 -              struct commit_list *list = NULL;
 -              struct commit *merge_head, *head;
 -
 -              head = lookup_commit_reference(orig_head);
 -              commit_list_insert(head, &list);
 -              merge_head = lookup_commit_reference(merge_heads.sha1[0]);
 -              if (is_descendant_of(merge_head, list)) {
 -                      /* we can fast-forward this without invoking rebase */
 -                      opt_ff = "--ff-only";
 -                      return run_merge();
 +              int ret = 0;
 +              if ((recurse_submodules == RECURSE_SUBMODULES_ON ||
 +                   recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) &&
 +                  submodule_touches_in_range(&rebase_fork_point, &curr_head))
 +                      die(_("cannot rebase with locally recorded submodule modifications"));
 +              if (!autostash) {
 +                      struct commit_list *list = NULL;
 +                      struct commit *merge_head, *head;
 +
 +                      head = lookup_commit_reference(&orig_head);
 +                      commit_list_insert(head, &list);
 +                      merge_head = lookup_commit_reference(&merge_heads.oid[0]);
 +                      if (is_descendant_of(merge_head, list)) {
 +                              /* we can fast-forward this without invoking rebase */
 +                              opt_ff = "--ff-only";
 +                              ret = run_merge();
 +                      }
                }
 -              return run_rebase(curr_head, *merge_heads.sha1, rebase_fork_point);
 +              ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point);
 +
 +              if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON ||
 +                           recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND))
 +                      ret = rebase_submodules();
 +
 +              return ret;
        } else {
 -              return run_merge();
 +              int ret = run_merge();
 +              if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON ||
 +                           recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND))
 +                      ret = update_submodules();
 +              return ret;
        }
  }
diff --combined builtin/push.c
index 03846e83795c477c8e802d078c5a5ed77140d550,1410b66d0e1ed15f3bac8d7a41ccc1bf1f9840ac..2ac81042292ef1852ec9a31dea34ec91a1b796e3
@@@ -2,7 -2,6 +2,7 @@@
   * "git push"
   */
  #include "cache.h"
 +#include "config.h"
  #include "refs.h"
  #include "run-command.h"
  #include "builtin.h"
@@@ -481,7 -480,7 +481,7 @@@ static int git_push_config(const char *
        } else if (!strcmp(k, "push.gpgsign")) {
                const char *value;
                if (!git_config_get_value("push.gpgsign", &value)) {
-                       switch (git_config_maybe_bool("push.gpgsign", value)) {
+                       switch (git_parse_maybe_bool(value)) {
                        case 0:
                                set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_NEVER);
                                break;
                const char *value;
                if (!git_config_get_value("push.recursesubmodules", &value))
                        recurse_submodules = parse_push_recurse_submodules_arg(k, value);
 +      } else if (!strcmp(k, "submodule.recurse")) {
 +              int val = git_config_bool(k, v) ?
 +                      RECURSE_SUBMODULES_ON_DEMAND : RECURSE_SUBMODULES_OFF;
 +              recurse_submodules = val;
        }
  
        return git_default_config(k, v, NULL);
@@@ -515,8 -510,8 +515,8 @@@ int cmd_push(int argc, const char **arg
        int push_cert = -1;
        int rc;
        const char *repo = NULL;        /* default repository */
 -      static struct string_list push_options = STRING_LIST_INIT_DUP;
 -      static struct string_list_item *item;
 +      struct string_list push_options = STRING_LIST_INIT_DUP;
 +      const struct string_list_item *item;
  
        struct option options[] = {
                OPT__VERBOSITY(&verbosity),
                        die(_("push options must not have new line characters"));
  
        rc = do_push(repo, flags, &push_options);
 +      string_list_clear(&push_options, 0);
        if (rc == -1)
                usage_with_options(push_usage, options);
        else
diff --combined builtin/remote.c
index 6273c0c23c904d5f789ff794458cf0c3f2661d94,ecd7b3a8cc554197330a0bba556e806c8e9a2ed6..a995ea86c17474be248a974469f7d535c942fc70
@@@ -1,5 -1,4 +1,5 @@@
  #include "builtin.h"
 +#include "config.h"
  #include "parse-options.h"
  #include "transport.h"
  #include "remote.h"
@@@ -301,7 -300,7 +301,7 @@@ static int config_read_branches(const c
                        }
                        string_list_append(&info->merge, xstrdup(value));
                } else {
-                       int v = git_config_maybe_bool(orig_key, value);
+                       int v = git_parse_maybe_bool(value);
                        if (v >= 0)
                                info->rebase = v;
                        else if (!strcmp(value, "preserve"))
@@@ -692,7 -691,7 +692,7 @@@ static int mv(int argc, const char **ar
                read_ref_full(item->string, RESOLVE_REF_READING, oid.hash, &flag);
                if (!(flag & REF_ISSYMREF))
                        continue;
 -              if (delete_ref(item->string, NULL, REF_NODEREF))
 +              if (delete_ref(NULL, item->string, NULL, REF_NODEREF))
                        die(_("deleting '%s' failed"), item->string);
        }
        for (i = 0; i < remote_branches.nr; i++) {
@@@ -770,9 -769,7 +770,9 @@@ static int rm(int argc, const char **ar
                                strbuf_reset(&buf);
                                strbuf_addf(&buf, "branch.%s.%s",
                                                item->string, *k);
 -                              git_config_set(buf.buf, NULL);
 +                              result = git_config_set_gently(buf.buf, NULL);
 +                              if (result && result != CONFIG_NOTHING_SET)
 +                                      die(_("could not unset '%s'"), buf.buf);
                        }
                }
        }
        strbuf_release(&buf);
  
        if (!result)
 -              result = delete_refs(&branches, REF_NODEREF);
 +              result = delete_refs("remote: remove", &branches, REF_NODEREF);
        string_list_clear(&branches, 0);
  
        if (skipped.nr) {
@@@ -1152,11 -1149,8 +1152,11 @@@ static int show(int argc, const char **
                        url_nr = states.remote->url_nr;
                }
                for (i = 0; i < url_nr; i++)
 -                      /* TRANSLATORS: the colon ':' should align with
 -                         the one in "  Fetch URL: %s" translation */
 +                      /*
 +                       * TRANSLATORS: the colon ':' should align
 +                       * with the one in " Fetch URL: %s"
 +                       * translation.
 +                       */
                        printf_ln(_("  Push  URL: %s"), url[i]);
                if (!i)
                        printf_ln(_("  Push  URL: %s"), _("(no URL)"));
@@@ -1254,7 -1248,7 +1254,7 @@@ static int set_head(int argc, const cha
                        head_name = xstrdup(states.heads.items[0].string);
                free_remote_ref_states(&states);
        } else if (opt_d && !opt_a && argc == 1) {
 -              if (delete_ref(buf.buf, NULL, REF_NODEREF))
 +              if (delete_ref(NULL, buf.buf, NULL, REF_NODEREF))
                        result |= error(_("Could not delete %s"), buf.buf);
        } else
                usage_with_options(builtin_remote_sethead_usage, options);
@@@ -1305,7 -1299,7 +1305,7 @@@ static int prune_remote(const char *rem
        string_list_sort(&refs_to_prune);
  
        if (!dry_run)
 -              result |= delete_refs(&refs_to_prune, 0);
 +              result |= delete_refs("remote: prune", &refs_to_prune, 0);
  
        for_each_string_list_item(item, &states.stale) {
                const char *refname = item->util;
diff --combined builtin/send-pack.c
index 633e0c3cdd3171e6e51944c5b68e4a4afa48862e,a3d68d60ab77e9248d5289c9158fa110ab0eb547..fc4f0bb5fbc033604a13a147094c0d1bc661db17
@@@ -1,5 -1,4 +1,5 @@@
  #include "builtin.h"
 +#include "config.h"
  #include "commit.h"
  #include "refs.h"
  #include "pkt-line.h"
@@@ -105,7 -104,7 +105,7 @@@ static int send_pack_config(const char 
        if (!strcmp(k, "push.gpgsign")) {
                const char *value;
                if (!git_config_get_value("push.gpgsign", &value)) {
-                       switch (git_config_maybe_bool("push.gpgsign", value)) {
+                       switch (git_parse_maybe_bool(value)) {
                        case 0:
                                args.push_cert = SEND_PACK_PUSH_CERT_NEVER;
                                break;
@@@ -132,8 -131,8 +132,8 @@@ int cmd_send_pack(int argc, const char 
        const char *dest = NULL;
        int fd[2];
        struct child_process *conn;
 -      struct sha1_array extra_have = SHA1_ARRAY_INIT;
 -      struct sha1_array shallow = SHA1_ARRAY_INIT;
 +      struct oid_array extra_have = OID_ARRAY_INIT;
 +      struct oid_array shallow = OID_ARRAY_INIT;
        struct ref *remote_refs, *local_refs;
        int ret;
        int helper_status = 0;
        unsigned force_update = 0;
        unsigned quiet = 0;
        int push_cert = 0;
 +      struct string_list push_options = STRING_LIST_INIT_NODUP;
        unsigned use_thin_pack = 0;
        unsigned atomic = 0;
        unsigned stateless_rpc = 0;
                { OPTION_CALLBACK,
                  0, "signed", &push_cert, "yes|no|if-asked", N_("GPG sign the push"),
                  PARSE_OPT_OPTARG, option_parse_push_signed },
 +              OPT_STRING_LIST(0, "push-option", &push_options,
 +                              N_("server-specific"),
 +                              N_("option to transmit")),
                OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
                OPT_BOOL(0, "thin", &use_thin_pack, N_("use thin pack")),
                OPT_BOOL(0, "atomic", &atomic, N_("request atomic transaction on remote side")),
        args.use_thin_pack = use_thin_pack;
        args.atomic = atomic;
        args.stateless_rpc = stateless_rpc;
 +      args.push_options = push_options.nr ? &push_options : NULL;
  
        if (from_stdin) {
                struct argv_array all_refspecs = ARGV_ARRAY_INIT;
diff --combined config.c
index c1450732d9937511a0efa65e9adcf2b3c5319f60,434a7daf193a603e4b4cc9a86ee3038b8649fbd3..777527daef8a292ec6a3589319c5d84593b3d487
+++ b/config.c
@@@ -6,8 -6,6 +6,8 @@@
   *
   */
  #include "cache.h"
 +#include "config.h"
 +#include "repository.h"
  #include "lockfile.h"
  #include "exec_cmd.h"
  #include "strbuf.h"
@@@ -15,8 -13,6 +15,8 @@@
  #include "hashmap.h"
  #include "string-list.h"
  #include "utf8.h"
 +#include "dir.h"
 +#include "color.h"
  
  struct config_source {
        struct config_source *prev;
@@@ -74,6 -70,13 +74,6 @@@ static int core_compression_seen
  static int pack_compression_seen;
  static int zlib_compression_seen;
  
 -/*
 - * Default config_set that contains key-value pairs from the usual set of config
 - * config files (i.e repo specific .git/config, user wide ~/.gitconfig, XDG
 - * config file and the global /etc/gitconfig)
 - */
 -static struct config_set the_config_set;
 -
  static int config_file_fgetc(struct config_source *conf)
  {
        return getc_unlocked(conf->u.file);
@@@ -131,7 -134,7 +131,7 @@@ static int handle_path_include(const ch
        if (!path)
                return config_error_nonbool("include.path");
  
 -      expanded = expand_user_path(path);
 +      expanded = expand_user_path(path, 0);
        if (!expanded)
                return error("could not expand include path '%s'", path);
        path = expanded;
        return ret;
  }
  
 +static int prepare_include_condition_pattern(struct strbuf *pat)
 +{
 +      struct strbuf path = STRBUF_INIT;
 +      char *expanded;
 +      int prefix = 0;
 +
 +      expanded = expand_user_path(pat->buf, 1);
 +      if (expanded) {
 +              strbuf_reset(pat);
 +              strbuf_addstr(pat, expanded);
 +              free(expanded);
 +      }
 +
 +      if (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {
 +              const char *slash;
 +
 +              if (!cf || !cf->path)
 +                      return error(_("relative config include "
 +                                     "conditionals must come from files"));
 +
 +              strbuf_realpath(&path, cf->path, 1);
 +              slash = find_last_dir_sep(path.buf);
 +              if (!slash)
 +                      die("BUG: how is this possible?");
 +              strbuf_splice(pat, 0, 1, path.buf, slash - path.buf);
 +              prefix = slash - path.buf + 1 /* slash */;
 +      } else if (!is_absolute_path(pat->buf))
 +              strbuf_insert(pat, 0, "**/", 3);
 +
 +      if (pat->len && is_dir_sep(pat->buf[pat->len - 1]))
 +              strbuf_addstr(pat, "**");
 +
 +      strbuf_release(&path);
 +      return prefix;
 +}
 +
 +static int include_by_gitdir(const struct config_options *opts,
 +                           const char *cond, size_t cond_len, int icase)
 +{
 +      struct strbuf text = STRBUF_INIT;
 +      struct strbuf pattern = STRBUF_INIT;
 +      int ret = 0, prefix;
 +      const char *git_dir;
 +      int already_tried_absolute = 0;
 +
 +      if (opts->git_dir)
 +              git_dir = opts->git_dir;
 +      else
 +              goto done;
 +
 +      strbuf_realpath(&text, git_dir, 1);
 +      strbuf_add(&pattern, cond, cond_len);
 +      prefix = prepare_include_condition_pattern(&pattern);
 +
 +again:
 +      if (prefix < 0)
 +              goto done;
 +
 +      if (prefix > 0) {
 +              /*
 +               * perform literal matching on the prefix part so that
 +               * any wildcard character in it can't create side effects.
 +               */
 +              if (text.len < prefix)
 +                      goto done;
 +              if (!icase && strncmp(pattern.buf, text.buf, prefix))
 +                      goto done;
 +              if (icase && strncasecmp(pattern.buf, text.buf, prefix))
 +                      goto done;
 +      }
 +
 +      ret = !wildmatch(pattern.buf + prefix, text.buf + prefix,
 +                       icase ? WM_CASEFOLD : 0);
 +
 +      if (!ret && !already_tried_absolute) {
 +              /*
 +               * We've tried e.g. matching gitdir:~/work, but if
 +               * ~/work is a symlink to /mnt/storage/work
 +               * strbuf_realpath() will expand it, so the rule won't
 +               * match. Let's match against a
 +               * strbuf_add_absolute_path() version of the path,
 +               * which'll do the right thing
 +               */
 +              strbuf_reset(&text);
 +              strbuf_add_absolute_path(&text, git_dir);
 +              already_tried_absolute = 1;
 +              goto again;
 +      }
 +done:
 +      strbuf_release(&pattern);
 +      strbuf_release(&text);
 +      return ret;
 +}
 +
 +static int include_condition_is_true(const struct config_options *opts,
 +                                   const char *cond, size_t cond_len)
 +{
 +
 +      if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
 +              return include_by_gitdir(opts, cond, cond_len, 0);
 +      else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
 +              return include_by_gitdir(opts, cond, cond_len, 1);
 +
 +      /* unknown conditionals are always false */
 +      return 0;
 +}
 +
  int git_config_include(const char *var, const char *value, void *data)
  {
        struct config_include_data *inc = data;
 +      const char *cond, *key;
 +      int cond_len;
        int ret;
  
        /*
  
        if (!strcmp(var, "include.path"))
                ret = handle_path_include(value, inc);
 +
 +      if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
 +          (cond && include_condition_is_true(inc->opts, cond, cond_len)) &&
 +          !strcmp(key, "path"))
 +              ret = handle_path_include(value, inc);
 +
        return ret;
  }
  
@@@ -313,104 -201,11 +313,104 @@@ void git_config_push_parameter(const ch
        strbuf_release(&env);
  }
  
 +static inline int iskeychar(int c)
 +{
 +      return isalnum(c) || c == '-';
 +}
 +
 +/*
 + * Auxiliary function to sanity-check and split the key into the section
 + * identifier and variable name.
 + *
 + * Returns 0 on success, -1 when there is an invalid character in the key and
 + * -2 if there is no section name in the key.
 + *
 + * store_key - pointer to char* which will hold a copy of the key with
 + *             lowercase section and variable name
 + * baselen - pointer to int which will hold the length of the
 + *           section + subsection part, can be NULL
 + */
 +static int git_config_parse_key_1(const char *key, char **store_key, int *baselen_, int quiet)
 +{
 +      int i, dot, baselen;
 +      const char *last_dot = strrchr(key, '.');
 +
 +      /*
 +       * Since "key" actually contains the section name and the real
 +       * key name separated by a dot, we have to know where the dot is.
 +       */
 +
 +      if (last_dot == NULL || last_dot == key) {
 +              if (!quiet)
 +                      error("key does not contain a section: %s", key);
 +              return -CONFIG_NO_SECTION_OR_NAME;
 +      }
 +
 +      if (!last_dot[1]) {
 +              if (!quiet)
 +                      error("key does not contain variable name: %s", key);
 +              return -CONFIG_NO_SECTION_OR_NAME;
 +      }
 +
 +      baselen = last_dot - key;
 +      if (baselen_)
 +              *baselen_ = baselen;
 +
 +      /*
 +       * Validate the key and while at it, lower case it for matching.
 +       */
 +      if (store_key)
 +              *store_key = xmallocz(strlen(key));
 +
 +      dot = 0;
 +      for (i = 0; key[i]; i++) {
 +              unsigned char c = key[i];
 +              if (c == '.')
 +                      dot = 1;
 +              /* Leave the extended basename untouched.. */
 +              if (!dot || i > baselen) {
 +                      if (!iskeychar(c) ||
 +                          (i == baselen + 1 && !isalpha(c))) {
 +                              if (!quiet)
 +                                      error("invalid key: %s", key);
 +                              goto out_free_ret_1;
 +                      }
 +                      c = tolower(c);
 +              } else if (c == '\n') {
 +                      if (!quiet)
 +                              error("invalid key (newline): %s", key);
 +                      goto out_free_ret_1;
 +              }
 +              if (store_key)
 +                      (*store_key)[i] = c;
 +      }
 +
 +      return 0;
 +
 +out_free_ret_1:
 +      if (store_key) {
 +              FREE_AND_NULL(*store_key);
 +      }
 +      return -CONFIG_INVALID_KEY;
 +}
 +
 +int git_config_parse_key(const char *key, char **store_key, int *baselen)
 +{
 +      return git_config_parse_key_1(key, store_key, baselen, 0);
 +}
 +
 +int git_config_key_is_valid(const char *key)
 +{
 +      return !git_config_parse_key_1(key, NULL, NULL, 1);
 +}
 +
  int git_config_parse_parameter(const char *text,
                               config_fn_t fn, void *data)
  {
        const char *value;
 +      char *canonical_name;
        struct strbuf **pair;
 +      int ret;
  
        pair = strbuf_split_str(text, '=', 2);
        if (!pair[0])
                strbuf_list_free(pair);
                return error("bogus config parameter: %s", text);
        }
 -      strbuf_tolower(pair[0]);
 -      if (fn(pair[0]->buf, value, data) < 0) {
 -              strbuf_list_free(pair);
 -              return -1;
 +
 +      if (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) {
 +              ret = -1;
 +      } else {
 +              ret = (fn(canonical_name, value, data) < 0) ? -1 : 0;
 +              free(canonical_name);
        }
        strbuf_list_free(pair);
 -      return 0;
 +      return ret;
  }
  
  int git_config_from_parameters(config_fn_t fn, void *data)
@@@ -563,6 -356,11 +563,6 @@@ static char *parse_value(void
        }
  }
  
 -static inline int iskeychar(int c)
 -{
 -      return isalnum(c) || c == '-';
 -}
 -
  static int get_value(config_fn_t fn, void *data, struct strbuf *name)
  {
        int c;
         */
        cf->linenr--;
        ret = fn(name->buf, value, data);
 -      cf->linenr++;
 +      if (ret >= 0)
 +              cf->linenr++;
        return ret;
  }
  
@@@ -854,15 -651,6 +854,15 @@@ int git_parse_ulong(const char *value, 
        return 1;
  }
  
 +static int git_parse_ssize_t(const char *value, ssize_t *ret)
 +{
 +      intmax_t tmp;
 +      if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(ssize_t)))
 +              return 0;
 +      *ret = tmp;
 +      return 1;
 +}
 +
  NORETURN
  static void die_bad_number(const char *name, const char *value)
  {
@@@ -921,15 -709,7 +921,15 @@@ unsigned long git_config_ulong(const ch
        return ret;
  }
  
- int git_parse_maybe_bool(const char *value)
 +ssize_t git_config_ssize_t(const char *name, const char *value)
 +{
 +      ssize_t ret;
 +      if (!git_parse_ssize_t(value, &ret))
 +              die_bad_number(name, value);
 +      return ret;
 +}
 +
+ static int git_parse_maybe_bool_text(const char *value)
  {
        if (!value)
                return 1;
        return -1;
  }
  
- int git_config_maybe_bool(const char *name, const char *value)
+ int git_parse_maybe_bool(const char *value)
  {
-       int v = git_parse_maybe_bool(value);
+       int v = git_parse_maybe_bool_text(value);
        if (0 <= v)
                return v;
        if (git_parse_int(value, &v))
        return -1;
  }
  
+ int git_config_maybe_bool(const char *name, const char *value)
+ {
+       return git_parse_maybe_bool(value);
+ }
  int git_config_bool_or_int(const char *name, const char *value, int *is_bool)
  {
-       int v = git_parse_maybe_bool(value);
+       int v = git_parse_maybe_bool_text(value);
        if (0 <= v) {
                *is_bool = 1;
                return v;
@@@ -985,7 -770,7 +990,7 @@@ int git_config_pathname(const char **de
  {
        if (!value)
                return config_error_nonbool(var);
 -      *dest = expand_user_path(value);
 +      *dest = expand_user_path(value, 0);
        if (!*dest)
                die(_("failed to expand user dir in: '%s'"), value);
        return 0;
@@@ -1351,9 -1136,6 +1356,9 @@@ int git_default_config(const char *var
        if (starts_with(var, "advice."))
                return git_default_advice_config(var, value);
  
 +      if (git_color_config(var, value, dummy) < 0)
 +              return -1;
 +
        if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
                pager_use_color = git_config_bool(var,value);
                return 0;
@@@ -1435,7 -1217,7 +1440,7 @@@ int git_config_from_file(config_fn_t fn
        int ret = -1;
        FILE *f;
  
 -      f = fopen(filename, "r");
 +      f = fopen_or_warn(filename, "r");
        if (f) {
                flockfile(f);
                ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename, filename, f, data);
@@@ -1464,9 -1246,9 +1469,9 @@@ int git_config_from_mem(config_fn_t fn
        return do_config_from(&top, fn, data);
  }
  
 -int git_config_from_blob_sha1(config_fn_t fn,
 +int git_config_from_blob_oid(config_fn_t fn,
                              const char *name,
 -                            const unsigned char *sha1,
 +                            const struct object_id *oid,
                              void *data)
  {
        enum object_type type;
        unsigned long size;
        int ret;
  
 -      buf = read_sha1_file(sha1, &type, &size);
 +      buf = read_sha1_file(oid->hash, &type, &size);
        if (!buf)
                return error("unable to load config blob object '%s'", name);
        if (type != OBJ_BLOB) {
@@@ -1492,11 -1274,11 +1497,11 @@@ static int git_config_from_blob_ref(con
                                    const char *name,
                                    void *data)
  {
 -      unsigned char sha1[20];
 +      struct object_id oid;
  
 -      if (get_sha1(name, sha1) < 0)
 +      if (get_oid(name, &oid) < 0)
                return error("unable to resolve config blob '%s'", name);
 -      return git_config_from_blob_sha1(fn, name, sha1, data);
 +      return git_config_from_blob_oid(fn, name, &oid, data);
  }
  
  const char *git_etc_gitconfig(void)
@@@ -1534,18 -1316,12 +1539,18 @@@ int git_config_system(void
        return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
  }
  
 -static int do_git_config_sequence(config_fn_t fn, void *data)
 +static int do_git_config_sequence(const struct config_options *opts,
 +                                config_fn_t fn, void *data)
  {
        int ret = 0;
        char *xdg_config = xdg_config_home("config");
 -      char *user_config = expand_user_path("~/.gitconfig");
 -      char *repo_config = have_git_dir() ? git_pathdup("config") : NULL;
 +      char *user_config = expand_user_path("~/.gitconfig", 0);
 +      char *repo_config;
 +
 +      if (opts->commondir)
 +              repo_config = mkpathdup("%s/config", opts->commondir);
 +      else
 +              repo_config = NULL;
  
        current_parsing_scope = CONFIG_SCOPE_SYSTEM;
        if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0))
        return ret;
  }
  
 -int git_config_with_options(config_fn_t fn, void *data,
 -                          struct git_config_source *config_source,
 -                          int respect_includes)
 +int config_with_options(config_fn_t fn, void *data,
 +                      struct git_config_source *config_source,
 +                      const struct config_options *opts)
  {
        struct config_include_data inc = CONFIG_INCLUDE_INIT;
  
 -      if (respect_includes) {
 +      if (opts->respect_includes) {
                inc.fn = fn;
                inc.data = data;
 +              inc.opts = opts;
                fn = git_config_include;
                data = &inc;
        }
        else if (config_source && config_source->blob)
                return git_config_from_blob_ref(fn, config_source->blob, data);
  
 -      return do_git_config_sequence(fn, data);
 -}
 -
 -static void git_config_raw(config_fn_t fn, void *data)
 -{
 -      if (git_config_with_options(fn, data, NULL, 1) < 0)
 -              /*
 -               * git_config_with_options() normally returns only
 -               * zero, as most errors are fatal, and
 -               * non-fatal potential errors are guarded by "if"
 -               * statements that are entered only when no error is
 -               * possible.
 -               *
 -               * If we ever encounter a non-fatal error, it means
 -               * something went really wrong and we should stop
 -               * immediately.
 -               */
 -              die(_("unknown error occurred while reading the configuration files"));
 +      return do_git_config_sequence(opts, fn, data);
  }
  
  static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
        }
  }
  
 -static void git_config_check_init(void);
 -
 -void git_config(config_fn_t fn, void *data)
 +void read_early_config(config_fn_t cb, void *data)
  {
 -      git_config_check_init();
 -      configset_iter(&the_config_set, fn, data);
 +      struct config_options opts = {0};
 +      struct strbuf commondir = STRBUF_INIT;
 +      struct strbuf gitdir = STRBUF_INIT;
 +
 +      opts.respect_includes = 1;
 +
 +      if (have_git_dir()) {
 +              opts.commondir = get_git_common_dir();
 +              opts.git_dir = get_git_dir();
 +      /*
 +       * When setup_git_directory() was not yet asked to discover the
 +       * GIT_DIR, we ask discover_git_directory() to figure out whether there
 +       * is any repository config we should use (but unlike
 +       * setup_git_directory_gently(), no global state is changed, most
 +       * notably, the current working directory is still the same after the
 +       * call).
 +       */
 +      } else if (!discover_git_directory(&commondir, &gitdir)) {
 +              opts.commondir = commondir.buf;
 +              opts.git_dir = gitdir.buf;
 +      }
 +
 +      config_with_options(cb, data, NULL, &opts);
 +
 +      strbuf_release(&commondir);
 +      strbuf_release(&gitdir);
  }
  
  static struct config_set_element *configset_find_element(struct config_set *cs, const char *key)
@@@ -1718,20 -1488,15 +1723,20 @@@ static int configset_add_value(struct c
        return 0;
  }
  
 -static int config_set_element_cmp(const struct config_set_element *e1,
 -                               const struct config_set_element *e2, const void *unused)
 +static int config_set_element_cmp(const void *unused_cmp_data,
 +                                const void *entry,
 +                                const void *entry_or_key,
 +                                const void *unused_keydata)
  {
 +      const struct config_set_element *e1 = entry;
 +      const struct config_set_element *e2 = entry_or_key;
 +
        return strcmp(e1->key, e2->key);
  }
  
  void git_configset_init(struct config_set *cs)
  {
 -      hashmap_init(&cs->config_hash, (hashmap_cmp_fn)config_set_element_cmp, 0);
 +      hashmap_init(&cs->config_hash, config_set_element_cmp, NULL, 0);
        cs->hash_initialized = 1;
        cs->list.nr = 0;
        cs->list.alloc = 0;
@@@ -1852,7 -1617,7 +1857,7 @@@ int git_configset_get_maybe_bool(struc
  {
        const char *value;
        if (!git_configset_get_value(cs, key, &value)) {
-               *dest = git_config_maybe_bool(key, value);
+               *dest = git_parse_maybe_bool(value);
                if (*dest == -1)
                        return -1;
                return 0;
@@@ -1869,223 -1634,86 +1874,223 @@@ int git_configset_get_pathname(struct c
                return 1;
  }
  
 -static void git_config_check_init(void)
 +/* Functions use to read configuration from a repository */
 +static void repo_read_config(struct repository *repo)
 +{
 +      struct config_options opts;
 +
 +      opts.respect_includes = 1;
 +      opts.commondir = repo->commondir;
 +      opts.git_dir = repo->gitdir;
 +
 +      if (!repo->config)
 +              repo->config = xcalloc(1, sizeof(struct config_set));
 +      else
 +              git_configset_clear(repo->config);
 +
 +      git_configset_init(repo->config);
 +
 +      if (config_with_options(config_set_callback, repo->config, NULL, &opts) < 0)
 +              /*
 +               * config_with_options() normally returns only
 +               * zero, as most errors are fatal, and
 +               * non-fatal potential errors are guarded by "if"
 +               * statements that are entered only when no error is
 +               * possible.
 +               *
 +               * If we ever encounter a non-fatal error, it means
 +               * something went really wrong and we should stop
 +               * immediately.
 +               */
 +              die(_("unknown error occurred while reading the configuration files"));
 +}
 +
 +static void git_config_check_init(struct repository *repo)
  {
 -      if (the_config_set.hash_initialized)
 +      if (repo->config && repo->config->hash_initialized)
                return;
 -      git_configset_init(&the_config_set);
 -      git_config_raw(config_set_callback, &the_config_set);
 +      repo_read_config(repo);
  }
  
 -void git_config_clear(void)
 +static void repo_config_clear(struct repository *repo)
  {
 -      if (!the_config_set.hash_initialized)
 +      if (!repo->config || !repo->config->hash_initialized)
                return;
 -      git_configset_clear(&the_config_set);
 +      git_configset_clear(repo->config);
  }
  
 -int git_config_get_value(const char *key, const char **value)
 +void repo_config(struct repository *repo, config_fn_t fn, void *data)
  {
 -      git_config_check_init();
 -      return git_configset_get_value(&the_config_set, key, value);
 +      git_config_check_init(repo);
 +      configset_iter(repo->config, fn, data);
  }
  
 -const struct string_list *git_config_get_value_multi(const char *key)
 +int repo_config_get_value(struct repository *repo,
 +                        const char *key, const char **value)
  {
 -      git_config_check_init();
 -      return git_configset_get_value_multi(&the_config_set, key);
 +      git_config_check_init(repo);
 +      return git_configset_get_value(repo->config, key, value);
  }
  
 -int git_config_get_string_const(const char *key, const char **dest)
 +const struct string_list *repo_config_get_value_multi(struct repository *repo,
 +                                                    const char *key)
 +{
 +      git_config_check_init(repo);
 +      return git_configset_get_value_multi(repo->config, key);
 +}
 +
 +int repo_config_get_string_const(struct repository *repo,
 +                               const char *key, const char **dest)
 +{
 +      int ret;
 +      git_config_check_init(repo);
 +      ret = git_configset_get_string_const(repo->config, key, dest);
 +      if (ret < 0)
 +              git_die_config(key, NULL);
 +      return ret;
 +}
 +
 +int repo_config_get_string(struct repository *repo,
 +                         const char *key, char **dest)
 +{
 +      git_config_check_init(repo);
 +      return repo_config_get_string_const(repo, key, (const char **)dest);
 +}
 +
 +int repo_config_get_int(struct repository *repo,
 +                      const char *key, int *dest)
 +{
 +      git_config_check_init(repo);
 +      return git_configset_get_int(repo->config, key, dest);
 +}
 +
 +int repo_config_get_ulong(struct repository *repo,
 +                        const char *key, unsigned long *dest)
 +{
 +      git_config_check_init(repo);
 +      return git_configset_get_ulong(repo->config, key, dest);
 +}
 +
 +int repo_config_get_bool(struct repository *repo,
 +                       const char *key, int *dest)
 +{
 +      git_config_check_init(repo);
 +      return git_configset_get_bool(repo->config, key, dest);
 +}
 +
 +int repo_config_get_bool_or_int(struct repository *repo,
 +                              const char *key, int *is_bool, int *dest)
 +{
 +      git_config_check_init(repo);
 +      return git_configset_get_bool_or_int(repo->config, key, is_bool, dest);
 +}
 +
 +int repo_config_get_maybe_bool(struct repository *repo,
 +                             const char *key, int *dest)
 +{
 +      git_config_check_init(repo);
 +      return git_configset_get_maybe_bool(repo->config, key, dest);
 +}
 +
 +int repo_config_get_pathname(struct repository *repo,
 +                           const char *key, const char **dest)
  {
        int ret;
 -      git_config_check_init();
 -      ret = git_configset_get_string_const(&the_config_set, key, dest);
 +      git_config_check_init(repo);
 +      ret = git_configset_get_pathname(repo->config, key, dest);
        if (ret < 0)
                git_die_config(key, NULL);
        return ret;
  }
  
 +/* Functions used historically to read configuration from 'the_repository' */
 +void git_config(config_fn_t fn, void *data)
 +{
 +      repo_config(the_repository, fn, data);
 +}
 +
 +void git_config_clear(void)
 +{
 +      repo_config_clear(the_repository);
 +}
 +
 +int git_config_get_value(const char *key, const char **value)
 +{
 +      return repo_config_get_value(the_repository, key, value);
 +}
 +
 +const struct string_list *git_config_get_value_multi(const char *key)
 +{
 +      return repo_config_get_value_multi(the_repository, key);
 +}
 +
 +int git_config_get_string_const(const char *key, const char **dest)
 +{
 +      return repo_config_get_string_const(the_repository, key, dest);
 +}
 +
  int git_config_get_string(const char *key, char **dest)
  {
 -      git_config_check_init();
 -      return git_config_get_string_const(key, (const char **)dest);
 +      return repo_config_get_string(the_repository, key, dest);
  }
  
  int git_config_get_int(const char *key, int *dest)
  {
 -      git_config_check_init();
 -      return git_configset_get_int(&the_config_set, key, dest);
 +      return repo_config_get_int(the_repository, key, dest);
  }
  
  int git_config_get_ulong(const char *key, unsigned long *dest)
  {
 -      git_config_check_init();
 -      return git_configset_get_ulong(&the_config_set, key, dest);
 +      return repo_config_get_ulong(the_repository, key, dest);
  }
  
  int git_config_get_bool(const char *key, int *dest)
  {
 -      git_config_check_init();
 -      return git_configset_get_bool(&the_config_set, key, dest);
 +      return repo_config_get_bool(the_repository, key, dest);
  }
  
  int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest)
  {
 -      git_config_check_init();
 -      return git_configset_get_bool_or_int(&the_config_set, key, is_bool, dest);
 +      return repo_config_get_bool_or_int(the_repository, key, is_bool, dest);
  }
  
  int git_config_get_maybe_bool(const char *key, int *dest)
  {
 -      git_config_check_init();
 -      return git_configset_get_maybe_bool(&the_config_set, key, dest);
 +      return repo_config_get_maybe_bool(the_repository, key, dest);
  }
  
  int git_config_get_pathname(const char *key, const char **dest)
  {
 -      int ret;
 -      git_config_check_init();
 -      ret = git_configset_get_pathname(&the_config_set, key, dest);
 -      if (ret < 0)
 -              git_die_config(key, NULL);
 +      return repo_config_get_pathname(the_repository, key, dest);
 +}
 +
 +/*
 + * Note: This function exists solely to maintain backward compatibility with
 + * 'fetch' and 'update_clone' storing configuration in '.gitmodules' and should
 + * NOT be used anywhere else.
 + *
 + * Runs the provided config function on the '.gitmodules' file found in the
 + * working directory.
 + */
 +void config_from_gitmodules(config_fn_t fn, void *data)
 +{
 +      if (the_repository->worktree) {
 +              char *file = repo_worktree_path(the_repository, GITMODULES_FILE);
 +              git_config_from_file(fn, file, data);
 +              free(file);
 +      }
 +}
 +
 +int git_config_get_expiry(const char *key, const char **output)
 +{
 +      int ret = git_config_get_string_const(key, output);
 +      if (ret)
 +              return ret;
 +      if (strcmp(*output, "now")) {
 +              timestamp_t now = approxidate("now");
 +              if (approxidate(*output) >= now)
 +                      git_die_config(key, _("Invalid %s: '%s'"), key, *output);
 +      }
        return ret;
  }
  
@@@ -2105,39 -1733,14 +2110,39 @@@ int git_config_get_untracked_cache(void
                if (!strcasecmp(v, "keep"))
                        return -1;
  
 -              error("unknown core.untrackedCache value '%s'; "
 -                    "using 'keep' default value", v);
 +              error(_("unknown core.untrackedCache value '%s'; "
 +                      "using 'keep' default value"), v);
                return -1;
        }
  
        return -1; /* default value */
  }
  
 +int git_config_get_split_index(void)
 +{
 +      int val;
 +
 +      if (!git_config_get_maybe_bool("core.splitindex", &val))
 +              return val;
 +
 +      return -1; /* default value */
 +}
 +
 +int git_config_get_max_percent_split_change(void)
 +{
 +      int val = -1;
 +
 +      if (!git_config_get_int("splitindex.maxpercentchange", &val)) {
 +              if (0 <= val && val <= 100)
 +                      return val;
 +
 +              return error(_("splitIndex.maxPercentChange value '%d' "
 +                             "should be between 0 and 100"), val);
 +      }
 +
 +      return -1; /* default value */
 +}
 +
  NORETURN
  void git_die_config_linenr(const char *key, const char *filename, int linenr)
  {
@@@ -2391,6 -1994,93 +2396,6 @@@ void git_config_set(const char *key, co
        git_config_set_multivar(key, value, NULL, 0);
  }
  
 -/*
 - * Auxiliary function to sanity-check and split the key into the section
 - * identifier and variable name.
 - *
 - * Returns 0 on success, -1 when there is an invalid character in the key and
 - * -2 if there is no section name in the key.
 - *
 - * store_key - pointer to char* which will hold a copy of the key with
 - *             lowercase section and variable name
 - * baselen - pointer to int which will hold the length of the
 - *           section + subsection part, can be NULL
 - */
 -static int git_config_parse_key_1(const char *key, char **store_key, int *baselen_, int quiet)
 -{
 -      int i, dot, baselen;
 -      const char *last_dot = strrchr(key, '.');
 -
 -      /*
 -       * Since "key" actually contains the section name and the real
 -       * key name separated by a dot, we have to know where the dot is.
 -       */
 -
 -      if (last_dot == NULL || last_dot == key) {
 -              if (!quiet)
 -                      error("key does not contain a section: %s", key);
 -              return -CONFIG_NO_SECTION_OR_NAME;
 -      }
 -
 -      if (!last_dot[1]) {
 -              if (!quiet)
 -                      error("key does not contain variable name: %s", key);
 -              return -CONFIG_NO_SECTION_OR_NAME;
 -      }
 -
 -      baselen = last_dot - key;
 -      if (baselen_)
 -              *baselen_ = baselen;
 -
 -      /*
 -       * Validate the key and while at it, lower case it for matching.
 -       */
 -      if (store_key)
 -              *store_key = xmallocz(strlen(key));
 -
 -      dot = 0;
 -      for (i = 0; key[i]; i++) {
 -              unsigned char c = key[i];
 -              if (c == '.')
 -                      dot = 1;
 -              /* Leave the extended basename untouched.. */
 -              if (!dot || i > baselen) {
 -                      if (!iskeychar(c) ||
 -                          (i == baselen + 1 && !isalpha(c))) {
 -                              if (!quiet)
 -                                      error("invalid key: %s", key);
 -                              goto out_free_ret_1;
 -                      }
 -                      c = tolower(c);
 -              } else if (c == '\n') {
 -                      if (!quiet)
 -                              error("invalid key (newline): %s", key);
 -                      goto out_free_ret_1;
 -              }
 -              if (store_key)
 -                      (*store_key)[i] = c;
 -      }
 -
 -      return 0;
 -
 -out_free_ret_1:
 -      if (store_key) {
 -              free(*store_key);
 -              *store_key = NULL;
 -      }
 -      return -CONFIG_INVALID_KEY;
 -}
 -
 -int git_config_parse_key(const char *key, char **store_key, int *baselen)
 -{
 -      return git_config_parse_key_1(key, store_key, baselen, 0);
 -}
 -
 -int git_config_key_is_valid(const char *key)
 -{
 -      return !git_config_parse_key_1(key, NULL, NULL, 1);
 -}
 -
  /*
   * If value==NULL, unset in (remove from) config,
   * if value_regex!=NULL, disregard key/value pairs where value does not match.
@@@ -2738,7 -2428,7 +2743,7 @@@ int git_config_rename_section_in_file(c
        struct lock_file *lock;
        int out_fd;
        char buf[1024];
 -      FILE *config_file;
 +      FILE *config_file = NULL;
        struct stat st;
  
        if (new_name && !section_name_is_ok(new_name)) {
        }
  
        if (!(config_file = fopen(config_filename, "rb"))) {
 +              ret = warn_on_fopen_errors(config_filename);
 +              if (ret)
 +                      goto out;
                /* no config file means nothing to rename, no error */
                goto commit_and_out;
        }
                }
        }
        fclose(config_file);
 +      config_file = NULL;
  commit_and_out:
        if (commit_lock_file(lock) < 0)
                ret = error_errno("could not write config file %s",
                                  config_filename);
  out:
 +      if (config_file)
 +              fclose(config_file);
        rollback_lock_file(lock);
  out_no_rollback:
        free(filename_buf);
@@@ -2857,10 -2541,11 +2862,10 @@@ int parse_config_key(const char *var
                     const char **subsection, int *subsection_len,
                     const char **key)
  {
 -      int section_len = strlen(section);
        const char *dot;
  
        /* Does it start with "section." ? */
 -      if (!starts_with(var, section) || var[section_len] != '.')
 +      if (!skip_prefix(var, section, &var) || *var != '.')
                return -1;
  
        /*
        *key = dot + 1;
  
        /* Did we have a subsection at all? */
 -      if (dot == var + section_len) {
 -              *subsection = NULL;
 -              *subsection_len = 0;
 +      if (dot == var) {
 +              if (subsection) {
 +                      *subsection = NULL;
 +                      *subsection_len = 0;
 +              }
        }
        else {
 -              *subsection = var + section_len + 1;
 +              if (!subsection)
 +                      return -1;
 +              *subsection = var + 1;
                *subsection_len = dot - *subsection;
        }
  
diff --combined pager.c
index 4dd9e1b26592bd3e7a7ddb52182ea79971557db0,40347d4e22e4da8c13c5d53b2fc58149e1a3e380..92b23e6cd1d44a26c86afeeb748ddc0aee3f9154
+++ b/pager.c
@@@ -1,5 -1,4 +1,5 @@@
  #include "cache.h"
 +#include "config.h"
  #include "run-command.h"
  #include "sigchain.h"
  
@@@ -44,6 -43,37 +44,6 @@@ static int core_pager_config(const cha
        return 0;
  }
  
 -static void read_early_config(config_fn_t cb, void *data)
 -{
 -      git_config_with_options(cb, data, NULL, 1);
 -
 -      /*
 -       * Note that this is a really dirty hack that does the wrong thing in
 -       * many cases. The crux of the problem is that we cannot run
 -       * setup_git_directory() early on in git's setup, so we have no idea if
 -       * we are in a repository or not, and therefore are not sure whether
 -       * and how to read repository-local config.
 -       *
 -       * So if we _aren't_ in a repository (or we are but we would reject its
 -       * core.repositoryformatversion), we'll read whatever is in .git/config
 -       * blindly. Similarly, if we _are_ in a repository, but not at the
 -       * root, we'll fail to find .git/config (because it's really
 -       * ../.git/config, etc). See t7006 for a complete set of failures.
 -       *
 -       * However, we have historically provided this hack because it does
 -       * work some of the time (namely when you are at the top-level of a
 -       * valid repository), and would rarely make things worse (i.e., you do
 -       * not generally have a .git/config file sitting around).
 -       */
 -      if (!startup_info->have_repository) {
 -              struct git_config_source repo_config;
 -
 -              memset(&repo_config, 0, sizeof(repo_config));
 -              repo_config.file = ".git/config";
 -              git_config_with_options(cb, data, &repo_config, 1);
 -      }
 -}
 -
  const char *git_pager(int stdout_is_tty)
  {
        const char *pager;
@@@ -136,7 -166,9 +136,7 @@@ void setup_pager(void
  
  int pager_in_use(void)
  {
 -      const char *env;
 -      env = getenv("GIT_PAGER_IN_USE");
 -      return env ? git_config_bool("GIT_PAGER_IN_USE", env) : 0;
 +      return git_env_bool("GIT_PAGER_IN_USE", 0);
  }
  
  /*
@@@ -194,7 -226,7 +194,7 @@@ static int pager_command_config(const c
        const char *cmd;
  
        if (skip_prefix(var, "pager.", &cmd) && !strcmp(cmd, data->cmd)) {
-               int b = git_config_maybe_bool(var, value);
+               int b = git_parse_maybe_bool(value);
                if (b >= 0)
                        data->want = b;
                else {
diff --combined submodule-config.c
index 2b83c2319fbf5af69383cf188980e7787c767ab3,0fcdb392679162ec2a4d59033401a46dc55a35df..0c839019e1dca2e30ac0113303d55b4387bbe96d
@@@ -1,10 -1,7 +1,10 @@@
  #include "cache.h"
 +#include "repository.h"
 +#include "config.h"
  #include "submodule-config.h"
  #include "submodule.h"
  #include "strbuf.h"
 +#include "parse-options.h"
  
  /*
   * submodule cache lookup structure
@@@ -17,7 -14,6 +17,7 @@@
  struct submodule_cache {
        struct hashmap for_path;
        struct hashmap for_name;
 +      unsigned initialized:1;
  };
  
  /*
@@@ -34,40 -30,29 +34,40 @@@ enum lookup_type 
        lookup_path
  };
  
 -static struct submodule_cache the_submodule_cache;
 -static int is_cache_init;
 -
 -static int config_path_cmp(const struct submodule_entry *a,
 -                         const struct submodule_entry *b,
 -                         const void *unused)
 +static int config_path_cmp(const void *unused_cmp_data,
 +                         const void *entry,
 +                         const void *entry_or_key,
 +                         const void *unused_keydata)
  {
 +      const struct submodule_entry *a = entry;
 +      const struct submodule_entry *b = entry_or_key;
 +
        return strcmp(a->config->path, b->config->path) ||
               hashcmp(a->config->gitmodules_sha1, b->config->gitmodules_sha1);
  }
  
 -static int config_name_cmp(const struct submodule_entry *a,
 -                         const struct submodule_entry *b,
 -                         const void *unused)
 +static int config_name_cmp(const void *unused_cmp_data,
 +                         const void *entry,
 +                         const void *entry_or_key,
 +                         const void *unused_keydata)
  {
 +      const struct submodule_entry *a = entry;
 +      const struct submodule_entry *b = entry_or_key;
 +
        return strcmp(a->config->name, b->config->name) ||
               hashcmp(a->config->gitmodules_sha1, b->config->gitmodules_sha1);
  }
  
 -static void cache_init(struct submodule_cache *cache)
 +static struct submodule_cache *submodule_cache_alloc(void)
 +{
 +      return xcalloc(1, sizeof(struct submodule_cache));
 +}
 +
 +static void submodule_cache_init(struct submodule_cache *cache)
  {
 -      hashmap_init(&cache->for_path, (hashmap_cmp_fn) config_path_cmp, 0);
 -      hashmap_init(&cache->for_name, (hashmap_cmp_fn) config_name_cmp, 0);
 +      hashmap_init(&cache->for_path, config_path_cmp, NULL, 0);
 +      hashmap_init(&cache->for_name, config_name_cmp, NULL, 0);
 +      cache->initialized = 1;
  }
  
  static void free_one_config(struct submodule_entry *entry)
        free(entry->config);
  }
  
 -static void cache_free(struct submodule_cache *cache)
 +static void submodule_cache_clear(struct submodule_cache *cache)
  {
        struct hashmap_iter iter;
        struct submodule_entry *entry;
  
 +      if (!cache->initialized)
 +              return;
 +
        /*
         * We iterate over the name hash here to be symmetric with the
         * allocation of struct submodule entries. Each is allocated by
  
        hashmap_free(&cache->for_path, 1);
        hashmap_free(&cache->for_name, 1);
 +      cache->initialized = 0;
 +}
 +
 +void submodule_cache_free(struct submodule_cache *cache)
 +{
 +      submodule_cache_clear(cache);
 +      free(cache);
  }
  
  static unsigned int hash_sha1_string(const unsigned char *sha1,
@@@ -238,7 -213,7 +238,7 @@@ static struct submodule *lookup_or_crea
  static int parse_fetch_recurse(const char *opt, const char *arg,
                               int die_on_error)
  {
-       switch (git_config_maybe_bool(opt, arg)) {
+       switch (git_parse_maybe_bool(arg)) {
        case 1:
                return RECURSE_SUBMODULES_ON;
        case 0:
        }
  }
  
 +int parse_submodule_fetchjobs(const char *var, const char *value)
 +{
 +      int fetchjobs = git_config_int(var, value);
 +      if (fetchjobs < 0)
 +              die(_("negative values not allowed for submodule.fetchjobs"));
 +      return fetchjobs;
 +}
 +
  int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg)
  {
        return parse_fetch_recurse(opt, arg, 1);
  }
  
-       switch (git_config_maybe_bool(opt, arg)) {
 +int option_fetch_parse_recurse_submodules(const struct option *opt,
 +                                        const char *arg, int unset)
 +{
 +      int *v;
 +
 +      if (!opt->value)
 +              return -1;
 +
 +      v = opt->value;
 +
 +      if (unset) {
 +              *v = RECURSE_SUBMODULES_OFF;
 +      } else {
 +              if (arg)
 +                      *v = parse_fetch_recurse_submodules_arg(opt->long_name, arg);
 +              else
 +                      *v = RECURSE_SUBMODULES_ON;
 +      }
 +      return 0;
 +}
 +
 +static int parse_update_recurse(const char *opt, const char *arg,
 +                              int die_on_error)
 +{
++      switch (git_parse_maybe_bool(arg)) {
 +      case 1:
 +              return RECURSE_SUBMODULES_ON;
 +      case 0:
 +              return RECURSE_SUBMODULES_OFF;
 +      default:
 +              if (die_on_error)
 +                      die("bad %s argument: %s", opt, arg);
 +              return RECURSE_SUBMODULES_ERROR;
 +      }
 +}
 +
 +int parse_update_recurse_submodules_arg(const char *opt, const char *arg)
 +{
 +      return parse_update_recurse(opt, arg, 1);
 +}
 +
  static int parse_push_recurse(const char *opt, const char *arg,
                               int die_on_error)
  {
-       switch (git_config_maybe_bool(opt, arg)) {
+       switch (git_parse_maybe_bool(arg)) {
        case 1:
                /* There's no simple "on" value when pushing */
                if (die_on_error)
@@@ -407,7 -333,7 +407,7 @@@ static int parse_config(const char *var
                         strcmp(value, "all") &&
                         strcmp(value, "none"))
                        warning("Invalid parameter '%s' for config option "
 -                                      "'submodule.%s.ignore'", value, var);
 +                                      "'submodule.%s.ignore'", value, name.buf);
                else {
                        free((void *) submodule->ignore);
                        submodule->ignore = xstrdup(value);
        return ret;
  }
  
 -int gitmodule_sha1_from_commit(const unsigned char *treeish_name,
 -                                    unsigned char *gitmodules_sha1,
 +int gitmodule_oid_from_commit(const struct object_id *treeish_name,
 +                                    struct object_id *gitmodules_oid,
                                      struct strbuf *rev)
  {
        int ret = 0;
  
 -      if (is_null_sha1(treeish_name)) {
 -              hashclr(gitmodules_sha1);
 +      if (is_null_oid(treeish_name)) {
 +              oidclr(gitmodules_oid);
                return 1;
        }
  
 -      strbuf_addf(rev, "%s:.gitmodules", sha1_to_hex(treeish_name));
 -      if (get_sha1(rev->buf, gitmodules_sha1) >= 0)
 +      strbuf_addf(rev, "%s:.gitmodules", oid_to_hex(treeish_name));
 +      if (get_oid(rev->buf, gitmodules_oid) >= 0)
                ret = 1;
  
        return ret;
   * revisions.
   */
  static const struct submodule *config_from(struct submodule_cache *cache,
 -              const unsigned char *treeish_name, const char *key,
 +              const struct object_id *treeish_name, const char *key,
                enum lookup_type lookup_type)
  {
        struct strbuf rev = STRBUF_INIT;
        unsigned long config_size;
        char *config = NULL;
 -      unsigned char sha1[20];
 +      struct object_id oid;
        enum object_type type;
        const struct submodule *submodule = NULL;
        struct parse_config_parameter parameter;
                return entry->config;
        }
  
 -      if (!gitmodule_sha1_from_commit(treeish_name, sha1, &rev))
 +      if (!gitmodule_oid_from_commit(treeish_name, &oid, &rev))
                goto out;
  
        switch (lookup_type) {
        case lookup_name:
 -              submodule = cache_lookup_name(cache, sha1, key);
 +              submodule = cache_lookup_name(cache, oid.hash, key);
                break;
        case lookup_path:
 -              submodule = cache_lookup_path(cache, sha1, key);
 +              submodule = cache_lookup_path(cache, oid.hash, key);
                break;
        }
        if (submodule)
                goto out;
  
 -      config = read_sha1_file(sha1, &type, &config_size);
 +      config = read_sha1_file(oid.hash, &type, &config_size);
        if (!config || type != OBJ_BLOB)
                goto out;
  
        /* fill the submodule config into the cache */
        parameter.cache = cache;
 -      parameter.treeish_name = treeish_name;
 -      parameter.gitmodules_sha1 = sha1;
 +      parameter.treeish_name = treeish_name->hash;
 +      parameter.gitmodules_sha1 = oid.hash;
        parameter.overwrite = 0;
        git_config_from_mem(parse_config, CONFIG_ORIGIN_SUBMODULE_BLOB, rev.buf,
                        config, config_size, &parameter);
  
        switch (lookup_type) {
        case lookup_name:
 -              return cache_lookup_name(cache, sha1, key);
 +              return cache_lookup_name(cache, oid.hash, key);
        case lookup_path:
 -              return cache_lookup_path(cache, sha1, key);
 +              return cache_lookup_path(cache, oid.hash, key);
        default:
                return NULL;
        }
@@@ -547,62 -473,43 +547,62 @@@ out
        return submodule;
  }
  
 -static void ensure_cache_init(void)
 +static void submodule_cache_check_init(struct repository *repo)
  {
 -      if (is_cache_init)
 +      if (repo->submodule_cache && repo->submodule_cache->initialized)
                return;
  
 -      cache_init(&the_submodule_cache);
 -      is_cache_init = 1;
 +      if (!repo->submodule_cache)
 +              repo->submodule_cache = submodule_cache_alloc();
 +
 +      submodule_cache_init(repo->submodule_cache);
  }
  
 -int parse_submodule_config_option(const char *var, const char *value)
 +int submodule_config_option(struct repository *repo,
 +                          const char *var, const char *value)
  {
        struct parse_config_parameter parameter;
 -      parameter.cache = &the_submodule_cache;
 +
 +      submodule_cache_check_init(repo);
 +
 +      parameter.cache = repo->submodule_cache;
        parameter.treeish_name = NULL;
        parameter.gitmodules_sha1 = null_sha1;
        parameter.overwrite = 1;
  
 -      ensure_cache_init();
        return parse_config(var, value, &parameter);
  }
  
 -const struct submodule *submodule_from_name(const unsigned char *treeish_name,
 +int parse_submodule_config_option(const char *var, const char *value)
 +{
 +      return submodule_config_option(the_repository, var, value);
 +}
 +
 +const struct submodule *submodule_from_name(const struct object_id *treeish_name,
                const char *name)
  {
 -      ensure_cache_init();
 -      return config_from(&the_submodule_cache, treeish_name, name, lookup_name);
 +      submodule_cache_check_init(the_repository);
 +      return config_from(the_repository->submodule_cache, treeish_name, name, lookup_name);
  }
  
 -const struct submodule *submodule_from_path(const unsigned char *treeish_name,
 +const struct submodule *submodule_from_path(const struct object_id *treeish_name,
                const char *path)
  {
 -      ensure_cache_init();
 -      return config_from(&the_submodule_cache, treeish_name, path, lookup_path);
 +      submodule_cache_check_init(the_repository);
 +      return config_from(the_repository->submodule_cache, treeish_name, path, lookup_path);
 +}
 +
 +const struct submodule *submodule_from_cache(struct repository *repo,
 +                                           const struct object_id *treeish_name,
 +                                           const char *key)
 +{
 +      submodule_cache_check_init(repo);
 +      return config_from(repo->submodule_cache, treeish_name,
 +                         key, lookup_path);
  }
  
  void submodule_free(void)
  {
 -      cache_free(&the_submodule_cache);
 -      is_cache_init = 0;
 +      if (the_repository->submodule_cache)
 +              submodule_cache_clear(the_repository->submodule_cache);
  }
diff --combined t/t5534-push-signed.sh
index 464ffdd147abbae354ae3adfe593b89a37d5c726,f88487bea2121ca3245b79592b786252b6935e7a..1cea758f789edef35a61ce1a9bc11d9fda7bd7ac
@@@ -71,6 -71,13 +71,13 @@@ test_expect_success 'push --signed fail
        test_i18ngrep "the receiving end does not support" err
  '
  
+ test_expect_success 'push --signed=1 is accepted' '
+       prepare_dst &&
+       mkdir -p dst/.git/hooks &&
+       test_must_fail git push --signed=1 dst noop ff +noff 2>err &&
+       test_i18ngrep "the receiving end does not support" err
+ '
  test_expect_success GPG 'no certificate for a signed push with no update' '
        prepare_dst &&
        mkdir -p dst/.git/hooks &&
@@@ -119,51 -126,11 +126,51 @@@ test_expect_success GPG 'signed push se
                sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert
        ) >expect &&
  
 -      grep "$(git rev-parse noop ff) refs/heads/ff" dst/push-cert &&
 -      grep "$(git rev-parse noop noff) refs/heads/noff" dst/push-cert &&
 +      noop=$(git rev-parse noop) &&
 +      ff=$(git rev-parse ff) &&
 +      noff=$(git rev-parse noff) &&
 +      grep "$noop $ff refs/heads/ff" dst/push-cert &&
 +      grep "$noop $noff refs/heads/noff" dst/push-cert &&
        test_cmp expect dst/push-cert-status
  '
  
 +test_expect_success GPG 'inconsistent push options in signed push not allowed' '
 +      # First, invoke receive-pack with dummy input to obtain its preamble.
 +      prepare_dst &&
 +      git -C dst config receive.certnonceseed sekrit &&
 +      git -C dst config receive.advertisepushoptions 1 &&
 +      printf xxxx | test_might_fail git receive-pack dst >preamble &&
 +
 +      # Then, invoke push. Simulate a receive-pack that sends the preamble we
 +      # obtained, followed by a dummy packet.
 +      write_script myscript <<-\EOF &&
 +              cat preamble &&
 +              printf xxxx &&
 +              cat >push
 +      EOF
 +      test_might_fail git push --push-option="foo" --push-option="bar" \
 +              --receive-pack="\"$(pwd)/myscript\"" --signed dst --delete ff &&
 +
 +      # Replay the push output on a fresh dst, checking that ff is truly
 +      # deleted.
 +      prepare_dst &&
 +      git -C dst config receive.certnonceseed sekrit &&
 +      git -C dst config receive.advertisepushoptions 1 &&
 +      git receive-pack dst <push &&
 +      test_must_fail git -C dst rev-parse ff &&
 +
 +      # Tweak the push output to make the push option outside the cert
 +      # different, then replay it on a fresh dst, checking that ff is not
 +      # deleted.
 +      perl -pe "s/([^ ])bar/\$1baz/" push >push.tweak &&
 +      prepare_dst &&
 +      git -C dst config receive.certnonceseed sekrit &&
 +      git -C dst config receive.advertisepushoptions 1 &&
 +      git receive-pack dst <push.tweak >out &&
 +      git -C dst rev-parse ff &&
 +      grep "inconsistent push options" out
 +'
 +
  test_expect_success GPG 'fail without key and heed user.signingkey' '
        prepare_dst &&
        mkdir -p dst/.git/hooks &&
                sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert
        ) >expect &&
  
 -      grep "$(git rev-parse noop ff) refs/heads/ff" dst/push-cert &&
 -      grep "$(git rev-parse noop noff) refs/heads/noff" dst/push-cert &&
 +      noop=$(git rev-parse noop) &&
 +      ff=$(git rev-parse ff) &&
 +      noff=$(git rev-parse noff) &&
 +      grep "$noop $ff refs/heads/ff" dst/push-cert &&
 +      grep "$noop $noff refs/heads/noff" dst/push-cert &&
        test_cmp expect dst/push-cert-status
  '