support for filtering trees and blobs based on depth
cover support for filtering trees and blobs based on depth
This adds support for depth >0 in the tree:<depth> filter. Before this patch, only <depth>=0 is supported, which means all trees and blobs are filtered.
The purpose of this is to allow fetching of entire directories in a partial clone use case. If I do a partial clone of a repo with no objects and then want to do something like "make" it will be quite slow of we initiate a separate fetch for every file needed. Alternatively, fetching directories at a time - as soon as any file in a directory is accessed - is a reasonable approach.
Thank you,
Matthew DeVore (3):
list-objects: support for skipping tree traversal Documentation/git-rev-list: s/<commit>/<object>/
list-objects-filter: teach tree:# how to handle >0
Documentation/git-rev-list.txt | 21 ++++--- Documentation/rev-list-options.txt | 24 +++++--- builtin/rev-list.c | 2 +- list-objects-filter-options.c | 6 +- list-objects-filter-options.h | 1 + list-objects-filter.c | 52 +++++++++++++--- list-objects-filter.h | 6 ++ list-objects.c | 5 +- t/t6112-rev-list-filters-objects.sh | 94 +++++++++++++++++++++++++++++ 9 files changed, 178 insertions(+), 33 deletions(-)
Signature · 1 line
2.19.1.331.ge82ca0e54c-goog
RFC PATCH 1/3 list-objects: support for skipping tree traversal
The tree:0 filter does not need to traverse the trees that it has filtered out, so optimize list-objects and list-objects-filter to skip traversing the trees entirely. Before this patch, we iterated over all children of the tree, and did nothing for all of them, which was wasteful.
--- list-objects-filter.c | 11 +++++++++-- list-objects-filter.h | 6 ++++++ list-objects.c | 5 ++++- t/t6112-rev-list-filters-objects.sh | 10 ++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-)
list-objects-filter.c +9 -2
@@ -102,9 +102,16 @@ static enum list_objects_filter_result filter_trees_none(case LOFS_BEGIN_TREE :case LOFS_BLOB : -if (filter_data->omits) +if (filter_data->omits) {oidset_insert (filter_data->omits, &obj->oid); -return LOFR_MARK_SEEN ;/* but not LOFR_DO_SHOW (hard omit) */ +/* _MARK_SEEN but not _DO_SHOW (hard omit) */ +return LOFR_MARK_SEEN ; + } +else +/* +* Not collecting omits so no need to to traverse tree. +*/ +return LOFR_SKIP_TREE |LOFR_MARK_SEEN ;case LOFS_END_TREE :assert (obj->type ==OBJ_TREE );
list-objects-filter.h +6 -0
@@ -24,6 +24,11 @@ struct oidset;* In general, objects should only be shown once, but * this result DOES NOT imply that we mark it SEEN. * +* _SKIP_TREE : Used in LOFS_BEGIN_TREE situation - indicates that +* the tree's children should not be iterated over. This +* is used as an optimization when all children will +* definitely be ignored. +* * Most of the time, you want the combination (_MARK_SEEN | _DO_SHOW) * but they can be used independently, such as when sparse-checkout * pattern matching is being applied.
@@ -45,6 +50,7 @@ enum list_objects_filter_result {LOFR_ZERO =0 ,LOFR_MARK_SEEN =1 <<0 ,LOFR_DO_SHOW =1 <<1 , + LOFR_SKIP_TREE = 1<<2, }; enum list_objects_filter_situation {
list-objects.c +4 -1
@@ -11,6 +11,7 @@#include "list-objects-filter-options.h" #include "packfile.h" #include "object-store.h" +#include "trace.h" struct traversal_context {struct rev_info *revs;
@@ -184,7 +185,9 @@ static void process_tree(struct traversal_context *ctx,if (base->len)strbuf_addch (base,'/' ); -if (!failed_parse) +if (r &LOFR_SKIP_TREE ) +trace_printf ("Skipping contents of tree %s...\n" , base->buf); +else if (!failed_parse)process_tree_contents (ctx, tree, base);if ((obj->flags &NOT_USER_GIVEN ) && ctx->filter_fn) {
t/t6112-rev-list-filters-objects.sh +10 -0
@@ -244,6 +244,16 @@ test_expect_success 'verify tree:0 includes trees in "filtered" output' ' test_cmp expected filtered_types ' +# Make sure tree:0 does not iterate through any trees. + +test_expect_success 'filter a GIANT tree through tree:0' ' + GIT_TRACE=1 git -C r3 rev-list \ + --objects --filter=tree:0 HEAD 2>filter_trace && + grep "Skipping contents of tree [.][.][.]" filter_trace >actual && + # One line for each commit traversed. + test_line_count = 2 actual +' + # Delete some loose objects and use rev-list, but WITHOUT any filtering. # This models previously omitted objects that we did not receive.
Signature · 1 line
2.19.1.331.ge82ca0e54c-goog
Re: [RFC PATCH 1/3] list-objects: support for skipping tree traversal
Quoted 6 lines · Matthew DeVore
The tree:0 filter does not need to traverse the trees that it has filtered out, so optimize list-objects and list-objects-filter to skip traversing the trees entirely. Before this patch, we iterated over all children of the tree, and did nothing for all of them, which was wasteful.
Quoted 7 lines
Signed-off-by Matthew DeVore <[email protected]>--- list-objects-filter.c | 11 +++++++++-- list-objects-filter.h | 6 ++++++ list-objects.c | 5 ++++- t/t6112-rev-list-filters-objects.sh | 10 ++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-)
This step looks more like "ow, we could have done the tree:0 support that is in 'next' better" than a part of "here is a series to do tree:N for non zero value of N".
If that is the case, I'd prefer to see this step polished enough before [2-3/3] of this RFC is worked on, so that we can merge the tree:0 (but not yet tree:N) support that is solid down to 'master' soonish.
Quoted 10 lines
list-objects-filter.c +1
-1@@ -102,9 +102,16 @@ static enum list_objects_filter_result filter_trees_none(case LOFS_BEGIN_TREE :case LOFS_BLOB : -if (filter_data->omits) +if (filter_data->omits) {
list-objects-filter.c static enum list_objects_filter_result filter_trees_none( · line 114
Quoted 10 lines
list-objects-filter.c +8
-1oidset_insert (filter_data->omits, &obj->oid); -return LOFR_MARK_SEEN ;/* but not LOFR_DO_SHOW (hard omit) */ +/* _MARK_SEEN but not _DO_SHOW (hard omit) */ +return LOFR_MARK_SEEN ; + } +else +/* +* Not collecting omits so no need to to traverse tree. +*/ +return LOFR_SKIP_TREE |LOFR_MARK_SEEN ;
OK, so "not collecting omits" is synonymous to "N==0, we aren't doing tree at any level", and at this point in the series before the support for N>0 is introduced, we'd always take this "else" clause because tree:0 is the only thing we support?
Style: our modern style is to use {} around the body which is a single statement on the else clause when the body of the corresponding if clause needs {} around (and vice versa), so
if (filter_data->omits) {
oidset_isnert(...);
return ...;
} else {
return ...;
}Quoted 37 lines
list-objects-filter.h +6
-0@@ -24,6 +24,11 @@ struct oidset;* In general, objects should only be shown once, but * this result DOES NOT imply that we mark it SEEN. * +* _SKIP_TREE : Used in LOFS_BEGIN_TREE situation - indicates that +* the tree's children should not be iterated over. This +* is used as an optimization when all children will +* definitely be ignored. +* * Most of the time, you want the combination (_MARK_SEEN | _DO_SHOW) * but they can be used independently, such as when sparse-checkout * pattern matching is being applied. @@ -45,6 +50,7 @@ enum list_objects_filter_result {LOFR_ZERO =0 ,LOFR_MARK_SEEN =1 <<0 ,LOFR_DO_SHOW =1 <<1 , + LOFR_SKIP_TREE = 1<<2, }; enum list_objects_filter_situation {list-objects.c +1
-0@@ -11,6 +11,7 @@#include "list-objects-filter-options.h" #include "packfile.h" #include "object-store.h" +#include "trace.h" struct traversal_context {struct rev_info *revs;
list-objects.c static void process_tree(struct traversal_context *ctx, · line 191
Quoted 8 lines
list-objects.c +3
-1@@ -184,7 +185,9 @@ static void process_tree(struct traversal_context *ctx,if (base->len)strbuf_addch (base,'/' ); -if (!failed_parse) +if (r &LOFR_SKIP_TREE ) +trace_printf ("Skipping contents of tree %s...\n" , base->buf); +else if (!failed_parse)process_tree_contents (ctx, tree, base);
Even when failed_parse==true, i.e. we found that the tree object's data cannot be understood, if we have skip-tree bit set, that means we do not care---we won't be descending into its children anyway.
Makes sense.
Quoted 5 lines
t/t6112-rev-list-filters-objects.sh +0
-0
t/t6112-rev-list-filters-objects.sh test_expect_success 'verify tree:0 includes trees in "filtered" output' ' · line 252
Quoted 9 lines
t/t6112-rev-list-filters-objects.sh +6
-0@@ -244,6 +244,16 @@ test_expect_success 'verify tree:0 includes trees in "filtered" output' ' test_cmp expected filtered_types ' +# Make sure tree:0 does not iterate through any trees. + +test_expect_success 'filter a GIANT tree through tree:0' ' + GIT_TRACE=1 git -C r3 rev-list \ + --objects --filter=tree:0 HEAD 2>filter_trace && + grep "Skipping contents of tree [.][.][.]" filter_trace >actual &&
Here you are not jus tmaking sure SKIP_TREE bit is set for some tree, but it is set when base->buf is an empty string (i.e. the top level tree)? Which makes sense, and the next text makes sure that between the two commits, the number of total "top level" trees is 2, but I wonder if it is more direct to also make sure that the code is not even seeing or skipping any tree inside these top level trees (i.e. the same message but for ""!=base->buf should never appear in the trace).
t/t6112-rev-list-filters-objects.sh test_expect_success 'verify tree:0 includes trees in "filtered" output' ' · line 258
Quoted 6 lines
t/t6112-rev-list-filters-objects.sh +4
-0+ # One line for each commit traversed. + test_line_count = 2 actual +' + # Delete some loose objects and use rev-list, but WITHOUT any filtering. # This models previously omitted objects that we did not receive.
Thanks.
Re: [RFC PATCH 1/3] list-objects: support for skipping tree traversal
Quoted 9 lines · Junio C Hamano
This step looks more like "ow, we could have done the tree:0 support that is in 'next' better" than a part of "here is a series to do tree:N for non zero value of N".
If that is the case, I'd prefer to see this step polished enough before [2-3/3] of this RFC is worked on, so that we can merge the tree:0 (but not yet tree:N) support that is solid down to 'master' soonish.
That's fair. So I will prioritize this patch above the rest of the patchset and send it separately in the future.
Quoted 4 lines
OK, so "not collecting omits" is synonymous to "N==0, we aren't doing tree at any level", and at this point in the series before the support for N>0 is introduced, we'd always take this "else" clause because tree:0 is the only thing we support?
Actually "not collecting omits" and depth==0 are orthogonal concepts. "Collect omits" is the logic needed to implement the --filter-print-omitted flag on git rev-list. You can do this when depth==0 (it will actually print all the trees and blobs recursively). "Collect omits" is tested *with* tree:0 in test t6112 in the test labeled 'verify tree:0 includes trees in "filtered" output'
IOW, both branches here are important even in this patch. If we are collecting omits, then we can't skip the tree because omits collecting is recursive. Otherwise, we *can* skip the tree.
But maybe printing omits should not be recursive? The decision was never discussed. The code to not be recursive is simpler, because we don't need this if/else. Recursiveness is counter-intuitive since we would "skip" a tree and at the same time print its contents.
Quoted 5 lines
Style: our modern style is to use {} around the body which is a single statement on the else clause when the body of the corresponding if clause needs {} around (and vice versa), so
Fixed, and I didn't realize I was supposed to be hugging "else" with the curly braces. What you say is what CodingGuidelines says to do. Thanks for pointing that out.
Quoted 5 lines
Even when failed_parse==true, i.e. we found that the tree object's data cannot be understood, if we have skip-tree bit set, that means we do not care---we won't be descending into its children anyway.
Yes.
Quoted 7 lines
t/t6112-rev-list-filters-objects.sh test_expect_success 'verify tree:0 includes trees in "filtered" output' ' · line 252
Quoted 6 lines
t/t6112-rev-list-filters-objects.sh +6
-0+# Make sure tree:0 does not iterate through any trees. + +test_expect_success 'filter a GIANT tree through tree:0' ' + GIT_TRACE=1 git -C r3 rev-list \ + --objects --filter=tree:0 HEAD 2>filter_trace && + grep "Skipping contents of tree [.][.][.]" filter_trace >actual &&
Quoted 8 lines
Here you are not jus tmaking sure
SKIP_TREEbit is set for some tree, but it is set whenbase->bufis an empty string (i.e. the top level tree)? Which makes sense, and the next text makes sure that between the two commits, the number of total "top level" trees is 2, but I wonder if it is more direct to also make sure that the code is not even seeing or skipping any tree inside these top level trees (i.e. the same message but for ""!=base->buf should never appear in the trace).
Makes sense. I added another check in this test for other "Skipping" messages.
Thank you for reviewing.
RFC PATCH 2/3 Documentation/git-rev-list: s/<commit>/<object>/
In reply to Matthew DeVoregit-rev-list has a mode where it works on the granularity of trees and blobs, rather than commits only. When discussing this mode in documenation, it can get awkward to refer to the list of arguments that may include blobs and trees as <commit>. It is especially awkward in a follow-up patch, so prepare for that patch by renaming the argument.
In addition to simply renaming the argument, also reword documentation in some places such that we include non-commit objects in our terminology. In other words, s/commit/object/ in any prose where the context obviously applies to trees and blobs in a non-pathological way.
--- Documentation/git-rev-list.txt | 21 ++++++++++++--------- Documentation/rev-list-options.txt | 16 ++++++++-------- builtin/rev-list.c | 2 +- 3 files changed, 21 insertions(+), 18 deletions(-)
Documentation/git-rev-list.txt +12 -9
@@ -60,20 +60,23 @@ SYNOPSIS [ --no-walk ] [ --do-walk ] [ --count ] [ --use-bitmap-index ] - <commit>... [ \-- <paths>... ] + <object>... [ \-- <paths>... ] DESCRIPTION ----------- -List commits that are reachable by following the `parent` links from the -given commit(s), but exclude commits that are reachable from the one(s) -given with a '{caret}' in front of them. The output is given in reverse -chronological order by default. +List objects that are reachable by following references from the given +object(s), but exclude objects that are reachable from the one(s) given +with a '{caret}' in front of them. -You can think of this as a set operation. Commits given on the command -line form a set of commits that are reachable from any of them, and then -commits reachable from any of the ones given with '{caret}' in front are -subtracted from that set. The remaining commits are what comes out in the +By default, only commit objects are shown, and the commits are shown in +reverse chronological order. The '--object' flag causes non-commit objects +to also be shown. + +You can think of this as a set operation. Objects given on the command +line form a set of objects that are reachable from any of them, and then +objects reachable from any of the ones given with '{caret}' in front are +subtracted from that set. The remaining objects are what come out in the command's output. Various other options and paths parameters can be used to further limit the result.
Documentation/rev-list-options.txt +8 -8
@@ -139,29 +139,29 @@ parents) and `--max-parents=-1` (negative numbers denote no upper limit). --all:: Pretend as if all the refs in `refs/`, along with `HEAD`, are - listed on the command line as '<commit>'. + listed on the command line as '<object>'. --branches[=<pattern>]:: Pretend as if all the refs in `refs/heads` are listed - on the command line as '<commit>'. If '<pattern>' is given, limit + on the command line as '<object>'. If '<pattern>' is given, limit branches to ones matching given shell glob. If pattern lacks '?', '{asterisk}', or '[', '/{asterisk}' at the end is implied. --tags[=<pattern>]:: Pretend as if all the refs in `refs/tags` are listed - on the command line as '<commit>'. If '<pattern>' is given, limit + on the command line as '<object>'. If '<pattern>' is given, limit tags to ones matching given shell glob. If pattern lacks '?', '{asterisk}', or '[', '/{asterisk}' at the end is implied. --remotes[=<pattern>]:: Pretend as if all the refs in `refs/remotes` are listed - on the command line as '<commit>'. If '<pattern>' is given, limit + on the command line as '<object>'. If '<pattern>' is given, limit remote-tracking branches to ones matching given shell glob. If pattern lacks '?', '{asterisk}', or '[', '/{asterisk}' at the end is implied. --glob=<glob-pattern>:: Pretend as if all the refs matching shell glob '<glob-pattern>' - are listed on the command line as '<commit>'. Leading 'refs/', + are listed on the command line as '<object>'. Leading 'refs/', is automatically prepended if missing. If pattern lacks '?', '{asterisk}', or '[', '/{asterisk}' at the end is implied.
@@ -182,7 +182,7 @@ explicitly. --reflog:: Pretend as if all objects mentioned by reflogs are listed on the - command line as `<commit>`. + command line as `<object>`. --single-worktree:: By default, all working trees will be examined by the
@@ -205,9 +205,9 @@ ifndef::git-rev-list[] endif::git-rev-list[] --stdin:: - In addition to the '<commit>' listed on the command + In addition to the '<object>' listed on the command line, read them from the standard input. If a `--` separator is - seen, stop reading commits and start reading paths to limit the + seen, stop reading objects and start reading paths to limit the result. ifdef::git-rev-list[]
builtin/rev-list.c +1 -1
@@ -21,7 +21,7 @@#include "object-store.h" static const char rev_list_usage[] = -"git rev-list [OPTION] <commit-id>... [ -- paths... ]\n" +"git rev-list [OPTION] <object-id>... [ -- paths... ]\n" " limiting output:\n" " --max-count=<n>\n" " --max-age=<epoch>\n"
Signature · 1 line
2.19.1.331.ge82ca0e54c-goog
Re: [RFC PATCH 2/3] Documentation/git-rev-list: s/<commit>/<object>/
Quoted 8 lines · Matthew DeVore
Documentation/git-rev-list.txt +3
-4-List commits that are reachable by following the `parent` links from the -given commit(s), but exclude commits that are reachable from the one(s) -given with a '{caret}' in front of them. The output is given in reverse -chronological order by default. +List objects that are reachable by following references from the given +object(s), but exclude objects that are reachable from the one(s) given +with a '{caret}' in front of them.
Documentation/git-rev-list.txt SYNOPSIS
Quoted 10 lines
Documentation/git-rev-list.txt +7
-0+By default, only commit objects are shown, and the commits are shown in +reverse chronological order. The '--object' flag causes non-commit objects +to also be shown. +You can think of this as a set operation. Objects given on the command +line form a set of objects that are reachable from any of them, and then +objects reachable from any of the ones given with '{caret}' in front are +subtracted from that set. The remaining objects are what come out in the command's output. Various other options and paths parameters can be used to further limit the result.
I am not sure if this is a good rewrite. It gives a false impression as if you'd not see anything if I did this:
git rev-list --objects ^master md/filter-trees:t/valgrind
especially if you change the SYNOPSIS and make it claim that the command takes <object> as starting point. Updating <commit> to <object> for those that are _shown_ is OK, but the s/commit/object/ for those that are given as the starting points is not a good change at all, if I understand what the code is designed to do correctly.
It is more like "this is a set operation across commits. We also show objects that are reachable from the commits in the resulting set and are not reachable from the commits in the set that were excluded when --objects option is given".
Re: [RFC PATCH 2/3] Documentation/git-rev-list: s/<commit>/<object>/
Quoted 25 lines · Junio C Hamano
Quoted 8 lines · Matthew DeVore
Documentation/git-rev-list.txt +3
-4-List commits that are reachable by following the `parent` links from the -given commit(s), but exclude commits that are reachable from the one(s) -given with a '{caret}' in front of them. The output is given in reverse -chronological order by default. +List objects that are reachable by following references from the given +object(s), but exclude objects that are reachable from the one(s) given +with a '{caret}' in front of them.Documentation/git-rev-list.txt SYNOPSIS
Quoted 10 lines
Documentation/git-rev-list.txt +7
-0+By default, only commit objects are shown, and the commits are shown in +reverse chronological order. The '--object' flag causes non-commit objects +to also be shown. +You can think of this as a set operation. Objects given on the command +line form a set of objects that are reachable from any of them, and then +objects reachable from any of the ones given with '{caret}' in front are +subtracted from that set. The remaining objects are what come out in the command's output. Various other options and paths parameters can be used to further limit the result.I am not sure if this is a good rewrite. It gives a false impression as if you'd not see anything if I did this:
Quoted 2 lines
git rev-list --objects ^master md/filter-trees:t/valgrind
Oh that's interesting. So my mental model conflicts with the command's behavior. It actually is surprising behavior because:
# this shows all files that were modified in the HEAD commit
git rev-list --objects ^HEAD~1^{tree} HEAD:# but this shows *all* files at HEAD git rev-list --objects ^HEAD~1 HEAD:
Which means that ^commit and ^non-commit are treated inherently differently. Maybe I should fix this bug before clarifying this documentation...
Quoted 5 lines
It is more like "this is a set operation across commits. We also show objects that are reachable from the commits in the resulting set and are not reachable from the commits in the set that were excluded when --objects option is given".
That would be correct though it wouldn't tell that you can use "--objects ^foo-tree bar-tree." Without fixing the above bug, I could add to your wording something to the effect of "You can also use trees to include and exclude sets of objects rather than commits." Which implies that mixing "--objects ^commit tree" on the command line is undefined.
Re: [RFC PATCH 2/3] Documentation/git-rev-list: s/<commit>/<object>/
Quoted 7 lines · Matthew DeVore
Quoted 5 lines
It is more like "this is a set operation across commits. We also show objects that are reachable from the commits in the resulting set and are not reachable from the commits in the set that were excluded when --objects option is given".
That would be correct though it wouldn't tell that you can use "--objects ^foo-tree bar-tree."
Yeah, but quite honestly, I consider that it is working by accident, not by design, in the current code (iow, you are looking at a behaviour of whatever the code happens to do). "rev-list" is pretty much set operation across commits, and anything that deals with a non commit-ish given from the command line is an afterthought at best, and happenstance in reality.
I do not mean to say that the code must stay that way, though.
Re: [RFC PATCH 2/3] Documentation/git-rev-list: s/<commit>/<object>/
Quoted 11 lines · Junio C Hamano
Quoted 7 lines · Matthew DeVore
Quoted 5 lines
It is more like "this is a set operation across commits. We also show objects that are reachable from the commits in the resulting set and are not reachable from the commits in the set that were excluded when --objects option is given".
That would be correct though it wouldn't tell that you can use "--objects ^foo-tree bar-tree."
Quoted 8 lines
Yeah, but quite honestly, I consider that it is working by accident, not by design, in the current code (iow, you are looking at a behaviour of whatever the code happens to do). "rev-list" is pretty much set operation across commits, and anything that deals with a non commit-ish given from the command line is an afterthought at best, and happenstance in reality.
I do not mean to say that the code must stay that way, though.
I tried fixing the issue of "--objects ^commitobj treeobj" not properly excluding objects reachable from commitobj, but this ended up causing t5616-partial-clone.sh to fail. In the test labeled "manual prefetch of missing objects", we create a clone of srv.bare without blobs called "pc1", then push some new commits to srv.bare (via a separate "local" repo), and try to fetch missing blobs with this command:
$ git -C pc1 fetch-pack --stdin "file://$(pwd)/srv.bare" <observed.oids
Where observed.oids contains all the blobs that were missing. It tells the remote that it already has the "refs/heads/master" commit, which means it is excluded. Before, this worked fine, since it didn't mean the blobs were excluded, only the commit itself. With the fix I naively put together (and didn't share), because one of the blobs is reachable from "refs/heads/master" it wouldn't pull it. I guess I can change it so that items given directly to stdin or argv are always fetched, though I'm not sure how easy that will be, and I'm not as interested in fixing this as I once was. I just wanted the documentation to outline the object-enumeration capabilities. So I'll send a re-roll which makes much more modest changes to the documentation.
Re: [RFC PATCH 2/3] Documentation/git-rev-list: s/<commit>/<object>/
Quoted 6 lines · Matthew DeVore
$ git -C pc1 fetch-pack --stdin "file://$(pwd)/srv.bare" <observed.oidsWhere observed.oids contains all the blobs that were missing. It tells the remote that it already has the "refs/heads/master" commit, which means it is excluded. Before, this worked fine, since it didn't mean the blobs were excluded, only the commit itself.
So 'master' has some blob in its tree, which is missing from the repository, in this test? Which means that such a repository is "corrupt" and does not pass connectivity check by fsck.
I am of two minds. If we claim that we have everything that is needed to complete the commit sitting at the tip of 'master', I think it is correct for the other side not to send a blob that is in 'master' (or its ancestors), so your "fix" may (at least technically) be more correct than the status quo. On the other hand, if possession of commit 'master' does not defeat an explicit request for a blob in it, that would actually be a good thing---it would be a very straight-forward way to recover from such form of repository corruption.
Fetching isolated objects without walking is also something that would help backfill a lazily created clone, and I even vaguely recall an effort to allow objects explicitly requested to be always fetched regardless of the connectivity, if I am not mistaken (JTan?)
Anyway, thanks for a thoughtful response.
RFC PATCH 3/3 list-objects-filter: teach tree:# how to handle >0
In reply to Matthew DeVoreImplement positive values for <depth> in the tree:<depth> filter. The exact semantics are described in Documentation/rev-list-options.txt.
The long-term goal at the end of this is to allow a partial clone to eagerly fetch an entire directory of files by fetching a tree and specifying <depth>=1. This, for instance, would make a build operation fast and convenient. It is fast because the partial clone does not need to fetch each file individually, and convenient because the user does not need to supply a sparse-checkout specification.
--- Documentation/rev-list-options.txt | 8 ++- list-objects-filter-options.c | 6 +-- list-objects-filter-options.h | 1 + list-objects-filter.c | 49 +++++++++++++---- t/t6112-rev-list-filters-objects.sh | 84 +++++++++++++++++++++++++++++ 5 files changed, 132 insertions(+), 16 deletions(-)
Documentation/rev-list-options.txt +6 -2
@@ -734,8 +734,12 @@ specification contained in <path>. + The form '--filter=tree:<depth>' omits all blobs and trees whose depth from the root tree is >= <depth> (minimum depth if an object is located -at multiple depths in the commits traversed). Currently, only <depth>=0 -is supported, which omits all blobs and trees. +at multiple depths in the commits traversed). <depth>=0 will not include +any trees or blobs unless included explicitly in <object>. <depth>=1 +will include only the tree and blobs which are referenced directly by a +commit reachable from <object> or an object given in <object>. <depth>=2 +is like <depth>=1 while also including trees and blobs one more level +removed from <object> or a reachable commit. --no-filter:: Turn off any previous `--filter=` argument.
list-objects-filter-options.c +3 -3
@@ -50,12 +50,12 @@ static int gently_parse_list_objects_filter( } }else if (skip_prefix (arg,"tree:" , &v0)) { -unsigned long depth; -if (!git_parse_ulong (v0, &depth) || depth !=0 ) { +if (!git_parse_ulong (v0, + &filter_options->tree_depth_limit_value)) {if (errbuf) {strbuf_addstr ( errbuf, -_ ("only 'tree:0' is supported" )); +_ ("expected 'tree:<int>'" )); }return 1 ; }
list-objects-filter-options.h +1 -0
@@ -44,6 +44,7 @@ struct list_objects_filter_options {struct object_id *sparse_oid_value;char *sparse_path_value;unsigned long blob_limit_value; +unsigned long tree_depth_limit_value; };/* Normalized command line arguments */
list-objects-filter.c +38 -11
98 lines not on this page · raw message
t/t6112-rev-list-filters-objects.sh +84 -0
90 lines not on this page · raw message
Signature · 1 line
2.19.1.331.ge82ca0e54c-goog
Re: [RFC PATCH 3/3] list-objects-filter: teach tree:# how to handle >0
Quoted 4 lines · Matthew DeVore
The long-term goal at the end of this is to allow a partial clone to eagerly fetch an entire directory of files by fetching a tree and specifying <depth>=1. This, for instance, would make a build operation fast and convenient
This would reduce round-trip, as you do not have to fetch the tree to see what its contents are before issuing another set of fetches for them. Those who are building virtual filesystem that let you mount a specific tree object, perhaps via fuse, may find it useful, too, even though I suspect that may not be your primary focus.
Documentation/rev-list-options.txt specification contained in <path>. · line 738
Quoted 12 lines
Documentation/rev-list-options.txt +2
-2@@ -734,8 +734,12 @@ specification contained in <path>. + The form '--filter=tree:<depth>' omits all blobs and trees whose depth from the root tree is >= <depth> (minimum depth if an object is located -at multiple depths in the commits traversed). Currently, only <depth>=0 -is supported, which omits all blobs and trees. +at multiple depths in the commits traversed). <depth>=0 will not include +any trees or blobs unless included explicitly in <object>. <depth>=1
Here, <object> refers to the objects directly requested on the command line (or --stdin)? Triggering this question from me is a sign that this description may want to say a bit more to avoid the same question from the real readers. Perhaps replace "included explicitly in <object>" with "explicitly requested by listing them on the command line or feeding them with --stdin", or something like that?
Quoted 7 lines
list-objects-filter-options.c +0
-0@@ -50,12 +50,12 @@ static int gently_parse_list_objects_filter( }
list-objects-filter-options.c static int gently_parse_list_objects_filter( · line 58
Quoted 10 lines
list-objects-filter-options.c +3
-3}else if (skip_prefix (arg,"tree:" , &v0)) { -unsigned long depth; -if (!git_parse_ulong (v0, &depth) || depth !=0 ) { +if (!git_parse_ulong (v0, + &filter_options->tree_depth_limit_value)) {if (errbuf) {strbuf_addstr ( errbuf, -_ ("only 'tree:0' is supported" )); +_ ("expected 'tree:<int>'" ));
We do not accept "tree:-1", even though "-1" is an int. Is it too obvious to worry about? I do not think we want to say tree:<uint> even if we do want to make it clear we reject "tree:-1"
I am wondering if "expected 'tree:<depth>'" would work better.
list-objects-filter-options.h struct list_objects_filter_options { · line 48
Quoted 10 lines
list-objects-filter-options.h +1
-0@@ -44,6 +44,7 @@ struct list_objects_filter_options {struct object_id *sparse_oid_value;char *sparse_path_value;unsigned long blob_limit_value; +unsigned long tree_depth_limit_value; };
This change gets it right by adding "depth" in the field name and it is not a comment on this patch, but someday not in too distant future we should rename the "blob_limit_value" to make it clear that it is filtering by number of bytes, as other filtering criteria on blobs that can be expressed in ulong are quite possible.
list-objects-filter-options.h
Quoted 12 lines
list-objects-filter-options.h +1
-1-static enum list_objects_filter_result filter_trees_none( +static enum list_objects_filter_result filter_trees_depth( enum list_objects_filter_situation filter_situation,list-objects-filter-options.h +3
-1struct object *obj, const char *pathname, const char *filename, void *filter_data_) { - struct filter_trees_none_data *filter_data = filter_data_; + struct filter_trees_depth_data *filter_data = filter_data_; + +int too_deep = filter_data->current_depth >= filter_data->max_depth;
Does max mean "maximum allowed", or "this and anything larger are rejected". The latter sound wrong, but I offhand do not know if your current_depth counts from 0 or 1, so there may not be off-by-one.
- dir.c::within_depth() that is used by pathspec matching that in turn is used by "grep --max-depth=1" does "if (depth > max_depth)", which sounds more in line with the usual convention, I would think.
- pack-objects has max_delta_cache_size, which also is used as "maximum allowed", not "this is already too big". So is its max_depth.
There may be other examples. One existing violator I noticed was the "reject blobs that is this size or larger" in this file; it is called "max_bytes", but it is apparently not "maximum allowed", which we probably would want to fix.
list-objects-filter-options.h
Quoted 4 lines
list-objects-filter-options.h +4
-0+/* +* Note that we do not use _MARK_SEEN in order to allow re-traversal in +* case we encounter a tree or blob again at a shallower depth. +*/
Hmph. Earlier tree:0 support never even read the actual tree, so this was not a problem. We wouldn't have found a tree in deeper places first and then at a shallower depth, as we wouldn't have seen any tree in any depth deeper than the surface anyway.
Now we need to worry about a tree that originally gets seen in a deeper depth (that is still below the allowed maximum) to reappear at a shallower place, so a subtree within it that used to be too deep may now be within the allowed maximum depth.
Step 1 of these three patches made sure trees are not even opened under "tree:0", so it was not just optimizing/shrinking the output of rev-list but also optimizing the traversal. When we are collecting omits, however, this one now returns _ZERO which means we still traverse into the tree, even under "tree:0"? I must be reading the code incorrectly (in general, when we are seeing a tree object that itself is at the maximum allowed depth, trees found by reading its contents will never become eligible for output, even if they are found at a shallower depth than their other copies were previously found, I would think).
list-objects-filter-options.h
Quoted 7 lines
list-objects-filter-options.h +1
-1switch (filter_situation) {default :BUG ("unknown filter_situation: %d" , filter_situation); -case LOFS_BEGIN_TREE :case LOFS_BLOB : +if (!too_deep) goto include_it;
Style: on two lines, like you did below for the next if() statement.
Quoted 36 lines
list-objects-filter-options.h +19
-3+ +if (filter_data->omits) +oidset_insert (filter_data->omits, &obj->oid); + +return LOFR_ZERO ; + +case LOFS_BEGIN_TREE : + filter_data->current_depth++; + +if (!too_deep) goto include_it; +if (filter_data->omits) {oidset_insert (filter_data->omits, &obj->oid); -/* _MARK_SEEN but not _DO_SHOW (hard omit) */ -return LOFR_MARK_SEEN ; +return LOFR_ZERO ; }else /* * Not collecting omits so no need to to traverse tree. */ -return LOFR_SKIP_TREE |LOFR_MARK_SEEN ; +return LOFR_SKIP_TREE ;case LOFS_END_TREE :assert (obj->type ==OBJ_TREE ); + filter_data->current_depth--;return LOFR_ZERO ; } + +include_it: +if (filter_data->omits) +oidset_remove (filter_data->omits, &obj->oid); +return LOFR_MARK_SEEN |LOFR_DO_SHOW ; }
Re: [RFC PATCH 3/3] list-objects-filter: teach tree:# how to handle >0
A quick update: I've read Junio's comments on this patchset and basically agree with them, but I haven't had a chance to apply them yet. I plan to pick this patchset up again (as well as the patch "md/list-lazy-objects-fix") once things settle down in my day job.
Quoted 179 lines · Junio C Hamano
Quoted 4 lines · Matthew DeVore
The long-term goal at the end of this is to allow a partial clone to eagerly fetch an entire directory of files by fetching a tree and specifying <depth>=1. This, for instance, would make a build operation fast and convenient
This would reduce round-trip, as you do not have to fetch the tree to see what its contents are before issuing another set of fetches for them. Those who are building virtual filesystem that let you mount a specific tree object, perhaps via fuse, may find it useful, too, even though I suspect that may not be your primary focus.
Documentation/rev-list-options.txt specification contained in <path>. · line 738
Quoted 12 lines
Documentation/rev-list-options.txt +2
-2@@ -734,8 +734,12 @@ specification contained in <path>. + The form '--filter=tree:<depth>' omits all blobs and trees whose depth from the root tree is >= <depth> (minimum depth if an object is located -at multiple depths in the commits traversed). Currently, only <depth>=0 -is supported, which omits all blobs and trees. +at multiple depths in the commits traversed). <depth>=0 will not include +any trees or blobs unless included explicitly in <object>. <depth>=1Here, <object> refers to the objects directly requested on the command line (or --stdin)? Triggering this question from me is a sign that this description may want to say a bit more to avoid the same question from the real readers. Perhaps replace "included explicitly in <object>" with "explicitly requested by listing them on the command line or feeding them with
--stdin", or something like that?Quoted 7 lines
list-objects-filter-options.c +0
-0@@ -50,12 +50,12 @@ static int gently_parse_list_objects_filter( }list-objects-filter-options.c static int gently_parse_list_objects_filter( · line 58
Quoted 10 lines
list-objects-filter-options.c +3
-3}else if (skip_prefix (arg,"tree:" , &v0)) { -unsigned long depth; -if (!git_parse_ulong (v0, &depth) || depth !=0 ) { +if (!git_parse_ulong (v0, + &filter_options->tree_depth_limit_value)) {if (errbuf) {strbuf_addstr ( errbuf, -_ ("only 'tree:0' is supported" )); +_ ("expected 'tree:<int>'" ));We do not accept "tree:-1", even though "-1" is an int. Is it too obvious to worry about? I do not think we want to say tree:<uint> even if we do want to make it clear we reject "tree:-1"
I am wondering if "expected 'tree:<depth>'" would work better.
list-objects-filter-options.h struct list_objects_filter_options { · line 48
Quoted 10 lines
list-objects-filter-options.h +1
-0@@ -44,6 +44,7 @@ struct list_objects_filter_options {struct object_id *sparse_oid_value;char *sparse_path_value;unsigned long blob_limit_value; +unsigned long tree_depth_limit_value; };This change gets it right by adding "depth" in the field name and it is not a comment on this patch, but someday not in too distant future we should rename the "
blob_limit_value" to make it clear that it is filtering by number of bytes, as other filtering criteria on blobs that can be expressed in ulong are quite possible.list-objects-filter-options.h
Quoted 12 lines
list-objects-filter-options.h +1
-1-static enum list_objects_filter_result filter_trees_none( +static enum list_objects_filter_result filter_trees_depth( enum list_objects_filter_situation filter_situation,struct object *obj,
const char *pathname,
const char *filename,
void*filter_data_)
{list-objects-filter-options.h +3
-1-struct filter_trees_none_data *filter_data = filter_data_; +struct filter_trees_depth_data *filter_data = filter_data_; + +int too_deep = filter_data->current_depth >= filter_data->max_depth;Does max mean "maximum allowed", or "this and anything larger are rejected". The latter sound wrong, but I offhand do not know if your
current_depthcounts from 0 or 1, so there may not be off-by-one.-
dir.c::within_depth()that is used by pathspec matching that in turn is used by "grep --max-depth=1" does "if (depth >max_depth)", which sounds more in line with the usual convention, I would think.- pack-objects has
max_delta_cache_size, which also is used as "maximum allowed", not "this is already too big". So is itsmax_depth.There may be other examples. One existing violator I noticed was the "reject blobs that is this size or larger" in this file; it is called "
max_bytes", but it is apparently not "maximum allowed", which we probably would want to fix.list-objects-filter-options.h
Quoted 4 lines
list-objects-filter-options.h +4
-0+/* +* Note that we do not use _MARK_SEEN in order to allow re-traversal in +* case we encounter a tree or blob again at a shallower depth. +*/ Hmph. Earlier tree:0 support never even read the actual tree, so this was not a problem. We wouldn't have found a tree in deeper places first and then at a shallower depth, as we wouldn't have seen any tree in any depth deeper than the surface anyway.
Now we need to worry about a tree that originally gets seen in a deeper depth (that is still below the allowed maximum) to reappear at a shallower place, so a subtree within it that used to be too deep may now be within the allowed maximum depth.
Step 1 of these three patches made sure trees are not even opened under "tree:0", so it was not just optimizing/shrinking the output of rev-list but also optimizing the traversal. When we are collecting omits, however, this one now returns
_ZEROwhich means we still traverse into the tree, even under "tree:0"? I must be reading the code incorrectly (in general, when we are seeing a tree object that itself is at the maximum allowed depth, trees found by reading its contents will never become eligible for output, even if they are found at a shallower depth than their other copies were previously found, I would think).list-objects-filter-options.h
Quoted 7 lines
switch (filter_situation) { default: BUG("unknown filter_situation: %d", filter_situation);list-objects-filter-options.h +1
-1-case LOFS_BEGIN_TREE :case LOFS_BLOB : +if (!too_deep) goto include_it;Style: on two lines, like you did below for the next
if()statement.Quoted 36 lines
list-objects-filter-options.h +19
-3+ +if (filter_data->omits) +oidset_insert (filter_data->omits, &obj->oid); + +return LOFR_ZERO ; + +case LOFS_BEGIN_TREE : + filter_data->current_depth++; + +if (!too_deep) goto include_it; +if (filter_data->omits) {oidset_insert (filter_data->omits, &obj->oid); -/* _MARK_SEEN but not _DO_SHOW (hard omit) */ -return LOFR_MARK_SEEN ; +return LOFR_ZERO ; }else /* * Not collecting omits so no need to to traverse tree. */ -return LOFR_SKIP_TREE |LOFR_MARK_SEEN ; +return LOFR_SKIP_TREE ;case LOFS_END_TREE :assert (obj->type ==OBJ_TREE ); + filter_data->current_depth--;return LOFR_ZERO ; } + +include_it: +if (filter_data->omits) +oidset_remove (filter_data->omits, &obj->oid); +return LOFR_MARK_SEEN |LOFR_DO_SHOW ; }
Re: [RFC PATCH 3/3] list-objects-filter: teach tree:# how to handle >0
In reply to Junio C HamanoQuoted 8 lines · Junio C Hamano
Quoted 4 lines · Matthew DeVore
The long-term goal at the end of this is to allow a partial clone to eagerly fetch an entire directory of files by fetching a tree and specifying <depth>=1. This, for instance, would make a build operation fast and convenient
Quoted 5 lines
This would reduce round-trip, as you do not have to fetch the tree to see what its contents are before issuing another set of fetches for them. Those who are building virtual filesystem that let you mount a specific tree object, perhaps via fuse, may find it useful, too, even though I suspect that may not be your primary focus.
I added a paragraph mentioning the "roundtrip" aspect to the commit message, since this *may* help someone find a use for it.
Quoted 14 lines
Documentation/rev-list-options.txt specification contained in <path>. · line 738
Quoted 13 lines
Documentation/rev-list-options.txt +2
-2@@ -734,8 +734,12 @@ specification contained in <path>. + The form '--filter=tree:<depth>' omits all blobs and trees whose depth from the root tree is >= <depth> (minimum depth if an object is located -at multiple depths in the commits traversed). Currently, only <depth>=0 -is supported, which omits all blobs and trees. +at multiple depths in the commits traversed). <depth>=0 will not include +any trees or blobs unless included explicitly in <object>. <depth>=1
Quoted 7 lines
Here, <object> refers to the objects directly requested on the command line (or --stdin)? Triggering this question from me is a sign that this description may want to say a bit more to avoid the same question from the real readers. Perhaps replace "included explicitly in <object>" with "explicitly requested by listing them on the command line or feeding them with
--stdin", or something like that?
I have reworded this to be more explicit about "stdin," and also to avoid directly admitting that trees are allowed in the <commit> argument, since I've dropped the s/<commit>/<object>/ patch.
Here is the new paragraph:
The form '--filter=tree:<depth>' omits all blobs and trees whose depth from the root tree is >= <depth> (minimum depth if an object is located at multiple depths in the commits traversed). <depth>=0 will not include any trees or blobs unless included explicitly in the command-line (or standard input when --stdin is used). <depth>=1 will include only the tree and blobs which are referenced directly by a commit reachable from <commit> or an explicitly-given object. <depth>=2 is like <depth>=1 while also including trees and blobs one more level removed from an explicitly-given commit or tree.
Quoted 7 lines
Documentation/rev-list-options.txt specification contained in <path>. · line 739
Quoted 1 line
Documentation/rev-list-options.txt +1
-0+ _("expected 'tree:<int>'"));We do not accept "tree:-1", even though "-1" is an int. Is it too obvious to worry about? I do not think we want to say tree:<uint> even if we do want to make it clear we reject "tree:-1"
I am wondering if "expected 'tree:<depth>'" would work better.
Yes, I agree, <depth> is more helpful and not ambiguous in a significant way. Fixed.
Quoted 8 lines
Documentation/rev-list-options.txt specification contained in <path>. · line 741
Quoted 2 lines
Documentation/rev-list-options.txt +1
-0+ unsigned long tree_depth_limit_value; };This change gets it right by adding "depth" in the field name and it is not a comment on this patch, but someday not in too distant future we should rename the "
blob_limit_value" to make it clear that it is filtering by number of bytes, as other filtering criteria on blobs that can be expressed in ulong are quite possible.
Agreed.
Quoted 10 lines
Quoted 10 lines
Documentation/rev-list-options.txt +1
-2-static enum list_objects_filter_result filter_trees_none( +static enum list_objects_filter_result filter_trees_depth( enum list_objects_filter_situation filter_situation, struct object *obj, const char *pathname, const char *filename, void *filter_data_) { - struct filter_trees_none_data *filter_data = filter_data_;
Quoted 9 lines
Documentation/rev-list-options.txt
Quoted 3 lines
Documentation/rev-list-options.txt +3
-0+ struct filter_trees_depth_data *filter_data = filter_data_; + + int too_deep = filter_data->current_depth >= filter_data->max_depth;Does max mean "maximum allowed", or "this and anything larger are rejected". The latter sound wrong, but I offhand do not know if your
current_depthcounts from 0 or 1, so there may not be off-by-one.
It means "this and anything larger are rejected." The documentation words it similarly:
"
The form '--filter=tree:<depth>' omits all blobs and trees whose depth from the root tree is >= <depth> ...
"
There is no intuitive phrase to mean "distance from root tree minus one" (left operand) and I don't want to change the filter option field to something different from what the user entered (right operand), so I think we'd best use ">=" and I've renamed the field to "exclusion_trigger_depth".
Quoted 5 lines
There may be other examples. One existing violator I noticed was the "reject blobs that is this size or larger" in this file; it is called "
max_bytes", but it is apparently not "maximum allowed", which we probably would want to fix.
That's not ideal. The documentation suggests it means "maximum allowed," and JGit apparently is interpreting the value as "maximum allowed," so we should probably fix the semantics in Git. Or were you suggesting to keep the behavior the same but fix the naming and docs?
Quoted 10 lines
Documentation/rev-list-options.txt
Quoted 4 lines
Documentation/rev-list-options.txt +4
-0+ /* + * Note that we do not use _MARK_SEEN in order to allow re-traversal in + * case we encounter a tree or blob again at a shallower depth. + */Hmph. Earlier tree:0 support never even read the actual tree, so this was not a problem. We wouldn't have found a tree in deeper places first and then at a shallower depth, as we wouldn't have seen any tree in any depth deeper than the surface anyway.
Quoted 4 lines
Now we need to worry about a tree that originally gets seen in a deeper depth (that is still below the allowed maximum) to reappear at a shallower place, so a subtree within it that used to be too deep may now be within the allowed maximum depth.
That's true, and it points out the fact that we can't use LOFR_MARK_SEEN even if we do LOFR_DO_SHOW, since subtrees may or may not be within the limit in the future. I've fixed that for v+1 of the patch, and added a corresponding test. I'm also refactoring the filters_tree_depth function to not use goto. I think it is easier to follow that way.
Quoted 11 lines
Step 1 of these three patches made sure trees are not even opened under "tree:0", so it was not just optimizing/shrinking the output of rev-list but also optimizing the traversal. When we are collecting omits, however, this one now returns
_ZEROwhich means we still traverse into the tree, even under "tree:0"? I must be reading the code incorrectly (in general, when we are seeing a tree object that itself is at the maximum allowed depth, trees found by reading its contents will never become eligible for output, even if they are found at a shallower depth than their other copies were previously found, I would think).
You are reading the code right - when the tree is too deep, we sometimes return LOFR_ZERO, sometimes _SKIP_TREE - the latter if we are collecting omits. But as you observe, we don't need to traverse an omitted tree *if it has already been marked as omitted*, since that suggests that its children have also been marked as omitted. So I've added a new commit to this patchset which optimizes this case. The next patchset will have 2 commits (subtracting the documentation rewrite one and ones that are already scheduled to merge):
Patch 1/2 - implement tree:<depth> for positive depths Patch 2/2 - stop iterating through trees for collecting omits if the omits are already marked