Volume XXII, number 280Wednesday, October 7, 2026Latest message 2 hours ago

The Git List

News and archive of git@vger.kernel.org, since April 2005

patch, 3 partsconnected: search promisor objects generically

25 messages between Jun 22, 2026 and Jun 26, 2026, from Patrick Steinhardt, Junio C Hamano, Christian Couder.

Plain Markdown or JSON for tools and agents. Diffs are folded; open one to read it.

Patrick SteinhardtJun 22, 2026, 08:49 UTC on lore
Hi,

this patch series refactors "connected.c" so that we search for promisor objects in a generic way instead of reaching into internal of the object database. As a result, the connectivity checks will work properly in repos that don't use packfiles in the first place.

The series is built on top of 8d96f09e92 (Merge branch 'js/objects-larger-than-4gb-on-windows', 2026-06-19) with ps/odb-source-packed at 1bba3c035d (odb/source-packed: drop pointer to "files" parent source, 2026-06-17) merged into it.

Thanks!
Patrick
---
Patrick Steinhardt (3):
      odb/source-packed: extract logic to skip certain packs
      odb/source-packed: support flags when iterating an object prefix
      connected: search promisor objects generically
 connected.c         | 39 +++++++++++++++++++++++++--------------
 odb/source-packed.c | 50 +++++++++++++++++++++++++++++++++++++-------------
 2 files changed, 62 insertions(+), 27 deletions(-)

--- base-commit: 4a8e7a446f41435e157131162dfe901eca9250fe change-id: 20260612-pks-connected-generic-promisor-checks-2933bff3028d

Patrick SteinhardtJun 22, 2026, 08:49 UTC in reply to Patrick Steinhardt on lore

[PATCH 1/3] odb/source-packed: extract logic to skip certain packs

The caller can pass flags that allow them to filter out specific kinds of objects when iterating objects via `odb_for_each_object()`. This only works for "normal" iteration though, as we `BUG()` when the user passes flags and specifies an object prefix.

This limitation will be lifted in the next commit. Prepare for this by extracting the logic that skips certain kinds of packs so that we can easily reuse it.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb/source-packed.c | 28 ++++++++++++++++++----------
 1 file changed, 18 insertions(+), 10 deletions(-)
Show changes to odb/source-packed.c +18 −10
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 42c28fba0e..3afc4bf01f 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -126,6 +126,22 @@ static int match_hash(unsigned len, const unsigned char *a, const unsigned char
 	return 1;
 }
 
+static bool should_exclude_pack(struct packed_git *p, enum odb_for_each_object_flags flags)
+{
+	if ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)
+		return true;
+	if ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&
+	    !p->pack_promisor)
+		return true;
+	if ((flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&
+	    p->pack_keep_in_core)
+		return true;
+	if ((flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&
+	    p->pack_keep)
+		return true;
+	return false;
+}
+
 static int for_each_prefixed_object_in_midx(
 	struct odb_source_packed *store,
 	struct multi_pack_index *m,
@@ -306,17 +322,9 @@ static int odb_source_packed_for_each_object(struct odb_source *source,
 	for (e = packfile_store_get_packs(packed); e; e = e->next) {
 		struct packed_git *p = e->pack;
 
-		if ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)
-			continue;
-		if ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&
-		    !p->pack_promisor)
-			continue;
-		if ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&
-		    p->pack_keep_in_core)
-			continue;
-		if ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&
-		    p->pack_keep)
+		if (should_exclude_pack(p, opts->flags))
 			continue;
+
 		if (open_pack_index(p)) {
 			pack_errors = 1;
 			continue;
-- 
2.55.0.rc1.745.g43192e7977.dirty
Patrick SteinhardtJun 22, 2026, 08:49 UTC in reply to Patrick Steinhardt on lore

[PATCH 2/3] odb/source-packed: support flags when iterating an object prefix

Callers of `odb_for_each_object()` can specify an optional object name prefix so that we only yield objects that match it. This is incompatible though with passing flags at the same time, as we don't yet know to handle them.

Loosen this restriction by calling `should_exclude_pack()`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb/source-packed.c | 22 +++++++++++++++++++---
 1 file changed, 19 insertions(+), 3 deletions(-)
Show changes to odb/source-packed.c +19 −3
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 3afc4bf01f..6f31f0ff94 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -148,6 +148,7 @@ static int for_each_prefixed_object_in_midx(
 	const struct odb_for_each_object_options *opts,
 	struct odb_source_packed_for_each_object_wrapper_data *data)
 {
+	bool pack_errors = false;
 	int ret;
 
 	for (; m; m = m->base_midx) {
@@ -171,6 +172,20 @@ static int for_each_prefixed_object_in_midx(
 			const struct object_id *current = NULL;
 			struct object_id oid;
 
+			if (opts->flags) {
+				uint32_t pack_id = nth_midxed_pack_int_id(m, i);
+				struct packed_git *pack;
+
+				if (prepare_midx_pack(m, pack_id)) {
+					pack_errors = true;
+					continue;
+				}
+
+				pack = nth_midxed_pack(m, pack_id);
+				if (should_exclude_pack(pack, opts->flags))
+					continue;
+			}
+
 			current = nth_midxed_object_oid(&oid, m, i);
 
 			if (!match_hash(len, opts->prefix->hash, current->hash))
@@ -198,6 +213,8 @@ static int for_each_prefixed_object_in_midx(
 	ret = 0;
 
 out:
+	if (!ret && pack_errors)
+		ret = -1;
 	return ret;
 }
 
@@ -260,9 +277,6 @@ static int odb_source_packed_for_each_prefixed_object(
 	bool pack_errors = false;
 	int ret;
 
-	if (opts->flags)
-		BUG("flags unsupported");
-
 	store->skip_mru_updates = true;
 
 	m = get_multi_pack_index(store);
@@ -275,6 +289,8 @@ static int odb_source_packed_for_each_prefixed_object(
 	for (e = packfile_store_get_packs(store); e; e = e->next) {
 		if (e->pack->multi_pack_index)
 			continue;
+		if (should_exclude_pack(e->pack, opts->flags))
+			continue;
 
 		if (open_pack_index(e->pack)) {
 			pack_errors = true;
-- 
2.55.0.rc1.745.g43192e7977.dirty
Patrick SteinhardtJun 22, 2026, 08:49 UTC in reply to Patrick Steinhardt on lore

[PATCH 3/3] connected: search promisor objects generically

When performing connectivity checks we have to figure out whether any of the new objects are promisor objects, as we cannot assume full connectivity if so.

This check is performed by iterating through all packfiles in the repository and searching each of them for the given object. Of course, this mechanism is quite specific to implementation details of the object database, as we assume that it uses packfiles in the first place.

Refactor the logic so that we instead use `odb_for_each_object_ext()` with an object prefix filter and the `ODB_FOR_EACH_OBJECT_PROMISOR_ONLY` flag. This will yield all objects that have the exact object name and that are part of a promisor pack in a generic way.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 connected.c | 39 +++++++++++++++++++++++++--------------
 1 file changed, 25 insertions(+), 14 deletions(-)
Show changes to connected.c +25 −14
diff --git a/connected.c b/connected.c
index 7e26976832..9a666f0cdf 100644
--- a/connected.c
+++ b/connected.c
@@ -11,6 +11,13 @@
 #include "packfile.h"
 #include "promisor-remote.h"
 
+static int promised_object_cb(const struct object_id *oid UNUSED,
+			      struct object_info *oi UNUSED,
+			      void *payload UNUSED)
+{
+	return 1;
+}
+
 /*
  * If we feed all the commits we want to verify to this command
  *
@@ -46,6 +53,11 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
 	}
 
 	if (repo_has_promisor_remote(the_repository)) {
+		struct odb_for_each_object_options opts = {
+			.flags = ODB_FOR_EACH_OBJECT_PROMISOR_ONLY,
+			.prefix_hex_len = the_repository->hash_algo->hexsz,
+		};
+
 		/*
 		 * For partial clones, we don't want to have to do a regular
 		 * connectivity check because we have to enumerate and exclude
@@ -54,31 +66,30 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
 		 * object is a promisor object. Instead, just make sure we
 		 * received, in a promisor packfile, the objects pointed to by
 		 * each wanted ref.
-		 *
-		 * Before checking for promisor packs, be sure we have the
-		 * latest pack-files loaded into memory.
 		 */
-		odb_reprepare(the_repository->objects);
 		do {
-			struct packed_git *p;
-
-			repo_for_each_pack(the_repository, p) {
-				if (!p->pack_promisor)
-					continue;
-				if (find_pack_entry_one(oid, p))
-					goto promisor_pack_found;
+			opts.prefix = oid;
+
+			err = odb_for_each_object_ext(the_repository->objects,
+						      NULL, promised_object_cb,
+						      NULL, &opts);
+			if (err < 0)
+				break;
+			if (err > 0) {
+				err = 0;
+				continue;
 			}
+
 			/*
 			 * Fallback to rev-list with oid and the rest of the
 			 * object IDs provided by fn.
 			 */
 			goto no_promisor_pack_found;
-promisor_pack_found:
-			;
 		} while ((oid = fn(cb_data)) != NULL);
+
 		if (opt->err_fd)
 			close(opt->err_fd);
-		return 0;
+		return err;
 	}
 
 no_promisor_pack_found:
-- 
2.55.0.rc1.745.g43192e7977.dirty
Junio C HamanoJun 22, 2026, 17:51 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH 1/3] odb/source-packed: extract logic to skip certain packs

Patrick Steinhardt <ps@pks.im> writes:
Show 13 quoted lines
> The caller can pass flags that allow them to filter out specific kinds
> of objects when iterating objects via `odb_for_each_object()`. This only
> works for "normal" iteration though, as we `BUG()` when the user passes
> flags and specifies an object prefix.
>
> This limitation will be lifted in the next commit. Prepare for this by
> extracting the logic that skips certain kinds of packs so that we can
> easily reuse it.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  odb/source-packed.c | 28 ++++++++++++++++++----------
>  1 file changed, 18 insertions(+), 10 deletions(-)
Quite straight-forward creation of a simple helper function.
Show 47 quoted lines
> diff --git a/odb/source-packed.c b/odb/source-packed.c
> index 42c28fba0e..3afc4bf01f 100644
> --- a/odb/source-packed.c
> +++ b/odb/source-packed.c
> @@ -126,6 +126,22 @@ static int match_hash(unsigned len, const unsigned char *a, const unsigned char
>  	return 1;
>  }
>  
> +static bool should_exclude_pack(struct packed_git *p, enum odb_for_each_object_flags flags)
> +{
> +	if ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)
> +		return true;
> +	if ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&
> +	    !p->pack_promisor)
> +		return true;
> +	if ((flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&
> +	    p->pack_keep_in_core)
> +		return true;
> +	if ((flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&
> +	    p->pack_keep)
> +		return true;
> +	return false;
> +}
> +
>  static int for_each_prefixed_object_in_midx(
>  	struct odb_source_packed *store,
>  	struct multi_pack_index *m,
> @@ -306,17 +322,9 @@ static int odb_source_packed_for_each_object(struct odb_source *source,
>  	for (e = packfile_store_get_packs(packed); e; e = e->next) {
>  		struct packed_git *p = e->pack;
>  
> -		if ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)
> -			continue;
> -		if ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&
> -		    !p->pack_promisor)
> -			continue;
> -		if ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&
> -		    p->pack_keep_in_core)
> -			continue;
> -		if ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&
> -		    p->pack_keep)
> +		if (should_exclude_pack(p, opts->flags))
>  			continue;
> +
>  		if (open_pack_index(p)) {
>  			pack_errors = 1;
>  			continue;
Junio C HamanoJun 22, 2026, 17:57 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH 3/3] connected: search promisor objects generically

Patrick Steinhardt <ps@pks.im> writes:
Show 14 quoted lines
> When performing connectivity checks we have to figure out whether any of
> the new objects are promisor objects, as we cannot assume full
> connectivity if so.
>
> This check is performed by iterating through all packfiles in the
> repository and searching each of them for the given object. Of course,
> this mechanism is quite specific to implementation details of the object
> database, as we assume that it uses packfiles in the first place.
>
> Refactor the logic so that we instead use `odb_for_each_object_ext()`
> with an object prefix filter and the `ODB_FOR_EACH_OBJECT_PROMISOR_ONLY`
> flag. This will yield all objects that have the exact object name and
> that are part of a promisor pack in a generic way.
> ...
Show 5 quoted lines
> -		 *
> -		 * Before checking for promisor packs, be sure we have the
> -		 * latest pack-files loaded into memory.
>  		 */
> -		odb_reprepare(the_repository->objects);
Hmph?
Show 19 quoted lines
>  		do {
> -			struct packed_git *p;
> -
> -			repo_for_each_pack(the_repository, p) {
> -				if (!p->pack_promisor)
> -					continue;
> -				if (find_pack_entry_one(oid, p))
> -					goto promisor_pack_found;
> +			opts.prefix = oid;
> +
> +			err = odb_for_each_object_ext(the_repository->objects,
> +						      NULL, promised_object_cb,
> +						      NULL, &opts);
> +			if (err < 0)
> +				break;
> +			if (err > 0) {
> +				err = 0;
> +				continue;
>  			}

So we used to manually iterate and stop when we have a matching pack entry, but now "stop when we find" is done by promisor_object_cb callback that returns 1.

What is the reason why we no longer odb_(re)prepare() upfront before going into the loop? Would it make us miss a newly added promisor packs? We will fall back to rev-list for correctness, so it may not matter, though.

Show 17 quoted lines
> +
>  			/*
>  			 * Fallback to rev-list with oid and the rest of the
>  			 * object IDs provided by fn.
>  			 */
>  			goto no_promisor_pack_found;
> -promisor_pack_found:
> -			;
>  		} while ((oid = fn(cb_data)) != NULL);
> +
>  		if (opt->err_fd)
>  			close(opt->err_fd);
> -		return 0;
> +		return err;
>  	}
>  
>  no_promisor_pack_found:
Christian CouderJun 23, 2026, 07:45 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH 3/3] connected: search promisor objects generically

On Mon, Jun 22, 2026 at 10:50 AM Patrick Steinhardt <ps@pks.im> wrote:
Show 59 quoted lines
>
> When performing connectivity checks we have to figure out whether any of
> the new objects are promisor objects, as we cannot assume full
> connectivity if so.
>
> This check is performed by iterating through all packfiles in the
> repository and searching each of them for the given object. Of course,
> this mechanism is quite specific to implementation details of the object
> database, as we assume that it uses packfiles in the first place.
>
> Refactor the logic so that we instead use `odb_for_each_object_ext()`
> with an object prefix filter and the `ODB_FOR_EACH_OBJECT_PROMISOR_ONLY`
> flag. This will yield all objects that have the exact object name and
> that are part of a promisor pack in a generic way.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  connected.c | 39 +++++++++++++++++++++++++--------------
>  1 file changed, 25 insertions(+), 14 deletions(-)
>
> diff --git a/connected.c b/connected.c
> index 7e26976832..9a666f0cdf 100644
> --- a/connected.c
> +++ b/connected.c
> @@ -11,6 +11,13 @@
>  #include "packfile.h"
>  #include "promisor-remote.h"
>
> +static int promised_object_cb(const struct object_id *oid UNUSED,
> +                             struct object_info *oi UNUSED,
> +                             void *payload UNUSED)
> +{
> +       return 1;
> +}
> +
>  /*
>   * If we feed all the commits we want to verify to this command
>   *
> @@ -46,6 +53,11 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
>         }
>
>         if (repo_has_promisor_remote(the_repository)) {
> +               struct odb_for_each_object_options opts = {
> +                       .flags = ODB_FOR_EACH_OBJECT_PROMISOR_ONLY,
> +                       .prefix_hex_len = the_repository->hash_algo->hexsz,
> +               };
> +
>                 /*
>                  * For partial clones, we don't want to have to do a regular
>                  * connectivity check because we have to enumerate and exclude
> @@ -54,31 +66,30 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
>                  * object is a promisor object. Instead, just make sure we
>                  * received, in a promisor packfile, the objects pointed to by
>                  * each wanted ref.
> -                *
> -                * Before checking for promisor packs, be sure we have the
> -                * latest pack-files loaded into memory.
>                  */
> -               odb_reprepare(the_repository->objects);

Like Junio, I am not sure it's correct to remove the `odb_reprepare(the_repository->objects)` call.

I think it was added for good reasons in b739d971 (connected.c: reprepare packs for corner cases, 2020-03-13) and I am not sure odb_for_each_object_ext() is performing something similar.

At least the commit message should mention this change and explain a bit why the reasons the call was added are not valid anymore.

Show 36 quoted lines
>                 do {
> -                       struct packed_git *p;
> -
> -                       repo_for_each_pack(the_repository, p) {
> -                               if (!p->pack_promisor)
> -                                       continue;
> -                               if (find_pack_entry_one(oid, p))
> -                                       goto promisor_pack_found;
> +                       opts.prefix = oid;
> +
> +                       err = odb_for_each_object_ext(the_repository->objects,
> +                                                     NULL, promised_object_cb,
> +                                                     NULL, &opts);
> +                       if (err < 0)
> +                               break;
> +                       if (err > 0) {
> +                               err = 0;
> +                               continue;
>                         }
> +
>                         /*
>                          * Fallback to rev-list with oid and the rest of the
>                          * object IDs provided by fn.
>                          */
>                         goto no_promisor_pack_found;
> -promisor_pack_found:
> -                       ;
>                 } while ((oid = fn(cb_data)) != NULL);
> +
>                 if (opt->err_fd)
>                         close(opt->err_fd);
> -               return 0;
> +               return err;
>         }
>
>  no_promisor_pack_found:

These changes are difficult to understand as there are a number of `goto`, `break`, `return`, etc involved.

I think it comes in the first place from check_connected() doing too many things, and adding a preparatory commit to refactor it would help.

For example the preparatory commit could move a lot of code from check_connected() to the following new functions:

/*
 * Returns:
 *   1  = all wanted OIDs found in promisor packs: connected, done.
 *   0  = at least one OID not found: caller must fall back to rev-list.
 *  <0  = error.
 * On the fallback (0) return, *oid is left pointing at the first
 * not-found OID so the rev-list path can resume the iteration.
 */
static int check_connected_promisor(oid_iterate_fn fn, void *cb_data,
                                     const struct object_id **oid);
/*
 * In a non-promisor repo, pass the first OID as `oid`.
 * Otherwise pass the first not-found OID resumed from
 * check_connected_promisor() as `oid`.
 */
static int check_connected_rev_list(oid_iterate_fn fn, void *cb_data,
                                     struct check_connected_options *opt,
                                     const struct object_id *oid);
Patrick SteinhardtJun 24, 2026, 09:33 UTC in reply to Junio C Hamano on lore

Re: [PATCH 3/3] connected: search promisor objects generically

On Mon, Jun 22, 2026 at 10:57:10AM -0700, Junio C Hamano wrote:
> Patrick Steinhardt <ps@pks.im> writes:
[snip]
Show 7 quoted lines
> > -		 *
> > -		 * Before checking for promisor packs, be sure we have the
> > -		 * latest pack-files loaded into memory.
> >  		 */
> > -		odb_reprepare(the_repository->objects);
> 
> Hmph?

Oh, I think I completely misread this as `odb_alternates_prepare()`, which is something you typically see before loops like this. By using a helper like `odb_for_each_object_ext()` we of course wouldn't have to call that function anymore.

But this here is of course different, as this call would also cause us to reload packfiles and loose objects.

Show 28 quoted lines
> >  		do {
> > -			struct packed_git *p;
> > -
> > -			repo_for_each_pack(the_repository, p) {
> > -				if (!p->pack_promisor)
> > -					continue;
> > -				if (find_pack_entry_one(oid, p))
> > -					goto promisor_pack_found;
> > +			opts.prefix = oid;
> > +
> > +			err = odb_for_each_object_ext(the_repository->objects,
> > +						      NULL, promised_object_cb,
> > +						      NULL, &opts);
> > +			if (err < 0)
> > +				break;
> > +			if (err > 0) {
> > +				err = 0;
> > +				continue;
> >  			}
> 
> So we used to manually iterate and stop when we have a matching pack
> entry, but now "stop when we find" is done by promisor_object_cb
> callback that returns 1.
> 
> What is the reason why we no longer odb_(re)prepare() upfront before
> going into the loop?  Would it make us miss a newly added promisor
> packs?  We will fall back to rev-list for correctness, so it may not
> matter, though.
So yes, this is a bug.
Thanks!
Patrick
Patrick SteinhardtJun 24, 2026, 09:33 UTC in reply to Christian Couder on lore

Re: [PATCH 3/3] connected: search promisor objects generically

On Tue, Jun 23, 2026 at 09:45:44AM +0200, Christian Couder wrote:
Show 24 quoted lines
> On Mon, Jun 22, 2026 at 10:50 AM Patrick Steinhardt <ps@pks.im> wrote:
> > diff --git a/connected.c b/connected.c
> > index 7e26976832..9a666f0cdf 100644
> > --- a/connected.c
> > +++ b/connected.c
> > @@ -54,31 +66,30 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
> >                  * object is a promisor object. Instead, just make sure we
> >                  * received, in a promisor packfile, the objects pointed to by
> >                  * each wanted ref.
> > -                *
> > -                * Before checking for promisor packs, be sure we have the
> > -                * latest pack-files loaded into memory.
> >                  */
> > -               odb_reprepare(the_repository->objects);
> 
> Like Junio, I am not sure it's correct to remove the
> `odb_reprepare(the_repository->objects)` call.
> 
> I think it was added for good reasons in b739d971 (connected.c:
> reprepare packs for corner cases, 2020-03-13) and I am not sure
> odb_for_each_object_ext() is performing something similar.
> 
> At least the commit message should mention this change and explain a
> bit why the reasons the call was added are not valid anymore.

Yeah, I think you're both correct. The only explanation I have is that I might have repeatedly misread this as `odb_prepare_alternates()`, which is something we often call before suck loops.

Show 39 quoted lines
> >                 do {
> > -                       struct packed_git *p;
> > -
> > -                       repo_for_each_pack(the_repository, p) {
> > -                               if (!p->pack_promisor)
> > -                                       continue;
> > -                               if (find_pack_entry_one(oid, p))
> > -                                       goto promisor_pack_found;
> > +                       opts.prefix = oid;
> > +
> > +                       err = odb_for_each_object_ext(the_repository->objects,
> > +                                                     NULL, promised_object_cb,
> > +                                                     NULL, &opts);
> > +                       if (err < 0)
> > +                               break;
> > +                       if (err > 0) {
> > +                               err = 0;
> > +                               continue;
> >                         }
> > +
> >                         /*
> >                          * Fallback to rev-list with oid and the rest of the
> >                          * object IDs provided by fn.
> >                          */
> >                         goto no_promisor_pack_found;
> > -promisor_pack_found:
> > -                       ;
> >                 } while ((oid = fn(cb_data)) != NULL);
> > +
> >                 if (opt->err_fd)
> >                         close(opt->err_fd);
> > -               return 0;
> > +               return err;
> >         }
> >
> >  no_promisor_pack_found:
> 
> These changes are difficult to understand as there are a number of
> `goto`, `break`, `return`, etc involved.
Yeah, agreed. I had my issues understanding this logic, too.
Show 6 quoted lines
> I think it comes in the first place from check_connected() doing too
> many things, and adding a preparatory commit to refactor it would
> help.
> 
> For example the preparatory commit could move a lot of code from
> check_connected() to the following new functions:
I'll give that a try, thanks!
Patrick
Patrick SteinhardtJun 24, 2026, 10:37 UTC in reply to Patrick Steinhardt on lore

[PATCH v2 0/4] connected: search promisor objects generically

Hi,

this patch series refactors "connected.c" so that we search for promisor objects in a generic way instead of reaching into internal of the object database. As a result, the connectivity checks will work properly in repos that don't use packfiles in the first place.

The series is built on top of 8d96f09e92 (Merge branch 'js/objects-larger-than-4gb-on-windows', 2026-06-19) with ps/odb-source-packed at 1bba3c035d (odb/source-packed: drop pointer to "files" parent source, 2026-06-17) merged into it.

Changes in v2:
  - Fix the accidentally-dropped call to `odb_reprepare()`.
  - Add a preparatory commit that splits out `check_connected_promisor()`.
    I think also splitting out `check_connected_rev_list()` would only
    have diminishing returns, so I skipped that part.
  - Link to v1: https://patch.msgid.link/20260622-pks-connected-generic-promisor-checks-v1-0-25eba2698202@pks.im
Thanks!
Patrick
---
Patrick Steinhardt (4):
      odb/source-packed: extract logic to skip certain packs
      odb/source-packed: support flags when iterating an object prefix
      connected: split out promisor-based connectivity check
      connected: search promisor objects generically
 connected.c         | 95 ++++++++++++++++++++++++++++++++++-------------------
 odb/source-packed.c | 50 ++++++++++++++++++++--------
 2 files changed, 98 insertions(+), 47 deletions(-)
Range-diff versus v1:

1: 6ff1fc8d89 = 1: a1a1af0fc6 odb/source-packed: extract logic to skip certain packs 2: 1022a1fdcc = 2: bd81a9e478 odb/source-packed: support flags when iterating an object prefix 3: 102fab7df2 < -: ---------- connected: search promisor objects generically -: ---------- > 3: f39ef68c3e connected: split out promisor-based connectivity check -: ---------- > 4: 558f30a6f2 connected: search promisor objects generically

--- base-commit: 4a8e7a446f41435e157131162dfe901eca9250fe change-id: 20260612-pks-connected-generic-promisor-checks-2933bff3028d

Patrick SteinhardtJun 24, 2026, 10:37 UTC in reply to Patrick Steinhardt on lore

[PATCH v2 1/4] odb/source-packed: extract logic to skip certain packs

The caller can pass flags that allow them to filter out specific kinds of objects when iterating objects via `odb_for_each_object()`. This only works for "normal" iteration though, as we `BUG()` when the user passes flags and specifies an object prefix.

This limitation will be lifted in the next commit. Prepare for this by extracting the logic that skips certain kinds of packs so that we can easily reuse it.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb/source-packed.c | 28 ++++++++++++++++++----------
 1 file changed, 18 insertions(+), 10 deletions(-)
Show changes to odb/source-packed.c +18 −10
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 42c28fba0e..3afc4bf01f 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -126,6 +126,22 @@ static int match_hash(unsigned len, const unsigned char *a, const unsigned char
 	return 1;
 }
 
+static bool should_exclude_pack(struct packed_git *p, enum odb_for_each_object_flags flags)
+{
+	if ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)
+		return true;
+	if ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&
+	    !p->pack_promisor)
+		return true;
+	if ((flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&
+	    p->pack_keep_in_core)
+		return true;
+	if ((flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&
+	    p->pack_keep)
+		return true;
+	return false;
+}
+
 static int for_each_prefixed_object_in_midx(
 	struct odb_source_packed *store,
 	struct multi_pack_index *m,
@@ -306,17 +322,9 @@ static int odb_source_packed_for_each_object(struct odb_source *source,
 	for (e = packfile_store_get_packs(packed); e; e = e->next) {
 		struct packed_git *p = e->pack;
 
-		if ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)
-			continue;
-		if ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&
-		    !p->pack_promisor)
-			continue;
-		if ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&
-		    p->pack_keep_in_core)
-			continue;
-		if ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&
-		    p->pack_keep)
+		if (should_exclude_pack(p, opts->flags))
 			continue;
+
 		if (open_pack_index(p)) {
 			pack_errors = 1;
 			continue;
-- 
2.55.0.rc1.745.g43192e7977.dirty
Patrick SteinhardtJun 24, 2026, 10:37 UTC in reply to Patrick Steinhardt on lore

[PATCH v2 2/4] odb/source-packed: support flags when iterating an object prefix

Callers of `odb_for_each_object()` can specify an optional object name prefix so that we only yield objects that match it. This is incompatible though with passing flags at the same time, as we don't yet know to handle them.

Loosen this restriction by calling `should_exclude_pack()`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb/source-packed.c | 22 +++++++++++++++++++---
 1 file changed, 19 insertions(+), 3 deletions(-)
Show changes to odb/source-packed.c +19 −3
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 3afc4bf01f..6f31f0ff94 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -148,6 +148,7 @@ static int for_each_prefixed_object_in_midx(
 	const struct odb_for_each_object_options *opts,
 	struct odb_source_packed_for_each_object_wrapper_data *data)
 {
+	bool pack_errors = false;
 	int ret;
 
 	for (; m; m = m->base_midx) {
@@ -171,6 +172,20 @@ static int for_each_prefixed_object_in_midx(
 			const struct object_id *current = NULL;
 			struct object_id oid;
 
+			if (opts->flags) {
+				uint32_t pack_id = nth_midxed_pack_int_id(m, i);
+				struct packed_git *pack;
+
+				if (prepare_midx_pack(m, pack_id)) {
+					pack_errors = true;
+					continue;
+				}
+
+				pack = nth_midxed_pack(m, pack_id);
+				if (should_exclude_pack(pack, opts->flags))
+					continue;
+			}
+
 			current = nth_midxed_object_oid(&oid, m, i);
 
 			if (!match_hash(len, opts->prefix->hash, current->hash))
@@ -198,6 +213,8 @@ static int for_each_prefixed_object_in_midx(
 	ret = 0;
 
 out:
+	if (!ret && pack_errors)
+		ret = -1;
 	return ret;
 }
 
@@ -260,9 +277,6 @@ static int odb_source_packed_for_each_prefixed_object(
 	bool pack_errors = false;
 	int ret;
 
-	if (opts->flags)
-		BUG("flags unsupported");
-
 	store->skip_mru_updates = true;
 
 	m = get_multi_pack_index(store);
@@ -275,6 +289,8 @@ static int odb_source_packed_for_each_prefixed_object(
 	for (e = packfile_store_get_packs(store); e; e = e->next) {
 		if (e->pack->multi_pack_index)
 			continue;
+		if (should_exclude_pack(e->pack, opts->flags))
+			continue;
 
 		if (open_pack_index(e->pack)) {
 			pack_errors = true;
-- 
2.55.0.rc1.745.g43192e7977.dirty
Patrick SteinhardtJun 24, 2026, 10:37 UTC in reply to Patrick Steinhardt on lore

[PATCH v2 3/4] connected: split out promisor-based connectivity check

When performing a connectivity check in a partial clone we try to avoid doing the connectivity check by checking whether all new tips are part of a promisor pack. This makes use of the fact that we don't expect full connectivity for promised objects anyway, so it's basically fine if those objects are not fully connected.

The logic that handles this promisor-based check is somewhat hard to read though as it uses nested loops and gotos. Pull it out into a standalone function, which makes it a bit easier to reason about.

We'll also further simplify the function in the next commit.
Suggested-by: Christian Couder <christian.couder@gmail.com>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 connected.c | 85 ++++++++++++++++++++++++++++++++++++-------------------------
 1 file changed, 51 insertions(+), 34 deletions(-)
Show changes to connected.c +51 −34
diff --git a/connected.c b/connected.c
index 7e26976832..d2b334173f 100644
--- a/connected.c
+++ b/connected.c
@@ -11,6 +11,49 @@
 #include "packfile.h"
 #include "promisor-remote.h"
 
+/*
+ * For partial clones, we don't want to have to do a regular connectivity check
+ * because we have to enumerate and exclude all promisor objects (slow), and
+ * then the connectivity check itself becomes a no-op because in a partial
+ * clone every object is a promisor object. Instead, just make sure we
+ * received, in a promisor packfile, the objects pointed to by each wanted ref.
+ *
+ * Before checking for promisor packs, be sure we have the latest pack-files
+ * loaded into memory.
+ *
+ * Returns 1 when all object IDs have been found in promisor packs, in which
+ * case we're fully connected and thus done. Returns 0 when we have found
+ * objects in non-promisor packs, in which case we'll have to fall back to the
+ * rev-list-based connectivity checks. Returns a negative error code on error.
+ */
+static int check_connected_promisor(oid_iterate_fn fn,
+				    void *cb_data,
+				    const struct object_id **oid)
+{
+	odb_reprepare(the_repository->objects);
+	do {
+		struct packed_git *p;
+
+		repo_for_each_pack(the_repository, p) {
+			if (!p->pack_promisor)
+				continue;
+			if (find_pack_entry_one(*oid, p))
+				goto promisor_pack_found;
+		}
+
+		/*
+		 * We have found an object that is not part of a promisor pack,
+		 * and thus we cannot skip the full connectivity check.
+		 */
+		return 0;
+
+promisor_pack_found:
+		;
+	} while ((*oid = fn(cb_data)) != NULL);
+
+	return 1;
+}
+
 /*
  * If we feed all the commits we want to verify to this command
  *
@@ -46,42 +89,16 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
 	}
 
 	if (repo_has_promisor_remote(the_repository)) {
-		/*
-		 * For partial clones, we don't want to have to do a regular
-		 * connectivity check because we have to enumerate and exclude
-		 * all promisor objects (slow), and then the connectivity check
-		 * itself becomes a no-op because in a partial clone every
-		 * object is a promisor object. Instead, just make sure we
-		 * received, in a promisor packfile, the objects pointed to by
-		 * each wanted ref.
-		 *
-		 * Before checking for promisor packs, be sure we have the
-		 * latest pack-files loaded into memory.
-		 */
-		odb_reprepare(the_repository->objects);
-		do {
-			struct packed_git *p;
-
-			repo_for_each_pack(the_repository, p) {
-				if (!p->pack_promisor)
-					continue;
-				if (find_pack_entry_one(oid, p))
-					goto promisor_pack_found;
-			}
-			/*
-			 * Fallback to rev-list with oid and the rest of the
-			 * object IDs provided by fn.
-			 */
-			goto no_promisor_pack_found;
-promisor_pack_found:
-			;
-		} while ((oid = fn(cb_data)) != NULL);
-		if (opt->err_fd)
-			close(opt->err_fd);
-		return 0;
+		err = check_connected_promisor(fn, cb_data, &oid);
+		if (err) {
+			if (opt->err_fd)
+				close(opt->err_fd);
+			if (err > 0)
+				err = 0;
+			return err;
+		}
 	}
 
-no_promisor_pack_found:
 	if (opt->shallow_file) {
 		strvec_push(&rev_list.args, "--shallow-file");
 		strvec_push(&rev_list.args, opt->shallow_file);
-- 
2.55.0.rc1.745.g43192e7977.dirty
Patrick SteinhardtJun 24, 2026, 10:37 UTC in reply to Patrick Steinhardt on lore

[PATCH v2 4/4] connected: search promisor objects generically

When performing connectivity checks we have to figure out whether any of the new objects are promisor objects, as we cannot assume full connectivity if so.

This check is performed by iterating through all packfiles in the repository and searching each of them for the given object. Of course, this mechanism is quite specific to implementation details of the object database, as we assume that it uses packfiles in the first place.

Refactor the logic so that we instead use `odb_for_each_object_ext()` with an object prefix filter and the `ODB_FOR_EACH_OBJECT_PROMISOR_ONLY` flag. This will yield all objects that have the exact object name and that are part of a promisor pack in a generic way.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 connected.c | 32 +++++++++++++++++++++-----------
 1 file changed, 21 insertions(+), 11 deletions(-)
Show changes to connected.c +21 −11
diff --git a/connected.c b/connected.c
index d2b334173f..b557ff5db9 100644
--- a/connected.c
+++ b/connected.c
@@ -11,6 +11,13 @@
 #include "packfile.h"
 #include "promisor-remote.h"
 
+static int promised_object_cb(const struct object_id *oid UNUSED,
+			      struct object_info *oi UNUSED,
+			      void *payload UNUSED)
+{
+	return 1;
+}
+
 /*
  * For partial clones, we don't want to have to do a regular connectivity check
  * because we have to enumerate and exclude all promisor objects (slow), and
@@ -30,25 +37,28 @@ static int check_connected_promisor(oid_iterate_fn fn,
 				    void *cb_data,
 				    const struct object_id **oid)
 {
+	struct odb_for_each_object_options opts = {
+		.flags = ODB_FOR_EACH_OBJECT_PROMISOR_ONLY,
+		.prefix_hex_len = the_repository->hash_algo->hexsz,
+	};
+	int err;
+
 	odb_reprepare(the_repository->objects);
 	do {
-		struct packed_git *p;
+		opts.prefix = *oid;
 
-		repo_for_each_pack(the_repository, p) {
-			if (!p->pack_promisor)
-				continue;
-			if (find_pack_entry_one(*oid, p))
-				goto promisor_pack_found;
-		}
+		err = odb_for_each_object_ext(the_repository->objects,
+					      NULL, promised_object_cb,
+					      NULL, &opts);
+		if (err < 0)
+			return err;
 
 		/*
 		 * We have found an object that is not part of a promisor pack,
 		 * and thus we cannot skip the full connectivity check.
 		 */
-		return 0;
-
-promisor_pack_found:
-		;
+		if (err > 0)
+			return 0;
 	} while ((*oid = fn(cb_data)) != NULL);
 
 	return 1;
-- 
2.55.0.rc1.745.g43192e7977.dirty
Junio C HamanoJun 24, 2026, 16:27 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v2 4/4] connected: search promisor objects generically

Patrick Steinhardt <ps@pks.im> writes:
Show 61 quoted lines
> When performing connectivity checks we have to figure out whether any of
> the new objects are promisor objects, as we cannot assume full
> connectivity if so.
>
> This check is performed by iterating through all packfiles in the
> repository and searching each of them for the given object. Of course,
> this mechanism is quite specific to implementation details of the object
> database, as we assume that it uses packfiles in the first place.
>
> Refactor the logic so that we instead use `odb_for_each_object_ext()`
> with an object prefix filter and the `ODB_FOR_EACH_OBJECT_PROMISOR_ONLY`
> flag. This will yield all objects that have the exact object name and
> that are part of a promisor pack in a generic way.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  connected.c | 32 +++++++++++++++++++++-----------
>  1 file changed, 21 insertions(+), 11 deletions(-)
>
> diff --git a/connected.c b/connected.c
> index d2b334173f..b557ff5db9 100644
> --- a/connected.c
> +++ b/connected.c
> @@ -11,6 +11,13 @@
>  #include "packfile.h"
>  #include "promisor-remote.h"
>  
> +static int promised_object_cb(const struct object_id *oid UNUSED,
> +			      struct object_info *oi UNUSED,
> +			      void *payload UNUSED)
> +{
> +	return 1;
> +}
> +
>  /*
>   * For partial clones, we don't want to have to do a regular connectivity check
>   * because we have to enumerate and exclude all promisor objects (slow), and
> @@ -30,25 +37,28 @@ static int check_connected_promisor(oid_iterate_fn fn,
>  				    void *cb_data,
>  				    const struct object_id **oid)
>  {
> +	struct odb_for_each_object_options opts = {
> +		.flags = ODB_FOR_EACH_OBJECT_PROMISOR_ONLY,
> +		.prefix_hex_len = the_repository->hash_algo->hexsz,
> +	};
> +	int err;
> +
>  	odb_reprepare(the_repository->objects);
>  	do {
> -		struct packed_git *p;
> +		opts.prefix = *oid;
>  
> -		repo_for_each_pack(the_repository, p) {
> -			if (!p->pack_promisor)
> -				continue;
> -			if (find_pack_entry_one(*oid, p))
> -				goto promisor_pack_found;
> -		}
> +		err = odb_for_each_object_ext(the_repository->objects,
> +					      NULL, promised_object_cb,
> +					      NULL, &opts);

promised_object_cb() returns 1 without any computation since we are only interested in learning ODB_FOR_EACH_OBJECT_PROMISOR_ONLY finds any such object.

odb_for_each_object_ext() returns 0 (if it iterates all the sources to the end), but if its call to odb_source_for_each_object() yields non-zero value, the returned value comes back as "err" here, terminating the for-each iteration immediately.

odb_source_for_each_object() is implemented differently per the source backend, but taking an example of "packfile" backend, packfile_loose_for_each_object() ends up calling cb (wrapped in packfile_store_for_each_object_wrapper_data) via for_each_object_in_pack(), which stops immediately when cb returns non-zero and the value returned from there is the value given by cb, i.e., 1. So we will have err==1 when we find any object.

> +		if (err < 0)
> +			return err;
And err presumably is 1 in such a case, so this does not trigger.
Show 10 quoted lines
>  		/*
>  		 * We have found an object that is not part of a promisor pack,
>  		 * and thus we cannot skip the full connectivity check.
>  		 */
> -		return 0;
> -
> -promisor_pack_found:
> -		;
> +		if (err > 0)
> +			return 0;
And this does.

I may be misreading the patch, but as we return 0 from here, do we cause the caller to fall back to full connectivity check? The caller, check_connected(), sees a zero returned from here.

>  	} while ((*oid = fn(cb_data)) != NULL);
>  
>  	return 1;
Christian CouderJun 24, 2026, 17:02 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v2 2/4] odb/source-packed: support flags when iterating an object prefix

On Wed, Jun 24, 2026 at 12:37 PM Patrick Steinhardt <ps@pks.im> wrote:
Show 46 quoted lines
>
> Callers of `odb_for_each_object()` can specify an optional object name
> prefix so that we only yield objects that match it. This is incompatible
> though with passing flags at the same time, as we don't yet know to
> handle them.
>
> Loosen this restriction by calling `should_exclude_pack()`.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  odb/source-packed.c | 22 +++++++++++++++++++---
>  1 file changed, 19 insertions(+), 3 deletions(-)
>
> diff --git a/odb/source-packed.c b/odb/source-packed.c
> index 3afc4bf01f..6f31f0ff94 100644
> --- a/odb/source-packed.c
> +++ b/odb/source-packed.c
> @@ -148,6 +148,7 @@ static int for_each_prefixed_object_in_midx(
>         const struct odb_for_each_object_options *opts,
>         struct odb_source_packed_for_each_object_wrapper_data *data)
>  {
> +       bool pack_errors = false;
>         int ret;
>
>         for (; m; m = m->base_midx) {
> @@ -171,6 +172,20 @@ static int for_each_prefixed_object_in_midx(
>                         const struct object_id *current = NULL;
>                         struct object_id oid;
>
> +                       if (opts->flags) {
> +                               uint32_t pack_id = nth_midxed_pack_int_id(m, i);
> +                               struct packed_git *pack;
> +
> +                               if (prepare_midx_pack(m, pack_id)) {
> +                                       pack_errors = true;
> +                                       continue;
> +                               }
> +
> +                               pack = nth_midxed_pack(m, pack_id);
> +                               if (should_exclude_pack(pack, opts->flags))
> +                                       continue;
> +                       }
> +
>                         current = nth_midxed_object_oid(&oid, m, i);
>
>                         if (!match_hash(len, opts->prefix->hash, current->hash))
It looks like this is:
                        if (!match_hash(len, opts->prefix->hash, current->hash))
                                break;

and I wonder if the `if (opts->flags) { ... }` block would be better after that prefix check rather than before it.

Putting it after the prefix check would make sure we don't continue when the prefix doesn't match.

Patrick SteinhardtJun 25, 2026, 05:52 UTC in reply to Christian Couder on lore

Re: [PATCH v2 2/4] odb/source-packed: support flags when iterating an object prefix

On Wed, Jun 24, 2026 at 07:02:48PM +0200, Christian Couder wrote:
Show 37 quoted lines
> On Wed, Jun 24, 2026 at 12:37 PM Patrick Steinhardt <ps@pks.im> wrote:
> > diff --git a/odb/source-packed.c b/odb/source-packed.c
> > index 3afc4bf01f..6f31f0ff94 100644
> > --- a/odb/source-packed.c
> > +++ b/odb/source-packed.c
> > @@ -171,6 +172,20 @@ static int for_each_prefixed_object_in_midx(
> >                         const struct object_id *current = NULL;
> >                         struct object_id oid;
> >
> > +                       if (opts->flags) {
> > +                               uint32_t pack_id = nth_midxed_pack_int_id(m, i);
> > +                               struct packed_git *pack;
> > +
> > +                               if (prepare_midx_pack(m, pack_id)) {
> > +                                       pack_errors = true;
> > +                                       continue;
> > +                               }
> > +
> > +                               pack = nth_midxed_pack(m, pack_id);
> > +                               if (should_exclude_pack(pack, opts->flags))
> > +                                       continue;
> > +                       }
> > +
> >                         current = nth_midxed_object_oid(&oid, m, i);
> >
> >                         if (!match_hash(len, opts->prefix->hash, current->hash))
> 
> It looks like this is:
> 
>                         if (!match_hash(len, opts->prefix->hash, current->hash))
>                                 break;
> 
> and I wonder if the `if (opts->flags) { ... }` block would be better
> after that prefix check rather than before it.
> 
> Putting it after the prefix check would make sure we don't continue
> when the prefix doesn't match.
Hm, that's a good point indeed. Will adapt, thanks!
Patrick
Patrick SteinhardtJun 25, 2026, 05:54 UTC in reply to Junio C Hamano on lore

Re: [PATCH v2 4/4] connected: search promisor objects generically

On Wed, Jun 24, 2026 at 09:27:56AM -0700, Junio C Hamano wrote:
Show 82 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/connected.c b/connected.c
> > index d2b334173f..b557ff5db9 100644
> > --- a/connected.c
> > +++ b/connected.c
> > @@ -11,6 +11,13 @@
> >  #include "packfile.h"
> >  #include "promisor-remote.h"
> >  
> > +static int promised_object_cb(const struct object_id *oid UNUSED,
> > +			      struct object_info *oi UNUSED,
> > +			      void *payload UNUSED)
> > +{
> > +	return 1;
> > +}
> > +
> >  /*
> >   * For partial clones, we don't want to have to do a regular connectivity check
> >   * because we have to enumerate and exclude all promisor objects (slow), and
> > @@ -30,25 +37,28 @@ static int check_connected_promisor(oid_iterate_fn fn,
> >  				    void *cb_data,
> >  				    const struct object_id **oid)
> >  {
> > +	struct odb_for_each_object_options opts = {
> > +		.flags = ODB_FOR_EACH_OBJECT_PROMISOR_ONLY,
> > +		.prefix_hex_len = the_repository->hash_algo->hexsz,
> > +	};
> > +	int err;
> > +
> >  	odb_reprepare(the_repository->objects);
> >  	do {
> > -		struct packed_git *p;
> > +		opts.prefix = *oid;
> >  
> > -		repo_for_each_pack(the_repository, p) {
> > -			if (!p->pack_promisor)
> > -				continue;
> > -			if (find_pack_entry_one(*oid, p))
> > -				goto promisor_pack_found;
> > -		}
> > +		err = odb_for_each_object_ext(the_repository->objects,
> > +					      NULL, promised_object_cb,
> > +					      NULL, &opts);
> 
> promised_object_cb() returns 1 without any computation since we are
> only interested in learning ODB_FOR_EACH_OBJECT_PROMISOR_ONLY finds
> any such object.
> 
> odb_for_each_object_ext() returns 0 (if it iterates all the sources
> to the end), but if its call to odb_source_for_each_object() yields
> non-zero value, the returned value comes back as "err" here,
> terminating the for-each iteration immediately.
> 
> odb_source_for_each_object() is implemented differently per the
> source backend, but taking an example of "packfile" backend,
> packfile_loose_for_each_object() ends up calling cb (wrapped in
> packfile_store_for_each_object_wrapper_data) via
> for_each_object_in_pack(), which stops immediately when cb returns
> non-zero and the value returned from there is the value given by cb,
> i.e., 1.  So we will have err==1 when we find any object.
> 
> > +		if (err < 0)
> > +			return err;
> 
> And err presumably is 1 in such a case, so this does not trigger.
> 
> >  		/*
> >  		 * We have found an object that is not part of a promisor pack,
> >  		 * and thus we cannot skip the full connectivity check.
> >  		 */
> > -		return 0;
> > -
> > -promisor_pack_found:
> > -		;
> > +		if (err > 0)
> > +			return 0;
> 
> And this does.
> 
> I may be misreading the patch, but as we return 0 from here, do we
> cause the caller to fall back to full connectivity check?  The
> caller, check_connected(), sees a zero returned from here.

You're right, this is a result of the refactor. Previously we had it like this:

    err = odb_for_each_object_ext(the_repository->objects,
                              NULL, promised_object_cb,
                              NULL, &opts);
    if (err < 0)
            break;
    if (err > 0) {
            err = 0;
            continue;
    }

But that made us correctly skip to the next object. Now though we have to check for `if (!err) return 0;` in the refactored code. Makes me wonder whether the logic would be easier to follow like this:

Show changes to connected.c +7 −3
diff --git a/connected.c b/connected.c
index b557ff5db9..b5a9b0543d 100644
--- a/connected.c
+++ b/connected.c
@@ -13,8 +13,10 @@
 
 static int promised_object_cb(const struct object_id *oid UNUSED,
 			      struct object_info *oi UNUSED,
-			      void *payload UNUSED)
+			      void *payload)
 {
+	bool *found = payload;
+	*found = true;
 	return 1;
 }
 
@@ -45,11 +47,13 @@ static int check_connected_promisor(oid_iterate_fn fn,
 
 	odb_reprepare(the_repository->objects);
 	do {
+		bool found = false;
+
 		opts.prefix = *oid;
 
 		err = odb_for_each_object_ext(the_repository->objects,
 					      NULL, promised_object_cb,
-					      NULL, &opts);
+					      &found, &opts);
 		if (err < 0)
 			return err;
 
@@ -57,7 +61,7 @@ static int check_connected_promisor(oid_iterate_fn fn,
 		 * We have found an object that is not part of a promisor pack,
 		 * and thus we cannot skip the full connectivity check.
 		 */
-		if (err > 0)
+		if (!found)
 			return 0;
 	} while ((*oid = fn(cb_data)) != NULL);
 

It's also a bit concerning that this doesn't cause any tests to fail.
I'll try to figure out whether I can add one.

Thanks!

Patrick
Patrick SteinhardtJun 25, 2026, 09:57 UTC in reply to Patrick Steinhardt on lore

[PATCH v3 0/4] connected: search promisor objects generically

Hi,

this patch series refactors "connected.c" so that we search for promisor objects in a generic way instead of reaching into internal of the object database. As a result, the connectivity checks will work properly in repos that don't use packfiles in the first place.

The series is built on top of 8d96f09e92 (Merge branch 'js/objects-larger-than-4gb-on-windows', 2026-06-19) with ps/odb-source-packed at 1bba3c035d (odb/source-packed: drop pointer to "files" parent source, 2026-06-17) merged into it.

Changes in v3:
  - Fix reversed logic for whether the promised object was found, which
    broke in v2.
  - Add a test that verifies that we indeed use the optimized check.
  - Match the hash before computing the flags so that we break out of
    the loop more eagerly.
  - Link to v2: https://patch.msgid.link/20260624-pks-connected-generic-promisor-checks-v2-0-132d73ee47b9@pks.im
Changes in v2:
  - Fix the accidentally-dropped call to `odb_reprepare()`.
  - Add a preparatory commit that splits out `check_connected_promisor()`.
    I think also splitting out `check_connected_rev_list()` would only
    have diminishing returns, so I skipped that part.
  - Link to v1: https://patch.msgid.link/20260622-pks-connected-generic-promisor-checks-v1-0-25eba2698202@pks.im
Thanks!
Patrick
---
Patrick Steinhardt (4):
      odb/source-packed: extract logic to skip certain packs
      odb/source-packed: support flags when iterating an object prefix
      connected: split out promisor-based connectivity check
      connected: search promisor objects generically
 connected.c              | 98 +++++++++++++++++++++++++++++++-----------------
 odb/source-packed.c      | 50 +++++++++++++++++-------
 t/t5616-partial-clone.sh | 24 ++++++++++++
 3 files changed, 125 insertions(+), 47 deletions(-)
Range-diff versus v2:
1:  74d1d04183 = 1:  93b7b3b4cb odb/source-packed: extract logic to skip certain packs
2:  02aa39bf1e ! 2:  3fd0885b85 odb/source-packed: support flags when iterating an object prefix
    @@ odb/source-packed.c: static int for_each_prefixed_object_in_midx(
      
      	for (; m; m = m->base_midx) {
     @@ odb/source-packed.c: static int for_each_prefixed_object_in_midx(
    - 			const struct object_id *current = NULL;
    - 			struct object_id oid;
    + 			if (!match_hash(len, opts->prefix->hash, current->hash))
    + 				break;
      
     +			if (opts->flags) {
     +				uint32_t pack_id = nth_midxed_pack_int_id(m, i);
    @@ odb/source-packed.c: static int for_each_prefixed_object_in_midx(
     +					continue;
     +			}
     +
    - 			current = nth_midxed_object_oid(&oid, m, i);
    + 			if (data->request) {
    + 				struct object_info oi = *data->request;
      
    - 			if (!match_hash(len, opts->prefix->hash, current->hash))
     @@ odb/source-packed.c: static int for_each_prefixed_object_in_midx(
      	ret = 0;
      
3:  ff9df84f65 = 3:  47a4732daf connected: split out promisor-based connectivity check
4:  a10d2e6a1e ! 4:  239abf2731 connected: search promisor objects generically
    @@ Commit message
         flag. This will yield all objects that have the exact object name and
         that are part of a promisor pack in a generic way.
     
    +    Add a test to verify that we indeed use the optimization.
    +
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
     
      ## connected.c ##
    @@ connected.c
      
     +static int promised_object_cb(const struct object_id *oid UNUSED,
     +			      struct object_info *oi UNUSED,
    -+			      void *payload UNUSED)
    ++			      void *payload)
     +{
    ++	bool *found = payload;
    ++	*found = true;
     +	return 1;
     +}
     +
    @@ connected.c: static int check_connected_promisor(oid_iterate_fn fn,
      	odb_reprepare(the_repository->objects);
      	do {
     -		struct packed_git *p;
    -+		opts.prefix = *oid;
    ++		bool found = false;
      
     -		repo_for_each_pack(the_repository, p) {
     -			if (!p->pack_promisor)
    @@ connected.c: static int check_connected_promisor(oid_iterate_fn fn,
     -			if (find_pack_entry_one(*oid, p))
     -				goto promisor_pack_found;
     -		}
    -+		err = odb_for_each_object_ext(the_repository->objects,
    -+					      NULL, promised_object_cb,
    -+					      NULL, &opts);
    ++		opts.prefix = *oid;
    ++
    ++		err = odb_for_each_object_ext(the_repository->objects, NULL,
    ++					      promised_object_cb, &found, &opts);
     +		if (err < 0)
     +			return err;
      
    @@ connected.c: static int check_connected_promisor(oid_iterate_fn fn,
     -
     -promisor_pack_found:
     -		;
    -+		if (err > 0)
    ++		if (!found)
     +			return 0;
      	} while ((*oid = fn(cb_data)) != NULL);
      
      	return 1;
    +
    + ## t/t5616-partial-clone.sh ##
    +@@ t/t5616-partial-clone.sh: test_expect_success 'partial fetch inherits filter settings' '
    + 	test_line_count = 5 observed
    + '
    + 
    ++test_expect_success 'partial fetch does not spawn rev-list connectivity check' '
    ++	test_when_finished "rm -rf connectivity-remote connectivity-client" &&
    ++	git init connectivity-remote &&
    ++	test_commit -C connectivity-remote one &&
    ++	git -C connectivity-remote config uploadpack.allowfilter 1 &&
    ++	git -C connectivity-remote config uploadpack.allowanysha1inwant 1 &&
    ++
    ++	git clone --no-checkout --filter=blob:none \
    ++		"file://$(pwd)/connectivity-remote" connectivity-client &&
    ++
    ++	# When doing a partial fetch where all tips are part of a promisor pack
    ++	# we want to skip the connectivity check, as these objects are allowed
    ++	# to not be fully connected.
    ++	test_commit -C connectivity-remote two &&
    ++	GIT_TRACE2_EVENT="$(pwd)/partial.trace" git -C connectivity-client fetch origin &&
    ++	test_subcommand_flex ! git rev-list --objects --stdin <partial.trace &&
    ++
    ++	# Otherwise, when doing a fetch where any of the tips is not part of a
    ++	# promisor pack, then we must run the connectivity check.
    ++	test_commit -C connectivity-remote three &&
    ++	GIT_TRACE2_EVENT="$(pwd)/full.trace" git -C connectivity-client fetch --no-filter origin &&
    ++	test_subcommand_flex git rev-list --objects --stdin <full.trace
    ++'
    ++
    + # force dynamic object fetch using diff.
    + # we should only get 1 new blob (for the file in origin/main).
    + test_expect_success 'verify diff causes dynamic object fetch' '

--- base-commit: 4a8e7a446f41435e157131162dfe901eca9250fe change-id: 20260612-pks-connected-generic-promisor-checks-2933bff3028d

Patrick SteinhardtJun 25, 2026, 09:57 UTC in reply to Patrick Steinhardt on lore

[PATCH v3 1/4] odb/source-packed: extract logic to skip certain packs

The caller can pass flags that allow them to filter out specific kinds of objects when iterating objects via `odb_for_each_object()`. This only works for "normal" iteration though, as we `BUG()` when the user passes flags and specifies an object prefix.

This limitation will be lifted in the next commit. Prepare for this by extracting the logic that skips certain kinds of packs so that we can easily reuse it.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb/source-packed.c | 28 ++++++++++++++++++----------
 1 file changed, 18 insertions(+), 10 deletions(-)
Show changes to odb/source-packed.c +18 −10
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 42c28fba0e..3afc4bf01f 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -126,6 +126,22 @@ static int match_hash(unsigned len, const unsigned char *a, const unsigned char
 	return 1;
 }
 
+static bool should_exclude_pack(struct packed_git *p, enum odb_for_each_object_flags flags)
+{
+	if ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)
+		return true;
+	if ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&
+	    !p->pack_promisor)
+		return true;
+	if ((flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&
+	    p->pack_keep_in_core)
+		return true;
+	if ((flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&
+	    p->pack_keep)
+		return true;
+	return false;
+}
+
 static int for_each_prefixed_object_in_midx(
 	struct odb_source_packed *store,
 	struct multi_pack_index *m,
@@ -306,17 +322,9 @@ static int odb_source_packed_for_each_object(struct odb_source *source,
 	for (e = packfile_store_get_packs(packed); e; e = e->next) {
 		struct packed_git *p = e->pack;
 
-		if ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)
-			continue;
-		if ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&
-		    !p->pack_promisor)
-			continue;
-		if ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&
-		    p->pack_keep_in_core)
-			continue;
-		if ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&
-		    p->pack_keep)
+		if (should_exclude_pack(p, opts->flags))
 			continue;
+
 		if (open_pack_index(p)) {
 			pack_errors = 1;
 			continue;
-- 
2.55.0.rc1.745.g43192e7977.dirty
Patrick SteinhardtJun 25, 2026, 09:57 UTC in reply to Patrick Steinhardt on lore

[PATCH v3 2/4] odb/source-packed: support flags when iterating an object prefix

Callers of `odb_for_each_object()` can specify an optional object name prefix so that we only yield objects that match it. This is incompatible though with passing flags at the same time, as we don't yet know to handle them.

Loosen this restriction by calling `should_exclude_pack()`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb/source-packed.c | 22 +++++++++++++++++++---
 1 file changed, 19 insertions(+), 3 deletions(-)
Show changes to odb/source-packed.c +19 −3
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 3afc4bf01f..96fc436770 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -148,6 +148,7 @@ static int for_each_prefixed_object_in_midx(
 	const struct odb_for_each_object_options *opts,
 	struct odb_source_packed_for_each_object_wrapper_data *data)
 {
+	bool pack_errors = false;
 	int ret;
 
 	for (; m; m = m->base_midx) {
@@ -176,6 +177,20 @@ static int for_each_prefixed_object_in_midx(
 			if (!match_hash(len, opts->prefix->hash, current->hash))
 				break;
 
+			if (opts->flags) {
+				uint32_t pack_id = nth_midxed_pack_int_id(m, i);
+				struct packed_git *pack;
+
+				if (prepare_midx_pack(m, pack_id)) {
+					pack_errors = true;
+					continue;
+				}
+
+				pack = nth_midxed_pack(m, pack_id);
+				if (should_exclude_pack(pack, opts->flags))
+					continue;
+			}
+
 			if (data->request) {
 				struct object_info oi = *data->request;
 
@@ -198,6 +213,8 @@ static int for_each_prefixed_object_in_midx(
 	ret = 0;
 
 out:
+	if (!ret && pack_errors)
+		ret = -1;
 	return ret;
 }
 
@@ -260,9 +277,6 @@ static int odb_source_packed_for_each_prefixed_object(
 	bool pack_errors = false;
 	int ret;
 
-	if (opts->flags)
-		BUG("flags unsupported");
-
 	store->skip_mru_updates = true;
 
 	m = get_multi_pack_index(store);
@@ -275,6 +289,8 @@ static int odb_source_packed_for_each_prefixed_object(
 	for (e = packfile_store_get_packs(store); e; e = e->next) {
 		if (e->pack->multi_pack_index)
 			continue;
+		if (should_exclude_pack(e->pack, opts->flags))
+			continue;
 
 		if (open_pack_index(e->pack)) {
 			pack_errors = true;
-- 
2.55.0.rc1.745.g43192e7977.dirty
Patrick SteinhardtJun 25, 2026, 09:57 UTC in reply to Patrick Steinhardt on lore

[PATCH v3 3/4] connected: split out promisor-based connectivity check

When performing a connectivity check in a partial clone we try to avoid doing the connectivity check by checking whether all new tips are part of a promisor pack. This makes use of the fact that we don't expect full connectivity for promised objects anyway, so it's basically fine if those objects are not fully connected.

The logic that handles this promisor-based check is somewhat hard to read though as it uses nested loops and gotos. Pull it out into a standalone function, which makes it a bit easier to reason about.

We'll also further simplify the function in the next commit.
Suggested-by: Christian Couder <christian.couder@gmail.com>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 connected.c | 85 ++++++++++++++++++++++++++++++++++++-------------------------
 1 file changed, 51 insertions(+), 34 deletions(-)
Show changes to connected.c +51 −34
diff --git a/connected.c b/connected.c
index 7e26976832..d2b334173f 100644
--- a/connected.c
+++ b/connected.c
@@ -11,6 +11,49 @@
 #include "packfile.h"
 #include "promisor-remote.h"
 
+/*
+ * For partial clones, we don't want to have to do a regular connectivity check
+ * because we have to enumerate and exclude all promisor objects (slow), and
+ * then the connectivity check itself becomes a no-op because in a partial
+ * clone every object is a promisor object. Instead, just make sure we
+ * received, in a promisor packfile, the objects pointed to by each wanted ref.
+ *
+ * Before checking for promisor packs, be sure we have the latest pack-files
+ * loaded into memory.
+ *
+ * Returns 1 when all object IDs have been found in promisor packs, in which
+ * case we're fully connected and thus done. Returns 0 when we have found
+ * objects in non-promisor packs, in which case we'll have to fall back to the
+ * rev-list-based connectivity checks. Returns a negative error code on error.
+ */
+static int check_connected_promisor(oid_iterate_fn fn,
+				    void *cb_data,
+				    const struct object_id **oid)
+{
+	odb_reprepare(the_repository->objects);
+	do {
+		struct packed_git *p;
+
+		repo_for_each_pack(the_repository, p) {
+			if (!p->pack_promisor)
+				continue;
+			if (find_pack_entry_one(*oid, p))
+				goto promisor_pack_found;
+		}
+
+		/*
+		 * We have found an object that is not part of a promisor pack,
+		 * and thus we cannot skip the full connectivity check.
+		 */
+		return 0;
+
+promisor_pack_found:
+		;
+	} while ((*oid = fn(cb_data)) != NULL);
+
+	return 1;
+}
+
 /*
  * If we feed all the commits we want to verify to this command
  *
@@ -46,42 +89,16 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
 	}
 
 	if (repo_has_promisor_remote(the_repository)) {
-		/*
-		 * For partial clones, we don't want to have to do a regular
-		 * connectivity check because we have to enumerate and exclude
-		 * all promisor objects (slow), and then the connectivity check
-		 * itself becomes a no-op because in a partial clone every
-		 * object is a promisor object. Instead, just make sure we
-		 * received, in a promisor packfile, the objects pointed to by
-		 * each wanted ref.
-		 *
-		 * Before checking for promisor packs, be sure we have the
-		 * latest pack-files loaded into memory.
-		 */
-		odb_reprepare(the_repository->objects);
-		do {
-			struct packed_git *p;
-
-			repo_for_each_pack(the_repository, p) {
-				if (!p->pack_promisor)
-					continue;
-				if (find_pack_entry_one(oid, p))
-					goto promisor_pack_found;
-			}
-			/*
-			 * Fallback to rev-list with oid and the rest of the
-			 * object IDs provided by fn.
-			 */
-			goto no_promisor_pack_found;
-promisor_pack_found:
-			;
-		} while ((oid = fn(cb_data)) != NULL);
-		if (opt->err_fd)
-			close(opt->err_fd);
-		return 0;
+		err = check_connected_promisor(fn, cb_data, &oid);
+		if (err) {
+			if (opt->err_fd)
+				close(opt->err_fd);
+			if (err > 0)
+				err = 0;
+			return err;
+		}
 	}
 
-no_promisor_pack_found:
 	if (opt->shallow_file) {
 		strvec_push(&rev_list.args, "--shallow-file");
 		strvec_push(&rev_list.args, opt->shallow_file);
-- 
2.55.0.rc1.745.g43192e7977.dirty
Patrick SteinhardtJun 25, 2026, 09:57 UTC in reply to Patrick Steinhardt on lore

[PATCH v3 4/4] connected: search promisor objects generically

When performing connectivity checks we have to figure out whether any of the new objects are promisor objects, as we cannot assume full connectivity if so.

This check is performed by iterating through all packfiles in the repository and searching each of them for the given object. Of course, this mechanism is quite specific to implementation details of the object database, as we assume that it uses packfiles in the first place.

Refactor the logic so that we instead use `odb_for_each_object_ext()` with an object prefix filter and the `ODB_FOR_EACH_OBJECT_PROMISOR_ONLY` flag. This will yield all objects that have the exact object name and that are part of a promisor pack in a generic way.

Add a test to verify that we indeed use the optimization.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 connected.c              | 35 ++++++++++++++++++++++++-----------
 t/t5616-partial-clone.sh | 24 ++++++++++++++++++++++++
 2 files changed, 48 insertions(+), 11 deletions(-)
Show changes to 2 files +48 −11

connected.c, t/t5616-partial-clone.sh

diff --git a/connected.c b/connected.c
index d2b334173f..929b9bd28d 100644
--- a/connected.c
+++ b/connected.c
@@ -11,6 +11,15 @@
 #include "packfile.h"
 #include "promisor-remote.h"
 
+static int promised_object_cb(const struct object_id *oid UNUSED,
+			      struct object_info *oi UNUSED,
+			      void *payload)
+{
+	bool *found = payload;
+	*found = true;
+	return 1;
+}
+
 /*
  * For partial clones, we don't want to have to do a regular connectivity check
  * because we have to enumerate and exclude all promisor objects (slow), and
@@ -30,25 +39,29 @@ static int check_connected_promisor(oid_iterate_fn fn,
 				    void *cb_data,
 				    const struct object_id **oid)
 {
+	struct odb_for_each_object_options opts = {
+		.flags = ODB_FOR_EACH_OBJECT_PROMISOR_ONLY,
+		.prefix_hex_len = the_repository->hash_algo->hexsz,
+	};
+	int err;
+
 	odb_reprepare(the_repository->objects);
 	do {
-		struct packed_git *p;
+		bool found = false;
 
-		repo_for_each_pack(the_repository, p) {
-			if (!p->pack_promisor)
-				continue;
-			if (find_pack_entry_one(*oid, p))
-				goto promisor_pack_found;
-		}
+		opts.prefix = *oid;
+
+		err = odb_for_each_object_ext(the_repository->objects, NULL,
+					      promised_object_cb, &found, &opts);
+		if (err < 0)
+			return err;
 
 		/*
 		 * We have found an object that is not part of a promisor pack,
 		 * and thus we cannot skip the full connectivity check.
 		 */
-		return 0;
-
-promisor_pack_found:
-		;
+		if (!found)
+			return 0;
 	} while ((*oid = fn(cb_data)) != NULL);
 
 	return 1;
diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh
index 1c2805acca..905052072d 100755
--- a/t/t5616-partial-clone.sh
+++ b/t/t5616-partial-clone.sh
@@ -97,6 +97,30 @@ test_expect_success 'partial fetch inherits filter settings' '
 	test_line_count = 5 observed
 '
 
+test_expect_success 'partial fetch does not spawn rev-list connectivity check' '
+	test_when_finished "rm -rf connectivity-remote connectivity-client" &&
+	git init connectivity-remote &&
+	test_commit -C connectivity-remote one &&
+	git -C connectivity-remote config uploadpack.allowfilter 1 &&
+	git -C connectivity-remote config uploadpack.allowanysha1inwant 1 &&
+
+	git clone --no-checkout --filter=blob:none \
+		"file://$(pwd)/connectivity-remote" connectivity-client &&
+
+	# When doing a partial fetch where all tips are part of a promisor pack
+	# we want to skip the connectivity check, as these objects are allowed
+	# to not be fully connected.
+	test_commit -C connectivity-remote two &&
+	GIT_TRACE2_EVENT="$(pwd)/partial.trace" git -C connectivity-client fetch origin &&
+	test_subcommand_flex ! git rev-list --objects --stdin <partial.trace &&
+
+	# Otherwise, when doing a fetch where any of the tips is not part of a
+	# promisor pack, then we must run the connectivity check.
+	test_commit -C connectivity-remote three &&
+	GIT_TRACE2_EVENT="$(pwd)/full.trace" git -C connectivity-client fetch --no-filter origin &&
+	test_subcommand_flex git rev-list --objects --stdin <full.trace
+'
+
 # force dynamic object fetch using diff.
 # we should only get 1 new blob (for the file in origin/main).
 test_expect_success 'verify diff causes dynamic object fetch' '
-- 
2.55.0.rc1.745.g43192e7977.dirty
Junio C HamanoJun 25, 2026, 20:22 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v3 4/4] connected: search promisor objects generically

Patrick Steinhardt <ps@pks.im> writes:
Show 15 quoted lines
> When performing connectivity checks we have to figure out whether any of
> the new objects are promisor objects, as we cannot assume full
> connectivity if so.
>
> This check is performed by iterating through all packfiles in the
> repository and searching each of them for the given object. Of course,
> this mechanism is quite specific to implementation details of the object
> database, as we assume that it uses packfiles in the first place.
>
> Refactor the logic so that we instead use `odb_for_each_object_ext()`
> with an object prefix filter and the `ODB_FOR_EACH_OBJECT_PROMISOR_ONLY`
> flag. This will yield all objects that have the exact object name and
> that are part of a promisor pack in a generic way.
>
> Add a test to verify that we indeed use the optimization.

OK. The new test is a good way to catch the issue we noticed in the previous round, I guess. Looking good.

Thanks.
Christian CouderJun 26, 2026, 06:55 UTC in reply to Junio C Hamano on lore

Re: [PATCH v3 4/4] connected: search promisor objects generically

On Thu, Jun 25, 2026 at 10:22 PM Junio C Hamano <gitster@pobox.com> wrote:
Show 21 quoted lines
>
> Patrick Steinhardt <ps@pks.im> writes:
>
> > When performing connectivity checks we have to figure out whether any of
> > the new objects are promisor objects, as we cannot assume full
> > connectivity if so.
> >
> > This check is performed by iterating through all packfiles in the
> > repository and searching each of them for the given object. Of course,
> > this mechanism is quite specific to implementation details of the object
> > database, as we assume that it uses packfiles in the first place.
> >
> > Refactor the logic so that we instead use `odb_for_each_object_ext()`
> > with an object prefix filter and the `ODB_FOR_EACH_OBJECT_PROMISOR_ONLY`
> > flag. This will yield all objects that have the exact object name and
> > that are part of a promisor pack in a generic way.
> >
> > Add a test to verify that we indeed use the optimization.
>
> OK.  The new test is a good way to catch the issue we noticed in the
> previous round, I guess.  Looking good.
Yeah, it looks ready to me too.
Thanks.

Back to recent threads