list-objects: filter objects in traverse_commit_list

Create traverse_commit_list_filtered() and add filtering interface to allow certain objects to be omitted from the traversal. Update traverse_commit_list() to be a wrapper for the above with a null filter to minimize the number of callers that needed to be changed. Object filtering will be used in a future commit by rev-list and pack-objects for partial clone and fetch to omit unwanted objects from the result. traverse_bitmap_commit_list() does not work with filtering. If a packfile bitmap is present, it will not be used. It should be possible to extend such support in the future (at least to simple filters that do not require object pathnames), but that is beyond the scope of this patch series. Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com> Reviewed-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff Hostetler committed Nov 21, 2017 at 20:58 UTC 25ec7bcac044057900a0f3c9a8d6ccbb41a066bc
8 files changed +711 -17
Makefile
+2
@@ -807,6 +807,8 @@ LIB_OBJS += levenshtein.o
807 LIB_OBJS += line-log.o
808 LIB_OBJS += line-range.o
809 LIB_OBJS += list-objects.o
810 +LIB_OBJS += list-objects-filter.o
811 +LIB_OBJS += list-objects-filter-options.o
812 LIB_OBJS += ll-merge.o
813 LIB_OBJS += lockfile.o
814 LIB_OBJS += log-tree.o
list-objects-filter-options.c new
+81
@@ -0,0 +1,81 @@
1 +#include "cache.h"
2 +#include "commit.h"
3 +#include "config.h"
4 +#include "revision.h"
5 +#include "argv-array.h"
6 +#include "list-objects.h"
7 +#include "list-objects-filter.h"
8 +#include "list-objects-filter-options.h"
9 +
10 +/*
11 + * Parse value of the argument to the "filter" keword.
12 + * On the command line this looks like:
13 + * --filter=<arg>
14 + * and in the pack protocol as:
15 + * "filter" SP <arg>
16 + *
17 + * The filter keyword will be used by many commands.
18 + * See Documentation/rev-list-options.txt for allowed values for <arg>.
19 + *
20 + * Capture the given arg as the "filter_spec". This can be forwarded to
21 + * subordinate commands when necessary. We also "intern" the arg for
22 + * the convenience of the current command.
23 + */
24 +int parse_list_objects_filter(struct list_objects_filter_options *filter_options,
25 + const char *arg)
26 +{
27 + const char *v0;
28 +
29 + if (filter_options->choice)
30 + die(_("multiple object filter types cannot be combined"));
31 +
32 + filter_options->filter_spec = strdup(arg);
33 +
34 + if (!strcmp(arg, "blob:none")) {
35 + filter_options->choice = LOFC_BLOB_NONE;
36 + return 0;
37 + }
38 +
39 + if (skip_prefix(arg, "blob:limit=", &v0)) {
40 + if (!git_parse_ulong(v0, &filter_options->blob_limit_value))
41 + die(_("invalid filter-spec expression '%s'"), arg);
42 + filter_options->choice = LOFC_BLOB_LIMIT;
43 + return 0;
44 + }
45 +
46 + if (skip_prefix(arg, "sparse:oid=", &v0)) {
47 + struct object_context oc;
48 + struct object_id sparse_oid;
49 +
50 + /*
51 + * Try to parse <oid-expression> into an OID for the current
52 + * command, but DO NOT complain if we don't have the blob or
53 + * ref locally.
54 + */
55 + if (!get_oid_with_context(v0, GET_OID_BLOB,
56 + &sparse_oid, &oc))
57 + filter_options->sparse_oid_value = oiddup(&sparse_oid);
58 + filter_options->choice = LOFC_SPARSE_OID;
59 + return 0;
60 + }
61 +
62 + if (skip_prefix(arg, "sparse:path=", &v0)) {
63 + filter_options->choice = LOFC_SPARSE_PATH;
64 + filter_options->sparse_path_value = strdup(v0);
65 + return 0;
66 + }
67 +
68 + die(_("invalid filter-spec expression '%s'"), arg);
69 + return 0;
70 +}
71 +
72 +int opt_parse_list_objects_filter(const struct option *opt,
73 + const char *arg, int unset)
74 +{
75 + struct list_objects_filter_options *filter_options = opt->value;
76 +
77 + assert(arg);
78 + assert(!unset);
79 +
80 + return parse_list_objects_filter(filter_options, arg);
81 +}
list-objects-filter-options.h new
+58
@@ -0,0 +1,58 @@
1 +#ifndef LIST_OBJECTS_FILTER_OPTIONS_H
2 +#define LIST_OBJECTS_FILTER_OPTIONS_H
3 +
4 +#include "parse-options.h"
5 +
6 +/*
7 + * The list of defined filters for list-objects.
8 + */
9 +enum list_objects_filter_choice {
10 + LOFC_DISABLED = 0,
11 + LOFC_BLOB_NONE,
12 + LOFC_BLOB_LIMIT,
13 + LOFC_SPARSE_OID,
14 + LOFC_SPARSE_PATH,
15 + LOFC__COUNT /* must be last */
16 +};
17 +
18 +struct list_objects_filter_options {
19 + /*
20 + * 'filter_spec' is the raw argument value given on the command line
21 + * or protocol request. (The part after the "--keyword=".) For
22 + * commands that launch filtering sub-processes, this value should be
23 + * passed to them as received by the current process.
24 + */
25 + char *filter_spec;
26 +
27 + /*
28 + * 'choice' is determined by parsing the filter-spec. This indicates
29 + * the filtering algorithm to use.
30 + */
31 + enum list_objects_filter_choice choice;
32 +
33 + /*
34 + * Parsed values (fields) from within the filter-spec. These are
35 + * choice-specific; not all values will be defined for any given
36 + * choice.
37 + */
38 + struct object_id *sparse_oid_value;
39 + char *sparse_path_value;
40 + unsigned long blob_limit_value;
41 +};
42 +
43 +/* Normalized command line arguments */
44 +#define CL_ARG__FILTER "filter"
45 +
46 +int parse_list_objects_filter(
47 + struct list_objects_filter_options *filter_options,
48 + const char *arg);
49 +
50 +int opt_parse_list_objects_filter(const struct option *opt,
51 + const char *arg, int unset);
52 +
53 +#define OPT_PARSE_LIST_OBJECTS_FILTER(fo) \
54 + { OPTION_CALLBACK, 0, CL_ARG__FILTER, fo, N_("args"), \
55 + N_("object filtering"), PARSE_OPT_NONEG, \
56 + opt_parse_list_objects_filter }
57 +
58 +#endif /* LIST_OBJECTS_FILTER_OPTIONS_H */
list-objects-filter.c new
+401
@@ -0,0 +1,401 @@
1 +#include "cache.h"
2 +#include "dir.h"
3 +#include "tag.h"
4 +#include "commit.h"
5 +#include "tree.h"
6 +#include "blob.h"
7 +#include "diff.h"
8 +#include "tree-walk.h"
9 +#include "revision.h"
10 +#include "list-objects.h"
11 +#include "list-objects-filter.h"
12 +#include "list-objects-filter-options.h"
13 +#include "oidset.h"
14 +
15 +/* Remember to update object flag allocation in object.h */
16 +/*
17 + * FILTER_SHOWN_BUT_REVISIT -- we set this bit on tree objects
18 + * that have been shown, but should be revisited if they appear
19 + * in the traversal (until we mark it SEEN). This is a way to
20 + * let us silently de-dup calls to show() in the caller. This
21 + * is subtly different from the "revision.h:SHOWN" and the
22 + * "sha1_name.c:ONELINE_SEEN" bits. And also different from
23 + * the non-de-dup usage in pack-bitmap.c
24 + */
25 +#define FILTER_SHOWN_BUT_REVISIT (1<<21)
26 +
27 +/*
28 + * A filter for list-objects to omit ALL blobs from the traversal.
29 + * And to OPTIONALLY collect a list of the omitted OIDs.
30 + */
31 +struct filter_blobs_none_data {
32 + struct oidset *omits;
33 +};
34 +
35 +static enum list_objects_filter_result filter_blobs_none(
36 + enum list_objects_filter_situation filter_situation,
37 + struct object *obj,
38 + const char *pathname,
39 + const char *filename,
40 + void *filter_data_)
41 +{
42 + struct filter_blobs_none_data *filter_data = filter_data_;
43 +
44 + switch (filter_situation) {
45 + default:
46 + die("unknown filter_situation");
47 + return LOFR_ZERO;
48 +
49 + case LOFS_BEGIN_TREE:
50 + assert(obj->type == OBJ_TREE);
51 + /* always include all tree objects */
52 + return LOFR_MARK_SEEN | LOFR_DO_SHOW;
53 +
54 + case LOFS_END_TREE:
55 + assert(obj->type == OBJ_TREE);
56 + return LOFR_ZERO;
57 +
58 + case LOFS_BLOB:
59 + assert(obj->type == OBJ_BLOB);
60 + assert((obj->flags & SEEN) == 0);
61 +
62 + if (filter_data->omits)
63 + oidset_insert(filter_data->omits, &obj->oid);
64 + return LOFR_MARK_SEEN; /* but not LOFR_DO_SHOW (hard omit) */
65 + }
66 +}
67 +
68 +static void *filter_blobs_none__init(
69 + struct oidset *omitted,
70 + struct list_objects_filter_options *filter_options,
71 + filter_object_fn *filter_fn,
72 + filter_free_fn *filter_free_fn)
73 +{
74 + struct filter_blobs_none_data *d = xcalloc(1, sizeof(*d));
75 + d->omits = omitted;
76 +
77 + *filter_fn = filter_blobs_none;
78 + *filter_free_fn = free;
79 + return d;
80 +}
81 +
82 +/*
83 + * A filter for list-objects to omit large blobs.
84 + * And to OPTIONALLY collect a list of the omitted OIDs.
85 + */
86 +struct filter_blobs_limit_data {
87 + struct oidset *omits;
88 + unsigned long max_bytes;
89 +};
90 +
91 +static enum list_objects_filter_result filter_blobs_limit(
92 + enum list_objects_filter_situation filter_situation,
93 + struct object *obj,
94 + const char *pathname,
95 + const char *filename,
96 + void *filter_data_)
97 +{
98 + struct filter_blobs_limit_data *filter_data = filter_data_;
99 + unsigned long object_length;
100 + enum object_type t;
101 +
102 + switch (filter_situation) {
103 + default:
104 + die("unknown filter_situation");
105 + return LOFR_ZERO;
106 +
107 + case LOFS_BEGIN_TREE:
108 + assert(obj->type == OBJ_TREE);
109 + /* always include all tree objects */
110 + return LOFR_MARK_SEEN | LOFR_DO_SHOW;
111 +
112 + case LOFS_END_TREE:
113 + assert(obj->type == OBJ_TREE);
114 + return LOFR_ZERO;
115 +
116 + case LOFS_BLOB:
117 + assert(obj->type == OBJ_BLOB);
118 + assert((obj->flags & SEEN) == 0);
119 +
120 + t = sha1_object_info(obj->oid.hash, &object_length);
121 + if (t != OBJ_BLOB) { /* probably OBJ_NONE */
122 + /*
123 + * We DO NOT have the blob locally, so we cannot
124 + * apply the size filter criteria. Be conservative
125 + * and force show it (and let the caller deal with
126 + * the ambiguity).
127 + */
128 + goto include_it;
129 + }
130 +
131 + if (object_length < filter_data->max_bytes)
132 + goto include_it;
133 +
134 + if (filter_data->omits)
135 + oidset_insert(filter_data->omits, &obj->oid);
136 + return LOFR_MARK_SEEN; /* but not LOFR_DO_SHOW (hard omit) */
137 + }
138 +
139 +include_it:
140 + if (filter_data->omits)
141 + oidset_remove(filter_data->omits, &obj->oid);
142 + return LOFR_MARK_SEEN | LOFR_DO_SHOW;
143 +}
144 +
145 +static void *filter_blobs_limit__init(
146 + struct oidset *omitted,
147 + struct list_objects_filter_options *filter_options,
148 + filter_object_fn *filter_fn,
149 + filter_free_fn *filter_free_fn)
150 +{
151 + struct filter_blobs_limit_data *d = xcalloc(1, sizeof(*d));
152 + d->omits = omitted;
153 + d->max_bytes = filter_options->blob_limit_value;
154 +
155 + *filter_fn = filter_blobs_limit;
156 + *filter_free_fn = free;
157 + return d;
158 +}
159 +
160 +/*
161 + * A filter driven by a sparse-checkout specification to only
162 + * include blobs that a sparse checkout would populate.
163 + *
164 + * The sparse-checkout spec can be loaded from a blob with the
165 + * given OID or from a local pathname. We allow an OID because
166 + * the repo may be bare or we may be doing the filtering on the
167 + * server.
168 + */
169 +struct frame {
170 + /*
171 + * defval is the usual default include/exclude value that
172 + * should be inherited as we recurse into directories based
173 + * upon pattern matching of the directory itself or of a
174 + * containing directory.
175 + */
176 + int defval;
177 +
178 + /*
179 + * 1 if the directory (recursively) contains any provisionally
180 + * omitted objects.
181 + *
182 + * 0 if everything (recursively) contained in this directory
183 + * has been explicitly included (SHOWN) in the result and
184 + * the directory may be short-cut later in the traversal.
185 + */
186 + unsigned child_prov_omit : 1;
187 +};
188 +
189 +struct filter_sparse_data {
190 + struct oidset *omits;
191 + struct exclude_list el;
192 +
193 + size_t nr, alloc;
194 + struct frame *array_frame;
195 +};
196 +
197 +static enum list_objects_filter_result filter_sparse(
198 + enum list_objects_filter_situation filter_situation,
199 + struct object *obj,
200 + const char *pathname,
201 + const char *filename,
202 + void *filter_data_)
203 +{
204 + struct filter_sparse_data *filter_data = filter_data_;
205 + int val, dtype;
206 + struct frame *frame;
207 +
208 + switch (filter_situation) {
209 + default:
210 + die("unknown filter_situation");
211 + return LOFR_ZERO;
212 +
213 + case LOFS_BEGIN_TREE:
214 + assert(obj->type == OBJ_TREE);
215 + dtype = DT_DIR;
216 + val = is_excluded_from_list(pathname, strlen(pathname),
217 + filename, &dtype, &filter_data->el,
218 + &the_index);
219 + if (val < 0)
220 + val = filter_data->array_frame[filter_data->nr].defval;
221 +
222 + ALLOC_GROW(filter_data->array_frame, filter_data->nr + 1,
223 + filter_data->alloc);
224 + filter_data->nr++;
225 + filter_data->array_frame[filter_data->nr].defval = val;
226 + filter_data->array_frame[filter_data->nr].child_prov_omit = 0;
227 +
228 + /*
229 + * A directory with this tree OID may appear in multiple
230 + * places in the tree. (Think of a directory move or copy,
231 + * with no other changes, so the OID is the same, but the
232 + * full pathnames of objects within this directory are new
233 + * and may match is_excluded() patterns differently.)
234 + * So we cannot mark this directory as SEEN (yet), since
235 + * that will prevent process_tree() from revisiting this
236 + * tree object with other pathname prefixes.
237 + *
238 + * Only _DO_SHOW the tree object the first time we visit
239 + * this tree object.
240 + *
241 + * We always show all tree objects. A future optimization
242 + * may want to attempt to narrow this.
243 + */
244 + if (obj->flags & FILTER_SHOWN_BUT_REVISIT)
245 + return LOFR_ZERO;
246 + obj->flags |= FILTER_SHOWN_BUT_REVISIT;
247 + return LOFR_DO_SHOW;
248 +
249 + case LOFS_END_TREE:
250 + assert(obj->type == OBJ_TREE);
251 + assert(filter_data->nr > 0);
252 +
253 + frame = &filter_data->array_frame[filter_data->nr];
254 + filter_data->nr--;
255 +
256 + /*
257 + * Tell our parent directory if any of our children were
258 + * provisionally omitted.
259 + */
260 + filter_data->array_frame[filter_data->nr].child_prov_omit |=
261 + frame->child_prov_omit;
262 +
263 + /*
264 + * If there are NO provisionally omitted child objects (ALL child
265 + * objects in this folder were INCLUDED), then we can mark the
266 + * folder as SEEN (so we will not have to revisit it again).
267 + */
268 + if (!frame->child_prov_omit)
269 + return LOFR_MARK_SEEN;
270 + return LOFR_ZERO;
271 +
272 + case LOFS_BLOB:
273 + assert(obj->type == OBJ_BLOB);
274 + assert((obj->flags & SEEN) == 0);
275 +
276 + frame = &filter_data->array_frame[filter_data->nr];
277 +
278 + dtype = DT_REG;
279 + val = is_excluded_from_list(pathname, strlen(pathname),
280 + filename, &dtype, &filter_data->el,
281 + &the_index);
282 + if (val < 0)
283 + val = frame->defval;
284 + if (val > 0) {
285 + if (filter_data->omits)
286 + oidset_remove(filter_data->omits, &obj->oid);
287 + return LOFR_MARK_SEEN | LOFR_DO_SHOW;
288 + }
289 +
290 + /*
291 + * Provisionally omit it. We've already established that
292 + * this pathname is not in the sparse-checkout specification
293 + * with the CURRENT pathname, so we *WANT* to omit this blob.
294 + *
295 + * However, a pathname elsewhere in the tree may also
296 + * reference this same blob, so we cannot reject it yet.
297 + * Leave the LOFR_ bits unset so that if the blob appears
298 + * again in the traversal, we will be asked again.
299 + */
300 + if (filter_data->omits)
301 + oidset_insert(filter_data->omits, &obj->oid);
302 +
303 + /*
304 + * Remember that at least 1 blob in this tree was
305 + * provisionally omitted. This prevents us from short
306 + * cutting the tree in future iterations.
307 + */
308 + frame->child_prov_omit = 1;
309 + return LOFR_ZERO;
310 + }
311 +}
312 +
313 +
314 +static void filter_sparse_free(void *filter_data)
315 +{
316 + struct filter_sparse_data *d = filter_data;
317 + /* TODO free contents of 'd' */
318 + free(d);
319 +}
320 +
321 +static void *filter_sparse_oid__init(
322 + struct oidset *omitted,
323 + struct list_objects_filter_options *filter_options,
324 + filter_object_fn *filter_fn,
325 + filter_free_fn *filter_free_fn)
326 +{
327 + struct filter_sparse_data *d = xcalloc(1, sizeof(*d));
328 + d->omits = omitted;
329 + if (add_excludes_from_blob_to_list(filter_options->sparse_oid_value,
330 + NULL, 0, &d->el) < 0)
331 + die("could not load filter specification");
332 +
333 + ALLOC_GROW(d->array_frame, d->nr + 1, d->alloc);
334 + d->array_frame[d->nr].defval = 0; /* default to include */
335 + d->array_frame[d->nr].child_prov_omit = 0;
336 +
337 + *filter_fn = filter_sparse;
338 + *filter_free_fn = filter_sparse_free;
339 + return d;
340 +}
341 +
342 +static void *filter_sparse_path__init(
343 + struct oidset *omitted,
344 + struct list_objects_filter_options *filter_options,
345 + filter_object_fn *filter_fn,
346 + filter_free_fn *filter_free_fn)
347 +{
348 + struct filter_sparse_data *d = xcalloc(1, sizeof(*d));
349 + d->omits = omitted;
350 + if (add_excludes_from_file_to_list(filter_options->sparse_path_value,
351 + NULL, 0, &d->el, NULL) < 0)
352 + die("could not load filter specification");
353 +
354 + ALLOC_GROW(d->array_frame, d->nr + 1, d->alloc);
355 + d->array_frame[d->nr].defval = 0; /* default to include */
356 + d->array_frame[d->nr].child_prov_omit = 0;
357 +
358 + *filter_fn = filter_sparse;
359 + *filter_free_fn = filter_sparse_free;
360 + return d;
361 +}
362 +
363 +typedef void *(*filter_init_fn)(
364 + struct oidset *omitted,
365 + struct list_objects_filter_options *filter_options,
366 + filter_object_fn *filter_fn,
367 + filter_free_fn *filter_free_fn);
368 +
369 +/*
370 + * Must match "enum list_objects_filter_choice".
371 + */
372 +static filter_init_fn s_filters[] = {
373 + NULL,
374 + filter_blobs_none__init,
375 + filter_blobs_limit__init,
376 + filter_sparse_oid__init,
377 + filter_sparse_path__init,
378 +};
379 +
380 +void *list_objects_filter__init(
381 + struct oidset *omitted,
382 + struct list_objects_filter_options *filter_options,
383 + filter_object_fn *filter_fn,
384 + filter_free_fn *filter_free_fn)
385 +{
386 + filter_init_fn init_fn;
387 +
388 + assert((sizeof(s_filters) / sizeof(s_filters[0])) == LOFC__COUNT);
389 +
390 + if (filter_options->choice >= LOFC__COUNT)
391 + die("invalid list-objects filter choice: %d",
392 + filter_options->choice);
393 +
394 + init_fn = s_filters[filter_options->choice];
395 + if (init_fn)
396 + return init_fn(omitted, filter_options,
397 + filter_fn, filter_free_fn);
398 + *filter_fn = NULL;
399 + *filter_free_fn = NULL;
400 + return NULL;
401 +}
list-objects-filter.h new
+77
@@ -0,0 +1,77 @@
1 +#ifndef LIST_OBJECTS_FILTER_H
2 +#define LIST_OBJECTS_FILTER_H
3 +
4 +/*
5 + * During list-object traversal we allow certain objects to be
6 + * filtered (omitted) from the result. The active filter uses
7 + * these result values to guide list-objects.
8 + *
9 + * _ZERO : Do nothing with the object at this time. It may
10 + * be revisited if it appears in another place in
11 + * the tree or in another commit during the overall
12 + * traversal.
13 + *
14 + * _MARK_SEEN : Mark this object as "SEEN" in the object flags.
15 + * This will prevent it from being revisited during
16 + * the remainder of the traversal. This DOES NOT
17 + * imply that it will be included in the results.
18 + *
19 + * _DO_SHOW : Show this object in the results (call show() on it).
20 + * In general, objects should only be shown once, but
21 + * this result DOES NOT imply that we mark it SEEN.
22 + *
23 + * Most of the time, you want the combination (_MARK_SEEN | _DO_SHOW)
24 + * but they can be used independently, such as when sparse-checkout
25 + * pattern matching is being applied.
26 + *
27 + * A _MARK_SEEN without _DO_SHOW can be called a hard-omit -- the
28 + * object is not shown and will never be reconsidered (unless a
29 + * previous iteration has already shown it).
30 + *
31 + * A _DO_SHOW without _MARK_SEEN can be used, for example, to
32 + * include a directory, but then revisit it to selectively include
33 + * or omit objects within it.
34 + *
35 + * A _ZERO can be called a provisional-omit -- the object is NOT shown,
36 + * but *may* be revisited (if the object appears again in the traversal).
37 + * Therefore, it will be omitted from the results *unless* a later
38 + * iteration causes it to be shown.
39 + */
40 +enum list_objects_filter_result {
41 + LOFR_ZERO = 0,
42 + LOFR_MARK_SEEN = 1<<0,
43 + LOFR_DO_SHOW = 1<<1,
44 +};
45 +
46 +enum list_objects_filter_situation {
47 + LOFS_BEGIN_TREE,
48 + LOFS_END_TREE,
49 + LOFS_BLOB
50 +};
51 +
52 +typedef enum list_objects_filter_result (*filter_object_fn)(
53 + enum list_objects_filter_situation filter_situation,
54 + struct object *obj,
55 + const char *pathname,
56 + const char *filename,
57 + void *filter_data);
58 +
59 +typedef void (*filter_free_fn)(void *filter_data);
60 +
61 +/*
62 + * Constructor for the set of defined list-objects filters.
63 + * Returns a generic "void *filter_data".
64 + *
65 + * The returned "filter_fn" will be used by traverse_commit_list()
66 + * to filter the results.
67 + *
68 + * The returned "filter_free_fn" is a destructor for the
69 + * filter_data.
70 + */
71 +void *list_objects_filter__init(
72 + struct oidset *omitted,
73 + struct list_objects_filter_options *filter_options,
74 + filter_object_fn *filter_fn,
75 + filter_free_fn *filter_free_fn);
76 +
77 +#endif /* LIST_OBJECTS_FILTER_H */
list-objects.c
+79 -16
@@ -7,16 +7,21 @@
7 #include "tree-walk.h"
8 #include "revision.h"
9 #include "list-objects.h"
10 +#include "list-objects-filter.h"
11 +#include "list-objects-filter-options.h"
12
13 static void process_blob(struct rev_info *revs,
14 struct blob *blob,
15 show_object_fn show,
16 struct strbuf *path,
17 const char *name,
16 - void *cb_data)
18 + void *cb_data,
19 + filter_object_fn filter_fn,
20 + void *filter_data)
21 {
22 struct object *obj = &blob->object;
23 size_t pathlen;
24 + enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW;
25
26 if (!revs->blob_objects)
27 return;
@@ -24,11 +29,17 @@ static void process_blob(struct rev_info *revs,
29 die("bad blob object");
30 if (obj->flags & (UNINTERESTING | SEEN))
31 return;
27 - obj->flags |= SEEN;
32
33 pathlen = path->len;
34 strbuf_addstr(path, name);
31 - show(obj, path->buf, cb_data);
35 + if (filter_fn)
36 + r = filter_fn(LOFS_BLOB, obj,
37 + path->buf, &path->buf[pathlen],
38 + filter_data);
39 + if (r & LOFR_MARK_SEEN)
40 + obj->flags |= SEEN;
41 + if (r & LOFR_DO_SHOW)
42 + show(obj, path->buf, cb_data);
43 strbuf_setlen(path, pathlen);
44 }
45
@@ -69,7 +80,9 @@ static void process_tree(struct rev_info *revs,
80 show_object_fn show,
81 struct strbuf *base,
82 const char *name,
72 - void *cb_data)
83 + void *cb_data,
84 + filter_object_fn filter_fn,
85 + void *filter_data)
86 {
87 struct object *obj = &tree->object;
88 struct tree_desc desc;
@@ -77,6 +90,7 @@ static void process_tree(struct rev_info *revs,
90 enum interesting match = revs->diffopt.pathspec.nr == 0 ?
91 all_entries_interesting: entry_not_interesting;
92 int baselen = base->len;
93 + enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW;
94
95 if (!revs->tree_objects)
96 return;
@@ -90,9 +104,15 @@ static void process_tree(struct rev_info *revs,
104 die("bad tree object %s", oid_to_hex(&obj->oid));
105 }
106
93 - obj->flags |= SEEN;
107 strbuf_addstr(base, name);
95 - show(obj, base->buf, cb_data);
108 + if (filter_fn)
109 + r = filter_fn(LOFS_BEGIN_TREE, obj,
110 + base->buf, &base->buf[baselen],
111 + filter_data);
112 + if (r & LOFR_MARK_SEEN)
113 + obj->flags |= SEEN;
114 + if (r & LOFR_DO_SHOW)
115 + show(obj, base->buf, cb_data);
116 if (base->len)
117 strbuf_addch(base, '/');
118
@@ -112,7 +132,7 @@ static void process_tree(struct rev_info *revs,
132 process_tree(revs,
133 lookup_tree(entry.oid),
134 show, base, entry.path,
115 - cb_data);
135 + cb_data, filter_fn, filter_data);
136 else if (S_ISGITLINK(entry.mode))
137 process_gitlink(revs, entry.oid->hash,
138 show, base, entry.path,
@@ -121,8 +141,19 @@ static void process_tree(struct rev_info *revs,
141 process_blob(revs,
142 lookup_blob(entry.oid),
143 show, base, entry.path,
124 - cb_data);
144 + cb_data, filter_fn, filter_data);
145 }
146 +
147 + if (filter_fn) {
148 + r = filter_fn(LOFS_END_TREE, obj,
149 + base->buf, &base->buf[baselen],
150 + filter_data);
151 + if (r & LOFR_MARK_SEEN)
152 + obj->flags |= SEEN;
153 + if (r & LOFR_DO_SHOW)
154 + show(obj, base->buf, cb_data);
155 + }
156 +
157 strbuf_setlen(base, baselen);
158 free_tree_buffer(tree);
159 }
@@ -183,10 +214,12 @@ static void add_pending_tree(struct rev_info *revs, struct tree *tree)
214 add_pending_object(revs, &tree->object, "");
215 }
216
186 -void traverse_commit_list(struct rev_info *revs,
187 - show_commit_fn show_commit,
188 - show_object_fn show_object,
189 - void *data)
217 +static void do_traverse(struct rev_info *revs,
218 + show_commit_fn show_commit,
219 + show_object_fn show_object,
220 + void *show_data,
221 + filter_object_fn filter_fn,
222 + void *filter_data)
223 {
224 int i;
225 struct commit *commit;
@@ -200,7 +233,7 @@ void traverse_commit_list(struct rev_info *revs,
233 */
234 if (commit->tree)
235 add_pending_tree(revs, commit->tree);
203 - show_commit(commit, data);
236 + show_commit(commit, show_data);
237 }
238 for (i = 0; i < revs->pending.nr; i++) {
239 struct object_array_entry *pending = revs->pending.objects + i;
@@ -211,19 +244,21 @@ void traverse_commit_list(struct rev_info *revs,
244 continue;
245 if (obj->type == OBJ_TAG) {
246 obj->flags |= SEEN;
214 - show_object(obj, name, data);
247 + show_object(obj, name, show_data);
248 continue;
249 }
250 if (!path)
251 path = "";
252 if (obj->type == OBJ_TREE) {
253 process_tree(revs, (struct tree *)obj, show_object,
221 - &base, path, data);
254 + &base, path, show_data,
255 + filter_fn, filter_data);
256 continue;
257 }
258 if (obj->type == OBJ_BLOB) {
259 process_blob(revs, (struct blob *)obj, show_object,
226 - &base, path, data);
260 + &base, path, show_data,
261 + filter_fn, filter_data);
262 continue;
263 }
264 die("unknown pending object %s (%s)",
@@ -232,3 +267,31 @@ void traverse_commit_list(struct rev_info *revs,
267 object_array_clear(&revs->pending);
268 strbuf_release(&base);
269 }
270 +
271 +void traverse_commit_list(struct rev_info *revs,
272 + show_commit_fn show_commit,
273 + show_object_fn show_object,
274 + void *show_data)
275 +{
276 + do_traverse(revs, show_commit, show_object, show_data, NULL, NULL);
277 +}
278 +
279 +void traverse_commit_list_filtered(
280 + struct list_objects_filter_options *filter_options,
281 + struct rev_info *revs,
282 + show_commit_fn show_commit,
283 + show_object_fn show_object,
284 + void *show_data,
285 + struct oidset *omitted)
286 +{
287 + filter_object_fn filter_fn = NULL;
288 + filter_free_fn filter_free_fn = NULL;
289 + void *filter_data = NULL;
290 +
291 + filter_data = list_objects_filter__init(omitted, filter_options,
292 + &filter_fn, &filter_free_fn);
293 + do_traverse(revs, show_commit, show_object, show_data,
294 + filter_fn, filter_data);
295 + if (filter_data && filter_free_fn)
296 + filter_free_fn(filter_data);
297 +}
list-objects.h
+12 -1
@@ -8,4 +8,15 @@ void traverse_commit_list(struct rev_info *, show_commit_fn, show_object_fn, voi
8 typedef void (*show_edge_fn)(struct commit *);
9 void mark_edges_uninteresting(struct rev_info *, show_edge_fn);
10
11 -#endif
11 +struct oidset;
12 +struct list_objects_filter_options;
13 +
14 +void traverse_commit_list_filtered(
15 + struct list_objects_filter_options *filter_options,
16 + struct rev_info *revs,
17 + show_commit_fn show_commit,
18 + show_object_fn show_object,
19 + void *show_data,
20 + struct oidset *omitted);
21 +
22 +#endif /* LIST_OBJECTS_H */
object.h
+1
@@ -38,6 +38,7 @@ struct object_array {
38 * http-push.c: 16-----19
39 * commit.c: 16-----19
40 * sha1_name.c: 20
41 + * list-objects-filter.c: 21
42 * builtin/fsck.c: 0--3
43 */
44 #define FLAG_BITS 27