* [Outreachy][PATCH] promisor-remote: honor promisor.fetchOrder when trying remotes
@ 2026-11-04 17:24 Yopa Nelly
2026-11-05 14:30 ` Tanga Joris
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Yopa Nelly @ 2026-11-04 17:24 UTC (permalink / raw)
To: git; +Cc: Nelly Yopa
From: Nelly Yopa <yopatatchou@gmail.com>
When a repo has multiple promisor remotes, Git just tries them in
the order they happen to be in the config. If you want to try one
remote before another, the only way is to move lines around in the
config file, which is awkward, especially once you have per-worktree
or system/global configs in play.
This adds a promisor.fetchOrder config variable that takes a
comma-separated list of remote names. Remotes listed there are tried
first, in the order they're listed. Remotes that aren't listed are
tried afterwards, in their original config order. The
extensions.partialClone remote still ends up last (that's done by
promisor_remote_move_to_tail() in promisor_remote_init()), unless
someone names it in fetchOrder, in which case its listed position
wins.
The sort happens inside each pass in try_promisor_remotes(), so the
existing "accepted remotes before the rest" behavior is unchanged.
Within each pass, the order from fetchOrder wins.
I put the parser in promisor_remote_config() next to the other
promisor keys. It stores the parsed list in a file-static
string_list. The per-remote order field is set in
promisor_remote_apply_fetch_order(), which runs at the end of
promisor_remote_init() after the partialClone remote has been moved
to the tail. The actual sort is a small stable insertion sort in
promisor_remote_sort(); order == 0 means "not listed" so unnamed
remotes still sort after named ones without losing their relative
order. It's called once at the start of try_promisor_remotes() and
is effectively a no-op on the second pass if the first one already
succeeded.
I didn't validate the names in fetchOrder against the set of
configured promisor remotes. A typo just means that remote doesn't
appear in the list and keeps its default position. That's how other
list-valued config variables in Git behave, and it keeps the parser
small.
No test, and I want to be upfront about why. The t5710 fixture
always has the server satisfy a filtered clone from its own
promisor remote, so the client never actually triggers a lazy fetch
inside that test, which means the ordering change can't be observed
there. I'd need a different fixture, one that gives the client two
promisor remotes and keeps the server out of the picture. I'm happy
to write that as a follow-up if that's the right direction.
Signed-off-by: Yopa Nelly <yopatatchou@gmail.com>
---
promisor-remote.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++-
promisor-remote.h | 1 +
2 files changed, 70 insertions(+), 1 deletion(-)
diff --git a/promisor-remote.c b/promisor-remote.c
index 316d9950aa..56e9eeabe3 100644
--- a/promisor-remote.c
+++ b/promisor-remote.c
@@ -21,6 +21,9 @@ struct promisor_remote_config {
struct promisor_remote **promisors_tail;
};
+static struct string_list fetch_order = STRING_LIST_INIT_DUP;
+static int fetch_order_initialized;
+
static int fetch_objects(struct repository *repo,
const char *remote_name,
const struct object_id *oids,
@@ -120,6 +123,20 @@ static int promisor_remote_config(const char *var, const char *value,
size_t namelen;
const char *subkey;
+ if (!strcmp(var, "promisor.fetchorder")) {
+ char *value_copy;
+
+ if (!value)
+ return config_error_nonbool(var);
+ value_copy = xstrdup(value);
+ string_list_split_in_place_f(&fetch_order, value_copy, ",", -1,
+ STRING_LIST_SPLIT_TRIM |
+ STRING_LIST_SPLIT_NONEMPTY);
+ free(value_copy);
+ fetch_order_initialized = 1;
+ return 0;
+ }
+
if (parse_config_key(var, "remote", &name, &namelen, &subkey) < 0)
return 0;
@@ -157,6 +174,24 @@ static int promisor_remote_config(const char *var, const char *value,
return 0;
}
+static void promisor_remote_apply_fetch_order(struct promisor_remote_config *config)
+{
+ struct promisor_remote *r;
+ size_t i;
+
+ if (!fetch_order_initialized)
+ return;
+
+ for (i = 0; i < fetch_order.nr; i++) {
+ for (r = config->promisors; r; r = r->next) {
+ if (!strcmp(r->name, fetch_order.items[i].string)) {
+ r->order = i + 1;
+ break;
+ }
+ }
+ }
+}
+
static void promisor_remote_init(struct repository *r)
{
struct promisor_remote_config *config;
@@ -180,6 +215,8 @@ static void promisor_remote_init(struct repository *r)
else
promisor_remote_new(config, r->repository_format_partial_clone);
}
+
+ promisor_remote_apply_fetch_order(config);
}
void promisor_remote_clear(struct promisor_remote_config *config)
@@ -262,6 +299,34 @@ static int remove_fetched_oids(struct repository *repo,
return remaining_nr;
}
+static void promisor_remote_sort(struct promisor_remote_config *config)
+{
+ struct promisor_remote *sorted = NULL;
+ struct promisor_remote *r = config->promisors;
+
+ if (!fetch_order_initialized)
+ return;
+
+ while (r) {
+ struct promisor_remote *next = r->next;
+ struct promisor_remote **pp = &sorted;
+
+ while (*pp && ((*pp)->order == 0
+ ? r->order != 0
+ : r->order == 0 || (*pp)->order <= r->order))
+ pp = &(*pp)->next;
+
+ r->next = *pp;
+ *pp = r;
+ r = next;
+ }
+
+ config->promisors = sorted;
+ config->promisors_tail = &config->promisors;
+ while (*config->promisors_tail)
+ config->promisors_tail = &(*config->promisors_tail)->next;
+}
+
/*
* Fetch the remaining objects (given in '*remaining_oids', which
* contains '*remaining_nr' object ids) from the known promisor
@@ -286,7 +351,10 @@ static int try_promisor_remotes(struct repository *repo,
int depth,
bool accepted_only)
{
- struct promisor_remote *r = repo->promisor_remote_config->promisors;
+ struct promisor_remote *r;
+
+ promisor_remote_sort(repo->promisor_remote_config);
+ r = repo->promisor_remote_config->promisors;
for (; r; r = r->next) {
if (accepted_only != r->accepted)
diff --git a/promisor-remote.h b/promisor-remote.h
index 301f5ac5cb..679a138bb1 100644
--- a/promisor-remote.h
+++ b/promisor-remote.h
@@ -17,6 +17,7 @@ struct promisor_remote {
char *partial_clone_filter;
char *advertised_filter;
unsigned int accepted : 1;
+ int order;
const char name[FLEX_ARRAY];
};
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [Outreachy][PATCH] promisor-remote: honor promisor.fetchOrder when trying remotes 2026-11-04 17:24 [Outreachy][PATCH] promisor-remote: honor promisor.fetchOrder when trying remotes Yopa Nelly @ 2026-11-05 14:30 ` Tanga Joris 2026-11-05 16:31 ` Kaartic Sivaraam 2026-11-05 22:52 ` [Outreachy][PATCH v2] " Yopa Nelly 2 siblings, 0 replies; 6+ messages in thread From: Tanga Joris @ 2026-11-05 14:30 UTC (permalink / raw) To: Yopa Nelly; +Cc: git, Kaartic Sivaraam, christian.couder Hello Nelly, Thanks for working on this, being able to choose the order of promisor remotes sounds useful. I tried your patch on top of master (0ffa3e35c7) in a partial clone, and I think there is a problem when the new variable is set: $ git config promisor.fetchOrder origin $ git show HEAD:f BUG: string-list.c:340: string_list_split_in_place() called with strdup_strings On Wed, 4 Nov 2026 at 20:48, Yopa Nelly <yopatatchou@gmail.com> wrote: > + char *value_copy; > + > + if (!value) > + return config_error_nonbool(var); > + value_copy = xstrdup(value); > + string_list_split_in_place_f(&fetch_order, value_copy, ",", -1, Without the variable it works fine. I think this is because fetch_order is created with STRING_LIST_INIT_DUP, but string_list_split_in_place_f() is used on it. Maybe string_list_split_f() would work here instead? Also, each time the variable is seen the names are added to the same list. The list is never cleared, so values from several config files pile up. It also looks like the list is file-static, so every repository in the process (for example submodules) shares it, while the rest of the promisor config lives in each repository's struct promisor_remote_config. > No test, and I want to be upfront about why. The t5710 fixture Thank you for being upfront about this. I noticed that t0410-partial-clone.sh already has a test with two promisor remotes ("fetching from another promisor remote"), so maybe a test for the order could go there. It would also catch the crash above. I also could not find the new variable in Documentation/config/promisor.adoc. I suggest adding it there too. > + if (!strcmp(var, "promisor.fetchorder")) { One small thing: this line is indented with spaces instead of a tab. Best, Tanga ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [Outreachy][PATCH] promisor-remote: honor promisor.fetchOrder when trying remotes 2026-11-04 17:24 [Outreachy][PATCH] promisor-remote: honor promisor.fetchOrder when trying remotes Yopa Nelly 2026-11-05 14:30 ` Tanga Joris @ 2026-11-05 16:31 ` Kaartic Sivaraam 2026-11-05 22:55 ` Tatchou Yopa Nelly 2026-11-05 22:52 ` [Outreachy][PATCH v2] " Yopa Nelly 2 siblings, 1 reply; 6+ messages in thread From: Kaartic Sivaraam @ 2026-11-05 16:31 UTC (permalink / raw) To: Yopa Nelly, git On 11/4/26 22:54, Yopa Nelly wrote: > From: Nelly Yopa <yopatatchou@gmail.com> > > When a repo has multiple promisor remotes, Git just tries them in > the order they happen to be in the config. If you want to try one > remote before another, the only way is to move lines around in the > config file, which is awkward, especially once you have per-worktree > or system/global configs in play. > > This adds a promisor.fetchOrder config variable that takes a > comma-separated list of remote names. Remotes listed there are tried > first, in the order they're listed. Remotes that aren't listed are > tried afterwards, in their original config order. The > extensions.partialClone remote still ends up last (that's done by > promisor_remote_move_to_tail() in promisor_remote_init()), unless > someone names it in fetchOrder, in which case its listed position > wins. > > The sort happens inside each pass in try_promisor_remotes(), so the > existing "accepted remotes before the rest" behavior is unchanged. > Within each pass, the order from fetchOrder wins. > > I put the parser in promisor_remote_config() next to the other > promisor keys. It stores the parsed list in a file-static > string_list. The per-remote order field is set in > promisor_remote_apply_fetch_order(), which runs at the end of > promisor_remote_init() after the partialClone remote has been moved > to the tail. The actual sort is a small stable insertion sort in > promisor_remote_sort(); order == 0 means "not listed" so unnamed > remotes still sort after named ones without losing their relative > order. It's called once at the start of try_promisor_remotes() and > is effectively a no-op on the second pass if the first one already > succeeded. > > I didn't validate the names in fetchOrder against the set of > configured promisor remotes. A typo just means that remote doesn't > appear in the list and keeps its default position. That's how other > list-valued config variables in Git behave, and it keeps the parser > small. > > No test, and I want to be upfront about why. The t5710 fixture > always has the server satisfy a filtered clone from its own > promisor remote, so the client never actually triggers a lazy fetch > inside that test, which means the ordering change can't be observed > there. I'd need a different fixture, one that gives the client two > promisor remotes and keeps the server out of the picture. I'm happy > to write that as a follow-up if that's the right direction. > To be honest, only part of the commit describes the change. The rest of it reads part like you're talking to the people on the lis. You might want to give the SubmittingPatches section a read: https://git-scm.com/docs/SubmittingPatches#describe-changes Also, this patch kind of tries achieve a part of the project without actually going through the RFC part of it. That feels a bit strange. Did you possibly misunderstand what a microproject is supposed to be? Did you possibly read the following guides and have some difficulties with parts of it? If so, I'd be glad to hear more about it so that we can fix the same. https://git.github.io/General-Microproject-Information/ https://git.github.io/General-Application-Information/ -- Sivaraam ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [Outreachy][PATCH] promisor-remote: honor promisor.fetchOrder when trying remotes 2026-11-05 16:31 ` Kaartic Sivaraam @ 2026-11-05 22:55 ` Tatchou Yopa Nelly 0 siblings, 0 replies; 6+ messages in thread From: Tatchou Yopa Nelly @ 2026-11-05 22:55 UTC (permalink / raw) To: Kaartic Sivaraam; +Cc: git Hi Tanga, Thanks for the careful review you caught real bugs. I've sent a v2 that addresses all of your points: - fetch_order is now in struct promisor_remote_config, not a file-static, so it is per-repository and gets cleared with the rest of the promisor config. - The list is now initialized with string_list_init_nodup() on first use, matching what string_list_split_in_place_f() expects, so the BUG is gone. I reproduced your exact sequence to confirm. - The docs entry is added to Documentation/config/promisor.adoc. - The whitespace is fixed. I also dropped the linked-list insertion sort. Instead, try_promisor_remotes() now walks the list twice: once in fetchOrder order for the named remotes, then in original config order for the rest. That's simpler than sorting a linked list at fetch time, and it keeps the accepted/unaccepted two-pass logic unchanged. I didn't add a test yet. You mentioned t0410-partial-clone.sh already has a test with two promisor remotes ("fetching from another promisor remote"). I'll look at whether the ordering can be verified there, either as part of a v3 or as a follow-up. Kaartic, I've also re-read SubmittingPatches#describe-changes and rewritten the commit message to describe the change rather than address the reviewers directly. On the RFC point: I treated the Phase 2 config work as the micro-patch and skipped the RFC step you pointed out. Should I step back and send the design RFC first, or keep the current patch as a prototype while we discuss the config shape on the list? Thanks, Yopa Nelly On Thu, 5 Nov 2026 at 17:31, Kaartic Sivaraam <kaartic.sivaraam@gmail.com> wrote: > > On 11/4/26 22:54, Yopa Nelly wrote: > > From: Nelly Yopa <yopatatchou@gmail.com> > > > > When a repo has multiple promisor remotes, Git just tries them in > > the order they happen to be in the config. If you want to try one > > remote before another, the only way is to move lines around in the > > config file, which is awkward, especially once you have per-worktree > > or system/global configs in play. > > > > This adds a promisor.fetchOrder config variable that takes a > > comma-separated list of remote names. Remotes listed there are tried > > first, in the order they're listed. Remotes that aren't listed are > > tried afterwards, in their original config order. The > > extensions.partialClone remote still ends up last (that's done by > > promisor_remote_move_to_tail() in promisor_remote_init()), unless > > someone names it in fetchOrder, in which case its listed position > > wins. > > > > The sort happens inside each pass in try_promisor_remotes(), so the > > existing "accepted remotes before the rest" behavior is unchanged. > > Within each pass, the order from fetchOrder wins. > > > > I put the parser in promisor_remote_config() next to the other > > promisor keys. It stores the parsed list in a file-static > > string_list. The per-remote order field is set in > > promisor_remote_apply_fetch_order(), which runs at the end of > > promisor_remote_init() after the partialClone remote has been moved > > to the tail. The actual sort is a small stable insertion sort in > > promisor_remote_sort(); order == 0 means "not listed" so unnamed > > remotes still sort after named ones without losing their relative > > order. It's called once at the start of try_promisor_remotes() and > > is effectively a no-op on the second pass if the first one already > > succeeded. > > > > I didn't validate the names in fetchOrder against the set of > > configured promisor remotes. A typo just means that remote doesn't > > appear in the list and keeps its default position. That's how other > > list-valued config variables in Git behave, and it keeps the parser > > small. > > > > No test, and I want to be upfront about why. The t5710 fixture > > always has the server satisfy a filtered clone from its own > > promisor remote, so the client never actually triggers a lazy fetch > > inside that test, which means the ordering change can't be observed > > there. I'd need a different fixture, one that gives the client two > > promisor remotes and keeps the server out of the picture. I'm happy > > to write that as a follow-up if that's the right direction. > > > > To be honest, only part of the commit describes the change. The rest of > it reads part like you're talking to the people on the lis. You might > want to give the SubmittingPatches section a read: > > https://git-scm.com/docs/SubmittingPatches#describe-changes > > Also, this patch kind of tries achieve a part of the project without > actually going through the RFC part of it. That feels a bit strange. Did > you possibly misunderstand what a microproject is supposed to be? Did > you possibly read the following guides and have some difficulties with > parts of it? If so, I'd be glad to hear more about it so that we can fix > the same. > > https://git.github.io/General-Microproject-Information/ > > https://git.github.io/General-Application-Information/ > > -- > Sivaraam > ^ permalink raw reply [flat|nested] 6+ messages in thread
* [Outreachy][PATCH v2] promisor-remote: honor promisor.fetchOrder when trying remotes 2026-11-04 17:24 [Outreachy][PATCH] promisor-remote: honor promisor.fetchOrder when trying remotes Yopa Nelly 2026-11-05 14:30 ` Tanga Joris 2026-11-05 16:31 ` Kaartic Sivaraam @ 2026-11-05 22:52 ` Yopa Nelly 2026-11-05 23:09 ` Junio C Hamano 2 siblings, 1 reply; 6+ messages in thread From: Yopa Nelly @ 2026-11-05 22:52 UTC (permalink / raw) To: git; +Cc: tangajoris, kaartic.sivaraam, Nelly Yopa From: Nelly Yopa <yopatatchou@gmail.com> When a repository is configured with more than one promisor remote, Git tries them one after another until the missing objects have been fetched. The order is currently determined only by the order in which remote.<name>.promisor entries appear in the configuration. There is no way to say "try this remote first" without moving config lines around. Add a promisor.fetchOrder configuration variable that takes a comma-separated list of remote names. Remotes named in the list are tried first, in the order listed. Remotes not named in the list are tried afterwards, in their original configuration order. The extensions.partialClone remote is still moved to the end of the list by promisor_remote_init() unless it is named in fetchOrder, in which case its listed position wins. The order is applied inside each of the two passes in lazy_fetch_objects() first the accepted remotes, then the rest so the existing "accepted before configured" behavior is unchanged. Within each pass, the fetchOrder order takes precedence over the configuration order. The parsed list is stored in struct promisor_remote_config and is re-parsed each time the config is read. Instead of sorting the linked list at fetch time, try_promisor_remotes() walks the list twice: once in fetchOrder order for the remotes named there, and once in the original config order for the rest. Signed-off-by: Yopa Nelly <yopatatchou@gmail.com> --- Documentation/config/promisor.adoc | 11 +++ promisor-remote.c | 108 +++++++++++++++++++++++++---- 2 files changed, 107 insertions(+), 12 deletions(-) diff --git a/Documentation/config/promisor.adoc b/Documentation/config/promisor.adoc index c1f3d766a6..ccc0925800 100644 --- a/Documentation/config/promisor.adoc +++ b/Documentation/config/promisor.adoc @@ -31,6 +31,17 @@ This list has no effect unless the "promisor.advertise" config variable is set to "true", and the "name" and "url" fields are always advertised regardless of this setting. +`promisor.fetchOrder`:: + A comma-separated list of promisor remote names. When Git + needs to lazily fetch a missing object, it tries the remotes + in this list first, in the order listed, then the remaining + configured promisor remotes in their configuration order. + The `extensions.partialClone` remote is tried last unless it + is named in this list. ++ +See linkgit:gitprotocol-v2[5] and the "Partial Clone Design +Notes" in link:technical/partial-clone.html[Documentation/technical/partial-clone]. + promisor.acceptFromServer:: Controls which promisor remotes advertised by a server (using the "promisor-remote" protocol capability) a client will accept. By diff --git a/promisor-remote.c b/promisor-remote.c index 316d9950aa..c664a48f88 100644 --- a/promisor-remote.c +++ b/promisor-remote.c @@ -19,8 +19,11 @@ struct promisor_remote_config { struct promisor_remote *promisors; struct promisor_remote **promisors_tail; + struct string_list fetch_order; + int fetch_order_initialized; }; + static int fetch_objects(struct repository *repo, const char *remote_name, const struct object_id *oids, @@ -120,6 +123,29 @@ static int promisor_remote_config(const char *var, const char *value, size_t namelen; const char *subkey; + if (!strcmp(var, "promisor.fetchorder")) { + char *value_copy; + + if (!value) + return config_error_nonbool(var); + /* + * The list is re-read from scratch every time the config + * is parsed, so start by clearing any previous entries. + */ + if (config->fetch_order_initialized) + string_list_clear(&config->fetch_order, 0); + else + string_list_init_nodup(&config->fetch_order); + + value_copy = xstrdup(value); + string_list_split_in_place_f(&config->fetch_order, value_copy, ",", -1, + STRING_LIST_SPLIT_TRIM | + STRING_LIST_SPLIT_NONEMPTY); + free(value_copy); + config->fetch_order_initialized = 1; + return 0; + } + if (parse_config_key(var, "remote", &name, &namelen, &subkey) < 0) return 0; @@ -157,6 +183,8 @@ static int promisor_remote_config(const char *var, const char *value, return 0; } + + static void promisor_remote_init(struct repository *r) { struct promisor_remote_config *config; @@ -180,6 +208,7 @@ static void promisor_remote_init(struct repository *r) else promisor_remote_new(config, r->repository_format_partial_clone); } + } void promisor_remote_clear(struct promisor_remote_config *config) @@ -193,6 +222,9 @@ void promisor_remote_clear(struct promisor_remote_config *config) } config->promisors_tail = &config->promisors; + if (config->fetch_order_initialized) + string_list_clear(&config->fetch_order, 0); + config->fetch_order_initialized = 0; } void repo_promisor_remote_reinit(struct repository *r) @@ -262,6 +294,8 @@ static int remove_fetched_oids(struct repository *repo, return remaining_nr; } + + /* * Fetch the remaining objects (given in '*remaining_oids', which * contains '*remaining_nr' object ids) from the known promisor @@ -279,6 +313,24 @@ static int remove_fetched_oids(struct repository *repo, * Return 1 when all the requested objects have been fetched, 0 * otherwise. */ +static int try_one_remote(struct repository *repo, struct promisor_remote *r, + struct object_id **remaining_oids, + int *remaining_nr, int *to_free, int depth) +{ + if (fetch_objects(repo, r->name, + *remaining_oids, *remaining_nr, depth) < 0) { + if (*remaining_nr == 1) + return 0; + *remaining_nr = remove_fetched_oids(repo, remaining_oids, + *remaining_nr, *to_free); + if (*remaining_nr) { + *to_free = 1; + return 0; + } + } + return 1; /* all fetched */ +} + static int try_promisor_remotes(struct repository *repo, struct object_id **remaining_oids, int *remaining_nr, @@ -286,24 +338,56 @@ static int try_promisor_remotes(struct repository *repo, int depth, bool accepted_only) { - struct promisor_remote *r = repo->promisor_remote_config->promisors; + struct promisor_remote_config *config = repo->promisor_remote_config; + struct promisor_remote *r; + size_t i; + int fetched; + + /* + * First pass: try the remotes in the order the user listed + * them in promisor.fetchOrder. + */ + if (config->fetch_order_initialized) { + for (i = 0; i < config->fetch_order.nr; i++) { + r = promisor_remote_lookup(config, + config->fetch_order.items[i].string, + NULL); + if (!r || accepted_only != r->accepted) + continue; - for (; r; r = r->next) { + fetched = try_one_remote(repo, r, remaining_oids, + remaining_nr, to_free, depth); + if (fetched) + return 1; + } + } + + /* + * Second pass: try the remaining remotes in their config order. + */ + for (r = config->promisors; r; r = r->next) { if (accepted_only != r->accepted) continue; - if (fetch_objects(repo, r->name, - *remaining_oids, *remaining_nr, depth) < 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; + + /* Skip ones we already tried in the first pass. */ + if (config->fetch_order_initialized) { + bool already_tried = false; + for (i = 0; i < config->fetch_order.nr; i++) { + if (!strcmp(r->name, config->fetch_order.items[i].string)) { + already_tried = true; + break; + } } + if (already_tried) + continue; } - return 1; /* all fetched */ + + fetched = try_one_remote(repo, r, remaining_oids, + remaining_nr, to_free, depth); + if (fetched) + return 1; } + return 0; } -- 2.43.0 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [Outreachy][PATCH v2] promisor-remote: honor promisor.fetchOrder when trying remotes 2026-11-05 22:52 ` [Outreachy][PATCH v2] " Yopa Nelly @ 2026-11-05 23:09 ` Junio C Hamano 0 siblings, 0 replies; 6+ messages in thread From: Junio C Hamano @ 2026-11-05 23:09 UTC (permalink / raw) To: Christian Couder; +Cc: Yopa Nelly, git, tangajoris, kaartic.sivaraam Christian, I thought you are the go-to person for promisor remote, and if I am not mistaken, you worked on letting the server side tell the clients which promisors to use first? How would this mesh well with that? Thanks. Yopa Nelly <yopatatchou@gmail.com> writes: > From: Nelly Yopa <yopatatchou@gmail.com> > > When a repository is configured with more than one promisor > remote, Git tries them one after another until the missing > objects have been fetched. The order is currently determined > only by the order in which remote.<name>.promisor entries appear > in the configuration. There is no way to say "try this remote > first" without moving config lines around. > > Add a promisor.fetchOrder configuration variable that takes a > comma-separated list of remote names. Remotes named in the list > are tried first, in the order listed. Remotes not named in the > list are tried afterwards, in their original configuration > order. The extensions.partialClone remote is still moved to the > end of the list by promisor_remote_init() unless it is named in > fetchOrder, in which case its listed position wins. > > The order is applied inside each of the two passes in > lazy_fetch_objects() first the accepted remotes, then the > rest so the existing "accepted before configured" behavior is > unchanged. Within each pass, the fetchOrder order takes > precedence over the configuration order. > > The parsed list is stored in struct promisor_remote_config and > is re-parsed each time the config is read. Instead of sorting > the linked list at fetch time, try_promisor_remotes() walks the > list twice: once in fetchOrder order for the remotes named > there, and once in the original config order for the rest. > > Signed-off-by: Yopa Nelly <yopatatchou@gmail.com> > --- > Documentation/config/promisor.adoc | 11 +++ > promisor-remote.c | 108 +++++++++++++++++++++++++---- > 2 files changed, 107 insertions(+), 12 deletions(-) > > diff --git a/Documentation/config/promisor.adoc b/Documentation/config/promisor.adoc > index c1f3d766a6..ccc0925800 100644 > --- a/Documentation/config/promisor.adoc > +++ b/Documentation/config/promisor.adoc > @@ -31,6 +31,17 @@ This list has no effect unless the "promisor.advertise" config > variable is set to "true", and the "name" and "url" fields are always > advertised regardless of this setting. > > +`promisor.fetchOrder`:: > + A comma-separated list of promisor remote names. When Git > + needs to lazily fetch a missing object, it tries the remotes > + in this list first, in the order listed, then the remaining > + configured promisor remotes in their configuration order. > + The `extensions.partialClone` remote is tried last unless it > + is named in this list. > ++ > +See linkgit:gitprotocol-v2[5] and the "Partial Clone Design > +Notes" in link:technical/partial-clone.html[Documentation/technical/partial-clone]. > + > promisor.acceptFromServer:: > Controls which promisor remotes advertised by a server (using the > "promisor-remote" protocol capability) a client will accept. By > diff --git a/promisor-remote.c b/promisor-remote.c > index 316d9950aa..c664a48f88 100644 > --- a/promisor-remote.c > +++ b/promisor-remote.c > @@ -19,8 +19,11 @@ > struct promisor_remote_config { > struct promisor_remote *promisors; > struct promisor_remote **promisors_tail; > + struct string_list fetch_order; > + int fetch_order_initialized; > }; > > + > static int fetch_objects(struct repository *repo, > const char *remote_name, > const struct object_id *oids, > @@ -120,6 +123,29 @@ static int promisor_remote_config(const char *var, const char *value, > size_t namelen; > const char *subkey; > > + if (!strcmp(var, "promisor.fetchorder")) { > + char *value_copy; > + > + if (!value) > + return config_error_nonbool(var); > + /* > + * The list is re-read from scratch every time the config > + * is parsed, so start by clearing any previous entries. > + */ > + if (config->fetch_order_initialized) > + string_list_clear(&config->fetch_order, 0); > + else > + string_list_init_nodup(&config->fetch_order); > + > + value_copy = xstrdup(value); > + string_list_split_in_place_f(&config->fetch_order, value_copy, ",", -1, > + STRING_LIST_SPLIT_TRIM | > + STRING_LIST_SPLIT_NONEMPTY); > + free(value_copy); > + config->fetch_order_initialized = 1; > + return 0; > + } > + > if (parse_config_key(var, "remote", &name, &namelen, &subkey) < 0) > return 0; > > @@ -157,6 +183,8 @@ static int promisor_remote_config(const char *var, const char *value, > return 0; > } > > + > + > static void promisor_remote_init(struct repository *r) > { > struct promisor_remote_config *config; > @@ -180,6 +208,7 @@ static void promisor_remote_init(struct repository *r) > else > promisor_remote_new(config, r->repository_format_partial_clone); > } > + > } > > void promisor_remote_clear(struct promisor_remote_config *config) > @@ -193,6 +222,9 @@ void promisor_remote_clear(struct promisor_remote_config *config) > } > > config->promisors_tail = &config->promisors; > + if (config->fetch_order_initialized) > + string_list_clear(&config->fetch_order, 0); > + config->fetch_order_initialized = 0; > } > > void repo_promisor_remote_reinit(struct repository *r) > @@ -262,6 +294,8 @@ static int remove_fetched_oids(struct repository *repo, > return remaining_nr; > } > > + > + > /* > * Fetch the remaining objects (given in '*remaining_oids', which > * contains '*remaining_nr' object ids) from the known promisor > @@ -279,6 +313,24 @@ static int remove_fetched_oids(struct repository *repo, > * Return 1 when all the requested objects have been fetched, 0 > * otherwise. > */ > +static int try_one_remote(struct repository *repo, struct promisor_remote *r, > + struct object_id **remaining_oids, > + int *remaining_nr, int *to_free, int depth) > +{ > + if (fetch_objects(repo, r->name, > + *remaining_oids, *remaining_nr, depth) < 0) { > + if (*remaining_nr == 1) > + return 0; > + *remaining_nr = remove_fetched_oids(repo, remaining_oids, > + *remaining_nr, *to_free); > + if (*remaining_nr) { > + *to_free = 1; > + return 0; > + } > + } > + return 1; /* all fetched */ > +} > + > static int try_promisor_remotes(struct repository *repo, > struct object_id **remaining_oids, > int *remaining_nr, > @@ -286,24 +338,56 @@ static int try_promisor_remotes(struct repository *repo, > int depth, > bool accepted_only) > { > - struct promisor_remote *r = repo->promisor_remote_config->promisors; > + struct promisor_remote_config *config = repo->promisor_remote_config; > + struct promisor_remote *r; > + size_t i; > + int fetched; > + > + /* > + * First pass: try the remotes in the order the user listed > + * them in promisor.fetchOrder. > + */ > + if (config->fetch_order_initialized) { > + for (i = 0; i < config->fetch_order.nr; i++) { > + r = promisor_remote_lookup(config, > + config->fetch_order.items[i].string, > + NULL); > + if (!r || accepted_only != r->accepted) > + continue; > > - for (; r; r = r->next) { > + fetched = try_one_remote(repo, r, remaining_oids, > + remaining_nr, to_free, depth); > + if (fetched) > + return 1; > + } > + } > + > + /* > + * Second pass: try the remaining remotes in their config order. > + */ > + for (r = config->promisors; r; r = r->next) { > if (accepted_only != r->accepted) > continue; > - if (fetch_objects(repo, r->name, > - *remaining_oids, *remaining_nr, depth) < 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; > + > + /* Skip ones we already tried in the first pass. */ > + if (config->fetch_order_initialized) { > + bool already_tried = false; > + for (i = 0; i < config->fetch_order.nr; i++) { > + if (!strcmp(r->name, config->fetch_order.items[i].string)) { > + already_tried = true; > + break; > + } > } > + if (already_tried) > + continue; > } > - return 1; /* all fetched */ > + > + fetched = try_one_remote(repo, r, remaining_oids, > + remaining_nr, to_free, depth); > + if (fetched) > + return 1; > } > + > return 0; > } ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-11-05 23:09 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-11-04 17:24 [Outreachy][PATCH] promisor-remote: honor promisor.fetchOrder when trying remotes Yopa Nelly 2026-11-05 14:30 ` Tanga Joris 2026-11-05 16:31 ` Kaartic Sivaraam 2026-11-05 22:55 ` Tatchou Yopa Nelly 2026-11-05 22:52 ` [Outreachy][PATCH v2] " Yopa Nelly 2026-11-05 23:09 ` Junio C Hamano
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox