Merge branch 'md/filter-trees'
authorJunio C Hamano <gitster@pobox.com>
Tue, 30 Oct 2018 06:43:39 +0000 (15:43 +0900)
committerJunio C Hamano <gitster@pobox.com>
Tue, 30 Oct 2018 06:43:39 +0000 (15:43 +0900)
The "rev-list --filter" feature learned to exclude all trees via
"tree:0" filter.

* md/filter-trees:
list-objects: support for skipping tree traversal
filter-trees: code clean-up of tests
list-objects-filter: implement filter tree:0
list-objects-filter-options: do not over-strbuf_init
list-objects-filter: use BUG rather than die
revision: mark non-user-given objects instead
rev-list: handle missing tree objects properly
list-objects: always parse trees gently
list-objects: refactor to process_tree_contents
list-objects: store common func args in struct

1  2 
builtin/rev-list.c
list-objects-filter.h
list-objects.c
revision.c
revision.h
t/t0410-partial-clone.sh
t/t5317-pack-objects-filter-objects.sh
t/t5616-partial-clone.sh
t/t6112-rev-list-filters-objects.sh
diff --combined builtin/rev-list.c
index cc1b70522f7bcdb77fd33ccd9d9948926b8333ad,49d6deed7012cb2c529ad971c6e45abf654fe097..5064d08e1b8ad04544a76d1f0496134e2c15079c
@@@ -6,6 -6,7 +6,7 @@@
  #include "list-objects.h"
  #include "list-objects-filter.h"
  #include "list-objects-filter-options.h"
+ #include "object.h"
  #include "object-store.h"
  #include "pack.h"
  #include "pack-bitmap.h"
@@@ -209,7 -210,8 +210,8 @@@ static inline void finish_object__ma(st
         */
        switch (arg_missing_action) {
        case MA_ERROR:
-               die("missing blob object '%s'", oid_to_hex(&obj->oid));
+               die("missing %s object '%s'",
+                   type_name(obj->type), oid_to_hex(&obj->oid));
                return;
  
        case MA_ALLOW_ANY:
        case MA_ALLOW_PROMISOR:
                if (is_promisor_object(&obj->oid))
                        return;
-               die("unexpected missing blob object '%s'",
-                   oid_to_hex(&obj->oid));
+               die("unexpected missing %s object '%s'",
+                   type_name(obj->type), oid_to_hex(&obj->oid));
                return;
  
        default:
  static int finish_object(struct object *obj, const char *name, void *cb_data)
  {
        struct rev_list_info *info = cb_data;
-       if (obj->type == OBJ_BLOB && !has_object_file(&obj->oid)) {
+       if (!has_object_file(&obj->oid)) {
                finish_object__ma(obj);
                return 1;
        }
@@@ -370,9 -372,10 +372,10 @@@ int cmd_rev_list(int argc, const char *
                usage(rev_list_usage);
  
        git_config(git_default_config, NULL);
 -      init_revisions(&revs, prefix);
 +      repo_init_revisions(the_repository, &revs, prefix);
        revs.abbrev = DEFAULT_ABBREV;
        revs.commit_format = CMIT_FMT_UNSPECIFIED;
+       revs.do_not_die_on_missing_tree = 1;
  
        /*
         * Scan the argument list before invoking setup_revisions(), so that we
        if ((!revs.commits && reflog_walk_empty(revs.reflog_info) &&
             (!(revs.tag_objects || revs.tree_objects || revs.blob_objects) &&
              !revs.pending.nr) &&
 -           !revs.rev_input_given) ||
 +           !revs.rev_input_given && !revs.read_from_stdin) ||
            revs.diff)
                usage(rev_list_usage);
  
diff --combined list-objects-filter.h
index a6f6b4990b43c8f4c8cb94ba5b890370287ec868,9c19875a4100d36d48237aa3aa52943647bbe5c6..52b4a84da9418497a4fc37e50cb8e0dcbc5b70bd
@@@ -1,10 -1,6 +1,10 @@@
  #ifndef LIST_OBJECTS_FILTER_H
  #define LIST_OBJECTS_FILTER_H
  
 +struct list_objects_filter_options;
 +struct object;
 +struct oidset;
 +
  /*
   * During list-object traversal we allow certain objects to be
   * filtered (omitted) from the result.  The active filter uses
   *              In general, objects should only be shown once, but
   *              this result DOES NOT imply that we mark it SEEN.
   *
+  * _SKIP_TREE : Used in LOFS_BEGIN_TREE situation - indicates that
+  *              the tree's children should not be iterated over. This
+  *              is used as an optimization when all children will
+  *              definitely be ignored.
+  *
   * Most of the time, you want the combination (_MARK_SEEN | _DO_SHOW)
   * but they can be used independently, such as when sparse-checkout
   * pattern matching is being applied.
@@@ -45,6 -46,7 +50,7 @@@ enum list_objects_filter_result 
        LOFR_ZERO      = 0,
        LOFR_MARK_SEEN = 1<<0,
        LOFR_DO_SHOW   = 1<<1,
+       LOFR_SKIP_TREE = 1<<2,
  };
  
  enum list_objects_filter_situation {
diff --combined list-objects.c
index 0c2989d5ca7cc4df523376dce94829b1b6be9110,d1e3d217c561c9a4829c94f2837b3d837599365f..c41cc80db5bc86279bcf560109f599a9783b2755
  #include "list-objects-filter-options.h"
  #include "packfile.h"
  #include "object-store.h"
+ #include "trace.h"
  
- static void process_blob(struct rev_info *revs,
+ struct traversal_context {
+       struct rev_info *revs;
+       show_object_fn show_object;
+       show_commit_fn show_commit;
+       void *show_data;
+       filter_object_fn filter_fn;
+       void *filter_data;
+ };
+ static void process_blob(struct traversal_context *ctx,
                         struct blob *blob,
-                        show_object_fn show,
                         struct strbuf *path,
-                        const char *name,
-                        void *cb_data,
-                        filter_object_fn filter_fn,
-                        void *filter_data)
+                        const char *name)
  {
        struct object *obj = &blob->object;
        size_t pathlen;
        enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW;
  
-       if (!revs->blob_objects)
+       if (!ctx->revs->blob_objects)
                return;
        if (!obj)
                die("bad blob object");
         * may cause the actual filter to report an incomplete list
         * of missing objects.
         */
-       if (revs->exclude_promisor_objects &&
+       if (ctx->revs->exclude_promisor_objects &&
            !has_object_file(&obj->oid) &&
            is_promisor_object(&obj->oid))
                return;
  
        pathlen = path->len;
        strbuf_addstr(path, name);
-       if (!(obj->flags & USER_GIVEN) && filter_fn)
-               r = filter_fn(LOFS_BLOB, obj,
-                             path->buf, &path->buf[pathlen],
-                             filter_data);
+       if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn)
+               r = ctx->filter_fn(LOFS_BLOB, obj,
+                                  path->buf, &path->buf[pathlen],
+                                  ctx->filter_data);
        if (r & LOFR_MARK_SEEN)
                obj->flags |= SEEN;
        if (r & LOFR_DO_SHOW)
-               show(obj, path->buf, cb_data);
+               ctx->show_object(obj, path->buf, ctx->show_data);
        strbuf_setlen(path, pathlen);
  }
  
   * the link, and how to do it. Whether it necessarily makes
   * any sense what-so-ever to ever do that is another issue.
   */
- static void process_gitlink(struct rev_info *revs,
+ static void process_gitlink(struct traversal_context *ctx,
                            const unsigned char *sha1,
-                           show_object_fn show,
                            struct strbuf *path,
-                           const char *name,
-                           void *cb_data)
+                           const char *name)
  {
        /* Nothing to do */
  }
  
- static void process_tree(struct rev_info *revs,
+ static void process_tree(struct traversal_context *ctx,
                         struct tree *tree,
-                        show_object_fn show,
                         struct strbuf *base,
-                        const char *name,
-                        void *cb_data,
-                        filter_object_fn filter_fn,
-                        void *filter_data)
+                        const char *name);
+ static void process_tree_contents(struct traversal_context *ctx,
+                                 struct tree *tree,
+                                 struct strbuf *base)
  {
-       struct object *obj = &tree->object;
        struct tree_desc desc;
        struct name_entry entry;
-       enum interesting match = revs->diffopt.pathspec.nr == 0 ?
-               all_entries_interesting: entry_not_interesting;
+       enum interesting match = ctx->revs->diffopt.pathspec.nr == 0 ?
+               all_entries_interesting : entry_not_interesting;
+       init_tree_desc(&desc, tree->buffer, tree->size);
+       while (tree_entry(&desc, &entry)) {
+               if (match != all_entries_interesting) {
+                       match = tree_entry_interesting(&entry, base, 0,
+                                                      &ctx->revs->diffopt.pathspec);
+                       if (match == all_entries_not_interesting)
+                               break;
+                       if (match == entry_not_interesting)
+                               continue;
+               }
+               if (S_ISDIR(entry.mode)) {
+                       struct tree *t = lookup_tree(the_repository, entry.oid);
+                       t->object.flags |= NOT_USER_GIVEN;
+                       process_tree(ctx, t, base, entry.path);
+               }
+               else if (S_ISGITLINK(entry.mode))
+                       process_gitlink(ctx, entry.oid->hash,
+                                       base, entry.path);
+               else {
+                       struct blob *b = lookup_blob(the_repository, entry.oid);
+                       b->object.flags |= NOT_USER_GIVEN;
+                       process_blob(ctx, b, base, entry.path);
+               }
+       }
+ }
+ static void process_tree(struct traversal_context *ctx,
+                        struct tree *tree,
+                        struct strbuf *base,
+                        const char *name)
+ {
+       struct object *obj = &tree->object;
+       struct rev_info *revs = ctx->revs;
        int baselen = base->len;
        enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW;
-       int gently = revs->ignore_missing_links ||
-                    revs->exclude_promisor_objects;
+       int failed_parse;
  
        if (!revs->tree_objects)
                return;
                die("bad tree object");
        if (obj->flags & (UNINTERESTING | SEEN))
                return;
-       if (parse_tree_gently(tree, gently) < 0) {
+       failed_parse = parse_tree_gently(tree, 1);
+       if (failed_parse) {
                if (revs->ignore_missing_links)
                        return;
  
                    is_promisor_object(&obj->oid))
                        return;
  
-               die("bad tree object %s", oid_to_hex(&obj->oid));
+               if (!revs->do_not_die_on_missing_tree)
+                       die("bad tree object %s", oid_to_hex(&obj->oid));
        }
  
        strbuf_addstr(base, name);
-       if (!(obj->flags & USER_GIVEN) && filter_fn)
-               r = filter_fn(LOFS_BEGIN_TREE, obj,
-                             base->buf, &base->buf[baselen],
-                             filter_data);
+       if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn)
+               r = ctx->filter_fn(LOFS_BEGIN_TREE, obj,
+                                  base->buf, &base->buf[baselen],
+                                  ctx->filter_data);
        if (r & LOFR_MARK_SEEN)
                obj->flags |= SEEN;
        if (r & LOFR_DO_SHOW)
-               show(obj, base->buf, cb_data);
+               ctx->show_object(obj, base->buf, ctx->show_data);
        if (base->len)
                strbuf_addch(base, '/');
  
-       init_tree_desc(&desc, tree->buffer, tree->size);
+       if (r & LOFR_SKIP_TREE)
+               trace_printf("Skipping contents of tree %s...\n", base->buf);
+       else if (!failed_parse)
+               process_tree_contents(ctx, tree, base);
  
-       while (tree_entry(&desc, &entry)) {
-               if (match != all_entries_interesting) {
-                       match = tree_entry_interesting(&entry, base, 0,
-                                                      &revs->diffopt.pathspec);
-                       if (match == all_entries_not_interesting)
-                               break;
-                       if (match == entry_not_interesting)
-                               continue;
-               }
-               if (S_ISDIR(entry.mode))
-                       process_tree(revs,
-                                    lookup_tree(the_repository, entry.oid),
-                                    show, base, entry.path,
-                                    cb_data, filter_fn, filter_data);
-               else if (S_ISGITLINK(entry.mode))
-                       process_gitlink(revs, entry.oid->hash,
-                                       show, base, entry.path,
-                                       cb_data);
-               else
-                       process_blob(revs,
-                                    lookup_blob(the_repository, entry.oid),
-                                    show, base, entry.path,
-                                    cb_data, filter_fn, filter_data);
-       }
-       if (!(obj->flags & USER_GIVEN) && filter_fn) {
-               r = filter_fn(LOFS_END_TREE, obj,
-                             base->buf, &base->buf[baselen],
-                             filter_data);
+       if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn) {
+               r = ctx->filter_fn(LOFS_END_TREE, obj,
+                                  base->buf, &base->buf[baselen],
+                                  ctx->filter_data);
                if (r & LOFR_MARK_SEEN)
                        obj->flags |= SEEN;
                if (r & LOFR_DO_SHOW)
-                       show(obj, base->buf, cb_data);
+                       ctx->show_object(obj, base->buf, ctx->show_data);
        }
  
        strbuf_setlen(base, baselen);
@@@ -196,7 -214,7 +214,7 @@@ static void mark_edge_parents_uninteres
                struct commit *parent = parents->item;
                if (!(parent->object.flags & UNINTERESTING))
                        continue;
 -              mark_tree_uninteresting(get_commit_tree(parent));
 +              mark_tree_uninteresting(revs->repo, get_commit_tree(parent));
                if (revs->edge_hint && !(parent->object.flags & SHOWN)) {
                        parent->object.flags |= SHOWN;
                        show_edge(parent);
@@@ -213,8 -231,7 +231,8 @@@ void mark_edges_uninteresting(struct re
                struct commit *commit = list->item;
  
                if (commit->object.flags & UNINTERESTING) {
 -                      mark_tree_uninteresting(get_commit_tree(commit));
 +                      mark_tree_uninteresting(revs->repo,
 +                                              get_commit_tree(commit));
                        if (revs->edge_hint_aggressive && !(commit->object.flags & SHOWN)) {
                                commit->object.flags |= SHOWN;
                                show_edge(commit);
                        struct commit *commit = (struct commit *)obj;
                        if (obj->type != OBJ_COMMIT || !(obj->flags & UNINTERESTING))
                                continue;
 -                      mark_tree_uninteresting(get_commit_tree(commit));
 +                      mark_tree_uninteresting(revs->repo,
 +                                              get_commit_tree(commit));
                        if (!(obj->flags & SHOWN)) {
                                obj->flags |= SHOWN;
                                show_edge(commit);
@@@ -244,19 -260,15 +262,15 @@@ static void add_pending_tree(struct rev
        add_pending_object(revs, &tree->object, "");
  }
  
- static void traverse_trees_and_blobs(struct rev_info *revs,
-                                    struct strbuf *base,
-                                    show_object_fn show_object,
-                                    void *show_data,
-                                    filter_object_fn filter_fn,
-                                    void *filter_data)
+ static void traverse_trees_and_blobs(struct traversal_context *ctx,
+                                    struct strbuf *base)
  {
        int i;
  
        assert(base->len == 0);
  
-       for (i = 0; i < revs->pending.nr; i++) {
-               struct object_array_entry *pending = revs->pending.objects + i;
+       for (i = 0; i < ctx->revs->pending.nr; i++) {
+               struct object_array_entry *pending = ctx->revs->pending.objects + i;
                struct object *obj = pending->item;
                const char *name = pending->name;
                const char *path = pending->path;
                        continue;
                if (obj->type == OBJ_TAG) {
                        obj->flags |= SEEN;
-                       show_object(obj, name, show_data);
+                       ctx->show_object(obj, name, ctx->show_data);
                        continue;
                }
                if (!path)
                        path = "";
                if (obj->type == OBJ_TREE) {
-                       process_tree(revs, (struct tree *)obj, show_object,
-                                    base, path, show_data,
-                                    filter_fn, filter_data);
+                       process_tree(ctx, (struct tree *)obj, base, path);
                        continue;
                }
                if (obj->type == OBJ_BLOB) {
-                       process_blob(revs, (struct blob *)obj, show_object,
-                                    base, path, show_data,
-                                    filter_fn, filter_data);
+                       process_blob(ctx, (struct blob *)obj, base, path);
                        continue;
                }
                die("unknown pending object %s (%s)",
                    oid_to_hex(&obj->oid), name);
        }
-       object_array_clear(&revs->pending);
+       object_array_clear(&ctx->revs->pending);
  }
  
- static void do_traverse(struct rev_info *revs,
-                       show_commit_fn show_commit,
-                       show_object_fn show_object,
-                       void *show_data,
-                       filter_object_fn filter_fn,
-                       void *filter_data)
+ static void do_traverse(struct traversal_context *ctx)
  {
        struct commit *commit;
        struct strbuf csp; /* callee's scratch pad */
        strbuf_init(&csp, PATH_MAX);
  
-       while ((commit = get_revision(revs)) != NULL) {
+       while ((commit = get_revision(ctx->revs)) != NULL) {
                /*
                 * an uninteresting boundary commit may not have its tree
                 * parsed yet, but we are not going to show them anyway
                 */
-               if (get_commit_tree(commit))
-                       add_pending_tree(revs, get_commit_tree(commit));
-               show_commit(commit, show_data);
+               if (get_commit_tree(commit)) {
+                       struct tree *tree = get_commit_tree(commit);
+                       tree->object.flags |= NOT_USER_GIVEN;
+                       add_pending_tree(ctx->revs, tree);
+               }
+               ctx->show_commit(commit, ctx->show_data);
  
-               if (revs->tree_blobs_in_commit_order)
+               if (ctx->revs->tree_blobs_in_commit_order)
                        /*
                         * NEEDSWORK: Adding the tree and then flushing it here
                         * needs a reallocation for each commit. Can we pass the
                         * tree directory without allocation churn?
                         */
-                       traverse_trees_and_blobs(revs, &csp,
-                                                show_object, show_data,
-                                                filter_fn, filter_data);
+                       traverse_trees_and_blobs(ctx, &csp);
        }
-       traverse_trees_and_blobs(revs, &csp,
-                                show_object, show_data,
-                                filter_fn, filter_data);
+       traverse_trees_and_blobs(ctx, &csp);
        strbuf_release(&csp);
  }
  
@@@ -328,7 -330,14 +332,14 @@@ void traverse_commit_list(struct rev_in
                          show_object_fn show_object,
                          void *show_data)
  {
-       do_traverse(revs, show_commit, show_object, show_data, NULL, NULL);
+       struct traversal_context ctx;
+       ctx.revs = revs;
+       ctx.show_commit = show_commit;
+       ctx.show_object = show_object;
+       ctx.show_data = show_data;
+       ctx.filter_fn = NULL;
+       ctx.filter_data = NULL;
+       do_traverse(&ctx);
  }
  
  void traverse_commit_list_filtered(
        void *show_data,
        struct oidset *omitted)
  {
-       filter_object_fn filter_fn = NULL;
+       struct traversal_context ctx;
        filter_free_fn filter_free_fn = NULL;
-       void *filter_data = NULL;
-       filter_data = list_objects_filter__init(omitted, filter_options,
-                                               &filter_fn, &filter_free_fn);
-       do_traverse(revs, show_commit, show_object, show_data,
-                   filter_fn, filter_data);
-       if (filter_data && filter_free_fn)
-               filter_free_fn(filter_data);
+       ctx.revs = revs;
+       ctx.show_object = show_object;
+       ctx.show_commit = show_commit;
+       ctx.show_data = show_data;
+       ctx.filter_fn = NULL;
+       ctx.filter_data = list_objects_filter__init(omitted, filter_options,
+                                                   &ctx.filter_fn, &filter_free_fn);
+       do_traverse(&ctx);
+       if (ctx.filter_data && filter_free_fn)
+               filter_free_fn(ctx.filter_data);
  }
diff --combined revision.c
index b5108b75abc9ff0ef0438f4cba2e932e935ff94f,6d355b43c37ac15ca113c513750fcb2d435a2bef..a1ddb9e11cbe3a52bb8d3eee3785524db4055854
@@@ -24,7 -24,6 +24,7 @@@
  #include "packfile.h"
  #include "worktree.h"
  #include "argv-array.h"
 +#include "commit-reach.h"
  
  volatile show_early_output_fn_t show_early_output;
  
@@@ -52,8 -51,7 +52,8 @@@ static void mark_blob_uninteresting(str
        blob->object.flags |= UNINTERESTING;
  }
  
 -static void mark_tree_contents_uninteresting(struct tree *tree)
 +static void mark_tree_contents_uninteresting(struct repository *r,
 +                                           struct tree *tree)
  {
        struct tree_desc desc;
        struct name_entry entry;
        while (tree_entry(&desc, &entry)) {
                switch (object_type(entry.mode)) {
                case OBJ_TREE:
 -                      mark_tree_uninteresting(lookup_tree(the_repository, entry.oid));
 +                      mark_tree_uninteresting(r, lookup_tree(r, entry.oid));
                        break;
                case OBJ_BLOB:
 -                      mark_blob_uninteresting(lookup_blob(the_repository, entry.oid));
 +                      mark_blob_uninteresting(lookup_blob(r, entry.oid));
                        break;
                default:
                        /* Subproject commit - not in this repository */
@@@ -83,7 -81,7 +83,7 @@@
        free_tree_buffer(tree);
  }
  
 -void mark_tree_uninteresting(struct tree *tree)
 +void mark_tree_uninteresting(struct repository *r, struct tree *tree)
  {
        struct object *obj;
  
@@@ -94,7 -92,7 +94,7 @@@
        if (obj->flags & UNINTERESTING)
                return;
        obj->flags |= UNINTERESTING;
 -      mark_tree_contents_uninteresting(tree);
 +      mark_tree_contents_uninteresting(r, tree);
  }
  
  struct commit_stack {
@@@ -177,7 -175,6 +177,6 @@@ static void add_pending_object_with_pat
                strbuf_release(&buf);
                return; /* do not add the commit itself */
        }
-       obj->flags |= USER_GIVEN;
        add_object_array_with_path(obj, name, &revs->pending, mode, path);
  }
  
@@@ -200,7 -197,7 +199,7 @@@ void add_head_to_pending(struct rev_inf
        struct object *obj;
        if (get_oid("HEAD", &oid))
                return;
 -      obj = parse_object(the_repository, &oid);
 +      obj = parse_object(revs->repo, &oid);
        if (!obj)
                return;
        add_pending_object(revs, obj, "HEAD");
@@@ -212,7 -209,7 +211,7 @@@ static struct object *get_reference(str
  {
        struct object *object;
  
 -      object = parse_object(the_repository, oid);
 +      object = parse_object(revs->repo, oid);
        if (!object) {
                if (revs->ignore_missing)
                        return object;
@@@ -249,7 -246,7 +248,7 @@@ static struct commit *handle_commit(str
                        add_pending_object(revs, object, tag->tag);
                if (!tag->tagged)
                        die("bad tag");
 -              object = parse_object(the_repository, &tag->tagged->oid);
 +              object = parse_object(revs->repo, &tag->tagged->oid);
                if (!object) {
                        if (revs->ignore_missing_links || (flags & UNINTERESTING))
                                return NULL;
                if (!revs->tree_objects)
                        return NULL;
                if (flags & UNINTERESTING) {
 -                      mark_tree_contents_uninteresting(tree);
 +                      mark_tree_contents_uninteresting(revs->repo, tree);
                        return NULL;
                }
                add_pending_object_with_path(revs, object, name, mode, path);
@@@ -879,7 -876,7 +878,7 @@@ static void cherry_pick_list(struct com
                return;
  
        left_first = left_count < right_count;
 -      init_patch_ids(&ids);
 +      init_patch_ids(revs->repo, &ids);
        ids.diffopts.pathspec = revs->diffopt.pathspec;
  
        /* Compute patch-ids for one side */
@@@ -1255,7 -1252,7 +1254,7 @@@ static void handle_one_reflog_commit(st
  {
        struct all_refs_cb *cb = cb_data;
        if (!is_null_oid(oid)) {
 -              struct object *o = parse_object(the_repository, oid);
 +              struct object *o = parse_object(cb->all_revs->repo, oid);
                if (o) {
                        o->flags |= cb->all_flags;
                        /* ??? CMDLINEFLAGS ??? */
@@@ -1314,7 -1311,7 +1313,7 @@@ void add_reflogs_to_pending(struct rev_
  
        cb.all_revs = revs;
        cb.all_flags = flags;
 -      cb.refs = get_main_ref_store(the_repository);
 +      cb.refs = get_main_ref_store(revs->repo);
        for_each_reflog(handle_one_reflog, &cb);
  
        if (!revs->single_worktree)
@@@ -1328,7 -1325,7 +1327,7 @@@ static void add_cache_tree(struct cache
        int i;
  
        if (it->entry_count >= 0) {
 -              struct tree *tree = lookup_tree(the_repository, &it->oid);
 +              struct tree *tree = lookup_tree(revs->repo, &it->oid);
                add_pending_object_with_path(revs, &tree->object, "",
                                             040000, path->buf);
        }
@@@ -1354,7 -1351,7 +1353,7 @@@ static void do_add_index_objects_to_pen
                if (S_ISGITLINK(ce->ce_mode))
                        continue;
  
 -              blob = lookup_blob(the_repository, &ce->oid);
 +              blob = lookup_blob(revs->repo, &ce->oid);
                if (!blob)
                        die("unable to add index blob to traversal");
                add_pending_object_with_path(revs, &blob->object, "",
@@@ -1372,8 -1369,8 +1371,8 @@@ void add_index_objects_to_pending(struc
  {
        struct worktree **worktrees, **p;
  
 -      read_cache();
 -      do_add_index_objects_to_pending(revs, &the_index);
 +      read_index(revs->repo->index);
 +      do_add_index_objects_to_pending(revs, revs->repo->index);
  
        if (revs->single_worktree)
                return;
@@@ -1441,13 -1438,10 +1440,13 @@@ static int add_parents_only(struct rev_
        return 1;
  }
  
 -void init_revisions(struct rev_info *revs, const char *prefix)
 +void repo_init_revisions(struct repository *r,
 +                       struct rev_info *revs,
 +                       const char *prefix)
  {
        memset(revs, 0, sizeof(*revs));
  
 +      revs->repo = r;
        revs->abbrev = DEFAULT_ABBREV;
        revs->ignore_merges = 1;
        revs->simplify_history = 1;
        revs->commit_format = CMIT_FMT_DEFAULT;
        revs->expand_tabs_in_log_default = 8;
  
 -      init_grep_defaults();
 -      grep_init(&revs->grep_filter, prefix);
 +      init_grep_defaults(revs->repo);
 +      grep_init(&revs->grep_filter, revs->repo, prefix);
        revs->grep_filter.status_only = 1;
  
 -      diff_setup(&revs->diffopt);
 +      repo_diff_setup(revs->repo, &revs->diffopt);
        if (prefix && !revs->diffopt.prefix) {
                revs->diffopt.prefix = prefix;
                revs->diffopt.prefix_length = strlen(prefix);
@@@ -1501,7 -1495,6 +1500,7 @@@ static void prepare_show_merge(struct r
        struct object_id oid;
        const char **prune = NULL;
        int i, prune_num = 1; /* counting terminating NULL */
 +      struct index_state *istate = revs->repo->index;
  
        if (get_oid("HEAD", &oid))
                die("--merge without HEAD?");
        free_commit_list(bases);
        head->object.flags |= SYMMETRIC_LEFT;
  
 -      if (!active_nr)
 -              read_cache();
 -      for (i = 0; i < active_nr; i++) {
 -              const struct cache_entry *ce = active_cache[i];
 +      if (!istate->cache_nr)
 +              read_index(istate);
 +      for (i = 0; i < istate->cache_nr; i++) {
 +              const struct cache_entry *ce = istate->cache[i];
                if (!ce_stage(ce))
                        continue;
 -              if (ce_path_match(ce, &revs->prune_data, NULL)) {
 +              if (ce_path_match(istate, ce, &revs->prune_data, NULL)) {
                        prune_num++;
                        REALLOC_ARRAY(prune, prune_num);
                        prune[prune_num-2] = ce->name;
                        prune[prune_num-1] = NULL;
                }
 -              while ((i+1 < active_nr) &&
 -                     ce_same_name(ce, active_cache[i+1]))
 +              while ((i+1 < istate->cache_nr) &&
 +                     ce_same_name(ce, istate->cache[i+1]))
                        i++;
        }
        clear_pathspec(&revs->prune_data);
@@@ -1587,8 -1580,8 +1586,8 @@@ static int handle_dotdot_1(const char *
                *dotdot = '\0';
        }
  
 -      a_obj = parse_object(the_repository, &a_oid);
 -      b_obj = parse_object(the_repository, &b_oid);
 +      a_obj = parse_object(revs->repo, &a_oid);
 +      b_obj = parse_object(revs->repo, &b_oid);
        if (!a_obj || !b_obj)
                return dotdot_missing(arg, dotdot, revs, symmetric);
  
                struct commit *a, *b;
                struct commit_list *exclude;
  
 -              a = lookup_commit_reference(the_repository, &a_obj->oid);
 -              b = lookup_commit_reference(the_repository, &b_obj->oid);
 +              a = lookup_commit_reference(revs->repo, &a_obj->oid);
 +              b = lookup_commit_reference(revs->repo, &b_obj->oid);
                if (!a || !b)
                        return dotdot_missing(arg, dotdot, revs, symmetric);
  
@@@ -2210,7 -2203,7 +2209,7 @@@ static int handle_revision_pseudo_opt(c
                        BUG("--single-worktree cannot be used together with submodule");
                refs = get_submodule_ref_store(submodule);
        } else
 -              refs = get_main_ref_store(the_repository);
 +              refs = get_main_ref_store(revs->repo);
  
        /*
         * NOTE!
@@@ -2324,7 -2317,7 +2323,7 @@@ static void NORETURN diagnose_missing_d
   */
  int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *opt)
  {
 -      int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0, revarg_opt;
 +      int i, flags, left, seen_dashdash, got_rev_arg = 0, revarg_opt;
        struct argv_array prune_data = ARGV_ARRAY_INIT;
        const char *submodule = NULL;
  
        revarg_opt = opt ? opt->revarg_opt : 0;
        if (seen_dashdash)
                revarg_opt |= REVARG_CANNOT_BE_FILENAME;
 -      read_from_stdin = 0;
        for (left = i = 1; i < argc; i++) {
                const char *arg = argv[i];
                if (*arg == '-') {
                                        argv[left++] = arg;
                                        continue;
                                }
 -                              if (read_from_stdin++)
 +                              if (revs->read_from_stdin++)
                                        die("--stdin given twice?");
                                read_revisions_from_stdin(revs, &prune_data);
                                continue;
@@@ -2890,10 -2884,9 +2889,10 @@@ void reset_revision_walk(void
  static int mark_uninteresting(const struct object_id *oid,
                              struct packed_git *pack,
                              uint32_t pos,
 -                            void *unused)
 +                            void *cb)
  {
 -      struct object *o = parse_object(the_repository, oid);
 +      struct rev_info *revs = cb;
 +      struct object *o = parse_object(revs->repo, oid);
        o->flags |= UNINTERESTING | SEEN;
        return 0;
  }
@@@ -2926,7 -2919,7 +2925,7 @@@ int prepare_revision_walk(struct rev_in
                revs->treesame.name = "treesame";
  
        if (revs->exclude_promisor_objects) {
 -              for_each_packed_object(mark_uninteresting, NULL,
 +              for_each_packed_object(mark_uninteresting, revs,
                                       FOR_EACH_OBJECT_PROMISOR_ONLY);
        }
  
@@@ -3244,7 -3237,7 +3243,7 @@@ static void track_linear(struct rev_inf
                struct commit_list *p;
                for (p = revs->previous_parents; p; p = p->next)
                        if (p->item == NULL || /* first commit */
 -                          !oidcmp(&p->item->object.oid, &commit->object.oid))
 +                          oideq(&p->item->object.oid, &commit->object.oid))
                                break;
                revs->linear = p != NULL;
        }
diff --combined revision.h
index bc30a3023e20542480b7d771dd1e4001c518f30b,4dc45bb9ad03c54352ec347c1cc0396625b157a9..1cd0c4b200887e6b73d6a2473712bb5832f7e079
@@@ -1,7 -1,6 +1,7 @@@
  #ifndef REVISION_H
  #define REVISION_H
  
 +#include "commit.h"
  #include "parse-options.h"
  #include "grep.h"
  #include "notes.h"
  #define SYMMETRIC_LEFT        (1u<<8)
  #define PATCHSAME     (1u<<9)
  #define BOTTOM                (1u<<10)
- #define USER_GIVEN    (1u<<25) /* given directly by the user */
+ /*
+  * Indicates object was reached by traversal. i.e. not given by user on
+  * command-line or stdin.
+  * NEEDSWORK: NOT_USER_GIVEN doesn't apply to commits because we only support
+  * filtering trees and blobs, but it may be useful to support filtering commits
+  * in the future.
+  */
+ #define NOT_USER_GIVEN        (1u<<25)
  #define TRACK_LINEAR  (1u<<26)
- #define ALL_REV_FLAGS (((1u<<11)-1) | USER_GIVEN | TRACK_LINEAR)
+ #define ALL_REV_FLAGS (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR)
  
  #define DECORATE_SHORT_REFS   1
  #define DECORATE_FULL_REFS    2
  
 -struct rev_info;
  struct log_info;
 +struct repository;
 +struct rev_info;
  struct string_list;
  struct saved_parents;
  define_shared_commit_slab(revision_sources, char *);
@@@ -61,7 -66,6 +68,7 @@@ struct rev_info 
        /* Starting list */
        struct commit_list *commits;
        struct object_array pending;
 +      struct repository *repo;
  
        /* Parents of shown commits */
        struct object_array boundary_commits;
         */
        int rev_input_given;
  
 +      /*
 +       * Whether we read from stdin due to the --stdin option.
 +       */
 +      int read_from_stdin;
 +
        /* topo-sort */
        enum rev_sort_order sort_order;
  
                        line_level_traverse:1,
                        tree_blobs_in_commit_order:1,
  
+                       /*
+                        * Blobs are shown without regard for their existence.
+                        * But not so for trees: unless exclude_promisor_objects
+                        * is set and the tree in question is a promisor object;
+                        * OR ignore_missing_links is set, the revision walker
+                        * dies with a "bad tree object HASH" message when
+                        * encountering a missing tree. For callers that can
+                        * handle missing trees and want them to be filterable
+                        * and showable, set this to true. The revision walker
+                        * will filter and show such a missing tree as usual,
+                        * but will not attempt to recurse into this tree
+                        * object.
+                        */
+                       do_not_die_on_missing_tree:1,
                        /* for internal use only */
                        exclude_promisor_objects:1;
  
        /* notes-specific options: which refs to show */
        struct display_notes_opt notes_opt;
  
 +      /* interdiff */
 +      const struct object_id *idiff_oid1;
 +      const struct object_id *idiff_oid2;
 +      const char *idiff_title;
 +
 +      /* range-diff */
 +      const char *rdiff1;
 +      const char *rdiff2;
 +      int creation_factor;
 +      const char *rdiff_title;
 +
        /* commit counts */
        int count_left;
        int count_right;
        struct revision_sources *sources;
  };
  
 -extern int ref_excluded(struct string_list *, const char *path);
 +int ref_excluded(struct string_list *, const char *path);
  void clear_ref_exclusion(struct string_list **);
  void add_ref_exclusion(struct string_list **, const char *exclude);
  
@@@ -266,49 -269,44 +288,49 @@@ extern volatile show_early_output_fn_t 
  struct setup_revision_opt {
        const char *def;
        void (*tweak)(struct rev_info *, struct setup_revision_opt *);
 -      const char *submodule;
 +      const char *submodule;  /* TODO: drop this and use rev_info->repo */
        int assume_dashdash;
        unsigned revarg_opt;
  };
  
 -extern void init_revisions(struct rev_info *revs, const char *prefix);
 -extern int setup_revisions(int argc, const char **argv, struct rev_info *revs,
 -                         struct setup_revision_opt *);
 -extern void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
 -                             const struct option *options,
 -                             const char * const usagestr[]);
 +#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
 +#define init_revisions(revs, prefix) repo_init_revisions(the_repository, revs, prefix)
 +#endif
 +void repo_init_revisions(struct repository *r,
 +                       struct rev_info *revs,
 +                       const char *prefix);
 +int setup_revisions(int argc, const char **argv, struct rev_info *revs,
 +                  struct setup_revision_opt *);
 +void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
 +                      const struct option *options,
 +                      const char * const usagestr[]);
  #define REVARG_CANNOT_BE_FILENAME 01
  #define REVARG_COMMITTISH 02
 -extern int handle_revision_arg(const char *arg, struct rev_info *revs,
 -                             int flags, unsigned revarg_opt);
 +int handle_revision_arg(const char *arg, struct rev_info *revs,
 +                      int flags, unsigned revarg_opt);
  
 -extern void reset_revision_walk(void);
 -extern int prepare_revision_walk(struct rev_info *revs);
 -extern struct commit *get_revision(struct rev_info *revs);
 -extern char *get_revision_mark(const struct rev_info *revs,
 -                             const struct commit *commit);
 -extern void put_revision_mark(const struct rev_info *revs,
 -                            const struct commit *commit);
 +void reset_revision_walk(void);
 +int prepare_revision_walk(struct rev_info *revs);
 +struct commit *get_revision(struct rev_info *revs);
 +char *get_revision_mark(const struct rev_info *revs,
 +                      const struct commit *commit);
 +void put_revision_mark(const struct rev_info *revs,
 +                     const struct commit *commit);
  
 -extern void mark_parents_uninteresting(struct commit *commit);
 -extern void mark_tree_uninteresting(struct tree *tree);
 +void mark_parents_uninteresting(struct commit *commit);
 +void mark_tree_uninteresting(struct repository *r, struct tree *tree);
  
 -extern void show_object_with_name(FILE *, struct object *, const char *);
 +void show_object_with_name(FILE *, struct object *, const char *);
  
 -extern void add_pending_object(struct rev_info *revs,
 -                             struct object *obj, const char *name);
 -extern void add_pending_oid(struct rev_info *revs,
 -                          const char *name, const struct object_id *oid,
 -                          unsigned int flags);
 +void add_pending_object(struct rev_info *revs,
 +                      struct object *obj, const char *name);
 +void add_pending_oid(struct rev_info *revs,
 +                   const char *name, const struct object_id *oid,
 +                   unsigned int flags);
  
 -extern void add_head_to_pending(struct rev_info *);
 -extern void add_reflogs_to_pending(struct rev_info *, unsigned int flags);
 -extern void add_index_objects_to_pending(struct rev_info *, unsigned int flags);
 +void add_head_to_pending(struct rev_info *);
 +void add_reflogs_to_pending(struct rev_info *, unsigned int flags);
 +void add_index_objects_to_pending(struct rev_info *, unsigned int flags);
  
  enum commit_action {
        commit_ignore,
        commit_error
  };
  
 -extern enum commit_action get_commit_action(struct rev_info *revs,
 -                                          struct commit *commit);
 -extern enum commit_action simplify_commit(struct rev_info *revs,
 -                                        struct commit *commit);
 +enum commit_action get_commit_action(struct rev_info *revs,
 +                                   struct commit *commit);
 +enum commit_action simplify_commit(struct rev_info *revs,
 +                                 struct commit *commit);
  
  enum rewrite_result {
        rewrite_one_ok,
  
  typedef enum rewrite_result (*rewrite_parent_fn_t)(struct rev_info *revs, struct commit **pp);
  
 -extern int rewrite_parents(struct rev_info *revs, struct commit *commit,
 -      rewrite_parent_fn_t rewrite_parent);
 +int rewrite_parents(struct rev_info *revs,
 +                  struct commit *commit,
 +                  rewrite_parent_fn_t rewrite_parent);
  
  /*
   * The log machinery saves the original parent list so that
   * get_saved_parents() will transparently return commit->parents if
   * history simplification is off.
   */
 -extern struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit);
 +struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit);
  
  #endif
diff --combined t/t0410-partial-clone.sh
index c521d7d6c61f3e3c76d5c4d51440ae114004a362,2f4ea487a4304d0282f655aca6f1013d075489ad..ba3887f178b03a71b1ee1d2149429e2ee76814a6
@@@ -170,59 -170,6 +170,59 @@@ test_expect_success 'fetching of missin
        git verify-pack --verbose "$IDX" | grep "$HASH"
  '
  
 +test_expect_success 'fetching of missing objects works with ref-in-want enabled' '
 +      # ref-in-want requires protocol version 2
 +      git -C server config protocol.version 2 &&
 +      git -C server config uploadpack.allowrefinwant 1 &&
 +      git -C repo config protocol.version 2 &&
 +
 +      rm -rf repo/.git/objects/* &&
 +      rm -f trace &&
 +      GIT_TRACE_PACKET="$(pwd)/trace" git -C repo cat-file -p "$HASH" &&
 +      grep "git< fetch=.*ref-in-want" trace
 +'
 +
 +test_expect_success 'fetching of missing blobs works' '
 +      rm -rf server repo &&
 +      test_create_repo server &&
 +      test_commit -C server foo &&
 +      git -C server repack -a -d --write-bitmap-index &&
 +
 +      git clone "file://$(pwd)/server" repo &&
 +      git hash-object repo/foo.t >blobhash &&
 +      rm -rf repo/.git/objects/* &&
 +
 +      git -C server config uploadpack.allowanysha1inwant 1 &&
 +      git -C server config uploadpack.allowfilter 1 &&
 +      git -C repo config core.repositoryformatversion 1 &&
 +      git -C repo config extensions.partialclone "origin" &&
 +
 +      git -C repo cat-file -p $(cat blobhash)
 +'
 +
 +test_expect_success 'fetching of missing trees does not fetch blobs' '
 +      rm -rf server repo &&
 +      test_create_repo server &&
 +      test_commit -C server foo &&
 +      git -C server repack -a -d --write-bitmap-index &&
 +
 +      git clone "file://$(pwd)/server" repo &&
 +      git -C repo rev-parse foo^{tree} >treehash &&
 +      git hash-object repo/foo.t >blobhash &&
 +      rm -rf repo/.git/objects/* &&
 +
 +      git -C server config uploadpack.allowanysha1inwant 1 &&
 +      git -C server config uploadpack.allowfilter 1 &&
 +      git -C repo config core.repositoryformatversion 1 &&
 +      git -C repo config extensions.partialclone "origin" &&
 +      git -C repo cat-file -p $(cat treehash) &&
 +
 +      # Ensure that the tree, but not the blob, is fetched
 +      git -C repo rev-list --objects --missing=print $(cat treehash) >objects &&
 +      grep "^$(cat treehash)" objects &&
 +      grep "^[?]$(cat blobhash)" objects
 +'
 +
  test_expect_success 'rev-list stops traversal at missing and promised commit' '
        rm -rf repo &&
        test_create_repo repo &&
  
        git -C repo config core.repositoryformatversion 1 &&
        git -C repo config extensions.partialclone "arbitrary string" &&
 -      git -C repo rev-list --exclude-promisor-objects --objects bar >out &&
 +      GIT_TEST_COMMIT_GRAPH=0 git -C repo rev-list --exclude-promisor-objects --objects bar >out &&
        grep $(git -C repo rev-parse bar) out &&
        ! grep $FOO out
  '
  
+ test_expect_success 'missing tree objects with --missing=allow-promisor and --exclude-promisor-objects' '
+       rm -rf repo &&
+       test_create_repo repo &&
+       test_commit -C repo foo &&
+       test_commit -C repo bar &&
+       test_commit -C repo baz &&
+       promise_and_delete $(git -C repo rev-parse bar^{tree}) &&
+       promise_and_delete $(git -C repo rev-parse foo^{tree}) &&
+       git -C repo config core.repositoryformatversion 1 &&
+       git -C repo config extensions.partialclone "arbitrary string" &&
+       git -C repo rev-list --missing=allow-promisor --objects HEAD >objs 2>rev_list_err &&
+       test_must_be_empty rev_list_err &&
+       # 3 commits, 3 blobs, and 1 tree
+       test_line_count = 7 objs &&
+       # Do the same for --exclude-promisor-objects, but with all trees gone.
+       promise_and_delete $(git -C repo rev-parse baz^{tree}) &&
+       git -C repo rev-list --exclude-promisor-objects --objects HEAD >objs 2>rev_list_err &&
+       test_must_be_empty rev_list_err &&
+       # 3 commits, no blobs or trees
+       test_line_count = 3 objs
+ '
+ test_expect_success 'missing non-root tree object and rev-list' '
+       rm -rf repo &&
+       test_create_repo repo &&
+       mkdir repo/dir &&
+       echo foo >repo/dir/foo &&
+       git -C repo add dir/foo &&
+       git -C repo commit -m "commit dir/foo" &&
+       promise_and_delete $(git -C repo rev-parse HEAD:dir) &&
+       git -C repo config core.repositoryformatversion 1 &&
+       git -C repo config extensions.partialclone "arbitrary string" &&
+       git -C repo rev-list --missing=allow-any --objects HEAD >objs 2>rev_list_err &&
+       test_must_be_empty rev_list_err &&
+       # 1 commit and 1 tree
+       test_line_count = 2 objs
+ '
  test_expect_success 'rev-list stops traversal at missing and promised tree' '
        rm -rf repo &&
        test_create_repo repo &&
@@@ -324,91 -316,28 +369,91 @@@ test_expect_success 'rev-list accepts m
        git -C repo rev-list --exclude-promisor-objects --objects "$COMMIT" "$TREE" "$BLOB"
  '
  
 -test_expect_success 'gc does not repack promisor objects' '
 +test_expect_success 'gc repacks promisor objects separately from non-promisor objects' '
        rm -rf repo &&
        test_create_repo repo &&
 -      test_commit -C repo my_commit &&
 +      test_commit -C repo one &&
 +      test_commit -C repo two &&
  
 -      TREE_HASH=$(git -C repo rev-parse HEAD^{tree}) &&
 -      HASH=$(printf "$TREE_HASH\n" | pack_as_from_promisor) &&
 +      TREE_ONE=$(git -C repo rev-parse one^{tree}) &&
 +      printf "$TREE_ONE\n" | pack_as_from_promisor &&
 +      TREE_TWO=$(git -C repo rev-parse two^{tree}) &&
 +      printf "$TREE_TWO\n" | pack_as_from_promisor &&
  
        git -C repo config core.repositoryformatversion 1 &&
        git -C repo config extensions.partialclone "arbitrary string" &&
        git -C repo gc &&
  
 -      # Ensure that the promisor packfile still exists, and remove it
 -      test -e repo/.git/objects/pack/pack-$HASH.pack &&
 -      rm repo/.git/objects/pack/pack-$HASH.* &&
 -
 -      # Ensure that the single other pack contains the commit, but not the tree
 +      # Ensure that exactly one promisor packfile exists, and that it
 +      # contains the trees but not the commits
 +      ls repo/.git/objects/pack/pack-*.promisor >promisorlist &&
 +      test_line_count = 1 promisorlist &&
 +      PROMISOR_PACKFILE=$(sed "s/.promisor/.pack/" <promisorlist) &&
 +      git verify-pack $PROMISOR_PACKFILE -v >out &&
 +      grep "$TREE_ONE" out &&
 +      grep "$TREE_TWO" out &&
 +      ! grep "$(git -C repo rev-parse one)" out &&
 +      ! grep "$(git -C repo rev-parse two)" out &&
 +
 +      # Remove the promisor packfile and associated files
 +      rm $(sed "s/.promisor//" <promisorlist).* &&
 +
 +      # Ensure that the single other pack contains the commits, but not the
 +      # trees
        ls repo/.git/objects/pack/pack-*.pack >packlist &&
        test_line_count = 1 packlist &&
        git verify-pack repo/.git/objects/pack/pack-*.pack -v >out &&
 -      grep "$(git -C repo rev-parse HEAD)" out &&
 -      ! grep "$TREE_HASH" out
 +      grep "$(git -C repo rev-parse one)" out &&
 +      grep "$(git -C repo rev-parse two)" out &&
 +      ! grep "$TREE_ONE" out &&
 +      ! grep "$TREE_TWO" out
 +'
 +
 +test_expect_success 'gc does not repack promisor objects if there are none' '
 +      rm -rf repo &&
 +      test_create_repo repo &&
 +      test_commit -C repo one &&
 +
 +      git -C repo config core.repositoryformatversion 1 &&
 +      git -C repo config extensions.partialclone "arbitrary string" &&
 +      git -C repo gc &&
 +
 +      # Ensure that only one pack exists
 +      ls repo/.git/objects/pack/pack-*.pack >packlist &&
 +      test_line_count = 1 packlist
 +'
 +
 +repack_and_check () {
 +      rm -rf repo2 &&
 +      cp -r repo repo2 &&
 +      git -C repo2 repack $1 -d &&
 +      git -C repo2 fsck &&
 +
 +      git -C repo2 cat-file -e $2 &&
 +      git -C repo2 cat-file -e $3
 +}
 +
 +test_expect_success 'repack -d does not irreversibly delete promisor objects' '
 +      rm -rf repo &&
 +      test_create_repo repo &&
 +      git -C repo config core.repositoryformatversion 1 &&
 +      git -C repo config extensions.partialclone "arbitrary string" &&
 +
 +      git -C repo commit --allow-empty -m one &&
 +      git -C repo commit --allow-empty -m two &&
 +      git -C repo commit --allow-empty -m three &&
 +      git -C repo commit --allow-empty -m four &&
 +      ONE=$(git -C repo rev-parse HEAD^^^) &&
 +      TWO=$(git -C repo rev-parse HEAD^^) &&
 +      THREE=$(git -C repo rev-parse HEAD^) &&
 +
 +      printf "$TWO\n" | pack_as_from_promisor &&
 +      printf "$THREE\n" | pack_as_from_promisor &&
 +      delete_object repo "$ONE" &&
 +
 +      repack_and_check -a "$TWO" "$THREE" &&
 +      repack_and_check -A "$TWO" "$THREE" &&
 +      repack_and_check -l "$TWO" "$THREE"
  '
  
  test_expect_success 'gc stops traversal when a missing but promised object is reached' '
index 2e718f0bdede397d399399536707743f9f7cff34,d9dccf4d4db8be7f3f727d394c320b3e30b04728..24541ea137517354a8b738bdef34728b49f2850e
@@@ -21,21 -21,17 +21,21 @@@ test_expect_success 'setup r1' 
  
  test_expect_success 'verify blob count in normal packfile' '
        git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 +              >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
        git -C r1 pack-objects --rev --stdout >all.pack <<-EOF &&
        HEAD
        EOF
        git -C r1 index-pack ../all.pack &&
 -      git -C r1 verify-pack -v ../all.pack \
 -              | grep blob \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 -      test_cmp observed expected
 +
 +      git -C r1 verify-pack -v ../all.pack >verify_result &&
 +      grep blob verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify blob:none packfile has no blobs' '
        HEAD
        EOF
        git -C r1 index-pack ../filter.pack &&
 -      git -C r1 verify-pack -v ../filter.pack \
 -              | grep blob \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 +
 +      git -C r1 verify-pack -v ../filter.pack >verify_result &&
 +      grep blob verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
        nr=$(wc -l <observed) &&
        test 0 -eq $nr
  '
  
  test_expect_success 'verify normal and blob:none packfiles have same commits/trees' '
 -      git -C r1 verify-pack -v ../all.pack \
 -              | grep -E "commit|tree" \
 -              | awk -f print_1.awk \
 -              | sort >expected &&
 -      git -C r1 verify-pack -v ../filter.pack \
 -              | grep -E "commit|tree" \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 -      test_cmp observed expected
 +      git -C r1 verify-pack -v ../all.pack >verify_result &&
 +      grep -E "commit|tree" verify_result |
 +      awk -f print_1.awk |
 +      sort >expected &&
 +
 +      git -C r1 verify-pack -v ../filter.pack >verify_result &&
 +      grep -E "commit|tree" verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
+ test_expect_success 'get an error for missing tree object' '
+       git init r5 &&
+       echo foo >r5/foo &&
+       git -C r5 add foo &&
+       git -C r5 commit -m "foo" &&
+       del=$(git -C r5 rev-parse HEAD^{tree} | sed "s|..|&/|") &&
+       rm r5/.git/objects/$del &&
+       test_must_fail git -C r5 pack-objects --rev --stdout 2>bad_tree <<-EOF &&
+       HEAD
+       EOF
+       grep "bad tree object" bad_tree
+ '
+ test_expect_success 'setup for tests of tree:0' '
+       mkdir r1/subtree &&
+       echo "This is a file in a subtree" >r1/subtree/file &&
+       git -C r1 add subtree/file &&
+       git -C r1 commit -m subtree
+ '
+ test_expect_success 'verify tree:0 packfile has no blobs or trees' '
+       git -C r1 pack-objects --rev --stdout --filter=tree:0 >commitsonly.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r1 index-pack ../commitsonly.pack &&
+       git -C r1 verify-pack -v ../commitsonly.pack >objs &&
+       ! grep -E "tree|blob" objs
+ '
+ test_expect_success 'grab tree directly when using tree:0' '
+       # We should get the tree specified directly but not its blobs or subtrees.
+       git -C r1 pack-objects --rev --stdout --filter=tree:0 >commitsonly.pack <<-EOF &&
+       HEAD:
+       EOF
+       git -C r1 index-pack ../commitsonly.pack &&
+       git -C r1 verify-pack -v ../commitsonly.pack >objs &&
+       awk "/tree|blob/{print \$1}" objs >trees_and_blobs &&
+       git -C r1 rev-parse HEAD: >expected &&
+       test_cmp expected trees_and_blobs
+ '
  # Test blob:limit=<n>[kmg] filter.
  # We boundary test around the size parameter.  The filter is strictly less than
  # the value, so size 500 and 1000 should have the same results, but 1001 should
@@@ -83,21 -116,18 +124,21 @@@ test_expect_success 'setup r2' 
  '
  
  test_expect_success 'verify blob count in normal packfile' '
 -      git -C r2 ls-files -s large.1000 large.10000 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 +      git -C r2 ls-files -s large.1000 large.10000 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
        git -C r2 pack-objects --rev --stdout >all.pack <<-EOF &&
        HEAD
        EOF
        git -C r2 index-pack ../all.pack &&
 -      git -C r2 verify-pack -v ../all.pack \
 -              | grep blob \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 -      test_cmp observed expected
 +
 +      git -C r2 verify-pack -v ../all.pack >verify_result &&
 +      grep blob verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify blob:limit=500 omits all blobs' '
        HEAD
        EOF
        git -C r2 index-pack ../filter.pack &&
 -      git -C r2 verify-pack -v ../filter.pack \
 -              | grep blob \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 +
 +      git -C r2 verify-pack -v ../filter.pack >verify_result &&
 +      grep blob verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
        nr=$(wc -l <observed) &&
        test 0 -eq $nr
  '
@@@ -120,119 -148,100 +161,119 @@@ test_expect_success 'verify blob:limit=
        HEAD
        EOF
        git -C r2 index-pack ../filter.pack &&
 -      git -C r2 verify-pack -v ../filter.pack \
 -              | grep blob \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 +
 +      git -C r2 verify-pack -v ../filter.pack >verify_result &&
 +      grep blob verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
        nr=$(wc -l <observed) &&
        test 0 -eq $nr
  '
  
  test_expect_success 'verify blob:limit=1001' '
 -      git -C r2 ls-files -s large.1000 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 +      git -C r2 ls-files -s large.1000 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
        git -C r2 pack-objects --rev --stdout --filter=blob:limit=1001 >filter.pack <<-EOF &&
        HEAD
        EOF
        git -C r2 index-pack ../filter.pack &&
 -      git -C r2 verify-pack -v ../filter.pack \
 -              | grep blob \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 -      test_cmp observed expected
 +
 +      git -C r2 verify-pack -v ../filter.pack >verify_result &&
 +      grep blob verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify blob:limit=10001' '
 -      git -C r2 ls-files -s large.1000 large.10000 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 +      git -C r2 ls-files -s large.1000 large.10000 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
        git -C r2 pack-objects --rev --stdout --filter=blob:limit=10001 >filter.pack <<-EOF &&
        HEAD
        EOF
        git -C r2 index-pack ../filter.pack &&
 -      git -C r2 verify-pack -v ../filter.pack \
 -              | grep blob \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 -      test_cmp observed expected
 +
 +      git -C r2 verify-pack -v ../filter.pack >verify_result &&
 +      grep blob verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify blob:limit=1k' '
 -      git -C r2 ls-files -s large.1000 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 +      git -C r2 ls-files -s large.1000 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
        git -C r2 pack-objects --rev --stdout --filter=blob:limit=1k >filter.pack <<-EOF &&
        HEAD
        EOF
        git -C r2 index-pack ../filter.pack &&
 -      git -C r2 verify-pack -v ../filter.pack \
 -              | grep blob \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 -      test_cmp observed expected
 +
 +      git -C r2 verify-pack -v ../filter.pack >verify_result &&
 +      grep blob verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify explicitly specifying oversized blob in input' '
 -      git -C r2 ls-files -s large.1000 large.10000 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 +      git -C r2 ls-files -s large.1000 large.10000 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
        git -C r2 pack-objects --rev --stdout --filter=blob:limit=1k >filter.pack <<-EOF &&
        HEAD
        $(git -C r2 rev-parse HEAD:large.10000)
        EOF
        git -C r2 index-pack ../filter.pack &&
 -      git -C r2 verify-pack -v ../filter.pack \
 -              | grep blob \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 -      test_cmp observed expected
 +
 +      git -C r2 verify-pack -v ../filter.pack >verify_result &&
 +      grep blob verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify blob:limit=1m' '
 -      git -C r2 ls-files -s large.1000 large.10000 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 +      git -C r2 ls-files -s large.1000 large.10000 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
        git -C r2 pack-objects --rev --stdout --filter=blob:limit=1m >filter.pack <<-EOF &&
        HEAD
        EOF
        git -C r2 index-pack ../filter.pack &&
 -      git -C r2 verify-pack -v ../filter.pack \
 -              | grep blob \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 -      test_cmp observed expected
 +
 +      git -C r2 verify-pack -v ../filter.pack >verify_result &&
 +      grep blob verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify normal and blob:limit packfiles have same commits/trees' '
 -      git -C r2 verify-pack -v ../all.pack \
 -              | grep -E "commit|tree" \
 -              | awk -f print_1.awk \
 -              | sort >expected &&
 -      git -C r2 verify-pack -v ../filter.pack \
 -              | grep -E "commit|tree" \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 -      test_cmp observed expected
 +      git -C r2 verify-pack -v ../all.pack >verify_result &&
 +      grep -E "commit|tree" verify_result |
 +      awk -f print_1.awk |
 +      sort >expected &&
 +
 +      git -C r2 verify-pack -v ../filter.pack >verify_result &&
 +      grep -E "commit|tree" verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  # Test sparse:path=<path> filter.
@@@ -257,85 -266,71 +298,85 @@@ test_expect_success 'setup r3' 
  
  test_expect_success 'verify blob count in normal packfile' '
        git -C r3 ls-files -s sparse1 sparse2 dir1/sparse1 dir1/sparse2 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 +              >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
        git -C r3 pack-objects --rev --stdout >all.pack <<-EOF &&
        HEAD
        EOF
        git -C r3 index-pack ../all.pack &&
 -      git -C r3 verify-pack -v ../all.pack \
 -              | grep blob \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 -      test_cmp observed expected
 +
 +      git -C r3 verify-pack -v ../all.pack >verify_result &&
 +      grep blob verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify sparse:path=pattern1' '
 -      git -C r3 ls-files -s dir1/sparse1 dir1/sparse2 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 +      git -C r3 ls-files -s dir1/sparse1 dir1/sparse2 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
        git -C r3 pack-objects --rev --stdout --filter=sparse:path=../pattern1 >filter.pack <<-EOF &&
        HEAD
        EOF
        git -C r3 index-pack ../filter.pack &&
 -      git -C r3 verify-pack -v ../filter.pack \
 -              | grep blob \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 -      test_cmp observed expected
 +
 +      git -C r3 verify-pack -v ../filter.pack >verify_result &&
 +      grep blob verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify normal and sparse:path=pattern1 packfiles have same commits/trees' '
 -      git -C r3 verify-pack -v ../all.pack \
 -              | grep -E "commit|tree" \
 -              | awk -f print_1.awk \
 -              | sort >expected &&
 -      git -C r3 verify-pack -v ../filter.pack \
 -              | grep -E "commit|tree" \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 -      test_cmp observed expected
 +      git -C r3 verify-pack -v ../all.pack >verify_result &&
 +      grep -E "commit|tree" verify_result |
 +      awk -f print_1.awk |
 +      sort >expected &&
 +
 +      git -C r3 verify-pack -v ../filter.pack >verify_result &&
 +      grep -E "commit|tree" verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify sparse:path=pattern2' '
 -      git -C r3 ls-files -s sparse1 dir1/sparse1 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 +      git -C r3 ls-files -s sparse1 dir1/sparse1 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
        git -C r3 pack-objects --rev --stdout --filter=sparse:path=../pattern2 >filter.pack <<-EOF &&
        HEAD
        EOF
        git -C r3 index-pack ../filter.pack &&
 -      git -C r3 verify-pack -v ../filter.pack \
 -              | grep blob \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 -      test_cmp observed expected
 +
 +      git -C r3 verify-pack -v ../filter.pack >verify_result &&
 +      grep blob verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify normal and sparse:path=pattern2 packfiles have same commits/trees' '
 -      git -C r3 verify-pack -v ../all.pack \
 -              | grep -E "commit|tree" \
 -              | awk -f print_1.awk \
 -              | sort >expected &&
 -      git -C r3 verify-pack -v ../filter.pack \
 -              | grep -E "commit|tree" \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 -      test_cmp observed expected
 +      git -C r3 verify-pack -v ../all.pack >verify_result &&
 +      grep -E "commit|tree" verify_result |
 +      awk -f print_1.awk |
 +      sort >expected &&
 +
 +      git -C r3 verify-pack -v ../filter.pack >verify_result &&
 +      grep -E "commit|tree" verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  # Test sparse:oid=<oid-ish> filter.
@@@ -359,58 -354,48 +400,58 @@@ test_expect_success 'setup r4' 
  
  test_expect_success 'verify blob count in normal packfile' '
        git -C r4 ls-files -s pattern sparse1 sparse2 dir1/sparse1 dir1/sparse2 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 +              >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
        git -C r4 pack-objects --rev --stdout >all.pack <<-EOF &&
        HEAD
        EOF
        git -C r4 index-pack ../all.pack &&
 -      git -C r4 verify-pack -v ../all.pack \
 -              | grep blob \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 -      test_cmp observed expected
 +
 +      git -C r4 verify-pack -v ../all.pack >verify_result &&
 +      grep blob verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify sparse:oid=OID' '
 -      git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 +      git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
        oid=$(git -C r4 ls-files -s pattern | awk -f print_2.awk) &&
        git -C r4 pack-objects --rev --stdout --filter=sparse:oid=$oid >filter.pack <<-EOF &&
        HEAD
        EOF
        git -C r4 index-pack ../filter.pack &&
 -      git -C r4 verify-pack -v ../filter.pack \
 -              | grep blob \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 -      test_cmp observed expected
 +
 +      git -C r4 verify-pack -v ../filter.pack >verify_result &&
 +      grep blob verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify sparse:oid=oid-ish' '
 -      git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 +      git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
        git -C r4 pack-objects --rev --stdout --filter=sparse:oid=master:pattern >filter.pack <<-EOF &&
        HEAD
        EOF
        git -C r4 index-pack ../filter.pack &&
 -      git -C r4 verify-pack -v ../filter.pack \
 -              | grep blob \
 -              | awk -f print_1.awk \
 -              | sort >observed &&
 -      test_cmp observed expected
 +
 +      git -C r4 verify-pack -v ../filter.pack >verify_result &&
 +      grep blob verify_result |
 +      awk -f print_1.awk |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  # Delete some loose objects and use pack-objects, but WITHOUT any filtering.
  
  test_expect_success 'setup r1 - delete loose blobs' '
        git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 +              >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
        for id in `cat expected | sed "s|..|&/|"`
        do
                rm r1/.git/objects/$id
diff --combined t/t5616-partial-clone.sh
index 6391437529ca7c4f742d2668fdefc77df1112743,392caa08fd8a3612a3b641a094856e48fcfd5d3f..336f02a41a66b48269e8eb6030d5323cffb8b686
@@@ -34,12 -34,10 +34,12 @@@ test_expect_success 'setup bare clone f
  # confirm partial clone was registered in the local config.
  test_expect_success 'do partial clone 1' '
        git clone --no-checkout --filter=blob:none "file://$(pwd)/srv.bare" pc1 &&
 -      git -C pc1 rev-list HEAD --quiet --objects --missing=print \
 -              | awk -f print_1.awk \
 -              | sed "s/?//" \
 -              | sort >observed.oids &&
 +
 +      git -C pc1 rev-list --quiet --objects --missing=print HEAD >revs &&
 +      awk -f print_1.awk revs |
 +      sed "s/?//" |
 +      sort >observed.oids &&
 +
        test_cmp expect_1.oids observed.oids &&
        test "$(git -C pc1 config --local core.repositoryformatversion)" = "1" &&
        test "$(git -C pc1 config --local extensions.partialclone)" = "origin" &&
  
  # checkout master to force dynamic object fetch of blobs at HEAD.
  test_expect_success 'verify checkout with dynamic object fetch' '
 -      git -C pc1 rev-list HEAD --quiet --objects --missing=print >observed &&
 +      git -C pc1 rev-list --quiet --objects --missing=print HEAD >observed &&
        test_line_count = 4 observed &&
        git -C pc1 checkout master &&
 -      git -C pc1 rev-list HEAD --quiet --objects --missing=print >observed &&
 +      git -C pc1 rev-list --quiet --objects --missing=print HEAD >observed &&
        test_line_count = 0 observed
  '
  
@@@ -74,8 -72,7 +74,8 @@@ test_expect_success 'push new commits t
  # have the new blobs.
  test_expect_success 'partial fetch inherits filter settings' '
        git -C pc1 fetch origin &&
 -      git -C pc1 rev-list master..origin/master --quiet --objects --missing=print >observed &&
 +      git -C pc1 rev-list --quiet --objects --missing=print \
 +              master..origin/master >observed &&
        test_line_count = 5 observed
  '
  
@@@ -83,8 -80,7 +83,8 @@@
  # we should only get 1 new blob (for the file in origin/master).
  test_expect_success 'verify diff causes dynamic object fetch' '
        git -C pc1 diff master..origin/master -- file.1.txt &&
 -      git -C pc1 rev-list master..origin/master --quiet --objects --missing=print >observed &&
 +      git -C pc1 rev-list --quiet --objects --missing=print \
 +               master..origin/master >observed &&
        test_line_count = 4 observed
  '
  
@@@ -93,8 -89,7 +93,8 @@@
  test_expect_success 'verify blame causes dynamic object fetch' '
        git -C pc1 blame origin/master -- file.1.txt >observed.blame &&
        test_cmp expect.blame observed.blame &&
 -      git -C pc1 rev-list master..origin/master --quiet --objects --missing=print >observed &&
 +      git -C pc1 rev-list --quiet --objects --missing=print \
 +              master..origin/master >observed &&
        test_line_count = 0 observed
  '
  
@@@ -114,8 -109,7 +114,8 @@@ test_expect_success 'push new commits t
  # Verify we have all the new blobs.
  test_expect_success 'override inherited filter-spec using --no-filter' '
        git -C pc1 fetch --no-filter origin &&
 -      git -C pc1 rev-list master..origin/master --quiet --objects --missing=print >observed &&
 +      git -C pc1 rev-list --quiet --objects --missing=print \
 +              master..origin/master >observed &&
        test_line_count = 0 observed
  '
  
@@@ -136,22 -130,16 +136,22 @@@ test_expect_success 'push new commits t
  # perhaps combined with a command in dry-run mode.
  test_expect_success 'manual prefetch of missing objects' '
        git -C pc1 fetch --filter=blob:none origin &&
 -      git -C pc1 rev-list master..origin/master --quiet --objects --missing=print \
 -              | awk -f print_1.awk \
 -              | sed "s/?//" \
 -              | sort >observed.oids &&
 +
 +      git -C pc1 rev-list --quiet --objects --missing=print \
 +               master..origin/master >revs &&
 +      awk -f print_1.awk revs |
 +      sed "s/?//" |
 +      sort >observed.oids &&
 +
        test_line_count = 6 observed.oids &&
        git -C pc1 fetch-pack --stdin "file://$(pwd)/srv.bare" <observed.oids &&
 -      git -C pc1 rev-list master..origin/master --quiet --objects --missing=print \
 -              | awk -f print_1.awk \
 -              | sed "s/?//" \
 -              | sort >observed.oids &&
 +
 +      git -C pc1 rev-list --quiet --objects --missing=print \
 +              master..origin/master >revs &&
 +      awk -f print_1.awk revs |
 +      sed "s/?//" |
 +      sort >observed.oids &&
 +
        test_line_count = 0 observed.oids
  '
  
@@@ -166,6 -154,48 +166,48 @@@ test_expect_success 'partial clone wit
        grep "git index-pack.*--fsck-objects" trace
  '
  
+ test_expect_success 'use fsck before and after manually fetching a missing subtree' '
+       # push new commit so server has a subtree
+       mkdir src/dir &&
+       echo "in dir" >src/dir/file.txt &&
+       git -C src add dir/file.txt &&
+       git -C src commit -m "file in dir" &&
+       git -C src push -u srv master &&
+       SUBTREE=$(git -C src rev-parse HEAD:dir) &&
+       rm -rf dst &&
+       git clone --no-checkout --filter=tree:0 "file://$(pwd)/srv.bare" dst &&
+       git -C dst fsck &&
+       # Make sure we only have commits, and all trees and blobs are missing.
+       git -C dst rev-list --missing=allow-any --objects master \
+               >fetched_objects &&
+       awk -f print_1.awk fetched_objects |
+       xargs -n1 git -C dst cat-file -t >fetched_types &&
+       sort -u fetched_types >unique_types.observed &&
+       echo commit >unique_types.expected &&
+       test_cmp unique_types.expected unique_types.observed &&
+       # Auto-fetch a tree with cat-file.
+       git -C dst cat-file -p $SUBTREE >tree_contents &&
+       grep file.txt tree_contents &&
+       # fsck still works after an auto-fetch of a tree.
+       git -C dst fsck &&
+       # Auto-fetch all remaining trees and blobs with --missing=error
+       git -C dst rev-list --missing=error --objects master >fetched_objects &&
+       test_line_count = 70 fetched_objects &&
+       awk -f print_1.awk fetched_objects |
+       xargs -n1 git -C dst cat-file -t >fetched_types &&
+       sort -u fetched_types >unique_types.observed &&
+       test_write_lines blob commit tree >unique_types.expected &&
+       test_cmp unique_types.expected unique_types.observed
+ '
  test_expect_success 'partial clone fetches blobs pointed to by refs even if normally filtered out' '
        rm -rf src dst &&
        git init src &&
        git -C dst fsck
  '
  
 +test_expect_success 'fetch what is specified on CLI even if already promised' '
 +      rm -rf src dst.git &&
 +      git init src &&
 +      test_commit -C src foo &&
 +      test_config -C src uploadpack.allowfilter 1 &&
 +      test_config -C src uploadpack.allowanysha1inwant 1 &&
 +
 +      git hash-object --stdin <src/foo.t >blob &&
 +
 +      git clone --bare --filter=blob:none "file://$(pwd)/src" dst.git &&
 +      git -C dst.git rev-list --objects --quiet --missing=print HEAD >missing_before &&
 +      grep "?$(cat blob)" missing_before &&
 +      git -C dst.git fetch origin $(cat blob) &&
 +      git -C dst.git rev-list --objects --quiet --missing=print HEAD >missing_after &&
 +      ! grep "?$(cat blob)" missing_after
 +'
 +
  . "$TEST_DIRECTORY"/lib-httpd.sh
  start_httpd
  
@@@ -223,7 -236,7 +265,7 @@@ test_expect_success 'upon cloning, chec
  
        # Craft a packfile not including that blob.
        git -C "$SERVER" rev-parse HEAD |
 -              git -C "$SERVER" pack-objects --stdout >incomplete.pack &&
 +      git -C "$SERVER" pack-objects --stdout >incomplete.pack &&
  
        # Replace the existing packfile with the crafted one. The protocol
        # requires that the packfile be sent in sideband 1, hence the extra
index 53975c572465314957aaef360dea040a5be2db22,c6aae93b572cdc7108d7201832bb46ad4b968a84..eb32505a6ef8804f4f7762ce0332f00beeacbe99
@@@ -21,31 -21,36 +21,43 @@@ test_expect_success 'setup r1' 
  
  test_expect_success 'verify blob:none omits all 5 blobs' '
        git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 -      git -C r1 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:none \
 -              | awk -f print_1.awk \
 -              | sed "s/~//" \
 -              | sort >observed &&
 -      test_cmp observed expected
 +              >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
 +      git -C r1 rev-list --quiet --objects --filter-print-omitted \
 +              --filter=blob:none HEAD >revs &&
 +      awk -f print_1.awk revs |
 +      sed "s/~//" |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
+ test_expect_success 'specify blob explicitly prevents filtering' '
+       file_3=$(git -C r1 ls-files -s file.3 |
+                awk -f print_2.awk) &&
+       file_4=$(git -C r1 ls-files -s file.4 |
+                awk -f print_2.awk) &&
+       git -C r1 rev-list --objects --filter=blob:none HEAD $file_3 >observed &&
+       grep "$file_3" observed &&
+       ! grep "$file_4" observed
+ '
  test_expect_success 'verify emitted+omitted == all' '
 -      git -C r1 rev-list HEAD --objects \
 -              | awk -f print_1.awk \
 -              | sort >expected &&
 -      git -C r1 rev-list HEAD --objects --filter-print-omitted --filter=blob:none \
 -              | awk -f print_1.awk \
 -              | sed "s/~//" \
 -              | sort >observed &&
 -      test_cmp observed expected
 +      git -C r1 rev-list --objects HEAD >revs &&
 +      awk -f print_1.awk revs |
 +      sort >expected &&
 +
 +      git -C r1 rev-list --objects --filter-print-omitted --filter=blob:none \
 +              HEAD >revs &&
 +      awk -f print_1.awk revs |
 +      sed "s/~//" |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  
@@@ -65,83 -70,67 +77,83 @@@ test_expect_success 'setup r2' 
  '
  
  test_expect_success 'verify blob:limit=500 omits all blobs' '
 -      git -C r2 ls-files -s large.1000 large.10000 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 -      git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=500 \
 -              | awk -f print_1.awk \
 -              | sed "s/~//" \
 -              | sort >observed &&
 -      test_cmp observed expected
 +      git -C r2 ls-files -s large.1000 large.10000 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
 +      git -C r2 rev-list --quiet --objects --filter-print-omitted \
 +              --filter=blob:limit=500 HEAD >revs &&
 +      awk -f print_1.awk revs |
 +      sed "s/~//" |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify emitted+omitted == all' '
 -      git -C r2 rev-list HEAD --objects \
 -              | awk -f print_1.awk \
 -              | sort >expected &&
 -      git -C r2 rev-list HEAD --objects --filter-print-omitted --filter=blob:limit=500 \
 -              | awk -f print_1.awk \
 -              | sed "s/~//" \
 -              | sort >observed &&
 -      test_cmp observed expected
 +      git -C r2 rev-list --objects HEAD >revs &&
 +      awk -f print_1.awk revs |
 +      sort >expected &&
 +
 +      git -C r2 rev-list --objects --filter-print-omitted \
 +              --filter=blob:limit=500 HEAD >revs &&
 +      awk -f print_1.awk revs |
 +      sed "s/~//" |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify blob:limit=1000' '
 -      git -C r2 ls-files -s large.1000 large.10000 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 -      git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1000 \
 -              | awk -f print_1.awk \
 -              | sed "s/~//" \
 -              | sort >observed &&
 -      test_cmp observed expected
 +      git -C r2 ls-files -s large.1000 large.10000 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
 +      git -C r2 rev-list --quiet --objects --filter-print-omitted \
 +              --filter=blob:limit=1000 HEAD >revs &&
 +      awk -f print_1.awk revs |
 +      sed "s/~//" |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify blob:limit=1001' '
 -      git -C r2 ls-files -s large.10000 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 -      git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1001 \
 -              | awk -f print_1.awk \
 -              | sed "s/~//" \
 -              | sort >observed &&
 -      test_cmp observed expected
 +      git -C r2 ls-files -s large.10000 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
 +      git -C r2 rev-list --quiet --objects --filter-print-omitted \
 +              --filter=blob:limit=1001 HEAD >revs &&
 +      awk -f print_1.awk revs |
 +      sed "s/~//" |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify blob:limit=1k' '
 -      git -C r2 ls-files -s large.10000 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 -      git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1k \
 -              | awk -f print_1.awk \
 -              | sed "s/~//" \
 -              | sort >observed &&
 -      test_cmp observed expected
 +      git -C r2 ls-files -s large.10000 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
 +      git -C r2 rev-list --quiet --objects --filter-print-omitted \
 +              --filter=blob:limit=1k HEAD >revs &&
 +      awk -f print_1.awk revs |
 +      sed "s/~//" |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify blob:limit=1m' '
 -      cat </dev/null >expected &&
 -      git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1m \
 -              | awk -f print_1.awk \
 -              | sed "s/~//" \
 -              | sort >observed &&
 -      test_cmp observed expected
 +      git -C r2 rev-list --quiet --objects --filter-print-omitted \
 +              --filter=blob:limit=1m HEAD >revs &&
 +      awk -f print_1.awk revs |
 +      sed "s/~//" |
 +      sort >observed &&
 +
 +      test_must_be_empty observed
  '
  
  # Test sparse:path=<path> filter.
@@@ -165,31 -154,25 +177,31 @@@ test_expect_success 'setup r3' 
  '
  
  test_expect_success 'verify sparse:path=pattern1 omits top-level files' '
 -      git -C r3 ls-files -s sparse1 sparse2 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 -      git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:path=../pattern1 \
 -              | awk -f print_1.awk \
 -              | sed "s/~//" \
 -              | sort >observed &&
 -      test_cmp observed expected
 +      git -C r3 ls-files -s sparse1 sparse2 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
 +      git -C r3 rev-list --quiet --objects --filter-print-omitted \
 +              --filter=sparse:path=../pattern1 HEAD >revs &&
 +      awk -f print_1.awk revs |
 +      sed "s/~//" |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify sparse:path=pattern2 omits both sparse2 files' '
 -      git -C r3 ls-files -s sparse2 dir1/sparse2 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 -      git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:path=../pattern2 \
 -              | awk -f print_1.awk \
 -              | sed "s/~//" \
 -              | sort >observed &&
 -      test_cmp observed expected
 +      git -C r3 ls-files -s sparse2 dir1/sparse2 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
 +      git -C r3 rev-list --quiet --objects --filter-print-omitted \
 +              --filter=sparse:path=../pattern2 HEAD >revs &&
 +      awk -f print_1.awk revs |
 +      sed "s/~//" |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  # Test sparse:oid=<oid-ish> filter.
@@@ -203,55 -186,94 +215,105 @@@ test_expect_success 'setup r3 part 2' 
  '
  
  test_expect_success 'verify sparse:oid=OID omits top-level files' '
 -      git -C r3 ls-files -s pattern sparse1 sparse2 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 +      git -C r3 ls-files -s pattern sparse1 sparse2 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
        oid=$(git -C r3 ls-files -s pattern | awk -f print_2.awk) &&
 -      git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:oid=$oid \
 -              | awk -f print_1.awk \
 -              | sed "s/~//" \
 -              | sort >observed &&
 -      test_cmp observed expected
 +
 +      git -C r3 rev-list --quiet --objects --filter-print-omitted \
 +              --filter=sparse:oid=$oid HEAD >revs &&
 +      awk -f print_1.awk revs |
 +      sed "s/~//" |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'verify sparse:oid=oid-ish omits top-level files' '
 -      git -C r3 ls-files -s pattern sparse1 sparse2 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 -      git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:oid=master:pattern \
 -              | awk -f print_1.awk \
 -              | sed "s/~//" \
 -              | sort >observed &&
 -      test_cmp observed expected
 +      git -C r3 ls-files -s pattern sparse1 sparse2 >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
 +      git -C r3 rev-list --quiet --objects --filter-print-omitted \
 +              --filter=sparse:oid=master:pattern HEAD >revs &&
 +      awk -f print_1.awk revs |
 +      sed "s/~//" |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
+ test_expect_success 'rev-list W/ --missing=print and --missing=allow-any for trees' '
+       TREE=$(git -C r3 rev-parse HEAD:dir1) &&
+       # Create a spare repo because we will be deleting objects from this one.
+       git clone r3 r3.b &&
+       rm r3.b/.git/objects/$(echo $TREE | sed "s|^..|&/|") &&
+       git -C r3.b rev-list --quiet --missing=print --objects HEAD \
+               >missing_objs 2>rev_list_err &&
+       echo "?$TREE" >expected &&
+       test_cmp expected missing_objs &&
+       # do not complain when a missing tree cannot be parsed
+       test_must_be_empty rev_list_err &&
+       git -C r3.b rev-list --missing=allow-any --objects HEAD \
+               >objs 2>rev_list_err &&
+       ! grep $TREE objs &&
+       test_must_be_empty rev_list_err
+ '
+ # Test tree:0 filter.
+ test_expect_success 'verify tree:0 includes trees in "filtered" output' '
+       git -C r3 rev-list --quiet --objects --filter-print-omitted \
+               --filter=tree:0 HEAD >revs &&
+       awk -f print_1.awk revs |
+       sed s/~// |
+       xargs -n1 git -C r3 cat-file -t >unsorted_filtered_types &&
+       sort -u unsorted_filtered_types >filtered_types &&
+       test_write_lines blob tree >expected &&
+       test_cmp expected filtered_types
+ '
+ # Make sure tree:0 does not iterate through any trees.
+ test_expect_success 'filter a GIANT tree through tree:0' '
+       GIT_TRACE=1 git -C r3 rev-list \
+               --objects --filter=tree:0 HEAD 2>filter_trace &&
+       grep "Skipping contents of tree [.][.][.]" filter_trace >actual &&
+       # One line for each commit traversed.
+       test_line_count = 2 actual &&
+       # Make sure no other trees were considered besides the root.
+       ! grep "Skipping contents of tree [^.]" filter_trace
+ '
  # Delete some loose objects and use rev-list, but WITHOUT any filtering.
  # This models previously omitted objects that we did not receive.
  
  test_expect_success 'rev-list W/ --missing=print' '
        git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
 -              | awk -f print_2.awk \
 -              | sort >expected &&
 +              >ls_files_result &&
 +      awk -f print_2.awk ls_files_result |
 +      sort >expected &&
 +
        for id in `cat expected | sed "s|..|&/|"`
        do
                rm r1/.git/objects/$id
        done &&
 -      git -C r1 rev-list --quiet HEAD --missing=print --objects \
 -              | awk -f print_1.awk \
 -              | sed "s/?//" \
 -              | sort >observed &&
 -      test_cmp observed expected
 +
 +      git -C r1 rev-list --quiet --missing=print --objects HEAD >revs &&
 +      awk -f print_1.awk revs |
 +      sed "s/?//" |
 +      sort >observed &&
 +
 +      test_cmp expected observed
  '
  
  test_expect_success 'rev-list W/O --missing fails' '