Git development
 help / color / mirror / Atom feed
* Re: [PATCH 4/6] SubmittingPatches: document Based-on-patch-by trailer
From: Kristoffer Haugsbakk @ 2026-06-16 20:07 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <xmqqse6tnho1.fsf@gitster.g>

On Thu, Jun 11, 2026, at 18:52, Junio C Hamano wrote:
> kristofferhaugsbakk@fastmail.com writes:
>
>> +. `Based-on-patch-by:` can be used when someone else authored parts of
>> +  the patch that you are submitting. This might be relevant if someone
>> +  sent a patch to the mailing list without a commit message or a
>> +  `Signed-off-by:` and you have picked it up.
>
> Hmph, this seems to encourage pick up material that come outside of
> the usual DCO process, which should not be the intention of this
> document.

Oh, I have misread the room on this subject. It would be better to drop
the mention of signoff here.

>
> Unless the changes are trivial enough to not be copyrightable, it
> may be better to say "... if someone submitted a preliminary patch or
> a detailed code snippet with their sign-off", plus encourage asking
> the original author to sign-off if it initially came without, or
> something like that?

Okay, since they provided something concrete to copy (cf. Helped-by
where they did not provide precise changes in patch form, according to
the below context), it’s best to mention that signoff is relevant here.

>
>>  . `Helped-by:` is used to credit someone who suggested ideas for
>>    changes without providing the precise changes in patch form.
>>  . `Mentored-by:` is used to credit someone with helping develop a

^ permalink raw reply

* Re: [PATCH 1/6] SubmittingPatches: encourage trailer use for substantial help
From: Kristoffer Haugsbakk @ 2026-06-16 20:14 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <xmqq1pedowl2.fsf@gitster.g>

On Thu, Jun 11, 2026, at 18:44, Junio C Hamano wrote:
> kristofferhaugsbakk@fastmail.com writes:
>
>> Let’s replace “If you like” with outright encouragment in this section
>
> "encouragement"?

Yep.

>
>> At the same, it is important to temper this recommendation to a sign-
>> ificant enough contribution; in my experience beginners can be eager
>
> "At the same time"?

Yep.

>
> It is a bit unusual to see a long word split at the end of a line
> to line-wrap in our documentation and commit log messages.

A bit unusual is an understatement. I cannot find any other commit log
message writers that have split a word on a syllable. All linebreaks
that I’ve found are on existing hyphens. Like

       ... multi-pack-
       indexes

I’ll avoid this in the future.

>
>> ---
>>  Documentation/SubmittingPatches | 14 +++++++++++---
>>  1 file changed, 11 insertions(+), 3 deletions(-)
>
> The patch text itself looks great.  Thanks.

Thanks for going over these.

^ permalink raw reply

* Re: [PATCH 6/6] SubmittingPatches: note that trailer order matters
From: Kristoffer Haugsbakk @ 2026-06-16 20:18 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <xmqq8q8mt4eo.fsf@gitster.g>

On Thu, Jun 11, 2026, at 00:30, Junio C Hamano wrote:
>>[snip]
>>  Only capitalize the very first letter of the trailer, i.e. favor
>>  `Signed-off-by:` over `Signed-Off-By:` and `Acked-by:` over `Acked-By:`.
>>
>> +Note that these trailers should come before your `Signed-off-by:`
>> +trailer. You are signing off to the patch as well as the message. This
>> +also makes it clear who added trailers when multiple people have signed
>> +off on a patch.
>
> Perhaps first mention the underlying rule that they are added in the
> order that helps us to understand the chronological order of events.
> That would avoid giving a wrong impression that the nature of each
> trailer keys determine the order of these lines.

You’re right. It’s best to lead with the time-based order. That
naturally leads into the implication that we can easily read what
trailers that any given person added.

I’ve seen discussions in the past about whether trailers ought to be
sorted by *kind*, and maybe if first e.g. you should first have
Tested-by, then Reviewed-by, then Signed-off-by... best to not give such
an impression by accident.

Thanks.

^ permalink raw reply

* Re: [PATCH v3 02/11] doc: interpret-trailers: replace “lines” with “metadata”
From: Kristoffer Haugsbakk @ 2026-06-16 20:32 UTC (permalink / raw)
  To: Matt Hunter, git; +Cc: Christian Couder, jackmanb, Linus Arver, D. Ben Knoble
In-Reply-To: <DJ5W2I8UYXAA.3O4JQUHFMKP5X@lfurio.us>

On Thu, Jun 11, 2026, at 05:10, Matt Hunter wrote:
> On Wed Jun 10, 2026 at 5:21 PM EDT, kristofferhaugsbakk wrote:
>>[snip]
>>  DESCRIPTION
>>  -----------
>> -Add or parse _trailer_ lines at the end of the otherwise
>> +Add or parse trailers metadata at the end of the otherwise
>
> fwiw, I think "trailer metadata" reads more naturally.

You’re right that your version reads more naturally. I went back and
forth on this.

1. We’re introducing the jargon, and the format is often discussed as
   plural “trailers”, with its constituent parts being singular
   “trailer”
2. What this replaces uses “trailer”, but it rescues the plural mood
   with “lines”
3. This is very soon going to go into the constituent parts, including
   each trailer, so we’re contrasting the concept name (trailers) with
   its parts

But I think your version is overall better. It reads better and there is
no way to confuse “trailer metadata” (trailers as a collection) with
just a single “trailer”.

^ permalink raw reply

* Re: [PATCH GSoC RFC v12 09/12] transport: add client support for object-info
From: Junio C Hamano @ 2026-06-16 20:35 UTC (permalink / raw)
  To: Pablo Sabater
  Cc: eric.peijian, chriscool, git, jltobler, karthik.188, toon,
	chandrapratap3519
In-Reply-To: <20260608-ps-eric-work-rebase-v12-9-5338b766e658@gmail.com>

Pablo Sabater <pabloosabaterr@gmail.com> writes:

[jc: removed recipients from Cc: list whose addresses bounce]

> From: Calvin Wan <calvinwan@google.com>
>
> Sometimes, it is beneficial to retrieve information about an object
> without downloading it entirely. The server-side logic for this
> functionality was implemented in commit "a2ba162cda (object-info:
> ...
> diff --git a/fetch-object-info.c b/fetch-object-info.c
> ...
> +int fetch_object_info(const enum protocol_version version, struct object_info_args *args,
> +		      struct packet_reader *reader, struct object_info *object_info_data,
> +		      const int stateless_rpc, const int fd_out)
> +{
> ...
> +	for (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++) {
> +		struct string_list object_info_values = STRING_LIST_INIT_DUP;
> +
> +		string_list_split(&object_info_values, reader->line, " ", -1);
> +		if (0 <= size_index) {
> +			if (!strcmp(object_info_values.items[1 + size_index].string, ""))
> +				die("object-info: server does not recognize object %s",
> +				    object_info_values.items[0].string);
> +
> +			if (strtoul_ul(object_info_values.items[1 + size_index].string, 10, object_info_data[i].sizep))


Overly long lines need to be fixed, by using a shorter and crisper
variable name in such a short scope, and line wrapping if needed.

More importantly, on this line (wrapped):

			if (strtoul_ul(object_info_values.items[1 + size_index].string,
				       10, object_info_data[i].sizep))

we notice object_info_data[i] is of type "struct object_info", which
is 

    struct object_info {
            /* Request */
            enum object_type *typep;
            size_t *sizep;
            off_t *disk_sizep;
	    ...

but the last parameter strtoul_ul() takes is unsurprisingly a
pointer to "unsigned long", not a pointer to "size_t".

Which will break on 32-bit boxes where size_t is "unsigned int"
that is 32-bit and different from "unsigned long".

Perhaps something along this line?


diff --git a/fetch-object-info.c b/fetch-object-info.c
index 425929a269..5210e7d954 100644
--- a/fetch-object-info.c
+++ b/fetch-object-info.c
@@ -75,14 +75,17 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar
 
 		string_list_split(&object_info_values, reader->line, " ", -1);
 		if (0 <= size_index) {
+			unsigned long sz;
 			if (!strcmp(object_info_values.items[1 + size_index].string, ""))
 				die("object-info: server does not recognize object %s",
 				    object_info_values.items[0].string);
 
-			if (strtoul_ul(object_info_values.items[1 + size_index].string, 10, object_info_data[i].sizep))
+			if (strtoul_ul(object_info_values.items[1 + size_index].string,
+				       10, &sz))
 				die("object-info: ref %s has invalid size %s",
 				    object_info_values.items[0].string,
 				    object_info_values.items[1 + size_index].string);
+			*object_info_data[i].sizep = sz;
 		}
 
 		string_list_clear(&object_info_values, 0);

^ permalink raw reply related

* Re: [PATCH v2 01/17] packfile: rename `struct packfile_store` to `odb_source_packed`
From: Justin Tobler @ 2026-06-16 21:01 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git, Karthik Nayak
In-Reply-To: <20260609-pks-odb-source-packed-v2-1-839089132c8b@pks.im>

On 26/06/09 10:50AM, Patrick Steinhardt wrote:
> Not too long ago, we have introduced the packfile store in b7983adb51
> (packfile: introduce a new `struct packfile_store`, 2025-09-23). This
> struct is responsible for managing all of our access to packfiles and is
> used as one of the two sources of objects for the "files" source.
> 
> Back when I introduced this structure I didn't have the clear vision yet
> that it will eventually also turn into a proper object database source,
> and how exactly that infrastructure will look like. Now though it's
> becoming increasingly clear that it does make sense to treat it just the
> same as any of our other ODB sources.

We already have `struct odb_source_loose` which is used in `struct
odb_source_files` which is a proper ODB source. Since it seems accessing
packed objects is moving in the same direction, the renaming here makes
sense to me.

> The consequence is that the naming is now a bit out-of-date: it's just
> another source and will be turned into a proper `struct odb_source` over
> the next couple of commits, but it's not named accordingly.
> 
> Rename the structure to `odb_source_packed` to align it with this goal
> and to bring it in line with the other sources we already have.

The rest of this patch is just trivial renames and looks good to me.

-Justin

^ permalink raw reply

* Re: [PATCH v2 04/17] odb/source-packed: store pointer to "files" instead of generic source
From: Justin Tobler @ 2026-06-16 21:14 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git, Karthik Nayak
In-Reply-To: <20260609-pks-odb-source-packed-v2-4-839089132c8b@pks.im>

On 26/06/09 10:50AM, Patrick Steinhardt wrote:
> The `struct odb_source_packed` holds a pointer to its owning parent
> source. The way that Git is currently structured, this parent is always
> the "files" source. In subsequent commits we're going to detangle that
> so that the "packed" source doesn't have any owning parent source at
> all, which makes it usable as a completely standalone source.

Out of curiousity, `struct odb_source_loose` also stores a similar to
pointer to its owning parent. Is the plan to also eventually do the same
there?

> Detangling this mess is somewhat intricate though, and is made even more
> intricate because it's not always clear which kind of source one is
> holding at a specific point in time -- either the parent "files" source,
> or the child "packed" source.
> 
> Make this relationship more explicit by storing a pointer to the "files"
> source instead of storing a pointer to a generic `struct odb_source`.
> This will help make subsequent steps a bit clearer.
> 
> Note that this is a temporary step, only. At the end of this series
> we will have dropped the parent pointer completely.

Ok, so IIUC the eventual goal is to get rid of the pointer entirely, but
for now we are just making its concrete type explicit without having to
downcast. It's not immediately obvious to me how this step gets us
closer to that goal, but that may become more obvious in the next
patches. :)

> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  odb/source-files.c  |  2 +-
>  odb/source-packed.c |  4 ++--
>  odb/source-packed.h |  4 ++--
>  packfile.c          | 12 ++++++------
>  4 files changed, 11 insertions(+), 11 deletions(-)
> 
> diff --git a/odb/source-files.c b/odb/source-files.c
> index 191562f316..e04525fb08 100644
> --- a/odb/source-files.c
> +++ b/odb/source-files.c
> @@ -269,7 +269,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
>  	CALLOC_ARRAY(files, 1);
>  	odb_source_init(&files->base, odb, ODB_SOURCE_FILES, path, local);
>  	files->loose = odb_source_loose_new(odb, path, local);
> -	files->packed = odb_source_packed_new(&files->base);
> +	files->packed = odb_source_packed_new(files);
>  
>  	files->base.free = odb_source_files_free;
>  	files->base.close = odb_source_files_close;
> diff --git a/odb/source-packed.c b/odb/source-packed.c
> index 1e94b47ea0..12e785be48 100644
> --- a/odb/source-packed.c
> +++ b/odb/source-packed.c
> @@ -1,11 +1,11 @@
>  #include "git-compat-util.h"
>  #include "odb/source-packed.h"
>  
> -struct odb_source_packed *odb_source_packed_new(struct odb_source *source)
> +struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)
>  {
>  	struct odb_source_packed *store;
>  	CALLOC_ARRAY(store, 1);
> -	store->source = source;
> +	store->files = parent;
>  	strmap_init(&store->packs_by_path);
>  	return store;
>  }
> diff --git a/odb/source-packed.h b/odb/source-packed.h
> index 327be4ad65..3c2d229a17 100644
> --- a/odb/source-packed.h
> +++ b/odb/source-packed.h
> @@ -9,7 +9,7 @@
>   * A store that manages packfiles for a given object database.
>   */
>  struct odb_source_packed {
> -	struct odb_source *source;
> +	struct odb_source_files *files;

The rest of this patch updates the callsites accordingly and looks
correct.

-Justin

^ permalink raw reply

* Re: [PATCH v2 2/7] patch-delta: use size_t for sizes
From: Junio C Hamano @ 2026-06-16 21:26 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget
  Cc: git, Kristofer Karlsson, Patrick Steinhardt, Johannes Schindelin
In-Reply-To: <66a642c39e7755755fe388af7612ac8c9bf41a5a.1781524349.git.gitgitgadget@gmail.com>

"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> Widen `patch_delta()`'s three size parameters to `size_t` and switch
> its internal use of `get_delta_hdr_size()` to the `_sz` variant.
> Then propagate the wider type through the callers.

Makes sense.  

^ permalink raw reply

* Re: [PATCH v2 04/17] odb/source-packed: store pointer to "files" instead of generic source
From: Justin Tobler @ 2026-06-16 21:28 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git, Karthik Nayak
In-Reply-To: <ajG69JZHx_u2mt7q@denethor>

On 26/06/16 04:14PM, Justin Tobler wrote:
> On 26/06/09 10:50AM, Patrick Steinhardt wrote:
> > The `struct odb_source_packed` holds a pointer to its owning parent
> > source. The way that Git is currently structured, this parent is always
> > the "files" source. In subsequent commits we're going to detangle that
> > so that the "packed" source doesn't have any owning parent source at
> > all, which makes it usable as a completely standalone source.
> 
> Out of curiousity, `struct odb_source_loose` also stores a similar to
> pointer to its owning parent. Is the plan to also eventually do the same
> there?

Ok, I think I got myself mixed up. IIUC the `struct odb_source` pointer
currently stored in `struct odb_source_loose` is not to the parent
source, but to its "base". And the eventual goal here is to move towards
that same direction which makes sense.

-Justin

^ permalink raw reply

* Re: [PATCH GSoC RFC v12 09/12] transport: add client support for object-info
From: Junio C Hamano @ 2026-06-16 21:31 UTC (permalink / raw)
  To: Pablo Sabater
  Cc: eric.peijian, chriscool, git, jltobler, karthik.188, toon,
	chandrapratap3519
In-Reply-To: <xmqq1pe62pgo.fsf@gitster.g>

Junio C Hamano <gitster@pobox.com> writes:

> Pablo Sabater <pabloosabaterr@gmail.com> writes:
>
> [jc: removed recipients from Cc: list whose addresses bounce]
>
>> From: Calvin Wan <calvinwan@google.com>
>>
>> Sometimes, it is beneficial to retrieve information about an object
>> without downloading it entirely. The server-side logic for this
>> functionality was implemented in commit "a2ba162cda (object-info:
>> ...
>> diff --git a/fetch-object-info.c b/fetch-object-info.c
>> ...
>> +int fetch_object_info(const enum protocol_version version, struct object_info_args *args,
>> +		      struct packet_reader *reader, struct object_info *object_info_data,
>> +		      const int stateless_rpc, const int fd_out)
>> +{
>> ...
>> +	for (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++) {
>> +		struct string_list object_info_values = STRING_LIST_INIT_DUP;
>> +
>> +		string_list_split(&object_info_values, reader->line, " ", -1);
>> +		if (0 <= size_index) {
>> +			if (!strcmp(object_info_values.items[1 + size_index].string, ""))
>> +				die("object-info: server does not recognize object %s",
>> +				    object_info_values.items[0].string);
>> +
>> +			if (strtoul_ul(object_info_values.items[1 + size_index].string, 10, object_info_data[i].sizep))
>
>
> Overly long lines need to be fixed, by using a shorter and crisper
> variable name in such a short scope, and line wrapping if needed.
>
> More importantly, on this line (wrapped):
>
> 			if (strtoul_ul(object_info_values.items[1 + size_index].string,
> 				       10, object_info_data[i].sizep))
>
> we notice object_info_data[i] is of type "struct object_info", which
> is 
>
>     struct object_info {
>             /* Request */
>             enum object_type *typep;
>             size_t *sizep;
>             off_t *disk_sizep;
> 	    ...
>
> but the last parameter strtoul_ul() takes is unsurprisingly a
> pointer to "unsigned long", not a pointer to "size_t".
>
> Which will break on 32-bit boxes where size_t is "unsigned int"
> that is 32-bit and different from "unsigned long".
>
> Perhaps something along this line?


Not quite.  This "size_t *sizep" has been very recently introduced
by Dscho in a topic that is in-flight.

The ps/cat-file-remote-object-info topic alone does not have this
type-mismatch problem.  Below needs to be addressed as an evil merge
at the integration side, so you have nothing to do.  I'll have to
tweak the merges.

Sorry for a false alarm.



> diff --git a/fetch-object-info.c b/fetch-object-info.c
> index 425929a269..5210e7d954 100644
> --- a/fetch-object-info.c
> +++ b/fetch-object-info.c
> @@ -75,14 +75,17 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar
>  
>  		string_list_split(&object_info_values, reader->line, " ", -1);
>  		if (0 <= size_index) {
> +			unsigned long sz;
>  			if (!strcmp(object_info_values.items[1 + size_index].string, ""))
>  				die("object-info: server does not recognize object %s",
>  				    object_info_values.items[0].string);
>  
> -			if (strtoul_ul(object_info_values.items[1 + size_index].string, 10, object_info_data[i].sizep))
> +			if (strtoul_ul(object_info_values.items[1 + size_index].string,
> +				       10, &sz))
>  				die("object-info: ref %s has invalid size %s",
>  				    object_info_values.items[0].string,
>  				    object_info_values.items[1 + size_index].string);
> +			*object_info_data[i].sizep = sz;
>  		}
>  
>  		string_list_clear(&object_info_values, 0);

^ permalink raw reply

* Re: SHA-1/SHA-256 interoperability work is functional
From: brian m. carlson @ 2026-06-16 21:31 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <xmqqldce2r1a.fsf@gitster.g>

[-- Attachment #1: Type: text/plain, Size: 6352 bytes --]

On 2026-06-16 at 20:01:37, Junio C Hamano wrote:
> Not that I specifically care about packfile URI, but this one is
> curious.  How would regular "fetch" and "push" traffic work under
> the new world order?  Presumably we will keep one characteristic of
> the protocol, that the packdata stream is the only thing that is
> given to the other side and no object names are given, because the
> receiving end would not want to blindly trust the object name the
> sending end _claims_ to have sent and instead recomputes the object
> name out of the packed objects in the data stream ("if we rehash
> and recompute the object names from the datastream, the other side
> cannot lie to us" IIRC was a security measure).
> 
> For a regular "fetch" and "push" to work, we would need to recompute
> the native object names and also somehow compute the compatibility
> object names if we are in interoperability mode, no?
> 
> If we download *.pack files from a packfile URI, wouldn't it be the
> same story?

Let me explain how conversion works.  Say you have an empty local
repository with SHA-256 as the main algorithm and SHA-1 as the
compatibility algorithm, plus a SHA-1 remote.  When you do `git fetch`,
you get a SHA-1 pack.  You cannot write this into the repository because
your repository doesn't use SHA-1 as the main algorithm, so `git
index-pack` takes the pack and maps any objects.  If the objects are in
the new pack, they get rewritten based on the dependencies; otherwise,
Git uses the existing maps in the repository to rewrite the objects.
`git index-pack` then writes a completely new SHA-256 pack with an index
containing the SHA-1 mapping, using the corresponding deltas[0].

However, `git index-pack` can only index and map objects for one pack at
a time.  We therefore need any pack that we get to be connected to our
existing history so that we can rely on our existing maps to remap
objects that are not in the pack.  For instance, if we get a commit
without its parents, then we'll simply die because those objects cannot
be mapped and we can't write the mapping in the index.

The problem is that that packfile URIs result in multiple packs (one of
which is the dynamically generated protocol pack) that, _in total_,
provide a complete history with what we have, but are not necessarily
individually connected to our existing history.  Moreover, the
dynamically generated protocol pack is sent _first_, so if we have
packfile URIs, that pack is almost certainly guaranteed _not_ to connect
to our existing history.  We would therefore have to pause index-pack,
download all the packfile URIs, index those packfiles (which would have
to be connected to our history), and then unpause index-pack to rewrite
the history.  This is not impossible, but it's tricky, and it has yet to
be implemented.  Someone may decide that this is a valuable feature and
implement it, but it's not on my to-do list.

This doesn't pose a problem with single-algorithm repositories because
if you have unreferenced and unconnected objects, no big deal.  They
just don't get used and will eventually get GC'd.  But since we can't
map those objects in a multi-algorithm world, that's fatal and those
packs can't be indexed.

> > * Large object promisors cannot be used if the server does not actually have
> > 	the entire history, since the server must have a complete history in order to
> > 	provide object mappings.
> 
> Again, this one worries me a bit, but perhaps I am not reading it
> correctly.  Does this mean that the server side says "this is the
> data for object whose name is X in the SHA-1 world, which translates
> to X256 in the SHA-256 world", the receiving end blindly trusts
> without having a way to verify?

The server provides algorithm mappings for for submodules, shallow
clones, and partial clones.  For shallow clones and partial clones, you
have to trust the server anyway because you're already getting a
truncated history.  If you complete the history by fetching the missing
objects and run `git fsck`, then it will detect if the mapping is
invalid because the server was dishonest and complain.  You will have a
corrupt set of mappings to the compatibility algorithm, but those could
theoretically be repaired.

However, in order for the server to produce those mappings, it has to
know the entire history.  If there are objects that are outside the
repository in a secondary location, the server will not have mappings
for those objects and so it will abort the protocol.

The server does not normally provide mappings for non-submodules if
you're doing a regular fetch or clone, since the client has a
self-contained history and does not need those objects to compute the
mapping.  That means that regular clones and fetches work just fine
against existing servers as long as no submodules are involved[1].

The tricky part is submodules.  Because the data is in a separate
repository, we cannot be certain of the mapping.  The documentation says
this:

  There is a potential security problem with providing mappings of
  submodules over the protocol.  Namely, there is no way to guarantee
  that the SHA-1 object ID and the SHA-256 object ID correspond to the
  same commit.  This means that, for example, a malicious server could
  provide a SHA-256 object ID for a submodule that was up to date with
  all security fixes, but map that to a SHA-1 object ID for an older
  commit with security problems.

We therefore reject submodule mappings if fsck verification for
transferred objects is enabled unless the user has explicitly enabled
submodule mappings.

[0] If A deltas against B in SHA-1, then when those are rewritten into
SHA-256, we delta the SHA-256 A against the SHA-256 B.  This does not
guarantee the best possible delta, but it is much cheaper than
redeltifying and because we expect remapped objects to have the same
shape, it should delta well enough in most cases.
[1] For instance, if you build my branch, you can do
`git clone --object-format=sha256:sha1 https://github.com/bk2204/lawn.git`
and it just works since there are no submodules.  My dotfiles, on the
other hand, have submodules and will not work without protocol support.
-- 
brian m. carlson (they/them)
Toronto, Ontario, CA

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 325 bytes --]

^ permalink raw reply

* Re: [PATCH v2 05/17] odb/source-packed: start converting to a proper `struct odb_source`
From: Justin Tobler @ 2026-06-16 21:36 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git, Karthik Nayak
In-Reply-To: <20260609-pks-odb-source-packed-v2-5-839089132c8b@pks.im>

On 26/06/09 10:50AM, Patrick Steinhardt wrote:
> Start converting `struct odb_source_packed` into a proper pluggable
> `struct odb_source` by embedding the base struct and assigning it the
> new `ODB_SOURCE_PACKED` type. Furthermore, wire up lifecycle management
> of this source by implementing the `free` callback and taking ownership
> of the chdir notifications.
> 
> Note that the packed source is not yet functional as a standalone `struct
> odb_source`, as it's missing all of the callback implementations. These
> will be wired up in subsequent commits.

Ok.

> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
[snip]
>  struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)
>  {
> -	struct odb_source_packed *store;
> -	CALLOC_ARRAY(store, 1);
> -	store->files = parent;
> -	strmap_init(&store->packs_by_path);
> -	return store;
> +	struct odb_source_packed *packed;
> +
> +	CALLOC_ARRAY(packed, 1);
> +	odb_source_init(&packed->base, parent->base.odb, ODB_SOURCE_PACKED,
> +			parent->base.path, parent->base.local);
> +	packed->files = parent;
> +	strmap_init(&packed->packs_by_path);
> +
> +	packed->base.free = odb_source_packed_free;
> +
> +	if (!is_absolute_path(parent->base.path))
> +		chdir_notify_register(NULL, odb_source_packed_reparent, packed);

Out of curiousity, did the packfile store previously not have to worry
about changing directories?

> +
> +	return packed;
>  }
> diff --git a/odb/source-packed.h b/odb/source-packed.h
> index 3c2d229a17..68e64cabab 100644
> --- a/odb/source-packed.h
> +++ b/odb/source-packed.h
> @@ -9,6 +9,7 @@
>   * A store that manages packfiles for a given object database.
>   */
>  struct odb_source_packed {
> +	struct odb_source base;
>  	struct odb_source_files *files;

At first I got confused as to why we were adding back a `struct
odb_source` pointer, but this is for the "base" not the parent ODB
source. We need to embed a `struct odb_source` here to make this a
proper ODB source.

Looking good.

-Justin

^ permalink raw reply

* Re: [PATCH v3 02/11] doc: interpret-trailers: replace “lines” with “metadata”
From: Matt Hunter @ 2026-06-16 21:39 UTC (permalink / raw)
  To: Kristoffer Haugsbakk, git
  Cc: Christian Couder, jackmanb, Linus Arver, D. Ben Knoble
In-Reply-To: <26c2fcb2-2618-4bb9-a6e4-a6135934556d@app.fastmail.com>

On Tue Jun 16, 2026 at 4:32 PM EDT, Kristoffer Haugsbakk wrote:
> On Thu, Jun 11, 2026, at 05:10, Matt Hunter wrote:
>> On Wed Jun 10, 2026 at 5:21 PM EDT, kristofferhaugsbakk wrote:
>>>[snip]
>>>  DESCRIPTION
>>>  -----------
>>> -Add or parse _trailer_ lines at the end of the otherwise
>>> +Add or parse trailers metadata at the end of the otherwise
>>
>> fwiw, I think "trailer metadata" reads more naturally.
>
> You’re right that your version reads more naturally. I went back and
> forth on this.

After reading your points, I started debating the grammatical
correctness of it to myself too.  Though I think I stand by my original
knee-jerk response.

>
> 1. We’re introducing the jargon, and the format is often discussed as
>    plural “trailers”, with its constituent parts being singular
>    “trailer”
> 2. What this replaces uses “trailer”, but it rescues the plural mood
>    with “lines”
> 3. This is very soon going to go into the constituent parts, including
>    each trailer, so we’re contrasting the concept name (trailers) with
>    its parts
>
> But I think your version is overall better. It reads better and there is
> no way to confuse “trailer metadata” (trailers as a collection) with
> just a single “trailer”.

Yes, "metadata" reads as a hint to me that interpret-trailers works with
the overall trailer block too.

Another thought I had after seeing this again is that the text could
just say "Add or parse trailers at the end ...", but "metadata" is a lot
more useful, since it's an introduction to what trailers _are_.  So the
patch is an improvement over the original context imo.

^ permalink raw reply

* Re: [PATCH v2 07/17] odb/source-packed: wire up `reprepare()` callback
From: Justin Tobler @ 2026-06-16 21:53 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git, Karthik Nayak
In-Reply-To: <20260609-pks-odb-source-packed-v2-7-839089132c8b@pks.im>

On 26/06/09 10:51AM, Patrick Steinhardt wrote:
> Move the logic to prepare and reprepare the "packed" source into
> "odb/source-packed.c" and wire it up as the `reprepare()` callback.
> 
> Note that "preparing" a source is not yet generic. Eventually, it would
> probably make sense to turn the existing `reprepare()` callback into a
> `prepare()` callback with an optional flag to force re-preparing. But
> this step will be handled in a separate patch series.

I do find the prepare vs reprepare semantics a bit confusing. The
mentioned change above would be nice to see in the future. :)

> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
[snip]
>  static void odb_source_packed_reparent(const char *name UNUSED,
>  				       const char *old_cwd,
>  				       const char *new_cwd,
> @@ -58,6 +214,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)
>  
>  	packed->base.free = odb_source_packed_free;
>  	packed->base.close = odb_source_packed_close;
> +	packed->base.reprepare = odb_source_packed_reprepare;

We moved `packfile_store_reprepare()` logic into "odb/source-packed.c"
and setup the callback making it accessible via `odb_source_reprepare()`
instead. Makes sense.

>  	if (!is_absolute_path(parent->base.path))
>  		chdir_notify_register(NULL, odb_source_packed_reparent, packed);
> diff --git a/odb/source-packed.h b/odb/source-packed.h
> index 68e64cabab..9d4796261a 100644
> --- a/odb/source-packed.h
> +++ b/odb/source-packed.h
> @@ -81,4 +81,13 @@ static inline struct odb_source_packed *odb_source_packed_downcast(struct odb_so
>  	return container_of(source, struct odb_source_packed, base);
>  }
>  
> +/*
> + * Prepare the source by loading packfiles and multi-pack indices for
> + * all alternates. This becomes a no-op if the source is already prepared.
> + *
> + * It shouldn't typically be necessary to call this function directly, as
> + * functions that access the source know to prepare it.
> + */
> +void odb_source_packed_prepare(struct odb_source_packed *source);

The logic for `packfile_store_prepare()` is also moved into
"odb/source-packed.c" and made accessible this function since there
isn't a matching callback.

The other changes are mostly 1:1 moves of associated logic and callsite
updates. Looks good.

-Justin

^ permalink raw reply

* Re: [PATCH v2 11/17] odb/source-packed: wire up `for_each_object()` callback
From: Justin Tobler @ 2026-06-16 22:10 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git, Karthik Nayak
In-Reply-To: <20260609-pks-odb-source-packed-v2-11-839089132c8b@pks.im>

On 26/06/09 10:51AM, Patrick Steinhardt wrote:
> Move `packfile_store_for_each_object()` and its associated helpers from
> "packfile.c" into "odb/source-packed.c" and wire it up as the
> `for_each_object()` callback of the "packed" source.
> 
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
[snip]
> @@ -291,6 +548,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)
>  	packed->base.reprepare = odb_source_packed_reprepare;
>  	packed->base.read_object_info = odb_source_packed_read_object_info;
>  	packed->base.read_object_stream = odb_source_packed_read_object_stream;
> +	packed->base.for_each_object = odb_source_packed_for_each_object;

Wiring up the callback. Looks good.

>  	if (!is_absolute_path(parent->base.path))
>  		chdir_notify_register(NULL, odb_source_packed_reparent, packed);
> diff --git a/packfile.c b/packfile.c
> index 42c84397eb..b8d6054c16 100644
> --- a/packfile.c
> +++ b/packfile.c
> @@ -1362,8 +1362,8 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset,
>  	hashmap_add(&delta_base_cache, &ent->ent);
>  }
>  
> -static int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset,
> -					     uint32_t *maybe_index_pos, struct object_info *oi)
> +int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset,
> +				      uint32_t *maybe_index_pos, struct object_info *oi)

Looks like we are also exposing `packed_object_info_with_index_pos()`
now. Not sure yet if this is also intended to be temporary like
`find_pack_entry()` in a previous patch though though.

-Justin

^ permalink raw reply

* Re: [PATCH] cat-file: speed up default format
From: René Scharfe @ 2026-06-16 22:14 UTC (permalink / raw)
  To: Jeff King; +Cc: Git List
In-Reply-To: <20260616111237.GA687438@coredump.intra.peff.net>

On 6/16/26 1:12 PM, Jeff King wrote:
> On Mon, Jun 15, 2026 at 11:53:07PM +0200, René Scharfe wrote:
> 
>> We can also store literal characters in there.  An opcode plus with a
>> payload char incurs an overhead of 50%, which sounds high, but at least
>> the default format only has two of them and it's much better than
>> storing pointer plus size for an overhead of more than 90% in case of a
>> single char.
> 
> True, and it's a size win if the literal portions tend to be small
> (fewer than 15 bytes). You do lose out on the ability to strbuf_add()
> them in one go, though. So lots more strbuf_grow() checks, etc. If you
> really wanted to get fancy, you could follow the opcode with a length
> represented as a variable-sized integer, followed by the literal bytes.

Or an opcode that shovels a fixed-size string.  Depends on how much
literal text people include in their formats.

> I'm not sure that Git's formatting code needs to squeeze out quite that
> much performance, though.

Good point.  Near-native performance would be necessary to make peephole
optimizations like the special handling of the default format
unnecessary, which I understand exists to speed up Gitaly [1], but I
guess most users don't have such high demands.  And there's no point in
removing a few lines of duplicate code if the necessary machinery adds a
lot of complexity.  Though the code discussed so far was not too crazy
IMHO.

[1] https://gitlab.com/gitlab-org/gitaly/-/blob/master/internal/git/gitpipe/catfile_info.go

> OK, so we managed another 1%. But I'm skeptical that this linear opcode
> technique is where we want to go in the long run, if we're ever going to
> unify formatters.

Agreed.

René


^ permalink raw reply

* Re: [PATCH] cat-file: speed up default format
From: Junio C Hamano @ 2026-06-16 22:21 UTC (permalink / raw)
  To: René Scharfe; +Cc: Jeff King, Git List
In-Reply-To: <47e3cf16-217e-45d4-91e2-5a1abb4ee49e@web.de>

René Scharfe <l.s.r@web.de> writes:

> On 6/16/26 1:12 PM, Jeff King wrote:
>> On Mon, Jun 15, 2026 at 11:53:07PM +0200, René Scharfe wrote:
>> 
>>> We can also store literal characters in there.  An opcode plus with a
>>> payload char incurs an overhead of 50%, which sounds high, but at least
>>> the default format only has two of them and it's much better than
>>> storing pointer plus size for an overhead of more than 90% in case of a
>>> single char.
>> 
>> True, and it's a size win if the literal portions tend to be small
>> (fewer than 15 bytes). You do lose out on the ability to strbuf_add()
>> them in one go, though. So lots more strbuf_grow() checks, etc. If you
>> really wanted to get fancy, you could follow the opcode with a length
>> represented as a variable-sized integer, followed by the literal bytes.
>
> Or an opcode that shovels a fixed-size string.  Depends on how much
> literal text people include in their formats.
>
>> I'm not sure that Git's formatting code needs to squeeze out quite that
>> much performance, though.
>
> Good point.  Near-native performance would be necessary to make peephole
> optimizations like the special handling of the default format
> unnecessary, which I understand exists to speed up Gitaly [1], but I
> guess most users don't have such high demands.  And there's no point in
> removing a few lines of duplicate code if the necessary machinery adds a
> lot of complexity.  Though the code discussed so far was not too crazy
> IMHO.
>
> [1] https://gitlab.com/gitlab-org/gitaly/-/blob/master/internal/git/gitpipe/catfile_info.go
>
>> OK, so we managed another 1%. But I'm skeptical that this linear opcode
>> technique is where we want to go in the long run, if we're ever going to
>> unify formatters.
>
> Agreed.
>
> René

Obviously I agree with the conclusion, but it was fun to watch cute
experiments from the sidelines.  Thanks for entertainment ;-)

^ permalink raw reply

* [PATCH v2 0/7] Introduce fetch.followRemoteHEAD config variable
From: Matt Hunter @ 2026-06-16 22:25 UTC (permalink / raw)
  To: git; +Cc: Bence Ferdinandy, Jeff King, Junio C Hamano
In-Reply-To: <20260612055947.1499497-1-m@lfurio.us>

git-fetch presently offers some useful ways to control how remote HEAD
symbolic-refs are (or aren't) updated when fetching from remote
repositories.  Namely this is done via the
'remote.<name>.followRemoteHEAD' configuration variable.

However, this setting can be somewhat painful to use if you prefer a
default other than "create" and often work with multiple different
remote repositories.

This series introduces the variable 'fetch.followRemoteHEAD', which
provides a configurable default in place of per-remote settings.

'fetch.followRemoteHEAD' functions exactly the same as the original
variable, except that it doesn't allow warning suppression via
'warn-if-not-$branch'.  Given that different remotes will vary their
HEAD and set of branches independently, setting a false-positive
globally in this way doesn't make logical sense.

While it is not mentioned by any of the patches in this series, note
also that the behavior introduced by 012bc566bad7 (remote set-head: set
followRemoteHEAD to "warn" if "always") is unaffected by this series,
and this feature continues to work for only the
'remote.<name>.followRemoteHEAD' variable.

--- 

Changes in v2:
  - Don't die() if the value of fetch.followRemoteHEAD is unrecognized.
  - Use case-sensitive matching for fetch.followRemoteHEAD values.
  - Avoid the phrase "configuration option".
  - Minor documentation wording changes.
  - Link to v1: https://patch.msgid.link/20260612055947.1499497-1-m@lfurio.us

Matt Hunter (7):
  fetch: fixup set_head advice for warn-if-not-branch
  doc: explain fetchRemoteHEADWarn advice
  t5510: cleanup remote in followRemoteHEAD dangling ref test
  fetch: rename function report_set_head
  fetch: refactor do_fetch handling of followRemoteHEAD
  fetch: add configuration variable fetch.followRemoteHEAD
  fetch: fixup a misaligned comment

 Documentation/config/advice.adoc |   4 ++
 Documentation/config/fetch.adoc  |  19 ++++++
 Documentation/config/remote.adoc |  21 +++---
 builtin/fetch.c                  |  49 ++++++++++----
 remote.h                         |  14 ++--
 t/t5510-fetch.sh                 | 106 +++++++++++++++++++++++++++++++
 6 files changed, 183 insertions(+), 30 deletions(-)

Range-diff against v1:
1:  779fb9bfc59f = 1:  2106228f7b98 fetch: fixup set_head advice for warn-if-not-branch
2:  aacc2856bc77 = 2:  b1c58c06e0c7 doc: explain fetchRemoteHEADWarn advice
3:  f5272eaafbcc = 3:  c1d11e8883e6 t5510: cleanup remote in followRemoteHEAD dangling ref test
4:  43a17027c13e = 4:  6306c8212fc0 fetch: rename function report_set_head
5:  c719435d9675 ! 5:  3c7257094686 fetch: refactor do_fetch handling of followRemoteHEAD
    @@ Commit message
     
         Update enum follow_remote_head_settings to include the value
         FOLLOW_REMOTE_UNCONFIGURED as the new zero-initialized value for
    -    followRemoteHEAD.  This will allow us to distinguish between the option
    -    being unset vs. explicitly set to 'create', which is ultimately the
    -    system default.  The unnecessary indentation is removed.
    +    followRemoteHEAD.  This will allow us to distinguish between the
    +    variable being unset vs. explicitly set to 'create', which is ultimately
    +    the system default.  The unnecessary indentation is removed.
     
         The do_fetch function is likewise updated to perform its own decision
         making to determine the effective followRemoteHEAD mode, falling back to
         the system default if necessary.  This will enable the next patch to
    -    introduce a user-configurable fallback default option.
    +    introduce a user-configurable default.
     
    -    Function set_head now accepts this value as an argument rather than only
    +    Function set_head now accepts the mode as an argument rather than only
         considering the value defined by the remote.
     
         The use of the 'warn-if-not-$branch' value is awkward in the context of
    -    a global default option, since the branches will differ between
    -    individual remotes.  For this reason, it's left out of this scheme and
    -    handling of the no_warn_branch variable is untouched.  Since a
    -    remote-specific setting for followRemoteHEAD takes priority, we can
    -    assume that if remote->no_warn_branch is set, then the remote is also
    -    asserting FOLLOW_REMOTE_WARN as the effective operating mode, and it
    -    will be honored by do_fetch.
    +    a global default, since the branches will differ between individual
    +    remotes.  For this reason, it's left out of this scheme and handling of
    +    the no_warn_branch variable is untouched.  Since a remote-specific
    +    value for followRemoteHEAD takes priority, we can assume that if
    +    remote->no_warn_branch is set, then the remote is also asserting
    +    FOLLOW_REMOTE_WARN as the effective operating mode, and it will be
    +    honored by do_fetch.
     
         Signed-off-by: Matt Hunter <m@lfurio.us>
     
6:  56f6fc8ded2d ! 6:  af9f99b1ceb2 fetch: add configuration option fetch.followRemoteHEAD
    @@ Metadata
     Author: Matt Hunter <m@lfurio.us>
     
      ## Commit message ##
    -    fetch: add configuration option fetch.followRemoteHEAD
    +    fetch: add configuration variable fetch.followRemoteHEAD
     
    -    'fetch.followRemoteHEAD' is added as a generic option used by all
    +    'fetch.followRemoteHEAD' is added as a generic setting used by all
         remotes for which 'remote.<name>.followRemoteHEAD' is undefined.  If
    -    both options are undefined, a builtin default of "create" is in effect,
    -    matching the previous behavior.
    +    both variables are undefined, a builtin default of "create" is in
    +    effect, matching the previous behavior.
     
         As mentioned in the previous patch, 'fetch.followRemoteHEAD' supports
         all of the values that its 'remote' counterpart does _except_
    @@ Commit message
         repositories.
     
         Documentation and advice messages for both of the followRemoteHEAD
    -    options are reworded to better capture the relationship between the two.
    +    variables are reworded to better capture the relationship between the
    +    two.
     
         The added tests assert feature parity between the two followRemoteHEAD
    -    options, as well as the fact that 'remote.<name>.followRemoteHEAD'
    +    variables, as well as the fact that 'remote.<name>.followRemoteHEAD'
         always supersedes this new configurable default.
     
    +    Helped-by: Junio C Hamano <gitster@pobox.com>
         Signed-off-by: Matt Hunter <m@lfurio.us>
     
      ## Documentation/config/fetch.adoc ##
    @@ Documentation/config/fetch.adoc: the new bundle URI.
      remove the value for the `fetch.bundleCreationToken` value before fetching.
     +
     +`fetch.followRemoteHEAD`::
    -+	When fetching using a default refspec, this option determines how to handle
    ++	When fetching using a default refspec, this setting determines how to handle
     +	differences between a fetched remote's `HEAD` and the local
     +	`remotes/<name>/HEAD` symbolic-ref.  Its value is one of
     ++
    @@ Documentation/config/remote.adoc: Blank values signal to ignore all previous val
     -	Setting it to "always" will silently update `remotes/<name>/HEAD` to
     -	the value on the remote.  Finally, setting it to "never" will never
     -	change or create the local reference.
    -+	When fetching this remote using its default refspec, this option determines
    ++	When fetching this remote using its default refspec, this setting determines
     +	how to handle differences between the remote's `HEAD` and the local
    -+	`remotes/<name>/HEAD` symbolic-ref.  Overrides the setting for
    ++	`remotes/<name>/HEAD` symbolic-ref.  Overrides the value of
     +	`fetch.followRemoteHEAD`.  See `fetch.followRemoteHEAD` for a description of
     +	accepted values.
     ++
    -+In addition to the values supported by `fetch.followRemoteHEAD`, this option may
    -+also take on the value "warn-if-not-`$branch`", which behaves like "warn", but
    -+ignores the warning if the remote's `HEAD` is `remotes/<name>/$branch`.
    ++In addition to the values supported by `fetch.followRemoteHEAD`, this setting
    ++may also take on the value "warn-if-not-`$branch`", which behaves like "warn",
    ++but ignores the warning if the remote's `HEAD` is `remotes/<name>/$branch`.
     
      ## builtin/fetch.c ##
     @@ builtin/fetch.c: static struct string_list negotiation_include = STRING_LIST_INIT_NODUP;
    @@ builtin/fetch.c: static int git_fetch_config(const char *k, const char *v,
     +	if (!strcmp(k, "fetch.followremotehead")) {
     +		if (!v)
     +			return config_error_nonbool(k);
    -+		else if (!strcasecmp(v, "never"))
    ++		else if (!strcmp(v, "never"))
     +			fetch_config->follow_remote_head = FOLLOW_REMOTE_NEVER;
    -+		else if (!strcasecmp(v, "create"))
    ++		else if (!strcmp(v, "create"))
     +			fetch_config->follow_remote_head = FOLLOW_REMOTE_CREATE;
    -+		else if (!strcasecmp(v, "warn"))
    ++		else if (!strcmp(v, "warn"))
     +			fetch_config->follow_remote_head = FOLLOW_REMOTE_WARN;
    -+		else if (!strcasecmp(v, "always"))
    ++		else if (!strcmp(v, "always"))
     +			fetch_config->follow_remote_head = FOLLOW_REMOTE_ALWAYS;
    -+		else
    -+			die(_("invalid value for '%s': '%s'"),
    -+				"fetch.followRemoteHEAD", v);
     +	}
     +
      	return git_default_config(k, v, ctx, cb);
    @@ builtin/fetch.c: static const char *strip_refshead(const char *name){
     -	   "will disable the warning until the remote changes HEAD to something else.");
     +	N_("Run 'git remote set-head %s %s' to follow the change, or modify\n"
     +	   "either of the 'remote.%s.followRemoteHEAD' or 'fetch.followRemoteHEAD'\n"
    -+	   "configuration options to handle the situation differently.\n\n"
    ++	   "configuration variables to handle the situation differently.\n\n"
     +
    -+	   "Using this specific option\n\n"
    ++	   "Using this specific setting\n\n"
     +	   "    git config set remote.%s.followRemoteHEAD warn-if-not-%s\n\n"
     +	   "will suppress the warning until the remote changes HEAD to something else.");
      
7:  5e0bdd0f00b4 = 7:  5c80107f6488 fetch: fixup a misaligned comment

base-commit: 0fae78c9d55efe705877ea537fe42c59164ccd94
-- 
2.54.0


^ permalink raw reply

* [PATCH v2 1/7] fetch: fixup set_head advice for warn-if-not-branch
From: Matt Hunter @ 2026-06-16 22:25 UTC (permalink / raw)
  To: git; +Cc: Bence Ferdinandy, Jeff King, Junio C Hamano
In-Reply-To: <20260616222606.1003521-1-m@lfurio.us>

Specifying the word 'branch' in the command is not correct - a mismatch
with both the implementation in remote.c and the documentation.

Signed-off-by: Matt Hunter <m@lfurio.us>
---
 builtin/fetch.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/fetch.c b/builtin/fetch.c
index c1d7c672f4e0..82969e230f5a 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1700,7 +1700,7 @@ static void set_head_advice_msg(const char *remote, const char *head_name)
 	N_("Run 'git remote set-head %s %s' to follow the change, or set\n"
 	   "'remote.%s.followRemoteHEAD' configuration option to a different value\n"
 	   "if you do not want to see this message. Specifically running\n"
-	   "'git config set remote.%s.followRemoteHEAD warn-if-not-branch-%s'\n"
+	   "'git config set remote.%s.followRemoteHEAD warn-if-not-%s'\n"
 	   "will disable the warning until the remote changes HEAD to something else.");
 
 	advise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),
-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 2/7] doc: explain fetchRemoteHEADWarn advice
From: Matt Hunter @ 2026-06-16 22:25 UTC (permalink / raw)
  To: git; +Cc: Bence Ferdinandy, Jeff King, Junio C Hamano
In-Reply-To: <20260616222606.1003521-1-m@lfurio.us>

When the user sets 'remote.<name>.followRemoteHEAD' to
'warn[-if-not-$branch]', git-fetch will report when a fetched HEAD
disagrees with the locally-configured remote's HEAD.  This additional
advice instructs the user how to deal with these warnings, but was
previously undocumented in git-config.

Signed-off-by: Matt Hunter <m@lfurio.us>
---
 Documentation/config/advice.adoc | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/Documentation/config/advice.adoc b/Documentation/config/advice.adoc
index 257db5891817..c3c190ba6a4f 100644
--- a/Documentation/config/advice.adoc
+++ b/Documentation/config/advice.adoc
@@ -48,6 +48,10 @@ all advice messages.
 		to create a local branch after the fact.
 	diverging::
 		Shown when a fast-forward is not possible.
+	fetchRemoteHEADWarn::
+		Shown when linkgit:git-fetch[1] reveals that a remote `HEAD`
+		differs from what is set locally and the user has opted into
+		receiving a warning in this situation.
 	fetchShowForcedUpdates::
 		Shown when linkgit:git-fetch[1] takes a long time
 		to calculate forced updates after ref updates, or to warn
-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 3/7] t5510: cleanup remote in followRemoteHEAD dangling ref test
From: Matt Hunter @ 2026-06-16 22:25 UTC (permalink / raw)
  To: git; +Cc: Bence Ferdinandy, Jeff King, Junio C Hamano
In-Reply-To: <20260616222606.1003521-1-m@lfurio.us>

A later patch will introduce a new test which closely mirrors this one.
Update this test to remove the 'custom-head' remote it creates.
Otherwise, the two tests will conflict with each other, as the second
one to execute will fail to create this remote (which already exists,
thanks to the first test).

Signed-off-by: Matt Hunter <m@lfurio.us>
---
 t/t5510-fetch.sh | 1 +
 1 file changed, 1 insertion(+)

diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index eca9a973b5cb..43190630e714 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -251,6 +251,7 @@ test_expect_success 'followRemoteHEAD does not kick in with refspecs' '
 '
 
 test_expect_success 'followRemoteHEAD create does not overwrite dangling symref' '
+	test_when_finished "git -C two remote remove custom-head" &&
 	git -C two remote add -m does-not-exist custom-head ../one &&
 	test_config -C two remote.custom-head.followRemoteHEAD create &&
 	git -C two fetch custom-head &&
-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 4/7] fetch: rename function report_set_head
From: Matt Hunter @ 2026-06-16 22:25 UTC (permalink / raw)
  To: git; +Cc: Bence Ferdinandy, Jeff King, Junio C Hamano
In-Reply-To: <20260616222606.1003521-1-m@lfurio.us>

Update to the slightly more obvious name 'warn_set_head', which matches
the verbiage of the followRemoteHEAD options.

Signed-off-by: Matt Hunter <m@lfurio.us>
---
 builtin/fetch.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/builtin/fetch.c b/builtin/fetch.c
index 82969e230f5a..9a45e1e7a44d 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1707,7 +1707,7 @@ static void set_head_advice_msg(const char *remote, const char *head_name)
 			remote, head_name, remote, remote, head_name);
 }
 
-static void report_set_head(const char *remote, const char *head_name,
+static void warn_set_head(const char *remote, const char *head_name,
 			struct strbuf *buf_prev, int updateres) {
 	struct strbuf buf_prefix = STRBUF_INIT;
 	const char *prev_head = NULL;
@@ -1787,7 +1787,7 @@ static int set_head(const struct ref *remote_refs, struct remote *remote)
 	if (verbosity >= 0 &&
 		follow_remote_head == FOLLOW_REMOTE_WARN &&
 		(!no_warn_branch || strcmp(no_warn_branch, head_name)))
-		report_set_head(remote->name, head_name, &b_local_head, was_detached);
+		warn_set_head(remote->name, head_name, &b_local_head, was_detached);
 
 cleanup:
 	free(head_name);
-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 5/7] fetch: refactor do_fetch handling of followRemoteHEAD
From: Matt Hunter @ 2026-06-16 22:25 UTC (permalink / raw)
  To: git; +Cc: Bence Ferdinandy, Jeff King, Junio C Hamano
In-Reply-To: <20260616222606.1003521-1-m@lfurio.us>

Update enum follow_remote_head_settings to include the value
FOLLOW_REMOTE_UNCONFIGURED as the new zero-initialized value for
followRemoteHEAD.  This will allow us to distinguish between the
variable being unset vs. explicitly set to 'create', which is ultimately
the system default.  The unnecessary indentation is removed.

The do_fetch function is likewise updated to perform its own decision
making to determine the effective followRemoteHEAD mode, falling back to
the system default if necessary.  This will enable the next patch to
introduce a user-configurable default.

Function set_head now accepts the mode as an argument rather than only
considering the value defined by the remote.

The use of the 'warn-if-not-$branch' value is awkward in the context of
a global default, since the branches will differ between individual
remotes.  For this reason, it's left out of this scheme and handling of
the no_warn_branch variable is untouched.  Since a remote-specific
value for followRemoteHEAD takes priority, we can assume that if
remote->no_warn_branch is set, then the remote is also asserting
FOLLOW_REMOTE_WARN as the effective operating mode, and it will be
honored by do_fetch.

Signed-off-by: Matt Hunter <m@lfurio.us>
---
 builtin/fetch.c | 14 ++++++++++----
 remote.h        | 14 ++++++++------
 2 files changed, 18 insertions(+), 10 deletions(-)

diff --git a/builtin/fetch.c b/builtin/fetch.c
index 9a45e1e7a44d..3cc7efdd83a0 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1729,12 +1729,12 @@ static void warn_set_head(const char *remote, const char *head_name,
 	strbuf_release(&buf_prefix);
 }
 
-static int set_head(const struct ref *remote_refs, struct remote *remote)
+static int set_head(const struct ref *remote_refs, struct remote *remote,
+			int follow_remote_head)
 {
 	int result = 0, create_only, baremirror, was_detached;
 	struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
 		      b_local_head = STRBUF_INIT;
-	int follow_remote_head = remote->follow_remote_head;
 	const char *no_warn_branch = remote->no_warn_branch;
 	char *head_name = NULL;
 	struct ref *ref, *matches;
@@ -1901,6 +1901,7 @@ static int do_fetch(struct transport *transport,
 	struct ref_update_display_info_array display_array = { 0 };
 	struct strmap rejected_refs = STRMAP_INIT;
 	int summary_width = 0;
+	int follow_remote_head;
 
 	if (tags == TAGS_DEFAULT) {
 		if (transport->remote->fetch_tags == 2)
@@ -1916,6 +1917,11 @@ static int do_fetch(struct transport *transport,
 			goto cleanup;
 	}
 
+	if (transport->remote->follow_remote_head)
+		follow_remote_head = transport->remote->follow_remote_head;
+	else
+		follow_remote_head = BUILTIN_FOLLOW_REMOTE_HEAD_DFLT;
+
 	if (rs->nr) {
 		refspec_ref_prefixes(rs, &transport_ls_refs_options.ref_prefixes);
 	} else {
@@ -1924,7 +1930,7 @@ static int do_fetch(struct transport *transport,
 		if (transport->remote->fetch.nr) {
 			refspec_ref_prefixes(&transport->remote->fetch,
 					     &transport_ls_refs_options.ref_prefixes);
-			if (transport->remote->follow_remote_head != FOLLOW_REMOTE_NEVER)
+			if (follow_remote_head != FOLLOW_REMOTE_NEVER)
 				do_set_head = 1;
 		}
 		if (branch && branch_has_merge_config(branch) &&
@@ -2131,7 +2137,7 @@ static int do_fetch(struct transport *transport,
 		 * Way too many cases where this can go wrong so let's just
 		 * ignore errors and fail silently for now.
 		 */
-		set_head(remote_refs, transport->remote);
+		set_head(remote_refs, transport->remote, follow_remote_head);
 	}
 
 cleanup:
diff --git a/remote.h b/remote.h
index 54b17e4b028b..72a54d84ad51 100644
--- a/remote.h
+++ b/remote.h
@@ -62,12 +62,14 @@ struct remote_state {
 void remote_state_clear(struct remote_state *remote_state);
 struct remote_state *remote_state_new(void);
 
-	enum follow_remote_head_settings {
-		FOLLOW_REMOTE_NEVER = -1,
-		FOLLOW_REMOTE_CREATE = 0,
-		FOLLOW_REMOTE_WARN = 1,
-		FOLLOW_REMOTE_ALWAYS = 2,
-	};
+#define BUILTIN_FOLLOW_REMOTE_HEAD_DFLT FOLLOW_REMOTE_CREATE
+enum follow_remote_head_settings {
+	FOLLOW_REMOTE_UNCONFIGURED = 0,
+	FOLLOW_REMOTE_NEVER,
+	FOLLOW_REMOTE_CREATE,
+	FOLLOW_REMOTE_WARN,
+	FOLLOW_REMOTE_ALWAYS,
+};
 
 struct remote {
 	struct hashmap_entry ent;
-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 6/7] fetch: add configuration variable fetch.followRemoteHEAD
From: Matt Hunter @ 2026-06-16 22:25 UTC (permalink / raw)
  To: git; +Cc: Bence Ferdinandy, Jeff King, Junio C Hamano
In-Reply-To: <20260616222606.1003521-1-m@lfurio.us>

'fetch.followRemoteHEAD' is added as a generic setting used by all
remotes for which 'remote.<name>.followRemoteHEAD' is undefined.  If
both variables are undefined, a builtin default of "create" is in
effect, matching the previous behavior.

As mentioned in the previous patch, 'fetch.followRemoteHEAD' supports
all of the values that its 'remote' counterpart does _except_
warn-if-not-$branch, due to its tighter coupling to individual remote
repositories.

Documentation and advice messages for both of the followRemoteHEAD
variables are reworded to better capture the relationship between the
two.

The added tests assert feature parity between the two followRemoteHEAD
variables, as well as the fact that 'remote.<name>.followRemoteHEAD'
always supersedes this new configurable default.

Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Matt Hunter <m@lfurio.us>
---
 Documentation/config/fetch.adoc  |  19 ++++++
 Documentation/config/remote.adoc |  21 +++----
 builtin/fetch.c                  |  29 +++++++--
 t/t5510-fetch.sh                 | 105 +++++++++++++++++++++++++++++++
 4 files changed, 157 insertions(+), 17 deletions(-)

diff --git a/Documentation/config/fetch.adoc b/Documentation/config/fetch.adoc
index 04ac90912d3a..00435e9a16d9 100644
--- a/Documentation/config/fetch.adoc
+++ b/Documentation/config/fetch.adoc
@@ -126,3 +126,22 @@ the new bundle URI.
 The creation token values are chosen by the provider serving the specific
 bundle URI. If you modify the URI at `fetch.bundleURI`, then be sure to
 remove the value for the `fetch.bundleCreationToken` value before fetching.
+
+`fetch.followRemoteHEAD`::
+	When fetching using a default refspec, this setting determines how to handle
+	differences between a fetched remote's `HEAD` and the local
+	`remotes/<name>/HEAD` symbolic-ref.  Its value is one of
++
+--
+`create`;;
+	Create `remotes/<name>/HEAD` if a ref exists on the remote, but not locally.
+	An existing symbolic-ref will not be touched.  This is the default value.
+`warn`;;
+	Display a warning if the remote advertises a different `HEAD` than what is
+	set locally.  Behaves like "create" if the local symbolic-ref doesn't exist.
+`always`;;
+	Silently update `remotes/<name>/HEAD` whenever the remote advertises a new
+	value.
+`never`;;
+	Never create or modify the `remotes/<name>/HEAD` symbolic-ref.
+--
diff --git a/Documentation/config/remote.adoc b/Documentation/config/remote.adoc
index eb9c8a3c4884..04724bc51628 100644
--- a/Documentation/config/remote.adoc
+++ b/Documentation/config/remote.adoc
@@ -157,15 +157,12 @@ Blank values signal to ignore all previous values, allowing a reset of
 the list from broader config scenarios.
 
 remote.<name>.followRemoteHEAD::
-	How linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`
-	when fetching using the configured refspecs of a remote.
-	The default value is "create", which will create `remotes/<name>/HEAD`
-	if it exists on the remote, but not locally; this will not touch an
-	already existing local reference. Setting it to "warn" will print
-	a message if the remote has a different value than the local one;
-	in case there is no local reference, it behaves like "create".
-	A variant on "warn" is "warn-if-not-$branch", which behaves like
-	"warn", but if `HEAD` on the remote is `$branch` it will be silent.
-	Setting it to "always" will silently update `remotes/<name>/HEAD` to
-	the value on the remote.  Finally, setting it to "never" will never
-	change or create the local reference.
+	When fetching this remote using its default refspec, this setting determines
+	how to handle differences between the remote's `HEAD` and the local
+	`remotes/<name>/HEAD` symbolic-ref.  Overrides the value of
+	`fetch.followRemoteHEAD`.  See `fetch.followRemoteHEAD` for a description of
+	accepted values.
++
+In addition to the values supported by `fetch.followRemoteHEAD`, this setting
+may also take on the value "warn-if-not-`$branch`", which behaves like "warn",
+but ignores the warning if the remote's `HEAD` is `remotes/<name>/$branch`.
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 3cc7efdd83a0..1375fc4e0547 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -103,6 +103,7 @@ static struct string_list negotiation_include = STRING_LIST_INIT_NODUP;
 
 struct fetch_config {
 	enum display_format display_format;
+	enum follow_remote_head_settings follow_remote_head;
 	int all;
 	int prune;
 	int prune_tags;
@@ -173,6 +174,19 @@ static int git_fetch_config(const char *k, const char *v,
 			    "fetch.output", v);
 	}
 
+	if (!strcmp(k, "fetch.followremotehead")) {
+		if (!v)
+			return config_error_nonbool(k);
+		else if (!strcmp(v, "never"))
+			fetch_config->follow_remote_head = FOLLOW_REMOTE_NEVER;
+		else if (!strcmp(v, "create"))
+			fetch_config->follow_remote_head = FOLLOW_REMOTE_CREATE;
+		else if (!strcmp(v, "warn"))
+			fetch_config->follow_remote_head = FOLLOW_REMOTE_WARN;
+		else if (!strcmp(v, "always"))
+			fetch_config->follow_remote_head = FOLLOW_REMOTE_ALWAYS;
+	}
+
 	return git_default_config(k, v, ctx, cb);
 }
 
@@ -1697,11 +1711,13 @@ static const char *strip_refshead(const char *name){
 static void set_head_advice_msg(const char *remote, const char *head_name)
 {
 	const char message_advice_set_head[] =
-	N_("Run 'git remote set-head %s %s' to follow the change, or set\n"
-	   "'remote.%s.followRemoteHEAD' configuration option to a different value\n"
-	   "if you do not want to see this message. Specifically running\n"
-	   "'git config set remote.%s.followRemoteHEAD warn-if-not-%s'\n"
-	   "will disable the warning until the remote changes HEAD to something else.");
+	N_("Run 'git remote set-head %s %s' to follow the change, or modify\n"
+	   "either of the 'remote.%s.followRemoteHEAD' or 'fetch.followRemoteHEAD'\n"
+	   "configuration variables to handle the situation differently.\n\n"
+
+	   "Using this specific setting\n\n"
+	   "    git config set remote.%s.followRemoteHEAD warn-if-not-%s\n\n"
+	   "will suppress the warning until the remote changes HEAD to something else.");
 
 	advise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),
 			remote, head_name, remote, remote, head_name);
@@ -1919,6 +1935,8 @@ static int do_fetch(struct transport *transport,
 
 	if (transport->remote->follow_remote_head)
 		follow_remote_head = transport->remote->follow_remote_head;
+	else if (config->follow_remote_head)
+		follow_remote_head = config->follow_remote_head;
 	else
 		follow_remote_head = BUILTIN_FOLLOW_REMOTE_HEAD_DFLT;
 
@@ -2477,6 +2495,7 @@ int cmd_fetch(int argc,
 {
 	struct fetch_config config = {
 		.display_format = DISPLAY_FORMAT_FULL,
+		.follow_remote_head = FOLLOW_REMOTE_UNCONFIGURED,
 		.prune = -1,
 		.prune_tags = -1,
 		.show_forced_updates = 1,
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 43190630e714..6f0ae1bdd798 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -140,6 +140,16 @@ test_expect_success "fetch test remote HEAD change" '
 	)
 '
 
+test_expect_success "fetch test default followRemoteHEAD never" '
+	git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
+	test_config -C two fetch.followRemoteHEAD "never" &&
+	GIT_TRACE_PACKET=$PWD/trace.out git -C two fetch &&
+	# Confirm that we do not even ask for HEAD when we are
+	# not going to act on it.
+	test_grep ! "ref-prefix HEAD" trace.out &&
+	test_must_fail git -C two rev-parse --verify refs/remotes/origin/HEAD
+'
+
 test_expect_success "fetch test followRemoteHEAD never" '
 	git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
 	test_config -C two remote.origin.followRemoteHEAD "never" &&
@@ -150,6 +160,21 @@ test_expect_success "fetch test followRemoteHEAD never" '
 	test_must_fail git -C two rev-parse --verify refs/remotes/origin/HEAD
 '
 
+test_expect_success "fetch test default followRemoteHEAD warn no change" '
+	git -C two rev-parse --verify refs/remotes/origin/other &&
+	git -C two remote set-head origin other &&
+	git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+	git -C two rev-parse --verify refs/remotes/origin/main &&
+	test_config -C two fetch.followRemoteHEAD "warn" &&
+	git -C two fetch >output &&
+	echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+		"but we have ${SQ}other${SQ} locally." >expect &&
+	test_cmp expect output &&
+	head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+	branch=$(git -C two rev-parse refs/remotes/origin/other) &&
+	test "z$head" = "z$branch"
+'
+
 test_expect_success "fetch test followRemoteHEAD warn no change" '
 	git -C two rev-parse --verify refs/remotes/origin/other &&
 	git -C two remote set-head origin other &&
@@ -165,6 +190,17 @@ test_expect_success "fetch test followRemoteHEAD warn no change" '
 	test "z$head" = "z$branch"
 '
 
+test_expect_success "fetch test default followRemoteHEAD warn create" '
+	git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
+	test_config -C two fetch.followRemoteHEAD "warn" &&
+	git -C two rev-parse --verify refs/remotes/origin/main &&
+	output=$(git -C two fetch) &&
+	test "z" = "z$output" &&
+	head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+	branch=$(git -C two rev-parse refs/remotes/origin/main) &&
+	test "z$head" = "z$branch"
+'
+
 test_expect_success "fetch test followRemoteHEAD warn create" '
 	git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
 	test_config -C two remote.origin.followRemoteHEAD "warn" &&
@@ -176,6 +212,18 @@ test_expect_success "fetch test followRemoteHEAD warn create" '
 	test "z$head" = "z$branch"
 '
 
+test_expect_success "fetch test default followRemoteHEAD warn detached" '
+	git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
+	git -C two update-ref refs/remotes/origin/HEAD HEAD &&
+	HEAD=$(git -C two log --pretty="%H") &&
+	test_config -C two fetch.followRemoteHEAD "warn" &&
+	git -C two fetch >output &&
+	echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+		"but we have a detached HEAD pointing to" \
+		"${SQ}${HEAD}${SQ} locally." >expect &&
+	test_cmp expect output
+'
+
 test_expect_success "fetch test followRemoteHEAD warn detached" '
 	git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
 	git -C two update-ref refs/remotes/origin/HEAD HEAD &&
@@ -188,6 +236,19 @@ test_expect_success "fetch test followRemoteHEAD warn detached" '
 	test_cmp expect output
 '
 
+test_expect_success "fetch test default followRemoteHEAD warn quiet" '
+	git -C two rev-parse --verify refs/remotes/origin/other &&
+	git -C two remote set-head origin other &&
+	git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+	git -C two rev-parse --verify refs/remotes/origin/main &&
+	test_config -C two fetch.followRemoteHEAD "warn" &&
+	output=$(git -C two fetch --quiet) &&
+	test "z" = "z$output" &&
+	head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+	branch=$(git -C two rev-parse refs/remotes/origin/other) &&
+	test "z$head" = "z$branch"
+'
+
 test_expect_success "fetch test followRemoteHEAD warn quiet" '
 	git -C two rev-parse --verify refs/remotes/origin/other &&
 	git -C two remote set-head origin other &&
@@ -229,6 +290,18 @@ test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is di
 	test "z$head" = "z$branch"
 '
 
+test_expect_success "fetch test default followRemoteHEAD always" '
+	git -C two rev-parse --verify refs/remotes/origin/other &&
+	git -C two remote set-head origin other &&
+	git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+	git -C two rev-parse --verify refs/remotes/origin/main &&
+	test_config -C two fetch.followRemoteHEAD "always" &&
+	git -C two fetch &&
+	head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+	branch=$(git -C two rev-parse refs/remotes/origin/main) &&
+	test "z$head" = "z$branch"
+'
+
 test_expect_success "fetch test followRemoteHEAD always" '
 	git -C two rev-parse --verify refs/remotes/origin/other &&
 	git -C two remote set-head origin other &&
@@ -241,6 +314,28 @@ test_expect_success "fetch test followRemoteHEAD always" '
 	test "z$head" = "z$branch"
 '
 
+test_expect_success 'per-remote followRemoteHEAD takes priority over fetch default' '
+	git -C two rev-parse --verify refs/remotes/origin/other &&
+	git -C two remote set-head origin other &&
+	git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+	git -C two rev-parse --verify refs/remotes/origin/main &&
+	test_config -C two fetch.followRemoteHEAD "never" &&
+	test_config -C two remote.origin.followRemoteHEAD "always" &&
+	git -C two fetch &&
+	head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+	branch=$(git -C two rev-parse refs/remotes/origin/main) &&
+	test "z$head" = "z$branch"
+'
+
+test_expect_success 'default followRemoteHEAD does not kick in with refspecs' '
+	git -C two remote set-head origin other &&
+	test_config -C two fetch.followRemoteHEAD always &&
+	git -C two fetch origin refs/heads/main:refs/remotes/origin/main &&
+	echo refs/remotes/origin/other >expect &&
+	git -C two symbolic-ref refs/remotes/origin/HEAD >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'followRemoteHEAD does not kick in with refspecs' '
 	git -C two remote set-head origin other &&
 	test_config -C two remote.origin.followRemoteHEAD always &&
@@ -250,6 +345,16 @@ test_expect_success 'followRemoteHEAD does not kick in with refspecs' '
 	test_cmp expect actual
 '
 
+test_expect_success 'default followRemoteHEAD create does not overwrite dangling symref' '
+	test_when_finished "git -C two remote remove custom-head" &&
+	git -C two remote add -m does-not-exist custom-head ../one &&
+	test_config -C two fetch.followRemoteHEAD create &&
+	git -C two fetch custom-head &&
+	echo refs/remotes/custom-head/does-not-exist >expect &&
+	git -C two symbolic-ref refs/remotes/custom-head/HEAD >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'followRemoteHEAD create does not overwrite dangling symref' '
 	test_when_finished "git -C two remote remove custom-head" &&
 	git -C two remote add -m does-not-exist custom-head ../one &&
-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 7/7] fetch: fixup a misaligned comment
From: Matt Hunter @ 2026-06-16 22:25 UTC (permalink / raw)
  To: git; +Cc: Bence Ferdinandy, Jeff King, Junio C Hamano
In-Reply-To: <20260616222606.1003521-1-m@lfurio.us>

Signed-off-by: Matt Hunter <m@lfurio.us>
---
 builtin/fetch.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/fetch.c b/builtin/fetch.c
index 1375fc4e0547..d942bf6aa029 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1789,7 +1789,7 @@ static int set_head(const struct ref *remote_refs, struct remote *remote,
 		strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote->name);
 		strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote->name, head_name);
 	}
-		/* make sure it's valid */
+	/* make sure it's valid */
 	if (!baremirror && !refs_ref_exists(refs, b_remote_head.buf)) {
 		result = 1;
 		goto cleanup;
-- 
2.54.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox