Merge branch 'ma/leakplugs'
authorJunio C Hamano <gitster@pobox.com>
Fri, 29 Sep 2017 02:23:43 +0000 (11:23 +0900)
committerJunio C Hamano <gitster@pobox.com>
Fri, 29 Sep 2017 02:23:43 +0000 (11:23 +0900)
Memory leaks in various codepaths have been plugged.

* ma/leakplugs:
pack-bitmap[-write]: use `object_array_clear()`, don't leak
object_array: add and use `object_array_pop()`
object_array: use `object_array_clear()`, not `free()`
leak_pending: use `object_array_clear()`, not `free()`
commit: fix memory leak in `reduce_heads()`
builtin/commit: fix memory leak in `prepare_index()`

1  2 
bisect.c
builtin/checkout.c
builtin/commit.c
builtin/fast-export.c
builtin/fsck.c
commit.c
pack-bitmap.c
revision.h
shallow.c
submodule.c
upload-pack.c
diff --combined bisect.c
index 2549eaf7b15152a9c130e63e44c8d8f4fc865233,fc797f6aea90597397f8f25753db0da439848449..96beeb5d13630672fba3021468670a1bcfc72158
+++ b/bisect.c
@@@ -680,16 -680,16 +680,16 @@@ static int is_expected_rev(const struc
        return res;
  }
  
 -static int bisect_checkout(const unsigned char *bisect_rev, int no_checkout)
 +static int bisect_checkout(const struct object_id *bisect_rev, int no_checkout)
  {
        char bisect_rev_hex[GIT_MAX_HEXSZ + 1];
  
 -      memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), GIT_SHA1_HEXSZ + 1);
 -      update_ref(NULL, "BISECT_EXPECTED_REV", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
 +      memcpy(bisect_rev_hex, oid_to_hex(bisect_rev), GIT_SHA1_HEXSZ + 1);
 +      update_ref(NULL, "BISECT_EXPECTED_REV", bisect_rev->hash, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
  
        argv_checkout[2] = bisect_rev_hex;
        if (no_checkout) {
 -              update_ref(NULL, "BISECT_HEAD", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
 +              update_ref(NULL, "BISECT_HEAD", bisect_rev->hash, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
        } else {
                int res;
                res = run_command_v_opt(argv_checkout, RUN_GIT_CMD);
@@@ -796,7 -796,7 +796,7 @@@ static void check_merge_bases(int no_ch
                        handle_skipped_merge_base(mb);
                } else {
                        printf(_("Bisecting: a merge base must be tested\n"));
 -                      exit(bisect_checkout(mb->hash, no_checkout));
 +                      exit(bisect_checkout(mb, no_checkout));
                }
        }
  
@@@ -826,7 -826,8 +826,8 @@@ static int check_ancestors(const char *
  
        /* Clean up objects used, as they will be reused. */
        clear_commit_marks_for_object_array(&pending_copy, ALL_REV_FLAGS);
-       free(pending_copy.objects);
+       object_array_clear(&pending_copy);
  
        return res;
  }
@@@ -939,7 -940,7 +940,7 @@@ int bisect_next_all(const char *prefix
        struct rev_info revs;
        struct commit_list *tried;
        int reaches = 0, all = 0, nr, steps;
 -      const unsigned char *bisect_rev;
 +      struct object_id *bisect_rev;
        char *steps_msg;
  
        read_bisect_terms(&term_bad, &term_good);
                exit(4);
        }
  
 -      bisect_rev = revs.commits->item->object.oid.hash;
 +      bisect_rev = &revs.commits->item->object.oid;
  
 -      if (!hashcmp(bisect_rev, current_bad_oid->hash)) {
 +      if (!oidcmp(bisect_rev, current_bad_oid)) {
                exit_if_skipped_commits(tried, current_bad_oid);
 -              printf("%s is the first %s commit\n", sha1_to_hex(bisect_rev),
 +              printf("%s is the first %s commit\n", oid_to_hex(bisect_rev),
                        term_bad);
                show_diff_tree(prefix, revs.commits->item);
                /* This means the bisection process succeeded. */
diff --combined builtin/checkout.c
index d091f06274ca12c218ca9538ddee40dcd72dbe36,52f1b67708b98a199efa842360faf1b065a37284..3345a0d16f21c38fe5fe6f32fa5b0a139e21c4a3
@@@ -436,7 -436,6 +436,7 @@@ static int reset_tree(struct tree *tree
                 * update paths in the work tree, and we cannot revert
                 * them.
                 */
 +              /* fallthrough */
        case 0:
                return 0;
        default:
@@@ -797,9 -796,14 +797,14 @@@ static void orphaned_commit_warning(str
        for_each_ref(add_pending_uninteresting_ref, &revs);
        add_pending_oid(&revs, "HEAD", &new->object.oid, UNINTERESTING);
  
+       /* Save pending objects, so they can be cleaned up later. */
        refs = revs.pending;
        revs.leak_pending = 1;
  
+       /*
+        * prepare_revision_walk (together with .leak_pending = 1) makes us
+        * the sole owner of the list of pending objects.
+        */
        if (prepare_revision_walk(&revs))
                die(_("internal error in revision walk"));
        if (!(old->object.flags & UNINTERESTING))
        else
                describe_detached_head(_("Previous HEAD position was"), old);
  
+       /* Clean up objects used, as they will be reused. */
        clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS);
-       free(refs.objects);
+       object_array_clear(&refs);
  }
  
  static int switch_branches(const struct checkout_opts *opts,
@@@ -862,7 -868,7 +869,7 @@@ static int git_checkout_config(const ch
        }
  
        if (starts_with(var, "submodule."))
 -              return submodule_config(var, value, NULL);
 +              return git_default_submodule_config(var, value, NULL);
  
        return git_xmerge_config(var, value, NULL);
  }
@@@ -1183,6 -1189,7 +1190,6 @@@ int cmd_checkout(int argc, const char *
        opts.prefix = prefix;
        opts.show_progress = -1;
  
 -      gitmodules_config();
        git_config(git_checkout_config, &opts);
  
        opts.track = BRANCH_TRACK_UNSPECIFIED;
diff --combined builtin/commit.c
index 39d5b7f6c79368d5b723a25cd701b82ec399ca3c,ce76b6f96025a6e2785314b534afe7c8e9157a43..0f8ddb6866b3d14c4e6b84f019110387c7f77319
@@@ -195,6 -195,7 +195,6 @@@ static void determine_whence(struct wt_
  static void status_init_config(struct wt_status *s, config_fn_t fn)
  {
        wt_status_prepare(s);
 -      gitmodules_config();
        git_config(fn, s);
        determine_whence(s);
        init_diff_ui_defaults();
@@@ -335,7 -336,7 +335,7 @@@ static void refresh_cache_or_die(int re
  static const char *prepare_index(int argc, const char **argv, const char *prefix,
                                 const struct commit *current_head, int is_status)
  {
-       struct string_list partial;
+       struct string_list partial = STRING_LIST_INIT_DUP;
        struct pathspec pathspec;
        int refresh_flags = REFRESH_QUIET;
        const char *ret;
                        warning(_("Failed to update main cache tree"));
  
                commit_style = COMMIT_NORMAL;
-               return get_lock_file_path(&index_lock);
+               ret = get_lock_file_path(&index_lock);
+               goto out;
        }
  
        /*
                if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
                        die(_("unable to write new_index file"));
                commit_style = COMMIT_NORMAL;
-               return get_lock_file_path(&index_lock);
+               ret = get_lock_file_path(&index_lock);
+               goto out;
        }
  
        /*
                        rollback_lock_file(&index_lock);
                }
                commit_style = COMMIT_AS_IS;
-               return get_index_file();
+               ret = get_index_file();
+               goto out;
        }
  
        /*
                        die(_("cannot do a partial commit during a cherry-pick."));
        }
  
-       string_list_init(&partial, 1);
        if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec))
                exit(1);
  
        discard_cache();
        ret = get_lock_file_path(&false_lock);
        read_cache_from(ret);
+ out:
+       string_list_clear(&partial, 0);
+       clear_pathspec(&pathspec);
        return ret;
  }
  
@@@ -509,7 -515,7 +514,7 @@@ static int run_status(FILE *fp, const c
        s->index_file = index_file;
        s->fp = fp;
        s->nowarn = nowarn;
 -      s->is_initial = get_sha1(s->reference, oid.hash) ? 1 : 0;
 +      s->is_initial = get_oid(s->reference, &oid) ? 1 : 0;
        if (!s->is_initial)
                hashcpy(s->sha1_commit, oid.hash);
        s->status_format = status_format;
@@@ -890,7 -896,7 +895,7 @@@ static int prepare_to_commit(const cha
                if (amend)
                        parent = "HEAD^1";
  
 -              if (get_sha1(parent, oid.hash)) {
 +              if (get_oid(parent, &oid)) {
                        int i, ita_nr = 0;
  
                        for (i = 0; i < active_nr; i++)
                return 0;
        }
  
 -      /*
 -       * Re-read the index as pre-commit hook could have updated it,
 -       * and write it out as a tree.  We must do this before we invoke
 -       * the editor and after we invoke run_status above.
 -       */
 -      discard_cache();
 +      if (!no_verify && find_hook("pre-commit")) {
 +              /*
 +               * Re-read the index as pre-commit hook could have updated it,
 +               * and write it out as a tree.  We must do this before we invoke
 +               * the editor and after we invoke run_status above.
 +               */
 +              discard_cache();
 +      }
        read_cache_from(index_file);
 +
        if (update_main_cache_tree(0)) {
                error(_("Error building trees"));
                return 0;
@@@ -1389,7 -1392,7 +1394,7 @@@ int cmd_status(int argc, const char **a
  
        fd = hold_locked_index(&index_lock, 0);
  
 -      s.is_initial = get_sha1(s.reference, oid.hash) ? 1 : 0;
 +      s.is_initial = get_oid(s.reference, &oid) ? 1 : 0;
        if (!s.is_initial)
                hashcpy(s.sha1_commit, oid.hash);
  
@@@ -1431,6 -1434,7 +1436,6 @@@ static void print_summary(const char *p
        struct rev_info rev;
        struct commit *commit;
        struct strbuf format = STRBUF_INIT;
 -      struct object_id junk_oid;
        const char *head;
        struct pretty_print_context pctx = {0};
        struct strbuf author_ident = STRBUF_INIT;
        rev.diffopt.break_opt = 0;
        diff_setup_done(&rev.diffopt);
  
 -      head = resolve_ref_unsafe("HEAD", 0, junk_oid.hash, NULL);
 +      head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
        if (!strcmp(head, "HEAD"))
                head = _("detached HEAD");
        else
@@@ -1658,7 -1662,7 +1663,7 @@@ int cmd_commit(int argc, const char **a
        status_format = STATUS_FORMAT_NONE; /* Ignore status.short */
        s.colopts = 0;
  
 -      if (get_sha1("HEAD", oid.hash))
 +      if (get_oid("HEAD", &oid))
                current_head = NULL;
        else {
                current_head = lookup_commit_or_die(&oid, "HEAD");
        if (!quiet)
                print_summary(prefix, &oid, !current_head);
  
 -      strbuf_release(&err);
 +      UNLEAK(err);
 +      UNLEAK(sb);
        return 0;
  }
diff --combined builtin/fast-export.c
index da42ee5e604b72a8b3ac1edf38d8f75bd6805419,cff8d0fc5bbd8bd7acdd561b6c10ddc28e93b5bb..2fb60d6d48eae68affdcf64f6dbab0282408f393
@@@ -344,7 -344,6 +344,7 @@@ static void show_filemodify(struct diff
                            struct diff_options *options, void *data)
  {
        int i;
 +      struct string_list *changed = data;
  
        /*
         * Handle files below a directory first, in case they are all deleted
                case DIFF_STATUS_DELETED:
                        printf("D ");
                        print_path(spec->path);
 +                      string_list_insert(changed, spec->path);
                        putchar('\n');
                        break;
  
                case DIFF_STATUS_COPIED:
                case DIFF_STATUS_RENAMED:
 -                      printf("%c ", q->queue[i]->status);
 -                      print_path(ospec->path);
 -                      putchar(' ');
 -                      print_path(spec->path);
 -                      putchar('\n');
 -
 -                      if (!oidcmp(&ospec->oid, &spec->oid) &&
 -                          ospec->mode == spec->mode)
 -                              break;
 +                      /*
 +                       * If a change in the file corresponding to ospec->path
 +                       * has been observed, we cannot trust its contents
 +                       * because the diff is calculated based on the prior
 +                       * contents, not the current contents.  So, declare a
 +                       * copy or rename only if there was no change observed.
 +                       */
 +                      if (!string_list_has_string(changed, ospec->path)) {
 +                              printf("%c ", q->queue[i]->status);
 +                              print_path(ospec->path);
 +                              putchar(' ');
 +                              print_path(spec->path);
 +                              string_list_insert(changed, spec->path);
 +                              putchar('\n');
 +
 +                              if (!oidcmp(&ospec->oid, &spec->oid) &&
 +                                  ospec->mode == spec->mode)
 +                                      break;
 +                      }
                        /* fallthrough */
  
                case DIFF_STATUS_TYPE_CHANGED:
                                       get_object_mark(object));
                        }
                        print_path(spec->path);
 +                      string_list_insert(changed, spec->path);
                        putchar('\n');
                        break;
  
@@@ -541,8 -528,7 +541,8 @@@ static void anonymize_ident_line(const 
        *end = out->buf + out->len;
  }
  
 -static void handle_commit(struct commit *commit, struct rev_info *rev)
 +static void handle_commit(struct commit *commit, struct rev_info *rev,
 +                        struct string_list *paths_of_changed_objects)
  {
        int saved_output_format = rev->diffopt.output_format;
        const char *commit_buffer;
        if (full_tree)
                printf("deleteall\n");
        log_tree_diff_flush(rev);
 +      string_list_clear(paths_of_changed_objects, 0);
        rev->diffopt.output_format = saved_output_format;
  
        printf("\n");
@@@ -645,16 -630,14 +645,15 @@@ static void *anonymize_tag(const void *
        return strbuf_detach(&out, len);
  }
  
 -static void handle_tail(struct object_array *commits, struct rev_info *revs)
 +static void handle_tail(struct object_array *commits, struct rev_info *revs,
 +                      struct string_list *paths_of_changed_objects)
  {
        struct commit *commit;
        while (commits->nr) {
-               commit = (struct commit *)commits->objects[commits->nr - 1].item;
+               commit = (struct commit *)object_array_pop(commits);
                if (has_unshown_parent(commit))
                        return;
 -              handle_commit(commit, revs);
 +              handle_commit(commit, revs, paths_of_changed_objects);
-               commits->nr--;
        }
  }
  
@@@ -993,7 -976,6 +992,7 @@@ int cmd_fast_export(int argc, const cha
        char *export_filename = NULL, *import_filename = NULL;
        uint32_t lastimportid;
        struct string_list refspecs_list = STRING_LIST_INIT_NODUP;
 +      struct string_list paths_of_changed_objects = STRING_LIST_INIT_DUP;
        struct option options[] = {
                OPT_INTEGER(0, "progress", &progress,
                            N_("show progress after <n> objects")),
        if (prepare_revision_walk(&revs))
                die("revision walk setup failed");
        revs.diffopt.format_callback = show_filemodify;
 +      revs.diffopt.format_callback_data = &paths_of_changed_objects;
        DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
        while ((commit = get_revision(&revs))) {
                if (has_unshown_parent(commit)) {
                        add_object_array(&commit->object, NULL, &commits);
                }
                else {
 -                      handle_commit(commit, &revs);
 -                      handle_tail(&commits, &revs);
 +                      handle_commit(commit, &revs, &paths_of_changed_objects);
 +                      handle_tail(&commits, &revs, &paths_of_changed_objects);
                }
        }
  
diff --combined builtin/fsck.c
index 1e4c471b4141e6ec350c4caf58bacafb6ac07b7c,7d4ad027338ee59218378edf97884ea648b3c39e..56afe405b8072b3099a23661ac586f673e54ff0e
@@@ -15,7 -15,6 +15,7 @@@
  #include "progress.h"
  #include "streaming.h"
  #include "decorate.h"
 +#include "packfile.h"
  
  #define REACHABLE 0x0001
  #define SEEN      0x0002
@@@ -180,14 -179,9 +180,9 @@@ static int traverse_reachable(void
        unsigned int nr = 0;
        int result = 0;
        if (show_progress)
 -              progress = start_progress_delay(_("Checking connectivity"), 0, 0, 2);
 +              progress = start_delayed_progress(_("Checking connectivity"), 0);
        while (pending.nr) {
-               struct object_array_entry *entry;
-               struct object *obj;
-               entry = pending.objects + --pending.nr;
-               obj = entry->item;
-               result |= traverse_one_object(obj);
+               result |= traverse_one_object(object_array_pop(&pending));
                display_progress(progress, ++nr);
        }
        stop_progress(&progress);
@@@ -731,12 -725,12 +726,12 @@@ int cmd_fsck(int argc, const char **arg
  
        for (i = 0; i < argc; i++) {
                const char *arg = argv[i];
 -              unsigned char sha1[20];
 -              if (!get_sha1(arg, sha1)) {
 -                      struct object *obj = lookup_object(sha1);
 +              struct object_id oid;
 +              if (!get_oid(arg, &oid)) {
 +                      struct object *obj = lookup_object(oid.hash);
  
                        if (!obj || !(obj->flags & HAS_OBJ)) {
 -                              error("%s: object missing", sha1_to_hex(sha1));
 +                              error("%s: object missing", oid_to_hex(&oid));
                                errors_found |= ERROR_OBJECT;
                                continue;
                        }
diff --combined commit.c
index 906298052d485867599a98af1cf49e4728028b50,f73976bcc9bafe6de3b7d91e12aff1308b0c9210..1e0e633790bb834ad05ed9619e4afa0bac33ce19
+++ b/commit.c
@@@ -59,7 -59,7 +59,7 @@@ struct commit *lookup_commit_reference_
        struct object_id oid;
        struct commit *commit;
  
 -      if (get_sha1_committish(name, oid.hash))
 +      if (get_oid_committish(name, &oid))
                return NULL;
        commit = lookup_commit_reference(&oid);
        if (parse_commit(commit))
@@@ -134,41 -134,35 +134,41 @@@ int register_commit_graft(struct commit
        return 0;
  }
  
 -struct commit_graft *read_graft_line(char *buf, int len)
 +struct commit_graft *read_graft_line(struct strbuf *line)
  {
        /* The format is just "Commit Parent1 Parent2 ...\n" */
 -      int i;
 +      int i, phase;
 +      const char *tail = NULL;
        struct commit_graft *graft = NULL;
 -      const int entry_size = GIT_SHA1_HEXSZ + 1;
 +      struct object_id dummy_oid, *oid;
  
 -      while (len && isspace(buf[len-1]))
 -              buf[--len] = '\0';
 -      if (buf[0] == '#' || buf[0] == '\0')
 +      strbuf_rtrim(line);
 +      if (!line->len || line->buf[0] == '#')
                return NULL;
 -      if ((len + 1) % entry_size)
 -              goto bad_graft_data;
 -      i = (len + 1) / entry_size - 1;
 -      graft = xmalloc(st_add(sizeof(*graft), st_mult(GIT_SHA1_RAWSZ, i)));
 -      graft->nr_parent = i;
 -      if (get_oid_hex(buf, &graft->oid))
 -              goto bad_graft_data;
 -      for (i = GIT_SHA1_HEXSZ; i < len; i += entry_size) {
 -              if (buf[i] != ' ')
 -                      goto bad_graft_data;
 -              if (get_sha1_hex(buf + i + 1, graft->parent[i/entry_size].hash))
 +      /*
 +       * phase 0 verifies line, counts hashes in line and allocates graft
 +       * phase 1 fills graft
 +       */
 +      for (phase = 0; phase < 2; phase++) {
 +              oid = graft ? &graft->oid : &dummy_oid;
 +              if (parse_oid_hex(line->buf, oid, &tail))
                        goto bad_graft_data;
 +              for (i = 0; *tail != '\0'; i++) {
 +                      oid = graft ? &graft->parent[i] : &dummy_oid;
 +                      if (!isspace(*tail++) || parse_oid_hex(tail, oid, &tail))
 +                              goto bad_graft_data;
 +              }
 +              if (!graft) {
 +                      graft = xmalloc(st_add(sizeof(*graft),
 +                                             st_mult(sizeof(struct object_id), i)));
 +                      graft->nr_parent = i;
 +              }
        }
        return graft;
  
  bad_graft_data:
 -      error("bad graft data: %s", buf);
 -      free(graft);
 +      error("bad graft data: %s", line->buf);
 +      assert(!graft);
        return NULL;
  }
  
@@@ -180,7 -174,7 +180,7 @@@ static int read_graft_file(const char *
                return -1;
        while (!strbuf_getwholeline(&buf, fp, '\n')) {
                /* The format is just "Commit Parent1 Parent2 ...\n" */
 -              struct commit_graft *graft = read_graft_line(buf.buf, buf.len);
 +              struct commit_graft *graft = read_graft_line(&buf);
                if (!graft)
                        continue;
                if (register_commit_graft(graft, 1))
@@@ -205,11 -199,11 +205,11 @@@ static void prepare_commit_graft(void
        commit_graft_prepared = 1;
  }
  
 -struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
 +struct commit_graft *lookup_commit_graft(const struct object_id *oid)
  {
        int pos;
        prepare_commit_graft();
 -      pos = commit_graft_pos(sha1);
 +      pos = commit_graft_pos(oid->hash);
        if (pos < 0)
                return NULL;
        return commit_graft[pos];
@@@ -340,7 -334,7 +340,7 @@@ int parse_commit_buffer(struct commit *
        bufptr += tree_entry_len + 1; /* "tree " + "hex sha1" + "\n" */
        pptr = &item->parents;
  
 -      graft = lookup_commit_graft(item->object.oid.hash);
 +      graft = lookup_commit_graft(&item->object.oid);
        while (bufptr + parent_entry_len < tail && !memcmp(bufptr, "parent ", 7)) {
                struct commit *new_parent;
  
@@@ -1086,6 -1080,7 +1086,7 @@@ struct commit_list *reduce_heads(struc
        num_head = remove_redundant(array, num_head);
        for (i = 0; i < num_head; i++)
                tail = &commit_list_insert(array[i], tail)->next;
+       free(array);
        return result;
  }
  
@@@ -1570,13 -1565,10 +1571,13 @@@ int commit_tree_extended(const char *ms
        if (encoding_is_utf8 && !verify_utf8(&buffer))
                fprintf(stderr, _(commit_utf8_warn));
  
 -      if (sign_commit && do_sign_commit(&buffer, sign_commit))
 -              return -1;
 +      if (sign_commit && do_sign_commit(&buffer, sign_commit)) {
 +              result = -1;
 +              goto out;
 +      }
  
        result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
 +out:
        strbuf_release(&buffer);
        return result;
  }
@@@ -1595,7 -1587,7 +1596,7 @@@ struct commit *get_merge_parent(const c
        struct object *obj;
        struct commit *commit;
        struct object_id oid;
 -      if (get_sha1(name, oid.hash))
 +      if (get_oid(name, &oid))
                return NULL;
        obj = parse_object(&oid);
        commit = (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);
diff --combined pack-bitmap.c
index cb3d14ba45a94dab5fee3b2ebc52eb379506313e,0a49c1595a4ca047619b0e592ef91b1c63e3ddd4..42e3d5f4f26ee0f31d13c2a3d15bc31da5f7c1d4
@@@ -9,7 -9,6 +9,7 @@@
  #include "pack-bitmap.h"
  #include "pack-revindex.h"
  #include "pack-objects.h"
 +#include "packfile.h"
  
  /*
   * An entry on the bitmap index, representing the bitmap for a given
@@@ -654,8 -653,6 +654,6 @@@ static int in_bitmapped_pack(struct obj
  int prepare_bitmap_walk(struct rev_info *revs)
  {
        unsigned int i;
-       unsigned int pending_nr = revs->pending.nr;
-       struct object_array_entry *pending_e = revs->pending.objects;
  
        struct object_list *wants = NULL;
        struct object_list *haves = NULL;
                        return -1;
        }
  
-       for (i = 0; i < pending_nr; ++i) {
-               struct object *object = pending_e[i].item;
+       for (i = 0; i < revs->pending.nr; ++i) {
+               struct object *object = revs->pending.objects[i].item;
  
                if (object->type == OBJ_NONE)
                        parse_object_or_die(&object->oid, NULL);
        if (!bitmap_git.loaded && load_pack_bitmap() < 0)
                return -1;
  
-       revs->pending.nr = 0;
-       revs->pending.alloc = 0;
-       revs->pending.objects = NULL;
+       object_array_clear(&revs->pending);
  
        if (haves) {
                revs->ignore_missing_links = 1;
diff --combined revision.h
index 3a3d3e2cf824bf0ae404204c1ea81dad813d5f93,3162cc78e8d160c9ba77e2045272b32837527dd9..54761200adf2d5111b8aba097299bd2fd080a949
@@@ -96,7 -96,6 +96,7 @@@ struct rev_info 
                        topo_order:1,
                        simplify_merges:1,
                        simplify_by_decoration:1,
 +                      single_worktree:1,
                        tag_objects:1,
                        tree_objects:1,
                        blob_objects:1,
                        date_mode_explicit:1,
                        preserve_subject:1;
        unsigned int    disable_stdin:1;
+       /*
+        * Set `leak_pending` to prevent `prepare_revision_walk()` from clearing
+        * the array of pending objects (`pending`). It will still forget about
+        * the array and its entries, so they really are leaked. This can be
+        * useful if the `struct object_array` `pending` is copied before
+        * calling `prepare_revision_walk()`. By setting `leak_pending`, you
+        * effectively claim ownership of the old array, so you should most
+        * likely call `object_array_clear(&pending_copy)` once you are done.
+        * Observe that this is about ownership of the array and its entries,
+        * not the commits referenced by those entries.
+        */
        unsigned int    leak_pending:1;
        /* --show-linear-break */
        unsigned int    track_linear:1,
diff --combined shallow.c
index eabb65d3a7c286832f5a93bb732e8c9c94fa772e,901ac979171e4563d09724c48478c96a212a8bb3..df4d44ea7a34a6d6bef119d1494f1c055614cbb1
+++ b/shallow.c
@@@ -99,7 -99,7 +99,7 @@@ struct commit_list *get_shallow_commits
                                cur_depth = 0;
                        } else {
                                commit = (struct commit *)
-                                       stack.objects[--stack.nr].item;
+                                       object_array_pop(&stack);
                                cur_depth = *(int *)commit->util;
                        }
                }
                cur_depth++;
                if ((depth != INFINITE_DEPTH && cur_depth >= depth) ||
                    (is_repository_shallow() && !commit->parents &&
 -                   (graft = lookup_commit_graft(commit->object.oid.hash)) != NULL &&
 +                   (graft = lookup_commit_graft(&commit->object.oid)) != NULL &&
                     graft->nr_parent < 0)) {
                        commit_list_insert(commit, &result);
                        commit->object.flags |= shallow_flag;
@@@ -286,26 -286,28 +286,26 @@@ int write_shallow_commits(struct strbu
        return write_shallow_commits_1(out, use_pack_protocol, extra, 0);
  }
  
 -static struct tempfile temporary_shallow;
 -
  const char *setup_temporary_shallow(const struct oid_array *extra)
  {
 +      struct tempfile *temp;
        struct strbuf sb = STRBUF_INIT;
 -      int fd;
  
        if (write_shallow_commits(&sb, 0, extra)) {
 -              fd = xmks_tempfile(&temporary_shallow, git_path("shallow_XXXXXX"));
 +              temp = xmks_tempfile(git_path("shallow_XXXXXX"));
  
 -              if (write_in_full(fd, sb.buf, sb.len) != sb.len)
 +              if (write_in_full(temp->fd, sb.buf, sb.len) < 0 ||
 +                  close_tempfile_gently(temp) < 0)
                        die_errno("failed to write to %s",
 -                                get_tempfile_path(&temporary_shallow));
 -              close_tempfile(&temporary_shallow);
 +                                get_tempfile_path(temp));
                strbuf_release(&sb);
 -              return get_tempfile_path(&temporary_shallow);
 +              return get_tempfile_path(temp);
        }
        /*
         * is_repository_shallow() sees empty string as "no shallow
         * file".
         */
 -      return get_tempfile_path(&temporary_shallow);
 +      return "";
  }
  
  void setup_alternate_shallow(struct lock_file *shallow_lock,
                                       LOCK_DIE_ON_ERROR);
        check_shallow_file_for_update();
        if (write_shallow_commits(&sb, 0, extra)) {
 -              if (write_in_full(fd, sb.buf, sb.len) != sb.len)
 +              if (write_in_full(fd, sb.buf, sb.len) < 0)
                        die_errno("failed to write to %s",
                                  get_lock_file_path(shallow_lock));
                *alternate_shallow_file = get_lock_file_path(shallow_lock);
@@@ -366,7 -368,7 +366,7 @@@ void prune_shallow(int show_only
                                       LOCK_DIE_ON_ERROR);
        check_shallow_file_for_update();
        if (write_shallow_commits_1(&sb, 0, NULL, SEEN_ONLY)) {
 -              if (write_in_full(fd, sb.buf, sb.len) != sb.len)
 +              if (write_in_full(fd, sb.buf, sb.len) < 0)
                        die_errno("failed to write to %s",
                                  get_lock_file_path(&shallow_lock));
                commit_lock_file(&shallow_lock);
@@@ -396,7 -398,7 +396,7 @@@ void prepare_shallow_info(struct shallo
        for (i = 0; i < sa->nr; i++) {
                if (has_object_file(sa->oid + i)) {
                        struct commit_graft *graft;
 -                      graft = lookup_commit_graft(sa->oid[i].hash);
 +                      graft = lookup_commit_graft(&sa->oid[i]);
                        if (graft && graft->nr_parent < 0)
                                continue;
                        info->ours[info->nr_ours++] = i;
diff --combined submodule.c
index b12600fc798f4427c8ad3bff1384c6fb5a6f4bc5,79fd01f7b022c136a8f84681504bc3537bedb3f1..f2f30bb488c05d9002d755feed65e9ca781a440c
  #include "worktree.h"
  #include "parse-options.h"
  
 -static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;
  static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
 -static int parallel_jobs = 1;
  static struct string_list changed_submodule_paths = STRING_LIST_INIT_DUP;
  static int initialized_fetch_ref_tips;
  static struct oid_array ref_tips_before_fetch;
  static struct oid_array ref_tips_after_fetch;
  
  /*
 - * The following flag is set if the .gitmodules file is unmerged. We then
 - * disable recursion for all submodules where .git/config doesn't have a
 - * matching config entry because we can't guess what might be configured in
 - * .gitmodules unless the user resolves the conflict. When a command line
 - * option is given (which always overrides configuration) this flag will be
 - * ignored.
 + * Check if the .gitmodules file is unmerged. Parsing of the .gitmodules file
 + * will be disabled because we can't guess what might be configured in
 + * .gitmodules unless the user resolves the conflict.
   */
 -static int gitmodules_is_unmerged;
 +int is_gitmodules_unmerged(const struct index_state *istate)
 +{
 +      int pos = index_name_pos(istate, GITMODULES_FILE, strlen(GITMODULES_FILE));
 +      if (pos < 0) { /* .gitmodules not found or isn't merged */
 +              pos = -1 - pos;
 +              if (istate->cache_nr > pos) {  /* there is a .gitmodules */
 +                      const struct cache_entry *ce = istate->cache[pos];
 +                      if (ce_namelen(ce) == strlen(GITMODULES_FILE) &&
 +                          !strcmp(ce->name, GITMODULES_FILE))
 +                              return 1;
 +              }
 +      }
 +
 +      return 0;
 +}
  
  /*
 - * This flag is set if the .gitmodules file had unstaged modifications on
 - * startup. This must be checked before allowing modifications to the
 - * .gitmodules file with the intention to stage them later, because when
 - * continuing we would stage the modifications the user didn't stage herself
 - * too. That might change in a future version when we learn to stage the
 - * changes we do ourselves without staging any previous modifications.
 + * Check if the .gitmodules file has unstaged modifications.  This must be
 + * checked before allowing modifications to the .gitmodules file with the
 + * intention to stage them later, because when continuing we would stage the
 + * modifications the user didn't stage herself too. That might change in a
 + * future version when we learn to stage the changes we do ourselves without
 + * staging any previous modifications.
   */
 -static int gitmodules_is_modified;
 +int is_staging_gitmodules_ok(const struct index_state *istate)
 +{
 +      int pos = index_name_pos(istate, GITMODULES_FILE, strlen(GITMODULES_FILE));
  
 -int is_staging_gitmodules_ok(void)
 +      if ((pos >= 0) && (pos < istate->cache_nr)) {
 +              struct stat st;
 +              if (lstat(GITMODULES_FILE, &st) == 0 &&
 +                  ce_match_stat(istate->cache[pos], &st, 0) & DATA_CHANGED)
 +                      return 0;
 +      }
 +
 +      return 1;
 +}
 +
 +static int for_each_remote_ref_submodule(const char *submodule,
 +                                       each_ref_fn fn, void *cb_data)
  {
 -      return !gitmodules_is_modified;
 +      return refs_for_each_remote_ref(get_submodule_ref_store(submodule),
 +                                      fn, cb_data);
  }
  
  /*
@@@ -86,13 -63,13 +86,13 @@@ int update_path_in_gitmodules(const cha
        struct strbuf entry = STRBUF_INIT;
        const struct submodule *submodule;
  
 -      if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */
 +      if (!file_exists(GITMODULES_FILE)) /* Do nothing without .gitmodules */
                return -1;
  
 -      if (gitmodules_is_unmerged)
 +      if (is_gitmodules_unmerged(&the_index))
                die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first"));
  
 -      submodule = submodule_from_path(null_sha1, oldpath);
 +      submodule = submodule_from_path(&null_oid, oldpath);
        if (!submodule || !submodule->name) {
                warning(_("Could not find section in .gitmodules where path=%s"), oldpath);
                return -1;
        strbuf_addstr(&entry, "submodule.");
        strbuf_addstr(&entry, submodule->name);
        strbuf_addstr(&entry, ".path");
 -      if (git_config_set_in_file_gently(".gitmodules", entry.buf, newpath) < 0) {
 +      if (git_config_set_in_file_gently(GITMODULES_FILE, entry.buf, newpath) < 0) {
                /* Maybe the user already did that, don't error out here */
                warning(_("Could not update .gitmodules entry %s"), entry.buf);
                strbuf_release(&entry);
@@@ -120,20 -97,20 +120,20 @@@ int remove_path_from_gitmodules(const c
        struct strbuf sect = STRBUF_INIT;
        const struct submodule *submodule;
  
 -      if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */
 +      if (!file_exists(GITMODULES_FILE)) /* Do nothing without .gitmodules */
                return -1;
  
 -      if (gitmodules_is_unmerged)
 +      if (is_gitmodules_unmerged(&the_index))
                die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first"));
  
 -      submodule = submodule_from_path(null_sha1, path);
 +      submodule = submodule_from_path(&null_oid, path);
        if (!submodule || !submodule->name) {
                warning(_("Could not find section in .gitmodules where path=%s"), path);
                return -1;
        }
        strbuf_addstr(&sect, "submodule.");
        strbuf_addstr(&sect, submodule->name);
 -      if (git_config_rename_section_in_file(".gitmodules", sect.buf, NULL) < 0) {
 +      if (git_config_rename_section_in_file(GITMODULES_FILE, sect.buf, NULL) < 0) {
                /* Maybe the user already did that, don't error out here */
                warning(_("Could not remove .gitmodules entry for %s"), path);
                strbuf_release(&sect);
  
  void stage_updated_gitmodules(void)
  {
 -      if (add_file_to_cache(".gitmodules", 0))
 +      if (add_file_to_cache(GITMODULES_FILE, 0))
                die(_("staging updated .gitmodules failed"));
  }
  
@@@ -170,20 -147,42 +170,20 @@@ done
  void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
                                             const char *path)
  {
 -      const struct submodule *submodule = submodule_from_path(null_sha1, path);
 +      const struct submodule *submodule = submodule_from_path(&null_oid, path);
        if (submodule) {
 -              if (submodule->ignore)
 -                      handle_ignore_submodules_arg(diffopt, submodule->ignore);
 -              else if (gitmodules_is_unmerged)
 -                      DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES);
 -      }
 -}
 +              const char *ignore;
 +              char *key;
  
 -/* For loading from the .gitmodules file. */
 -static int git_modules_config(const char *var, const char *value, void *cb)
 -{
 -      if (!strcmp(var, "submodule.fetchjobs")) {
 -              parallel_jobs = git_config_int(var, value);
 -              if (parallel_jobs < 0)
 -                      die(_("negative values not allowed for submodule.fetchJobs"));
 -              return 0;
 -      } else if (starts_with(var, "submodule."))
 -              return parse_submodule_config_option(var, value);
 -      else if (!strcmp(var, "fetch.recursesubmodules")) {
 -              config_fetch_recurse_submodules = parse_fetch_recurse_submodules_arg(var, value);
 -              return 0;
 -      }
 -      return 0;
 -}
 +              key = xstrfmt("submodule.%s.ignore", submodule->name);
 +              if (repo_config_get_string_const(the_repository, key, &ignore))
 +                      ignore = submodule->ignore;
 +              free(key);
  
 -/* Loads all submodule settings from the config. */
 -int submodule_config(const char *var, const char *value, void *cb)
 -{
 -      if (!strcmp(var, "submodule.recurse")) {
 -              int v = git_config_bool(var, value) ?
 -                      RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
 -              config_update_recurse_submodules = v;
 -              return 0;
 -      } else {
 -              return git_modules_config(var, value, cb);
 +              if (ignore)
 +                      handle_ignore_submodules_arg(diffopt, ignore);
 +              else if (is_gitmodules_unmerged(&the_index))
 +                      DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES);
        }
  }
  
@@@ -215,6 -214,74 +215,6 @@@ int option_parse_recurse_submodules_wor
        return 0;
  }
  
 -void load_submodule_cache(void)
 -{
 -      if (config_update_recurse_submodules == RECURSE_SUBMODULES_OFF)
 -              return;
 -
 -      gitmodules_config();
 -      git_config(submodule_config, NULL);
 -}
 -
 -void gitmodules_config(void)
 -{
 -      const char *work_tree = get_git_work_tree();
 -      if (work_tree) {
 -              struct strbuf gitmodules_path = STRBUF_INIT;
 -              int pos;
 -              strbuf_addstr(&gitmodules_path, work_tree);
 -              strbuf_addstr(&gitmodules_path, "/.gitmodules");
 -              if (read_cache() < 0)
 -                      die("index file corrupt");
 -              pos = cache_name_pos(".gitmodules", 11);
 -              if (pos < 0) { /* .gitmodules not found or isn't merged */
 -                      pos = -1 - pos;
 -                      if (active_nr > pos) {  /* there is a .gitmodules */
 -                              const struct cache_entry *ce = active_cache[pos];
 -                              if (ce_namelen(ce) == 11 &&
 -                                  !memcmp(ce->name, ".gitmodules", 11))
 -                                      gitmodules_is_unmerged = 1;
 -                      }
 -              } else if (pos < active_nr) {
 -                      struct stat st;
 -                      if (lstat(".gitmodules", &st) == 0 &&
 -                          ce_match_stat(active_cache[pos], &st, 0) & DATA_CHANGED)
 -                              gitmodules_is_modified = 1;
 -              }
 -
 -              if (!gitmodules_is_unmerged)
 -                      git_config_from_file(git_modules_config,
 -                              gitmodules_path.buf, NULL);
 -              strbuf_release(&gitmodules_path);
 -      }
 -}
 -
 -static int gitmodules_cb(const char *var, const char *value, void *data)
 -{
 -      struct repository *repo = data;
 -      return submodule_config_option(repo, var, value);
 -}
 -
 -void repo_read_gitmodules(struct repository *repo)
 -{
 -      char *gitmodules_path = repo_worktree_path(repo, ".gitmodules");
 -
 -      git_config_from_file(gitmodules_cb, gitmodules_path, repo);
 -      free(gitmodules_path);
 -}
 -
 -void gitmodules_config_sha1(const unsigned char *commit_sha1)
 -{
 -      struct strbuf rev = STRBUF_INIT;
 -      unsigned char sha1[20];
 -
 -      if (gitmodule_sha1_from_commit(commit_sha1, sha1, &rev)) {
 -              git_config_from_blob_sha1(git_modules_config, rev.buf,
 -                                        sha1, NULL);
 -      }
 -      strbuf_release(&rev);
 -}
 -
  /*
   * Determine if a submodule has been initialized at a given 'path'
   */
@@@ -226,7 -293,7 +226,7 @@@ int is_submodule_active(struct reposito
        const struct string_list *sl;
        const struct submodule *module;
  
 -      module = submodule_from_cache(repo, null_sha1, path);
 +      module = submodule_from_cache(repo, &null_oid, path);
  
        /* early return if there isn't a path->module mapping */
        if (!module)
@@@ -343,38 -410,24 +343,38 @@@ void die_path_inside_submodule(const st
        }
  }
  
 -int parse_submodule_update_strategy(const char *value,
 -              struct submodule_update_strategy *dst)
 +enum submodule_update_type parse_submodule_update_type(const char *value)
  {
 -      free((void*)dst->command);
 -      dst->command = NULL;
        if (!strcmp(value, "none"))
 -              dst->type = SM_UPDATE_NONE;
 +              return SM_UPDATE_NONE;
        else if (!strcmp(value, "checkout"))
 -              dst->type = SM_UPDATE_CHECKOUT;
 +              return SM_UPDATE_CHECKOUT;
        else if (!strcmp(value, "rebase"))
 -              dst->type = SM_UPDATE_REBASE;
 +              return SM_UPDATE_REBASE;
        else if (!strcmp(value, "merge"))
 -              dst->type = SM_UPDATE_MERGE;
 -      else if (skip_prefix(value, "!", &value)) {
 -              dst->type = SM_UPDATE_COMMAND;
 -              dst->command = xstrdup(value);
 -      } else
 +              return SM_UPDATE_MERGE;
 +      else if (*value == '!')
 +              return SM_UPDATE_COMMAND;
 +      else
 +              return SM_UPDATE_UNSPECIFIED;
 +}
 +
 +int parse_submodule_update_strategy(const char *value,
 +              struct submodule_update_strategy *dst)
 +{
 +      enum submodule_update_type type;
 +
 +      free((void*)dst->command);
 +      dst->command = NULL;
 +
 +      type = parse_submodule_update_type(value);
 +      if (type == SM_UPDATE_UNSPECIFIED)
                return -1;
 +
 +      dst->type = type;
 +      if (type == SM_UPDATE_COMMAND)
 +              dst->command = xstrdup(value + 1);
 +
        return 0;
  }
  
@@@ -437,7 -490,9 +437,7 @@@ static int prepare_submodule_summary(st
        return prepare_revision_walk(rev);
  }
  
 -static void print_submodule_summary(struct rev_info *rev, FILE *f,
 -              const char *line_prefix,
 -              const char *del, const char *add, const char *reset)
 +static void print_submodule_summary(struct rev_info *rev, struct diff_options *o)
  {
        static const char format[] = "  %m %s";
        struct strbuf sb = STRBUF_INIT;
                ctx.date_mode = rev->date_mode;
                ctx.output_encoding = get_log_output_encoding();
                strbuf_setlen(&sb, 0);
 -              strbuf_addstr(&sb, line_prefix);
 -              if (commit->object.flags & SYMMETRIC_LEFT) {
 -                      if (del)
 -                              strbuf_addstr(&sb, del);
 -              }
 -              else if (add)
 -                      strbuf_addstr(&sb, add);
                format_commit_message(commit, format, &sb, &ctx);
 -              if (reset)
 -                      strbuf_addstr(&sb, reset);
                strbuf_addch(&sb, '\n');
 -              fprintf(f, "%s", sb.buf);
 +              if (commit->object.flags & SYMMETRIC_LEFT)
 +                      diff_emit_submodule_del(o, sb.buf);
 +              else
 +                      diff_emit_submodule_add(o, sb.buf);
        }
        strbuf_release(&sb);
  }
@@@ -480,9 -541,11 +480,9 @@@ void prepare_submodule_repo_env(struct 
   * attempt to lookup both the left and right commits and put them into the
   * left and right pointers.
   */
 -static void show_submodule_header(FILE *f, const char *path,
 -              const char *line_prefix,
 +static void show_submodule_header(struct diff_options *o, const char *path,
                struct object_id *one, struct object_id *two,
 -              unsigned dirty_submodule, const char *meta,
 -              const char *reset,
 +              unsigned dirty_submodule,
                struct commit **left, struct commit **right,
                struct commit_list **merge_bases)
  {
        int fast_forward = 0, fast_backward = 0;
  
        if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
 -              fprintf(f, "%sSubmodule %s contains untracked content\n",
 -                      line_prefix, path);
 +              diff_emit_submodule_untracked(o, path);
 +
        if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
 -              fprintf(f, "%sSubmodule %s contains modified content\n",
 -                      line_prefix, path);
 +              diff_emit_submodule_modified(o, path);
  
        if (is_null_oid(one))
                message = "(new submodule)";
        }
  
  output_header:
 -      strbuf_addf(&sb, "%s%sSubmodule %s ", line_prefix, meta, path);
 +      strbuf_addf(&sb, "Submodule %s ", path);
        strbuf_add_unique_abbrev(&sb, one->hash, DEFAULT_ABBREV);
        strbuf_addstr(&sb, (fast_backward || fast_forward) ? ".." : "...");
        strbuf_add_unique_abbrev(&sb, two->hash, DEFAULT_ABBREV);
        if (message)
 -              strbuf_addf(&sb, " %s%s\n", message, reset);
 +              strbuf_addf(&sb, " %s\n", message);
        else
 -              strbuf_addf(&sb, "%s:%s\n", fast_backward ? " (rewind)" : "", reset);
 -      fwrite(sb.buf, sb.len, 1, f);
 +              strbuf_addf(&sb, "%s:\n", fast_backward ? " (rewind)" : "");
 +      diff_emit_submodule_header(o, sb.buf);
  
        strbuf_release(&sb);
  }
  
 -void show_submodule_summary(FILE *f, const char *path,
 -              const char *line_prefix,
 +void show_submodule_summary(struct diff_options *o, const char *path,
                struct object_id *one, struct object_id *two,
 -              unsigned dirty_submodule, const char *meta,
 -              const char *del, const char *add, const char *reset)
 +              unsigned dirty_submodule)
  {
        struct rev_info rev;
        struct commit *left = NULL, *right = NULL;
        struct commit_list *merge_bases = NULL;
  
 -      show_submodule_header(f, path, line_prefix, one, two, dirty_submodule,
 -                            meta, reset, &left, &right, &merge_bases);
 +      show_submodule_header(o, path, one, two, dirty_submodule,
 +                            &left, &right, &merge_bases);
  
        /*
         * If we don't have both a left and a right pointer, there is no
  
        /* Treat revision walker failure the same as missing commits */
        if (prepare_submodule_summary(&rev, path, left, right, merge_bases)) {
 -              fprintf(f, "%s(revision walker failed)\n", line_prefix);
 +              diff_emit_submodule_error(o, "(revision walker failed)\n");
                goto out;
        }
  
 -      print_submodule_summary(&rev, f, line_prefix, del, add, reset);
 +      print_submodule_summary(&rev, o);
  
  out:
        if (merge_bases)
        clear_commit_marks(right, ~0);
  }
  
 -void show_submodule_inline_diff(FILE *f, const char *path,
 -              const char *line_prefix,
 +void show_submodule_inline_diff(struct diff_options *o, const char *path,
                struct object_id *one, struct object_id *two,
 -              unsigned dirty_submodule, const char *meta,
 -              const char *del, const char *add, const char *reset,
 -              const struct diff_options *o)
 +              unsigned dirty_submodule)
  {
        const struct object_id *old = &empty_tree_oid, *new = &empty_tree_oid;
        struct commit *left = NULL, *right = NULL;
        struct commit_list *merge_bases = NULL;
 -      struct strbuf submodule_dir = STRBUF_INIT;
        struct child_process cp = CHILD_PROCESS_INIT;
 +      struct strbuf sb = STRBUF_INIT;
  
 -      show_submodule_header(f, path, line_prefix, one, two, dirty_submodule,
 -                            meta, reset, &left, &right, &merge_bases);
 +      show_submodule_header(o, path, one, two, dirty_submodule,
 +                            &left, &right, &merge_bases);
  
        /* We need a valid left and right commit to display a difference */
        if (!(left || is_null_oid(one)) ||
        if (right)
                new = two;
  
 -      fflush(f);
        cp.git_cmd = 1;
        cp.dir = path;
 -      cp.out = dup(fileno(f));
 +      cp.out = -1;
        cp.no_stdin = 1;
  
        /* TODO: other options may need to be passed here. */
        argv_array_pushl(&cp.args, "diff", "--submodule=diff", NULL);
 +      argv_array_pushf(&cp.args, "--color=%s", want_color(o->use_color) ?
 +                       "always" : "never");
  
 -      argv_array_pushf(&cp.args, "--line-prefix=%s", line_prefix);
        if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
                argv_array_pushf(&cp.args, "--src-prefix=%s%s/",
                                 o->b_prefix, path);
                argv_array_push(&cp.args, oid_to_hex(new));
  
        prepare_submodule_repo_env(&cp.env_array);
 -      if (run_command(&cp))
 -              fprintf(f, "(diff failed)\n");
 +      if (start_command(&cp))
 +              diff_emit_submodule_error(o, "(diff failed)\n");
 +
 +      while (strbuf_getwholeline_fd(&sb, cp.out, '\n') != EOF)
 +              diff_emit_submodule_pipethrough(o, sb.buf, sb.len);
 +
 +      if (finish_command(&cp))
 +              diff_emit_submodule_error(o, "(diff failed)\n");
  
  done:
 -      strbuf_release(&submodule_dir);
 +      strbuf_release(&sb);
        if (merge_bases)
                free_commit_list(merge_bases);
        if (left)
                clear_commit_marks(right, ~0);
  }
  
 -void set_config_fetch_recurse_submodules(int value)
 -{
 -      config_fetch_recurse_submodules = value;
 -}
 -
  int should_update_submodules(void)
  {
        return config_update_recurse_submodules == RECURSE_SUBMODULES_ON;
@@@ -670,7 -738,7 +670,7 @@@ const struct submodule *submodule_from_
        if (!should_update_submodules())
                return NULL;
  
 -      return submodule_from_path(null_sha1, ce->name);
 +      return submodule_from_path(&null_oid, ce->name);
  }
  
  static struct oid_array *submodule_commits(struct string_list *submodules,
@@@ -774,36 -842,19 +774,36 @@@ static int append_oid_to_argv(const str
        return 0;
  }
  
 +struct has_commit_data {
 +      int result;
 +      const char *path;
 +};
 +
  static int check_has_commit(const struct object_id *oid, void *data)
  {
 -      int *has_commit = data;
 +      struct has_commit_data *cb = data;
  
 -      if (!lookup_commit_reference(oid))
 -              *has_commit = 0;
 +      enum object_type type = sha1_object_info(oid->hash, NULL);
  
 -      return 0;
 +      switch (type) {
 +      case OBJ_COMMIT:
 +              return 0;
 +      case OBJ_BAD:
 +              /*
 +               * Object is missing or invalid. If invalid, an error message
 +               * has already been printed.
 +               */
 +              cb->result = 0;
 +              return 0;
 +      default:
 +              die(_("submodule entry '%s' (%s) is a %s, not a commit"),
 +                  cb->path, oid_to_hex(oid), typename(type));
 +      }
  }
  
  static int submodule_has_commits(const char *path, struct oid_array *commits)
  {
 -      int has_commit = 1;
 +      struct has_commit_data has_commit = { 1, path };
  
        /*
         * Perform a cheap, but incorrect check for the existence of 'commits'.
  
        oid_array_for_each_unique(commits, check_has_commit, &has_commit);
  
 -      if (has_commit) {
 +      if (has_commit.result) {
                /*
                 * Even if the submodule is checked out and the commit is
                 * present, make sure it exists in the submodule's object store
                cp.dir = path;
  
                if (capture_command(&cp, &out, GIT_MAX_HEXSZ + 1) || out.len)
 -                      has_commit = 0;
 +                      has_commit.result = 0;
  
                strbuf_release(&out);
        }
  
 -      return has_commit;
 +      return has_commit.result;
  }
  
  static int submodule_needs_pushing(const char *path, struct oid_array *commits)
@@@ -1106,6 -1157,7 +1106,6 @@@ int submodule_touches_in_range(struct o
        struct argv_array args = ARGV_ARRAY_INIT;
        int ret;
  
 -      gitmodules_config();
        /* No need to check if there are no submodules configured */
        if (!submodule_from_path(NULL, NULL))
                return 0;
@@@ -1130,11 -1182,10 +1130,11 @@@ struct submodule_parallel_fetch 
        const char *work_tree;
        const char *prefix;
        int command_line_option;
 +      int default_option;
        int quiet;
        int result;
  };
 -#define SPF_INIT {0, ARGV_ARRAY_INIT, NULL, NULL, 0, 0, 0}
 +#define SPF_INIT {0, ARGV_ARRAY_INIT, NULL, NULL, 0, 0, 0, 0}
  
  static int get_next_submodule(struct child_process *cp,
                              struct strbuf *err, void *data, void **task_cb)
                if (!S_ISGITLINK(ce->ce_mode))
                        continue;
  
 -              submodule = submodule_from_path(null_sha1, ce->name);
 -              if (!submodule)
 -                      submodule = submodule_from_name(null_sha1, ce->name);
 +              submodule = submodule_from_path(&null_oid, ce->name);
  
                default_argv = "yes";
                if (spf->command_line_option == RECURSE_SUBMODULES_DEFAULT) {
 -                      if (submodule &&
 -                          submodule->fetch_recurse !=
 -                                              RECURSE_SUBMODULES_NONE) {
 -                              if (submodule->fetch_recurse ==
 -                                              RECURSE_SUBMODULES_OFF)
 +                      int fetch_recurse = RECURSE_SUBMODULES_NONE;
 +
 +                      if (submodule) {
 +                              char *key;
 +                              const char *value;
 +
 +                              fetch_recurse = submodule->fetch_recurse;
 +                              key = xstrfmt("submodule.%s.fetchRecurseSubmodules", submodule->name);
 +                              if (!repo_config_get_string_const(the_repository, key, &value)) {
 +                                      fetch_recurse = parse_fetch_recurse_submodules_arg(key, value);
 +                              }
 +                              free(key);
 +                      }
 +
 +                      if (fetch_recurse != RECURSE_SUBMODULES_NONE) {
 +                              if (fetch_recurse == RECURSE_SUBMODULES_OFF)
                                        continue;
 -                              if (submodule->fetch_recurse ==
 -                                              RECURSE_SUBMODULES_ON_DEMAND) {
 +                              if (fetch_recurse == RECURSE_SUBMODULES_ON_DEMAND) {
                                        if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
                                                continue;
                                        default_argv = "on-demand";
                                }
                        } else {
 -                              if ((config_fetch_recurse_submodules == RECURSE_SUBMODULES_OFF) ||
 -                                  gitmodules_is_unmerged)
 +                              if (spf->default_option == RECURSE_SUBMODULES_OFF)
                                        continue;
 -                              if (config_fetch_recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) {
 +                              if (spf->default_option == RECURSE_SUBMODULES_ON_DEMAND) {
                                        if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
                                                continue;
                                        default_argv = "on-demand";
@@@ -1249,7 -1293,6 +1249,7 @@@ static int fetch_finish(int retvalue, s
  
  int fetch_populated_submodules(const struct argv_array *options,
                               const char *prefix, int command_line_option,
 +                             int default_option,
                               int quiet, int max_parallel_jobs)
  {
        int i;
  
        spf.work_tree = get_git_work_tree();
        spf.command_line_option = command_line_option;
 +      spf.default_option = default_option;
        spf.quiet = quiet;
        spf.prefix = prefix;
  
        argv_array_push(&spf.args, "--recurse-submodules-default");
        /* default value, "--submodule-prefix" and its value are added later */
  
 -      if (max_parallel_jobs < 0)
 -              max_parallel_jobs = parallel_jobs;
 -
        calculate_changed_submodule_paths();
        run_processes_parallel(max_parallel_jobs,
                               get_next_submodule,
@@@ -1537,7 -1582,7 +1537,7 @@@ int submodule_move_head(const char *pat
        if (old && !is_submodule_populated_gently(path, error_code_ptr))
                return 0;
  
 -      sub = submodule_from_path(null_sha1, path);
 +      sub = submodule_from_path(&null_oid, path);
  
        if (!sub)
                die("BUG: could not get submodule information for '%s'", path);
@@@ -1651,8 -1696,6 +1651,8 @@@ static int find_first_merges(struct obj
                        oid_to_hex(&a->object.oid));
        init_revisions(&revs, NULL);
        rev_opts.submodule = path;
 +      /* FIXME: can't handle linked worktrees in submodules yet */
 +      revs.single_worktree = path != NULL;
        setup_revisions(ARRAY_SIZE(rev_args)-1, rev_args, &revs, &rev_opts);
  
        /* save all revisions from the above list that contain b */
                        add_object_array(merges.objects[i].item, NULL, result);
        }
  
-       free(merges.objects);
+       object_array_clear(&merges);
        return result->nr;
  }
  
@@@ -1790,10 -1833,15 +1790,10 @@@ int merge_submodule(struct object_id *r
                        print_commit((struct commit *) merges.objects[i].item);
        }
  
-       free(merges.objects);
+       object_array_clear(&merges);
        return 0;
  }
  
 -int parallel_submodules(void)
 -{
 -      return parallel_jobs;
 -}
 -
  /*
   * Embeds a single submodules git directory into the superprojects git dir,
   * non recursively.
@@@ -1816,7 -1864,7 +1816,7 @@@ static void relocate_single_git_dir_int
  
        real_old_git_dir = real_pathdup(old_git_dir, 1);
  
 -      sub = submodule_from_path(null_sha1, path);
 +      sub = submodule_from_path(&null_oid, path);
        if (!sub)
                die(_("could not lookup name for submodule '%s'"), path);
  
@@@ -1872,7 -1920,7 +1872,7 @@@ void absorb_git_dir_into_superproject(c
                * superproject did not rewrite the git file links yet,
                * fix it now.
                */
 -              sub = submodule_from_path(null_sha1, path);
 +              sub = submodule_from_path(&null_oid, path);
                if (!sub)
                        die(_("could not lookup name for submodule '%s'"), path);
                connect_work_tree_and_git_dir(path,
@@@ -2014,7 -2062,8 +2014,7 @@@ int submodule_to_gitdir(struct strbuf *
                strbuf_addstr(buf, git_dir);
        }
        if (!is_git_directory(buf->buf)) {
 -              gitmodules_config();
 -              sub = submodule_from_path(null_sha1, submodule);
 +              sub = submodule_from_path(&null_oid, submodule);
                if (!sub) {
                        ret = -1;
                        goto cleanup;
diff --combined upload-pack.c
index 06d822aad2b480458263813beeecca028983a306,ec0eee8fc4322ddde7d3c11733f93d428b79f4b3..e25f725c0feaa57c9a09c976628217c99dfb5652
@@@ -888,7 -888,7 +888,7 @@@ static void receive_needs(void
                }
  
        shallow_nr += shallows.nr;
-       free(shallows.objects);
+       object_array_clear(&shallows);
  }
  
  /* return non-zero if the ref is hidden, otherwise 0 */
@@@ -965,10 -965,11 +965,10 @@@ static int find_symref(const char *refn
  {
        const char *symref_target;
        struct string_list_item *item;
 -      struct object_id unused;
  
        if ((flag & REF_ISSYMREF) == 0)
                return 0;
 -      symref_target = resolve_ref_unsafe(refname, 0, unused.hash, &flag);
 +      symref_target = resolve_ref_unsafe(refname, 0, NULL, &flag);
        if (!symref_target || (flag & REF_ISSYMREF) == 0)
                die("'%s' is a symref but it is not?", refname);
        item = string_list_append(cb_data, refname);