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
This commit is contained in:
Junio C Hamano 2018-10-30 15:43:39 +09:00
commit 77d503757d
13 changed files with 432 additions and 127 deletions

View File

@ -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.

View File

@ -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

View File

@ -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;
}

View File

@ -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 */

View File

@ -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);
@ -80,6 +79,61 @@ static void *filter_blobs_none__init(
return d;
}
/*
* 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];

View File

@ -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 {

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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 &&

View File

@ -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

View File

@ -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 &&

View File

@ -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.