Git development
 help / color / mirror / Atom feed
* [PATCH] checkout: add --fetch to fetch remote before resolving start-point
From: Harald Nordgren via GitGitGadget @ 2026-04-24 10:03 UTC (permalink / raw)
  To: git; +Cc: Harald Nordgren, Harald Nordgren

From: Harald Nordgren <haraldnordgren@gmail.com>

Add a --fetch option to git checkout and git switch, plus a
checkout.autoFetch config to enable it by default. When set and the
start-point argument names a configured remote (either bare, like
"origin", or prefixed, like "origin/foo"), fetch that remote before
resolving the ref. Aborts the checkout if the fetch fails.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
    checkout: add --fetch to fetch remote before resolving start-point
    
    A workflow I run several times a day looks like:
    
    git fetch origin
    git checkout -b new_branch origin/some-branch
    
    
    The first command exists purely to make the second one see an up-to-date
    view of the remote. If I forget it, origin/some-branch points at a stale
    commit, and I end up creating a local branch from the wrong starting
    point.
    
    This series teaches git checkout (and git switch) a new --fetch flag
    that folds the two steps into one:
    
    git checkout --fetch -b new_branch origin/some-branch
    
    
    When the start-point argument names a configured remote — either bare
    (origin, which resolves to the remote's default branch) or in / form —
    git fetch is run before the start-point is resolved. If the fetch fails,
    the checkout aborts and no local branch is created.
    
    A new checkout.autoFetch config option enables the same behavior by
    default, for users who always want it.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2281%2FHaraldNordgren%2Fcheckout-fetch-start-point-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2281/HaraldNordgren/checkout-fetch-start-point-v1
Pull-Request: https://github.com/git/git/pull/2281

 builtin/checkout.c    | 48 ++++++++++++++++++++++++++++++++++++++--
 t/t7201-co.sh         | 51 +++++++++++++++++++++++++++++++++++++++++++
 t/t9902-completion.sh |  1 +
 3 files changed, 98 insertions(+), 2 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index e031e61886..c8fbc4923b 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -30,7 +30,9 @@
 #include "repo-settings.h"
 #include "resolve-undo.h"
 #include "revision.h"
+#include "run-command.h"
 #include "setup.h"
+#include "strvec.h"
 #include "submodule.h"
 #include "symlinks.h"
 #include "trace2.h"
@@ -61,6 +63,7 @@ struct checkout_opts {
 	int count_checkout_paths;
 	int overlay_mode;
 	int dwim_new_local_branch;
+	int auto_fetch;
 	int discard_changes;
 	int accept_ref;
 	int accept_pathspec;
@@ -112,6 +115,34 @@ struct branch_info {
 	char *checkout;
 };
 
+static void fetch_remote_for_start_point(const char *arg)
+{
+	const char *slash;
+	char *remote_name;
+	struct remote *remote;
+	struct child_process cmd = CHILD_PROCESS_INIT;
+
+	if (!arg || !*arg)
+		return;
+
+	slash = strchr(arg, '/');
+	if (slash == arg)
+		return;
+	remote_name = slash ? xstrndup(arg, slash - arg) : xstrdup(arg);
+
+	remote = remote_get(remote_name);
+	if (!remote || !remote_is_configured(remote, 1)) {
+		free(remote_name);
+		return;
+	}
+
+	strvec_pushl(&cmd.args, "fetch", remote_name, NULL);
+	cmd.git_cmd = 1;
+	free(remote_name);
+	if (run_command(&cmd))
+		die(_("failed to fetch start-point '%s'"), arg);
+}
+
 static void branch_info_release(struct branch_info *info)
 {
 	free(info->name);
@@ -1237,6 +1268,10 @@ static int git_checkout_config(const char *var, const char *value,
 		opts->dwim_new_local_branch = git_config_bool(var, value);
 		return 0;
 	}
+	if (!strcmp(var, "checkout.autofetch")) {
+		opts->auto_fetch = git_config_bool(var, value);
+		return 0;
+	}
 
 	if (starts_with(var, "submodule."))
 		return git_default_submodule_config(var, value, NULL);
@@ -1942,8 +1977,13 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 			opts->dwim_new_local_branch &&
 			opts->track == BRANCH_TRACK_UNSPECIFIED &&
 			!opts->new_branch;
-		int n = parse_branchname_arg(argc, argv, dwim_ok, which_command,
-					     &new_branch_info, opts, &rev);
+		int n;
+
+		if (opts->auto_fetch)
+			fetch_remote_for_start_point(argv[0]);
+
+		n = parse_branchname_arg(argc, argv, dwim_ok, which_command,
+					 &new_branch_info, opts, &rev);
 		argv += n;
 		argc -= n;
 	} else if (!opts->accept_ref && opts->from_treeish) {
@@ -2052,6 +2092,8 @@ int cmd_checkout(int argc,
 		OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")),
 		OPT_BOOL(0, "auto-advance", &opts.auto_advance,
 			 N_("auto advance to the next file when selecting hunks interactively")),
+		OPT_BOOL(0, "fetch", &opts.auto_fetch,
+			 N_("fetch from the remote first if <start-point> is a remote-tracking ref")),
 		OPT_END()
 	};
 
@@ -2102,6 +2144,8 @@ int cmd_switch(int argc,
 			 N_("second guess 'git switch <no-such-branch>'")),
 		OPT_BOOL(0, "discard-changes", &opts.discard_changes,
 			 N_("throw away local modifications")),
+		OPT_BOOL(0, "fetch", &opts.auto_fetch,
+			 N_("fetch from the remote first if <start-point> is a remote-tracking ref")),
 		OPT_END()
 	};
 
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 9bcf7c0b40..60ddebd9c3 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -801,4 +801,55 @@ test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
 	test_cmp_config "" --default "" branch.main2.merge
 '
 
+test_expect_success 'setup upstream for --fetch tests' '
+	git checkout main &&
+	git init fetch_upstream &&
+	test_commit -C fetch_upstream u_main &&
+	git remote add fetch_upstream fetch_upstream &&
+	git fetch fetch_upstream &&
+	git -C fetch_upstream checkout -b fetch_new &&
+	test_commit -C fetch_upstream u_new
+'
+
+test_expect_success 'checkout --fetch -b picks up branch created upstream after clone' '
+	git checkout main &&
+	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_new &&
+	git checkout --fetch -b local_new fetch_upstream/fetch_new &&
+	test_cmp_rev refs/remotes/fetch_upstream/fetch_new HEAD
+'
+
+test_expect_success 'checkout --fetch with bare remote name fetches the remote' '
+	git checkout main &&
+	git -C fetch_upstream checkout -b fetch_new2 &&
+	test_commit -C fetch_upstream u_new2 &&
+	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_new2 &&
+	git checkout --fetch -b local_from_remote fetch_upstream &&
+	git rev-parse --verify refs/remotes/fetch_upstream/fetch_new2
+'
+
+test_expect_success 'checkout --fetch aborts and does not create branch on fetch failure' '
+	git checkout main &&
+	test_might_fail git branch -D bogus &&
+	test_must_fail git checkout --fetch -b bogus fetch_upstream/does_not_exist &&
+	test_must_fail git rev-parse --verify refs/heads/bogus
+'
+
+test_expect_success 'checkout.autoFetch=true enables fetching without --fetch' '
+	git checkout main &&
+	git -C fetch_upstream checkout -b fetch_cfg &&
+	test_commit -C fetch_upstream u_cfg &&
+	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_cfg &&
+	git -c checkout.autoFetch=true checkout -b local_cfg fetch_upstream/fetch_cfg &&
+	test_cmp_rev refs/remotes/fetch_upstream/fetch_cfg HEAD
+'
+
+test_expect_success 'switch --fetch -c picks up branch created upstream after clone' '
+	git checkout main &&
+	git -C fetch_upstream checkout -b fetch_switch &&
+	test_commit -C fetch_upstream u_switch &&
+	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_switch &&
+	git switch --fetch -c local_switch fetch_upstream/fetch_switch &&
+	test_cmp_rev refs/remotes/fetch_upstream/fetch_switch HEAD
+'
+
 test_done
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 2f9a597ec7..dc1d63669f 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2602,6 +2602,7 @@ test_expect_success 'double dash "git checkout"' '
 	--ignore-other-worktrees Z
 	--recurse-submodules Z
 	--auto-advance Z
+	--fetch Z
 	--progress Z
 	--guess Z
 	--no-guess Z

base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0
-- 
gitgitgadget

^ permalink raw reply related

* Re: [PATCH v2 4/9] refs: return `ref_transaction_error` from `ref_transaction_update()`
From: Toon Claes @ 2026-04-24 11:01 UTC (permalink / raw)
  To: Karthik Nayak, git; +Cc: Karthik Nayak, gitster, ps
In-Reply-To: <20260423-refs-move-to-generic-layer-v2-4-ae5a4f146d7d@gmail.com>

Karthik Nayak <karthik.188@gmail.com> writes:

> The `ref_transaction_update()` function is used to add updates to a
> given reference transactions. In the following commit, we'll add more
> validation to this function. As such, it would be beneficial if the
> function returns specific error types, so callers can differentiate
> between different errors.
>
> To facilitate this, return `enum ref_transaction_error` from the
> function and covert the existing '-1' returns to
> 'REF_TRANSACTION_ERROR_GENERIC'.

I had to look it up, but it seems this enum was introduced not long ago
in 76e760b999 (refs: introduce enum-based transaction error types,
2025-04-08). I'm happy to see it's reused here.

> Since this retains the existing
> behavior, no changes are made to any of the callers but this sets the
> necessary infrastructure for introduction of other errors.

Makes sense.

-- Toon

^ permalink raw reply

* Re: [PATCH v2 6/9] update-ref: handle rejections while adding updates
From: Toon Claes @ 2026-04-24 11:22 UTC (permalink / raw)
  To: Karthik Nayak, git; +Cc: Karthik Nayak, gitster, ps
In-Reply-To: <20260423-refs-move-to-generic-layer-v2-6-ae5a4f146d7d@gmail.com>

Karthik Nayak <karthik.188@gmail.com> writes:

> When using git-update-ref(1) with the '--batch-updates' flag, updates
> rejected by the reference backend are displayed to the user while other
> updates are applied. This only applies during the commit phase of the
> transaction.
>
> In the following commits, we'll also extend `ref_transaction_update()`
> to reject updates before a transaction is prepared/committed. In
> preparation, modify the code in update-ref to also handle non-generic
> rejections from `ref_transaction_update()`. This involves propagating
> information to each of the commands on whether updates are allowed to be
> rejected, and also checking for rejections and only dying for generic
> failures.
>
> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
> ---
>  builtin/update-ref.c | 100 ++++++++++++++++++++++++++++++++++++---------------
>  1 file changed, 71 insertions(+), 29 deletions(-)
>
> diff --git a/builtin/update-ref.c b/builtin/update-ref.c
> index 5259cc7226..99deaac6db 100644
> --- a/builtin/update-ref.c
> +++ b/builtin/update-ref.c
> @@ -25,6 +25,15 @@ static unsigned int default_flags;
>  static unsigned create_reflog_flag;
>  static const char *msg;
>  
> +struct command_options {
> +	/*
> +	 * Individual updates are allowed to fail without causing
> +	 * update-ref to exit. This is set when using the
> +	 * '--batch-updates' flag.
> +	 */
> +	bool allow_update_failures;
> +};
> +
>  /*
>   * Parse one whitespace- or NUL-terminated, possibly C-quoted argument
>   * and append the result to arg.  Return a pointer to the terminator.
> @@ -268,11 +277,13 @@ static void print_rejected_refs(const char *refname,
>   */
>  
>  static void parse_cmd_update(struct ref_transaction *transaction,
> -			     const char *next, const char *end)
> +			     const char *next, const char *end,
> +			     struct command_options *opts)
>  {
>  	struct strbuf err = STRBUF_INIT;
>  	char *refname;
>  	struct object_id new_oid, old_oid;
> +	enum ref_transaction_error tx_err;
>  	int have_old;
>  
>  	refname = parse_refname(&next);
> @@ -289,12 +300,20 @@ static void parse_cmd_update(struct ref_transaction *transaction,
>  	if (*next != line_termination)
>  		die("update %s: extra input: %s", refname, next);
>  
> -	if (ref_transaction_update(transaction, refname,
> -				   &new_oid, have_old ? &old_oid : NULL,
> -				   NULL, NULL,
> -				   update_flags | create_reflog_flag,
> -				   msg, &err))
> +	tx_err = ref_transaction_update(transaction, refname,
> +					&new_oid, have_old ? &old_oid : NULL,
> +					NULL, NULL,
> +					update_flags | create_reflog_flag,
> +					msg, &err);
> +
> +	if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
> +	    opts->allow_update_failures) {
> +		print_rejected_refs(refname, have_old ? &old_oid : NULL,
> +				    &new_oid, NULL, NULL, tx_err, err.buf,
> +				    NULL);
> +	} else if (tx_err) {
>  		die("%s", err.buf);

Why die() only on an ERROR_GENERIC? Is GENERIC the only error that stops
processing of other refs? Why? Would there be more errors in the future
that could be added to the pile of "fatal" errors like GENERIC?

I would rather see something that gives a more clear indication this
current ref is rejected. Maybe have a range in the enum:

enum ref_transaction_error {
	/* Default error code */
	REF_TRANSACTION_ERROR_GENERIC = -1,

        /* Ref rejected error range start */
	REF_TRANSACTION_REF_REJECTED = -100,

	/* Ref name conflict like A vs A/B */
	REF_TRANSACTION_ERROR_NAME_CONFLICT = -101,
	/* Ref to be created already exists */
	REF_TRANSACTION_ERROR_CREATE_EXISTS = -102,
	/* ref expected but doesn't exist */
	REF_TRANSACTION_ERROR_NONEXISTENT_REF = -103,
	/* Provided old_oid or old_target of reference doesn't match actual */
	REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE = -104,
	/* Provided new_oid or new_target is invalid */
	REF_TRANSACTION_ERROR_INVALID_NEW_VALUE = -105,
	/* Expected ref to be symref, but is a regular ref */
	REF_TRANSACTION_ERROR_EXPECTED_SYMREF = -106,
	/* Cannot create ref due to case-insensitive filesystem */
	REF_TRANSACTION_ERROR_CASE_CONFLICT = -107,
};

statis inline bool ref_rejected(enum ref_transaction_error err)
{
	return err < REF_TRANSACTION_REF_REJECTED;
}

The thing is, now you have this checking on GENERIC in two places, I'm
worried one or the other might be forgotten in the future.

Now maybe this is a bit of an overkill, so feel free to reject that
suggestion. But if you want to keep looking at GENERIC, how do you feel
about this version:

	if (tx_err == REF_TRANSACTION_ERROR_GENERIC)
		die("%s", err.buf);

	if (tx_err && opts->allow_update_failures)
		print_rejected_refs(refname, have_old ? &old_oid : NULL,
				    &new_oid, NULL, NULL, tx_err, err.buf,
				    NULL);

And a little line of comment saying why to die() on GENERIC wouldn't
hurt I think.

> @@ -341,13 +362,21 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,
>  	if (*next != line_termination)
>  		die("symref-update %s: extra input: %s", refname, next);
>  
> -	if (ref_transaction_update(transaction, refname, NULL,
> -				   have_old_oid ? &old_oid : NULL,
> -				   new_target,
> -				   have_old_oid ? NULL : old_target,
> -				   update_flags | create_reflog_flag,
> -				   msg, &err))
> +	tx_err = ref_transaction_update(transaction, refname, NULL,
> +					have_old_oid ? &old_oid : NULL,
> +					new_target,
> +					have_old_oid ? NULL : old_target,
> +					update_flags | create_reflog_flag,
> +					msg, &err);
> +
> +	if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
> +	    opts->allow_update_failures) {
> +		print_rejected_refs(refname, have_old_oid ? &old_oid : NULL,
> +				    NULL, have_old_oid ? NULL : old_target,
> +				    new_target, tx_err, err.buf, NULL);
> +	} else if (tx_err) {
>  		die("%s", err.buf);
> +	}

Obviously the same suggestion could be applied here.

-- 
Cheers,
Toon

^ permalink raw reply

* ⚡ New Release: SOC AUTOFIX AI PRO – Optimize Your PC for Free
From: lenterprises.b @ 2026-04-24 11:55 UTC (permalink / raw)
  To: git

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

Hello,

I’m excited to announce the release of my new free-use application: SOC AUTOFIX AI PRO. This tool helps users automatically scan their system, detect issues, and apply quick fixes through a modern dashboard interface.

Highlights:

Real-time monitoring of CPU, RAM, and Disk usage

Auto-fix for common issues like high memory or low disk space

Easy feedback and support bundle creation

Non-admin fixes such as DNS flush and temp file cleanup

The application is completely free to use. I’d love for you to try it out and share your feedback.

Download Link: https://drive.google.com/file/d/1IRAlPPz4DBD8LtzyrHexYWgJFugtgq_z/view?usp=sharing

Thank you for your support!



Best regards,

L Enterprises.

^ permalink raw reply

* Re: [PATCH v2 7/9] refs: move object parsing to the generic layer
From: Toon Claes @ 2026-04-24 12:06 UTC (permalink / raw)
  To: Karthik Nayak, git; +Cc: Karthik Nayak, gitster, ps
In-Reply-To: <20260423-refs-move-to-generic-layer-v2-7-ae5a4f146d7d@gmail.com>

Karthik Nayak <karthik.188@gmail.com> writes:

> Regular reference updates made via reference transactions validate that
> the provided object ID exists in the object database, which is done by
> calling 'parse_object()'. This check is done independently by the
> backends which leads to duplicated logic.
>
> Let's move this to the generic layer, ensuring the backends only have to
> care about reference storage and not about validation of the object IDs.
> With this also remove the 'REF_TRANSACTION_ERROR_INVALID_NEW_VALUE'
> error type as its no longer used.
>
> Since we don't iterate over individual references in
> `ref_transaction_prepare()`, we add this check to
> `ref_transaction_update()`. This means that the validation is done as
> soon as an update is queued, without needing to prepare the
> transaction. It can be argued that this is more ideal, since this
> validation has no dependency on the reference transaction being
> prepared.
>
> It must be noted that the change in behavior means that this error
> cannot be ignored even with usage of batched updates, since this happens
> when the update is being added to the transaction. But since the caller
> gets specific error codes, they can either abort the transaction or
> continue adding other updates to the transaction.
>
> Modify 'builtin/receive-pack.c' to now capture the error type so that
> the error propagated to the client stays the same. Also remove two of
> the tests which validates batch-updates with invalid new_oid.
>
> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
> ---
>  builtin/receive-pack.c  | 22 +++++++++++++---------
>  refs.c                  | 18 ++++++++++++++++++
>  refs/files-backend.c    | 28 ++--------------------------
>  refs/reftable-backend.c | 19 -------------------
>  4 files changed, 33 insertions(+), 54 deletions(-)
>
> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
> index 878aa7f0ed..0fb3d57de8 100644
> --- a/builtin/receive-pack.c
> +++ b/builtin/receive-pack.c
> @@ -1641,8 +1641,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)
>  			ret = NULL; /* good */
>  		}
>  		strbuf_release(&err);
> -	}
> -	else {
> +	} else {
> +		enum ref_transaction_error err_type;

Shall we also use `tx_err` like in builtin/update-ref.c?

> diff --git a/refs.c b/refs.c
> index efa16b739d..662a9e6f9e 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -1416,6 +1416,24 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa
>  	flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
>  	flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
>  
> +	if ((flags & REF_HAVE_NEW) && !new_target && !is_null_oid(new_oid) &&
> +	    !(flags & REF_SKIP_OID_VERIFICATION) && !(flags & REF_LOG_ONLY)) {

Compared to the version we used to have in refs/reftable-backend.c,
you've added `!new_target`. Why is that? If I understand correctly, that
only happens when `new_oid` is set. Wouldn't `!is_null_oid(new_oid)`
guard for that?

-- 
Cheers,
Toon

^ permalink raw reply

* Problem with git send-email and --reply-to
From: Christian König @ 2026-04-24 13:13 UTC (permalink / raw)
  To: git

Hello everybody,

either I've found a bug or there is something absolutely not obvious going on here with git send-email.

I want to use the --reply-to option with git send-email to make sure that people reply to my AMD mail address and not my gmail address used for sending mails.

When I use the option on the command line it works fine, but when I put that into my .gitconfig it doesn't seem to have any effect.

Any idea what could be wrong here?

.gitconfig looks like this:

[user]
    name = Christian König
    email = christian.koenig@amd.com
[sendemail]
    reply-to = christian.koenig@amd.com

Thanks in advance,
Christian.

^ permalink raw reply

* Re: Problem with git send-email and --reply-to
From: Ramsay Jones @ 2026-04-24 13:42 UTC (permalink / raw)
  To: Christian König, git
In-Reply-To: <26f3a5b2-1656-482a-9349-ca3592b8bba1@amd.com>



On 24/04/2026 2:13 pm, Christian König wrote:
> Hello everybody,
> 
> either I've found a bug or there is something absolutely not obvious going on here with git send-email.
> 
> I want to use the --reply-to option with git send-email to make sure that people reply to my AMD mail address and not my gmail address used for sending mails.
> 
> When I use the option on the command line it works fine, but when I put that into my .gitconfig it doesn't seem to have any effect.
> 
> Any idea what could be wrong here?
> 
> .gitconfig looks like this:
> 
> [user]
>     name = Christian König
>     email = christian.koenig@amd.com
> [sendemail]
>     reply-to = christian.koenig@amd.com

I don't think this is a valid config variable. It is not mentioned in the man page.
In the source, it is noted as '# Variables we fill in automatically, or via prompting:'
in addition to being part of the command-line '%options' map. So, ...

Sorry not to be of more help.

ATB,
Ramsay Jones




^ permalink raw reply

* Re: [PATCH] checkout: add --fetch to fetch remote before resolving start-point
From: Ramsay Jones @ 2026-04-24 13:48 UTC (permalink / raw)
  To: Harald Nordgren via GitGitGadget, git; +Cc: Harald Nordgren
In-Reply-To: <pull.2281.git.git.1777024991531.gitgitgadget@gmail.com>



On 24/04/2026 11:03 am, Harald Nordgren via GitGitGadget wrote:
> From: Harald Nordgren <haraldnordgren@gmail.com>
> 
> Add a --fetch option to git checkout and git switch, plus a
> checkout.autoFetch config to enable it by default. When set and the
> start-point argument names a configured remote (either bare, like
> "origin", or prefixed, like "origin/foo"), fetch that remote before
> resolving the ref. Aborts the checkout if the fetch fails.
> 
> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
> ---

[snip]

>  
> diff --git a/t/t7201-co.sh b/t/t7201-co.sh
> index 9bcf7c0b40..60ddebd9c3 100755
> --- a/t/t7201-co.sh
> +++ b/t/t7201-co.sh
> @@ -801,4 +801,55 @@ test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
>  	test_cmp_config "" --default "" branch.main2.merge
>  '
>  
> +test_expect_success 'setup upstream for --fetch tests' '
> +	git checkout main &&
> +	git init fetch_upstream &&
> +	test_commit -C fetch_upstream u_main &&
> +	git remote add fetch_upstream fetch_upstream &&
> +	git fetch fetch_upstream &&
> +	git -C fetch_upstream checkout -b fetch_new &&
> +	test_commit -C fetch_upstream u_new
> +'
> +
> +test_expect_success 'checkout --fetch -b picks up branch created upstream after clone' '
> +	git checkout main &&
> +	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_new &&
> +	git checkout --fetch -b local_new fetch_upstream/fetch_new &&
> +	test_cmp_rev refs/remotes/fetch_upstream/fetch_new HEAD
> +'
> +
> +test_expect_success 'checkout --fetch with bare remote name fetches the remote' '
> +	git checkout main &&
> +	git -C fetch_upstream checkout -b fetch_new2 &&
> +	test_commit -C fetch_upstream u_new2 &&
> +	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_new2 &&
> +	git checkout --fetch -b local_from_remote fetch_upstream &&
> +	git rev-parse --verify refs/remotes/fetch_upstream/fetch_new2
> +'
> +
> +test_expect_success 'checkout --fetch aborts and does not create branch on fetch failure' '
> +	git checkout main &&
> +	test_might_fail git branch -D bogus &&
> +	test_must_fail git checkout --fetch -b bogus fetch_upstream/does_not_exist &&
> +	test_must_fail git rev-parse --verify refs/heads/bogus
> +'
> +
> +test_expect_success 'checkout.autoFetch=true enables fetching without --fetch' '
> +	git checkout main &&
> +	git -C fetch_upstream checkout -b fetch_cfg &&
> +	test_commit -C fetch_upstream u_cfg &&
> +	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_cfg &&
> +	git -c checkout.autoFetch=true checkout -b local_cfg fetch_upstream/fetch_cfg &&
> +	test_cmp_rev refs/remotes/fetch_upstream/fetch_cfg HEAD
> +'
> +
> +test_expect_success 'switch --fetch -c picks up branch created upstream after clone' '
> +	git checkout main &&
> +	git -C fetch_upstream checkout -b fetch_switch &&
> +	test_commit -C fetch_upstream u_switch &&
> +	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_switch &&
> +	git switch --fetch -c local_switch fetch_upstream/fetch_switch &&
> +	test_cmp_rev refs/remotes/fetch_upstream/fetch_switch HEAD
> +'
> +

I was just skimming the list (so if this is not appropriate, please just ignore) and,
although I think '--no-fetch' will probably countermand the autoFetch config, I do not
see a test that confirms it.

Thanks.

ATB,
Ramsay Jones



^ permalink raw reply

* Re: [PATCH 2/2] builtin/history: introduce "fixup" subcommand
From: D. Ben Knoble @ 2026-04-24 14:43 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git, Elijah Newren
In-Reply-To: <aesTeWQqMTFd4gy8@pks.im>

On Fri, Apr 24, 2026 at 2:53 AM Patrick Steinhardt <ps@pks.im> wrote:
>
> On Thu, Apr 23, 2026 at 05:18:50PM -0400, D. Ben Knoble wrote:
> > On Thu, Apr 23, 2026 at 2:55 AM Patrick Steinhardt <ps@pks.im> wrote:
> > > On Wed, Apr 22, 2026 at 03:06:12PM -0400, D. Ben Knoble wrote:
> > > > On Wed, Apr 22, 2026 at 6:30 AM Patrick Steinhardt <ps@pks.im> wrote:
> > > > > diff --git a/Documentation/git-history.adoc b/Documentation/git-history.adoc
> > > > > index 24dc907033..3cdfc8ba02 100644
> > > > > --- a/Documentation/git-history.adoc
> > > > > +++ b/Documentation/git-history.adoc
> > > > > @@ -53,6 +55,19 @@ COMMANDS
> > > > >
> > > > >  The following commands are available to rewrite history in different ways:
> > > > >
> > > > > +`fixup <commit>`::
> > > > > +       Apply the currently staged changes to the specified commit. The staged
> > > > > +       changes are incorporated into the target commit's tree via a three-way
> > > > > +       merge, using HEAD's tree as the merge base, which is equivalent to
> > > > > +       linkgit:git-cherry-pick[1].
> > > >
> > > > I'm not quite sure what, as a user of "git history fixup," I'm
> > > > supposed to take from this. Does it make conflicts less likely when
> > > > creating the new fixup? I imagine it doesn't help with conflicts
> > > > between <commit> and HEAD that newly arise.
> > > >
> > > > Anyway, I'd think the mechanics are less relevant than the end-user
> > > > behavior at this point in the doc, unless the equivalence with
> > > > cherry-pick is supposed to tell me something about that behavior.
> > >
> > > There's at least two more or less obvious variants to do this:
> > >
> > >   - You generate the diff between HEAD and index and then try to reapply
> > >     the patch on top of the target commit.
> > >
> > >   - You perform the three-way merge.
> > >
> > > The second item is definitely more robust compared to generating the
> > > diff and reapplying it, and we use the exact same strategy to perform
> > > cherry-picks nowadays.
> > >
> > > > > diff --git a/builtin/history.c b/builtin/history.c
> > > > > index 549e352c74..6299f0dfa9 100644
> > > > > --- a/builtin/history.c
> > > > > +++ b/builtin/history.c
> > > [snip]
> > > > > +       /*
> > > > > +        * Perform the three-way merge to reapply changes in the index onto the
> > > > > +        * target commit. This is using basically the same logic as a
> > > > > +        * cherry-pick, where the base commit is our HEAD, ours is the original
> > > > > +        * tree and theirs is the index tree.
> > > > > +        */
> > > >
> > > > OTOH, this explanation helps quite a bit here :)
> > >
> > > Hm, okay. I felt that this explanation here is even more technical. How
> > > about:
> > >
> > >     `fixup <commit>`::
> > >         Apply the currently staged changes to the specified commit. This
> > >         is done by performing a three-way merge between the HEAD commit,
> > >         the target commit and the tree generated from staged changes.
> > >         This is using the same logic as linkgit:git-cherry-pick[1].
> > >
> > > Not sure that this is an improvement? Happy to hear other suggestions.
> > >
> > > Thanks!
> > >
> > > Patrick
> >
> > Hm. I think what I meant is that the in-code comment makes sense to
> > describe internals; for users, I'm not sure what I should get out of
> > that description of fixup.
> >
> > What I (think I) really care about is that it behaves a bit like `git
> > rebase -i` with a "fixup" command (modulo conflicts). Especially since
> > this is quite a bit more porcelain than plumbing, no?
> >
> > Idk. If the 3-way merge is valuable to keep, maybe it belongs in a
> > second paragraph just to push it out of the way of the primary
> > description ("Apply the currently staged changes to the specified
> > commit")?
>
> Ah, that's what you're getting at! I totally misunderstood what you
> wanted to say, this makes a lot more sense. How about this:

Yep, sorry!

>     `fixup <commit>`::
>         Apply the currently staged changes to the specified commit. This
>         is similar in nature to `git commit --fixup=<commit>` followed
>         by `git rebase --autosquash <commit>~`. Changes are applied to
>         the target commit by performing a three-way merge between the
>         HEAD commit, the target commit and the tree generated from
>         staged changes.

I think that's much better.

> Maybe there should be a new paragraph before we start talking about the
> technical details?

With this version I could go either way :)

> Thanks!
>
> Patrick

Thank you!

-- 
D. Ben Knoble

^ permalink raw reply

* [PATCH 1/8] test-lib: allow bare repository access when breaking changes are enabled
From: Johannes Schindelin via GitGitGadget @ 2026-04-24 15:01 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2098.git.1777042877.gitgitgadget@gmail.com>

From: Johannes Schindelin <johannes.schindelin@gmx.de>

A future patch will change the `safe.bareRepository` default from
`all` to `explicit` under `WITH_BREAKING_CHANGES`. At that point,
every test that operates on a bare repository through implicit
discovery would fail, regardless of whether the test is actually
about discovery or about how a specific command behaves once inside
a bare repository.

The maintainer suggested [1] setting `safe.bareRepository=all` in
the test environment's global config whenever `WITH_BREAKING_CHANGES`
is in effect, rather than adjusting each affected test to access
bare repositories explicitly (via `--git-dir`, `GIT_DIR`, or
similar). This means the test suite continues to exercise only the
historical default behavior even after the user-facing default
changes, relying on a small number of dedicated tests in t0035 to
validate the new, stricter default.

Since `$HOME` points at the trash directory (which doubles as the
test repository's working tree), writing to `$HOME/.gitconfig` also
creates a file inside the working tree. Exclude it via
`.git/info/exclude` to limit the fallout, though this does not
help tests that use `git ls-files --others` without
`--exclude-standard` or `git status --ignored`; those are addressed
by subsequent commits.

[1] https://lore.kernel.org/git/xmqqse98cc51.fsf@gitster.g/

Original-patch-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/test-lib.sh | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/t/test-lib.sh b/t/test-lib.sh
index 70fd3e9baf..b8726f4647 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -1597,6 +1597,12 @@ cd -P "$TRASH_DIRECTORY" || BAIL_OUT "cannot cd -P to \"$TRASH_DIRECTORY\""
 TRASH_DIRECTORY=$(pwd)
 HOME="$TRASH_DIRECTORY"
 
+if test -n "$WITH_BREAKING_CHANGES"
+then
+	git config --global safe.bareRepository all &&
+	echo "/.gitconfig" >>.git/info/exclude
+fi
+
 start_test_output "$0"
 
 # Convenience
-- 
gitgitgadget


^ permalink raw reply related

* [PATCH 2/8] t7900: do not let `$HOME/.gitconfig` interfere with XDG tests
From: Johannes Schindelin via GitGitGadget @ 2026-04-24 15:01 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2098.git.1777042877.gitgitgadget@gmail.com>

From: Johannes Schindelin <johannes.schindelin@gmx.de>

The XDG config tests for `git maintenance register/unregister`
create a fresh `$XDG_CONFIG_HOME/git/config` and expect git to use
that location. However, if `$HOME/.gitconfig` exists (which may
happen when test-lib.sh writes global config, e.g. to set
`safe.bareRepository`), git prefers `$HOME/.gitconfig` over the XDG
location, and the `maintenance.repo` entry ends up in the wrong
file.

This is an inherent consequence of setting global config in
test-lib.sh rather than adjusting individual tests: writing any
entry to `$HOME/.gitconfig` has side effects beyond the intended
setting, because the mere existence of that file changes which
global config location git prefers for all subsequent writes.
Individual per-test adjustments would not have this interaction.

Fix this by overriding `HOME` to a non-existent directory inside the
subshells that test XDG behavior. Since these subshells already
override `XDG_CONFIG_HOME`, they do not need `$HOME/.gitconfig` at
all, and the subshell scoping ensures the original `HOME` is
restored automatically.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t7900-maintenance.sh | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index 4700beacc1..4358df0424 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -101,8 +101,12 @@ test_expect_success "maintenance.autoDetach overrides gc.autoDetach" '
 test_expect_success 'register uses XDG_CONFIG_HOME config if it exists' '
 	test_when_finished rm -r .config/git/config &&
 	(
+		# Override HOME so that .gitconfig (which test-lib.sh may
+		# have created, e.g. to set safe.bareRepository) does not
+		# take precedence over the XDG location.
+		HOME=$PWD/must-not-exist &&
 		XDG_CONFIG_HOME=.config &&
-		export XDG_CONFIG_HOME &&
+		export HOME XDG_CONFIG_HOME &&
 		mkdir -p $XDG_CONFIG_HOME/git &&
 		>$XDG_CONFIG_HOME/git/config &&
 		git maintenance register &&
@@ -124,8 +128,12 @@ test_expect_success 'register does not need XDG_CONFIG_HOME config to exist' '
 test_expect_success 'unregister uses XDG_CONFIG_HOME config if it exists' '
 	test_when_finished rm -r .config/git/config &&
 	(
+		# Override HOME so that .gitconfig (which test-lib.sh may
+		# have created, e.g. to set safe.bareRepository) does not
+		# take precedence over the XDG location.
+		HOME=$PWD/must-not-exist &&
 		XDG_CONFIG_HOME=.config &&
-		export XDG_CONFIG_HOME &&
+		export HOME XDG_CONFIG_HOME &&
 		mkdir -p $XDG_CONFIG_HOME/git &&
 		>$XDG_CONFIG_HOME/git/config &&
 		git maintenance register &&
-- 
gitgitgadget


^ permalink raw reply related

* [PATCH 3/8] t1300: remove global config settings injected by test-lib.sh
From: Johannes Schindelin via GitGitGadget @ 2026-04-24 15:01 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2098.git.1777042877.gitgitgadget@gmail.com>

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Since test-lib.sh now writes `safe.bareRepository=all` to the global
config when `WITH_BREAKING_CHANGES` is in effect, that entry shows
up in `git config --list` output. Tests in t1300 that expect exact
config contents then fail because of this unexpected extra line.

Unlike the working-tree contamination fixed in the preceding
commits, this is not about the file's existence but about its
content leaking into test expectations. Since t1300 does not use
bare repositories, simply remove the injected setting in a
preparatory step.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Assisted-by: Claude Opus 4.6
---
 t/t1300-config.sh | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 128971ee12..11fc976f3a 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -11,6 +11,13 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-terminal.sh
 
+# test-lib.sh may have added global config (e.g. safe.bareRepository)
+# that would appear in "git config --list" output and break tests
+# that expect exact config contents.
+test_expect_success 'remove global config from test-lib.sh' '
+	test_might_fail git config --global --unset-all safe.bareRepository
+'
+
 for mode in legacy subcommands
 do
 
-- 
gitgitgadget


^ permalink raw reply related

* [PATCH 6/8] ls-files tests: filter `.gitconfig` from `--others` output
From: Johannes Schindelin via GitGitGadget @ 2026-04-24 15:01 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2098.git.1777042877.gitgitgadget@gmail.com>

From: Johannes Schindelin <johannes.schindelin@gmx.de>

The global `safe.bareRepository=all` setting in test-lib.sh is
written to `$HOME/.gitconfig`, which unfortunately lives inside the
test repository's working tree. The `.git/info/exclude` entry added
alongside it handles most commands, but `git ls-files --others`
without `--exclude-standard` does not consult `info/exclude` at
all, so the file appears in the output.

Ideally, each test that accesses a bare repository would simply
specify `--git-dir` or `GIT_DIR` explicitly, which would require no
global config and produce no side effects in the working tree. As
that approach was not taken, filter `.gitconfig` from the output
before comparing against expected results. In t7104, the test
already uses `--exclude-standard`, so it suffices to switch from
the bare `git ls-files -o` to `git ls-files -o --exclude-standard`
which respects the `info/exclude` entry; the other tests
deliberately omit `--exclude-standard` because their purpose is to
verify unfiltered `--others` output.

Assisted-by: Claude Opus 4.6
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t3000-ls-files-others.sh                         | 4 ++++
 t/t3001-ls-files-others-exclude.sh                 | 3 +++
 t/t3002-ls-files-dashpath.sh                       | 2 ++
 t/t3009-ls-files-others-nonsubmodule.sh            | 1 +
 t/t3011-common-prefixes-and-directory-traversal.sh | 3 ++-
 t/t7104-reset-hard.sh                              | 2 +-
 t/test-lib-functions.sh                            | 8 ++++++++
 7 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/t/t3000-ls-files-others.sh b/t/t3000-ls-files-others.sh
index b41e7f0daa..b4f0fbfc55 100755
--- a/t/t3000-ls-files-others.sh
+++ b/t/t3000-ls-files-others.sh
@@ -53,16 +53,19 @@ test_expect_success 'setup: expected output' '
 
 test_expect_success 'ls-files --others' '
 	git ls-files --others >output &&
+	test_filter_gitconfig output &&
 	test_cmp expected1 output
 '
 
 test_expect_success 'ls-files --others --directory' '
 	git ls-files --others --directory >output &&
+	test_filter_gitconfig output &&
 	test_cmp expected2 output
 '
 
 test_expect_success '--no-empty-directory hides empty directory' '
 	git ls-files --others --directory --no-empty-directory >output &&
+	test_filter_gitconfig output &&
 	test_cmp expected3 output
 '
 
@@ -70,6 +73,7 @@ test_expect_success 'ls-files --others handles non-submodule .git' '
 	mkdir not-a-submodule &&
 	echo foo >not-a-submodule/.git &&
 	git ls-files -o >output &&
+	test_filter_gitconfig output &&
 	test_cmp expected1 output
 '
 
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
index 4b67646285..202fb8d9ea 100755
--- a/t/t3001-ls-files-others-exclude.sh
+++ b/t/t3001-ls-files-others-exclude.sh
@@ -72,6 +72,7 @@ test_expect_success 'git ls-files --others with various exclude options.' '
        --exclude-per-directory=.gitignore \
        --exclude-from=.git/ignore \
 	>output &&
+	test_filter_gitconfig output &&
 	test_cmp expect output
 '
 
@@ -84,6 +85,7 @@ test_expect_success 'git ls-files --others with \r\n line endings.' '
        --exclude-per-directory=.gitignore \
        --exclude-from=.git/ignore \
 	>output &&
+	test_filter_gitconfig output &&
 	test_cmp expect output
 '
 
@@ -99,6 +101,7 @@ test_expect_success 'git ls-files --others with various exclude options.' '
        --exclude-per-directory=.gitignore \
        --exclude-from=.git/ignore \
 	>output &&
+	test_filter_gitconfig output &&
 	test_cmp expect output
 '
 
diff --git a/t/t3002-ls-files-dashpath.sh b/t/t3002-ls-files-dashpath.sh
index 31462cb441..6acaadbd67 100755
--- a/t/t3002-ls-files-dashpath.sh
+++ b/t/t3002-ls-files-dashpath.sh
@@ -24,6 +24,7 @@ test_expect_success 'setup' '
 test_expect_success 'git ls-files without path restriction.' '
 	test_when_finished "rm -f expect" &&
 	git ls-files --others >output &&
+	test_filter_gitconfig output &&
 	cat >expect <<-\EOF &&
 	--
 	-foo
@@ -63,6 +64,7 @@ test_expect_success 'git ls-files with path restriction with -- --.' '
 test_expect_success 'git ls-files with no path restriction.' '
 	test_when_finished "rm -f expect" &&
 	git ls-files --others -- >output &&
+	test_filter_gitconfig output &&
 	cat >expect <<-\EOF &&
 	--
 	-foo
diff --git a/t/t3009-ls-files-others-nonsubmodule.sh b/t/t3009-ls-files-others-nonsubmodule.sh
index 963f3462b7..dc990c277b 100755
--- a/t/t3009-ls-files-others-nonsubmodule.sh
+++ b/t/t3009-ls-files-others-nonsubmodule.sh
@@ -36,6 +36,7 @@ test_expect_success 'setup: directories' '
 
 test_expect_success 'ls-files --others handles untracked git repositories' '
 	git ls-files -o >output &&
+	test_filter_gitconfig output &&
 	cat >expect <<-EOF &&
 	nonrepo-untracked-file/untracked
 	output
diff --git a/t/t3011-common-prefixes-and-directory-traversal.sh b/t/t3011-common-prefixes-and-directory-traversal.sh
index 3da5b2b6e7..455e97954d 100755
--- a/t/t3011-common-prefixes-and-directory-traversal.sh
+++ b/t/t3011-common-prefixes-and-directory-traversal.sh
@@ -26,7 +26,7 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'git ls-files -o shows the right entries' '
-	cat <<-EOF >expect &&
+	cat >expect <<-EOF &&
 	.gitignore
 	actual
 	an_ignored_dir/ignored
@@ -39,6 +39,7 @@ test_expect_success 'git ls-files -o shows the right entries' '
 	untracked_repo/
 	EOF
 	git ls-files -o >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expect actual
 '
 
diff --git a/t/t7104-reset-hard.sh b/t/t7104-reset-hard.sh
index 7948ec392b..c23d6e3f52 100755
--- a/t/t7104-reset-hard.sh
+++ b/t/t7104-reset-hard.sh
@@ -21,7 +21,7 @@ test_expect_success setup '
 	rm -f hello &&
 	mkdir -p hello &&
 	>hello/world &&
-	test "$(git ls-files -o)" = hello/world
+	test "$(git ls-files -o --exclude-standard)" = hello/world
 
 '
 
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index f3af10fb7e..0505da78e8 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -2069,3 +2069,11 @@ test_trailing_hash () {
 test_redact_non_printables () {
     tr -d "\n\r" | tr "[\001-\040][\177-\377]" "."
 }
+
+# Remove .gitconfig entries from a file in place.  test-lib.sh may
+# create $HOME/.gitconfig (e.g. to set safe.bareRepository) which
+# can appear in ls-files or status output.
+test_filter_gitconfig () {
+	sed "/\\.gitconfig/d" "$1" >"$1.filtered" &&
+	mv "$1.filtered" "$1"
+}
-- 
gitgitgadget


^ permalink raw reply related

* [PATCH 7/8] status tests: filter `.gitconfig` from status output
From: Johannes Schindelin via GitGitGadget @ 2026-04-24 15:01 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2098.git.1777042877.gitgitgadget@gmail.com>

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Since test-lib.sh creates `$HOME/.gitconfig` when
`WITH_BREAKING_CHANGES` is in effect, the file appears in `git
status` output as either untracked (`?? .gitconfig`) or ignored
(`!! .gitconfig` / `! .gitconfig`, depending on porcelain version),
because the `.git/info/exclude` entry causes git to treat it as an
ignored file rather than hiding it entirely.

In t7061 and t7521, which are pervasively affected, introduce a
`filter_gitconfig` helper that strips all status-prefix variants of
`.gitconfig` from the output before comparison. In the remaining
scripts (t7060, t7064, t7508), apply targeted adjustments.

Assisted-by: Claude Opus 4.6
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t7060-wtstatus.sh        |  3 +--
 t/t7061-wtstatus-ignore.sh | 27 +++++++++++++++++++++++++++
 t/t7064-wtstatus-pv2.sh    |  1 +
 t/t7508-status.sh          |  4 ++++
 t/t7521-ignored-mode.sh    |  1 +
 5 files changed, 34 insertions(+), 2 deletions(-)

diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh
index 0f4344c55e..942ddbbf0e 100755
--- a/t/t7060-wtstatus.sh
+++ b/t/t7060-wtstatus.sh
@@ -9,6 +9,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
 test_expect_success setup '
 	git config --global advice.statusuoption false &&
+	echo "/.gitconfig" >>.git/info/exclude &&
 	test_commit A &&
 	test_commit B oneside added &&
 	git checkout A^0 &&
@@ -221,7 +222,6 @@ test_expect_success 'status --branch with detached HEAD' '
 	git status --branch --porcelain >actual &&
 	cat >expected <<-EOF &&
 	## HEAD (no branch)
-	?? .gitconfig
 	?? actual
 	?? expect
 	?? expected
@@ -237,7 +237,6 @@ test_expect_success 'status --porcelain=v1 --branch with detached HEAD' '
 	git status --branch --porcelain=v1 >actual &&
 	cat >expected <<-EOF &&
 	## HEAD (no branch)
-	?? .gitconfig
 	?? actual
 	?? expect
 	?? expected
diff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh
index 2f9bea9793..14ddaba2f3 100755
--- a/t/t7061-wtstatus-ignore.sh
+++ b/t/t7061-wtstatus-ignore.sh
@@ -18,6 +18,7 @@ test_expect_success 'status untracked directory with --ignored' '
 	: >untracked/ignored &&
 	: >untracked/uncommitted &&
 	git status --porcelain --ignored >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -27,6 +28,7 @@ test_expect_success 'same with gitignore starting with BOM' '
 	: >untracked/ignored &&
 	: >untracked/uncommitted &&
 	git status --porcelain --ignored >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -40,18 +42,22 @@ test_expect_success 'status untracked files --ignored with pathspec (no match)'
 test_expect_success 'status untracked files --ignored with pathspec (literal match)' '
 	git status --porcelain --ignored -- untracked/ignored >actual &&
 	echo "!! untracked/ignored" >expected &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual &&
 	git status --porcelain --ignored -- untracked/uncommitted >actual &&
 	echo "?? untracked/uncommitted" >expected &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
 test_expect_success 'status untracked files --ignored with pathspec (glob match)' '
 	git status --porcelain --ignored -- untracked/i\* >actual &&
 	echo "!! untracked/ignored" >expected &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual &&
 	git status --porcelain --ignored -- untracked/u\* >actual &&
 	echo "?? untracked/uncommitted" >expected &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -65,6 +71,7 @@ EOF
 
 test_expect_success 'status untracked directory with --ignored -u' '
 	git status --porcelain --ignored -u >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 cat >expected <<\EOF
@@ -76,9 +83,11 @@ test_expect_success 'status of untracked directory with --ignored works with or
 	git status --porcelain --ignored >tmp &&
 	grep untracked/ tmp >actual &&
 	rm tmp &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual &&
 
 	git status --porcelain --ignored untracked/ >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -89,6 +98,7 @@ EOF
 
 test_expect_success 'status prefixed untracked sub-directory with --ignored -u' '
 	git status --porcelain --ignored -u untracked/ >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -104,6 +114,7 @@ test_expect_success 'status ignored directory with --ignore' '
 	mkdir ignored &&
 	: >ignored/uncommitted &&
 	git status --porcelain --ignored >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -116,6 +127,7 @@ EOF
 
 test_expect_success 'status ignored directory with --ignore -u' '
 	git status --porcelain --ignored -u >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -130,6 +142,7 @@ test_expect_success 'status empty untracked directory with --ignore' '
 	mkdir untracked-ignored &&
 	mkdir untracked-ignored/test &&
 	git status --porcelain --ignored >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -141,6 +154,7 @@ EOF
 
 test_expect_success 'status empty untracked directory with --ignore -u' '
 	git status --porcelain --ignored -u >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -155,6 +169,7 @@ test_expect_success 'status untracked directory with ignored files with --ignore
 	: >untracked-ignored/ignored &&
 	: >untracked-ignored/test/ignored &&
 	git status --porcelain --ignored >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -168,6 +183,7 @@ EOF
 
 test_expect_success 'status untracked directory with ignored files with --ignore -u' '
 	git status --porcelain --ignored -u >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -185,6 +201,7 @@ test_expect_success 'status ignored tracked directory with --ignore' '
 	git commit -m. &&
 	echo "tracked" >.gitignore &&
 	git status --porcelain --ignored >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -196,6 +213,7 @@ EOF
 
 test_expect_success 'status ignored tracked directory with --ignore -u' '
 	git status --porcelain --ignored -u >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -208,6 +226,7 @@ EOF
 test_expect_success 'status ignored tracked directory and ignored file with --ignore' '
 	echo "committed" >>.gitignore &&
 	git status --porcelain --ignored >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -219,6 +238,7 @@ EOF
 
 test_expect_success 'status ignored tracked directory and ignored file with --ignore -u' '
 	git status --porcelain --ignored -u >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -233,6 +253,7 @@ test_expect_success 'status ignored tracked directory and uncommitted file with
 	echo "tracked" >.gitignore &&
 	: >tracked/uncommitted &&
 	git status --porcelain --ignored >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -245,6 +266,7 @@ EOF
 
 test_expect_success 'status ignored tracked directory and uncommitted file with --ignore -u' '
 	git status --porcelain --ignored -u >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -260,6 +282,7 @@ test_expect_success 'status ignored tracked directory with uncommitted file in u
 	mkdir tracked/ignored &&
 	: >tracked/ignored/uncommitted &&
 	git status --porcelain --ignored >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -272,6 +295,7 @@ EOF
 
 test_expect_success 'status ignored tracked directory with uncommitted file in untracked subdir with --ignore -u' '
 	git status --porcelain --ignored -u >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -287,6 +311,7 @@ test_expect_success 'status ignored tracked directory with uncommitted file in t
 	git add -f tracked/ignored/committed &&
 	git commit -m. &&
 	git status --porcelain --ignored >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -299,6 +324,7 @@ EOF
 
 test_expect_success 'status ignored tracked directory with uncommitted file in tracked subdir with --ignore -u' '
 	git status --porcelain --ignored -u >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
@@ -310,6 +336,7 @@ test_expect_success 'status ignores submodule in excluded directory' '
 	git init tracked/submodule &&
 	test_commit -C tracked/submodule initial &&
 	git status --porcelain --ignored -u tracked/submodule >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expected actual
 '
 
diff --git a/t/t7064-wtstatus-pv2.sh b/t/t7064-wtstatus-pv2.sh
index 8bbc5ce6d9..be6c931a96 100755
--- a/t/t7064-wtstatus-pv2.sh
+++ b/t/t7064-wtstatus-pv2.sh
@@ -231,6 +231,7 @@ test_expect_success 'ignored files are printed with --ignored' '
 	EOF
 
 	git status --porcelain=v2 --ignored --untracked-files=all >actual &&
+	test_filter_gitconfig actual &&
 	test_cmp expect actual
 '
 
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index a5e21bf8bf..5f76ec62d8 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -263,6 +263,7 @@ test_expect_success 'status with gitignore' '
 	!! untracked
 	EOF
 	git status -s --ignored >output &&
+	test_filter_gitconfig output &&
 	test_cmp expect output &&
 
 	cat >expect <<\EOF &&
@@ -296,6 +297,7 @@ Ignored files:
 
 EOF
 	git status --ignored >output &&
+	test_filter_gitconfig output &&
 	test_cmp expect output
 '
 
@@ -328,6 +330,7 @@ test_expect_success 'status with gitignore (nothing untracked)' '
 	!! untracked
 	EOF
 	git status -s --ignored >output &&
+	test_filter_gitconfig output &&
 	test_cmp expect output &&
 
 	cat >expect <<\EOF &&
@@ -358,6 +361,7 @@ Ignored files:
 
 EOF
 	git status --ignored >output &&
+	test_filter_gitconfig output &&
 	test_cmp expect output
 '
 
diff --git a/t/t7521-ignored-mode.sh b/t/t7521-ignored-mode.sh
index a88b02b06e..7ea0b0d2f2 100755
--- a/t/t7521-ignored-mode.sh
+++ b/t/t7521-ignored-mode.sh
@@ -30,6 +30,7 @@ test_expect_success 'Verify behavior of status on directories with ignored files
 		dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign &&
 
 	git status --porcelain=v2 --ignored=matching --untracked-files=all >output &&
+	test_filter_gitconfig output &&
 	test_cmp expect output
 '
 
-- 
gitgitgadget


^ permalink raw reply related

* [PATCH 8/8] safe.bareRepository: default to "explicit" with WITH_BREAKING_CHANGES
From: Johannes Schindelin via GitGitGadget @ 2026-04-24 15:01 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2098.git.1777042877.gitgitgadget@gmail.com>

From: Johannes Schindelin <johannes.schindelin@gmx.de>

When an attacker can convince a user to clone a crafted repository
that contains an embedded bare repository with malicious hooks, any Git
command the user runs after entering that subdirectory will discover
the bare repository and execute the hooks. The user does not even need
to run a Git command explicitly: many shell prompts run `git status`
in the background to display branch and dirty state information, and
`git status` in turn may invoke the fsmonitor hook if so configured,
making the user vulnerable the moment they `cd` into the directory. The
`safe.bareRepository` configuration variable (introduced in 8959555cee7e
(setup_git_directory(): add an owner check for the top-level directory,
2022-03-02)) already provides protection against this attack vector by
allowing users to set it to "explicit", but the default remained "all"
for backwards compatibility.

Since Git 3.0 is the natural point to change defaults to safer
values, flip the default from "all" to "explicit" when built with
`WITH_BREAKING_CHANGES`. This means Git will refuse to work with bare
repositories that are discovered implicitly by walking up the directory
tree. Bare repositories specified via `--git-dir` or `GIT_DIR` continue
to work, and directories that look like `.git`, worktrees, or submodule
directories are unaffected (the existing `is_implicit_bare_repo()`
whitelist handles those cases).

Users who rely on implicit bare repository discovery can restore the
previous behavior by setting `safe.bareRepository=all` in their global
or system configuration.

The test for the "safe.bareRepository in the repository" scenario
needed a more involved fix: it writes a `safe.bareRepository=all`
entry into the bare repository's own config to verify that repo-local
config does not override the protected (global) setting. Previously,
`test_config -C` was used to write that entry, but its cleanup runs `git
-C <bare-repo> config --unset`, which itself fails when the default is
"explicit" and the global config has already been cleaned up. Switching
to direct git config --file access avoids going through repository
discovery entirely.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/BreakingChanges.adoc | 24 ++++++++++++++++++++++++
 Documentation/config/safe.adoc     | 10 ++++++++--
 setup.c                            |  4 ++++
 t/t0035-safe-bare-repository.sh    | 10 ++++++++--
 4 files changed, 44 insertions(+), 4 deletions(-)

diff --git a/Documentation/BreakingChanges.adoc b/Documentation/BreakingChanges.adoc
index af59c43f42..73bb939359 100644
--- a/Documentation/BreakingChanges.adoc
+++ b/Documentation/BreakingChanges.adoc
@@ -216,6 +216,30 @@ would be significant, we may decide to defer this change to a subsequent minor
 release. This evaluation will also take into account our own experience with
 how painful it is to keep Rust an optional component.
 
+* The default value of `safe.bareRepository` will change from `all` to
+  `explicit`. It is all too easy for an attacker to trick a user into cloning a
+  repository that contains an embedded bare repository with malicious hooks
+  configured. If the user enters that subdirectory and runs any Git command, Git
+  discovers the bare repository and the hooks fire. The user does not even need
+  to run a Git command explicitly: many shell prompts run `git status` in the
+  background to display branch and dirty state information, and `git status` in
+  turn may invoke the fsmonitor hook if so configured, making the user
+  vulnerable the moment they `cd` into the directory. The `safe.bareRepository`
+  configuration variable was introduced in 8959555cee (setup_git_directory():
+  add an owner check for the top-level directory, 2022-03-02) with a default of
+  `all` to preserve backwards compatibility.
++
+Changing the default to `explicit` means that Git will refuse to work with bare
+repositories that are discovered implicitly by walking up the directory tree.
+Bare repositories specified explicitly via the `--git-dir` command-line option
+or the `GIT_DIR` environment variable continue to work regardless of this
+setting. Repositories that look like a `.git` directory, a worktree, or a
+submodule directory are also unaffected.
++
+Users who rely on implicit discovery of bare repositories can restore the
+previous behavior by setting `safe.bareRepository=all` in their global or
+system configuration.
+
 === Removals
 
 * Support for grafting commits has long been superseded by git-replace(1).
diff --git a/Documentation/config/safe.adoc b/Documentation/config/safe.adoc
index 2d45c98b12..5b1690aebe 100644
--- a/Documentation/config/safe.adoc
+++ b/Documentation/config/safe.adoc
@@ -2,10 +2,12 @@ safe.bareRepository::
 	Specifies which bare repositories Git will work with. The currently
 	supported values are:
 +
-* `all`: Git works with all bare repositories. This is the default.
+* `all`: Git works with all bare repositories. This is the default in
+  Git 2.x.
 * `explicit`: Git only works with bare repositories specified via
   the top-level `--git-dir` command-line option, or the `GIT_DIR`
-  environment variable (see linkgit:git[1]).
+  environment variable (see linkgit:git[1]). This will be the default
+  in Git 3.0.
 +
 If you do not use bare repositories in your workflow, then it may be
 beneficial to set `safe.bareRepository` to `explicit` in your global
@@ -13,6 +15,10 @@ config. This will protect you from attacks that involve cloning a
 repository that contains a bare repository and running a Git command
 within that directory.
 +
+If you use bare repositories regularly and want to preserve the current
+behavior after upgrading to Git 3.0, set `safe.bareRepository` to `all`
+in your global or system config.
++
 This config setting is only respected in protected configuration (see
 <<SCOPES>>). This prevents untrusted repositories from tampering with
 this value.
diff --git a/setup.c b/setup.c
index 7ec4427368..17c0662076 100644
--- a/setup.c
+++ b/setup.c
@@ -1485,7 +1485,11 @@ static int allowed_bare_repo_cb(const char *key, const char *value,
 
 static enum allowed_bare_repo get_allowed_bare_repo(void)
 {
+#ifdef WITH_BREAKING_CHANGES
+	enum allowed_bare_repo result = ALLOWED_BARE_REPO_EXPLICIT;
+#else
 	enum allowed_bare_repo result = ALLOWED_BARE_REPO_ALL;
+#endif
 	git_protected_config(allowed_bare_repo_cb, &result);
 	return result;
 }
diff --git a/t/t0035-safe-bare-repository.sh b/t/t0035-safe-bare-repository.sh
index ae7ef092ab..1d3d19f5b4 100755
--- a/t/t0035-safe-bare-repository.sh
+++ b/t/t0035-safe-bare-repository.sh
@@ -44,11 +44,16 @@ test_expect_success 'setup an embedded bare repo, secondary worktree and submodu
 	test_path_is_dir outer-repo/.git/modules/subn
 '
 
-test_expect_success 'safe.bareRepository unset' '
+test_expect_success !WITH_BREAKING_CHANGES 'safe.bareRepository unset' '
 	test_unconfig --global safe.bareRepository &&
 	expect_accepted_implicit -C outer-repo/bare-repo
 '
 
+test_expect_success WITH_BREAKING_CHANGES 'safe.bareRepository unset (defaults to explicit)' '
+	test_unconfig --global safe.bareRepository &&
+	expect_rejected -C outer-repo/bare-repo
+'
+
 test_expect_success 'safe.bareRepository=all' '
 	test_config_global safe.bareRepository all &&
 	expect_accepted_implicit -C outer-repo/bare-repo
@@ -63,7 +68,8 @@ test_expect_success 'safe.bareRepository in the repository' '
 	# safe.bareRepository must not be "explicit", otherwise
 	# git config fails with "fatal: not in a git directory" (like
 	# safe.directory)
-	test_config -C outer-repo/bare-repo safe.bareRepository all &&
+	test_when_finished "git config --file outer-repo/bare-repo/config --unset safe.bareRepository" &&
+	git config --file outer-repo/bare-repo/config safe.bareRepository all &&
 	test_config_global safe.bareRepository explicit &&
 	expect_rejected -C outer-repo/bare-repo
 '
-- 
gitgitgadget

^ permalink raw reply related

* [PATCH 4/8] t1305: use `--git-dir=.` for bare repo in include cycle test
From: Johannes Schindelin via GitGitGadget @ 2026-04-24 15:01 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2098.git.1777042877.gitgitgadget@gmail.com>

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Earlier tests in t1305 overwrite `$HOME/.gitconfig` with their own
content as part of testing config includes. This clobbers the
`safe.bareRepository=all` entry that test-lib.sh writes when
`WITH_BREAKING_CHANGES` is in effect, causing `git -C cycle config`
to fail with "not in a git directory" when it tries to access the
bare repository created by `git init --bare cycle`.

Use `--git-dir=.` to access the bare repo explicitly, avoiding the
dependency on global config for repository discovery.

Assisted-by: Claude Opus 4.6
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t1305-config-include.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
index 6e51f892f3..f3892578e4 100755
--- a/t/t1305-config-include.sh
+++ b/t/t1305-config-include.sh
@@ -350,9 +350,9 @@ test_expect_success 'conditional include, onbranch, implicit /** for /' '
 
 test_expect_success 'include cycles are detected' '
 	git init --bare cycle &&
-	git -C cycle config include.path cycle &&
+	git -C cycle --git-dir=. config include.path cycle &&
 	git config -f cycle/cycle include.path config &&
-	test_must_fail git -C cycle config --get-all test.value 2>stderr &&
+	test_must_fail git -C cycle --git-dir=. config --get-all test.value 2>stderr &&
 	grep "exceeded maximum include depth" stderr
 '
 
-- 
gitgitgadget


^ permalink raw reply related

* [PATCH 5/8] t5601: restore `.gitconfig` after includeIf test
From: Johannes Schindelin via GitGitGadget @ 2026-04-24 15:01 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2098.git.1777042877.gitgitgadget@gmail.com>

From: Johannes Schindelin <johannes.schindelin@gmx.de>

One test in t5601 overwrites `$HOME/.gitconfig` with an `includeIf`
configuration snippet and removes the file in its cleanup. This
destroys the `safe.bareRepository=all` entry that test-lib.sh
writes when `WITH_BREAKING_CHANGES` is in effect, causing later
tests that use `git -C <bare-repo> config` to fail with "not in a
git directory".

Back up `.gitconfig` before overwriting and restore it in the
cleanup, so the global config survives into subsequent tests.

Assisted-by: Claude Opus 4.6
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t5601-clone.sh | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index d743d986c4..3dd229c186 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -813,7 +813,9 @@ test_expect_success 'clone with includeIf' '
 	test_when_finished "rm -rf repo \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\"" &&
 	git clone --bare --no-local src "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
 
-	test_when_finished "rm \"$HOME\"/.gitconfig" &&
+	test_when_finished "cp \"$HOME\"/.gitconfig.bak \
+		\"$HOME\"/.gitconfig 2>/dev/null || rm -f \"$HOME\"/.gitconfig" &&
+	cp "$HOME"/.gitconfig "$HOME"/.gitconfig.bak 2>/dev/null &&
 	cat >"$HOME"/.gitconfig <<-EOF &&
 	[includeIf "onbranch:something"]
 		path = /does/not/exist.inc
-- 
gitgitgadget


^ permalink raw reply related

* [PATCH 0/8] safe.bareRepository: default to "explicit" with WITH_BREAKING_CHANGES
From: Johannes Schindelin via GitGitGadget @ 2026-04-24 15:01 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin

This supersedes my earlier series [*1*] which took the approach of adjusting
individual tests to access bare repositories explicitly.

As Junio suggested [*2*], this series instead takes the approach of setting
safe.bareRepository=all in the test environment's global config whenever
WITH_BREAKING_CHANGES is in effect, so that existing tests continue to work
without individual modifications.

Implementing this turned out to require a number of follow-up adjustments,
because writing to $HOME/.gitconfig has side effects beyond the intended
setting: $HOME is the trash directory, which doubles as the test
repository's working tree, so the file shows up in ls-files and status
output, and tests that manipulate $HOME/.gitconfig for their own purposes
can clobber or remove the setting. Patches 2 through 7 address these
interactions in the affected test scripts.

The final patch flips the safe.bareRepository default to "explicit" under
WITH_BREAKING_CHANGES.

Footnote [*1*]:
https://lore.kernel.org/git/pull.2076.git.1775140403.gitgitgadget@gmail.com/

Footnote [*2*]: https://lore.kernel.org/git/xmqqse98cc51.fsf@gitster.g/

Johannes Schindelin (8):
  test-lib: allow bare repository access when breaking changes are
    enabled
  t7900: do not let `$HOME/.gitconfig` interfere with XDG tests
  t1300: remove global config settings injected by test-lib.sh
  t1305: use `--git-dir=.` for bare repo in include cycle test
  t5601: restore `.gitconfig` after includeIf test
  ls-files tests: filter `.gitconfig` from `--others` output
  status tests: filter `.gitconfig` from status output
  safe.bareRepository: default to "explicit" with WITH_BREAKING_CHANGES

 Documentation/BreakingChanges.adoc            | 24 +++++++++++++++++
 Documentation/config/safe.adoc                | 10 +++++--
 setup.c                                       |  4 +++
 t/t0035-safe-bare-repository.sh               | 10 +++++--
 t/t1300-config.sh                             |  7 +++++
 t/t1305-config-include.sh                     |  4 +--
 t/t3000-ls-files-others.sh                    |  4 +++
 t/t3001-ls-files-others-exclude.sh            |  3 +++
 t/t3002-ls-files-dashpath.sh                  |  2 ++
 t/t3009-ls-files-others-nonsubmodule.sh       |  1 +
 ...common-prefixes-and-directory-traversal.sh |  3 ++-
 t/t5601-clone.sh                              |  4 ++-
 t/t7060-wtstatus.sh                           |  3 +--
 t/t7061-wtstatus-ignore.sh                    | 27 +++++++++++++++++++
 t/t7064-wtstatus-pv2.sh                       |  1 +
 t/t7104-reset-hard.sh                         |  2 +-
 t/t7508-status.sh                             |  4 +++
 t/t7521-ignored-mode.sh                       |  1 +
 t/t7900-maintenance.sh                        | 12 +++++++--
 t/test-lib-functions.sh                       |  8 ++++++
 t/test-lib.sh                                 |  6 +++++
 21 files changed, 127 insertions(+), 13 deletions(-)


base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2098%2Fdscho%2Fsafe-bare-repo-default-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2098/dscho/safe-bare-repo-default-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/2098
-- 
gitgitgadget

^ permalink raw reply

* [PATCH] alias: restore support for simple dotted aliases
From: Jonatan Holmgren @ 2026-04-24 15:10 UTC (permalink / raw)
  To: git; +Cc: peff, rsch, michael.grossfeld, Jonatan Holmgren
In-Reply-To: <PH7PR12MB73313034573C59C73F821BBFE52A2@PH7PR12MB7331.namprd12.prod.outlook.com>

Historically, config entries like alias.foo.bar expanded the alias
"foo.bar". The subsection-based alias syntax introduced in
ac1f12a9de (alias: support non-alphanumeric names via subsection
syntax, 2026-02-18) broke that behavior by treating such entries as
if they were subsection syntax.

Restore support for the old dotted form by falling back to the full
name when the final key is not "command". Add tests covering execution
and help output for simple dotted aliases.

Reported-by: Michael Grossfeld <michael.grossfeld@amd.com>
Helped-by: Jeff King <peff@peff.net>
---
 alias.c          | 16 ++++++++++++++--
 help.c           |  9 ++++++++-
 t/t0014-alias.sh | 12 ++++++++++++
 3 files changed, 34 insertions(+), 3 deletions(-)

diff --git a/alias.c b/alias.c
index ec9833dd30..e737c49edd 100644
--- a/alias.c
+++ b/alias.c
@@ -34,8 +34,20 @@ static int config_alias_cb(const char *var, const char *value,
 	if (subsection && !subsection_len)
 		subsection = NULL;
 
-	if (subsection && strcmp(key, "command"))
-		return 0;
+	if (subsection && strcmp(key, "command")) {
+		/*
+		 * We have historically supported the "alias.name" form when
+		 * "name" happens to contain dots (e.g., alias.foo.bar to allow
+		 * "git foo.bar". But our parsing above would split that into
+		 * subsection "foo".
+		 *
+		 * If we do not understand the final key in a subsection-style
+		 * variable, fall back to treating it as a two-level alias.
+		 */
+		key = var + strlen("alias.");
+		subsection = NULL;
+		subsection_len = 0;
+	}
 
 	if (data->alias) {
 		int match;
diff --git a/help.c b/help.c
index 3e59d07c37..46241492ce 100644
--- a/help.c
+++ b/help.c
@@ -592,14 +592,21 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 	/* Also use aliases for command lookup */
 	if (!parse_config_key(var, "alias", &subsection, &subsection_len,
 			      &key)) {
+		size_t key_len = strlen(key);
+
 		if (subsection) {
 			/* [alias "name"] command = value */
 			if (!strcmp(key, "command"))
 				add_cmdname(&cfg->aliases, subsection,
 					    subsection_len);
+			else {
+				key = var + strlen("alias.");
+				key_len = strlen(key);
+				add_cmdname(&cfg->aliases, key, key_len);
+			}
 		} else {
 			/* alias.name = value */
-			add_cmdname(&cfg->aliases, key, strlen(key));
+			add_cmdname(&cfg->aliases, key, key_len);
 		}
 	}
 
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 68b4903cbf..5144b0effd 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -128,6 +128,12 @@ test_expect_success 'subsection syntax works' '
 	test_grep "ran-subsection" output
 '
 
+test_expect_success 'simple dotted alias syntax still works' '
+	test_config alias.simple.dotted "!echo ran-simple-dotted" &&
+	git simple.dotted >output &&
+	test_grep "ran-simple-dotted" output
+'
+
 test_expect_success 'subsection syntax only accepts command key' '
 	test_config alias.invalid.notcommand value &&
 	test_must_fail git invalid 2>error &&
@@ -183,6 +189,12 @@ test_expect_success 'subsection aliases listed in help -a' '
 	test_grep "förgrena" output
 '
 
+test_expect_success 'simple dotted aliases listed in help -a' '
+	test_config alias.simple.listed "!echo test" &&
+	git help -a >output &&
+	test_grep "simple.listed" output
+'
+
 test_expect_success 'empty subsection treated as no subsection' '
 	test_config "alias..something" "!echo foobar" &&
 	git something >actual &&
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH v14 5/5] checkout -m: autostash when switching branches
From: Phillip Wood @ 2026-04-24 15:47 UTC (permalink / raw)
  To: Harald Nordgren via GitGitGadget, git
  Cc: Chris Torek, Jeff King, Harald Nordgren
In-Reply-To: <86f33df1eb043d92cc626092d512aec670c89bdb.1776270259.git.gitgitgadget@gmail.com>

Hi Harald

On 15/04/2026 17:24, Harald Nordgren via GitGitGadget wrote:
(trimming the documentation - I'll try and look at that next time)

> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index c80c62b37b..55c4db04c6 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -17,7 +17,6 @@
>   #include "merge-ll.h"
>   #include "lockfile.h"
>   #include "mem-pool.h"
> -#include "merge-ort-wrappers.h"
>   #include "object-file.h"
>   #include "object-name.h"
>   #include "odb.h"
> @@ -30,6 +29,7 @@
>   #include "repo-settings.h"
>   #include "resolve-undo.h"
>   #include "revision.h"
> +#include "sequencer.h"
>   #include "setup.h"
>   #include "submodule.h"
>   #include "symlinks.h"
> @@ -853,90 +853,8 @@ static int merge_working_tree(const struct checkout_opts *opts,
>   		ret = unpack_trees(2, trees, &topts);
>   		clear_unpack_trees_porcelain(&topts);
>   		if (ret == -1) {
> -			/*
> -			 * Unpack couldn't do a trivial merge; either
> -			 * give up or do a real merge, depending on
> -			 * whether the merge flag was used.
> -			 */
> -			struct tree *work;
> -			struct tree *old_tree;
> -			struct merge_options o;
> -			struct strbuf sb = STRBUF_INIT;
> -			struct strbuf old_commit_shortname = STRBUF_INIT;
> -
> -			if (!opts->merge) {
> -				rollback_lock_file(&lock_file);
> -				return 1;
> -			}
> -
> -			/*
> -			 * Without old_branch_info->commit, the below is the same as
> -			 * the two-tree unpack we already tried and failed.
> -			 */
> -			if (!old_branch_info->commit) {
> -				rollback_lock_file(&lock_file);
> -				return 1;
> -			}
> -			old_tree = repo_get_commit_tree(the_repository,
> -							old_branch_info->commit);
> -
> -			if (repo_index_has_changes(the_repository, old_tree, &sb))
> -				die(_("cannot continue with staged changes in "
> -				      "the following files:\n%s"), sb.buf);
> -			strbuf_release(&sb);
> -
> -			/* Do more real merge */
> -
> -			/*
> -			 * We update the index fully, then write the
> -			 * tree from the index, then merge the new
> -			 * branch with the current tree, with the old
> -			 * branch as the base. Then we reset the index
> -			 * (but not the working tree) to the new
> -			 * branch, leaving the working tree as the
> -			 * merged version, but skipping unmerged
> -			 * entries in the index.
> -			 */
> -
> -			add_files_to_cache(the_repository, NULL, NULL, NULL, 0,
> -					0, 0);
> -			init_ui_merge_options(&o, the_repository);
> -			o.verbosity = 0;
> -			work = write_in_core_index_as_tree(the_repository,
> -							   the_repository->index);
> -
> -			ret = reset_tree(new_tree,
> -					 opts, 1,
> -					 writeout_error, new_branch_info);
> -			if (ret) {
> -				rollback_lock_file(&lock_file);
> -				return ret;
> -			}
> -			o.ancestor = old_branch_info->name;
> -			if (!old_branch_info->name) {
> -				strbuf_add_unique_abbrev(&old_commit_shortname,
> -							 &old_branch_info->commit->object.oid,
> -							 DEFAULT_ABBREV);
> -				o.ancestor = old_commit_shortname.buf;
> -			}
> -			o.branch1 = new_branch_info->name;
> -			o.branch2 = "local";
> -			o.conflict_style = opts->conflict_style;
> -			ret = merge_ort_nonrecursive(&o,
> -						     new_tree,
> -						     work,
> -						     old_tree);
> -			if (ret < 0)
> -				die(NULL);
> -			ret = reset_tree(new_tree,
> -					 opts, 0,
> -					 writeout_error, new_branch_info);
> -			strbuf_release(&o.obuf);
> -			strbuf_release(&old_commit_shortname);
> -			if (ret) {
> -				rollback_lock_file(&lock_file);
> -				return ret;
> -			}
> +			rollback_lock_file(&lock_file);
> +			return ret;

ret is -1 so we return the same value if unpack_trees() fails as do the 
checks at the top of the function do when they fail with "return 
error(...)". Therefore we cannot determine whether a failure of this 
function is due to unpack_trees() or not and so we wont know whether to 
autostash or not. You need to return a unique value here like -2 (or 
ideally a named constant)

>   		}
>   	}
>   
> @@ -1181,6 +1099,10 @@ static int switch_branches(const struct checkout_opts *opts,
>   	struct object_id rev;
>   	int flag, writeout_error = 0;
>   	int do_merge = 1;
> +	int created_autostash = 0;
> +	struct strbuf old_commit_shortname = STRBUF_INIT;
> +	struct strbuf autostash_msg = STRBUF_INIT;
> +	const char *stash_label_base = NULL;
>   
>   	trace2_cmd_mode("branch");
>   
> @@ -1218,11 +1140,39 @@ static int switch_branches(const struct checkout_opts *opts,
>   			do_merge = 0;
>   	}
>   
> +	if (old_branch_info.name)
> +		stash_label_base = old_branch_info.name;
> +	else if (old_branch_info.commit) {

Style: if one branch of an if statement has braces then all branch should.

> +		strbuf_add_unique_abbrev(&old_commit_shortname,
> +					 &old_branch_info.commit->object.oid,
> +					 DEFAULT_ABBREV);
> +		stash_label_base = old_commit_shortname.buf;
> +	}
> +
>   	if (do_merge) {
>   		ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
> +		if (ret == -1 && opts->merge) {
> +			strbuf_addf(&autostash_msg,
> +				    "autostash while switching to '%s'",
> +				    new_branch_info->name);
> +			create_autostash_ref(the_repository,
> +					     "CHECKOUT_AUTOSTASH_HEAD",
> +					     autostash_msg.buf, true);
> +			created_autostash = 1;
> +			ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
> +		}
>   		if (ret) {
> +			if (created_autostash)
> +				apply_autostash_ref(the_repository,
> +						    "CHECKOUT_AUTOSTASH_HEAD",
> +						    new_branch_info->name,
> +						    "local",
> +						    stash_label_base,
> +						    autostash_msg.buf);

Good - now we only try to restore the stashed changes if we actually 
stashed. However we only restore the stashed changes if there was an 
error(). If there isn't an error we call update_refs_for_switch() before 
restoring them. It would be safer to restore them straight away in case 
that function ends up dying for any reason (though I think that's pretty 
unlikely)

	if (created_autostash) {
		if (opts->conflict_style >= 0)
			/* set up confilct style */
		apply_autostash_ref(...);
	}
	if (ret) {

>   			branch_info_release(&old_branch_info);
> -			return ret;
> +			strbuf_release(&old_commit_shortname);
> +			strbuf_release(&autostash_msg);
> +			return ret < 0 ? 1 : ret;

This changes the return value for all errors from merge_working_tree() - 
that's probably a good this as this value is used for the exit code and 
we don't really want an exit code of -1

>   		}
>   	}
>   
> @@ -1231,8 +1181,30 @@ static int switch_branches(const struct checkout_opts *opts,
>   
>   	update_refs_for_switch(opts, &old_branch_info, new_branch_info);
>   
> +	if (opts->conflict_style >= 0) {
> +		struct strbuf cfg = STRBUF_INIT;
> +		strbuf_addf(&cfg, "merge.conflictStyle=%s",
> +			    conflict_style_name(opts->conflict_style));
> +		git_config_push_parameter(cfg.buf);
> +		strbuf_release(&cfg);
> +	}
> +	apply_autostash_ref(the_repository, "CHECKOUT_AUTOSTASH_HEAD",
> +			    new_branch_info->name, "local",
> +			    stash_label_base,
> +			    autostash_msg.buf);> +	discard_index(the_repository->index);

As I said last time we should not be calling apply_autostash() if we 
have not created an autostash. We should also not discard and re-read 
the index if we haven't stashed. I do think we'd be better restoring the 
stashed changes in a single place as I said above.

> +	if (repo_read_index(the_repository) < 0)
> +		die(_("index file corrupt"));
> +
> +	if (created_autostash && !opts->quiet && new_branch_info->commit)
> +		show_local_changes(&new_branch_info->commit->object,
> +				   &opts->diff_options);

This shows the local changes, but it doesn't give any explanation of 
what the output is. For example when switching branches with a conflict 
I see

Your local changes are stashed, however, applying it to carry
forward your local changes resulted in conflicts:

  - You can try resolving them now.  If you resolved them
    successfully, discard the stash entry with "git stash drop".

  - Alternatively you can "git reset --hard" if you do not want
    to deal with them right now, and later "git stash pop" to
    recover your local changes.
M	t/t7201-co.sh

where the changes appear to be part of the advice message. Perhaps we 
should print a short (i.e. one sentance) message along the lines of

	The following paths have local changes

We should test what the user sees here as well.

> +
>   	ret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);
>   	branch_info_release(&old_branch_info);
> +	strbuf_release(&old_commit_shortname);
> +	strbuf_release(&autostash_msg);
>   
>   	return ret || writeout_error;
>   }
> diff --git a/sequencer.c b/sequencer.c
> index 7c0376d9e4..480e8e6c0b 100644
> --- a/sequencer.c
> +++ b/sequencer.c
> @@ -4765,15 +4765,23 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,
>   		strvec_push(&store.args, stash_oid);
>   		if (run_command(&store))
>   			ret = error(_("cannot store %s"), stash_oid);
> +		else if (attempt_apply)
> +			fprintf(stderr,
> +				_("Your local changes are stashed, however, applying it to carry\n"
> +				  "forward your local changes resulted in conflicts:\n"

I'm not sure we need to say "local changes" twice here

	Your local changes are stashed, however applying them
	resulted in conflicts.

> +				  "\n"
> +				  " - You can try resolving them now.  If you resolved them\n"
> +				  "   successfully, discard the stash entry with \"git stash drop\".\n"

s/resolved/resolve/

> +				  "\n"
> +				  " - Alternatively you can \"git reset --hard\" if you do not want\n"
> +				  "   to deal with them right now, and later \"git stash pop\" to\n"
> +				  "   recover your local changes.\n"));

I find the bulleted list a bit odd, maybe

	You can either resolve the conflicts and then discard the stash
  	with "git stash drop", or, if you do not want to resolve them
	now, run "git reset --hard" and apply the local changes later by
	running "git stash pop"

would be better?

>   		else
>   			fprintf(stderr,
> -				_("%s\n"
> +				_("Autostash exists; creating a new stash entry.\n"
>   				  "Your changes are safe in the stash.\n"
>   				  "You can run \"git stash pop\" or"
> -				  " \"git stash drop\" at any time.\n"),
> -				attempt_apply ?
> -				_("Applying autostash resulted in conflicts.") :
> -				_("Autostash exists; creating a new stash entry."));
> +				  " \"git stash drop\" at any time.\n"));
>   	}
>   
>   	return ret;
> diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh
> index ad3ba6a984..e4e2cb19ce 100755
> --- a/t/t3420-rebase-autostash.sh
> +++ b/t/t3420-rebase-autostash.sh
> @@ -61,18 +61,30 @@ create_expected_failure_apply () {
>   	First, rewinding head to replay your work on top of it...
>   	Applying: second commit
>   	Applying: third commit
> -	Applying autostash resulted in conflicts.
> -	Your changes are safe in the stash.
> -	You can run "git stash pop" or "git stash drop" at any time.
> +	Your local changes are stashed, however, applying it to carry
> +	forward your local changes resulted in conflicts:
> +
> +	 - You can try resolving them now.  If you resolved them
> +	   successfully, discard the stash entry with "git stash drop".
> +
> +	 - Alternatively you can "git reset --hard" if you do not want
> +	   to deal with them right now, and later "git stash pop" to
> +	   recover your local changes.
>   	EOF
>   }
>   
>   create_expected_failure_merge () {
>   	cat >expected <<-EOF
>   	$(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual)
> -	Applying autostash resulted in conflicts.
> -	Your changes are safe in the stash.
> -	You can run "git stash pop" or "git stash drop" at any time.
> +	Your local changes are stashed, however, applying it to carry
> +	forward your local changes resulted in conflicts:
> +
> +	 - You can try resolving them now.  If you resolved them
> +	   successfully, discard the stash entry with "git stash drop".
> +
> +	 - Alternatively you can "git reset --hard" if you do not want
> +	   to deal with them right now, and later "git stash pop" to
> +	   recover your local changes.
>   	Successfully rebased and updated refs/heads/rebased-feature-branch.
>   	EOF
>   }
> diff --git a/t/t7201-co.sh b/t/t7201-co.sh
> index 9bcf7c0b40..c474c6759f 100755
> --- a/t/t7201-co.sh
> +++ b/t/t7201-co.sh
> @@ -210,6 +210,214 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '
>   	test_cmp expect two
>   '
>   
> +test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '
> +	git checkout -f main &&
> +	git reset --hard &&
> +	git clean -f &&
> +
> +	fill a b X d e >two &&
> +	git checkout --merge --conflict=zdiff3 simple &&

If I change "zdiff3" to "diff3" this test still passes which is 
disappointing. As the code that parses the conflict style is shared with 
other commands and we already have tests for --conflict=diff3 and 
--conflict=merge I'm not sure this test adds much.

> +
> +	cat <<-EOF >expect &&
> +	a
> +	<<<<<<< simple
> +	c
> +	||||||| main
> +	b
> +	c
> +	d
> +	=======
> +	b
> +	X
> +	d
> +	>>>>>>> local
> +	e
> +	EOF
> +	test_cmp expect two
> +'
> +
> +test_expect_success 'checkout -m respects merge.conflictStyle config' '

Looking at the existing tests, 'checkout with --merge, in diff3 -m 
style' and 'checkout --conflict=merge, overriding config' already test 
that we respect merge.conflictStyle and that --conflict overrides it so 
I don't see what new coverage this test adds.

> +	git checkout -f main &&
> +	git reset --hard &&
> +	git clean -f &&
> +
> +	test_config merge.conflictStyle diff3 &&
> +	fill b d >two &&
> +	git checkout -m simple &&
> +
> +	cat <<-EOF >expect &&
> +	<<<<<<< simple
> +	a
> +	c
> +	e
> +	||||||| main
> +	a
> +	b
> +	c
> +	d
> +	e
> +	=======
> +	b
> +	d
> +	>>>>>>> local
> +	EOF
> +	test_cmp expect two
> +'
> +
> +test_expect_success 'checkout -m skips stash when no conflict' '
> +	git checkout -f main &&
> +	git clean -f &&
> +
> +	fill 0 x y z >same &&
> +	git stash list >stash-before &&
> +	git checkout -m side >actual 2>&1 &&

file "same" is unchanged between branch "side" and "branch" main so we 
do not need to stash it.

> +	test_grep ! "Created autostash" actual &&
> +	git stash list >stash-after &&
> +	test_cmp stash-before stash-after &&
> +	fill 0 x y z >expect &&
> +	test_cmp expect same

Even if we created an autostash this test would not pick it up as the 
stash is not written to refs/stash unless there are merge conflicts and 
we don't print "Created autostash" even when we do create an autostash. 
The same is true for "checkout -m -b skips stash with dirty tree" below. 
I don't see how we can check that a stash was not created without using 
GIT_TRACE to see if we run "git stash". Even that is fragile as we might 
start stashing without forking a separate process in future.

> +'
> +
> +test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '
> +	git checkout -f main &&
> +	git clean -f &&
> +
> +	fill 0 x y z >same &&
> +	git add same &&
> +	git checkout -m side >actual 2>&1 &&
> +	test_grep ! "Created autostash" actual &&
> +	fill 0 x y z >expect &&
> +	test_cmp expect same
> +'
> +
> +test_expect_success 'checkout -m stashes and applies on conflicting changes' '
> +	git checkout -f main &&
> +	git clean -f &&
> +
> +	fill 1 2 3 4 5 6 7 >one &&
> +	git checkout -m side >actual 2>&1 &&
> +	test_grep ! "Created autostash" actual &&
> +	test_grep "Applied autostash" actual &&
> +	fill 1 2 3 4 5 6 7 >expect &&
> +	test_cmp expect one
> +'

I don't think the two tests above add any extra coverage when we have 
the one below so they can be deleted. Our test suite is slow enough 
already - we only need one test to fail for any given issue.

> +test_expect_success 'checkout -m with mixed staged and unstaged changes' '
> +	git checkout -f main &&
> +	git clean -f &&
> +
> +	fill 0 x y z >same &&
> +	git add same &&
> +	fill 1 2 3 4 5 6 7 >one &&
> +	git checkout -m side >actual 2>&1 &&
> +	test_grep ! "Created autostash" actual &&
> +	test_grep "Applied autostash" actual &&
> +	fill 0 x y z >expect &&
> +	test_cmp expect same &&
> +	fill 1 2 3 4 5 6 7 >expect &&
> +	test_cmp expect one
> +'
> +
> +test_expect_success 'checkout -m stashes on truly conflicting changes' '

This use of conflicting is rather confusing - what's the difference 
between a conflicting change and a truly conflicting change?

I think a single test is sufficient to check that we create a valid 
stash entry

test_expect_success 'checkout -m stashes on truly conflicting changes' '
	git checkout -f main &&
	git clean -f &&

	fill 1 2 3 4 5 >one &&
	test_must_fail git checkout side 2>stderr &&
	test_grep "Your local changes" stderr &&
	git checkout -m side >actual 2>&1 &&
	test_grep ! "Created autostash" actual &&
	test_grep "resulted in conflicts" actual &&
	test_grep "git stash drop" actual &&
	test_grep "recover your local changes" actual &&
	git show --format=%B --diff-merges=1 refs/stash >actual &&
	sed /^index/d actual >actual.trimmed &&
	cat >expect <<-EOF &&
	On main: autostash while switching to ${SQ}side${SQ}
	diff --git a/one b/one
	--- a/one
	+++ b/one
	@@ -3,6 +3,3 @@
	 3
	 4
	 5
	-6
	-7
	-8
	EOF
	test_cmp expect actual.trimmed &&
'

Then we can delete from here to ...

> +	git checkout -f main &&
> +	git clean -f &&
> +
> +	fill 1 2 3 4 5 >one &&
> +	test_must_fail git checkout side 2>stderr &&
> +	test_grep "Your local changes" stderr &&
> +	git checkout -m side >actual 2>&1 &&
> +	test_grep ! "Created autostash" actual &&
> +	test_grep "resulted in conflicts" actual &&
> +	test_grep "git stash drop" actual &&
> +	git stash drop &&
> +	git reset --hard
> +'
> +
> +test_expect_success 'checkout -m produces usable stash on conflict' '
> +	git checkout -f main &&
> +	git clean -f &&
> +
> +	fill 1 2 3 4 5 >one &&
> +	git checkout -m side >actual 2>&1 &&
> +	test_grep "recover your local changes" actual &&
> +	git checkout -f main &&
> +	git stash pop &&
> +	fill 1 2 3 4 5 >expect &&
> +	test_cmp expect one
> +'
> +
> +test_expect_success 'checkout -m autostash message includes target branch' '
> +	git checkout -f main &&
> +	git clean -f &&
> +
> +	fill 1 2 3 4 5 >one &&
> +	git checkout -m side >actual 2>&1 &&
> +	git stash list >stash-list &&
> +	test_grep "autostash while switching to .side." stash-list &&
> +	git stash drop &&
> +	git checkout -f main &&
> +	git reset --hard
> +'
> +
> +test_expect_success 'checkout -m stashes on staged conflicting changes' '
> +	git checkout -f main &&
> +	git clean -f &&
> +
> +	fill 1 2 3 4 5 >one &&
> +	git add one &&
> +	git checkout -m side >actual 2>&1 &&
> +	test_grep ! "Created autostash" actual &&
> +	test_grep "resulted in conflicts" actual &&
> +	test_grep "git stash drop" actual &&
> +	git stash drop &&
> +	git reset --hard
> +'

... here


> +test_expect_success 'checkout -m applies stash cleanly with non-overlapping changes in same file' '

I've no idea what this is trying to do - it looks more like it is 
testing that "git stash" works rather than anything to do with "git 
checkout"

> +	git checkout -f main &&
> +	git reset --hard &&
> +	git clean -f &&
> +
> +	git checkout -b nonoverlap_base &&
> +	fill a b c d >file &&
> +	git add file &&
> +	git commit -m "add file" &&
> +
> +	git checkout -b nonoverlap_child &&
> +	fill a b c INSERTED d >file &&
> +	git commit -a -m "insert line near end of file" &&
> +
> +	fill DIRTY a b c INSERTED d >file &&
> +
> +	git stash list >stash-before &&
> +	git checkout -m nonoverlap_base 2>stderr &&
> +	test_grep "Applied autostash" stderr &&
> +	test_grep ! "resulted in conflicts" stderr &&
> +
> +	git stash list >stash-after &&
> +	test_cmp stash-before stash-after &&
> +
> +	fill DIRTY a b c d >expect &&
> +	test_cmp expect file &&
> +
> +	git checkout -f main &&
> +	git branch -D nonoverlap_base &&
> +	git branch -D nonoverlap_child
> +'
> +
> +test_expect_success 'checkout -m -b skips stash with dirty tree' '
> +	git checkout -f main &&
> +	git clean -f &&
> +
> +	fill 0 x y z >same &&
> +	git checkout -m -b newbranch >actual 2>&1 &&
> +	test_grep ! "Created autostash" actual &&
> +	fill 0 x y z >expect &&
> +	test_cmp expect same &&
> +	git checkout main &&
> +	git branch -D newbranch
> +'

As I said above I don't think this test is testing what it claims to.

I'd suggest adding the following test

test_expect_success 'checkout -m which would overwrite untracked file' '
	git checkout -f --detach main &&
	test_commit another-file &&
	git checkout HEAD^ &&
	>another-file.t &&
	test_must_fail git checkout -m @{-1} 2>err &&
	test_grep "another-file.t.*overwritten" err
'

which passes on master but fails with these patches applied. We need to 
make sure that we don't set "quiet" in unpack_tree_opts the second time 
we call merge_working_tree(). The test could be improved by adding some 
local changes.

This is looking better, but there are still a couple of problems that 
need addressing before it can be considered ready for merging.

Thanks

Phillip

>   test_expect_success 'switch to another branch while carrying a deletion' '
>   	git checkout -f main &&
>   	git reset --hard &&
> diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
> index 9838094b66..cbef8a534e 100755
> --- a/t/t7600-merge.sh
> +++ b/t/t7600-merge.sh
> @@ -914,7 +914,7 @@ test_expect_success 'merge with conflicted --autostash changes' '
>   	git diff >expect &&
>   	test_when_finished "test_might_fail git stash drop" &&
>   	git merge --autostash c3 2>err &&
> -	test_grep "Applying autostash resulted in conflicts." err &&
> +	test_grep "your local changes resulted in conflicts" err &&
>   	git show HEAD:file >merge-result &&
>   	test_cmp result.1-9 merge-result &&
>   	git stash show -p >actual &&
> diff --git a/xdiff-interface.c b/xdiff-interface.c
> index f043330f2a..5ee2b96d0a 100644
> --- a/xdiff-interface.c
> +++ b/xdiff-interface.c
> @@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)
>   		return -1;
>   }
>   
> +const char *conflict_style_name(int style)
> +{
> +	switch (style) {
> +	case XDL_MERGE_DIFF3:
> +		return "diff3";
> +	case XDL_MERGE_ZEALOUS_DIFF3:
> +		return "zdiff3";
> +	default:
> +		return "merge";
> +	}
> +}
> +
>   int git_xmerge_style = -1;
>   
>   int git_xmerge_config(const char *var, const char *value,
> diff --git a/xdiff-interface.h b/xdiff-interface.h
> index fbc4ceec40..ce54e1c0e0 100644
> --- a/xdiff-interface.h
> +++ b/xdiff-interface.h
> @@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);
>   void xdiff_clear_find_func(xdemitconf_t *xecfg);
>   struct config_context;
>   int parse_conflict_style_name(const char *value);
> +const char *conflict_style_name(int style);
>   int git_xmerge_config(const char *var, const char *value,
>   		      const struct config_context *ctx, void *cb);
>   extern int git_xmerge_style;


^ permalink raw reply

* Re: [PATCH v14 0/5] checkout: 'autostash' for branch switching
From: Phillip Wood @ 2026-04-24 15:52 UTC (permalink / raw)
  To: Harald Nordgren via GitGitGadget, git
  Cc: Chris Torek, Jeff King, Harald Nordgren
In-Reply-To: <pull.2234.v14.git.git.1776270259.gitgitgadget@gmail.com>

Hi Harald

On 15/04/2026 17:24, Harald Nordgren via GitGitGadget wrote:
> Simplifying the tests in t/t3903-stash.sh according to Phillip Wood's
> comment. I believe everything sound be fixed now or responded to.

This is better than the last cover letter, but it would be helpful to 
describe the changes you have made rather than just say your responding 
to a reviewers comment. Junio has given some suggestions for 
coverletters to take inspiration from.

Also when you reply to messages can you try and keep the subject line 
please - nearly all the responses from you have the subject

Re: [PATCH] checkout: add --autostash option for branch switching

regardless of the message they're actually replying to. That makes it 
hard to see which message you're replying to.

I've left some comments on patch 5, it is definitely looking better but 
there are still a couple of things that need fixing.

Thanks

Phillip

> Also rebasing against upstream.
> 
> Harald Nordgren (5):
>    stash: add --label-ours, --label-theirs, --label-base for apply
>    sequencer: allow create_autostash to run silently
>    sequencer: teach autostash apply to take optional conflict marker
>      labels
>    checkout: rollback lock on early returns in merge_working_tree
>    checkout -m: autostash when switching branches
> 
>   Documentation/git-checkout.adoc |  58 ++++-----
>   Documentation/git-stash.adoc    |  11 +-
>   Documentation/git-switch.adoc   |  33 ++---
>   builtin/checkout.c              | 149 +++++++++++------------
>   builtin/commit.c                |   3 +-
>   builtin/merge.c                 |  15 ++-
>   builtin/stash.c                 |  28 +++--
>   sequencer.c                     |  73 ++++++++---
>   sequencer.h                     |   7 +-
>   t/t3420-rebase-autostash.sh     |  24 +++-
>   t/t3903-stash.sh                |  24 ++++
>   t/t7201-co.sh                   | 208 ++++++++++++++++++++++++++++++++
>   t/t7600-merge.sh                |   2 +-
>   xdiff-interface.c               |  12 ++
>   xdiff-interface.h               |   1 +
>   xdiff/xmerge.c                  |   6 +-
>   16 files changed, 483 insertions(+), 171 deletions(-)
> 
> 
> base-commit: 9f223ef1c026d91c7ac68cc0211bde255dda6199
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v14
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v14
> Pull-Request: https://github.com/git/git/pull/2234
> 
> Range-diff vs v13:
> 
>   1:  43bfdf2136 ! 1:  e18c25599a stash: add --label-ours, --label-theirs, --label-base for apply
>       @@ builtin/stash.c: static int branch_stash(int argc, const char **argv, const char
>         
>        
>         ## t/t3903-stash.sh ##
>       -@@ t/t3903-stash.sh: test_expect_success 'restore untracked files even when we hit conflicts' '
>       - 	)
>       +@@ t/t3903-stash.sh: setup_stash() {
>       + 	git add other-file &&
>       + 	test_tick &&
>       + 	git commit -m initial &&
>       ++	git tag initial &&
>       + 	echo 2 >file &&
>       + 	git add file &&
>       + 	echo 3 >file &&
>       +@@ t/t3903-stash.sh: test_expect_success 'stash.index=false overridden by --index' '
>       + 	test_cmp expect file
>         '
>         
>        +test_expect_success 'apply with custom conflict labels' '
>       -+	test_when_finished "git reset --hard && git stash drop" &&
>       -+	git reset --hard &&
>       ++	git reset --hard initial &&
>        +	test_commit label-base conflict-file base-content &&
>        +	echo stashed >conflict-file &&
>        +	git stash push -m "stashed" &&
>       @@ t/t3903-stash.sh: test_expect_success 'restore untracked files even when we hit
>        +'
>        +
>        +test_expect_success 'apply with empty conflict labels' '
>       -+	test_when_finished "git reset --hard && git stash drop" &&
>       -+	git reset --hard &&
>       ++	git reset --hard initial &&
>        +	test_commit empty-label-base conflict-file base-content &&
>        +	echo stashed >conflict-file &&
>        +	git stash push -m "stashed" &&
>       @@ t/t3903-stash.sh: test_expect_success 'restore untracked files even when we hit
>        +	test_grep "^>>>>>>>$" conflict-file
>        +'
>        +
>       - test_expect_success 'stash create reports a locked index' '
>       - 	test_when_finished "rm -rf repo" &&
>       - 	git init repo &&
>       + test_done
>        
>         ## xdiff/xmerge.c ##
>        @@ xdiff/xmerge.c: static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
>   2:  7f3c32f5e9 = 2:  ce29b10264 sequencer: allow create_autostash to run silently
>   3:  b279d1dac8 = 3:  73051d1762 sequencer: teach autostash apply to take optional conflict marker labels
>   4:  04869314ec = 4:  191058d8e3 checkout: rollback lock on early returns in merge_working_tree
>   5:  4b3c6025ac = 5:  86f33df1eb checkout -m: autostash when switching branches
> 


^ permalink raw reply

* Re: [PATCH] alias: restore support for simple dotted aliases
From: Kristoffer Haugsbakk @ 2026-04-24 16:09 UTC (permalink / raw)
  To: Jonatan Holmgren, git; +Cc: Jeff King, rsch, michael.grossfeld
In-Reply-To: <20260424151053.917066-1-jonatan@jontes.page>

On Fri, Apr 24, 2026, at 17:10, Jonatan Holmgren wrote:
> Historically, config entries like alias.foo.bar expanded the alias
> "foo.bar". The subsection-based alias syntax introduced in
> ac1f12a9de (alias: support non-alphanumeric names via subsection
> syntax, 2026-02-18) broke that behavior by treating such entries as
> if they were subsection syntax.
>
> Restore support for the old dotted form by falling back to the full
> name when the final key is not "command". Add tests covering execution
> and help output for simple dotted aliases.
>
> Reported-by: Michael Grossfeld <michael.grossfeld@amd.com>
> Helped-by: Jeff King <peff@peff.net>

Missing signoff.

> ---
>[snip]

^ permalink raw reply

* [PATCH] alias: restore support for simple dotted aliases
From: Jonatan Holmgren @ 2026-04-24 16:17 UTC (permalink / raw)
  To: git; +Cc: peff, rsch, michael.grossfeld, Jonatan Holmgren
In-Reply-To: <PH7PR12MB73313034573C59C73F821BBFE52A2@PH7PR12MB7331.namprd12.prod.outlook.com>

Historically, config entries like alias.foo.bar expanded the alias
"foo.bar". The subsection-based alias syntax introduced in
ac1f12a9de (alias: support non-alphanumeric names via subsection
syntax, 2026-02-18) broke that behavior by treating such entries as
if they were subsection syntax.

Restore support for the old dotted form by falling back to the full
name when the final key is not "command". Add tests covering execution
and help output for simple dotted aliases.

Reported-by: Michael Grossfeld <michael.grossfeld@amd.com>
Helped-by: Jeff King <peff@peff.net>
Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 alias.c          | 16 ++++++++++++++--
 help.c           |  9 ++++++++-
 t/t0014-alias.sh | 12 ++++++++++++
 3 files changed, 34 insertions(+), 3 deletions(-)

diff --git a/alias.c b/alias.c
index ec9833dd30..e737c49edd 100644
--- a/alias.c
+++ b/alias.c
@@ -34,8 +34,20 @@ static int config_alias_cb(const char *var, const char *value,
 	if (subsection && !subsection_len)
 		subsection = NULL;
 
-	if (subsection && strcmp(key, "command"))
-		return 0;
+	if (subsection && strcmp(key, "command")) {
+		/*
+		 * We have historically supported the "alias.name" form when
+		 * "name" happens to contain dots (e.g., alias.foo.bar to allow
+		 * "git foo.bar". But our parsing above would split that into
+		 * subsection "foo".
+		 *
+		 * If we do not understand the final key in a subsection-style
+		 * variable, fall back to treating it as a two-level alias.
+		 */
+		key = var + strlen("alias.");
+		subsection = NULL;
+		subsection_len = 0;
+	}
 
 	if (data->alias) {
 		int match;
diff --git a/help.c b/help.c
index 3e59d07c37..46241492ce 100644
--- a/help.c
+++ b/help.c
@@ -592,14 +592,21 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 	/* Also use aliases for command lookup */
 	if (!parse_config_key(var, "alias", &subsection, &subsection_len,
 			      &key)) {
+		size_t key_len = strlen(key);
+
 		if (subsection) {
 			/* [alias "name"] command = value */
 			if (!strcmp(key, "command"))
 				add_cmdname(&cfg->aliases, subsection,
 					    subsection_len);
+			else {
+				key = var + strlen("alias.");
+				key_len = strlen(key);
+				add_cmdname(&cfg->aliases, key, key_len);
+			}
 		} else {
 			/* alias.name = value */
-			add_cmdname(&cfg->aliases, key, strlen(key));
+			add_cmdname(&cfg->aliases, key, key_len);
 		}
 	}
 
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 68b4903cbf..5144b0effd 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -128,6 +128,12 @@ test_expect_success 'subsection syntax works' '
 	test_grep "ran-subsection" output
 '
 
+test_expect_success 'simple dotted alias syntax still works' '
+	test_config alias.simple.dotted "!echo ran-simple-dotted" &&
+	git simple.dotted >output &&
+	test_grep "ran-simple-dotted" output
+'
+
 test_expect_success 'subsection syntax only accepts command key' '
 	test_config alias.invalid.notcommand value &&
 	test_must_fail git invalid 2>error &&
@@ -183,6 +189,12 @@ test_expect_success 'subsection aliases listed in help -a' '
 	test_grep "förgrena" output
 '
 
+test_expect_success 'simple dotted aliases listed in help -a' '
+	test_config alias.simple.listed "!echo test" &&
+	git help -a >output &&
+	test_grep "simple.listed" output
+'
+
 test_expect_success 'empty subsection treated as no subsection' '
 	test_config "alias..something" "!echo foobar" &&
 	git something >actual &&
-- 
2.54.0


^ permalink raw reply related

* Re: Problem with git send-email and --reply-to
From: Tian Yuchen @ 2026-04-24 16:29 UTC (permalink / raw)
  To: Christian König, git
In-Reply-To: <26f3a5b2-1656-482a-9349-ca3592b8bba1@amd.com>

On 4/24/26 21:13, Christian König wrote:
> Hello everybody,
> 
> either I've found a bug or there is something absolutely not obvious going on here with git send-email.
> 
> I want to use the --reply-to option with git send-email to make sure that people reply to my AMD mail address and not my gmail address used for sending mails.
> 
> When I use the option on the command line it works fine, but when I put that into my .gitconfig it doesn't seem to have any effect.
> 
> Any idea what could be wrong here?
> 
> .gitconfig looks like this:
> 
> [user]
>      name = Christian König
>      email = christian.koenig@amd.com
> [sendemail]
>      reply-to = christian.koenig@amd.com
> 
> Thanks in advance,
> Christian.

The most straightforward solution might be:

[format]
     headers = "Reply-To: christian.koenig@amd.com"

This is equivalent to manually adding this line at the beginning of the 
email. In most cases, there shouldn’t be any problems, I guess?

Thanks, Yuchen


^ permalink raw reply

* Re: [PATCH v2 8/9] refs: add peeled object ID to the `ref_update` struct
From: Toon Claes @ 2026-04-24 16:44 UTC (permalink / raw)
  To: Karthik Nayak, git; +Cc: Karthik Nayak, gitster, ps
In-Reply-To: <20260423-refs-move-to-generic-layer-v2-8-ae5a4f146d7d@gmail.com>

Karthik Nayak <karthik.188@gmail.com> writes:

> Certain reference backend {packed, reftable}, have the ability to also

Shouldn't it be:

  Certain reference backends {packed, reftable} have the ability to also

> store the peeled object ID for a reference pointing to a tag object.
> This has the added benefit that during retrieval of such references, we
> also obtain the peeled object ID without having to use the ODB.
>
> To provide this functionality, each backend independently calls the ODB
> to obtain the peeled OID. To move this functionality to the generic
> layer, there must be support infrastructure to pass in a peeled OID for
> reference updates.
>
> Add a `peeled` field to the `ref_update` structure and modify
> `ref_transaction_add_update()` to receive and copy this object ID to the
> `ref_update` structure. Finally, modify `ref_transaction_update()` to
> peel tag objects and pass the peeled OID to
> `ref_transaction_add_update()`.
>
> Update all callers of these functions with the new function parameters.
> Callers which only add reflog updates, need to only pass in NULL, since
> for reflogs, we don't store peeled OIDs. Reference deletions also only
> need to pass in NULL. For others, pass along the peeled OID if
> available.
>
> In a following commit, we'll modify the backends to use this peeled OID
> instead of parsing it themselves.
>
> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
> ---
>  refs.c                  | 15 +++++++++++++--
>  refs/files-backend.c    | 20 ++++++++++++--------
>  refs/refs-internal.h    | 14 ++++++++++++++
>  refs/reftable-backend.c |  6 +++---
>  4 files changed, 42 insertions(+), 13 deletions(-)
>
> diff --git a/refs/refs-internal.h b/refs/refs-internal.h
> index d103387ebf..307dcb277b 100644
> --- a/refs/refs-internal.h
> +++ b/refs/refs-internal.h
> @@ -39,6 +39,13 @@ struct ref_transaction;
>   */
>  #define REF_LOG_ONLY (1 << 7)
>  
> +/*
> + * The reference contains a peeled object ID. This is used when the
> + * new_oid is pointing to a tag object and the reference backend
> + * wants to also store the peeled value for optimized retrieval.
> + */
> +#define REF_HAVE_PEELED (1 << 15)

How did you end up picking this value?

I did some grepping to figure out if it would conflict with anything:

    git grep -h '#define REF_' -- '*.h' '*.c' |
      awk '/0x/{n=strtonum($3);b=0;while(n>1){n/=2;b++};$3="(1 << "b")"} 1' |
      sort -t'<' -k3 -n |
      column -t

(Yeah I got some help from AI to write the `awk` command)

Resulting in:

    #define  REF_EXCLUSIONS_INIT                   {                \
    #define  REF_FILTER_H
    #define  REF_FILTER_INIT                       {                \
    #define  REF_FORMAT_INIT                       {                \
    #define  REF_FORMATTING_STATE_INIT             {                0   }
    #define  REF_NO_DEREF                          (1               <<  0)
    #define  REF_NORMAL                            (1u              <<  0)
    #define  REF_STATES_INIT                       {                \
    #define  REF_STORE_ALL_CAPS                    (REF_STORE_READ  |   \
    #define  REF_STORE_CREATE_ON_DISK_IS_WORKTREE  (1               <<  0)
    #define  REF_STORE_READ                        (1               <<  0)
    #define  REF_TRANSACTION_UPDATE_ALLOWED_FLAGS  \
    #define  REF_BRANCHES                          (1u              <<  1)
    #define  REF_FORCE_CREATE_REFLOG               (1               <<  1)
    #define  REF_STORE_WRITE                       (1               <<  1)   /*  can  perform  update  operations  */
    #define  REF_HAVE_NEW                          (1               <<  2)
    #define  REF_STORE_ODB                         (1               <<  2)   /*  has  access   to      object      database  */
    #define  REF_TAGS                              (1u              <<  2)
    #define  REF_HAVE_OLD                          (1               <<  3)
    #define  REF_STORE_MAIN                        (1               <<  3)
    #define  REF_DIR                               (1               <<  4)
    #define  REF_IS_PRUNING                        (1               <<  4)
    #define  REF_DELETING                          (1               <<  5)
    #define  REF_INCOMPLETE                        (1               <<  5)
    #define  REF_KNOWS_PEELED                      (1               <<  6)
    #define  REF_NEEDS_COMMIT                      (1               <<  6)
    #define  REF_LOG_ONLY                          (1               <<  7)
    #define  REF_UPDATE_VIA_HEAD                   (1               <<  8)
    #define  REF_UPDATE_VIA_HEAD                   (1               <<  8)
    #define  REF_DELETED_RMDIR                     (1               <<  9)
    #define  REF_SKIP_OID_VERIFICATION             (1               <<  10)
    #define  REF_SKIP_REFNAME_VERIFICATION         (1               <<  11)
    #define  REF_SKIP_CREATE_REFLOG                (1               <<  12)
    #define  REF_LOG_USE_PROVIDED_OIDS             (1               <<  13)
    #define  REF_LOG_VIA_SPLIT                     (1               <<  14)
    #define  REF_HAVE_PEELED                       (1               <<  15)

So I guess it makes sense to use `(1 << 15)`.

-- 
Cheers,
Toon

^ permalink raw reply


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