Merge branch 'cc/promisor-auto-config-url'

Promisor remote handling has been refactored and fixed in
preparation for auto-configuration of advertised remotes.

* cc/promisor-auto-config-url:
  t5710: use proper file:// URIs for absolute paths
  promisor-remote: remove the 'accepted' strvec
  promisor-remote: keep accepted promisor_info structs alive
  promisor-remote: refactor accept_from_server()
  promisor-remote: refactor has_control_char()
  promisor-remote: refactor should_accept_remote() control flow
  promisor-remote: reject empty name or URL in advertised remote
  promisor-remote: clarify that a remote is ignored
  promisor-remote: pass config entry to all_fields_match() directly
  promisor-remote: try accepted remotes before others in get_direct()
main
Junio C Hamano 2026-05-11 10:05:53 +09:00
commit a19de4d24a
3 changed files with 238 additions and 111 deletions

View File

@ -848,6 +848,10 @@ advertised, it can reply with "promisor-remote=<pr-names>" where
where `pr-name` is the urlencoded name of a promisor remote the server
advertised and the client accepts.

The promisor remotes that the client accepted will be tried before the
other configured promisor remotes when the client attempts to fetch
missing objects.

Note that, everywhere in this document, the ';' and ',' characters
MUST be encoded if they appear in `pr-name` or `field-value`.


View File

@ -268,11 +268,35 @@ static int remove_fetched_oids(struct repository *repo,
return remaining_nr;
}

static int try_promisor_remotes(struct repository *repo,
struct object_id **remaining_oids,
int *remaining_nr, int *to_free,
bool accepted_only)
{
struct promisor_remote *r = repo->promisor_remote_config->promisors;

for (; r; r = r->next) {
if (accepted_only != r->accepted)
continue;
if (fetch_objects(repo, r->name, *remaining_oids, *remaining_nr) < 0) {
if (*remaining_nr == 1)
continue;
*remaining_nr = remove_fetched_oids(repo, remaining_oids,
*remaining_nr, *to_free);
if (*remaining_nr) {
*to_free = 1;
continue;
}
}
return 1; /* all fetched */
}
return 0;
}

void promisor_remote_get_direct(struct repository *repo,
const struct object_id *oids,
int oid_nr)
{
struct promisor_remote *r;
struct object_id *remaining_oids = (struct object_id *)oids;
int remaining_nr = oid_nr;
int to_free = 0;
@ -283,19 +307,13 @@ void promisor_remote_get_direct(struct repository *repo,

promisor_remote_init(repo);

for (r = repo->promisor_remote_config->promisors; r; r = r->next) {
if (fetch_objects(repo, r->name, remaining_oids, remaining_nr) < 0) {
if (remaining_nr == 1)
continue;
remaining_nr = remove_fetched_oids(repo, &remaining_oids,
remaining_nr, to_free);
if (remaining_nr) {
to_free = 1;
continue;
}
}
/* Try accepted remotes first (those the server told us to use) */
if (try_promisor_remotes(repo, &remaining_oids, &remaining_nr,
&to_free, true))
goto all_fetched;
if (try_promisor_remotes(repo, &remaining_oids, &remaining_nr,
&to_free, false))
goto all_fetched;
}

for (i = 0; i < remaining_nr; i++) {
if (is_promisor_object(repo, &remaining_oids[i]))
@ -557,6 +575,12 @@ enum accept_promisor {
ACCEPT_ALL
};

/*
* Check if a specific field and its advertised value match the local
* configuration of a given promisor remote.
*
* Returns 1 if they match, 0 otherwise.
*/
static int match_field_against_config(const char *field, const char *value,
struct promisor_info *config_info)
{
@ -568,9 +592,18 @@ static int match_field_against_config(const char *field, const char *value,
return 0;
}

/*
* Check that the advertised fields match the local configuration.
*
* When 'config_entry' is NULL (ACCEPT_ALL mode), every checked field
* must match at least one remote in 'config_info'.
*
* When 'config_entry' points to a specific remote's config, the
* checked fields are compared against that single remote only.
*/
static int all_fields_match(struct promisor_info *advertised,
struct string_list *config_info,
int in_list)
struct promisor_info *config_entry)
{
struct string_list *fields = fields_checked();
struct string_list_item *item_checked;
@ -579,7 +612,6 @@ static int all_fields_match(struct promisor_info *advertised,
int match = 0;
const char *field = item_checked->string;
const char *value = NULL;
struct string_list_item *item;

if (!strcasecmp(field, promisor_field_filter))
value = advertised->filter;
@ -589,7 +621,11 @@ static int all_fields_match(struct promisor_info *advertised,
if (!value)
return 0;

if (in_list) {
if (config_entry) {
match = match_field_against_config(field, value,
config_entry);
} else {
struct string_list_item *item;
for_each_string_list_item(item, config_info) {
struct promisor_info *p = item->util;
if (match_field_against_config(field, value, p)) {
@ -597,12 +633,6 @@ static int all_fields_match(struct promisor_info *advertised,
break;
}
}
} else {
item = string_list_lookup(config_info, advertised->name);
if (item) {
struct promisor_info *p = item->util;
match = match_field_against_config(field, value, p);
}
}

if (!match)
@ -612,6 +642,14 @@ static int all_fields_match(struct promisor_info *advertised,
return 1;
}

static bool has_control_char(const char *s)
{
for (const char *c = s; *c; c++)
if (iscntrl(*c))
return true;
return false;
}

static int should_accept_remote(enum accept_promisor accept,
struct promisor_info *advertised,
struct string_list *config_info)
@ -621,8 +659,13 @@ static int should_accept_remote(enum accept_promisor accept,
const char *remote_name = advertised->name;
const char *remote_url = advertised->url;

if (!remote_url || !*remote_url)
BUG("no or empty URL advertised for remote '%s'; "
"this remote should have been rejected earlier",
remote_name);

if (accept == ACCEPT_ALL)
return all_fields_match(advertised, config_info, 1);
return all_fields_match(advertised, config_info, NULL);

/* Get config info for that promisor remote */
item = string_list_lookup(config_info, remote_name);
@ -634,23 +677,19 @@ static int should_accept_remote(enum accept_promisor accept,
p = item->util;

if (accept == ACCEPT_KNOWN_NAME)
return all_fields_match(advertised, config_info, 0);
return all_fields_match(advertised, config_info, p);

if (accept != ACCEPT_KNOWN_URL)
BUG("Unhandled 'enum accept_promisor' value '%d'", accept);

if (!remote_url || !*remote_url) {
warning(_("no or empty URL advertised for remote '%s'"), remote_name);
if (strcmp(p->url, remote_url)) {
warning(_("known remote named '%s' but with URL '%s' instead of '%s', "
"ignoring this remote"),
remote_name, p->url, remote_url);
return 0;
}

if (!strcmp(p->url, remote_url))
return all_fields_match(advertised, config_info, 0);

warning(_("known remote named '%s' but with URL '%s' instead of '%s'"),
remote_name, p->url, remote_url);

return 0;
return all_fields_match(advertised, config_info, p);
}

static int skip_field_name_prefix(const char *elem, const char *field_name, const char **value)
@ -691,9 +730,9 @@ static struct promisor_info *parse_one_advertised_remote(const char *remote_info

string_list_clear(&elem_list, 0);

if (!info->name || !info->url) {
warning(_("server advertised a promisor remote without a name or URL: %s"),
remote_info);
if (!info->name || !*info->name || !info->url || !*info->url) {
warning(_("server advertised a promisor remote without a name or URL: '%s', "
"ignoring this remote"), remote_info);
promisor_info_free(info);
return NULL;
}
@ -741,18 +780,14 @@ static bool valid_filter(const char *filter, const char *remote_name)
return !res;
}

/* Check that a token doesn't contain any control character */
static bool valid_token(const char *token, const char *remote_name)
{
const char *c = token;

for (; *c; c++)
if (iscntrl(*c)) {
warning(_("invalid token '%s' for remote '%s' "
"will not be stored"),
token, remote_name);
return false;
}
if (has_control_char(token)) {
warning(_("invalid token '%s' for remote '%s' "
"will not be stored"),
token, remote_name);
return false;
}

return true;
}
@ -827,20 +862,12 @@ static bool promisor_store_advertised_fields(struct promisor_info *advertised,
return reload_config;
}

static void filter_promisor_remote(struct repository *repo,
struct strvec *accepted,
const char *info)
static enum accept_promisor accept_from_server(struct repository *repo)
{
const char *accept_str;
enum accept_promisor accept = ACCEPT_NONE;
struct string_list config_info = STRING_LIST_INIT_NODUP;
struct string_list remote_info = STRING_LIST_INIT_DUP;
struct store_info *store_info = NULL;
struct string_list_item *item;
bool reload_config = false;
struct string_list accepted_filters = STRING_LIST_INIT_DUP;

if (!repo_config_get_string_tmp(the_repository, "promisor.acceptfromserver", &accept_str)) {
if (!repo_config_get_string_tmp(repo, "promisor.acceptfromserver", &accept_str)) {
if (!*accept_str || !strcasecmp("None", accept_str))
accept = ACCEPT_NONE;
else if (!strcasecmp("KnownUrl", accept_str))
@ -854,6 +881,20 @@ static void filter_promisor_remote(struct repository *repo,
accept_str, "promisor.acceptfromserver");
}

return accept;
}

static void filter_promisor_remote(struct repository *repo,
struct string_list *accepted_remotes,
const char *info)
{
struct string_list config_info = STRING_LIST_INIT_NODUP;
struct string_list remote_info = STRING_LIST_INIT_DUP;
struct store_info *store_info = NULL;
struct string_list_item *item;
bool reload_config = false;
enum accept_promisor accept = accept_from_server(repo);

if (accept == ACCEPT_NONE)
return;

@ -880,17 +921,10 @@ static void filter_promisor_remote(struct repository *repo,
if (promisor_store_advertised_fields(advertised, store_info))
reload_config = true;

strvec_push(accepted, advertised->name);

/* Capture advertised filters for accepted remotes */
if (advertised->filter) {
struct string_list_item *i;
i = string_list_append(&accepted_filters, advertised->name);
i->util = xstrdup(advertised->filter);
}
string_list_append(accepted_remotes, advertised->name)->util = advertised;
} else {
promisor_info_free(advertised);
}

promisor_info_free(advertised);
}

promisor_info_list_clear(&config_info);
@ -900,39 +934,36 @@ static void filter_promisor_remote(struct repository *repo,
if (reload_config)
repo_promisor_remote_reinit(repo);

/* Apply accepted remote filters to the stable repo state */
for_each_string_list_item(item, &accepted_filters) {
struct promisor_remote *r = repo_promisor_remote_find(repo, item->string);
/* Apply accepted remotes to the stable repo state */
for_each_string_list_item(item, accepted_remotes) {
struct promisor_info *info = item->util;
struct promisor_remote *r = repo_promisor_remote_find(repo, info->name);

if (r) {
free(r->advertised_filter);
r->advertised_filter = item->util;
item->util = NULL;
}
}

string_list_clear(&accepted_filters, 1);

/* Mark the remotes as accepted in the repository state */
for (size_t i = 0; i < accepted->nr; i++) {
struct promisor_remote *r = repo_promisor_remote_find(repo, accepted->v[i]);
if (r)
r->accepted = 1;
if (info->filter) {
free(r->advertised_filter);
r->advertised_filter = xstrdup(info->filter);
}
}
}
}

void promisor_remote_reply(const char *info, char **accepted_out)
{
struct strvec accepted = STRVEC_INIT;
struct string_list accepted_remotes = STRING_LIST_INIT_NODUP;

filter_promisor_remote(the_repository, &accepted, info);
filter_promisor_remote(the_repository, &accepted_remotes, info);

if (accepted_out) {
if (accepted.nr) {
if (accepted_remotes.nr) {
struct strbuf reply = STRBUF_INIT;
for (size_t i = 0; i < accepted.nr; i++) {
if (i)
struct string_list_item *item;

for_each_string_list_item(item, &accepted_remotes) {
if (reply.len)
strbuf_addch(&reply, ';');
strbuf_addstr_urlencode(&reply, accepted.v[i], allow_unsanitized);
strbuf_addstr_urlencode(&reply, item->string, allow_unsanitized);
}
*accepted_out = strbuf_detach(&reply, NULL);
} else {
@ -940,7 +971,7 @@ void promisor_remote_reply(const char *info, char **accepted_out)
}
}

strvec_clear(&accepted);
promisor_info_list_clear(&accepted_remotes);
}

void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes)

View File

@ -76,6 +76,31 @@ copy_to_lop () {
cp "$path" "$path2"
}

# On Windows, `pwd` returns a path like 'D:/foo/bar'. Prepend '/' to turn
# it into '/D:/foo/bar', which is what git expects in file:// URLs on Windows.
# On Unix, the path already starts with '/', so this is a no-op.
pwd_path=$(pwd)
case "$pwd_path" in
[a-zA-Z]:*) pwd_path="/$pwd_path" ;;
esac

# Allowed characters: alphanumeric, standard path/URI (_ . ~ / : -),
# and those percent-encoded below (% space = , ;)
rest=$(printf "%s" "$pwd_path" | tr -d 'a-zA-Z0-9_.~/:% =,;-')
if test -n "$rest"
then
skip_all="PWD contains unsupported special characters"
test_done
fi

TRASH_DIRECTORY_URL="file://$pwd_path"

encoded_path=$(printf "%s" "$pwd_path" |
sed -e 's/%/%25/g' -e 's/ /%20/g' -e 's/=/%3D/g' \
-e 's/;/%3B/g' -e 's/,/%2C/g')

ENCODED_TRASH_DIRECTORY_URL="file://$encoded_path"

test_expect_success "setup for testing promisor remote advertisement" '
# Create another bare repo called "lop" (for Large Object Promisor)
git init --bare lop &&
@ -88,7 +113,7 @@ test_expect_success "setup for testing promisor remote advertisement" '
initialize_server 1 "$oid" &&

# Configure lop as promisor remote for server
git -C server remote add lop "file://$(pwd)/lop" &&
git -C server remote add lop "$TRASH_DIRECTORY_URL/lop" &&
git -C server config remote.lop.promisor true &&

git -C lop config uploadpack.allowFilter true &&
@ -104,7 +129,7 @@ test_expect_success "clone with promisor.advertise set to 'true'" '
# Clone from server to create a client
GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
-c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
-c remote.lop.url="file://$(pwd)/lop" \
-c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
-c promisor.acceptfromserver=All \
--no-local --filter="blob:limit=5k" server client &&

@ -119,7 +144,7 @@ test_expect_success "clone with promisor.advertise set to 'false'" '
# Clone from server to create a client
GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
-c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
-c remote.lop.url="file://$(pwd)/lop" \
-c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
-c promisor.acceptfromserver=All \
--no-local --filter="blob:limit=5k" server client &&

@ -137,7 +162,7 @@ test_expect_success "clone with promisor.acceptfromserver set to 'None'" '
# Clone from server to create a client
GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
-c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
-c remote.lop.url="file://$(pwd)/lop" \
-c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
-c promisor.acceptfromserver=None \
--no-local --filter="blob:limit=5k" server client &&

@ -156,8 +181,8 @@ test_expect_success "init + fetch with promisor.advertise set to 'true'" '
git -C client init &&
git -C client config remote.lop.promisor true &&
git -C client config remote.lop.fetch "+refs/heads/*:refs/remotes/lop/*" &&
git -C client config remote.lop.url "file://$(pwd)/lop" &&
git -C client config remote.server.url "file://$(pwd)/server" &&
git -C client config remote.lop.url "$TRASH_DIRECTORY_URL/lop" &&
git -C client config remote.server.url "$TRASH_DIRECTORY_URL/server" &&
git -C client config remote.server.fetch "+refs/heads/*:refs/remotes/server/*" &&
git -C client config promisor.acceptfromserver All &&
GIT_NO_LAZY_FETCH=0 git -C client fetch --filter="blob:limit=5k" server &&
@ -166,6 +191,75 @@ test_expect_success "init + fetch with promisor.advertise set to 'true'" '
check_missing_objects server 1 "$oid"
'

test_expect_success "clone with two promisors but only one advertised" '
git -C server config promisor.advertise true &&
test_when_finished "rm -rf client unused_lop" &&

# Create a promisor that will be configured but not be used
git init --bare unused_lop &&

# Clone from server to create a client
GIT_TRACE="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \
-c remote.unused_lop.promisor=true \
-c remote.unused_lop.fetch="+refs/heads/*:refs/remotes/unused_lop/*" \
-c remote.unused_lop.url="$TRASH_DIRECTORY_URL/unused_lop" \
-c remote.lop.promisor=true \
-c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
-c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
-c promisor.acceptfromserver=All \
--no-local --filter="blob:limit=5k" server client &&

# Check that "unused_lop" appears before "lop" in the config
printf "remote.%s.promisor true\n" "unused_lop" "lop" "origin" >expect &&
git -C client config get --all --show-names --regexp "^remote\..*\.promisor$" >actual &&
test_cmp expect actual &&

# Check that "lop" was tried
test_grep " fetch lop " trace &&
# Check that "unused_lop" was not contacted
# This means "lop", the accepted promisor, was tried first
test_grep ! " fetch unused_lop " trace &&

# Check that the largest object is still missing on the server
check_missing_objects server 1 "$oid"
'

test_expect_success "init + fetch two promisors but only one advertised" '
git -C server config promisor.advertise true &&
test_when_finished "rm -rf client unused_lop" &&

# Create a promisor that will be configured but not be used
git init --bare unused_lop &&

mkdir client &&
git -C client init &&
git -C client config remote.unused_lop.promisor true &&
git -C client config remote.unused_lop.fetch "+refs/heads/*:refs/remotes/unused_lop/*" &&
git -C client config remote.unused_lop.url "$TRASH_DIRECTORY_URL/unused_lop" &&
git -C client config remote.lop.promisor true &&
git -C client config remote.lop.fetch "+refs/heads/*:refs/remotes/lop/*" &&
git -C client config remote.lop.url "$TRASH_DIRECTORY_URL/lop" &&
git -C client config remote.server.url "$TRASH_DIRECTORY_URL/server" &&
git -C client config remote.server.fetch "+refs/heads/*:refs/remotes/server/*" &&
git -C client config promisor.acceptfromserver All &&

# Check that "unused_lop" appears before "lop" in the config
printf "remote.%s.promisor true\n" "unused_lop" "lop" >expect &&
git -C client config get --all --show-names --regexp "^remote\..*\.promisor$" >actual &&
test_cmp expect actual &&

GIT_TRACE="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git -C client fetch --filter="blob:limit=5k" server &&

# Check that "lop" was tried
test_grep " fetch lop " trace &&
# Check that "unused_lop" was not contacted
# This means "lop", the accepted promisor, was tried first
test_grep ! " fetch unused_lop " trace &&

# Check that the largest object is still missing on the server
check_missing_objects server 1 "$oid"
'

test_expect_success "clone with promisor.acceptfromserver set to 'KnownName'" '
git -C server config promisor.advertise true &&
test_when_finished "rm -rf client" &&
@ -173,7 +267,7 @@ test_expect_success "clone with promisor.acceptfromserver set to 'KnownName'" '
# Clone from server to create a client
GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
-c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
-c remote.lop.url="file://$(pwd)/lop" \
-c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
-c promisor.acceptfromserver=KnownName \
--no-local --filter="blob:limit=5k" server client &&

@ -188,7 +282,7 @@ test_expect_success "clone with 'KnownName' and different remote names" '
# Clone from server to create a client
GIT_NO_LAZY_FETCH=0 git clone -c remote.serverTwo.promisor=true \
-c remote.serverTwo.fetch="+refs/heads/*:refs/remotes/lop/*" \
-c remote.serverTwo.url="file://$(pwd)/lop" \
-c remote.serverTwo.url="$TRASH_DIRECTORY_URL/lop" \
-c promisor.acceptfromserver=KnownName \
--no-local --filter="blob:limit=5k" server client &&

@ -225,7 +319,7 @@ test_expect_success "clone with promisor.acceptfromserver set to 'KnownUrl'" '
# Clone from server to create a client
GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
-c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
-c remote.lop.url="file://$(pwd)/lop" \
-c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
-c promisor.acceptfromserver=KnownUrl \
--no-local --filter="blob:limit=5k" server client &&

@ -242,7 +336,7 @@ test_expect_success "clone with 'KnownUrl' and different remote urls" '
# Clone from server to create a client
GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
-c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
-c remote.lop.url="file://$(pwd)/serverTwo" \
-c remote.lop.url="$TRASH_DIRECTORY_URL/serverTwo" \
-c promisor.acceptfromserver=KnownUrl \
--no-local --filter="blob:limit=5k" server client &&

@ -257,7 +351,7 @@ test_expect_success "clone with 'KnownUrl' and url not configured on the server"
git -C server config promisor.advertise true &&
test_when_finished "rm -rf client" &&

test_when_finished "git -C server config set remote.lop.url \"file://$(pwd)/lop\"" &&
test_when_finished "git -C server config set remote.lop.url \"$TRASH_DIRECTORY_URL/lop\"" &&
git -C server config unset remote.lop.url &&

# Clone from server to create a client
@ -266,7 +360,7 @@ test_expect_success "clone with 'KnownUrl' and url not configured on the server"
# missing, so the remote name will be used instead which will fail.
test_must_fail env GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
-c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
-c remote.lop.url="file://$(pwd)/lop" \
-c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
-c promisor.acceptfromserver=KnownUrl \
--no-local --filter="blob:limit=5k" server client &&

@ -278,7 +372,7 @@ test_expect_success "clone with 'KnownUrl' and empty url, so not advertised" '
git -C server config promisor.advertise true &&
test_when_finished "rm -rf client" &&

test_when_finished "git -C server config set remote.lop.url \"file://$(pwd)/lop\"" &&
test_when_finished "git -C server config set remote.lop.url \"$TRASH_DIRECTORY_URL/lop\"" &&
git -C server config set remote.lop.url "" &&

# Clone from server to create a client
@ -287,7 +381,7 @@ test_expect_success "clone with 'KnownUrl' and empty url, so not advertised" '
# so the remote name will be used instead which will fail.
test_must_fail env GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
-c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
-c remote.lop.url="file://$(pwd)/lop" \
-c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
-c promisor.acceptfromserver=KnownUrl \
--no-local --filter="blob:limit=5k" server client &&

@ -311,13 +405,12 @@ test_expect_success "clone with promisor.sendFields" '
GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \
-c remote.lop.promisor=true \
-c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
-c remote.lop.url="file://$(pwd)/lop" \
-c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
-c promisor.acceptfromserver=All \
--no-local --filter="blob:limit=5k" server client &&

# Check that fields are properly transmitted
ENCODED_URL=$(echo "file://$(pwd)/lop" | sed -e "s/ /%20/g") &&
PR1="name=lop,url=$ENCODED_URL,partialCloneFilter=blob:none" &&
PR1="name=lop,url=$ENCODED_TRASH_DIRECTORY_URL/lop,partialCloneFilter=blob:none" &&
PR2="name=otherLop,url=https://invalid.invalid,partialCloneFilter=blob:limit=10k,token=fooBar" &&
test_grep "clone< promisor-remote=$PR1;$PR2" trace &&
test_grep "clone> promisor-remote=lop;otherLop" trace &&
@ -342,15 +435,14 @@ test_expect_success "clone with promisor.checkFields" '
GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \
-c remote.lop.promisor=true \
-c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
-c remote.lop.url="file://$(pwd)/lop" \
-c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
-c remote.lop.partialCloneFilter="blob:none" \
-c promisor.acceptfromserver=All \
-c promisor.checkFields=partialcloneFilter \
--no-local --filter="blob:limit=5k" server client &&

# Check that fields are properly transmitted
ENCODED_URL=$(echo "file://$(pwd)/lop" | sed -e "s/ /%20/g") &&
PR1="name=lop,url=$ENCODED_URL,partialCloneFilter=blob:none" &&
PR1="name=lop,url=$ENCODED_TRASH_DIRECTORY_URL/lop,partialCloneFilter=blob:none" &&
PR2="name=otherLop,url=https://invalid.invalid,partialCloneFilter=blob:limit=10k,token=fooBar" &&
test_grep "clone< promisor-remote=$PR1;$PR2" trace &&
test_grep "clone> promisor-remote=lop" trace &&
@ -380,7 +472,7 @@ test_expect_success "clone with promisor.storeFields=partialCloneFilter" '
GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \
-c remote.lop.promisor=true \
-c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
-c remote.lop.url="file://$(pwd)/lop" \
-c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
-c remote.lop.token="fooYYY" \
-c remote.lop.partialCloneFilter="blob:none" \
-c promisor.acceptfromserver=All \
@ -432,7 +524,7 @@ test_expect_success "clone and fetch with --filter=auto" '

GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \
-c remote.lop.promisor=true \
-c remote.lop.url="file://$(pwd)/lop" \
-c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
-c promisor.acceptfromserver=All \
--no-local --filter=auto server client 2>err &&

@ -489,7 +581,7 @@ test_expect_success "clone with promisor.advertise set to 'true' but don't delet
# Clone from server to create a client
GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
-c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
-c remote.lop.url="file://$(pwd)/lop" \
-c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
-c promisor.acceptfromserver=All \
--no-local --filter="blob:limit=5k" server client &&