mirror of
https://github.com/git/git.git
synced 2024-11-23 18:05:29 +08:00
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:
commit
77d503757d
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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];
|
||||
|
@ -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 {
|
||||
|
233
list-objects.c
233
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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
26
revision.h
26
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;
|
||||
|
||||
|
@ -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 &&
|
||||
|
@ -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
|
||||
|
@ -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 &&
|
||||
|
@ -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.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user