summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar Junio C Hamano <gitster@pobox.com>2018-10-30 15:43:39 +0900
committerLibravatar Junio C Hamano <gitster@pobox.com>2018-10-30 15:43:39 +0900
commit77d503757d6328703f9571a4432dd83800bc26bb (patch)
tree55b106aab90b9b44a8d8e7cc38af63f2a3e743b9
parentSixth batch for 2.20 (diff)
parentlist-objects: support for skipping tree traversal (diff)
downloadtgif-77d503757d6328703f9571a4432dd83800bc26bb.tar.xz
Merge branch 'md/filter-trees'
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
-rw-r--r--Documentation/rev-list-options.txt5
-rw-r--r--builtin/rev-list.c11
-rw-r--r--list-objects-filter-options.c19
-rw-r--r--list-objects-filter-options.h1
-rw-r--r--list-objects-filter.c67
-rw-r--r--list-objects-filter.h6
-rw-r--r--list-objects.c235
-rw-r--r--revision.c1
-rw-r--r--revision.h26
-rwxr-xr-xt/t0410-partial-clone.sh45
-rwxr-xr-xt/t5317-pack-objects-filter-objects.sh41
-rwxr-xr-xt/t5616-partial-clone.sh42
-rwxr-xr-xt/t6112-rev-list-filters-objects.sh62
13 files changed, 433 insertions, 128 deletions
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index 7b273635de..5f1672913b 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -731,6 +731,11 @@ the requested refs.
+
The form '--filter=sparse:path=<path>' similarly uses a sparse-checkout
specification contained in <path>.
++
+The form '--filter=tree:<depth>' omits all blobs and trees whose depth
+from the root tree is >= <depth> (minimum depth if an object is located
+at multiple depths in the commits traversed). Currently, only <depth>=0
+is supported, which omits all blobs and trees.
--no-filter::
Turn off any previous `--filter=` argument.
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index cc1b70522f..5064d08e1b 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -6,6 +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 @@ static inline void finish_object__ma(struct object *obj)
*/
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:
@@ -222,8 +224,8 @@ static inline void finish_object__ma(struct object *obj)
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:
@@ -235,7 +237,7 @@ static inline void finish_object__ma(struct object *obj)
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;
}
@@ -373,6 +375,7 @@ int cmd_rev_list(int argc, const char **argv, const char *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
diff --git a/list-objects-filter-options.c b/list-objects-filter-options.c
index c0e2bd6a06..e8da2e8581 100644
--- a/list-objects-filter-options.c
+++ b/list-objects-filter-options.c
@@ -30,7 +30,6 @@ static int gently_parse_list_objects_filter(
if (filter_options->choice) {
if (errbuf) {
- strbuf_init(errbuf, 0);
strbuf_addstr(
errbuf,
_("multiple filter-specs cannot be combined"));
@@ -50,6 +49,19 @@ static int gently_parse_list_objects_filter(
return 0;
}
+ } else if (skip_prefix(arg, "tree:", &v0)) {
+ unsigned long depth;
+ if (!git_parse_ulong(v0, &depth) || depth != 0) {
+ if (errbuf) {
+ strbuf_addstr(
+ errbuf,
+ _("only 'tree:0' is supported"));
+ }
+ return 1;
+ }
+ filter_options->choice = LOFC_TREE_NONE;
+ return 0;
+
} else if (skip_prefix(arg, "sparse:oid=", &v0)) {
struct object_context oc;
struct object_id sparse_oid;
@@ -71,10 +83,9 @@ static int gently_parse_list_objects_filter(
return 0;
}
- if (errbuf) {
- strbuf_init(errbuf, 0);
+ if (errbuf)
strbuf_addf(errbuf, "invalid filter-spec '%s'", arg);
- }
+
memset(filter_options, 0, sizeof(*filter_options));
return 1;
}
diff --git a/list-objects-filter-options.h b/list-objects-filter-options.h
index 0000a61f82..af64e5c66f 100644
--- a/list-objects-filter-options.h
+++ b/list-objects-filter-options.h
@@ -10,6 +10,7 @@ enum list_objects_filter_choice {
LOFC_DISABLED = 0,
LOFC_BLOB_NONE,
LOFC_BLOB_LIMIT,
+ LOFC_TREE_NONE,
LOFC_SPARSE_OID,
LOFC_SPARSE_PATH,
LOFC__COUNT /* must be last */
diff --git a/list-objects-filter.c b/list-objects-filter.c
index a0ba78b20c..765f3df3b0 100644
--- a/list-objects-filter.c
+++ b/list-objects-filter.c
@@ -44,8 +44,7 @@ static enum list_objects_filter_result filter_blobs_none(
switch (filter_situation) {
default:
- die("unknown filter_situation");
- return LOFR_ZERO;
+ BUG("unknown filter_situation: %d", filter_situation);
case LOFS_BEGIN_TREE:
assert(obj->type == OBJ_TREE);
@@ -81,6 +80,61 @@ static void *filter_blobs_none__init(
}
/*
+ * A filter for list-objects to omit ALL trees and blobs from the traversal.
+ * Can OPTIONALLY collect a list of the omitted OIDs.
+ */
+struct filter_trees_none_data {
+ struct oidset *omits;
+};
+
+static enum list_objects_filter_result filter_trees_none(
+ enum list_objects_filter_situation filter_situation,
+ struct object *obj,
+ const char *pathname,
+ const char *filename,
+ void *filter_data_)
+{
+ struct filter_trees_none_data *filter_data = filter_data_;
+
+ switch (filter_situation) {
+ default:
+ BUG("unknown filter_situation: %d", filter_situation);
+
+ case LOFS_BEGIN_TREE:
+ case LOFS_BLOB:
+ if (filter_data->omits) {
+ oidset_insert(filter_data->omits, &obj->oid);
+ /* _MARK_SEEN but not _DO_SHOW (hard omit) */
+ return LOFR_MARK_SEEN;
+ } else {
+ /*
+ * Not collecting omits so no need to to traverse tree.
+ */
+ return LOFR_SKIP_TREE | LOFR_MARK_SEEN;
+ }
+
+ case LOFS_END_TREE:
+ assert(obj->type == OBJ_TREE);
+ return LOFR_ZERO;
+
+ }
+}
+
+static void* filter_trees_none__init(
+ struct oidset *omitted,
+ struct list_objects_filter_options *filter_options,
+ filter_object_fn *filter_fn,
+ filter_free_fn *filter_free_fn)
+{
+ struct filter_trees_none_data *d = xcalloc(1, sizeof(*d));
+ d->omits = omitted;
+
+ *filter_fn = filter_trees_none;
+ *filter_free_fn = free;
+ return d;
+}
+
+/*
* A filter for list-objects to omit large blobs.
* And to OPTIONALLY collect a list of the omitted OIDs.
*/
@@ -102,8 +156,7 @@ static enum list_objects_filter_result filter_blobs_limit(
switch (filter_situation) {
default:
- die("unknown filter_situation");
- return LOFR_ZERO;
+ BUG("unknown filter_situation: %d", filter_situation);
case LOFS_BEGIN_TREE:
assert(obj->type == OBJ_TREE);
@@ -208,8 +261,7 @@ static enum list_objects_filter_result filter_sparse(
switch (filter_situation) {
default:
- die("unknown filter_situation");
- return LOFR_ZERO;
+ BUG("unknown filter_situation: %d", filter_situation);
case LOFS_BEGIN_TREE:
assert(obj->type == OBJ_TREE);
@@ -374,6 +426,7 @@ static filter_init_fn s_filters[] = {
NULL,
filter_blobs_none__init,
filter_blobs_limit__init,
+ filter_trees_none__init,
filter_sparse_oid__init,
filter_sparse_path__init,
};
@@ -389,7 +442,7 @@ void *list_objects_filter__init(
assert((sizeof(s_filters) / sizeof(s_filters[0])) == LOFC__COUNT);
if (filter_options->choice >= LOFC__COUNT)
- die("invalid list-objects filter choice: %d",
+ BUG("invalid list-objects filter choice: %d",
filter_options->choice);
init_fn = s_filters[filter_options->choice];
diff --git a/list-objects-filter.h b/list-objects-filter.h
index a6f6b4990b..52b4a84da9 100644
--- a/list-objects-filter.h
+++ b/list-objects-filter.h
@@ -24,6 +24,11 @@ struct oidset;
* 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 +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 --git a/list-objects.c b/list-objects.c
index 0c2989d5ca..c41cc80db5 100644
--- a/list-objects.c
+++ b/list-objects.c
@@ -11,21 +11,27 @@
#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");
@@ -41,21 +47,21 @@ static void process_blob(struct rev_info *revs,
* 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);
}
@@ -81,34 +87,66 @@ static void process_blob(struct rev_info *revs,
* 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;
@@ -116,7 +154,9 @@ static void process_tree(struct rev_info *revs,
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;
@@ -129,57 +169,35 @@ static void process_tree(struct rev_info *revs,
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);
@@ -244,19 +262,15 @@ static void add_pending_tree(struct rev_info *revs, struct tree *tree)
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;
@@ -264,62 +278,52 @@ static void traverse_trees_and_blobs(struct rev_info *revs,
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 +332,14 @@ void traverse_commit_list(struct rev_info *revs,
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(
@@ -339,14 +350,18 @@ 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 --git a/revision.c b/revision.c
index b5108b75ab..a1ddb9e11c 100644
--- a/revision.c
+++ b/revision.c
@@ -177,7 +177,6 @@ static void add_pending_object_with_path(struct rev_info *revs,
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);
}
diff --git a/revision.h b/revision.h
index bc30a3023e..1cd0c4b200 100644
--- a/revision.h
+++ b/revision.h
@@ -21,9 +21,16 @@
#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
@@ -133,6 +140,21 @@ struct rev_info {
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;
diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh
index c521d7d6c6..ba3887f178 100755
--- a/t/t0410-partial-clone.sh
+++ b/t/t0410-partial-clone.sh
@@ -239,6 +239,51 @@ test_expect_success 'rev-list stops traversal at missing and promised commit' '
! 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 &&
diff --git a/t/t5317-pack-objects-filter-objects.sh b/t/t5317-pack-objects-filter-objects.sh
index 2e718f0bde..24541ea137 100755
--- a/t/t5317-pack-objects-filter-objects.sh
+++ b/t/t5317-pack-objects-filter-objects.sh
@@ -67,6 +67,47 @@ test_expect_success 'verify normal and blob:none packfiles have same commits/tre
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
diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh
index 6391437529..336f02a41a 100755
--- a/t/t5616-partial-clone.sh
+++ b/t/t5616-partial-clone.sh
@@ -166,6 +166,48 @@ test_expect_success 'partial clone with transfer.fsckobjects=1 uses index-pack -
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 &&
diff --git a/t/t6112-rev-list-filters-objects.sh b/t/t6112-rev-list-filters-objects.sh
index 53975c5724..eb32505a6e 100755
--- a/t/t6112-rev-list-filters-objects.sh
+++ b/t/t6112-rev-list-filters-objects.sh
@@ -34,6 +34,18 @@ test_expect_success 'verify blob:none omits all 5 blobs' '
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 --objects HEAD >revs &&
awk -f print_1.awk revs |
@@ -232,6 +244,56 @@ test_expect_success 'verify sparse:oid=oid-ish omits top-level files' '
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.