summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar Junio C Hamano <gitster@pobox.com>2019-09-18 11:50:09 -0700
committerLibravatar Junio C Hamano <gitster@pobox.com>2019-09-18 11:50:09 -0700
commit627b82683447e299fc2e20140318c276efbf7de2 (patch)
treefd0da2b151024ebe5a71db00e75ec8a41eff9fd6
parentMerge branch 'cc/multi-promisor' (diff)
parentlist-objects-filter-options: make parser void (diff)
downloadtgif-627b82683447e299fc2e20140318c276efbf7de2.tar.xz
Merge branch 'md/list-objects-filter-combo'
The list-objects-filter API (used to create a sparse/lazy clone) learned to take a combined filter specification. * md/list-objects-filter-combo: list-objects-filter-options: make parser void list-objects-filter-options: clean up use of ALLOC_GROW list-objects-filter-options: allow mult. --filter strbuf: give URL-encoding API a char predicate fn list-objects-filter-options: make filter_spec a string_list list-objects-filter-options: move error check up list-objects-filter: implement composite filters list-objects-filter-options: always supply *errbuf list-objects-filter: put omits set in filter struct list-objects-filter: encapsulate filter components
-rw-r--r--Documentation/rev-list-options.txt16
-rw-r--r--builtin/clone.c8
-rw-r--r--builtin/fetch.c9
-rw-r--r--builtin/rev-list.c6
-rw-r--r--cache.h22
-rw-r--r--credential-store.c9
-rw-r--r--fetch-pack.c20
-rw-r--r--http.c6
-rw-r--r--list-objects-filter-options.c275
-rw-r--r--list-objects-filter-options.h57
-rw-r--r--list-objects-filter.c335
-rw-r--r--list-objects-filter.h40
-rw-r--r--list-objects.c55
-rw-r--r--strbuf.c15
-rw-r--r--strbuf.h7
-rwxr-xr-xt/t5616-partial-clone.sh19
-rwxr-xr-xt/t6112-rev-list-filters-objects.sh194
-rw-r--r--transport-helper.c10
-rw-r--r--transport.c1
-rw-r--r--upload-pack.c13
-rw-r--r--url.c6
-rw-r--r--url.h8
22 files changed, 889 insertions, 242 deletions
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index bb1251c036..90ff9e2bea 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -756,6 +756,22 @@ explicitly-given commit or tree.
Note that the form '--filter=sparse:path=<path>' that wants to read
from an arbitrary path on the filesystem has been dropped for security
reasons.
++
+Multiple '--filter=' flags can be specified to combine filters. Only
+objects which are accepted by every filter are included.
++
+The form '--filter=combine:<filter1>+<filter2>+...<filterN>' can also be
+used to combined several filters, but this is harder than just repeating
+the '--filter' flag and is usually not necessary. Filters are joined by
+'{plus}' and individual filters are %-encoded (i.e. URL-encoded).
+Besides the '{plus}' and '%' characters, the following characters are
+reserved and also must be encoded: `~!@#$^&*()[]{}\;",<>?`+&#39;&#96;+
+as well as all characters with ASCII code &lt;= `0x20`, which includes
+space and newline.
++
+Other arbitrary characters can also be encoded. For instance,
+'combine:tree:3+blob:none' and 'combine:tree%3A3+blob%3Anone' are
+equivalent.
--no-filter::
Turn off any previous `--filter=` argument.
diff --git a/builtin/clone.c b/builtin/clone.c
index f665b28ccc..2048b6760a 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1160,13 +1160,11 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
transport->server_options = &server_options;
if (filter_options.choice) {
- struct strbuf expanded_filter_spec = STRBUF_INIT;
- expand_list_objects_filter_spec(&filter_options,
- &expanded_filter_spec);
+ const char *spec =
+ expand_list_objects_filter_spec(&filter_options);
transport_set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER,
- expanded_filter_spec.buf);
+ spec);
transport_set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
- strbuf_release(&expanded_filter_spec);
}
if (transport->smart_options && !deepen && !filter_options.choice)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 538f0e7207..9b27ae9681 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1243,13 +1243,10 @@ static struct transport *prepare_transport(struct remote *remote, int deepen)
if (update_shallow)
set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
if (filter_options.choice) {
- struct strbuf expanded_filter_spec = STRBUF_INIT;
- expand_list_objects_filter_spec(&filter_options,
- &expanded_filter_spec);
- set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER,
- expanded_filter_spec.buf);
+ const char *spec =
+ expand_list_objects_filter_spec(&filter_options);
+ set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, spec);
set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
- strbuf_release(&expanded_filter_spec);
}
if (negotiation_tip.nr) {
if (transport->smart_options)
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 301ccb970b..b8dc2e1fba 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -473,8 +473,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
die(_("object filtering requires --objects"));
if (filter_options.choice == LOFC_SPARSE_OID &&
!filter_options.sparse_oid_value)
- die(_("invalid sparse value '%s'"),
- filter_options.filter_spec);
+ die(
+ _("invalid sparse value '%s'"),
+ list_objects_filter_spec(
+ &filter_options));
continue;
}
if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) {
diff --git a/cache.h b/cache.h
index 3cbad5b603..5624e6c02d 100644
--- a/cache.h
+++ b/cache.h
@@ -636,6 +636,9 @@ int daemonize(void);
* at least 'nr' entries; the number of entries currently allocated
* is 'alloc', using the standard growing factor alloc_nr() macro.
*
+ * Consider using ALLOC_GROW_BY instead of ALLOC_GROW as it has some
+ * added niceties.
+ *
* DO NOT USE any expression with side-effect for 'x', 'nr', or 'alloc'.
*/
#define ALLOC_GROW(x, nr, alloc) \
@@ -649,6 +652,25 @@ int daemonize(void);
} \
} while (0)
+/*
+ * Similar to ALLOC_GROW but handles updating of the nr value and
+ * zeroing the bytes of the newly-grown array elements.
+ *
+ * DO NOT USE any expression with side-effect for any of the
+ * arguments.
+ */
+#define ALLOC_GROW_BY(x, nr, increase, alloc) \
+ do { \
+ if (increase) { \
+ size_t new_nr = nr + (increase); \
+ if (new_nr < nr) \
+ BUG("negative growth in ALLOC_GROW_BY"); \
+ ALLOC_GROW(x, new_nr, alloc); \
+ memset((x) + nr, 0, sizeof(*(x)) * (increase)); \
+ nr = new_nr; \
+ } \
+ } while (0)
+
/* Initialize and use the cache information */
struct lock_file;
void preload_index(struct index_state *index,
diff --git a/credential-store.c b/credential-store.c
index ac295420dd..c010497cb2 100644
--- a/credential-store.c
+++ b/credential-store.c
@@ -72,15 +72,16 @@ static void store_credential_file(const char *fn, struct credential *c)
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf, "%s://", c->protocol);
- strbuf_addstr_urlencode(&buf, c->username, 1);
+ strbuf_addstr_urlencode(&buf, c->username, is_rfc3986_unreserved);
strbuf_addch(&buf, ':');
- strbuf_addstr_urlencode(&buf, c->password, 1);
+ strbuf_addstr_urlencode(&buf, c->password, is_rfc3986_unreserved);
strbuf_addch(&buf, '@');
if (c->host)
- strbuf_addstr_urlencode(&buf, c->host, 1);
+ strbuf_addstr_urlencode(&buf, c->host, is_rfc3986_unreserved);
if (c->path) {
strbuf_addch(&buf, '/');
- strbuf_addstr_urlencode(&buf, c->path, 0);
+ strbuf_addstr_urlencode(&buf, c->path,
+ is_rfc3986_reserved_or_unreserved);
}
rewrite_credential_file(fn, c, &buf);
diff --git a/fetch-pack.c b/fetch-pack.c
index d81f47c07b..6ccc6294ea 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -338,12 +338,9 @@ static int find_common(struct fetch_negotiator *negotiator,
}
}
if (server_supports_filtering && args->filter_options.choice) {
- struct strbuf expanded_filter_spec = STRBUF_INIT;
- expand_list_objects_filter_spec(&args->filter_options,
- &expanded_filter_spec);
- packet_buf_write(&req_buf, "filter %s",
- expanded_filter_spec.buf);
- strbuf_release(&expanded_filter_spec);
+ const char *spec =
+ expand_list_objects_filter_spec(&args->filter_options);
+ packet_buf_write(&req_buf, "filter %s", spec);
}
packet_buf_flush(&req_buf);
state_len = req_buf.len;
@@ -1112,7 +1109,7 @@ static int add_haves(struct fetch_negotiator *negotiator,
}
static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
- const struct fetch_pack_args *args,
+ struct fetch_pack_args *args,
const struct ref *wants, struct oidset *common,
int *haves_to_send, int *in_vain,
int sideband_all)
@@ -1153,13 +1150,10 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
/* Add filter */
if (server_supports_feature("fetch", "filter", 0) &&
args->filter_options.choice) {
- struct strbuf expanded_filter_spec = STRBUF_INIT;
+ const char *spec =
+ expand_list_objects_filter_spec(&args->filter_options);
print_verbose(args, _("Server supports filter"));
- expand_list_objects_filter_spec(&args->filter_options,
- &expanded_filter_spec);
- packet_buf_write(&req_buf, "filter %s",
- expanded_filter_spec.buf);
- strbuf_release(&expanded_filter_spec);
+ packet_buf_write(&req_buf, "filter %s", spec);
} else if (args->filter_options.choice) {
warning("filtering not recognized by server, ignoring");
}
diff --git a/http.c b/http.c
index 27aa0a3192..938b9e55af 100644
--- a/http.c
+++ b/http.c
@@ -513,9 +513,11 @@ static void set_proxyauth_name_password(CURL *result)
#else
struct strbuf s = STRBUF_INIT;
- strbuf_addstr_urlencode(&s, proxy_auth.username, 1);
+ strbuf_addstr_urlencode(&s, proxy_auth.username,
+ is_rfc3986_unreserved);
strbuf_addch(&s, ':');
- strbuf_addstr_urlencode(&s, proxy_auth.password, 1);
+ strbuf_addstr_urlencode(&s, proxy_auth.password,
+ is_rfc3986_unreserved);
curl_proxyuserpwd = strbuf_detach(&s, NULL);
curl_easy_setopt(result, CURLOPT_PROXYUSERPWD, curl_proxyuserpwd);
#endif
diff --git a/list-objects-filter-options.c b/list-objects-filter-options.c
index 28c571f922..4d88bfe64a 100644
--- a/list-objects-filter-options.c
+++ b/list-objects-filter-options.c
@@ -7,6 +7,13 @@
#include "list-objects-filter.h"
#include "list-objects-filter-options.h"
#include "promisor-remote.h"
+#include "trace.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.
@@ -33,16 +40,8 @@ static int gently_parse_list_objects_filter(
if (!arg)
return 0;
- if (filter_options->choice) {
- if (errbuf) {
- strbuf_addstr(
- errbuf,
- _("multiple filter-specs cannot be combined"));
- }
- return 1;
- }
-
- filter_options->filter_spec = strdup(arg);
+ if (filter_options->choice)
+ BUG("filter_options already populated");
if (!strcmp(arg, "blob:none")) {
filter_options->choice = LOFC_BLOB_NONE;
@@ -56,11 +55,7 @@ static int gently_parse_list_objects_filter(
} else if (skip_prefix(arg, "tree:", &v0)) {
if (!git_parse_ulong(v0, &filter_options->tree_exclude_depth)) {
- if (errbuf) {
- strbuf_addstr(
- errbuf,
- _("expected 'tree:<depth>'"));
- }
+ strbuf_addstr(errbuf, _("expected 'tree:<depth>'"));
return 1;
}
filter_options->choice = LOFC_TREE_DEPTH;
@@ -88,67 +83,253 @@ 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
* add new filters
*/
- if (errbuf)
- strbuf_addf(errbuf, _("invalid filter-spec '%s'"), arg);
+ strbuf_addf(errbuf, _("invalid filter-spec '%s'"), arg);
memset(filter_options, 0, sizeof(*filter_options));
return 1;
}
-int parse_list_objects_filter(struct list_objects_filter_options *filter_options,
- const char *arg)
+static const char *RESERVED_NON_WS = "~`!@#$^&*()[]{}\\;'\",<>?";
+
+static int has_reserved_character(
+ struct strbuf *sub_spec, struct strbuf *errbuf)
{
- struct strbuf buf = STRBUF_INIT;
- if (gently_parse_list_objects_filter(filter_options, arg, &buf))
- die("%s", buf.buf);
+ 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_BY(filter_options->sub, filter_options->sub_nr, 1,
+ filter_options->sub_alloc);
+
+ 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;
+}
+
+static int allow_unencoded(char ch)
+{
+ if (ch <= ' ' || ch == '%' || ch == '+')
+ return 0;
+ return !strchr(RESERVED_NON_WS, ch);
+}
+
+static void filter_spec_append_urlencode(
+ struct list_objects_filter_options *filter, const char *raw)
+{
+ struct strbuf buf = STRBUF_INIT;
+ strbuf_addstr_urlencode(&buf, raw, allow_unencoded);
+ trace_printf("Add to combine filter-spec: %s\n", buf.buf);
+ string_list_append(&filter->filter_spec, strbuf_detach(&buf, NULL));
+}
+
+/*
+ * Changes filter_options into an equivalent LOFC_COMBINE filter options
+ * instance. Does not do anything if filter_options is already LOFC_COMBINE.
+ */
+static void transform_to_combine_type(
+ struct list_objects_filter_options *filter_options)
+{
+ assert(filter_options->choice);
+ if (filter_options->choice == LOFC_COMBINE)
+ return;
+ {
+ const int initial_sub_alloc = 2;
+ struct list_objects_filter_options *sub_array =
+ xcalloc(initial_sub_alloc, sizeof(*sub_array));
+ sub_array[0] = *filter_options;
+ memset(filter_options, 0, sizeof(*filter_options));
+ filter_options->sub = sub_array;
+ filter_options->sub_alloc = initial_sub_alloc;
+ }
+ filter_options->sub_nr = 1;
+ filter_options->choice = LOFC_COMBINE;
+ string_list_append(&filter_options->filter_spec, xstrdup("combine:"));
+ filter_spec_append_urlencode(
+ filter_options,
+ list_objects_filter_spec(&filter_options->sub[0]));
+ /*
+ * We don't need the filter_spec strings for subfilter specs, only the
+ * top level.
+ */
+ string_list_clear(&filter_options->sub[0].filter_spec, /*free_util=*/0);
+}
+
+void list_objects_filter_die_if_populated(
+ struct list_objects_filter_options *filter_options)
+{
+ if (filter_options->choice)
+ die(_("multiple filter-specs cannot be combined"));
+}
+
+void parse_list_objects_filter(
+ struct list_objects_filter_options *filter_options,
+ const char *arg)
+{
+ struct strbuf errbuf = STRBUF_INIT;
+ int parse_error;
+
+ if (!filter_options->choice) {
+ string_list_append(&filter_options->filter_spec, xstrdup(arg));
+
+ parse_error = gently_parse_list_objects_filter(
+ filter_options, arg, &errbuf);
+ } else {
+ /*
+ * Make filter_options an LOFC_COMBINE spec so we can trivially
+ * add subspecs to it.
+ */
+ transform_to_combine_type(filter_options);
+
+ string_list_append(&filter_options->filter_spec, xstrdup("+"));
+ filter_spec_append_urlencode(filter_options, arg);
+ ALLOC_GROW_BY(filter_options->sub, filter_options->sub_nr, 1,
+ filter_options->sub_alloc);
+
+ parse_error = gently_parse_list_objects_filter(
+ &filter_options->sub[filter_options->sub_nr - 1], arg,
+ &errbuf);
+ }
+ if (parse_error)
+ die("%s", errbuf.buf);
+}
+
int opt_parse_list_objects_filter(const struct option *opt,
const char *arg, int unset)
{
struct list_objects_filter_options *filter_options = opt->value;
- if (unset || !arg) {
+ if (unset || !arg)
list_objects_filter_set_no_filter(filter_options);
- return 0;
+ else
+ parse_list_objects_filter(filter_options, arg);
+ return 0;
+}
+
+const char *list_objects_filter_spec(struct list_objects_filter_options *filter)
+{
+ if (!filter->filter_spec.nr)
+ BUG("no filter_spec available for this filter");
+ if (filter->filter_spec.nr != 1) {
+ struct strbuf concatted = STRBUF_INIT;
+ strbuf_add_separated_string_list(
+ &concatted, "", &filter->filter_spec);
+ string_list_clear(&filter->filter_spec, /*free_util=*/0);
+ string_list_append(
+ &filter->filter_spec, strbuf_detach(&concatted, NULL));
}
- return parse_list_objects_filter(filter_options, arg);
+ return filter->filter_spec.items[0].string;
}
-void expand_list_objects_filter_spec(
- const struct list_objects_filter_options *filter,
- struct strbuf *expanded_spec)
+const char *expand_list_objects_filter_spec(
+ struct list_objects_filter_options *filter)
{
- strbuf_init(expanded_spec, strlen(filter->filter_spec));
- if (filter->choice == LOFC_BLOB_LIMIT)
- strbuf_addf(expanded_spec, "blob:limit=%lu",
+ if (filter->choice == LOFC_BLOB_LIMIT) {
+ struct strbuf expanded_spec = STRBUF_INIT;
+ strbuf_addf(&expanded_spec, "blob:limit=%lu",
filter->blob_limit_value);
- else if (filter->choice == LOFC_TREE_DEPTH)
- strbuf_addf(expanded_spec, "tree:%lu",
- filter->tree_exclude_depth);
- else
- strbuf_addstr(expanded_spec, filter->filter_spec);
+ string_list_clear(&filter->filter_spec, /*free_util=*/0);
+ string_list_append(
+ &filter->filter_spec,
+ strbuf_detach(&expanded_spec, NULL));
+ }
+
+ return list_objects_filter_spec(filter);
}
void list_objects_filter_release(
struct list_objects_filter_options *filter_options)
{
- free(filter_options->filter_spec);
+ size_t sub;
+
+ if (!filter_options)
+ return;
+ string_list_clear(&filter_options->filter_spec, /*free_util=*/0);
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));
}
void partial_clone_register(
const char *remote,
- const struct list_objects_filter_options *filter_options)
+ struct list_objects_filter_options *filter_options)
{
char *cfg_name;
char *filter_name;
@@ -168,7 +349,9 @@ void partial_clone_register(
* the default for subsequent fetches from this remote.
*/
filter_name = xstrfmt("remote.%s.partialclonefilter", remote);
- git_config_set(filter_name, filter_options->filter_spec);
+ /* NEEDSWORK: 'expand' result leaking??? */
+ git_config_set(filter_name,
+ expand_list_objects_filter_spec(filter_options));
free(filter_name);
/* Make sure the config info are reset */
@@ -180,12 +363,18 @@ void partial_clone_get_default_filter_spec(
const char *remote)
{
struct promisor_remote *promisor = promisor_remote_find(remote);
+ struct strbuf errbuf = STRBUF_INIT;
/*
* Parse default value, but silently ignore it if it is invalid.
*/
- if (promisor)
- gently_parse_list_objects_filter(filter_options,
- promisor->partial_clone_filter,
- NULL);
+ if (!promisor)
+ return;
+
+ string_list_append(&filter_options->filter_spec,
+ promisor->partial_clone_filter);
+ gently_parse_list_objects_filter(filter_options,
+ promisor->partial_clone_filter,
+ &errbuf);
+ strbuf_release(&errbuf);
}
diff --git a/list-objects-filter-options.h b/list-objects-filter-options.h
index 8deaa287b5..b63c5ee1a3 100644
--- a/list-objects-filter-options.h
+++ b/list-objects-filter-options.h
@@ -2,7 +2,7 @@
#define LIST_OBJECTS_FILTER_OPTIONS_H
#include "parse-options.h"
-#include "strbuf.h"
+#include "string-list.h"
/*
* The list of defined filters for list-objects.
@@ -13,6 +13,7 @@ enum list_objects_filter_choice {
LOFC_BLOB_LIMIT,
LOFC_TREE_DEPTH,
LOFC_SPARSE_OID,
+ LOFC_COMBINE,
LOFC__COUNT /* must be last */
};
@@ -23,8 +24,10 @@ struct list_objects_filter_options {
* commands that launch filtering sub-processes, or for communication
* over the network, don't use this value; use the result of
* expand_list_objects_filter_spec() instead.
+ * To get the raw filter spec given by the user, use the result of
+ * list_objects_filter_spec().
*/
- char *filter_spec;
+ struct string_list filter_spec;
/*
* 'choice' is determined by parsing the filter-spec. This indicates
@@ -38,19 +41,40 @@ struct list_objects_filter_options {
unsigned int no_filter : 1;
/*
- * Parsed values (fields) from within the filter-spec. These are
- * choice-specific; not all values will be defined for any given
- * choice.
+ * BEGIN choice-specific parsed values from within the filter-spec. Only
+ * some values will be defined for any given choice.
*/
+
struct object_id *sparse_oid_value;
unsigned long blob_limit_value;
unsigned long tree_exclude_depth;
+
+ /* LOFC_COMBINE values */
+
+ /* This array contains all the subfilters which this filter combines. */
+ size_t sub_nr, sub_alloc;
+ struct list_objects_filter_options *sub;
+
+ /*
+ * END choice-specific parsed values.
+ */
};
/* Normalized command line arguments */
#define CL_ARG__FILTER "filter"
-int parse_list_objects_filter(
+void list_objects_filter_die_if_populated(
+ struct list_objects_filter_options *filter_options);
+
+/*
+ * Parses the filter spec string given by arg and either (1) simply places the
+ * result in filter_options if it is not yet populated or (2) combines it with
+ * the filter already in filter_options if it is already populated. In the case
+ * of (2), the filter specs are combined as if specified with 'combine:'.
+ *
+ * Dies and prints a user-facing message if an error occurs.
+ */
+void parse_list_objects_filter(
struct list_objects_filter_options *filter_options,
const char *arg);
@@ -65,13 +89,22 @@ int opt_parse_list_objects_filter(const struct option *opt,
/*
* Translates abbreviated numbers in the filter's filter_spec into their
* fully-expanded forms (e.g., "limit:blob=1k" becomes "limit:blob=1024").
+ * Returns a string owned by the list_objects_filter_options object.
*
- * This form should be used instead of the raw filter_spec field when
- * communicating with a remote process or subprocess.
+ * This form should be used instead of the raw list_objects_filter_spec()
+ * value when communicating with a remote process or subprocess.
*/
-void expand_list_objects_filter_spec(
- const struct list_objects_filter_options *filter,
- struct strbuf *expanded_spec);
+const char *expand_list_objects_filter_spec(
+ struct list_objects_filter_options *filter);
+
+/*
+ * Returns the filter spec string more or less in the form as the user
+ * entered it. This form of the filter_spec can be used in user-facing
+ * messages. Returns a string owned by the list_objects_filter_options
+ * object.
+ */
+const char *list_objects_filter_spec(
+ struct list_objects_filter_options *filter);
void list_objects_filter_release(
struct list_objects_filter_options *filter_options);
@@ -85,7 +118,7 @@ static inline void list_objects_filter_set_no_filter(
void partial_clone_register(
const char *remote,
- const struct list_objects_filter_options *filter_options);
+ struct list_objects_filter_options *filter_options);
void partial_clone_get_default_filter_spec(
struct list_objects_filter_options *filter_options,
const char *remote);
diff --git a/list-objects-filter.c b/list-objects-filter.c
index 36e1f774bc..d664264d65 100644
--- a/list-objects-filter.c
+++ b/list-objects-filter.c
@@ -26,11 +26,46 @@
*/
#define FILTER_SHOWN_BUT_REVISIT (1<<21)
-/*
- * A filter for list-objects to omit ALL blobs from the traversal.
- * And to OPTIONALLY collect a list of the omitted OIDs.
- */
-struct filter_blobs_none_data {
+struct subfilter {
+ struct filter *filter;
+ struct oidset seen;
+ struct oidset omits;
+ struct object_id skip_tree;
+ unsigned is_skipping_tree : 1;
+};
+
+struct filter {
+ enum list_objects_filter_result (*filter_object_fn)(
+ 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);
+
+ /*
+ * Optional. If this function is supplied and the filter needs
+ * to collect omits, then this function is called once before
+ * free_fn is called.
+ *
+ * This is required because the following two conditions hold:
+ *
+ * a. A tree filter can add and remove objects as an object
+ * graph is traversed.
+ * b. A combine filter's omit set is the union of all its
+ * subfilters, which may include tree: filters.
+ *
+ * As such, the omits sets must be separate sets, and can only
+ * be unioned after the traversal is completed.
+ */
+ void (*finalize_omits_fn)(struct oidset *omits, void *filter_data);
+
+ void (*free_fn)(void *filter_data);
+
+ void *filter_data;
+
+ /* If non-NULL, the filter collects a list of the omitted OIDs here. */
struct oidset *omits;
};
@@ -40,10 +75,9 @@ static enum list_objects_filter_result filter_blobs_none(
struct object *obj,
const char *pathname,
const char *filename,
+ struct oidset *omits,
void *filter_data_)
{
- struct filter_blobs_none_data *filter_data = filter_data_;
-
switch (filter_situation) {
default:
BUG("unknown filter_situation: %d", filter_situation);
@@ -61,24 +95,18 @@ static enum list_objects_filter_result filter_blobs_none(
assert(obj->type == OBJ_BLOB);
assert((obj->flags & SEEN) == 0);
- if (filter_data->omits)
- oidset_insert(filter_data->omits, &obj->oid);
+ if (omits)
+ oidset_insert(omits, &obj->oid);
return LOFR_MARK_SEEN; /* but not LOFR_DO_SHOW (hard omit) */
}
}
-static void *filter_blobs_none__init(
- struct oidset *omitted,
+static void filter_blobs_none__init(
struct list_objects_filter_options *filter_options,
- filter_object_fn *filter_fn,
- filter_free_fn *filter_free_fn)
+ struct filter *filter)
{
- struct filter_blobs_none_data *d = xcalloc(1, sizeof(*d));
- d->omits = omitted;
-
- *filter_fn = filter_blobs_none;
- *filter_free_fn = free;
- return d;
+ filter->filter_object_fn = filter_blobs_none;
+ filter->free_fn = free;
}
/*
@@ -86,8 +114,6 @@ static void *filter_blobs_none__init(
* Can OPTIONALLY collect a list of the omitted OIDs.
*/
struct filter_trees_depth_data {
- struct oidset *omits;
-
/*
* Maps trees to the minimum depth at which they were seen. It is not
* necessary to re-traverse a tree at deeper or equal depths than it has
@@ -110,16 +136,16 @@ struct seen_map_entry {
/* Returns 1 if the oid was in the omits set before it was invoked. */
static int filter_trees_update_omits(
struct object *obj,
- struct filter_trees_depth_data *filter_data,
+ struct oidset *omits,
int include_it)
{
- if (!filter_data->omits)
+ if (!omits)
return 0;
if (include_it)
- return oidset_remove(filter_data->omits, &obj->oid);
+ return oidset_remove(omits, &obj->oid);
else
- return oidset_insert(filter_data->omits, &obj->oid);
+ return oidset_insert(omits, &obj->oid);
}
static enum list_objects_filter_result filter_trees_depth(
@@ -128,6 +154,7 @@ static enum list_objects_filter_result filter_trees_depth(
struct object *obj,
const char *pathname,
const char *filename,
+ struct oidset *omits,
void *filter_data_)
{
struct filter_trees_depth_data *filter_data = filter_data_;
@@ -152,7 +179,7 @@ static enum list_objects_filter_result filter_trees_depth(
return LOFR_ZERO;
case LOFS_BLOB:
- filter_trees_update_omits(obj, filter_data, include_it);
+ filter_trees_update_omits(obj, omits, include_it);
return include_it ? LOFR_MARK_SEEN | LOFR_DO_SHOW : LOFR_ZERO;
case LOFS_BEGIN_TREE:
@@ -173,12 +200,12 @@ static enum list_objects_filter_result filter_trees_depth(
filter_res = LOFR_SKIP_TREE;
} else {
int been_omitted = filter_trees_update_omits(
- obj, filter_data, include_it);
+ obj, omits, include_it);
seen_info->depth = filter_data->current_depth;
if (include_it)
filter_res = LOFR_DO_SHOW;
- else if (filter_data->omits && !been_omitted)
+ else if (omits && !been_omitted)
/*
* Must update omit information of children
* recursively; they have not been omitted yet.
@@ -201,21 +228,18 @@ static void filter_trees_free(void *filter_data) {
free(d);
}
-static void *filter_trees_depth__init(
- struct oidset *omitted,
+static void filter_trees_depth__init(
struct list_objects_filter_options *filter_options,
- filter_object_fn *filter_fn,
- filter_free_fn *filter_free_fn)
+ struct filter *filter)
{
struct filter_trees_depth_data *d = xcalloc(1, sizeof(*d));
- d->omits = omitted;
oidmap_init(&d->seen_at_depth, 0);
d->exclude_depth = filter_