Merge branch 'tb/upload-pack-filters'
The component to respond to "git fetch" request is made more configurable to selectively allow or reject object filtering specification used for partial cloning. * tb/upload-pack-filters: t5616: use test_i18ngrep for upload-pack errors upload-pack.c: introduce 'uploadpackfilter.tree.maxDepth' upload-pack.c: allow banning certain object filter(s) list_objects_filter_options: introduce 'list_object_filter_config_name'
This commit is contained in:
commit
73a9255166
@ -57,6 +57,24 @@ uploadpack.allowFilter::
|
||||
If this option is set, `upload-pack` will support partial
|
||||
clone and partial fetch object filtering.
|
||||
|
||||
uploadpackfilter.allow::
|
||||
Provides a default value for unspecified object filters (see: the
|
||||
below configuration variable).
|
||||
Defaults to `true`.
|
||||
|
||||
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`.
|
||||
|
||||
uploadpackfilter.tree.maxDepth::
|
||||
Only allow `--filter=tree=<n>` when `n` is no more than the value of
|
||||
`uploadpackfilter.tree.maxDepth`. If set, this also implies
|
||||
`uploadpackfilter.tree.allow=true`, unless this configuration
|
||||
variable had already been set. Has no effect if unset.
|
||||
|
||||
uploadpack.allowRefInWant::
|
||||
If this option is set, `upload-pack` will support the `ref-in-want`
|
||||
feature of the protocol version 2 `fetch` command. This feature
|
||||
|
@ -15,6 +15,29 @@ static int parse_combine_filter(
|
||||
const char *arg,
|
||||
struct strbuf *errbuf);
|
||||
|
||||
const char *list_object_filter_config_name(enum list_objects_filter_choice c)
|
||||
{
|
||||
switch (c) {
|
||||
case LOFC_DISABLED:
|
||||
/* we have no name for "no filter at all" */
|
||||
break;
|
||||
case LOFC_BLOB_NONE:
|
||||
return "blob:none";
|
||||
case LOFC_BLOB_LIMIT:
|
||||
return "blob:limit";
|
||||
case LOFC_TREE_DEPTH:
|
||||
return "tree";
|
||||
case LOFC_SPARSE_OID:
|
||||
return "sparse:oid";
|
||||
case LOFC_COMBINE:
|
||||
return "combine";
|
||||
case LOFC__COUNT:
|
||||
/* not a real filter type; just the count of all filters */
|
||||
break;
|
||||
}
|
||||
BUG("list_object_filter_choice_name: invalid argument '%d'", c);
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse value of the argument to the "filter" keyword.
|
||||
* On the command line this looks like:
|
||||
|
@ -17,6 +17,12 @@ enum list_objects_filter_choice {
|
||||
LOFC__COUNT /* must be last */
|
||||
};
|
||||
|
||||
/*
|
||||
* Returns a configuration key suitable for describing the given object filter,
|
||||
* e.g.: "blob:none", "combine", etc.
|
||||
*/
|
||||
const char *list_object_filter_config_name(enum list_objects_filter_choice c);
|
||||
|
||||
struct list_objects_filter_options {
|
||||
/*
|
||||
* 'filter_spec' is the raw argument value given on the command line
|
||||
|
@ -235,6 +235,39 @@ test_expect_success 'implicitly construct combine: filter with repeated flags' '
|
||||
test_cmp unique_types.expected unique_types.actual
|
||||
'
|
||||
|
||||
test_expect_success 'upload-pack fails banned object filters' '
|
||||
test_config -C srv.bare uploadpackfilter.blob:none.allow false &&
|
||||
test_must_fail ok=sigpipe git clone --no-checkout --filter=blob:none \
|
||||
"file://$(pwd)/srv.bare" pc3 2>err &&
|
||||
test_i18ngrep "filter '\''blob:none'\'' not supported" err
|
||||
'
|
||||
|
||||
test_expect_success 'upload-pack fails banned combine object filters' '
|
||||
test_config -C srv.bare uploadpackfilter.allow false &&
|
||||
test_config -C srv.bare uploadpackfilter.combine.allow true &&
|
||||
test_config -C srv.bare uploadpackfilter.tree.allow true &&
|
||||
test_config -C srv.bare uploadpackfilter.blob:none.allow false &&
|
||||
test_must_fail ok=sigpipe git clone --no-checkout --filter=tree:1 \
|
||||
--filter=blob:none "file://$(pwd)/srv.bare" pc3 2>err &&
|
||||
test_i18ngrep "filter '\''blob:none'\'' not supported" err
|
||||
'
|
||||
|
||||
test_expect_success 'upload-pack fails banned object filters with fallback' '
|
||||
test_config -C srv.bare uploadpackfilter.allow false &&
|
||||
test_must_fail ok=sigpipe git clone --no-checkout --filter=blob:none \
|
||||
"file://$(pwd)/srv.bare" pc3 2>err &&
|
||||
test_i18ngrep "filter '\''blob:none'\'' not supported" err
|
||||
'
|
||||
|
||||
test_expect_success 'upload-pack limits tree depth filters' '
|
||||
test_config -C srv.bare uploadpackfilter.allow false &&
|
||||
test_config -C srv.bare uploadpackfilter.tree.allow true &&
|
||||
test_config -C srv.bare uploadpackfilter.tree.maxDepth 0 &&
|
||||
test_must_fail ok=sigpipe git clone --no-checkout --filter=tree:1 \
|
||||
"file://$(pwd)/srv.bare" pc3 2>err &&
|
||||
test_i18ngrep "tree filter allows max depth 0, but got 1" err
|
||||
'
|
||||
|
||||
test_expect_success 'partial clone fetches blobs pointed to by refs even if normally filtered out' '
|
||||
rm -rf src dst &&
|
||||
git init src &&
|
||||
|
104
upload-pack.c
104
upload-pack.c
@ -88,6 +88,7 @@ struct upload_pack_data {
|
||||
enum allow_uor allow_uor;
|
||||
|
||||
struct list_objects_filter_options filter_options;
|
||||
struct string_list allowed_filters;
|
||||
|
||||
struct packet_writer writer;
|
||||
|
||||
@ -103,6 +104,8 @@ struct upload_pack_data {
|
||||
unsigned no_progress : 1;
|
||||
unsigned use_include_tag : 1;
|
||||
unsigned allow_filter : 1;
|
||||
unsigned allow_filter_fallback : 1;
|
||||
unsigned long tree_filter_max_depth;
|
||||
|
||||
unsigned done : 1; /* v2 only */
|
||||
unsigned allow_ref_in_want : 1; /* v2 only */
|
||||
@ -120,6 +123,7 @@ static void upload_pack_data_init(struct upload_pack_data *data)
|
||||
struct string_list deepen_not = STRING_LIST_INIT_DUP;
|
||||
struct string_list uri_protocols = STRING_LIST_INIT_DUP;
|
||||
struct object_array extra_edge_obj = OBJECT_ARRAY_INIT;
|
||||
struct string_list allowed_filters = STRING_LIST_INIT_DUP;
|
||||
|
||||
memset(data, 0, sizeof(*data));
|
||||
data->symref = symref;
|
||||
@ -131,6 +135,9 @@ static void upload_pack_data_init(struct upload_pack_data *data)
|
||||
data->deepen_not = deepen_not;
|
||||
data->uri_protocols = uri_protocols;
|
||||
data->extra_edge_obj = extra_edge_obj;
|
||||
data->allowed_filters = allowed_filters;
|
||||
data->allow_filter_fallback = 1;
|
||||
data->tree_filter_max_depth = ULONG_MAX;
|
||||
packet_writer_init(&data->writer, 1);
|
||||
|
||||
data->keepalive = 5;
|
||||
@ -147,6 +154,7 @@ static void upload_pack_data_clear(struct upload_pack_data *data)
|
||||
string_list_clear(&data->deepen_not, 0);
|
||||
object_array_clear(&data->extra_edge_obj);
|
||||
list_objects_filter_release(&data->filter_options);
|
||||
string_list_clear(&data->allowed_filters, 1);
|
||||
|
||||
free((char *)data->pack_objects_hook);
|
||||
}
|
||||
@ -983,6 +991,63 @@ static int process_deepen_not(const char *line, struct string_list *deepen_not,
|
||||
return 0;
|
||||
}
|
||||
|
||||
NORETURN __attribute__((format(printf,2,3)))
|
||||
static void send_err_and_die(struct upload_pack_data *data,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
strbuf_vaddf(&buf, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
packet_writer_error(&data->writer, "%s", buf.buf);
|
||||
die("%s", buf.buf);
|
||||
}
|
||||
|
||||
static void check_one_filter(struct upload_pack_data *data,
|
||||
struct list_objects_filter_options *opts)
|
||||
{
|
||||
const char *key = list_object_filter_config_name(opts->choice);
|
||||
struct string_list_item *item = string_list_lookup(&data->allowed_filters,
|
||||
key);
|
||||
int allowed;
|
||||
|
||||
if (item)
|
||||
allowed = (intptr_t)item->util;
|
||||
else
|
||||
allowed = data->allow_filter_fallback;
|
||||
|
||||
if (!allowed)
|
||||
send_err_and_die(data, "filter '%s' not supported", key);
|
||||
|
||||
if (opts->choice == LOFC_TREE_DEPTH &&
|
||||
opts->tree_exclude_depth > data->tree_filter_max_depth)
|
||||
send_err_and_die(data,
|
||||
"tree filter allows max depth %lu, but got %lu",
|
||||
data->tree_filter_max_depth,
|
||||
opts->tree_exclude_depth);
|
||||
}
|
||||
|
||||
static void check_filter_recurse(struct upload_pack_data *data,
|
||||
struct list_objects_filter_options *opts)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
check_one_filter(data, opts);
|
||||
if (opts->choice != LOFC_COMBINE)
|
||||
return;
|
||||
|
||||
for (i = 0; i < opts->sub_nr; i++)
|
||||
check_filter_recurse(data, &opts->sub[i]);
|
||||
}
|
||||
|
||||
static void die_if_using_banned_filter(struct upload_pack_data *data)
|
||||
{
|
||||
check_filter_recurse(data, &data->filter_options);
|
||||
}
|
||||
|
||||
static void receive_needs(struct upload_pack_data *data,
|
||||
struct packet_reader *reader)
|
||||
{
|
||||
@ -1013,6 +1078,7 @@ static void receive_needs(struct upload_pack_data *data,
|
||||
die("git upload-pack: filtering capability not negotiated");
|
||||
list_objects_filter_die_if_populated(&data->filter_options);
|
||||
parse_list_objects_filter(&data->filter_options, arg);
|
||||
die_if_using_banned_filter(data);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1170,6 +1236,41 @@ static int find_symref(const char *refname, const struct object_id *oid,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_object_filter_config(const char *var, const char *value,
|
||||
struct upload_pack_data *data)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
const char *sub, *key;
|
||||
size_t sub_len;
|
||||
|
||||
if (parse_config_key(var, "uploadpackfilter", &sub, &sub_len, &key))
|
||||
return 0;
|
||||
|
||||
if (!sub) {
|
||||
if (!strcmp(key, "allow"))
|
||||
data->allow_filter_fallback = git_config_bool(var, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
strbuf_add(&buf, sub, sub_len);
|
||||
|
||||
if (!strcmp(key, "allow"))
|
||||
string_list_insert(&data->allowed_filters, buf.buf)->util =
|
||||
(void *)(intptr_t)git_config_bool(var, value);
|
||||
else if (!strcmp(buf.buf, "tree") && !strcmp(key, "maxdepth")) {
|
||||
if (!value) {
|
||||
strbuf_release(&buf);
|
||||
return config_error_nonbool(var);
|
||||
}
|
||||
string_list_insert(&data->allowed_filters, buf.buf)->util =
|
||||
(void *)(intptr_t)1;
|
||||
data->tree_filter_max_depth = git_config_ulong(var, value);
|
||||
}
|
||||
|
||||
strbuf_release(&buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int upload_pack_config(const char *var, const char *value, void *cb_data)
|
||||
{
|
||||
struct upload_pack_data *data = cb_data;
|
||||
@ -1209,6 +1310,8 @@ static int upload_pack_config(const char *var, const char *value, void *cb_data)
|
||||
return git_config_string(&data->pack_objects_hook, var, value);
|
||||
}
|
||||
|
||||
parse_object_filter_config(var, value, data);
|
||||
|
||||
return parse_hide_refs_config(var, value, "uploadpack");
|
||||
}
|
||||
|
||||
@ -1389,6 +1492,7 @@ static void process_args(struct packet_reader *request,
|
||||
if (data->allow_filter && skip_prefix(arg, "filter ", &p)) {
|
||||
list_objects_filter_die_if_populated(&data->filter_options);
|
||||
parse_list_objects_filter(&data->filter_options, p);
|
||||
die_if_using_banned_filter(data);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user