From: Patrick Steinhardt <hidden> Date: 2021-03-01 12:21:42
Hi,
I've recently had the usecase to retrieve all blobs introduces between
two versions which have a limit smaller than 200 bytes in order to find
all potential candidates for LFS pointers. This is currently done with
`git rev-list --objects --filter=blob:limit=200 <newrev> ^<oldrev>`, but
this is kind of inefficient: the resulting list is way too long as it
also potentially includes tags, commits and trees.
To be able to more efficiently answer this query, I've implemented
multiple things:
- A new object type filter `--filter=object:type=<type>` for
git-rev-list(1), which is implemented both for normal graph walks and
for the packfile bitmap index.
- Given that above usecase requires two filters (the object type
and blob size filters), bitmap filters were extended to support
combined filters.
- git-rev-list(1) doesn't filter user-provided objects and always prints
them. I don't want the listed commits though and only their referenced
potential LFS blobs. So I've added a new flag `--filter-provided`
which marks all provided objects as not-user-provided such that they
get filtered the same as all the other objects.
Altogether, this ends up with the following queries, both of which have
been executed in a well-packed linux.git repository:
# Previous query which uses object names as a heuristic to filter
# non-blob objects, which bars us from using bitmap indices because
# they cannot print paths.
$ time git rev-list --objects --filter=blob:limit=200 \
--object-names --all | sed -r '/^.{,41}$/d' | wc -l
4502300
real 1m23.872s
user 1m30.076s
sys 0m6.002s
# New query.
$ time git rev-list --objects --filter-provided \
--filter=object:type=blob --filter=blob:limit=200 \
--use-bitmap-index --all | wc -l
22585
real 0m19.216s
user 0m16.768s
sys 0m2.450s
So with the new optimized query, we can both significantly reduce the
list of candidate LFS pointers and execution time.
Patrick
Patrick Steinhardt (7):
revision: mark commit parents as NOT_USER_GIVEN
list-objects: move tag processing into its own function
list-objects: support filtering by tag and commit
list-objects: implement object type filter
pack-bitmap: implement object type filter
pack-bitmap: implement combined filter
rev-list: allow filtering of provided items
Documentation/rev-list-options.txt | 3 +
builtin/rev-list.c | 14 ++++
list-objects-filter-options.c | 14 ++++
list-objects-filter-options.h | 8 ++
list-objects-filter.c | 116 ++++++++++++++++++++++++++++
list-objects-filter.h | 2 +
list-objects.c | 32 +++++++-
pack-bitmap.c | 71 +++++++++++++++--
revision.c | 4 +-
revision.h | 3 -
t/t6112-rev-list-filters-objects.sh | 76 ++++++++++++++++++
t/t6113-rev-list-bitmap-filters.sh | 54 ++++++++++++-
12 files changed, 380 insertions(+), 17 deletions(-)
--
2.30.1
From: Patrick Steinhardt <hidden> Date: 2021-03-01 12:21:35
Object filters currently only support filtering blobs or trees based on
some criteria. This commit lays the foundation to also allow filtering
of tags and commits.
No change in behaviour is expected from this commit given that there are
no filters yet for those object types.
Signed-off-by: Patrick Steinhardt <redacted>
---
list-objects-filter.c | 40 ++++++++++++++++++++++++++++++++++++++++
list-objects-filter.h | 2 ++
list-objects.c | 24 +++++++++++++++++++++---
3 files changed, 63 insertions(+), 3 deletions(-)
@@ -82,6 +82,16 @@ static enum list_objects_filter_result filter_blobs_none(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_BEGIN_TREE:assert(obj->type==OBJ_TREE);/* always include all tree objects */
@@ -173,6 +183,16 @@ static enum list_objects_filter_result filter_trees_depth(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_END_TREE:assert(obj->type==OBJ_TREE);filter_data->current_depth--;
@@ -267,6 +287,16 @@ static enum list_objects_filter_result filter_blobs_limit(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_BEGIN_TREE:assert(obj->type==OBJ_TREE);/* always include all tree objects */
@@ -371,6 +401,16 @@ static enum list_objects_filter_result filter_sparse(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_BEGIN_TREE:assert(obj->type==OBJ_TREE);dtype=DT_DIR;
From: Patrick Steinhardt <hidden> Date: 2021-03-01 12:21:40
While it already is possible to filter objects by some criteria in
git-rev-list(1), it is not yet possible to filter out only a specific
type of objects. This makes some filters less useful. The `blob:limit`
filter for example filters blobs such that only those which are smaller
than the given limit are returned. But it is unfit to ask only for these
smallish blobs, given that git-rev-list(1) will continue to print tags,
commits and trees.
Now that we have the infrastructure in place to also filter tags and
commits, we can improve this situation by implementing a new filter
which selects objects based on their type. Above query can thus
trivially be implemented with the following command:
$ git rev-list --objects --filter=object:type=blob \
--filter=blob:limit=200
Furthermore, this filter allows to optimize for certain other cases: if
for example only tags or commits have been selected, there is no need to
walk down trees.
The new filter is not yet supported in bitmaps. This is going to be
implemented in a subsequent commit.
Signed-off-by: Patrick Steinhardt <redacted>
---
Documentation/rev-list-options.txt | 3 ++
list-objects-filter-options.c | 14 ++++++
list-objects-filter-options.h | 2 +
list-objects-filter.c | 76 +++++++++++++++++++++++++++++
t/t6112-rev-list-filters-objects.sh | 48 ++++++++++++++++++
5 files changed, 143 insertions(+)
@@ -892,6 +892,9 @@ or units. n may be zero. The suffixes k, m, and g can be used to name units in KiB, MiB, or GiB. For example, 'blob:limit=1k' is the same as 'blob:limit=1024'. ++The form '--filter=object:type=(tag|commit|tree|blob)' omits all objects+which are not of the requested type.++ The form '--filter=sparse:oid=<blob-ish>' uses a sparse-checkout specification contained in the blob (or blob-expression) '<blob-ish>' to omit blobs that would not be not required for a sparse checkout on
@@ -13,6 +13,7 @@ enum list_objects_filter_choice {LOFC_BLOB_LIMIT,LOFC_TREE_DEPTH,LOFC_SPARSE_OID,+LOFC_OBJECT_TYPE,LOFC_COMBINE,LOFC__COUNT/* must be last */};
@@ -545,6 +545,81 @@ static void filter_sparse_oid__init(filter->free_fn=filter_sparse_free;}+/*+*Afilterforlist-objectstoomitlargeblobs.+*AndtoOPTIONALLYcollectalistoftheomittedOIDs.+*/+structfilter_object_type_data{+enumobject_typeobject_type;+};++staticenumlist_objects_filter_resultfilter_object_type(+structrepository*r,+enumlist_objects_filter_situationfilter_situation,+structobject*obj,+constchar*pathname,+constchar*filename,+structoidset*omits,+void*filter_data_)+{+structfilter_object_type_data*filter_data=filter_data_;++switch(filter_situation){+default:+BUG("unknown filter_situation: %d",filter_situation);++caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+if(filter_data->object_type==OBJ_TAG)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+returnLOFR_MARK_SEEN;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+if(filter_data->object_type==OBJ_COMMIT)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+returnLOFR_MARK_SEEN;++caseLOFS_BEGIN_TREE:+assert(obj->type==OBJ_TREE);++/*+*Ifweonlywanttoshowcommitsortags,thenthereisno+*needtowalkdowntrees.+*/+if(filter_data->object_type==OBJ_COMMIT||+filter_data->object_type==OBJ_TAG)+returnLOFR_SKIP_TREE;++if(filter_data->object_type==OBJ_TREE)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++returnLOFR_MARK_SEEN;++caseLOFS_BLOB:+assert(obj->type==OBJ_BLOB);++if(filter_data->object_type==OBJ_BLOB)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+returnLOFR_MARK_SEEN;++caseLOFS_END_TREE:+returnLOFR_ZERO;+}+}++staticvoidfilter_object_type__init(+structlist_objects_filter_options*filter_options,+structfilter*filter)+{+structfilter_object_type_data*d=xcalloc(1,sizeof(*d));+d->object_type=filter_options->object_type;++filter->filter_data=d;+filter->filter_object_fn=filter_object_type;+filter->free_fn=free;+}+/* A filter which only shows objects shown by all sub-filters. */structcombine_filter_data{structsubfilter*sub;
@@ -159,6 +159,54 @@ test_expect_success 'verify blob:limit=1m' 'test_must_be_emptyobserved'+# Test object:type=<type> filter.++test_expect_success'setup object-type''+gitinitobject-type&&+echocontents>object-type/blob&&+git-Cobject-typeaddblob&&+git-Cobject-typecommit-mcommit-message&&+git-Cobject-typetagtag-mtag-message+'++test_expect_success'verify object:type= fails with invalid type''+test_must_failgit-Cobject-typerev-list--objects--filter=object:type=HEAD&&+test_must_failgit-Cobject-typerev-list--objects--filter=object:type=invalidHEAD+'++test_expect_success'verify object:type=blob prints blob and commit''+(+git-Cobject-typerev-parseHEAD&&+printf"%s blob\n"$(git-Cobject-typerev-parseHEAD:blob)+)>expected&&+git-Cobject-typerev-list--objects--filter=object:type=blobHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tree prints tree and commit''+(+git-Cobject-typerev-parseHEAD&&+printf"%s \n"$(git-Cobject-typerev-parseHEAD^{tree})+)>expected&&+git-Cobject-typerev-list--objects--filter=object:type=treeHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=commit prints commit''+git-Cobject-typerev-parseHEAD>expected&&+git-Cobject-typerev-list--objects--filter=object:type=commitHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tag prints tag''+(+git-Cobject-typerev-parseHEAD&&+printf"%s tag\n"$(git-Cobject-typerev-parsetag)+)>expected&&+git-Cobject-typerev-list--objects--filter=object:type=tagtag>actual&&+test_cmpexpectedactual+'+# Test sparse:path=<path> filter.# !!!!# NOTE: sparse:path filter support has been dropped for security reasons,
From: Patrick Steinhardt <hidden> Date: 2021-03-01 12:22:01
The NOT_USER_GIVEN flag of an object marks whether a flag was explicitly
provided by the user or not. The most important use case for this is
when filtering objects: only objects that were not explicitly requested
will get filtered.
The flag is currently only set for blobs and trees, which has been fine
given that there are no filters for tags or commits currently. We're
about to extend filtering capabilities to add object type filter though,
which requires us to set up the NOT_USER_GIVEN flag correctly -- if it's
not set, the object wouldn't get filtered at all.
Mark unseen commit parents as NOT_USER_GIVEN when processing parents.
Like this, explicitly provided parents stay user-given and thus
unfiltered, while parents which get loaded as part of the graph walk
can be filtered.
This commit shouldn't have any user-visible impact yet as there is no
logic to filter commits yet.
Signed-off-by: Patrick Steinhardt <redacted>
---
revision.c | 4 ++--
revision.h | 3 ---
2 files changed, 2 insertions(+), 5 deletions(-)
From: Patrick Steinhardt <hidden> Date: 2021-03-01 12:22:02
Move processing of tags into its own function to make the logic easier
to extend when we're going to implement filtering for tags. No change in
behaviour is expected from this commit.
Signed-off-by: Patrick Steinhardt <redacted>
---
list-objects.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
From: Patrick Steinhardt <hidden> Date: 2021-03-01 12:22:20
When the user has multiple objects filters specified, then this is
internally represented by having a "combined" filter. These combined
filters aren't yet supported by bitmap indices and can thus not be
accelerated.
Fix this by implementing support for these combined filters. The
implementation is quite trivial: when there's a combined filter, we
simply recurse into `filter_bitmap()` for all of the sub-filters.
Signed-off-by: Patrick Steinhardt <redacted>
---
pack-bitmap.c | 40 +++++++++++++++++++++++++++---
t/t6113-rev-list-bitmap-filters.sh | 7 ++++++
2 files changed, 43 insertions(+), 4 deletions(-)
From: Patrick Steinhardt <hidden> Date: 2021-03-01 12:22:34
The preceding commit has added a new object filter for git-rev-list(1)
which allows to filter objects by type. Implement the equivalent filter
for packfile bitmaps so that we can answer these queries fast.
Signed-off-by: Patrick Steinhardt <redacted>
---
pack-bitmap.c | 28 +++++++++++++++++++++++++---
t/t6113-rev-list-bitmap-filters.sh | 25 ++++++++++++++++++++++++-
2 files changed, 49 insertions(+), 4 deletions(-)
From: Patrick Steinhardt <hidden> Date: 2021-03-01 12:22:50
When providing an object filter, it is currently impossible to also
filter provided items. E.g. when executing `git rev-list HEAD` , the
commit this reference points to will be treated as user-provided and is
thus excluded from the filtering mechanism. This makes it harder than
necessary to properly use the new `--filter=object:type` filter given
that even if the user wants to only see blobs, he'll still see commits
of provided references.
Improve this by introducing a new `--filter-provided` option to the
git-rev-parse(1) command. If given, then all user-provided references
will be subject to filtering.
Signed-off-by: Patrick Steinhardt <redacted>
---
builtin/rev-list.c | 14 ++++++++++++++
list-objects-filter-options.h | 6 ++++++
pack-bitmap.c | 3 ++-
t/t6112-rev-list-filters-objects.sh | 28 ++++++++++++++++++++++++++++
t/t6113-rev-list-bitmap-filters.sh | 22 ++++++++++++++++++++++
5 files changed, 72 insertions(+), 1 deletion(-)
@@ -207,6 +207,34 @@ test_expect_success 'verify object:type=tag prints tag' 'test_cmpexpectedactual'+test_expect_success'verify object:type=blob prints only blob with --filter-provided''+printf"%s blob\n"$(git-Cobject-typerev-parseHEAD:blob)>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=blob--filter-providedHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tree prints only tree with --filter-provided''+printf"%s \n"$(git-Cobject-typerev-parseHEAD^{tree})>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=treeHEAD--filter-provided>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=commit prints only commit with --filter-provided''+git-Cobject-typerev-parseHEAD>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=commit--filter-providedHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tag prints only tag with --filter-provided''+printf"%s tag\n"$(git-Cobject-typerev-parsetag)>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=tag--filter-providedtag>actual&&+test_cmpexpectedactual+'+# Test sparse:path=<path> filter.# !!!!# NOTE: sparse:path filter support has been dropped for security reasons,
From: Jeff King <hidden> Date: 2021-03-10 21:40:41
On Mon, Mar 01, 2021 at 01:20:26PM +0100, Patrick Steinhardt wrote:
Altogether, this ends up with the following queries, both of which have
been executed in a well-packed linux.git repository:
# Previous query which uses object names as a heuristic to filter
# non-blob objects, which bars us from using bitmap indices because
# they cannot print paths.
$ time git rev-list --objects --filter=blob:limit=200 \
--object-names --all | sed -r '/^.{,41}$/d' | wc -l
4502300
real 1m23.872s
user 1m30.076s
sys 0m6.002s
# New query.
$ time git rev-list --objects --filter-provided \
--filter=object:type=blob --filter=blob:limit=200 \
--use-bitmap-index --all | wc -l
22585
real 0m19.216s
user 0m16.768s
sys 0m2.450s
Those produce very different answers. I guess because in the first one,
you still have a bunch of tree objects, too. You'd do much better to get
the actual types from cat-file, and filter on that. That also lets you
use bitmaps for the traversal portion. E.g.:
$ time git rev-list --use-bitmap-index --objects --filter=blob:limit=200 --all |
git cat-file --buffer --batch-check='%(objecttype) %(objectname)' |
perl -lne 'print $1 if /^blob (.*)/' | wc -l
14966
real 0m6.248s
user 0m7.810s
sys 0m0.440s
which is faster than what you showed above (this is on linux.git, but my
result is different; maybe you have more refs than me?). But we should
be able to do better purely internally, so I suspect my computer is just
faster (or maybe your extra refs just aren't well-covered by bitmaps).
Running with your patches I get:
$ time git rev-list --objects --use-bitmap-index --all \
--filter-provided --filter=object:type=blob \
--filter=blob:limit=200 | wc -l
16339
real 0m1.309s
user 0m1.234s
sys 0m0.079s
which is indeed faster. It's quite curious that the answer is not the
same, though! I think yours has some bugs. If I sort and diff the
results, I see some commits mentioned in the output. Perhaps this is
--filter-provided not working, as they all seem to be ref tips.
To be able to more efficiently answer this query, I've implemented
multiple things:
- A new object type filter `--filter=object:type=<type>` for
git-rev-list(1), which is implemented both for normal graph walks and
for the packfile bitmap index.
- Given that above usecase requires two filters (the object type
and blob size filters), bitmap filters were extended to support
combined filters.
That's probably reasonable, especially because it lets us use bitmaps. I
do have a dream that we'll eventually be able to support more extensive
formatting via log/rev-list, which would allow:
git rev-list --use-bitmap-index --objects --all \
--format=%(objecttype) %(objectname) |
perl -ne 'print $1 if /^blob (.*)/'
That should be faster than the separate cat-file (which has to re-lookup
each object, in addition to the extra pipe overhead), but I expect the
--filter solution should always be faster still, as it can very quickly
eliminate the majority of the objects at the bitmap level.
quoted hunk
- git-rev-list(1) doesn't filter user-provided objects and always prints them. I don't want the listed commits though and only their referenced potential LFS blobs. So I've added a new flag `--filter-provided` which marks all provided objects as not-user-provided such that they get filtered the same as all the other objects.
Yeah, this "user-provided" behavior was quite a surprise to me when I
started implementing the bitmap versions of the existing filters. It's
nice to have the option to specify which you want.
-Peff
From: Taylor Blau <hidden> Date: 2021-03-10 21:58:57
On Mon, Mar 01, 2021 at 01:20:26PM +0100, Patrick Steinhardt wrote:
quoted hunk
- A new object type filter `--filter=object:type=<type>` for git-rev-list(1), which is implemented both for normal graph walks and for the packfile bitmap index.
I understand what you're looking for here, but I worry that '--filter'
might be too leaky of an abstraction.
I was a little surprised to learn that you can clone a repository with
--filter=object:type=tree (excluding commits), but it does work. I'm
fine reusing a lot of the object filtering code if it makes this an
easier task, but I think it may be worthwhile to hide this new kind of
filter from upload-pack.
quoted hunk
- Given that above usecase requires two filters (the object type and blob size filters), bitmap filters were extended to support combined filters.
Nice. We didn't do this since the only previously supported filters were
blob:none and tree:0 (the latter implying the former), so there was no
need.
Thanks,
Taylor
From: Jeff King <hidden> Date: 2021-03-10 22:20:24
On Wed, Mar 10, 2021 at 04:58:16PM -0500, Taylor Blau wrote:
On Mon, Mar 01, 2021 at 01:20:26PM +0100, Patrick Steinhardt wrote:
quoted
- A new object type filter `--filter=object:type=<type>` for git-rev-list(1), which is implemented both for normal graph walks and for the packfile bitmap index.
I understand what you're looking for here, but I worry that '--filter'
might be too leaky of an abstraction.
I was a little surprised to learn that you can clone a repository with
--filter=object:type=tree (excluding commits), but it does work. I'm
fine reusing a lot of the object filtering code if it makes this an
easier task, but I think it may be worthwhile to hide this new kind of
filter from upload-pack.
I had a similar thought, but wouldn't the existing uploadpackfilter
config take care of this?
I guess the catch-all "allow" option defaults to "true", so we'd support
any new filters that are added. Which seems like a poor choice in
general, but flipping it would mean that servers have to update their
config.
I do wonder if it's that bad for clients to be able to specify something
like this, though. Even though there's not that much use for it with a
regular partial clone, it could conceivably used for some special cases.
I do think it would be more useful if you could OR together multiple
types. Asking for "commits|tags|trees" is really the same as the already
useful "blob:none". And "commits|tags" is the same as tree:depth=0.
-Peff
From: Patrick Steinhardt <hidden> Date: 2021-03-11 14:38:55
On Wed, Mar 10, 2021 at 04:39:22PM -0500, Jeff King wrote:
On Mon, Mar 01, 2021 at 01:20:26PM +0100, Patrick Steinhardt wrote:
quoted
Altogether, this ends up with the following queries, both of which have
been executed in a well-packed linux.git repository:
# Previous query which uses object names as a heuristic to filter
# non-blob objects, which bars us from using bitmap indices because
# they cannot print paths.
$ time git rev-list --objects --filter=blob:limit=200 \
--object-names --all | sed -r '/^.{,41}$/d' | wc -l
4502300
real 1m23.872s
user 1m30.076s
sys 0m6.002s
# New query.
$ time git rev-list --objects --filter-provided \
--filter=object:type=blob --filter=blob:limit=200 \
--use-bitmap-index --all | wc -l
22585
real 0m19.216s
user 0m16.768s
sys 0m2.450s
Those produce very different answers. I guess because in the first one,
you still have a bunch of tree objects, too. You'd do much better to get
the actual types from cat-file, and filter on that. That also lets you
use bitmaps for the traversal portion. E.g.:
They do provide different answers, and you're right that `--batch-check`
would have helped to filter by type. Your idea doesn't really work in my
usecase though to identify LFS pointers, at least not without additional
tooling on top of what you've provided. There'd at least need to be two
git-cat-file(1) processes: one to do the `--batch-check` thing to
actually filter by object type, and one to then read the actual LFS
pointer candidates from disk in order to see whether they are LFS
pointers or not.
Actually, we currently are doing something similar to that at GitLab: we
list all potential candidates via git-rev-list(1), write the output into
`git-cat-file --batch-check`, and anything that is a blob then gets
forwarded into `git-cat-file --batch`.
$ time git rev-list --use-bitmap-index --objects --filter=blob:limit=200 --all |
git cat-file --buffer --batch-check='%(objecttype) %(objectname)' |
perl -lne 'print $1 if /^blob (.*)/' | wc -l
14966
real 0m6.248s
user 0m7.810s
sys 0m0.440s
which is faster than what you showed above (this is on linux.git, but my
result is different; maybe you have more refs than me?). But we should
be able to do better purely internally, so I suspect my computer is just
faster (or maybe your extra refs just aren't well-covered by bitmaps).
Running with your patches I get:
I've got quite a beefy machine with a Ryzen 3 5800X, and I did do a `git
repack -Adfb` right before doig benchmarks. I do have the stable kernel
repository added though, which accounts for quite a lot of additional
references (3938) and objects (9.3M).
$ time git rev-list --objects --use-bitmap-index --all \
--filter-provided --filter=object:type=blob \
--filter=blob:limit=200 | wc -l
16339
real 0m1.309s
user 0m1.234s
sys 0m0.079s
which is indeed faster. It's quite curious that the answer is not the
same, though! I think yours has some bugs. If I sort and diff the
results, I see some commits mentioned in the output. Perhaps this is
--filter-provided not working, as they all seem to be ref tips.
I noticed it, too, and couldn't yet find an answer why that is.
Honestly, I found the NOT_USER_GIVEN flag quite confusing and I'm not at
all sure whether I've got all cases covered correctly. The previous was
how this was handled (`USER_GIVEN` instead of `NOT_USER_GIVEN`) would've
been easier to figure out for this specific usecase. But I guess it was
converted due to specific reasons.
I'll invest some more time to figure out what's happening here.
quoted
To be able to more efficiently answer this query, I've implemented
multiple things:
- A new object type filter `--filter=object:type=<type>` for
git-rev-list(1), which is implemented both for normal graph walks and
for the packfile bitmap index.
- Given that above usecase requires two filters (the object type
and blob size filters), bitmap filters were extended to support
combined filters.
That's probably reasonable, especially because it lets us use bitmaps. I
do have a dream that we'll eventually be able to support more extensive
formatting via log/rev-list, which would allow:
git rev-list --use-bitmap-index --objects --all \
--format=%(objecttype) %(objectname) |
perl -ne 'print $1 if /^blob (.*)/'
That should be faster than the separate cat-file (which has to re-lookup
each object, in addition to the extra pipe overhead), but I expect the
--filter solution should always be faster still, as it can very quickly
eliminate the majority of the objects at the bitmap level.
That'd be nice, even though it wouldn't help in my particular usecase: I
need to read each candidate blob to see whether it's an LFS pointer or
not anyway.
quoted
- git-rev-list(1) doesn't filter user-provided objects and always prints them. I don't want the listed commits though and only their referenced potential LFS blobs. So I've added a new flag `--filter-provided` which marks all provided objects as not-user-provided such that they get filtered the same as all the other objects.
Yeah, this "user-provided" behavior was quite a surprise to me when I
started implementing the bitmap versions of the existing filters. It's
nice to have the option to specify which you want.
-Peff
From: Patrick Steinhardt <hidden> Date: 2021-03-11 14:44:47
On Wed, Mar 10, 2021 at 05:19:44PM -0500, Jeff King wrote:
On Wed, Mar 10, 2021 at 04:58:16PM -0500, Taylor Blau wrote:
quoted
On Mon, Mar 01, 2021 at 01:20:26PM +0100, Patrick Steinhardt wrote:
quoted
- A new object type filter `--filter=object:type=<type>` for git-rev-list(1), which is implemented both for normal graph walks and for the packfile bitmap index.
I understand what you're looking for here, but I worry that '--filter'
might be too leaky of an abstraction.
I was a little surprised to learn that you can clone a repository with
--filter=object:type=tree (excluding commits), but it does work. I'm
fine reusing a lot of the object filtering code if it makes this an
easier task, but I think it may be worthwhile to hide this new kind of
filter from upload-pack.
I had a similar thought, but wouldn't the existing uploadpackfilter
config take care of this?
I guess the catch-all "allow" option defaults to "true", so we'd support
any new filters that are added. Which seems like a poor choice in
general, but flipping it would mean that servers have to update their
config.
I do wonder if it's that bad for clients to be able to specify something
like this, though. Even though there's not that much use for it with a
regular partial clone, it could conceivably used for some special cases.
I do think it would be more useful if you could OR together multiple
types. Asking for "commits|tags|trees" is really the same as the already
useful "blob:none". And "commits|tags" is the same as tree:depth=0.
I did waste a few thoughts on how this should be handled. I see two ways
of doing it:
- We could just implement the new `object:type` filter such that it
directly supports OR'ing. That's the easy way to do it, but it's
inflexible.
- We could extend combined filters to support OR-semantics in
addition to the current AND-semantics. In the end, that'd be a
much more flexible approach and potentially allow additional
usecases.
I lean more towards the latter as it feels like the better design. But
it's more involved, and I'm not sure I want to do it as part of this
patch series.
Patrick
From: Jeff King <hidden> Date: 2021-03-11 17:55:29
On Thu, Mar 11, 2021 at 03:38:11PM +0100, Patrick Steinhardt wrote:
quoted
Those produce very different answers. I guess because in the first one,
you still have a bunch of tree objects, too. You'd do much better to get
the actual types from cat-file, and filter on that. That also lets you
use bitmaps for the traversal portion. E.g.:
They do provide different answers, and you're right that `--batch-check`
would have helped to filter by type. Your idea doesn't really work in my
usecase though to identify LFS pointers, at least not without additional
tooling on top of what you've provided. There'd at least need to be two
git-cat-file(1) processes: one to do the `--batch-check` thing to
actually filter by object type, and one to then read the actual LFS
pointer candidates from disk in order to see whether they are LFS
pointers or not.
Actually, we currently are doing something similar to that at GitLab: we
list all potential candidates via git-rev-list(1), write the output into
`git-cat-file --batch-check`, and anything that is a blob then gets
forwarded into `git-cat-file --batch`.
You'd need that final cat-file with your patch, too, though. So I think
it makes sense to think about "generate the list of blobs" as the
primary action.
You can of course do the type and content dump as a single cat-file, but
in my experience that is much slower (because we waste time dumping
object content that the caller ultimately won't care about).
Thinking in the opposite direction, if we are filtering by type via
cat-file, we could do the size filter there, too. So:
git rev-list --use-bitmap-index --objects --all |
git cat-file --batch-check='%(objecttype) %(objectsize) %(objectname)' |
perl -lne 'print $2 if /^blob (\d+) (.*)/ && $1 < 200'
which produces the same answer as my earlier:
but takes about twice as long. Which is really just a roundabout way of
saying that yes, shoving things into "rev-list" can provide substantial
speedups. :)
quoted
which is faster than what you showed above (this is on linux.git, but my
result is different; maybe you have more refs than me?). But we should
be able to do better purely internally, so I suspect my computer is just
faster (or maybe your extra refs just aren't well-covered by bitmaps).
Running with your patches I get:
I've got quite a beefy machine with a Ryzen 3 5800X, and I did do a `git
repack -Adfb` right before doig benchmarks. I do have the stable kernel
repository added though, which accounts for quite a lot of additional
references (3938) and objects (9.3M).
Yeah, I wondered if it was something like that. Mine is just
torvalds/linux.git. Fetching stable/linux.git from kernel.org, running
"git repack -adb" on the result, and then repeating my timings gets me
numbers close to yours.
quoted
which is indeed faster. It's quite curious that the answer is not the
same, though! I think yours has some bugs. If I sort and diff the
results, I see some commits mentioned in the output. Perhaps this is
--filter-provided not working, as they all seem to be ref tips.
I noticed it, too, and couldn't yet find an answer why that is.
Honestly, I found the NOT_USER_GIVEN flag quite confusing and I'm not at
all sure whether I've got all cases covered correctly. The previous was
how this was handled (`USER_GIVEN` instead of `NOT_USER_GIVEN`) would've
been easier to figure out for this specific usecase. But I guess it was
converted due to specific reasons.
I'll invest some more time to figure out what's happening here.
Thanks. I also scratched my head at NOT_USER_GIVEN. I haven't looked at
this part of the filter code very much, but it seems like that is a
recipe for accidentally marking a commit as NOT_USER_GIVEN if we
traverse to it (even if it was originally _also_ given by the user).
-Peff
quoted
That's probably reasonable, especially because it lets us use bitmaps. I
do have a dream that we'll eventually be able to support more extensive
formatting via log/rev-list, which would allow:
git rev-list --use-bitmap-index --objects --all \
--format=%(objecttype) %(objectname) |
perl -ne 'print $1 if /^blob (.*)/'
That should be faster than the separate cat-file (which has to re-lookup
each object, in addition to the extra pipe overhead), but I expect the
--filter solution should always be faster still, as it can very quickly
eliminate the majority of the objects at the bitmap level.
That'd be nice, even though it wouldn't help in my particular usecase: I
need to read each candidate blob to see whether it's an LFS pointer or
not anyway.
I think it works out roughly the same as the --filter solution, in the
sense that both generate a list of candidate blobs that you'd read with
"cat-file --batch" (but of course it's still slower).
-Peff
From: Jeff King <hidden> Date: 2021-03-11 17:57:38
On Thu, Mar 11, 2021 at 03:43:39PM +0100, Patrick Steinhardt wrote:
quoted
I do wonder if it's that bad for clients to be able to specify something
like this, though. Even though there's not that much use for it with a
regular partial clone, it could conceivably used for some special cases.
I do think it would be more useful if you could OR together multiple
types. Asking for "commits|tags|trees" is really the same as the already
useful "blob:none". And "commits|tags" is the same as tree:depth=0.
I did waste a few thoughts on how this should be handled. I see two ways
of doing it:
- We could just implement the new `object:type` filter such that it
directly supports OR'ing. That's the easy way to do it, but it's
inflexible.
- We could extend combined filters to support OR-semantics in
addition to the current AND-semantics. In the end, that'd be a
much more flexible approach and potentially allow additional
usecases.
I lean more towards the latter as it feels like the better design. But
it's more involved, and I'm not sure I want to do it as part of this
patch series.
Yeah, I don't think that needs to be part of this series. The only thing
to consider for this series is whether it's a problem for clients to be
able to ask for type=blob from a server which has blindly turned on
uploadpack.allowFilter without restricting the types.
My gut is to say yes. Even if we don't have a particular use, I don't
think it hurts (and in general, I think people running public servers
with bitmaps really ought to set uploadpackfilter.allow=false anyway,
because stuff like non-zero tree-depth filters are expensive).
-Peff
From: Patrick Steinhardt <hidden> Date: 2021-03-15 11:26:37
On Wed, Mar 10, 2021 at 04:39:22PM -0500, Jeff King wrote:
On Mon, Mar 01, 2021 at 01:20:26PM +0100, Patrick Steinhardt wrote:
quoted
Altogether, this ends up with the following queries, both of which have
been executed in a well-packed linux.git repository:
# Previous query which uses object names as a heuristic to filter
# non-blob objects, which bars us from using bitmap indices because
# they cannot print paths.
$ time git rev-list --objects --filter=blob:limit=200 \
--object-names --all | sed -r '/^.{,41}$/d' | wc -l
4502300
real 1m23.872s
user 1m30.076s
sys 0m6.002s
# New query.
$ time git rev-list --objects --filter-provided \
--filter=object:type=blob --filter=blob:limit=200 \
--use-bitmap-index --all | wc -l
22585
real 0m19.216s
user 0m16.768s
sys 0m2.450s
Those produce very different answers. I guess because in the first one,
you still have a bunch of tree objects, too. You'd do much better to get
the actual types from cat-file, and filter on that. That also lets you
use bitmaps for the traversal portion. E.g.:
$ time git rev-list --use-bitmap-index --objects --filter=blob:limit=200 --all |
git cat-file --buffer --batch-check='%(objecttype) %(objectname)' |
perl -lne 'print $1 if /^blob (.*)/' | wc -l
14966
real 0m6.248s
user 0m7.810s
sys 0m0.440s
which is faster than what you showed above (this is on linux.git, but my
result is different; maybe you have more refs than me?). But we should
be able to do better purely internally, so I suspect my computer is just
faster (or maybe your extra refs just aren't well-covered by bitmaps).
Running with your patches I get:
$ time git rev-list --objects --use-bitmap-index --all \
--filter-provided --filter=object:type=blob \
--filter=blob:limit=200 | wc -l
16339
real 0m1.309s
user 0m1.234s
sys 0m0.079s
which is indeed faster. It's quite curious that the answer is not the
same, though! I think yours has some bugs. If I sort and diff the
results, I see some commits mentioned in the output. Perhaps this is
--filter-provided not working, as they all seem to be ref tips.
[snip]
I've found the issue: when converting filters to a combined filter via
`transform_to_combine_type()`, we reset the top-level filter via a call
to `memset()`. So for combined filters, the option wouldn't have taken
any effect because it got reset iff the `--filter-provided` option comes
before the second filter.
Patrick
From: Patrick Steinhardt <hidden> Date: 2021-03-15 13:15:25
Hi,
this is the second version of my patch series which implements a new
`object:type` filter for git-rev-parse(1) and git-upload-pack(1) and
extends support for bitmap indices to work with combined filters.
Changes compared to v1:
- I've added a patch up front which changes the uploadpack
documentation to explicitly document that setting
`uploadpackfilter.allow=true` will enable all future filters. I'm
not yet saying that this is the correct thing to do, but rather
added this patch such that we have a proper place to discuss this
topic. In the context of object-type filters, I do think though
that it's not an issue to default-enable type filters: they're not
expensive to compute anyway.
- `uploadpackfilter.<filter>.allow` documentation was updated to
mention the new filter.
- A bug was fixed which caused us to reset `--filter-allowed` in
case a normal filter was converted to a combined filter. I've
added tests to more thoroughly verify that filters work as
expected and also filter provided objects.
Please see the attached range-diff for more details.
Patrick
Patrick Steinhardt (8):
uploadpack.txt: document implication of `uploadpackfilter.allow`
revision: mark commit parents as NOT_USER_GIVEN
list-objects: move tag processing into its own function
list-objects: support filtering by tag and commit
list-objects: implement object type filter
pack-bitmap: implement object type filter
pack-bitmap: implement combined filter
rev-list: allow filtering of provided items
Documentation/config/uploadpack.txt | 9 ++-
Documentation/rev-list-options.txt | 3 +
builtin/rev-list.c | 14 ++++
list-objects-filter-options.c | 18 +++++
list-objects-filter-options.h | 8 ++
list-objects-filter.c | 116 ++++++++++++++++++++++++++++
list-objects-filter.h | 2 +
list-objects.c | 32 +++++++-
pack-bitmap.c | 71 +++++++++++++++--
revision.c | 4 +-
revision.h | 3 -
t/t6112-rev-list-filters-objects.sh | 76 ++++++++++++++++++
t/t6113-rev-list-bitmap-filters.sh | 68 +++++++++++++++-
13 files changed, 403 insertions(+), 21 deletions(-)
Range-diff against v1:
-: ---------- > 1: 270ff80dac uploadpack.txt: document implication of `uploadpackfilter.allow`
1: f2ce5dac89 = 2: ddbec75986 revision: mark commit parents as NOT_USER_GIVEN
2: 9feadba124 = 3: d8da0b24f4 list-objects: move tag processing into its own function
3: 4aa13ee83f = 4: 5545c189c5 list-objects: support filtering by tag and commit
4: 01b9fdbb9c ! 5: acf01472af list-objects: implement object type filter
@@ Commit message
Signed-off-by: Patrick Steinhardt [off-list ref]
+ ## Documentation/config/uploadpack.txt ##
+@@ Documentation/config/uploadpack.txt: uploadpackfilter.allow::
+ uploadpackfilter.<filter>.allow::
+ Explicitly allow or ban the object filter corresponding to
+ `<filter>`, where `<filter>` may be one of: `blob:none`,
+- `blob:limit`, `tree`, `sparse:oid`, or `combine`. If using
+- combined filters, both `combine` and all of the nested filter
+- kinds must be allowed. Defaults to `uploadpackfilter.allow`.
++ `blob:limit`, `object:type`, `tree`, `sparse:oid`, or `combine`.
++ If using combined filters, both `combine` and all of the nested
++ filter kinds must be allowed. Defaults to `uploadpackfilter.allow`.
+
+ uploadpackfilter.tree.maxDepth::
+ Only allow `--filter=tree:<n>` when `<n>` is no more than the value of
+
## Documentation/rev-list-options.txt ##
@@ Documentation/rev-list-options.txt: or units. n may be zero. The suffixes k, m, and g can be used to name
units in KiB, MiB, or GiB. For example, 'blob:limit=1k' is the same
5: c97fd28d8f = 6: 8073ab665b pack-bitmap: implement object type filter
6: fe2b7a1e55 = 7: fac3477d97 pack-bitmap: implement combined filter
7: b43bf401df ! 8: 0e26fee8b3 rev-list: allow filtering of provided items
@@ builtin/rev-list.c: int cmd_rev_list(int argc, const char **argv, const char *pr
oidset_init(&omitted_objects, DEFAULT_OIDSET_SIZE);
if (arg_missing_action == MA_PRINT)
+ ## list-objects-filter-options.c ##
+@@ list-objects-filter-options.c: static void transform_to_combine_type(
+ memset(filter_options, 0, sizeof(*filter_options));
+ filter_options->sub = sub_array;
+ filter_options->sub_alloc = initial_sub_alloc;
++ filter_options->filter_wants = sub_array[0].filter_wants;
+ }
+ filter_options->sub_nr = 1;
+ filter_options->choice = LOFC_COMBINE;
+@@ list-objects-filter-options.c: void parse_list_objects_filter(
+ parse_error = gently_parse_list_objects_filter(
+ &filter_options->sub[filter_options->sub_nr - 1], arg,
+ &errbuf);
++ if (!parse_error)
++ filter_options->sub[filter_options->sub_nr - 1].filter_wants =
++ filter_options->filter_wants;
+ }
+ if (parse_error)
+ die("%s", errbuf.buf);
+
## list-objects-filter-options.h ##
@@ list-objects-filter-options.h: struct list_objects_filter_options {
*/
@@ t/t6113-rev-list-bitmap-filters.sh: test_expect_success 'object:type filter' '
'
+test_expect_success 'object:type filter with --filter-provided' '
-+ git rev-list --objects --filter=object:type=tag --filter-provided tag >expect &&
++ git rev-list --objects --filter-provided --filter=object:type=tag tag >expect &&
+ git rev-list --use-bitmap-index \
-+ --objects --filter=object:type=tag --filter-provided tag >actual &&
++ --objects --filter-provided --filter=object:type=tag tag >actual &&
+ test_cmp expect actual &&
+
-+ git rev-list --objects --filter=object:type=commit --filter-provided tag >expect &&
++ git rev-list --objects --filter-provided --filter=object:type=commit tag >expect &&
+ git rev-list --use-bitmap-index \
-+ --objects --filter=object:type=commit --filter-provided tag >actual &&
++ --objects --filter-provided --filter=object:type=commit tag >actual &&
+ test_bitmap_traversal expect actual &&
+
-+ git rev-list --objects --filter=object:type=tree --filter-provided tag >expect &&
++ git rev-list --objects --filter-provided --filter=object:type=tree tag >expect &&
+ git rev-list --use-bitmap-index \
-+ --objects --filter=object:type=tree --filter-provided tag >actual &&
++ --objects --filter-provided --filter=object:type=tree tag >actual &&
+ test_bitmap_traversal expect actual &&
+
-+ git rev-list --objects --filter=object:type=blob --filter-provided tag >expect &&
++ git rev-list --objects --filter-provided --filter=object:type=blob tag >expect &&
+ git rev-list --use-bitmap-index \
-+ --objects --filter=object:type=blob --filter-provided tag >actual &&
++ --objects --filter-provided --filter=object:type=blob tag >actual &&
+ test_bitmap_traversal expect actual
+'
+
test_expect_success 'combine filter' '
git rev-list --objects --filter=blob:limit=1000 --filter=object:type=blob tag >expect &&
git rev-list --use-bitmap-index \
+@@ t/t6113-rev-list-bitmap-filters.sh: test_expect_success 'combine filter' '
+ test_bitmap_traversal expect actual
+ '
+
++test_expect_success 'combine filter with --filter-provided' '
++ git rev-list --objects --filter-provided --filter=blob:limit=1000 --filter=object:type=blob tag >expect &&
++ git rev-list --use-bitmap-index \
++ --objects --filter-provided --filter=blob:limit=1000 --filter=object:type=blob tag >actual &&
++ test_bitmap_traversal expect actual &&
++
++ git cat-file --batch-check="%(objecttype) %(objectsize)" <actual >objects &&
++ while read objecttype objectsize
++ do
++ test "$objecttype" = blob || return 1
++ test "$objectsize" -le 1000 || return 1
++ done <objects
++'
++
+ test_done
--
2.30.2
From: Patrick Steinhardt <hidden> Date: 2021-03-15 13:15:26
When `uploadpackfilter.allow` is set to `true`, it means that filters
are enabled by default except in the case where a filter is explicitly
disabled via `uploadpackilter.<filter>.allow`. This option will not only
enable the currently supported set of filters, but also any filters
which get added in the future. As such, an admin which wants to have
tight control over which filters are allowed and which aren't probably
shouldn't ever set `uploadpackfilter.allow=true`.
Amend the documentation to make the ramifications more explicit so that
admins are aware of this.
Signed-off-by: Patrick Steinhardt <redacted>
---
Documentation/config/uploadpack.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
@@ -59,7 +59,8 @@ uploadpack.allowFilter:: uploadpackfilter.allow:: Provides a default value for unspecified object filters (see: the- below configuration variable).+ below configuration variable). If set to `true`, this will also+ enable all filters which get added in the future. Defaults to `true`. uploadpackfilter.<filter>.allow::
From: Patrick Steinhardt <hidden> Date: 2021-03-15 13:15:26
The NOT_USER_GIVEN flag of an object marks whether a flag was explicitly
provided by the user or not. The most important use case for this is
when filtering objects: only objects that were not explicitly requested
will get filtered.
The flag is currently only set for blobs and trees, which has been fine
given that there are no filters for tags or commits currently. We're
about to extend filtering capabilities to add object type filter though,
which requires us to set up the NOT_USER_GIVEN flag correctly -- if it's
not set, the object wouldn't get filtered at all.
Mark unseen commit parents as NOT_USER_GIVEN when processing parents.
Like this, explicitly provided parents stay user-given and thus
unfiltered, while parents which get loaded as part of the graph walk
can be filtered.
This commit shouldn't have any user-visible impact yet as there is no
logic to filter commits yet.
Signed-off-by: Patrick Steinhardt <redacted>
---
revision.c | 4 ++--
revision.h | 3 ---
2 files changed, 2 insertions(+), 5 deletions(-)
From: Patrick Steinhardt <hidden> Date: 2021-03-15 13:15:27
Object filters currently only support filtering blobs or trees based on
some criteria. This commit lays the foundation to also allow filtering
of tags and commits.
No change in behaviour is expected from this commit given that there are
no filters yet for those object types.
Signed-off-by: Patrick Steinhardt <redacted>
---
list-objects-filter.c | 40 ++++++++++++++++++++++++++++++++++++++++
list-objects-filter.h | 2 ++
list-objects.c | 24 +++++++++++++++++++++---
3 files changed, 63 insertions(+), 3 deletions(-)
@@ -82,6 +82,16 @@ static enum list_objects_filter_result filter_blobs_none(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_BEGIN_TREE:assert(obj->type==OBJ_TREE);/* always include all tree objects */
@@ -173,6 +183,16 @@ static enum list_objects_filter_result filter_trees_depth(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_END_TREE:assert(obj->type==OBJ_TREE);filter_data->current_depth--;
@@ -267,6 +287,16 @@ static enum list_objects_filter_result filter_blobs_limit(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_BEGIN_TREE:assert(obj->type==OBJ_TREE);/* always include all tree objects */
@@ -371,6 +401,16 @@ static enum list_objects_filter_result filter_sparse(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_BEGIN_TREE:assert(obj->type==OBJ_TREE);dtype=DT_DIR;
From: Patrick Steinhardt <hidden> Date: 2021-03-15 13:15:27
Move processing of tags into its own function to make the logic easier
to extend when we're going to implement filtering for tags. No change in
behaviour is expected from this commit.
Signed-off-by: Patrick Steinhardt <redacted>
---
list-objects.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
From: Patrick Steinhardt <hidden> Date: 2021-03-15 13:15:57
While it already is possible to filter objects by some criteria in
git-rev-list(1), it is not yet possible to filter out only a specific
type of objects. This makes some filters less useful. The `blob:limit`
filter for example filters blobs such that only those which are smaller
than the given limit are returned. But it is unfit to ask only for these
smallish blobs, given that git-rev-list(1) will continue to print tags,
commits and trees.
Now that we have the infrastructure in place to also filter tags and
commits, we can improve this situation by implementing a new filter
which selects objects based on their type. Above query can thus
trivially be implemented with the following command:
$ git rev-list --objects --filter=object:type=blob \
--filter=blob:limit=200
Furthermore, this filter allows to optimize for certain other cases: if
for example only tags or commits have been selected, there is no need to
walk down trees.
The new filter is not yet supported in bitmaps. This is going to be
implemented in a subsequent commit.
Signed-off-by: Patrick Steinhardt <redacted>
---
Documentation/config/uploadpack.txt | 6 +--
Documentation/rev-list-options.txt | 3 ++
list-objects-filter-options.c | 14 ++++++
list-objects-filter-options.h | 2 +
list-objects-filter.c | 76 +++++++++++++++++++++++++++++
t/t6112-rev-list-filters-objects.sh | 48 ++++++++++++++++++
6 files changed, 146 insertions(+), 3 deletions(-)
@@ -66,9 +66,9 @@ uploadpackfilter.allow:: uploadpackfilter.<filter>.allow:: Explicitly allow or ban the object filter corresponding to `<filter>`, where `<filter>` may be one of: `blob:none`,- `blob:limit`, `tree`, `sparse:oid`, or `combine`. If using- combined filters, both `combine` and all of the nested filter- kinds must be allowed. Defaults to `uploadpackfilter.allow`.+ `blob:limit`, `object:type`, `tree`, `sparse:oid`, or `combine`.+ If using combined filters, both `combine` and all of the nested+ filter kinds must be allowed. Defaults to `uploadpackfilter.allow`. uploadpackfilter.tree.maxDepth:: Only allow `--filter=tree:<n>` when `<n>` is no more than the value of
@@ -892,6 +892,9 @@ or units. n may be zero. The suffixes k, m, and g can be used to name units in KiB, MiB, or GiB. For example, 'blob:limit=1k' is the same as 'blob:limit=1024'. ++The form '--filter=object:type=(tag|commit|tree|blob)' omits all objects+which are not of the requested type.++ The form '--filter=sparse:oid=<blob-ish>' uses a sparse-checkout specification contained in the blob (or blob-expression) '<blob-ish>' to omit blobs that would not be not required for a sparse checkout on
@@ -13,6 +13,7 @@ enum list_objects_filter_choice {LOFC_BLOB_LIMIT,LOFC_TREE_DEPTH,LOFC_SPARSE_OID,+LOFC_OBJECT_TYPE,LOFC_COMBINE,LOFC__COUNT/* must be last */};
@@ -545,6 +545,81 @@ static void filter_sparse_oid__init(filter->free_fn=filter_sparse_free;}+/*+*Afilterforlist-objectstoomitlargeblobs.+*AndtoOPTIONALLYcollectalistoftheomittedOIDs.+*/+structfilter_object_type_data{+enumobject_typeobject_type;+};++staticenumlist_objects_filter_resultfilter_object_type(+structrepository*r,+enumlist_objects_filter_situationfilter_situation,+structobject*obj,+constchar*pathname,+constchar*filename,+structoidset*omits,+void*filter_data_)+{+structfilter_object_type_data*filter_data=filter_data_;++switch(filter_situation){+default:+BUG("unknown filter_situation: %d",filter_situation);++caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+if(filter_data->object_type==OBJ_TAG)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+returnLOFR_MARK_SEEN;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+if(filter_data->object_type==OBJ_COMMIT)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+returnLOFR_MARK_SEEN;++caseLOFS_BEGIN_TREE:+assert(obj->type==OBJ_TREE);++/*+*Ifweonlywanttoshowcommitsortags,thenthereisno+*needtowalkdowntrees.+*/+if(filter_data->object_type==OBJ_COMMIT||+filter_data->object_type==OBJ_TAG)+returnLOFR_SKIP_TREE;++if(filter_data->object_type==OBJ_TREE)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++returnLOFR_MARK_SEEN;++caseLOFS_BLOB:+assert(obj->type==OBJ_BLOB);++if(filter_data->object_type==OBJ_BLOB)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+returnLOFR_MARK_SEEN;++caseLOFS_END_TREE:+returnLOFR_ZERO;+}+}++staticvoidfilter_object_type__init(+structlist_objects_filter_options*filter_options,+structfilter*filter)+{+structfilter_object_type_data*d=xcalloc(1,sizeof(*d));+d->object_type=filter_options->object_type;++filter->filter_data=d;+filter->filter_object_fn=filter_object_type;+filter->free_fn=free;+}+/* A filter which only shows objects shown by all sub-filters. */structcombine_filter_data{structsubfilter*sub;
@@ -159,6 +159,54 @@ test_expect_success 'verify blob:limit=1m' 'test_must_be_emptyobserved'+# Test object:type=<type> filter.++test_expect_success'setup object-type''+gitinitobject-type&&+echocontents>object-type/blob&&+git-Cobject-typeaddblob&&+git-Cobject-typecommit-mcommit-message&&+git-Cobject-typetagtag-mtag-message+'++test_expect_success'verify object:type= fails with invalid type''+test_must_failgit-Cobject-typerev-list--objects--filter=object:type=HEAD&&+test_must_failgit-Cobject-typerev-list--objects--filter=object:type=invalidHEAD+'++test_expect_success'verify object:type=blob prints blob and commit''+(+git-Cobject-typerev-parseHEAD&&+printf"%s blob\n"$(git-Cobject-typerev-parseHEAD:blob)+)>expected&&+git-Cobject-typerev-list--objects--filter=object:type=blobHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tree prints tree and commit''+(+git-Cobject-typerev-parseHEAD&&+printf"%s \n"$(git-Cobject-typerev-parseHEAD^{tree})+)>expected&&+git-Cobject-typerev-list--objects--filter=object:type=treeHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=commit prints commit''+git-Cobject-typerev-parseHEAD>expected&&+git-Cobject-typerev-list--objects--filter=object:type=commitHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tag prints tag''+(+git-Cobject-typerev-parseHEAD&&+printf"%s tag\n"$(git-Cobject-typerev-parsetag)+)>expected&&+git-Cobject-typerev-list--objects--filter=object:type=tagtag>actual&&+test_cmpexpectedactual+'+# Test sparse:path=<path> filter.# !!!!# NOTE: sparse:path filter support has been dropped for security reasons,
From: Patrick Steinhardt <hidden> Date: 2021-03-15 13:15:59
When the user has multiple objects filters specified, then this is
internally represented by having a "combined" filter. These combined
filters aren't yet supported by bitmap indices and can thus not be
accelerated.
Fix this by implementing support for these combined filters. The
implementation is quite trivial: when there's a combined filter, we
simply recurse into `filter_bitmap()` for all of the sub-filters.
Signed-off-by: Patrick Steinhardt <redacted>
---
pack-bitmap.c | 40 +++++++++++++++++++++++++++---
t/t6113-rev-list-bitmap-filters.sh | 7 ++++++
2 files changed, 43 insertions(+), 4 deletions(-)
From: Patrick Steinhardt <hidden> Date: 2021-03-15 13:16:01
When providing an object filter, it is currently impossible to also
filter provided items. E.g. when executing `git rev-list HEAD` , the
commit this reference points to will be treated as user-provided and is
thus excluded from the filtering mechanism. This makes it harder than
necessary to properly use the new `--filter=object:type` filter given
that even if the user wants to only see blobs, he'll still see commits
of provided references.
Improve this by introducing a new `--filter-provided` option to the
git-rev-parse(1) command. If given, then all user-provided references
will be subject to filtering.
Signed-off-by: Patrick Steinhardt <redacted>
---
builtin/rev-list.c | 14 +++++++++++
list-objects-filter-options.c | 4 ++++
list-objects-filter-options.h | 6 +++++
pack-bitmap.c | 3 ++-
t/t6112-rev-list-filters-objects.sh | 28 ++++++++++++++++++++++
t/t6113-rev-list-bitmap-filters.sh | 36 +++++++++++++++++++++++++++++
6 files changed, 90 insertions(+), 1 deletion(-)
@@ -207,6 +207,34 @@ test_expect_success 'verify object:type=tag prints tag' 'test_cmpexpectedactual'+test_expect_success'verify object:type=blob prints only blob with --filter-provided''+printf"%s blob\n"$(git-Cobject-typerev-parseHEAD:blob)>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=blob--filter-providedHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tree prints only tree with --filter-provided''+printf"%s \n"$(git-Cobject-typerev-parseHEAD^{tree})>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=treeHEAD--filter-provided>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=commit prints only commit with --filter-provided''+git-Cobject-typerev-parseHEAD>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=commit--filter-providedHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tag prints only tag with --filter-provided''+printf"%s tag\n"$(git-Cobject-typerev-parsetag)>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=tag--filter-providedtag>actual&&+test_cmpexpectedactual+'+# Test sparse:path=<path> filter.# !!!!# NOTE: sparse:path filter support has been dropped for security reasons,
From: Patrick Steinhardt <hidden> Date: 2021-03-15 13:16:03
The preceding commit has added a new object filter for git-rev-list(1)
which allows to filter objects by type. Implement the equivalent filter
for packfile bitmaps so that we can answer these queries fast.
Signed-off-by: Patrick Steinhardt <redacted>
---
pack-bitmap.c | 28 +++++++++++++++++++++++++---
t/t6113-rev-list-bitmap-filters.sh | 25 ++++++++++++++++++++++++-
2 files changed, 49 insertions(+), 4 deletions(-)
From: Jeff King <hidden> Date: 2021-04-06 17:17:36
On Mon, Mar 15, 2021 at 02:14:31PM +0100, Patrick Steinhardt wrote:
When `uploadpackfilter.allow` is set to `true`, it means that filters
are enabled by default except in the case where a filter is explicitly
disabled via `uploadpackilter.<filter>.allow`. This option will not only
enable the currently supported set of filters, but also any filters
which get added in the future. As such, an admin which wants to have
tight control over which filters are allowed and which aren't probably
shouldn't ever set `uploadpackfilter.allow=true`.
Amend the documentation to make the ramifications more explicit so that
admins are aware of this.
It might help to guide the admin a bit more here. What are we really
worried about? Probably that an expensive filter would be added that
would make an admin with a public-facing server unhappy.
Maybe we should be more explicit about our recommendations, like:
This defaults to `true` for historical reasons, but that includes
expensive-to-compute filters (both existing ones like `sparse`, but
also future ones). A safer value is to set this to `false` and
mark individual filters as allowed.
But then of course somebody wonders which set are expensive and which
ones are not. And really, "expensive" here is not that expensive. It is
"do not support bitmaps".
So I wonder if this concern is overblown in the first place. People who
care about using only bitmap-supported filters probably already set this
to "false". And vaguely calling things "expensive" is probably being
overly scary. But in that case, I'm not sure we even need to add a
reminder that future ones will also be enabled (OTOH, I do not mind it
so much; it is encouraging people to set this to false and mark
individual ones as allowed).
-Peff
From: Jeff King <hidden> Date: 2021-04-06 17:31:01
On Mon, Mar 15, 2021 at 02:14:36PM +0100, Patrick Steinhardt wrote:
The NOT_USER_GIVEN flag of an object marks whether a flag was explicitly
provided by the user or not. The most important use case for this is
when filtering objects: only objects that were not explicitly requested
will get filtered.
The flag is currently only set for blobs and trees, which has been fine
given that there are no filters for tags or commits currently. We're
about to extend filtering capabilities to add object type filter though,
which requires us to set up the NOT_USER_GIVEN flag correctly -- if it's
not set, the object wouldn't get filtered at all.
Mark unseen commit parents as NOT_USER_GIVEN when processing parents.
Like this, explicitly provided parents stay user-given and thus
unfiltered, while parents which get loaded as part of the graph walk
can be filtered.
This commit shouldn't have any user-visible impact yet as there is no
logic to filter commits yet.
I'm still scratching my head a bit to understand how NOT_USER_GIVEN can
possibly be correct (as opposed to USER_GIVEN). If we visit the commit
in a not-user-given context and add the flag, how do we know it wasn't
_also_ visited in a user-given context?
Just guessing, but perhaps the SEEN flag is saving us here? If we visit
the user-given commit itself first, then we give it the SEEN flag. Then
if we try to visit it again via parent traversal, we've already
processed it and don't add the NOT_USER_GIVEN flag here.
That seems the opposite of the order we'd usually traverse, but I think
we set SEEN on each commit in prepare_revision_walk(), before we do any
traversing.
So I _think_ it all works even with your changes here, but I have to say
this NOT_USER_GIVEN thing seems really fragile to me. Not new in your
series, of course, but something we may want to look at.
Just grepping around, "rev-list -g" will happily remove SEEN flags, so I
suspect it interacts badly with --filter. Just trying "rev-list -g
--objects --filter=object:type=blob HEAD" shows that it produces quite a
lot of commits (which I think is a more fundamental problem: it is not
walking the parent chain at all to assign these NOT_USER_GIVEN flags).
-Peff
From: Jeff King <hidden> Date: 2021-04-06 17:39:22
On Mon, Mar 15, 2021 at 02:14:40PM +0100, Patrick Steinhardt wrote:
Move processing of tags into its own function to make the logic easier
to extend when we're going to implement filtering for tags. No change in
behaviour is expected from this commit.
Makes sense. Even without extending the logic, it is nice to see the
symmetric with the tree/blob paths.
Although I think it's not quite symmetric in practice...
I'm skeptical that "base" will ever be meaningful here (as it would be
for trees and blobs), because we are never recursing a tree to hit a
tag. We do later pass it to filter_object(), but I think it will always
be the empty string (we even assert(base->len == 0) in the caller).
So I am tempted to say it should not take a base parameter at all, and
later the call to filter_object() added to process_tag() should just
pass an empty string as the base. That would make it clear we do not
expect any kind of "base". That's mostly academic, but I think it also
makes clear that the "name" field is not something that should be
appended to the base (unlike trees and blobs, it is the name we got from
the top-level parsing, not a pathname).
-Peff
From: Jeff King <hidden> Date: 2021-04-06 17:42:59
On Mon, Mar 15, 2021 at 02:14:50PM +0100, Patrick Steinhardt wrote:
While it already is possible to filter objects by some criteria in
git-rev-list(1), it is not yet possible to filter out only a specific
type of objects. This makes some filters less useful. The `blob:limit`
filter for example filters blobs such that only those which are smaller
than the given limit are returned. But it is unfit to ask only for these
smallish blobs, given that git-rev-list(1) will continue to print tags,
commits and trees.
Now that we have the infrastructure in place to also filter tags and
commits, we can improve this situation by implementing a new filter
which selects objects based on their type. Above query can thus
trivially be implemented with the following command:
$ git rev-list --objects --filter=object:type=blob \
--filter=blob:limit=200
Furthermore, this filter allows to optimize for certain other cases: if
for example only tags or commits have been selected, there is no need to
walk down trees.
Makes sense, and the implementation looks reasonable to me.
-Peff
From: Jeff King <hidden> Date: 2021-04-06 17:48:56
On Mon, Mar 15, 2021 at 02:14:55PM +0100, Patrick Steinhardt wrote:
The preceding commit has added a new object filter for git-rev-list(1)
which allows to filter objects by type. Implement the equivalent filter
for packfile bitmaps so that we can answer these queries fast.
Makes sense. The implementation looks pretty sensible. One observation:
quoted hunk
+static void filter_bitmap_object_type(struct bitmap_index *bitmap_git,+ struct object_list *tip_objects,+ struct bitmap *to_filter,+ enum object_type object_type)+{+ enum object_type t;++ if (object_type < OBJ_COMMIT || object_type > OBJ_TAG)+ BUG("filter_bitmap_object_type given invalid object");++ for (t = OBJ_COMMIT; t <= OBJ_TAG; t++) {+ if (t == object_type)+ continue;+ filter_bitmap_exclude_type(bitmap_git, tip_objects, to_filter, t);+ }+}
The OBJ_* constants are a contiguous set between COMMIT and TAG, and it
has to remain this way (because we use them to decipher the type fields
in pack files). But I don't think we've generally baked that assumption
into the code in this way.
Writing it out long-hand would be something like:
if (t != OBJ_COMMIT)
filter_bitmap_exclude_type(bitmap_git, tip_objects, to_filter, OBJ_COMMIT);
if (t != OBJ_TREE)
filter_bitmap_exclude_type(bitmap_git, tip_objects, to_filter, OBJ_TREE);
and so on, which isn't too bad. I dunno. That may be overly picky.
-Peff
From: Jeff King <hidden> Date: 2021-04-06 17:54:34
On Mon, Mar 15, 2021 at 02:14:59PM +0100, Patrick Steinhardt wrote:
When the user has multiple objects filters specified, then this is
internally represented by having a "combined" filter. These combined
filters aren't yet supported by bitmap indices and can thus not be
accelerated.
Fix this by implementing support for these combined filters. The
implementation is quite trivial: when there's a combined filter, we
simply recurse into `filter_bitmap()` for all of the sub-filters.
The goal makes sense.
Before this patch, I think your test:
would pass anyway, because we'd just skip using bitmaps. Is there a way
we can tell that the bitmap code actually kicked in? Maybe a perf test
would make it clear (those aren't always run, but hopefully we'd
eventually notice a regression there).
quoted hunk
+static int filter_supported(struct list_objects_filter_options *filter)+{+ int i;++ switch (filter->choice) {+ case LOFC_BLOB_NONE:+ case LOFC_BLOB_LIMIT:+ case LOFC_OBJECT_TYPE:+ return 1;+ case LOFC_TREE_DEPTH:+ if (filter->tree_exclude_depth == 0)+ return 1;+ return 0;+ case LOFC_COMBINE:+ for (i = 0; i < filter->sub_nr; i++)+ if (!filter_supported(&filter->sub[i]))+ return 0;+ return 1;+ default:+ return 0;+ }+}
Hmm. This is essentially reproducing the list in filter_bitmap() of
what's OK for bitmaps. So when adding a new filter, it would have to be
added in both places.
Can we preserve that property of the original code? I'd think that just
adding LOFC_COMBINE to filter_bitmap() would be sufficient. I.e., this
hunk:
quoted hunk
+ if (filter->choice == LOFC_COMBINE) {+ int i;+ for (i = 0; i < filter->sub_nr; i++) {+ filter_bitmap(bitmap_git, tip_objects, to_filter,+ &filter->sub[i]);+ }+ return 0;+ }
...except that we need to see if filter_bitmap() returns "-1" for any of
the recursive calls. Which we probably should be doing anyway to
propagate any errors (though I think the only "errors" we'd return are
"not supported", at least for now).
-Peff
From: Jeff King <hidden> Date: 2021-04-06 18:04:19
On Mon, Mar 15, 2021 at 02:15:05PM +0100, Patrick Steinhardt wrote:
When providing an object filter, it is currently impossible to also
filter provided items. E.g. when executing `git rev-list HEAD` , the
commit this reference points to will be treated as user-provided and is
thus excluded from the filtering mechanism. This makes it harder than
necessary to properly use the new `--filter=object:type` filter given
that even if the user wants to only see blobs, he'll still see commits
of provided references.
Improve this by introducing a new `--filter-provided` option to the
git-rev-parse(1) command. If given, then all user-provided references
will be subject to filtering.
I think this option is a good thing to have.
The name seems a little confusing to me, as I can read is as both
"please filter the provided objects" and "a filter has been provided".
I guess "--filter-print-provided" would be more clear. And also the
default, so you'd want "--no-filter-print-provided". That's kind of
clunky, though. Maybe "--filter-omit-provided"?
quoted hunk
@@ -694,6 +698,16 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) return show_bisect_vars(&info, reaches, all); }+ if (filter_options.filter_wants) {+ struct commit_list *c;+ for (i = 0; i < revs.pending.nr; i++) {+ struct object_array_entry *pending = revs.pending.objects + i;+ pending->item->flags |= NOT_USER_GIVEN;+ }+ for (c = revs.commits; c; c = c->next)+ c->item->object.flags |= NOT_USER_GIVEN;+ }
You store the flag inside the filter_options struct, which implies to me
that it's something that could be applied per-filter (at least in
theory; the command line option doesn't allow us to distinguish).
But here you treat it as a global flag that munges the NOT_USER_GIVEN
flags. Given that it's inside the filter_options struct, and that you
propagate it via transform_to_combine_type(), I'd have expected the LOFC
code to look at the flag and decide to ignore the whole user-given
concept completely.
To be clear, I don't mind at all having it as a global that applies to
all filters. I don't think the flexibility buys us anything. But since
it only applies to rev-list, why not just make it a global option within
rev-list?
And then these hunks:
I guess we'd need to pass that flag into prepare_bitmap_walk() here so
it knows not to bother with the wants-filtering. But that seems less bad
that stuffing it into the filter struct.
-Peff
From: Patrick Steinhardt <hidden> Date: 2021-04-09 10:21:04
On Tue, Apr 06, 2021 at 01:30:57PM -0400, Jeff King wrote:
On Mon, Mar 15, 2021 at 02:14:36PM +0100, Patrick Steinhardt wrote:
quoted
The NOT_USER_GIVEN flag of an object marks whether a flag was explicitly
provided by the user or not. The most important use case for this is
when filtering objects: only objects that were not explicitly requested
will get filtered.
The flag is currently only set for blobs and trees, which has been fine
given that there are no filters for tags or commits currently. We're
about to extend filtering capabilities to add object type filter though,
which requires us to set up the NOT_USER_GIVEN flag correctly -- if it's
not set, the object wouldn't get filtered at all.
Mark unseen commit parents as NOT_USER_GIVEN when processing parents.
Like this, explicitly provided parents stay user-given and thus
unfiltered, while parents which get loaded as part of the graph walk
can be filtered.
This commit shouldn't have any user-visible impact yet as there is no
logic to filter commits yet.
I'm still scratching my head a bit to understand how NOT_USER_GIVEN can
possibly be correct (as opposed to USER_GIVEN). If we visit the commit
in a not-user-given context and add the flag, how do we know it wasn't
_also_ visited in a user-given context?
Just guessing, but perhaps the SEEN flag is saving us here? If we visit
the user-given commit itself first, then we give it the SEEN flag. Then
if we try to visit it again via parent traversal, we've already
processed it and don't add the NOT_USER_GIVEN flag here.
Yes, I think that's mostly it.
That seems the opposite of the order we'd usually traverse, but I think
we set SEEN on each commit in prepare_revision_walk(), before we do any
traversing.
So I _think_ it all works even with your changes here, but I have to say
this NOT_USER_GIVEN thing seems really fragile to me. Not new in your
series, of course, but something we may want to look at.
Just grepping around, "rev-list -g" will happily remove SEEN flags, so I
suspect it interacts badly with --filter. Just trying "rev-list -g
--objects --filter=object:type=blob HEAD" shows that it produces quite a
lot of commits (which I think is a more fundamental problem: it is not
walking the parent chain at all to assign these NOT_USER_GIVEN flags).
I totally agree that this feels fragile, and developing this series with
NOT_USER_GIVEN wasn't the most enjoyable experience either. I wouldn't
love to be doing the conversion back to USER_GIVEN as part of this
series, but I wouldn't oppose doing that job either. Right now I don't
feel like I'm sufficiently sure that it's working for all cases, and
indeed your example with "rev-list -g" already shows one case where it's
breaking.
So let me know whether I should add the conversion as preparatory step.
Patrick
From: Patrick Steinhardt <hidden> Date: 2021-04-09 10:31:49
On Tue, Apr 06, 2021 at 01:54:31PM -0400, Jeff King wrote:
On Mon, Mar 15, 2021 at 02:14:59PM +0100, Patrick Steinhardt wrote:
quoted
When the user has multiple objects filters specified, then this is
internally represented by having a "combined" filter. These combined
filters aren't yet supported by bitmap indices and can thus not be
accelerated.
Fix this by implementing support for these combined filters. The
implementation is quite trivial: when there's a combined filter, we
simply recurse into `filter_bitmap()` for all of the sub-filters.
The goal makes sense.
Before this patch, I think your test:
would pass anyway, because we'd just skip using bitmaps. Is there a way
we can tell that the bitmap code actually kicked in? Maybe a perf test
would make it clear (those aren't always run, but hopefully we'd
eventually notice a regression there).
quoted
+static int filter_supported(struct list_objects_filter_options *filter)+{+ int i;++ switch (filter->choice) {+ case LOFC_BLOB_NONE:+ case LOFC_BLOB_LIMIT:+ case LOFC_OBJECT_TYPE:+ return 1;+ case LOFC_TREE_DEPTH:+ if (filter->tree_exclude_depth == 0)+ return 1;+ return 0;+ case LOFC_COMBINE:+ for (i = 0; i < filter->sub_nr; i++)+ if (!filter_supported(&filter->sub[i]))+ return 0;+ return 1;+ default:+ return 0;+ }+}
Hmm. This is essentially reproducing the list in filter_bitmap() of
what's OK for bitmaps. So when adding a new filter, it would have to be
added in both places.
Can we preserve that property of the original code? I'd think that just
adding LOFC_COMBINE to filter_bitmap() would be sufficient. I.e., this
hunk:
quoted
+ if (filter->choice == LOFC_COMBINE) {+ int i;+ for (i = 0; i < filter->sub_nr; i++) {+ filter_bitmap(bitmap_git, tip_objects, to_filter,+ &filter->sub[i]);+ }+ return 0;+ }
...except that we need to see if filter_bitmap() returns "-1" for any of
the recursive calls. Which we probably should be doing anyway to
propagate any errors (though I think the only "errors" we'd return are
"not supported", at least for now).
-Peff
But wouldn't that mean that we're now needlessly filtering via bitmaps
all the way down the combined filters only to realize at the end that it
cannot work because we've got a tree filter with non-zero tree depth?
Granted, this will not be the common case. But it still feels like we're
doing needless work for cases where we know that bitmaps cannot answer
the query.
Patrick
From: Patrick Steinhardt <hidden> Date: 2021-04-09 10:59:52
On Tue, Apr 06, 2021 at 02:04:15PM -0400, Jeff King wrote:
On Mon, Mar 15, 2021 at 02:15:05PM +0100, Patrick Steinhardt wrote:
quoted
When providing an object filter, it is currently impossible to also
filter provided items. E.g. when executing `git rev-list HEAD` , the
commit this reference points to will be treated as user-provided and is
thus excluded from the filtering mechanism. This makes it harder than
necessary to properly use the new `--filter=object:type` filter given
that even if the user wants to only see blobs, he'll still see commits
of provided references.
Improve this by introducing a new `--filter-provided` option to the
git-rev-parse(1) command. If given, then all user-provided references
will be subject to filtering.
I think this option is a good thing to have.
The name seems a little confusing to me, as I can read is as both
"please filter the provided objects" and "a filter has been provided".
I guess "--filter-print-provided" would be more clear. And also the
default, so you'd want "--no-filter-print-provided". That's kind of
clunky, though. Maybe "--filter-omit-provided"?
Hum, "--filter-omit-provided" doesn't sound good to me, either. Omit to
me sounds like it'd omit filtering provided items, but we're doing
the reverse thing.
How about "--filter-provided-revisions"? Verbose, but at least it cannot
be confused with a filter being provided.
quoted
@@ -694,6 +698,16 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) return show_bisect_vars(&info, reaches, all); }+ if (filter_options.filter_wants) {+ struct commit_list *c;+ for (i = 0; i < revs.pending.nr; i++) {+ struct object_array_entry *pending = revs.pending.objects + i;+ pending->item->flags |= NOT_USER_GIVEN;+ }+ for (c = revs.commits; c; c = c->next)+ c->item->object.flags |= NOT_USER_GIVEN;+ }
You store the flag inside the filter_options struct, which implies to me
that it's something that could be applied per-filter (at least in
theory; the command line option doesn't allow us to distinguish).
But here you treat it as a global flag that munges the NOT_USER_GIVEN
flags. Given that it's inside the filter_options struct, and that you
propagate it via transform_to_combine_type(), I'd have expected the LOFC
code to look at the flag and decide to ignore the whole user-given
concept completely.
To be clear, I don't mind at all having it as a global that applies to
all filters. I don't think the flexibility buys us anything. But since
it only applies to rev-list, why not just make it a global option within
rev-list?
[snip]
Fair point. This probably stems from the confusion where I initially
didn't realize that the filter_options is not a "global" options
structure, but in fact the filter itself already. That's also why there
had been the initial bug where converting filter options into a combined
filter led to `filter_wants` being dropped.
In any case, the resulting code with it being global to rev-list.c
instead of part of the options is a lot cleaner.
Patrick
From: Patrick Steinhardt <hidden> Date: 2021-04-09 11:18:15
On Tue, Apr 06, 2021 at 01:54:31PM -0400, Jeff King wrote:
On Mon, Mar 15, 2021 at 02:14:59PM +0100, Patrick Steinhardt wrote:
quoted
When the user has multiple objects filters specified, then this is
internally represented by having a "combined" filter. These combined
filters aren't yet supported by bitmap indices and can thus not be
accelerated.
Fix this by implementing support for these combined filters. The
implementation is quite trivial: when there's a combined filter, we
simply recurse into `filter_bitmap()` for all of the sub-filters.
The goal makes sense.
Before this patch, I think your test:
would pass anyway, because we'd just skip using bitmaps. Is there a way
we can tell that the bitmap code actually kicked in? Maybe a perf test
would make it clear (those aren't always run, but hopefully we'd
eventually notice a regression there).
I think that's not actually true. Note that we're using
`test_bitmap_traversal`:
test_bitmap_traversal () {
if test "$1" = "--no-confirm-bitmaps"
then
shift
elif cmp "$1" "$2"
then
echo >&2 "identical raw outputs; are you sure bitmaps were used?"
return 1
fi &&
cut -d' ' -f1 "$1" | sort >"$1.normalized" &&
sort "$2" >"$2.normalized" &&
test_cmp "$1.normalized" "$2.normalized" &&
rm -f "$1.normalized" "$2.normalized"
}
The output is different when using bitmap indices, which is why the
function knows to fail in case output is the same in both cases. So we
know that it cannot be the same here and thus we also know that the
bitmap case kicked in.
Patrick
From: Patrick Steinhardt <hidden> Date: 2021-04-09 11:27:57
When `uploadpackfilter.allow` is set to `true`, it means that filters
are enabled by default except in the case where a filter is explicitly
disabled via `uploadpackilter.<filter>.allow`. This option will not only
enable the currently supported set of filters, but also any filters
which get added in the future. As such, an admin which wants to have
tight control over which filters are allowed and which aren't probably
shouldn't ever set `uploadpackfilter.allow=true`.
Amend the documentation to make the ramifications more explicit so that
admins are aware of this.
Signed-off-by: Patrick Steinhardt <redacted>
---
Documentation/config/uploadpack.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
@@ -59,7 +59,8 @@ uploadpack.allowFilter:: uploadpackfilter.allow:: Provides a default value for unspecified object filters (see: the- below configuration variable).+ below configuration variable). If set to `true`, this will also+ enable all filters which get added in the future. Defaults to `true`. uploadpackfilter.<filter>.allow::
From: Patrick Steinhardt <hidden> Date: 2021-04-09 11:28:03
The NOT_USER_GIVEN flag of an object marks whether a flag was explicitly
provided by the user or not. The most important use case for this is
when filtering objects: only objects that were not explicitly requested
will get filtered.
The flag is currently only set for blobs and trees, which has been fine
given that there are no filters for tags or commits currently. We're
about to extend filtering capabilities to add object type filter though,
which requires us to set up the NOT_USER_GIVEN flag correctly -- if it's
not set, the object wouldn't get filtered at all.
Mark unseen commit parents as NOT_USER_GIVEN when processing parents.
Like this, explicitly provided parents stay user-given and thus
unfiltered, while parents which get loaded as part of the graph walk
can be filtered.
This commit shouldn't have any user-visible impact yet as there is no
logic to filter commits yet.
Signed-off-by: Patrick Steinhardt <redacted>
---
revision.c | 4 ++--
revision.h | 3 ---
2 files changed, 2 insertions(+), 5 deletions(-)
From: Patrick Steinhardt <hidden> Date: 2021-04-09 11:28:07
Move processing of tags into its own function to make the logic easier
to extend when we're going to implement filtering for tags. No change in
behaviour is expected from this commit.
Signed-off-by: Patrick Steinhardt <redacted>
---
list-objects.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
From: Patrick Steinhardt <hidden> Date: 2021-04-09 11:28:12
Object filters currently only support filtering blobs or trees based on
some criteria. This commit lays the foundation to also allow filtering
of tags and commits.
No change in behaviour is expected from this commit given that there are
no filters yet for those object types.
Signed-off-by: Patrick Steinhardt <redacted>
---
list-objects-filter.c | 40 ++++++++++++++++++++++++++++++++++++++++
list-objects-filter.h | 2 ++
list-objects.c | 22 +++++++++++++++++++---
3 files changed, 61 insertions(+), 3 deletions(-)
@@ -82,6 +82,16 @@ static enum list_objects_filter_result filter_blobs_none(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_BEGIN_TREE:assert(obj->type==OBJ_TREE);/* always include all tree objects */
@@ -173,6 +183,16 @@ static enum list_objects_filter_result filter_trees_depth(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_END_TREE:assert(obj->type==OBJ_TREE);filter_data->current_depth--;
@@ -267,6 +287,16 @@ static enum list_objects_filter_result filter_blobs_limit(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_BEGIN_TREE:assert(obj->type==OBJ_TREE);/* always include all tree objects */
@@ -371,6 +401,16 @@ static enum list_objects_filter_result filter_sparse(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_BEGIN_TREE:assert(obj->type==OBJ_TREE);dtype=DT_DIR;
From: Patrick Steinhardt <hidden> Date: 2021-04-09 11:28:16
While it already is possible to filter objects by some criteria in
git-rev-list(1), it is not yet possible to filter out only a specific
type of objects. This makes some filters less useful. The `blob:limit`
filter for example filters blobs such that only those which are smaller
than the given limit are returned. But it is unfit to ask only for these
smallish blobs, given that git-rev-list(1) will continue to print tags,
commits and trees.
Now that we have the infrastructure in place to also filter tags and
commits, we can improve this situation by implementing a new filter
which selects objects based on their type. Above query can thus
trivially be implemented with the following command:
$ git rev-list --objects --filter=object:type=blob \
--filter=blob:limit=200
Furthermore, this filter allows to optimize for certain other cases: if
for example only tags or commits have been selected, there is no need to
walk down trees.
The new filter is not yet supported in bitmaps. This is going to be
implemented in a subsequent commit.
Signed-off-by: Patrick Steinhardt <redacted>
---
Documentation/config/uploadpack.txt | 6 +--
Documentation/rev-list-options.txt | 3 ++
list-objects-filter-options.c | 14 ++++++
list-objects-filter-options.h | 2 +
list-objects-filter.c | 76 +++++++++++++++++++++++++++++
t/t6112-rev-list-filters-objects.sh | 48 ++++++++++++++++++
6 files changed, 146 insertions(+), 3 deletions(-)
@@ -66,9 +66,9 @@ uploadpackfilter.allow:: uploadpackfilter.<filter>.allow:: Explicitly allow or ban the object filter corresponding to `<filter>`, where `<filter>` may be one of: `blob:none`,- `blob:limit`, `tree`, `sparse:oid`, or `combine`. If using- combined filters, both `combine` and all of the nested filter- kinds must be allowed. Defaults to `uploadpackfilter.allow`.+ `blob:limit`, `object:type`, `tree`, `sparse:oid`, or `combine`.+ If using combined filters, both `combine` and all of the nested+ filter kinds must be allowed. Defaults to `uploadpackfilter.allow`. uploadpackfilter.tree.maxDepth:: Only allow `--filter=tree:<n>` when `<n>` is no more than the value of
@@ -892,6 +892,9 @@ or units. n may be zero. The suffixes k, m, and g can be used to name units in KiB, MiB, or GiB. For example, 'blob:limit=1k' is the same as 'blob:limit=1024'. ++The form '--filter=object:type=(tag|commit|tree|blob)' omits all objects+which are not of the requested type.++ The form '--filter=sparse:oid=<blob-ish>' uses a sparse-checkout specification contained in the blob (or blob-expression) '<blob-ish>' to omit blobs that would not be not required for a sparse checkout on
@@ -13,6 +13,7 @@ enum list_objects_filter_choice {LOFC_BLOB_LIMIT,LOFC_TREE_DEPTH,LOFC_SPARSE_OID,+LOFC_OBJECT_TYPE,LOFC_COMBINE,LOFC__COUNT/* must be last */};
@@ -545,6 +545,81 @@ static void filter_sparse_oid__init(filter->free_fn=filter_sparse_free;}+/*+*Afilterforlist-objectstoomitlargeblobs.+*AndtoOPTIONALLYcollectalistoftheomittedOIDs.+*/+structfilter_object_type_data{+enumobject_typeobject_type;+};++staticenumlist_objects_filter_resultfilter_object_type(+structrepository*r,+enumlist_objects_filter_situationfilter_situation,+structobject*obj,+constchar*pathname,+constchar*filename,+structoidset*omits,+void*filter_data_)+{+structfilter_object_type_data*filter_data=filter_data_;++switch(filter_situation){+default:+BUG("unknown filter_situation: %d",filter_situation);++caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+if(filter_data->object_type==OBJ_TAG)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+returnLOFR_MARK_SEEN;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+if(filter_data->object_type==OBJ_COMMIT)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+returnLOFR_MARK_SEEN;++caseLOFS_BEGIN_TREE:+assert(obj->type==OBJ_TREE);++/*+*Ifweonlywanttoshowcommitsortags,thenthereisno+*needtowalkdowntrees.+*/+if(filter_data->object_type==OBJ_COMMIT||+filter_data->object_type==OBJ_TAG)+returnLOFR_SKIP_TREE;++if(filter_data->object_type==OBJ_TREE)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++returnLOFR_MARK_SEEN;++caseLOFS_BLOB:+assert(obj->type==OBJ_BLOB);++if(filter_data->object_type==OBJ_BLOB)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+returnLOFR_MARK_SEEN;++caseLOFS_END_TREE:+returnLOFR_ZERO;+}+}++staticvoidfilter_object_type__init(+structlist_objects_filter_options*filter_options,+structfilter*filter)+{+structfilter_object_type_data*d=xcalloc(1,sizeof(*d));+d->object_type=filter_options->object_type;++filter->filter_data=d;+filter->filter_object_fn=filter_object_type;+filter->free_fn=free;+}+/* A filter which only shows objects shown by all sub-filters. */structcombine_filter_data{structsubfilter*sub;
@@ -159,6 +159,54 @@ test_expect_success 'verify blob:limit=1m' 'test_must_be_emptyobserved'+# Test object:type=<type> filter.++test_expect_success'setup object-type''+gitinitobject-type&&+echocontents>object-type/blob&&+git-Cobject-typeaddblob&&+git-Cobject-typecommit-mcommit-message&&+git-Cobject-typetagtag-mtag-message+'++test_expect_success'verify object:type= fails with invalid type''+test_must_failgit-Cobject-typerev-list--objects--filter=object:type=HEAD&&+test_must_failgit-Cobject-typerev-list--objects--filter=object:type=invalidHEAD+'++test_expect_success'verify object:type=blob prints blob and commit''+(+git-Cobject-typerev-parseHEAD&&+printf"%s blob\n"$(git-Cobject-typerev-parseHEAD:blob)+)>expected&&+git-Cobject-typerev-list--objects--filter=object:type=blobHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tree prints tree and commit''+(+git-Cobject-typerev-parseHEAD&&+printf"%s \n"$(git-Cobject-typerev-parseHEAD^{tree})+)>expected&&+git-Cobject-typerev-list--objects--filter=object:type=treeHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=commit prints commit''+git-Cobject-typerev-parseHEAD>expected&&+git-Cobject-typerev-list--objects--filter=object:type=commitHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tag prints tag''+(+git-Cobject-typerev-parseHEAD&&+printf"%s tag\n"$(git-Cobject-typerev-parsetag)+)>expected&&+git-Cobject-typerev-list--objects--filter=object:type=tagtag>actual&&+test_cmpexpectedactual+'+# Test sparse:path=<path> filter.# !!!!# NOTE: sparse:path filter support has been dropped for security reasons,
From: Patrick Steinhardt <hidden> Date: 2021-04-09 11:28:21
The preceding commit has added a new object filter for git-rev-list(1)
which allows to filter objects by type. Implement the equivalent filter
for packfile bitmaps so that we can answer these queries fast.
Signed-off-by: Patrick Steinhardt <redacted>
---
pack-bitmap.c | 29 ++++++++++++++++++++++++++---
t/t6113-rev-list-bitmap-filters.sh | 25 ++++++++++++++++++++++++-
2 files changed, 50 insertions(+), 4 deletions(-)
From: Patrick Steinhardt <hidden> Date: 2021-04-09 11:28:27
When the user has multiple objects filters specified, then this is
internally represented by having a "combined" filter. These combined
filters aren't yet supported by bitmap indices and can thus not be
accelerated.
Fix this by implementing support for these combined filters. The
implementation is quite trivial: when there's a combined filter, we
simply recurse into `filter_bitmap()` for all of the sub-filters.
Signed-off-by: Patrick Steinhardt <redacted>
---
pack-bitmap.c | 41 +++++++++++++++++++++++++++---
t/t6113-rev-list-bitmap-filters.sh | 7 +++++
2 files changed, 44 insertions(+), 4 deletions(-)
From: Patrick Steinhardt <hidden> Date: 2021-04-09 11:28:32
When providing an object filter, it is currently impossible to also
filter provided items. E.g. when executing `git rev-list HEAD` , the
commit this reference points to will be treated as user-provided and is
thus excluded from the filtering mechanism. This makes it harder than
necessary to properly use the new `--filter=object:type` filter given
that even if the user wants to only see blobs, he'll still see commits
of provided references.
Improve this by introducing a new `--filter-provided` option to the
git-rev-parse(1) command. If given, then all user-provided references
will be subject to filtering.
Signed-off-by: Patrick Steinhardt <redacted>
---
Documentation/rev-list-options.txt | 5 ++++
builtin/pack-objects.c | 2 +-
builtin/rev-list.c | 36 +++++++++++++++++++++--------
pack-bitmap.c | 6 +++--
pack-bitmap.h | 3 ++-
reachable.c | 2 +-
t/t6112-rev-list-filters-objects.sh | 28 ++++++++++++++++++++++
t/t6113-rev-list-bitmap-filters.sh | 36 +++++++++++++++++++++++++++++
8 files changed, 104 insertions(+), 14 deletions(-)
@@ -933,6 +933,11 @@ equivalent. --no-filter:: Turn off any previous `--filter=` argument.+--filter-provided-revisions::+ Filter the list of explicitly provided revisions, which would otherwise+ always be printed even if they did not match any of the filters. Only+ useful with `--filter=`.+ --filter-print-omitted:: Only useful with `--filter=`; prints a list of the objects omitted by the filter. Object IDs are prefixed with a ``~'' character.
@@ -207,6 +207,34 @@ test_expect_success 'verify object:type=tag prints tag' 'test_cmpexpectedactual'+test_expect_success'verify object:type=blob prints only blob with --filter-provided''+printf"%s blob\n"$(git-Cobject-typerev-parseHEAD:blob)>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=blob--filter-providedHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tree prints only tree with --filter-provided''+printf"%s \n"$(git-Cobject-typerev-parseHEAD^{tree})>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=treeHEAD--filter-provided>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=commit prints only commit with --filter-provided''+git-Cobject-typerev-parseHEAD>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=commit--filter-providedHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tag prints only tag with --filter-provided''+printf"%s tag\n"$(git-Cobject-typerev-parsetag)>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=tag--filter-providedtag>actual&&+test_cmpexpectedactual+'+# Test sparse:path=<path> filter.# !!!!# NOTE: sparse:path filter support has been dropped for security reasons,
From: Patrick Steinhardt <hidden> Date: 2021-04-09 11:32:27
When providing an object filter, it is currently impossible to also
filter provided items. E.g. when executing `git rev-list HEAD` , the
commit this reference points to will be treated as user-provided and is
thus excluded from the filtering mechanism. This makes it harder than
necessary to properly use the new `--filter=object:type` filter given
that even if the user wants to only see blobs, he'll still see commits
of provided references.
Improve this by introducing a new `--filter-provided` option to the
git-rev-parse(1) command. If given, then all user-provided references
will be subject to filtering.
Signed-off-by: Patrick Steinhardt <redacted>
---
Forgot to stage and add test changes to adjust for the changed flag
name.
Documentation/rev-list-options.txt | 5 ++++
builtin/pack-objects.c | 2 +-
builtin/rev-list.c | 36 +++++++++++++++++++++--------
pack-bitmap.c | 6 +++--
pack-bitmap.h | 3 ++-
reachable.c | 2 +-
t/t6112-rev-list-filters-objects.sh | 28 ++++++++++++++++++++++
t/t6113-rev-list-bitmap-filters.sh | 36 +++++++++++++++++++++++++++++
8 files changed, 104 insertions(+), 14 deletions(-)
@@ -933,6 +933,11 @@ equivalent. --no-filter:: Turn off any previous `--filter=` argument.+--filter-provided-revisions::+ Filter the list of explicitly provided revisions, which would otherwise+ always be printed even if they did not match any of the filters. Only+ useful with `--filter=`.+ --filter-print-omitted:: Only useful with `--filter=`; prints a list of the objects omitted by the filter. Object IDs are prefixed with a ``~'' character.
@@ -207,6 +207,34 @@ test_expect_success 'verify object:type=tag prints tag' 'test_cmpexpectedactual'+test_expect_success'verify object:type=blob prints only blob with --filter-provided-revisions''+printf"%s blob\n"$(git-Cobject-typerev-parseHEAD:blob)>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=blob--filter-provided-revisionsHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tree prints only tree with --filter-provided-revisions''+printf"%s \n"$(git-Cobject-typerev-parseHEAD^{tree})>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=treeHEAD--filter-provided-revisions>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=commit prints only commit with --filter-provided-revisions''+git-Cobject-typerev-parseHEAD>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=commit--filter-provided-revisionsHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tag prints only tag with --filter-provided-revisions''+printf"%s tag\n"$(git-Cobject-typerev-parsetag)>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=tag--filter-provided-revisionstag>actual&&+test_cmpexpectedactual+'+# Test sparse:path=<path> filter.# !!!!# NOTE: sparse:path filter support has been dropped for security reasons,
From: Philip Oakley <hidden> Date: 2021-04-09 15:00:34
typo nit.
On 09/04/2021 12:28, Patrick Steinhardt wrote:
When providing an object filter, it is currently impossible to also
filter provided items. E.g. when executing `git rev-list HEAD` , the
commit this reference points to will be treated as user-provided and is
thus excluded from the filtering mechanism. This makes it harder than
necessary to properly use the new `--filter=object:type` filter given
that even if the user wants to only see blobs, he'll still see commits
of provided references.
Improve this by introducing a new `--filter-provided` option to the
s/--filter-provided/--filter-provided-revisions/
Also in some tests - I presume the option should be spelled out in full.
quoted hunk
git-rev-parse(1) command. If given, then all user-provided references
will be subject to filtering.
Signed-off-by: Patrick Steinhardt <redacted>
---
Documentation/rev-list-options.txt | 5 ++++
builtin/pack-objects.c | 2 +-
builtin/rev-list.c | 36 +++++++++++++++++++++--------
pack-bitmap.c | 6 +++--
pack-bitmap.h | 3 ++-
reachable.c | 2 +-
t/t6112-rev-list-filters-objects.sh | 28 ++++++++++++++++++++++
t/t6113-rev-list-bitmap-filters.sh | 36 +++++++++++++++++++++++++++++
8 files changed, 104 insertions(+), 14 deletions(-)
@@ -933,6 +933,11 @@ equivalent. --no-filter:: Turn off any previous `--filter=` argument.+--filter-provided-revisions::+ Filter the list of explicitly provided revisions, which would otherwise+ always be printed even if they did not match any of the filters. Only+ useful with `--filter=`.+ --filter-print-omitted:: Only useful with `--filter=`; prints a list of the objects omitted by the filter. Object IDs are prefixed with a ``~'' character.
@@ -207,6 +207,34 @@ test_expect_success 'verify object:type=tag prints tag' 'test_cmpexpectedactual'+test_expect_success'verify object:type=blob prints only blob with --filter-provided''+printf"%s blob\n"$(git-Cobject-typerev-parseHEAD:blob)>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=blob--filter-providedHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tree prints only tree with --filter-provided''+printf"%s \n"$(git-Cobject-typerev-parseHEAD^{tree})>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=treeHEAD--filter-provided>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=commit prints only commit with --filter-provided''+git-Cobject-typerev-parseHEAD>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=commit--filter-providedHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tag prints only tag with --filter-provided''+printf"%s tag\n"$(git-Cobject-typerev-parsetag)>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=tag--filter-providedtag>actual&&+test_cmpexpectedactual+'+# Test sparse:path=<path> filter.# !!!!# NOTE: sparse:path filter support has been dropped for security reasons,
From: Jeff King <hidden> Date: 2021-04-09 15:53:13
On Fri, Apr 09, 2021 at 12:31:42PM +0200, Patrick Steinhardt wrote:
quoted
Hmm. This is essentially reproducing the list in filter_bitmap() of
what's OK for bitmaps. So when adding a new filter, it would have to be
added in both places.
Can we preserve that property of the original code? I'd think that just
adding LOFC_COMBINE to filter_bitmap() would be sufficient. I.e., this
hunk:
quoted
+ if (filter->choice == LOFC_COMBINE) {+ int i;+ for (i = 0; i < filter->sub_nr; i++) {+ filter_bitmap(bitmap_git, tip_objects, to_filter,+ &filter->sub[i]);+ }+ return 0;+ }
...except that we need to see if filter_bitmap() returns "-1" for any of
the recursive calls. Which we probably should be doing anyway to
propagate any errors (though I think the only "errors" we'd return are
"not supported", at least for now).
-Peff
But wouldn't that mean that we're now needlessly filtering via bitmaps
all the way down the combined filters only to realize at the end that it
cannot work because we've got a tree filter with non-zero tree depth?
Granted, this will not be the common case. But it still feels like we're
doing needless work for cases where we know that bitmaps cannot answer
the query.
I don't think so. We first call can_filter_bitmap(filter), which passes
NULL for bitmap_git. And then in filter_bitmap(), we only do actual work
if bitmap_git is non-NULL.
This is the same thing that saves us from even loading the bitmaps
(which is itself a non-trivial amount of work) if the filter cannot be
satisfied by bitmaps.
-Peff
would pass anyway, because we'd just skip using bitmaps. Is there a way
we can tell that the bitmap code actually kicked in? Maybe a perf test
would make it clear (those aren't always run, but hopefully we'd
eventually notice a regression there).
I think that's not actually true. Note that we're using
`test_bitmap_traversal`:
Ah, right. I forgot about the hackery in test_bitmap_traversal() to let
us tell the difference (even though I was the one who wrote it, I still
consider it hackery ;) ).
So yes, this is a good test that we are allowing the combine filter.
-Peff
From: Jeff King <hidden> Date: 2021-04-09 15:58:10
On Fri, Apr 09, 2021 at 12:59:41PM +0200, Patrick Steinhardt wrote:
quoted
The name seems a little confusing to me, as I can read is as both
"please filter the provided objects" and "a filter has been provided".
I guess "--filter-print-provided" would be more clear. And also the
default, so you'd want "--no-filter-print-provided". That's kind of
clunky, though. Maybe "--filter-omit-provided"?
Hum, "--filter-omit-provided" doesn't sound good to me, either. Omit to
me sounds like it'd omit filtering provided items, but we're doing
the reverse thing.
Yeah, I can see that.
How about "--filter-provided-revisions"? Verbose, but at least it cannot
be confused with a filter being provided.
Yes, that works for me. Maybe "--filter-provided-objects", since you
could also provide a non-revision on the command line (though I think
other parts of the docs are happy to refer to "revisions" or "commits"
on the command line, even though you can clearly provide non-commits
when used with --objects).
-Peff
From: Patrick Steinhardt <hidden> Date: 2021-04-12 13:15:29
On Fri, Apr 09, 2021 at 04:00:26PM +0100, Philip Oakley wrote:
typo nit.
On 09/04/2021 12:28, Patrick Steinhardt wrote:
quoted
When providing an object filter, it is currently impossible to also
filter provided items. E.g. when executing `git rev-list HEAD` , the
commit this reference points to will be treated as user-provided and is
thus excluded from the filtering mechanism. This makes it harder than
necessary to properly use the new `--filter=object:type` filter given
that even if the user wants to only see blobs, he'll still see commits
of provided references.
Improve this by introducing a new `--filter-provided` option to the
s/--filter-provided/--filter-provided-revisions/
Also in some tests - I presume the option should be spelled out in full.
Right. I did fix these in the resend because I forgot to stage changes,
but still had it in the commit message.
Fixed now, thanks!
Patrick
From: Patrick Steinhardt <hidden> Date: 2021-04-12 13:37:25
When `uploadpackfilter.allow` is set to `true`, it means that filters
are enabled by default except in the case where a filter is explicitly
disabled via `uploadpackilter.<filter>.allow`. This option will not only
enable the currently supported set of filters, but also any filters
which get added in the future. As such, an admin which wants to have
tight control over which filters are allowed and which aren't probably
shouldn't ever set `uploadpackfilter.allow=true`.
Amend the documentation to make the ramifications more explicit so that
admins are aware of this.
Signed-off-by: Patrick Steinhardt <redacted>
---
Documentation/config/uploadpack.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
@@ -59,7 +59,8 @@ uploadpack.allowFilter:: uploadpackfilter.allow:: Provides a default value for unspecified object filters (see: the- below configuration variable).+ below configuration variable). If set to `true`, this will also+ enable all filters which get added in the future. Defaults to `true`. uploadpackfilter.<filter>.allow::
From: Patrick Steinhardt <hidden> Date: 2021-04-12 13:37:30
The NOT_USER_GIVEN flag of an object marks whether a flag was explicitly
provided by the user or not. The most important use case for this is
when filtering objects: only objects that were not explicitly requested
will get filtered.
The flag is currently only set for blobs and trees, which has been fine
given that there are no filters for tags or commits currently. We're
about to extend filtering capabilities to add object type filter though,
which requires us to set up the NOT_USER_GIVEN flag correctly -- if it's
not set, the object wouldn't get filtered at all.
Mark unseen commit parents as NOT_USER_GIVEN when processing parents.
Like this, explicitly provided parents stay user-given and thus
unfiltered, while parents which get loaded as part of the graph walk
can be filtered.
This commit shouldn't have any user-visible impact yet as there is no
logic to filter commits yet.
Signed-off-by: Patrick Steinhardt <redacted>
---
revision.c | 4 ++--
revision.h | 3 ---
2 files changed, 2 insertions(+), 5 deletions(-)
From: Patrick Steinhardt <hidden> Date: 2021-04-12 13:37:38
Move processing of tags into its own function to make the logic easier
to extend when we're going to implement filtering for tags. No change in
behaviour is expected from this commit.
Signed-off-by: Patrick Steinhardt <redacted>
---
list-objects.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
From: Patrick Steinhardt <hidden> Date: 2021-04-12 13:37:39
Object filters currently only support filtering blobs or trees based on
some criteria. This commit lays the foundation to also allow filtering
of tags and commits.
No change in behaviour is expected from this commit given that there are
no filters yet for those object types.
Signed-off-by: Patrick Steinhardt <redacted>
---
list-objects-filter.c | 40 ++++++++++++++++++++++++++++++++++++++++
list-objects-filter.h | 2 ++
list-objects.c | 23 ++++++++++++++++++++---
3 files changed, 62 insertions(+), 3 deletions(-)
@@ -82,6 +82,16 @@ static enum list_objects_filter_result filter_blobs_none(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_BEGIN_TREE:assert(obj->type==OBJ_TREE);/* always include all tree objects */
@@ -173,6 +183,16 @@ static enum list_objects_filter_result filter_trees_depth(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_END_TREE:assert(obj->type==OBJ_TREE);filter_data->current_depth--;
@@ -267,6 +287,16 @@ static enum list_objects_filter_result filter_blobs_limit(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_BEGIN_TREE:assert(obj->type==OBJ_TREE);/* always include all tree objects */
@@ -371,6 +401,16 @@ static enum list_objects_filter_result filter_sparse(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_BEGIN_TREE:assert(obj->type==OBJ_TREE);dtype=DT_DIR;
From: Patrick Steinhardt <hidden> Date: 2021-04-12 13:37:43
While it already is possible to filter objects by some criteria in
git-rev-list(1), it is not yet possible to filter out only a specific
type of objects. This makes some filters less useful. The `blob:limit`
filter for example filters blobs such that only those which are smaller
than the given limit are returned. But it is unfit to ask only for these
smallish blobs, given that git-rev-list(1) will continue to print tags,
commits and trees.
Now that we have the infrastructure in place to also filter tags and
commits, we can improve this situation by implementing a new filter
which selects objects based on their type. Above query can thus
trivially be implemented with the following command:
$ git rev-list --objects --filter=object:type=blob \
--filter=blob:limit=200
Furthermore, this filter allows to optimize for certain other cases: if
for example only tags or commits have been selected, there is no need to
walk down trees.
The new filter is not yet supported in bitmaps. This is going to be
implemented in a subsequent commit.
Signed-off-by: Patrick Steinhardt <redacted>
---
Documentation/config/uploadpack.txt | 6 +--
Documentation/rev-list-options.txt | 3 ++
list-objects-filter-options.c | 14 ++++++
list-objects-filter-options.h | 2 +
list-objects-filter.c | 76 +++++++++++++++++++++++++++++
t/t6112-rev-list-filters-objects.sh | 48 ++++++++++++++++++
6 files changed, 146 insertions(+), 3 deletions(-)
@@ -66,9 +66,9 @@ uploadpackfilter.allow:: uploadpackfilter.<filter>.allow:: Explicitly allow or ban the object filter corresponding to `<filter>`, where `<filter>` may be one of: `blob:none`,- `blob:limit`, `tree`, `sparse:oid`, or `combine`. If using- combined filters, both `combine` and all of the nested filter- kinds must be allowed. Defaults to `uploadpackfilter.allow`.+ `blob:limit`, `object:type`, `tree`, `sparse:oid`, or `combine`.+ If using combined filters, both `combine` and all of the nested+ filter kinds must be allowed. Defaults to `uploadpackfilter.allow`. uploadpackfilter.tree.maxDepth:: Only allow `--filter=tree:<n>` when `<n>` is no more than the value of
@@ -892,6 +892,9 @@ or units. n may be zero. The suffixes k, m, and g can be used to name units in KiB, MiB, or GiB. For example, 'blob:limit=1k' is the same as 'blob:limit=1024'. ++The form '--filter=object:type=(tag|commit|tree|blob)' omits all objects+which are not of the requested type.++ The form '--filter=sparse:oid=<blob-ish>' uses a sparse-checkout specification contained in the blob (or blob-expression) '<blob-ish>' to omit blobs that would not be not required for a sparse checkout on
@@ -13,6 +13,7 @@ enum list_objects_filter_choice {LOFC_BLOB_LIMIT,LOFC_TREE_DEPTH,LOFC_SPARSE_OID,+LOFC_OBJECT_TYPE,LOFC_COMBINE,LOFC__COUNT/* must be last */};
@@ -545,6 +545,81 @@ static void filter_sparse_oid__init(filter->free_fn=filter_sparse_free;}+/*+*Afilterforlist-objectstoomitlargeblobs.+*AndtoOPTIONALLYcollectalistoftheomittedOIDs.+*/+structfilter_object_type_data{+enumobject_typeobject_type;+};++staticenumlist_objects_filter_resultfilter_object_type(+structrepository*r,+enumlist_objects_filter_situationfilter_situation,+structobject*obj,+constchar*pathname,+constchar*filename,+structoidset*omits,+void*filter_data_)+{+structfilter_object_type_data*filter_data=filter_data_;++switch(filter_situation){+default:+BUG("unknown filter_situation: %d",filter_situation);++caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+if(filter_data->object_type==OBJ_TAG)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+returnLOFR_MARK_SEEN;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+if(filter_data->object_type==OBJ_COMMIT)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+returnLOFR_MARK_SEEN;++caseLOFS_BEGIN_TREE:+assert(obj->type==OBJ_TREE);++/*+*Ifweonlywanttoshowcommitsortags,thenthereisno+*needtowalkdowntrees.+*/+if(filter_data->object_type==OBJ_COMMIT||+filter_data->object_type==OBJ_TAG)+returnLOFR_SKIP_TREE;++if(filter_data->object_type==OBJ_TREE)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++returnLOFR_MARK_SEEN;++caseLOFS_BLOB:+assert(obj->type==OBJ_BLOB);++if(filter_data->object_type==OBJ_BLOB)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+returnLOFR_MARK_SEEN;++caseLOFS_END_TREE:+returnLOFR_ZERO;+}+}++staticvoidfilter_object_type__init(+structlist_objects_filter_options*filter_options,+structfilter*filter)+{+structfilter_object_type_data*d=xcalloc(1,sizeof(*d));+d->object_type=filter_options->object_type;++filter->filter_data=d;+filter->filter_object_fn=filter_object_type;+filter->free_fn=free;+}+/* A filter which only shows objects shown by all sub-filters. */structcombine_filter_data{structsubfilter*sub;
@@ -159,6 +159,54 @@ test_expect_success 'verify blob:limit=1m' 'test_must_be_emptyobserved'+# Test object:type=<type> filter.++test_expect_success'setup object-type''+gitinitobject-type&&+echocontents>object-type/blob&&+git-Cobject-typeaddblob&&+git-Cobject-typecommit-mcommit-message&&+git-Cobject-typetagtag-mtag-message+'++test_expect_success'verify object:type= fails with invalid type''+test_must_failgit-Cobject-typerev-list--objects--filter=object:type=HEAD&&+test_must_failgit-Cobject-typerev-list--objects--filter=object:type=invalidHEAD+'++test_expect_success'verify object:type=blob prints blob and commit''+(+git-Cobject-typerev-parseHEAD&&+printf"%s blob\n"$(git-Cobject-typerev-parseHEAD:blob)+)>expected&&+git-Cobject-typerev-list--objects--filter=object:type=blobHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tree prints tree and commit''+(+git-Cobject-typerev-parseHEAD&&+printf"%s \n"$(git-Cobject-typerev-parseHEAD^{tree})+)>expected&&+git-Cobject-typerev-list--objects--filter=object:type=treeHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=commit prints commit''+git-Cobject-typerev-parseHEAD>expected&&+git-Cobject-typerev-list--objects--filter=object:type=commitHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tag prints tag''+(+git-Cobject-typerev-parseHEAD&&+printf"%s tag\n"$(git-Cobject-typerev-parsetag)+)>expected&&+git-Cobject-typerev-list--objects--filter=object:type=tagtag>actual&&+test_cmpexpectedactual+'+# Test sparse:path=<path> filter.# !!!!# NOTE: sparse:path filter support has been dropped for security reasons,
From: Patrick Steinhardt <hidden> Date: 2021-04-12 13:37:47
The preceding commit has added a new object filter for git-rev-list(1)
which allows to filter objects by type. Implement the equivalent filter
for packfile bitmaps so that we can answer these queries fast.
Signed-off-by: Patrick Steinhardt <redacted>
---
pack-bitmap.c | 29 ++++++++++++++++++++++++++---
t/t6113-rev-list-bitmap-filters.sh | 25 ++++++++++++++++++++++++-
2 files changed, 50 insertions(+), 4 deletions(-)
From: Patrick Steinhardt <hidden> Date: 2021-04-12 13:37:53
When the user has multiple objects filters specified, then this is
internally represented by having a "combined" filter. These combined
filters aren't yet supported by bitmap indices and can thus not be
accelerated.
Fix this by implementing support for these combined filters. The
implementation is quite trivial: when there's a combined filter, we
simply recurse into `filter_bitmap()` for all of the sub-filters.
Signed-off-by: Patrick Steinhardt <redacted>
---
pack-bitmap.c | 10 ++++++++++
t/t6113-rev-list-bitmap-filters.sh | 7 +++++++
2 files changed, 17 insertions(+)
From: Patrick Steinhardt <hidden> Date: 2021-04-12 13:37:58
When providing an object filter, it is currently impossible to also
filter provided items. E.g. when executing `git rev-list HEAD` , the
commit this reference points to will be treated as user-provided and is
thus excluded from the filtering mechanism. This makes it harder than
necessary to properly use the new `--filter=object:type` filter given
that even if the user wants to only see blobs, he'll still see commits
of provided references.
Improve this by introducing a new `--filter-provided-objects` option
to the git-rev-parse(1) command. If given, then all user-provided
references will be subject to filtering.
Signed-off-by: Patrick Steinhardt <redacted>
---
Documentation/rev-list-options.txt | 5 ++++
builtin/pack-objects.c | 2 +-
builtin/rev-list.c | 36 +++++++++++++++++++++--------
pack-bitmap.c | 6 +++--
pack-bitmap.h | 3 ++-
reachable.c | 2 +-
t/t6112-rev-list-filters-objects.sh | 28 ++++++++++++++++++++++
t/t6113-rev-list-bitmap-filters.sh | 36 +++++++++++++++++++++++++++++
8 files changed, 104 insertions(+), 14 deletions(-)
@@ -933,6 +933,11 @@ equivalent. --no-filter:: Turn off any previous `--filter=` argument.+--filter-provided-objects::+ Filter the list of explicitly provided objects, which would otherwise+ always be printed even if they did not match any of the filters. Only+ useful with `--filter=`.+ --filter-print-omitted:: Only useful with `--filter=`; prints a list of the objects omitted by the filter. Object IDs are prefixed with a ``~'' character.
@@ -207,6 +207,34 @@ test_expect_success 'verify object:type=tag prints tag' 'test_cmpexpectedactual'+test_expect_success'verify object:type=blob prints only blob with --filter-provided-objects''+printf"%s blob\n"$(git-Cobject-typerev-parseHEAD:blob)>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=blob--filter-provided-objectsHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tree prints only tree with --filter-provided-objects''+printf"%s \n"$(git-Cobject-typerev-parseHEAD^{tree})>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=treeHEAD--filter-provided-objects>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=commit prints only commit with --filter-provided-objects''+git-Cobject-typerev-parseHEAD>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=commit--filter-provided-objectsHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tag prints only tag with --filter-provided-objects''+printf"%s tag\n"$(git-Cobject-typerev-parsetag)>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=tag--filter-provided-objectstag>actual&&+test_cmpexpectedactual+'+# Test sparse:path=<path> filter.# !!!!# NOTE: sparse:path filter support has been dropped for security reasons,
From: Jeff King <hidden> Date: 2021-04-13 07:45:30
On Mon, Apr 12, 2021 at 03:37:14PM +0200, Patrick Steinhardt wrote:
this is the fourth version of my patch series which implements a new
`object:type` filter for git-rev-list(1) and git-upload-pack(1) and
extends support for bitmap indices to work with combined filters.
Changes compared to v3:
- Small style fix to not pass an empty string and `0`, but instead
simply pass two `NULL` pointers to
`list_objects_filter__filter_object, pointed out by Junio.
- I've changed patch 7/8 as proposed by Peff: support of combined
filters is now determined directly in `filter_bitmap()`, without
having to mirror all filter types in the new `filter_supported()`
function.
- Renamed `--filter-provided-revisions` to
`--filter-provided-objects` as proposed by Peff and addressed both
commit message and tests as pointed out by Philip.
Thanks. I have no more nits to pick. The only question left for me is
the big one of "do we like this with --filter, or should it be some kind
of rev-list option", as discussed in:
https://lore.kernel.org/git/YHB7R8hVRt+V+i2W@coredump.intra.peff.net/
As I said elsewhere, I'm OK going with this route. I just wanted to call
attention to that earlier response in case you had any final thoughts
(I'm guessing your final thoughts are "jeez, I already wrote it with
--filter, so let's just go with that" which is perfectly reasonable to
me. ;) ).
-Peff
From: Patrick Steinhardt <hidden> Date: 2021-04-13 08:06:23
On Tue, Apr 13, 2021 at 03:45:21AM -0400, Jeff King wrote:
On Mon, Apr 12, 2021 at 03:37:14PM +0200, Patrick Steinhardt wrote:
quoted
this is the fourth version of my patch series which implements a new
`object:type` filter for git-rev-list(1) and git-upload-pack(1) and
extends support for bitmap indices to work with combined filters.
Changes compared to v3:
- Small style fix to not pass an empty string and `0`, but instead
simply pass two `NULL` pointers to
`list_objects_filter__filter_object, pointed out by Junio.
- I've changed patch 7/8 as proposed by Peff: support of combined
filters is now determined directly in `filter_bitmap()`, without
having to mirror all filter types in the new `filter_supported()`
function.
- Renamed `--filter-provided-revisions` to
`--filter-provided-objects` as proposed by Peff and addressed both
commit message and tests as pointed out by Philip.
Thanks. I have no more nits to pick. The only question left for me is
the big one of "do we like this with --filter, or should it be some kind
of rev-list option", as discussed in:
https://lore.kernel.org/git/YHB7R8hVRt+V+i2W@coredump.intra.peff.net/
As I said elsewhere, I'm OK going with this route. I just wanted to call
attention to that earlier response in case you had any final thoughts
(I'm guessing your final thoughts are "jeez, I already wrote it with
--filter, so let's just go with that" which is perfectly reasonable to
me. ;) ).
I don't think it would help usability to add new `--show-blobs` and
`--show-trees` options. The user interface to show this kind of
information exists already with `--objects`, and by adding another way
of asking a similar query would raise the question of how these two ways
interact with each other:
- Does `--show-blobs` have effect if `--objects` is not set?
- Is `--objects` redundant if we have `--show-blobs`, or would
`--objects --show-blobs` list all objects regardless of whether
they're blobs or not?
- What would happen if the user says `--show-blobs --no-objects`?
- Are these options mutually exclusive?
We avoid all these questions by just adding it as a filter.
Furthermore, the filter also allows future iterations which build on top
of this. If we had a combined OR filter in addition to the existing
combined AND filter, the user could say "Give me all blobs which aren't
bigger than a specific size PLUS all trees with a depth smaller than 5
PLUS all commits and tags". It's not like I'd know of a specific usecase
for this right now, but I think the potential of having such filters in
the future is a plus.
Patrick
It must be an annotated tag because that's what the `--object` flag of
git-rev-list(1) is working on: objects. But anyway, as you say I can
still use these two helpers and then manually create the tag.
quoted
+test_expect_success 'verify object:type=blob prints blob and commit' '+ (+ git -C object-type rev-parse HEAD &&+ printf "%s blob\n" $(git -C object-type rev-parse HEAD:blob)+ ) >expected &&
Just use > and >> or a single printf with two arguments instead of a
subshell?
I don't really mind, happy to take this proposal.
Thanks!
Patrick
From: Jeff King <hidden> Date: 2021-04-15 09:42:35
On Tue, Apr 13, 2021 at 10:06:13AM +0200, Patrick Steinhardt wrote:
I don't think it would help usability to add new `--show-blobs` and
`--show-trees` options. The user interface to show this kind of
information exists already with `--objects`, and by adding another way
of asking a similar query would raise the question of how these two ways
interact with each other:
- Does `--show-blobs` have effect if `--objects` is not set?
- Is `--objects` redundant if we have `--show-blobs`, or would
`--objects --show-blobs` list all objects regardless of whether
they're blobs or not?
- What would happen if the user says `--show-blobs --no-objects`?
- Are these options mutually exclusive?
We avoid all these questions by just adding it as a filter.
I'm not too worried about those. I'd imagine that "--objects" becomes a
documented synonym for "--show-trees --show-blobs --show-commits
--show-tags", and then the usual interactions take over.
But...
Furthermore, the filter also allows future iterations which build on top
of this. If we had a combined OR filter in addition to the existing
combined AND filter, the user could say "Give me all blobs which aren't
bigger than a specific size PLUS all trees with a depth smaller than 5
PLUS all commits and tags". It's not like I'd know of a specific usecase
for this right now, but I think the potential of having such filters in
the future is a plus.
Yeah, that's true. My biggest complaint is lack of an OR filter, but we
could add that later. And then we would be _more_ flexible, as you note,
since we could and/or more filters.
So I'm OK proceeding with this direction.
-Peff
From: Patrick Steinhardt <hidden> Date: 2021-04-19 11:46:46
When `uploadpackfilter.allow` is set to `true`, it means that filters
are enabled by default except in the case where a filter is explicitly
disabled via `uploadpackilter.<filter>.allow`. This option will not only
enable the currently supported set of filters, but also any filters
which get added in the future. As such, an admin which wants to have
tight control over which filters are allowed and which aren't probably
shouldn't ever set `uploadpackfilter.allow=true`.
Amend the documentation to make the ramifications more explicit so that
admins are aware of this.
Signed-off-by: Patrick Steinhardt <redacted>
---
Documentation/config/uploadpack.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
@@ -59,7 +59,8 @@ uploadpack.allowFilter:: uploadpackfilter.allow:: Provides a default value for unspecified object filters (see: the- below configuration variable).+ below configuration variable). If set to `true`, this will also+ enable all filters which get added in the future. Defaults to `true`. uploadpackfilter.<filter>.allow::
From: Patrick Steinhardt <hidden> Date: 2021-04-19 11:46:48
The NOT_USER_GIVEN flag of an object marks whether a flag was explicitly
provided by the user or not. The most important use case for this is
when filtering objects: only objects that were not explicitly requested
will get filtered.
The flag is currently only set for blobs and trees, which has been fine
given that there are no filters for tags or commits currently. We're
about to extend filtering capabilities to add object type filter though,
which requires us to set up the NOT_USER_GIVEN flag correctly -- if it's
not set, the object wouldn't get filtered at all.
Mark unseen commit parents as NOT_USER_GIVEN when processing parents.
Like this, explicitly provided parents stay user-given and thus
unfiltered, while parents which get loaded as part of the graph walk
can be filtered.
This commit shouldn't have any user-visible impact yet as there is no
logic to filter commits yet.
Signed-off-by: Patrick Steinhardt <redacted>
---
revision.c | 4 ++--
revision.h | 3 ---
2 files changed, 2 insertions(+), 5 deletions(-)
From: Patrick Steinhardt <hidden> Date: 2021-04-19 11:46:52
Move processing of tags into its own function to make the logic easier
to extend when we're going to implement filtering for tags. No change in
behaviour is expected from this commit.
Signed-off-by: Patrick Steinhardt <redacted>
---
list-objects.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
From: Patrick Steinhardt <hidden> Date: 2021-04-19 11:46:58
Object filters currently only support filtering blobs or trees based on
some criteria. This commit lays the foundation to also allow filtering
of tags and commits.
No change in behaviour is expected from this commit given that there are
no filters yet for those object types.
Signed-off-by: Patrick Steinhardt <redacted>
---
list-objects-filter.c | 40 ++++++++++++++++++++++++++++++++++++++++
list-objects-filter.h | 2 ++
list-objects.c | 23 ++++++++++++++++++++---
3 files changed, 62 insertions(+), 3 deletions(-)
@@ -82,6 +82,16 @@ static enum list_objects_filter_result filter_blobs_none(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_BEGIN_TREE:assert(obj->type==OBJ_TREE);/* always include all tree objects */
@@ -173,6 +183,16 @@ static enum list_objects_filter_result filter_trees_depth(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_END_TREE:assert(obj->type==OBJ_TREE);filter_data->current_depth--;
@@ -267,6 +287,16 @@ static enum list_objects_filter_result filter_blobs_limit(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_BEGIN_TREE:assert(obj->type==OBJ_TREE);/* always include all tree objects */
@@ -371,6 +401,16 @@ static enum list_objects_filter_result filter_sparse(default:BUG("unknown filter_situation: %d",filter_situation);+caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+/* always include all tag objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+/* always include all commit objects */+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+caseLOFS_BEGIN_TREE:assert(obj->type==OBJ_TREE);dtype=DT_DIR;
From: Patrick Steinhardt <hidden> Date: 2021-04-19 11:47:19
While it already is possible to filter objects by some criteria in
git-rev-list(1), it is not yet possible to filter out only a specific
type of objects. This makes some filters less useful. The `blob:limit`
filter for example filters blobs such that only those which are smaller
than the given limit are returned. But it is unfit to ask only for these
smallish blobs, given that git-rev-list(1) will continue to print tags,
commits and trees.
Now that we have the infrastructure in place to also filter tags and
commits, we can improve this situation by implementing a new filter
which selects objects based on their type. Above query can thus
trivially be implemented with the following command:
$ git rev-list --objects --filter=object:type=blob \
--filter=blob:limit=200
Furthermore, this filter allows to optimize for certain other cases: if
for example only tags or commits have been selected, there is no need to
walk down trees.
The new filter is not yet supported in bitmaps. This is going to be
implemented in a subsequent commit.
Signed-off-by: Patrick Steinhardt <redacted>
---
Documentation/config/uploadpack.txt | 6 +--
Documentation/rev-list-options.txt | 3 ++
list-objects-filter-options.c | 15 ++++++
list-objects-filter-options.h | 3 ++
list-objects-filter.c | 76 +++++++++++++++++++++++++++++
t/t6112-rev-list-filters-objects.sh | 44 +++++++++++++++++
6 files changed, 144 insertions(+), 3 deletions(-)
@@ -66,9 +66,9 @@ uploadpackfilter.allow:: uploadpackfilter.<filter>.allow:: Explicitly allow or ban the object filter corresponding to `<filter>`, where `<filter>` may be one of: `blob:none`,- `blob:limit`, `tree`, `sparse:oid`, or `combine`. If using- combined filters, both `combine` and all of the nested filter- kinds must be allowed. Defaults to `uploadpackfilter.allow`.+ `blob:limit`, `object:type`, `tree`, `sparse:oid`, or `combine`.+ If using combined filters, both `combine` and all of the nested+ filter kinds must be allowed. Defaults to `uploadpackfilter.allow`. uploadpackfilter.tree.maxDepth:: Only allow `--filter=tree:<n>` when `<n>` is no more than the value of
@@ -892,6 +892,9 @@ or units. n may be zero. The suffixes k, m, and g can be used to name units in KiB, MiB, or GiB. For example, 'blob:limit=1k' is the same as 'blob:limit=1024'. ++The form '--filter=object:type=(tag|commit|tree|blob)' omits all objects+which are not of the requested type.++ The form '--filter=sparse:oid=<blob-ish>' uses a sparse-checkout specification contained in the blob (or blob-expression) '<blob-ish>' to omit blobs that would not be not required for a sparse checkout on
@@ -97,6 +99,19 @@ static int gently_parse_list_objects_filter(}return1;+}elseif(skip_prefix(arg,"object:type=",&v0)){+inttype=type_from_string_gently(v0,strlen(v0),1);+if(type<0){+strbuf_addf(errbuf,_("'%s' for 'object:type=<type>' is"+"not a valid object type"),v0);+return1;+}++filter_options->object_type=type;+filter_options->choice=LOFC_OBJECT_TYPE;++return0;+}elseif(skip_prefix(arg,"combine:",&v0)){returnparse_combine_filter(filter_options,v0,errbuf);
@@ -13,6 +14,7 @@ enum list_objects_filter_choice {LOFC_BLOB_LIMIT,LOFC_TREE_DEPTH,LOFC_SPARSE_OID,+LOFC_OBJECT_TYPE,LOFC_COMBINE,LOFC__COUNT/* must be last */};
@@ -545,6 +545,81 @@ static void filter_sparse_oid__init(filter->free_fn=filter_sparse_free;}+/*+*Afilterforlist-objectstoomitlargeblobs.+*AndtoOPTIONALLYcollectalistoftheomittedOIDs.+*/+structfilter_object_type_data{+enumobject_typeobject_type;+};++staticenumlist_objects_filter_resultfilter_object_type(+structrepository*r,+enumlist_objects_filter_situationfilter_situation,+structobject*obj,+constchar*pathname,+constchar*filename,+structoidset*omits,+void*filter_data_)+{+structfilter_object_type_data*filter_data=filter_data_;++switch(filter_situation){+default:+BUG("unknown filter_situation: %d",filter_situation);++caseLOFS_TAG:+assert(obj->type==OBJ_TAG);+if(filter_data->object_type==OBJ_TAG)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+returnLOFR_MARK_SEEN;++caseLOFS_COMMIT:+assert(obj->type==OBJ_COMMIT);+if(filter_data->object_type==OBJ_COMMIT)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+returnLOFR_MARK_SEEN;++caseLOFS_BEGIN_TREE:+assert(obj->type==OBJ_TREE);++/*+*Ifweonlywanttoshowcommitsortags,thenthereisno+*needtowalkdowntrees.+*/+if(filter_data->object_type==OBJ_COMMIT||+filter_data->object_type==OBJ_TAG)+returnLOFR_SKIP_TREE;++if(filter_data->object_type==OBJ_TREE)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;++returnLOFR_MARK_SEEN;++caseLOFS_BLOB:+assert(obj->type==OBJ_BLOB);++if(filter_data->object_type==OBJ_BLOB)+returnLOFR_MARK_SEEN|LOFR_DO_SHOW;+returnLOFR_MARK_SEEN;++caseLOFS_END_TREE:+returnLOFR_ZERO;+}+}++staticvoidfilter_object_type__init(+structlist_objects_filter_options*filter_options,+structfilter*filter)+{+structfilter_object_type_data*d=xcalloc(1,sizeof(*d));+d->object_type=filter_options->object_type;++filter->filter_data=d;+filter->filter_object_fn=filter_object_type;+filter->free_fn=free;+}+/* A filter which only shows objects shown by all sub-filters. */structcombine_filter_data{structsubfilter*sub;
@@ -159,6 +159,50 @@ test_expect_success 'verify blob:limit=1m' 'test_must_be_emptyobserved'+# Test object:type=<type> filter.++test_expect_success'setup object-type''+test_create_repoobject-type&&+test_commit--no-tag-Cobject-typemessageblob&&+git-Cobject-typetagtag-mtag-message+'++test_expect_success'verify object:type= fails with invalid type''+test_must_failgit-Cobject-typerev-list--objects--filter=object:type=HEAD&&+test_must_failgit-Cobject-typerev-list--objects--filter=object:type=invalidHEAD+'++test_expect_success'verify object:type=blob prints blob and commit''+git-Cobject-typerev-parseHEAD>expected&&+printf"%s blob\n"$(git-Cobject-typerev-parseHEAD:blob)>>expected&&+git-Cobject-typerev-list--objects--filter=object:type=blobHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tree prints tree and commit''+(+git-Cobject-typerev-parseHEAD&&+printf"%s \n"$(git-Cobject-typerev-parseHEAD^{tree})+)>expected&&+git-Cobject-typerev-list--objects--filter=object:type=treeHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=commit prints commit''+git-Cobject-typerev-parseHEAD>expected&&+git-Cobject-typerev-list--objects--filter=object:type=commitHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tag prints tag''+(+git-Cobject-typerev-parseHEAD&&+printf"%s tag\n"$(git-Cobject-typerev-parsetag)+)>expected&&+git-Cobject-typerev-list--objects--filter=object:type=tagtag>actual&&+test_cmpexpectedactual+'+# Test sparse:path=<path> filter.# !!!!# NOTE: sparse:path filter support has been dropped for security reasons,
From: Patrick Steinhardt <hidden> Date: 2021-04-19 11:47:21
When the user has multiple objects filters specified, then this is
internally represented by having a "combined" filter. These combined
filters aren't yet supported by bitmap indices and can thus not be
accelerated.
Fix this by implementing support for these combined filters. The
implementation is quite trivial: when there's a combined filter, we
simply recurse into `filter_bitmap()` for all of the sub-filters.
Signed-off-by: Patrick Steinhardt <redacted>
---
pack-bitmap.c | 10 ++++++++++
t/t6113-rev-list-bitmap-filters.sh | 7 +++++++
2 files changed, 17 insertions(+)
From: Patrick Steinhardt <hidden> Date: 2021-04-19 11:47:27
The preceding commit has added a new object filter for git-rev-list(1)
which allows to filter objects by type. Implement the equivalent filter
for packfile bitmaps so that we can answer these queries fast.
Signed-off-by: Patrick Steinhardt <redacted>
---
pack-bitmap.c | 29 ++++++++++++++++++++++++++---
t/t6113-rev-list-bitmap-filters.sh | 25 ++++++++++++++++++++++++-
2 files changed, 50 insertions(+), 4 deletions(-)
From: Patrick Steinhardt <hidden> Date: 2021-04-19 11:48:23
When providing an object filter, it is currently impossible to also
filter provided items. E.g. when executing `git rev-list HEAD` , the
commit this reference points to will be treated as user-provided and is
thus excluded from the filtering mechanism. This makes it harder than
necessary to properly use the new `--filter=object:type` filter given
that even if the user wants to only see blobs, he'll still see commits
of provided references.
Improve this by introducing a new `--filter-provided-objects` option
to the git-rev-parse(1) command. If given, then all user-provided
references will be subject to filtering.
Signed-off-by: Patrick Steinhardt <redacted>
---
Documentation/rev-list-options.txt | 5 ++++
builtin/pack-objects.c | 2 +-
builtin/rev-list.c | 36 +++++++++++++++++++++--------
pack-bitmap.c | 6 +++--
pack-bitmap.h | 3 ++-
reachable.c | 2 +-
t/t6112-rev-list-filters-objects.sh | 28 ++++++++++++++++++++++
t/t6113-rev-list-bitmap-filters.sh | 36 +++++++++++++++++++++++++++++
8 files changed, 104 insertions(+), 14 deletions(-)
@@ -933,6 +933,11 @@ equivalent. --no-filter:: Turn off any previous `--filter=` argument.+--filter-provided-objects::+ Filter the list of explicitly provided objects, which would otherwise+ always be printed even if they did not match any of the filters. Only+ useful with `--filter=`.+ --filter-print-omitted:: Only useful with `--filter=`; prints a list of the objects omitted by the filter. Object IDs are prefixed with a ``~'' character.
@@ -203,6 +203,34 @@ test_expect_success 'verify object:type=tag prints tag' 'test_cmpexpectedactual'+test_expect_success'verify object:type=blob prints only blob with --filter-provided-objects''+printf"%s blob\n"$(git-Cobject-typerev-parseHEAD:blob)>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=blob--filter-provided-objectsHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tree prints only tree with --filter-provided-objects''+printf"%s \n"$(git-Cobject-typerev-parseHEAD^{tree})>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=treeHEAD--filter-provided-objects>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=commit prints only commit with --filter-provided-objects''+git-Cobject-typerev-parseHEAD>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=commit--filter-provided-objectsHEAD>actual&&+test_cmpexpectedactual+'++test_expect_success'verify object:type=tag prints only tag with --filter-provided-objects''+printf"%s tag\n"$(git-Cobject-typerev-parsetag)>expected&&+git-Cobject-typerev-list--objects\+--filter=object:type=tag--filter-provided-objectstag>actual&&+test_cmpexpectedactual+'+# Test sparse:path=<path> filter.# !!!!# NOTE: sparse:path filter support has been dropped for security reasons,