]> git.ipfire.org Git - thirdparty/git.git/commitdiff
list-objects: implement object type filter
authorPatrick Steinhardt <ps@pks.im>
Mon, 19 Apr 2021 11:46:53 +0000 (13:46 +0200)
committerJunio C Hamano <gitster@pobox.com>
Mon, 19 Apr 2021 21:09:11 +0000 (14:09 -0700)
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 <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/config/uploadpack.txt
Documentation/rev-list-options.txt
list-objects-filter-options.c
list-objects-filter-options.h
list-objects-filter.c
t/t6112-rev-list-filters-objects.sh

index 6729a072ea9e7713f0763000cf971ec1888d2b0c..32fad5bbe817f2caa6201f0e496a06f4b055144a 100644 (file)
@@ -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
index b1c8f86c6efc128514533ef21b3e0272b8680838..3afa8fffbdf0900223c26dd3d61c66ba5acbc717 100644 (file)
@@ -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
index d2d1c81caf33fdc12eea412dd428bfe80ee36103..96a605c8adea3b29d38988274fb68faa8b266080 100644 (file)
@@ -29,6 +29,8 @@ const char *list_object_filter_config_name(enum list_objects_filter_choice c)
                return "tree";
        case LOFC_SPARSE_OID:
                return "sparse:oid";
+       case LOFC_OBJECT_TYPE:
+               return "object:type";
        case LOFC_COMBINE:
                return "combine";
        case LOFC__COUNT:
@@ -97,6 +99,19 @@ static int gently_parse_list_objects_filter(
                }
                return 1;
 
+       } else if (skip_prefix(arg, "object:type=", &v0)) {
+               int type = 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);
+                       return 1;
+               }
+
+               filter_options->object_type = type;
+               filter_options->choice = LOFC_OBJECT_TYPE;
+
+               return 0;
+
        } else if (skip_prefix(arg, "combine:", &v0)) {
                return parse_combine_filter(filter_options, v0, errbuf);
 
index 01767c3c968f33440aac3095a25d8cc08bec3659..da5b6737e27e36a6e198a59a070cd30f17179ceb 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef LIST_OBJECTS_FILTER_OPTIONS_H
 #define LIST_OBJECTS_FILTER_OPTIONS_H
 
+#include "cache.h"
 #include "parse-options.h"
 #include "string-list.h"
 
@@ -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 */
 };
@@ -54,6 +56,7 @@ struct list_objects_filter_options {
        char *sparse_oid_name;
        unsigned long blob_limit_value;
        unsigned long tree_exclude_depth;
+       enum object_type object_type;
 
        /* LOFC_COMBINE values */
 
index 0ebfa5296625a32258a91670877e1c72509047fe..1c1ee3d1bb18d1737bd9aded0472b899b1864459 100644 (file)
@@ -545,6 +545,81 @@ static void filter_sparse_oid__init(
        filter->free_fn = filter_sparse_free;
 }
 
+/*
+ * A filter for list-objects to omit large blobs.
+ * And to OPTIONALLY collect a list of the omitted OIDs.
+ */
+struct filter_object_type_data {
+       enum object_type object_type;
+};
+
+static enum list_objects_filter_result filter_object_type(
+       struct repository *r,
+       enum list_objects_filter_situation filter_situation,
+       struct object *obj,
+       const char *pathname,
+       const char *filename,
+       struct oidset *omits,
+       void *filter_data_)
+{
+       struct filter_object_type_data *filter_data = filter_data_;
+
+       switch (filter_situation) {
+       default:
+               BUG("unknown filter_situation: %d", filter_situation);
+
+       case LOFS_TAG:
+               assert(obj->type == OBJ_TAG);
+               if (filter_data->object_type == OBJ_TAG)
+                       return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+               return LOFR_MARK_SEEN;
+
+       case LOFS_COMMIT:
+               assert(obj->type == OBJ_COMMIT);
+               if (filter_data->object_type == OBJ_COMMIT)
+                       return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+               return LOFR_MARK_SEEN;
+
+       case LOFS_BEGIN_TREE:
+               assert(obj->type == OBJ_TREE);
+
+               /*
+                * If we only want to show commits or tags, then there is no
+                * need to walk down trees.
+                */
+               if (filter_data->object_type == OBJ_COMMIT ||
+                   filter_data->object_type == OBJ_TAG)
+                       return LOFR_SKIP_TREE;
+
+               if (filter_data->object_type == OBJ_TREE)
+                       return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+
+               return LOFR_MARK_SEEN;
+
+       case LOFS_BLOB:
+               assert(obj->type == OBJ_BLOB);
+
+               if (filter_data->object_type == OBJ_BLOB)
+                       return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+               return LOFR_MARK_SEEN;
+
+       case LOFS_END_TREE:
+               return LOFR_ZERO;
+       }
+}
+
+static void filter_object_type__init(
+       struct list_objects_filter_options *filter_options,
+       struct filter *filter)
+{
+       struct filter_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. */
 struct combine_filter_data {
        struct subfilter *sub;
@@ -691,6 +766,7 @@ static filter_init_fn s_filters[] = {
        filter_blobs_limit__init,
        filter_trees_depth__init,
        filter_sparse_oid__init,
+       filter_object_type__init,
        filter_combine__init,
 };
 
index 31457d13b9a0af27fbf0c08402d395be8a540c65..c810a6280741441d0a7b88c18081aed0f199ffa4 100755 (executable)
@@ -159,6 +159,50 @@ test_expect_success 'verify blob:limit=1m' '
        test_must_be_empty observed
 '
 
+# Test object:type=<type> filter.
+
+test_expect_success 'setup object-type' '
+       test_create_repo object-type &&
+       test_commit --no-tag -C object-type message blob &&
+       git -C object-type tag tag -m tag-message
+'
+
+test_expect_success 'verify object:type= fails with invalid type' '
+       test_must_fail git -C object-type rev-list --objects --filter=object:type= HEAD &&
+       test_must_fail git -C object-type rev-list --objects --filter=object:type=invalid HEAD
+'
+
+test_expect_success 'verify object:type=blob prints blob and commit' '
+       git -C object-type rev-parse HEAD >expected &&
+       printf "%s blob\n" $(git -C object-type rev-parse HEAD:blob) >>expected &&
+       git -C object-type rev-list --objects --filter=object:type=blob HEAD >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'verify object:type=tree prints tree and commit' '
+       (
+               git -C object-type rev-parse HEAD &&
+               printf "%s \n" $(git -C object-type rev-parse HEAD^{tree})
+       ) >expected &&
+       git -C object-type rev-list --objects --filter=object:type=tree HEAD >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'verify object:type=commit prints commit' '
+       git -C object-type rev-parse HEAD >expected &&
+       git -C object-type rev-list --objects --filter=object:type=commit HEAD >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'verify object:type=tag prints tag' '
+       (
+               git -C object-type rev-parse HEAD &&
+               printf "%s tag\n" $(git -C object-type rev-parse tag)
+       ) >expected &&
+       git -C object-type rev-list --objects --filter=object:type=tag tag >actual &&
+       test_cmp expected actual
+'
+
 # Test sparse:path=<path> filter.
 # !!!!
 # NOTE: sparse:path filter support has been dropped for security reasons,