list-objects-filter: implement composite filters
[gitweb.git] / list-objects-filter-options.c
index 7c3e397d299724cdb33745237c82944b121d1492..75d0236ee2da9feead511042085e3ae3f9f7c8a1 100644 (file)
@@ -6,6 +6,12 @@
 #include "list-objects.h"
 #include "list-objects-filter.h"
 #include "list-objects-filter-options.h"
+#include "url.h"
+
+static int parse_combine_filter(
+       struct list_objects_filter_options *filter_options,
+       const char *arg,
+       struct strbuf *errbuf);
 
 /*
  * Parse value of the argument to the "filter" keyword.
@@ -35,8 +41,6 @@ static int gently_parse_list_objects_filter(
                return 1;
        }
 
-       filter_options->filter_spec = strdup(arg);
-
        if (!strcmp(arg, "blob:none")) {
                filter_options->choice = LOFC_BLOB_NONE;
                return 0;
@@ -77,6 +81,10 @@ static int gently_parse_list_objects_filter(
                                _("sparse:path filters support has been dropped"));
                }
                return 1;
+
+       } else if (skip_prefix(arg, "combine:", &v0)) {
+               return parse_combine_filter(filter_options, v0, errbuf);
+
        }
        /*
         * Please update _git_fetch() in git-completion.bash when you
@@ -89,10 +97,95 @@ static int gently_parse_list_objects_filter(
        return 1;
 }
 
+static const char *RESERVED_NON_WS = "~`!@#$^&*()[]{}\\;'\",<>?";
+
+static int has_reserved_character(
+       struct strbuf *sub_spec, struct strbuf *errbuf)
+{
+       const char *c = sub_spec->buf;
+       while (*c) {
+               if (*c <= ' ' || strchr(RESERVED_NON_WS, *c)) {
+                       strbuf_addf(
+                               errbuf,
+                               _("must escape char in sub-filter-spec: '%c'"),
+                               *c);
+                       return 1;
+               }
+               c++;
+       }
+
+       return 0;
+}
+
+static int parse_combine_subfilter(
+       struct list_objects_filter_options *filter_options,
+       struct strbuf *subspec,
+       struct strbuf *errbuf)
+{
+       size_t new_index = filter_options->sub_nr++;
+       char *decoded;
+       int result;
+
+       ALLOC_GROW(filter_options->sub, filter_options->sub_nr,
+                  filter_options->sub_alloc);
+       memset(&filter_options->sub[new_index], 0,
+              sizeof(*filter_options->sub));
+
+       decoded = url_percent_decode(subspec->buf);
+
+       result = has_reserved_character(subspec, errbuf) ||
+               gently_parse_list_objects_filter(
+                       &filter_options->sub[new_index], decoded, errbuf);
+
+       free(decoded);
+       return result;
+}
+
+static int parse_combine_filter(
+       struct list_objects_filter_options *filter_options,
+       const char *arg,
+       struct strbuf *errbuf)
+{
+       struct strbuf **subspecs = strbuf_split_str(arg, '+', 0);
+       size_t sub;
+       int result = 0;
+
+       if (!subspecs[0]) {
+               strbuf_addstr(errbuf, _("expected something after combine:"));
+               result = 1;
+               goto cleanup;
+       }
+
+       for (sub = 0; subspecs[sub] && !result; sub++) {
+               if (subspecs[sub + 1]) {
+                       /*
+                        * This is not the last subspec. Remove trailing "+" so
+                        * we can parse it.
+                        */
+                       size_t last = subspecs[sub]->len - 1;
+                       assert(subspecs[sub]->buf[last] == '+');
+                       strbuf_remove(subspecs[sub], last, 1);
+               }
+               result = parse_combine_subfilter(
+                       filter_options, subspecs[sub], errbuf);
+       }
+
+       filter_options->choice = LOFC_COMBINE;
+
+cleanup:
+       strbuf_list_free(subspecs);
+       if (result) {
+               list_objects_filter_release(filter_options);
+               memset(filter_options, 0, sizeof(*filter_options));
+       }
+       return result;
+}
+
 int parse_list_objects_filter(struct list_objects_filter_options *filter_options,
                              const char *arg)
 {
        struct strbuf buf = STRBUF_INIT;
+       filter_options->filter_spec = strdup(arg);
        if (gently_parse_list_objects_filter(filter_options, arg, &buf))
                die("%s", buf.buf);
        return 0;
@@ -129,8 +222,15 @@ void expand_list_objects_filter_spec(
 void list_objects_filter_release(
        struct list_objects_filter_options *filter_options)
 {
+       size_t sub;
+
+       if (!filter_options)
+               return;
        free(filter_options->filter_spec);
        free(filter_options->sparse_oid_value);
+       for (sub = 0; sub < filter_options->sub_nr; sub++)
+               list_objects_filter_release(&filter_options->sub[sub]);
+       free(filter_options->sub);
        memset(filter_options, 0, sizeof(*filter_options));
 }
 
@@ -174,6 +274,8 @@ void partial_clone_get_default_filter_spec(
         */
        if (!core_partial_clone_filter_default)
                return;
+
+       filter_options->filter_spec = strdup(core_partial_clone_filter_default);
        gently_parse_list_objects_filter(filter_options,
                                         core_partial_clone_filter_default,
                                         &errbuf);