Git development
 help / color / mirror / Atom feed
* Re: [PATCH v3 5/5] builtin/refs: add "rename" subcommand
From: Toon Claes @ 2026-07-03 14:31 UTC (permalink / raw)
  To: Patrick Steinhardt, git; +Cc: Junio C Hamano
In-Reply-To: <20260630-pks-refs-writing-subcommands-v3-5-deb04de1ecef@pks.im>

Patrick Steinhardt <ps@pks.im> writes:

> Add a "rename" subcommand to git-refs(1) with the syntax:
>
>   $ git refs rename <oldref> <newref>
>
> It renames <oldref> together with its reflog to <newref>; even when used
> on a local branch ref, the current value and the reflog of the ref are
> the only things that are renamed. Document it and redirect casual users
> to "git branch -m" if that is what they wanted to do.
>
> Co-authored-by: Junio C Hamano <gitster@pobox.com>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  Documentation/git-refs.adoc |   6 ++
>  builtin/refs.c              |  49 +++++++++++++++++
>  t/meson.build               |   1 +
>  t/t1467-refs-rename.sh      | 131 ++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 187 insertions(+)
>
> diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc
> index e6a3528349..ce278c59bf 100644
> --- a/Documentation/git-refs.adoc
> +++ b/Documentation/git-refs.adoc
> @@ -23,6 +23,7 @@ git refs optimize [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude
>  git refs create [--message=<reason>] [--no-deref] [--create-reflog] <ref> <new-value>
>  git refs delete [--message=<reason>] [--no-deref] <ref> [<old-value>]
>  git refs update [--message=<reason>] [--no-deref] [--create-reflog] <ref> <new-value> [<old-value>]
> +git refs rename [--message=<reason>] <old-ref> <new-ref>

So symrefs cannot be renamed with this command?

-- 
Cheers,
Toon

^ permalink raw reply

* Re: Programmatically edit the git rebase sequence?
From: Matt Hunter @ 2026-07-03 14:33 UTC (permalink / raw)
  To: brian m. carlson, Matthias Beyer; +Cc: git, neikos
In-Reply-To: <ake8OAIyK-ELs-fU@fruit.crustytoothpaste.net>

On Fri Jul 3, 2026 at 9:42 AM EDT, brian m. carlson wrote:
> On 2026-07-03 at 12:02:33, Matthias Beyer wrote:
>> 
>> Now I fire up `git rebase -i master` and manually(!) match the list from
>> above `git-log` call and find the respective commits to edit them.
>> 
>> Is there a way I am not aware of to do that manual step programatically?
>> Something like
>> 
>>     git rebase -i master --edit-commits="$(git log master..mybranch --diff-filter=M --format="%H" -- "./subdir/*.rs")"
>> 
>> would be convenient here, although I would understand if that is too
>> much clutter for the already very heavy git CLI interface :-)
>
> Yes, such a thing exists.  You want `GIT_SEQUENCE_EDITOR`, which is an
> `EDITOR`-like command that edits the rebase list in place.  So tools
> like `ed`, `ex`, `sed -i`, `perl -i`, or `ruby -i` would be useful here.

Interesting tip - thanks!

>
> I personally use this alias, which explicitly does not edit the sequence
> list, to automatically squash in all squash and fixup commits without
> prompting:
>
>     srebase = "!f() { GIT_SEQUENCE_EDITOR=true git rebase -m -i --autosquash \"$@\"; };f"

'git rebase --autosquash' does work now without the need to invoke
--interactive mode.  I believe we got this with f8f87e082798
(Merge branch 'ak/rebase-autosquash')

^ permalink raw reply

* [PATCH 0/3] history: sign rewritten commits
From: Souma @ 2026-07-03 14:50 UTC (permalink / raw)
  To: git; +Cc: gitster, ps, Souma

This series updates `git history fixup`, `reword`, and `split` to honor `commit.gpgsign` as well as the `-S/--gpg-sign` and `--no-gpg-sign` options.
It adds regression tests that cover configuration-driven signing, command-line overrides, and the handling of replayed descendant commits.
Finally, it updates the history documentation to describe the new signing behavior and available options.

Souma (3):
  builtin/history: sign rewritten commits
  doc: document history signing options
  t345x: cover signed history rewrites

 Documentation/git-history.adoc | 14 ++++--
 builtin/history.c              | 80 ++++++++++++++++++++++++++--------
 replay.c                       | 13 +++---
 replay.h                       |  6 +++
 t/t3451-history-reword.sh      | 39 +++++++++++++++++
 t/t3452-history-split.sh       | 44 +++++++++++++++++++
 t/t3453-history-fixup.sh       | 39 +++++++++++++++++
 7 files changed, 209 insertions(+), 26 deletions(-)

-- 
2.55.0


^ permalink raw reply

* [PATCH 1/3] builtin/history: sign rewritten commits
From: Souma @ 2026-07-03 14:50 UTC (permalink / raw)
  To: git; +Cc: gitster, ps, Souma
In-Reply-To: <20260703145037.69832-1-git@5ouma.me>

The history commands create replacement commits directly instead of
using the sequencer or the commit porcelain. As a result, rewritten
commits ignore commit.gpgsign and cannot be signed on demand.

Read the usual signing configuration before parsing history options.
Add the commit-style -S/--gpg-sign knob, and pass the selected
signing key through direct rewrites and replayed descendants.

Signed-off-by: Souma <git@5ouma.me>
---
 builtin/history.c | 80 ++++++++++++++++++++++++++++++++++++-----------
 replay.c          | 13 +++++---
 replay.h          |  6 ++++
 3 files changed, 76 insertions(+), 23 deletions(-)

diff --git a/builtin/history.c b/builtin/history.c
index 091465a59e..8d669cf539 100644
--- a/builtin/history.c
+++ b/builtin/history.c
@@ -25,11 +25,11 @@
 #include "wt-status.h"
 
 #define GIT_HISTORY_FIXUP_USAGE \
-	N_("git history fixup <commit> [--dry-run] [--update-refs=(branches|head)] [--reedit-message] [--empty=(drop|keep|abort)]")
+	N_("git history fixup <commit> [--dry-run] [--update-refs=(branches|head)] [--reedit-message] [--empty=(drop|keep|abort)] [--[no-]gpg-sign[=<key-id>]]")
 #define GIT_HISTORY_REWORD_USAGE \
-	N_("git history reword <commit> [--dry-run] [--update-refs=(branches|head)]")
+	N_("git history reword <commit> [--dry-run] [--update-refs=(branches|head)] [--[no-]gpg-sign[=<key-id>]]")
 #define GIT_HISTORY_SPLIT_USAGE \
-	N_("git history split <commit> [--dry-run] [--update-refs=(branches|head)] [--] [<pathspec>...]")
+	N_("git history split <commit> [--dry-run] [--update-refs=(branches|head)] [--[no-]gpg-sign[=<key-id>]] [--] [<pathspec>...]")
 
 static void change_data_free(void *util, const char *str UNUSED)
 {
@@ -98,6 +98,30 @@ enum commit_tree_flags {
 	COMMIT_TREE_EDIT_MESSAGE = (1 << 0),
 };
 
+static int history_config(const char *var, const char *value,
+			  const struct config_context *ctx, void *data)
+{
+	const char **sign_commit = data;
+
+	if (!strcmp(var, "commit.gpgsign")) {
+		*sign_commit = git_config_bool(var, value) ? "" : NULL;
+		return 0;
+	}
+
+	return git_default_config(var, value, ctx, data);
+}
+
+#define OPT_HISTORY_GPG_SIGN(v) { \
+	.type = OPTION_STRING, \
+	.short_name = 'S', \
+	.long_name = "gpg-sign", \
+	.value = (v), \
+	.argh = N_("key-id"), \
+	.help = N_("GPG-sign rewritten commits"), \
+	.flags = PARSE_OPT_OPTARG, \
+	.defval = (intptr_t) "", \
+}
+
 static int commit_tree_ext(struct repository *repo,
 			   const char *action,
 			   struct commit *commit_with_message,
@@ -105,6 +129,7 @@ static int commit_tree_ext(struct repository *repo,
 			   const struct object_id *old_tree,
 			   const struct object_id *new_tree,
 			   struct commit **out,
+			   const char *sign_commit,
 			   enum commit_tree_flags flags)
 {
 	const char *exclude_gpgsig[] = {
@@ -144,7 +169,7 @@ static int commit_tree_ext(struct repository *repo,
 
 	ret = commit_tree_extended(commit_message.buf, commit_message.len, new_tree,
 				   parents, &rewritten_commit_oid, original_author,
-				   NULL, NULL, original_extra_headers);
+				   NULL, sign_commit, original_extra_headers);
 	if (ret < 0)
 		goto out;
 
@@ -160,7 +185,8 @@ static int commit_tree_ext(struct repository *repo,
 static int commit_tree_with_edited_message(struct repository *repo,
 					   const char *action,
 					   struct commit *original,
-					   struct commit **out)
+					   struct commit **out,
+					   const char *sign_commit)
 {
 	struct object_id parent_tree_oid;
 	const struct object_id *tree_oid;
@@ -181,7 +207,8 @@ static int commit_tree_with_edited_message(struct repository *repo,
 	}
 
 	return commit_tree_ext(repo, action, original, original->parents,
-			       &parent_tree_oid, tree_oid, out, COMMIT_TREE_EDIT_MESSAGE);
+			       &parent_tree_oid, tree_oid, out, sign_commit,
+			       COMMIT_TREE_EDIT_MESSAGE);
 }
 
 enum ref_action {
@@ -339,11 +366,13 @@ static int handle_reference_updates(struct rev_info *revs,
 				    struct commit *rewritten,
 				    const char *reflog_msg,
 				    int dry_run,
+				    const char *sign_commit,
 				    enum replay_empty_commit_action empty)
 {
 	const struct name_decoration *decoration;
 	struct replay_revisions_options opts = {
 		.empty = empty,
+		.sign_commit = sign_commit,
 	};
 	struct replay_result result = { 0 };
 	struct ref_transaction *transaction = NULL;
@@ -491,6 +520,7 @@ static int cmd_history_fixup(int argc,
 	enum replay_empty_commit_action empty = REPLAY_EMPTY_COMMIT_DROP;
 	enum ref_action action = REF_ACTION_DEFAULT;
 	enum commit_tree_flags flags = 0;
+	const char *sign_commit = NULL;
 	int dry_run = 0;
 	struct option options[] = {
 		OPT_CALLBACK_F(0, "update-refs", &action, "(branches|head)",
@@ -504,6 +534,7 @@ static int cmd_history_fixup(int argc,
 		OPT_CALLBACK_F(0, "empty", &empty, "(drop|keep|abort)",
 			       N_("how to handle commits that become empty"),
 			       PARSE_OPT_NONEG, parse_opt_empty),
+		OPT_HISTORY_GPG_SIGN(&sign_commit),
 		OPT_END(),
 	};
 	struct merge_result merge_result = { 0 };
@@ -515,12 +546,13 @@ static int cmd_history_fixup(int argc,
 	bool skip_commit = false;
 	int ret;
 
+	repo_config(repo, history_config, &sign_commit);
+
 	argc = parse_options(argc, argv, prefix, options, usage, 0);
 	if (argc != 1) {
 		ret = error(_("command expects a single revision"));
 		goto out;
 	}
-	repo_config(repo, git_default_config, NULL);
 
 	if (action == REF_ACTION_DEFAULT)
 		action = REF_ACTION_BRANCHES;
@@ -645,7 +677,7 @@ static int cmd_history_fixup(int argc,
 	if (!skip_commit) {
 		ret = commit_tree_ext(repo, "fixup", original, original->parents,
 				      &original_tree->object.oid, &merge_result.tree->object.oid,
-				      &rewritten, flags);
+				      &rewritten, sign_commit, flags);
 		if (ret < 0) {
 			ret = error(_("failed writing fixed-up commit"));
 			goto out;
@@ -655,7 +687,7 @@ static int cmd_history_fixup(int argc,
 	strbuf_addf(&reflog_msg, "fixup: updating %s", argv[0]);
 
 	ret = handle_reference_updates(&revs, action, original, rewritten,
-				       reflog_msg.buf, dry_run, empty);
+				       reflog_msg.buf, dry_run, sign_commit, empty);
 	if (ret < 0) {
 		ret = error(_("failed replaying descendants"));
 		goto out;
@@ -680,6 +712,7 @@ static int cmd_history_reword(int argc,
 		NULL,
 	};
 	enum ref_action action = REF_ACTION_DEFAULT;
+	const char *sign_commit = NULL;
 	int dry_run = 0;
 	struct option options[] = {
 		OPT_CALLBACK_F(0, "update-refs", &action, "(branches|head)",
@@ -687,6 +720,7 @@ static int cmd_history_reword(int argc,
 			       PARSE_OPT_NONEG, parse_ref_action),
 		OPT_BOOL('n', "dry-run", &dry_run,
 			 N_("perform a dry-run without updating any refs")),
+		OPT_HISTORY_GPG_SIGN(&sign_commit),
 		OPT_END(),
 	};
 	struct strbuf reflog_msg = STRBUF_INIT;
@@ -694,12 +728,13 @@ static int cmd_history_reword(int argc,
 	struct rev_info revs = { 0 };
 	int ret;
 
+	repo_config(repo, history_config, &sign_commit);
+
 	argc = parse_options(argc, argv, prefix, options, usage, 0);
 	if (argc != 1) {
 		ret = error(_("command expects a single revision"));
 		goto out;
 	}
-	repo_config(repo, git_default_config, NULL);
 
 	if (action == REF_ACTION_DEFAULT)
 		action = REF_ACTION_BRANCHES;
@@ -714,7 +749,8 @@ static int cmd_history_reword(int argc,
 	if (ret)
 		goto out;
 
-	ret = commit_tree_with_edited_message(repo, "reworded", original, &rewritten);
+	ret = commit_tree_with_edited_message(repo, "reworded", original,
+					      &rewritten, sign_commit);
 	if (ret < 0) {
 		ret = error(_("failed writing reworded commit"));
 		goto out;
@@ -723,7 +759,8 @@ static int cmd_history_reword(int argc,
 	strbuf_addf(&reflog_msg, "reword: updating %s", argv[0]);
 
 	ret = handle_reference_updates(&revs, action, original, rewritten,
-				       reflog_msg.buf, dry_run, REPLAY_EMPTY_COMMIT_ABORT);
+				       reflog_msg.buf, dry_run, sign_commit,
+				       REPLAY_EMPTY_COMMIT_ABORT);
 	if (ret < 0) {
 		ret = error(_("failed replaying descendants"));
 		goto out;
@@ -785,7 +822,8 @@ static int write_ondisk_index(struct repository *repo,
 static int split_commit(struct repository *repo,
 			struct commit *original,
 			struct pathspec *pathspec,
-			struct commit **out)
+			struct commit **out,
+			const char *sign_commit)
 {
 	struct interactive_options interactive_opts = INTERACTIVE_OPTIONS_INIT;
 	struct strbuf index_file = STRBUF_INIT;
@@ -862,7 +900,8 @@ static int split_commit(struct repository *repo,
 	 * that shall be diffed against is the parent of the original commit.
 	 */
 	ret = commit_tree_ext(repo, "split-out", original, original->parents, &parent_tree_oid,
-			      &split_tree->object.oid, &first_commit, COMMIT_TREE_EDIT_MESSAGE);
+			      &split_tree->object.oid, &first_commit, sign_commit,
+			      COMMIT_TREE_EDIT_MESSAGE);
 	if (ret < 0) {
 		ret = error(_("failed writing first commit"));
 		goto out;
@@ -879,7 +918,8 @@ static int split_commit(struct repository *repo,
 	new_tree_oid = &repo_get_commit_tree(repo, original)->object.oid;
 
 	ret = commit_tree_ext(repo, "split-out", original, parents, old_tree_oid,
-			      new_tree_oid, &second_commit, COMMIT_TREE_EDIT_MESSAGE);
+			      new_tree_oid, &second_commit, sign_commit,
+			      COMMIT_TREE_EDIT_MESSAGE);
 	if (ret < 0) {
 		ret = error(_("failed writing second commit"));
 		goto out;
@@ -907,6 +947,7 @@ static int cmd_history_split(int argc,
 		NULL,
 	};
 	enum ref_action action = REF_ACTION_DEFAULT;
+	const char *sign_commit = NULL;
 	int dry_run = 0;
 	struct option options[] = {
 		OPT_CALLBACK_F(0, "update-refs", &action, "(branches|head)",
@@ -914,6 +955,7 @@ static int cmd_history_split(int argc,
 			       PARSE_OPT_NONEG, parse_ref_action),
 		OPT_BOOL('n', "dry-run", &dry_run,
 			 N_("perform a dry-run without updating any refs")),
+		OPT_HISTORY_GPG_SIGN(&sign_commit),
 		OPT_END(),
 	};
 	struct commit *original, *rewritten = NULL;
@@ -922,12 +964,13 @@ static int cmd_history_split(int argc,
 	struct rev_info revs = { 0 };
 	int ret;
 
+	repo_config(repo, history_config, &sign_commit);
+
 	argc = parse_options(argc, argv, prefix, options, usage, 0);
 	if (argc < 1) {
 		ret = error(_("command expects a committish"));
 		goto out;
 	}
-	repo_config(repo, git_default_config, NULL);
 
 	if (action == REF_ACTION_DEFAULT)
 		action = REF_ACTION_BRANCHES;
@@ -953,14 +996,15 @@ static int cmd_history_split(int argc,
 		goto out;
 	}
 
-	ret = split_commit(repo, original, &pathspec, &rewritten);
+	ret = split_commit(repo, original, &pathspec, &rewritten, sign_commit);
 	if (ret < 0)
 		goto out;
 
 	strbuf_addf(&reflog_msg, "split: updating %s", argv[0]);
 
 	ret = handle_reference_updates(&revs, action, original, rewritten,
-				       reflog_msg.buf, dry_run, REPLAY_EMPTY_COMMIT_ABORT);
+				       reflog_msg.buf, dry_run, sign_commit,
+				       REPLAY_EMPTY_COMMIT_ABORT);
 	if (ret < 0) {
 		ret = error(_("failed replaying descendants"));
 		goto out;
diff --git a/replay.c b/replay.c
index da531d5bc6..683c384ef8 100644
--- a/replay.c
+++ b/replay.c
@@ -81,13 +81,13 @@ static struct commit *create_commit(struct repository *repo,
 				    struct tree *tree,
 				    struct commit *based_on,
 				    struct commit *parent,
-				    enum replay_mode mode)
+				    enum replay_mode mode,
+				    const char *sign_commit)
 {
 	struct object_id ret;
 	struct object *obj = NULL;
 	struct commit_list *parents = NULL;
 	char *author = NULL;
-	char *sign_commit = NULL; /* FIXME: cli users might want to sign again */
 	struct commit_extra_header *extra = NULL;
 	struct strbuf msg = STRBUF_INIT;
 	const char *out_enc = get_commit_output_encoding();
@@ -270,7 +270,8 @@ static struct commit *pick_regular_commit(struct repository *repo,
 					  struct merge_options *merge_opt,
 					  struct merge_result *result,
 					  enum replay_mode mode,
-					  enum replay_empty_commit_action empty)
+					  enum replay_empty_commit_action empty,
+					  const char *sign_commit)
 {
 	struct commit *base, *replayed_base;
 	struct tree *pickme_tree, *base_tree, *replayed_base_tree;
@@ -341,7 +342,8 @@ static struct commit *pick_regular_commit(struct repository *repo,
 		}
 	}
 
-	return create_commit(repo, result->tree, pickme, replayed_base, mode);
+	return create_commit(repo, result->tree, pickme, replayed_base, mode,
+			     sign_commit);
 }
 
 void replay_result_release(struct replay_result *result)
@@ -431,7 +433,8 @@ int replay_revisions(struct rev_info *revs,
 
 		last_commit = pick_regular_commit(revs->repo, commit, replayed_commits,
 						  mode == REPLAY_MODE_REVERT ? last_commit : onto,
-						  &merge_opt, &result, mode, opts->empty);
+						  &merge_opt, &result, mode, opts->empty,
+						  opts->sign_commit);
 		if (!last_commit)
 			break;
 
diff --git a/replay.h b/replay.h
index faf95c7459..c715504d78 100644
--- a/replay.h
+++ b/replay.h
@@ -57,6 +57,12 @@ struct replay_revisions_options {
 	 */
 	int contained;
 
+	/*
+	 * Key used to sign newly-created commits. An empty string requests the
+	 * default configured signing key, and NULL disables signing.
+	 */
+	const char *sign_commit;
+
 	/*
 	 * Controls what to do when a replayed commit becomes empty.
 	 * Defaults to REPLAY_EMPTY_COMMIT_DROP.
-- 
2.55.0


^ permalink raw reply related

* [PATCH 2/3] doc: document history signing options
From: Souma @ 2026-07-03 14:50 UTC (permalink / raw)
  To: git; +Cc: gitster, ps, Souma
In-Reply-To: <20260703145037.69832-1-git@5ouma.me>

The history manual and usage text should describe the signing controls now
accepted by fixup, reword, and split.

Document -S/--gpg-sign and --no-gpg-sign with the same key-id spelling and
configuration override behavior used by commit-style signing options.

Signed-off-by: Souma <git@5ouma.me>
---
 Documentation/git-history.adoc | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/Documentation/git-history.adoc b/Documentation/git-history.adoc
index 2ba8121795..a1dd5c8580 100644
--- a/Documentation/git-history.adoc
+++ b/Documentation/git-history.adoc
@@ -8,9 +8,9 @@ git-history - EXPERIMENTAL: Rewrite history
 SYNOPSIS
 --------
 [synopsis]
-git history fixup <commit> [--dry-run] [--update-refs=(branches|head)] [--reedit-message] [--empty=(drop|keep|abort)]
-git history reword <commit> [--dry-run] [--update-refs=(branches|head)]
-git history split <commit> [--dry-run] [--update-refs=(branches|head)] [--] [<pathspec>...]
+git history fixup <commit> [--dry-run] [--update-refs=(branches|head)] [--reedit-message] [--empty=(drop|keep|abort)] [--[no-]gpg-sign[=<key-id>]]
+git history reword <commit> [--dry-run] [--update-refs=(branches|head)] [--[no-]gpg-sign[=<key-id>]]
+git history split <commit> [--dry-run] [--update-refs=(branches|head)] [--[no-]gpg-sign[=<key-id>]] [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -109,6 +109,14 @@ OPTIONS
 `--reedit-message`::
 	Open an editor to modify the target commit's message.
 
+`-S[<key-id>]`::
+`--gpg-sign[=<key-id>]`::
+`--no-gpg-sign`::
+	GPG-sign rewritten commits. The _<key-id>_ argument is optional and
+	defaults to the committer identity; if specified, it must be stuck to
+	the option without a space. `--no-gpg-sign` is useful to countermand
+	both `commit.gpgSign` configuration and earlier `--gpg-sign`.
+
 `--empty=(drop|keep|abort)`::
 	Control what happens when a commit becomes empty as a result of the
 	fixup. This can happen in two situations:
-- 
2.55.0


^ permalink raw reply related

* [PATCH 3/3] t345x: cover signed history rewrites
From: Souma @ 2026-07-03 14:50 UTC (permalink / raw)
  To: git; +Cc: gitster, ps, Souma
In-Reply-To: <20260703145037.69832-1-git@5ouma.me>

History signing needs regression coverage because these commands bypass the
usual commit machinery and create replacement commits through lower-level
APIs.

Add GPG-gated tests for config-driven signing, command-line signing,
--no-gpg-sign precedence, and signing of replayed descendants after fixup,
reword, and split.

Signed-off-by: Souma <git@5ouma.me>
---
 t/t3451-history-reword.sh | 39 ++++++++++++++++++++++++++++++++++
 t/t3452-history-split.sh  | 44 +++++++++++++++++++++++++++++++++++++++
 t/t3453-history-fixup.sh  | 39 ++++++++++++++++++++++++++++++++++
 3 files changed, 122 insertions(+)

diff --git a/t/t3451-history-reword.sh b/t/t3451-history-reword.sh
index de7b357685..5b41fb6489 100755
--- a/t/t3451-history-reword.sh
+++ b/t/t3451-history-reword.sh
@@ -4,6 +4,7 @@ test_description='tests for git-history reword subcommand'
 
 . ./test-lib.sh
 . "$TEST_DIRECTORY/lib-log-graph.sh"
+. "$TEST_DIRECTORY/lib-gpg.sh"
 
 reword_with_message () {
 	cat >message &&
@@ -26,6 +27,37 @@ expect_log () {
 	test_cmp expect actual
 }
 
+test_reword_gpg_sign () {
+	must_fail= will=will
+	if test "x$1" = "x!"
+	then
+		must_fail=test_must_fail
+		will="will not"
+		shift
+	fi
+	conf=$1
+	shift
+
+	test_expect_success GPG "reword $* with commit.gpgsign=$conf $will sign rewritten history" "
+		test_when_finished 'rm -rf repo' &&
+		git init repo &&
+		(
+			cd repo &&
+			test_commit first &&
+			test_commit second &&
+			test_commit third &&
+
+			git config commit.gpgsign $conf &&
+			reword_with_message $* HEAD~ <<-EOF &&
+			second reworded
+			EOF
+
+			$must_fail git verify-commit HEAD~ &&
+			$must_fail git verify-commit HEAD
+		)
+	"
+}
+
 test_expect_success 'can reword tip of a branch' '
 	test_when_finished "rm -rf repo" &&
 	git init repo &&
@@ -77,6 +109,13 @@ test_expect_success 'can reword commit in the middle' '
 	)
 '
 
+test_reword_gpg_sign ! false
+test_reword_gpg_sign   true
+test_reword_gpg_sign   false --gpg-sign
+test_reword_gpg_sign ! true  --no-gpg-sign
+test_reword_gpg_sign ! true  --gpg-sign --no-gpg-sign
+test_reword_gpg_sign   false --no-gpg-sign --gpg-sign
+
 test_expect_success 'can reword commit in the middle even on detached head' '
 	test_when_finished "rm -rf repo" &&
 	git init repo &&
diff --git a/t/t3452-history-split.sh b/t/t3452-history-split.sh
index 8ed0cebb50..e96f492cc6 100755
--- a/t/t3452-history-split.sh
+++ b/t/t3452-history-split.sh
@@ -4,6 +4,7 @@ test_description='tests for git-history split subcommand'
 
 . ./test-lib.sh
 . "$TEST_DIRECTORY/lib-log-graph.sh"
+. "$TEST_DIRECTORY/lib-gpg.sh"
 
 # The fake editor takes multiple arguments, each of which represents a commit
 # message. Subsequent invocations of the editor will then yield those messages
@@ -36,6 +37,42 @@ expect_tree_entries () {
 	test_cmp expect actual
 }
 
+test_split_gpg_sign () {
+	must_fail= will=will
+	if test "x$1" = "x!"
+	then
+		must_fail=test_must_fail
+		will="will not"
+		shift
+	fi
+	conf=$1
+	shift
+
+	test_expect_success GPG "split $* with commit.gpgsign=$conf $will sign rewritten history" "
+		test_when_finished 'rm -rf repo' &&
+		git init repo &&
+		(
+			cd repo &&
+			test_commit initial &&
+			touch bar foo &&
+			git add . &&
+			git commit -m split-me &&
+			test_commit tip &&
+
+			git config commit.gpgsign $conf &&
+			set_fake_editor 'first' 'second' &&
+			git history split $* HEAD~ <<-EOF &&
+			y
+			n
+			EOF
+
+			$must_fail git verify-commit HEAD~2 &&
+			$must_fail git verify-commit HEAD~ &&
+			$must_fail git verify-commit HEAD
+		)
+	"
+}
+
 test_expect_success 'refuses to work with merge commits' '
 	test_when_finished "rm -rf repo" &&
 	git init repo &&
@@ -141,6 +178,13 @@ test_expect_success 'can split up tip commit' '
 	)
 '
 
+test_split_gpg_sign ! false
+test_split_gpg_sign   true
+test_split_gpg_sign   false --gpg-sign
+test_split_gpg_sign ! true  --no-gpg-sign
+test_split_gpg_sign ! true  --gpg-sign --no-gpg-sign
+test_split_gpg_sign   false --no-gpg-sign --gpg-sign
+
 test_expect_success 'can split up root commit' '
 	test_when_finished "rm -rf repo" &&
 	git init repo &&
diff --git a/t/t3453-history-fixup.sh b/t/t3453-history-fixup.sh
index 868298e248..cd20a23115 100755
--- a/t/t3453-history-fixup.sh
+++ b/t/t3453-history-fixup.sh
@@ -3,6 +3,7 @@
 test_description='tests for git-history fixup subcommand'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
 
 fixup_with_message () {
 	cat >message &&
@@ -21,6 +22,37 @@ expect_changes () {
 	test_cmp expect actual
 }
 
+test_fixup_gpg_sign () {
+	must_fail= will=will
+	if test "x$1" = "x!"
+	then
+		must_fail=test_must_fail
+		will="will not"
+		shift
+	fi
+	conf=$1
+	shift
+
+	test_expect_success GPG "fixup $* with commit.gpgsign=$conf $will sign rewritten history" "
+		test_when_finished 'rm -rf repo' &&
+		git init repo &&
+		(
+			cd repo &&
+			test_commit first &&
+			test_commit second &&
+			test_commit third &&
+
+			git config commit.gpgsign $conf &&
+			echo fix >>second.t &&
+			git add second.t &&
+			git history fixup $* HEAD~ &&
+
+			$must_fail git verify-commit HEAD~ &&
+			$must_fail git verify-commit HEAD
+		)
+	"
+}
+
 test_expect_success 'errors on missing commit argument' '
 	test_when_finished "rm -rf repo" &&
 	git init repo &&
@@ -229,6 +261,13 @@ test_expect_success 'preserves commit message and authorship' '
 	)
 '
 
+test_fixup_gpg_sign ! false
+test_fixup_gpg_sign   true
+test_fixup_gpg_sign   false --gpg-sign
+test_fixup_gpg_sign ! true  --no-gpg-sign
+test_fixup_gpg_sign ! true  --gpg-sign --no-gpg-sign
+test_fixup_gpg_sign   false --no-gpg-sign --gpg-sign
+
 test_expect_success 'updates all descendant branches by default' '
 	test_when_finished "rm -rf repo" &&
 	git init repo --initial-branch=main &&
-- 
2.55.0


^ permalink raw reply related

* Re: Unexpected recursion in 'git rm'
From: Mikael Magnusson @ 2026-07-03 15:25 UTC (permalink / raw)
  To: Евгений Плискин
  Cc: git
In-Reply-To: <323134122.20260702104910@gmail.com>

On Thu, Jul 2, 2026 at 9:51 AM Евгений Плискин <eugene.pliskin@gmail.com> wrote:
>
> Hello.
>
> The following git command does recurse directories as contrary to the reference (https://git-scm.com/docs/git-rm):
>
>     git rm -n *.json
>
> Without directory specification before '*.json' this command is not expected to recurse directories, but it really does.
>
> git version 2.55.0.windows.1

I can't see any formulation in the manpage reference that suggests it
wouldn't recurse, though you might overall get less surprised if you
set the failglob option in bash. Then the shell would notice *.json
has no matches, and you'd have to say git rm -n '*.json' to let git
process the glob instead of the shell. See also
https://git-scm.com/docs/gitglossary (as referenced from the git-rm
page) which says:

  the rest of the pathspec is a pattern for the remainder of the pathname.
  Paths relative to the directory prefix will be matched against that
  pattern using fnmatch(3); in particular, * and ? can match directory
  separators.

-- 
Mikael Magnusson

^ permalink raw reply

* Re: Programmatically edit the git rebase sequence?
From: D. Ben Knoble @ 2026-07-03 15:31 UTC (permalink / raw)
  To: brian m. carlson, Matthias Beyer, git, neikos
In-Reply-To: <ake8OAIyK-ELs-fU@fruit.crustytoothpaste.net>

On Fri, Jul 3, 2026 at 9:46 AM brian m. carlson
<sandals@crustytoothpaste.net> wrote:
>
> On 2026-07-03 at 12:02:33, Matthias Beyer wrote:
> > Is there a way I am not aware of to do that manual step programatically?
> > Something like
> >
> >     git rebase -i master --edit-commits="$(git log master..mybranch --diff-filter=M --format="%H" -- "./subdir/*.rs")"
> >
> > would be convenient here, although I would understand if that is too
> > much clutter for the already very heavy git CLI interface :-)
>
> Yes, such a thing exists.  You want `GIT_SEQUENCE_EDITOR`, which is an
> `EDITOR`-like command that edits the rebase list in place.  So tools
> like `ed`, `ex`, `sed -i`, `perl -i`, or `ruby -i` would be useful here.
>
> So you might want something like this (untested):
>
>     GIT_SEQUENCE_EDITOR="perl -pi -e 's/^pick ($(git log master..mybranch --diff-filter=M --format="%h" -- "./subdir/*.rs" | paste -d '\''|'\'' -s -))/edit \$1/'" \
>     git rebase -i master

Yep. Although, the last time I wrote a program that used
GIT_SEQUENCE_EDITOR, I had to deal with enough shell-nesting that it
was more convenient to make the editor program separate:

- git-split-topic [1] sets up a sequence editor with some interpolated
arguments that also re-invokes the original
- split-topic-editor [2] pre-processes the rebase script with ed

[1]: https://github.com/benknoble/Dotfiles/blob/ca48a09f783b78e038a41c5d60ee6b163337f580/links/bin/git-split-topic#L47-L53
[2]: https://github.com/benknoble/Dotfiles/blob/master/links/bin/split-topic-editor

See the comments in [1] for some weirdness in the invocation of the
sequence editor, where it gets "$@" appended to the command string
(meaning the last command in a chain might need to be written
specially).

And yes, I'm sure there's a few ways for things to go wrong with the
way some of the shell script variables are embedded into strings for
another shell to evaluate later; if I rewrote with Zsh, I could at
least use the ${(q)var} forms to perhaps handle that better…

-- 
D. Ben Knoble

^ permalink raw reply

* Re: [PATCH] meson: wire up USE_NSEC build knob
From: D. Ben Knoble @ 2026-07-03 15:46 UTC (permalink / raw)
  To: Patrick Steinhardt
  Cc: Jeff King, git, brian m . carlson, Junio C Hamano, Ramsay Jones
In-Reply-To: <ajjuoS5Qc3K0nCRl@pks.im>

[with apologies for the delay; I wasn't paying attention to "What's
cooking" to notice that this was waiting on my response.]

On Mon, Jun 22, 2026 at 4:13 AM Patrick Steinhardt <ps@pks.im> wrote:
>
> On Sun, Jun 21, 2026 at 01:49:34PM -0400, Jeff King wrote:
> > On Sat, Jun 20, 2026 at 12:00:24PM -0400, D. Ben Knoble wrote:
> >
> > > Autotools-style builds permit enabling USE_NSEC for cases where that's
> > > desired; the equivalent knob is missing from meson-based builds.
> >
> > Seems reasonable. This is not changing the defaults at all, but just
> > bringing meson's options to parity with the Makefile.

For now, I still think this makes me in favor of the patch: source
distributions like Gentoo can then offer the build knob for those who,
in Junio's words

> know the target box with its filesystems happen to be OK with the option

Otherwise, the discussion would suggest removing it from Makefile as
an option :)

> I was originally wondering whether I should recommend that Meson can
> auto-discover the availability of nanoseconds. But your below remarks
> make me question that.
>
> > I'm not still not sure if turning on USE_NSEC is a good idea. There's
> > some discussion in Documentation/technical/racy-git.adoc:
> >
> >   With `USE_NSEC`
> >   compile-time option, `st_mtim.tv_nsec` and `st_ctim.tv_nsec`
> >   members are also compared. On Linux, this is not enabled by default
> >   because in-core timestamps can have finer granularity than
> >   on-disk timestamps, resulting in meaningless changes when an
> >   inode is evicted from the inode cache.  See commit 8ce13b0
> >   of git://git.kernel.org/pub/scm/linux/kernel/git/tglx/history.git
> >   ([PATCH] Sync in core time granularity with filesystems,
> >   2005-01-04). This patch is included in kernel 2.6.11 and newer, but
> >   only fixes the issue for file systems with exactly 1 ns or 1 s
> >   resolution. Other file systems are still broken in current Linux
> >   kernels (e.g. CEPH, CIFS, NTFS, UDF), see
> >   https://lore.kernel.org/lkml/5577240D.7020309@gmail.com/
> >
> > That's the most succinct description of the problem I've seen, but I
> > have no idea how widely it still applies. Kernel 2.6.11 is quite old
> > now, but I could believe that other filesystems (especially network
> > ones) still exhibit the issue.
> >
> > So I guess if we wanted to go further it would take some digging as to
> > how each platform behaves, and then flipping the config.make.uname knob
> > for ones where it can be argued that the behavior is always reasonable.
>
> Yeah, it would be nice indeed to figure out whether these concerns still
> apply. If they do, I would argue that it might even make sense to remove
> the build option completely. It doesn't really make sense in my opinion
> to have a build option that nobody uses and that is subtly broken when
> enabled.
>
> > But that's all outside the scope of your patch here.
>
> Kind of, I guess. If we figure that this mechanism is still subtly broken
> then I'd argue that it doesn't make sense to expose the option via
> Meson.
>
> Patrick

This bit addressed more down-thread, so I'll reply there.

To summarize: If we're all leaning in the direction of a run-time flag
instead, I can noodle in that direction. That certainly involves a bit
more surgery than just giving Meson access to the option, but the
dynamism may be nice. I'm not too sure how we'd write a test case for
it, though.

^ permalink raw reply

* Re: [PATCH] meson: wire up USE_NSEC build knob
From: D. Ben Knoble @ 2026-07-03 15:46 UTC (permalink / raw)
  To: Jeff King
  Cc: Patrick Steinhardt, git, brian m . carlson, Junio C Hamano,
	Ramsay Jones
In-Reply-To: <20260628081806.GA3594700@coredump.intra.peff.net>

FWIW…

On Sun, Jun 28, 2026 at 4:18 AM Jeff King <peff@peff.net> wrote:
>
> On Mon, Jun 22, 2026 at 10:13:21AM +0200, Patrick Steinhardt wrote:
>
> > > So I guess if we wanted to go further it would take some digging as to
> > > how each platform behaves, and then flipping the config.make.uname knob
> > > for ones where it can be argued that the behavior is always reasonable.
> >
> > Yeah, it would be nice indeed to figure out whether these concerns still
> > apply. If they do, I would argue that it might even make sense to remove
> > the build option completely. It doesn't really make sense in my opinion
> > to have a build option that nobody uses and that is subtly broken when
> > enabled.
>
> I suspect it works just fine on some platforms and some filesystems
> (i.e., those that actually store nanoseconds on disk). So probably Linux
> with ext4 is OK. That's just guessing, though.
>
> If I understand the original problem correctly, then doing this:
>
>   touch foo
>   ls --full-time foo
>   echo 3 | sudo tee /proc/sys/vm/drop_caches
>   ls --full-time foo
>
> should be instructive. If it shows the same time for both "ls" calls,
> then USE_NSEC would be fine. If it doesn't, then the system is losing
> the nanosecond information when it drops the cache and has to reload
> from disk (and thus USE_NSEC would cause spurious stat mismatches).
>
> On my ext4 system, I get the same answers. So far so good.
>
> I get the same answers with a loopback-mounted ext2 system. Which
> surprised me a bit, but even unmounting and remounting the filesystem,
> the nanosecond times are still there. So...I guess ext2 supports
> nanoseconds.

I also get 9 digits of fractional precision (nanoseconds) with the
same answers across dropped cache on my XFS system.

> I tried with a vfat mount, and it also works: we don't have nanoseconds
> either before or after. That makes sense, and implies that modern Linux
> will always be OK (because it limits the cached VFS response to what the
> underlying filesystem can handle).
>
> So...maybe this is just a non-issue these days, at least on Linux?
>
> > > But that's all outside the scope of your patch here.
> >
> > Kind of, I guess. If we figure that this mechanism is still subtly broken
> > then I'd argue that it doesn't make sense to expose the option via
> > Meson.
>
> True, but AFAICT it probably is safe these days, at least one some
> platforms.
>
> -Peff

^ permalink raw reply

* Re: [PATCH] meson: wire up USE_NSEC build knob
From: D. Ben Knoble @ 2026-07-03 15:46 UTC (permalink / raw)
  To: Jeff King
  Cc: Patrick Steinhardt, git, brian m . carlson, Junio C Hamano,
	Ramsay Jones
In-Reply-To: <20260630054314.GD2495216@coredump.intra.peff.net>

On Tue, Jun 30, 2026 at 1:43 AM Jeff King <peff@peff.net> wrote:
>
> On Mon, Jun 29, 2026 at 08:08:42AM +0200, Patrick Steinhardt wrote:
>
> > > True, but AFAICT it probably is safe these days, at least one some
> > > platforms.
> >
> > Hm. That makes me wonder whether it is the completely wrong approach to
> > make this a build option then. If it works on some systems and only on
> > some filesystems, then a build option is just too coarse-grained. A
> > distro wouldn't really be able to ever enable the option, unless it knew
> > that repositories will only ever exist on a filesystem that works. Which
> > I guess is an assumption that no distro can make.
> >
> > So instead, I wonder whether we should treat this the same as for
> > example "core.ignoreCase", where we only use nanosecond resolution when
> > opted in by the user. Ideally, if we had a way to detect brokenness, we
> > could even make git-init(1) set it automatically.
>
> Yeah, this came up earlier in the thread. It would be nice if we could
> set it automatically, but I'm not sure we have a good way of testing a
> particular filesystem. I think the sequence is:
>
>   1. stat() a file, getting nanoseconds
>
>   2. somehow flush the kernel's in-core inode cache
>
>   3. stat() it again and compare
>
> Step 2 is the tricky part. ;) It's not only not portable, but probably
> something that would annoy users if we did it for every repo creation.
>
> It would also be nice if we could actually verify that the sequence
> above _does_ show the problem. I was not able to come up with a failing
> instance on my modern Linux machine (even going as far as unmounting and
> re-mounting for step 2).

Brian suggested in a sibling message that a statfs call could be used
for "known-good" file system types, IIUC.

> But I do agree in general that it should be a config flag and not a
> build option. Run-time flags are more friendly to users when there is no
> good reason to avoid them.
>
> -Peff

If we're all leaning in the direction of a run-time flag instead, I
can noodle in that direction. That certainly involves a bit more
surgery than just giving Meson access to the option, but the dynamism
may be nice. I'm not too sure how we'd write a test case for it,
though.

^ permalink raw reply

* Re: git-diff in a worktree is an order of magnitude slower?
From: D. Ben Knoble @ 2026-07-03 15:57 UTC (permalink / raw)
  To: Jeff King; +Cc: Junio C Hamano, Git
In-Reply-To: <20260621174518.GB2206349@coredump.intra.peff.net>

On Sun, Jun 21, 2026 at 1:45 PM Jeff King <peff@peff.net> wrote:
>
> On Sun, Jun 21, 2026 at 01:24:32PM -0400, Jeff King wrote:
>
> > I think this is the core of the issue. These entries are "racy git
> > dirty" in the sense that their mtimes are the same as the index mtime,
> > and so we double-check the contents. This is the first bullet point
> > under the "Racy Git" section of Documentation/technical/racy-git.adoc.
> >
> > But diffcore_skip_stat_unmatch() doesn't count them as dirty, so we
> > don't increment the counter, and thus top-level git-diff won't write out
> > the new index. And thus every subsequent diff repeats the same
> > expensive double-check.
> >
> > But I'm not sure where the blame lies. Either:
> >
> >   1. diffcore_skip_stat_unmatch() should be counting these in its
> >      "dirty" counter; or
>
> BTW, I don't think diffcore actually has the information it would need
> to do so. The racy stuff is handled under the hood in ie_match_stat(),
> which returns only a set of "changed" flags. So the caller cannot tell
> the difference between the two cases:
>
>   1. We checked ce_match_stat_basic() which said "no change", and then
>      is_racy_timestamp() was false, so that was good enough.
>
>   2. is_racy_timestamp() is true, so we further did a content check,
>      found nothing, and returned the same "no change"
>
> Obviously we could pass back another flag, but that would disrupt the
> other callers. Hmm. It looks like we could pass in a flag to say "assume
> racy entries are modified". And then they come back to the diff code,
> diffcore_skip_stat_unmatch() sees they're not real diffs and suppresses
> them, but we _do_ count them as stat-dirty.
>
> Like this:
>
> diff --git a/builtin/diff.c b/builtin/diff.c
> index 4b46e394ce..4d36b5c1e0 100644
> --- a/builtin/diff.c
> +++ b/builtin/diff.c
> @@ -271,6 +271,9 @@ static void builtin_diff_files(struct rev_info *revs, int argc, const char **arg
>                 argv++; argc--;
>         }
>
> +       if (revs->diffopt.skip_stat_unmatch)
> +               options |= DIFF_RACY_IS_MODIFIED;
> +
>         /*
>          * "diff --base" should not combine merges because it was not
>          * asked to.  "diff -c" should not densify (if the user wants
>
> That seems to work, in the sense that "git diff" does refresh the index
> afterwards. But the timings are a bit funny.
>
> In my working tree of linux.git with many racy entries it was ~500ms to
> do the first diff (and the second, and so on, because we never updated
> the index). After the patch above, it is 1800ms to do the first diff,
> and then fast (~30ms) after.
>
> I could believe it takes twice as long when we refresh the index
> (because I don't think we use the stat-cleanliness we collected from the
> diff, but rather just do a from-scratch index refresh). But that would
> imply it should take ~1000ms. Where does the extra 800ms go? I guess
> that somehow the content-check done by diffcore_skip_stat_unmatch() is
> slower than the one done by ie_match_stat(). I think the individual
> functions are respectively diff_filespec_check_stat_unmatch() and
> ce_modified_check_fs().
>
> I don't know if any of this is really worth digging too far. This feels
> like a case we could do a bit better at, but I wonder how much it
> matters in practice. As soon as you do any index-refresh (including "git
> status"), the racy entries are cleared and everything is faster. It
> just seems kind of lame that we write out the initial working tree with
> so many racy entries.
>
> -Peff

I'd like to dig into this some more, personally, but I'm not sure when
I'll have the time (and we're deep in the guts of 2 systems whose
implementation are quite foreign to me---the index and the diff
machinery). The main reason is that I noticed this all when trying to
figure out why my shell prompt was slow :) I'm willing to pay a slow
first prompt for all subsequent prompts to be faster without having to
remember (and alert others) "oh, this is racy git, just run 'git
status' to fix it" or something. Obviously it's even better if that
first racy diff + index update is not so slow, though.

I think I saw 2 potential areas to dig?
1. The time spent on that refresh index diff mentioned above
2. Limiting racy entries on initial write.

The latter was, I think, dismissed down-thread if I understood? It's
not so nice to stall for a full second just to avoid raciness, and
USE_NSEC alleviates the problem, too. (If that became available to
more folks, see USE_NSEC meson thread, then I suppose I would be less
likely to dig into (1) even though it sounds like an interesting
puzzle.) So maybe instead of "dismissed" I mean "we decided to keep
the USE_NSEC gate."

The former I saw some interesting discussion about how to communicate
bits to code, but no hint as to whether that changed your initial
measurements. I suppose I could try for myself, but it will take me
some time to process Junio's suggestions there into actual code.

-- 
D. Ben Knoble

^ permalink raw reply

* [GSoC Patch] repo: support category-based prefix querying for info keys
From: K Jayatheerth @ 2026-07-03 16:47 UTC (permalink / raw)
  To: git; +Cc: jltobler, lucasseikioshiro, K Jayatheerth

Currently, git repo info relies on an all-or-nothing query model
where users must either know the exact, fully-qualified key name or use
the --all flag to dump the entire repository state.
As the number of supported keys expands, dumping all metadata and
relying on external filters like grep becomes an inefficient bottleneck
for a plumbing command.

Enable category-based prefix querying so users can request
entire groups of related keys natively

Mentored-by: Justin Tobler <jltobler@gmail.com>
Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
---
Hi!

This patch adds category-based prefix querying to
`git repo info` as part of my GSoC project.
A quick note on the implementation: I replaced the `bsearch` with
a linear search to find the initial prefix match.
I discussed this with my mentors, and we decided to fall back
to a linear search because the overhead of a custom binary search
(to find the *first* match in a block) wasn't justified given
the currently small size of the `repo_info_field` array.

Since the array is strictly sorted alphabetically,
the loop safely short-circuits via `strncmp`
once it steps outside the matching prefix block.

Looking forward to feedback!

 Documentation/git-repo.adoc | 12 ++++++++
 builtin/repo.c              | 58 +++++++++++++++++++++++--------------
 t/t1900-repo-info.sh        | 16 ++++++++++
 3 files changed, 64 insertions(+), 22 deletions(-)

diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index 42262c1983..3e840d6323 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -25,6 +25,12 @@ COMMANDS
 	the requested data will be returned based on their keys (see "INFO KEYS"
 	section below).
 +
+If a `<key>` argument matches a category prefix (i.e. a namespace that ends at
+a `.` boundary), all keys within that namespace are returned. For example,
+`layout` returns both `layout.bare` and `layout.shallow`. The prefix must
+align with a namespace boundary; partial prefixes that do not end at a `.`
+separator (e.g. `lay`) are treated as unknown keys and will produce an error.
++
 The values are returned in the same order in which their respective keys were
 requested. The `--all` flag requests the values for all the available keys.
 +
@@ -126,6 +132,12 @@ using the `nul` format:
 git repo info --format=nul layout.bare layout.shallow
 ------------
 
+* Retrieves all keys under the `layout` category prefix:
++
+------------
+git repo info layout
+------------
+
 SEE ALSO
 --------
 linkgit:git-rev-parse[1]
diff --git a/builtin/repo.c b/builtin/repo.c
index 71a5c1c29c..91ea5b5459 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -90,24 +90,27 @@ static const struct repo_info_field repo_info_field[] = {
 	{ "references.format", get_references_format },
 };
 
-static int repo_info_field_cmp(const void *va, const void *vb)
+static int is_valid_prefix_match(const char *key, const char *prefix)
 {
-	const struct repo_info_field *a = va;
-	const struct repo_info_field *b = vb;
+	size_t prefix_len = strlen(prefix);
 
-	return strcmp(a->key, b->key);
+	if (!prefix_len)
+		return 0;
+
+	if (strncmp(key, prefix, prefix_len))
+		return 0;
+
+	return key[prefix_len] == '\0' || prefix[prefix_len - 1] == '.' || key[prefix_len] == '.';
 }
 
-static const struct repo_info_field *get_repo_info_field(const char *key)
+static size_t find_first_repo_info_field_match(const char *prefix)
 {
-	const struct repo_info_field search_key = { key, NULL };
-	const struct repo_info_field *found = bsearch(&search_key,
-						      repo_info_field,
-						      ARRAY_SIZE(repo_info_field),
-						      sizeof(*found),
-						      repo_info_field_cmp);
-
-	return found;
+	for (size_t i = 0; i < ARRAY_SIZE(repo_info_field); i++) {
+		if (is_valid_prefix_match(repo_info_field[i].key, prefix)) {
+			return i;
+		}
+	}
+	return SIZE_MAX;
 }
 
 static void print_field(enum output_format format, const char *key,
@@ -135,17 +138,28 @@ static int print_fields(int argc, const char **argv,
 	struct strbuf valbuf = STRBUF_INIT;
 
 	for (int i = 0; i < argc; i++) {
-		const char *key = argv[i];
-		const struct repo_info_field *field = get_repo_info_field(key);
-
-		if (!field) {
-			ret = error(_("key '%s' not found"), key);
-			continue;
+		const char *prefix = argv[i];
+		size_t prefix_len = strlen(prefix);
+		size_t idx = find_first_repo_info_field_match(prefix);
+		int found = 0;
+
+		for (; idx < ARRAY_SIZE(repo_info_field); idx++) {
+			const struct repo_info_field *field = &repo_info_field[idx];
+
+			if (strncmp(field->key, prefix, prefix_len))
+				break;
+
+			if (is_valid_prefix_match(field->key, prefix)) {
+				strbuf_reset(&valbuf);
+				field->get_value(repo, &valbuf);
+				print_field(format, field->key, valbuf.buf);
+				found = 1;
+			}
 		}
 
-		strbuf_reset(&valbuf);
-		field->get_value(repo, &valbuf);
-		print_field(format, key, valbuf.buf);
+		if (!found) {
+			ret = error(_("key '%s' not found"), prefix);
+		}
 	}
 
 	strbuf_release(&valbuf);
diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
index 39bb77dda0..80ae8f8396 100755
--- a/t/t1900-repo-info.sh
+++ b/t/t1900-repo-info.sh
@@ -149,6 +149,22 @@ test_expect_success 'git repo info --keys uses lines as its default output forma
 	test_cmp expect actual
 '
 
+test_expect_success 'git repo info with category prefix returns all keys in namespace' '
+	cat >expect <<-\EOF &&
+	layout.bare=false
+	layout.shallow=false
+	EOF
+	git init prefix-repo &&
+	git -C prefix-repo repo info layout >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git repo info with invalid partial boundary fails' '
+	echo "error: key ${SQ}lay${SQ} not found" >expect &&
+	test_must_fail git -C prefix-repo repo info lay 2>actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'git repo info -h shows only repo info usage' '
 	test_must_fail git repo info -h >actual &&
 	test_grep "git repo info" actual &&
-- 
2.55.0-rc1

^ permalink raw reply related

* Re: [PATCH v8 06/11] reset: introduce ability to skip updating HEAD
From: Junio C Hamano @ 2026-07-03 17:33 UTC (permalink / raw)
  To: Patrick Steinhardt
  Cc: Christian Couder, git, Pablo Sabater, Kristoffer Haugsbakk,
	Phillip Wood, Christian Couder
In-Reply-To: <akd14OKLT9lAMKTf@pks.im>

Patrick Steinhardt <ps@pks.im> writes:

> On Fri, Jul 03, 2026 at 08:59:30AM +0200, Christian Couder wrote:
>> On Wed, Jul 1, 2026 at 1:35 PM Patrick Steinhardt <ps@pks.im> wrote:
>> 
>> > @@ -113,6 +114,9 @@ int reset_working_tree(struct repository *r,
>> >         if (opts->branch_msg && !opts->branch)
>> >                 BUG("branch reflog message given without a branch");
>> >
>> > +       if (update_orig_head && !update_head)
>> > +               BUG("cannot update ORIG_HEAD without updating HEAD" );
>> 
>> There is a spurious space character between `HEAD"` and `);`.
>
> Ugh, indeed, well spotted. Junio, is this something you can fix up or
> shall I send a new version for this?

I just locally updated it, so no need to resend the whole series, as
there does not seem to be any other issues.

Thanks, all.

^ permalink raw reply

* Re: [PATCH v2 2/9] t0021: skip EXPENSIVE test that is broken without SIZE_T_IS_32BIT
From: Junio C Hamano @ 2026-07-03 17:36 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git, Johannes Schindelin, SZEDER Gábor, Jeff King
In-Reply-To: <20260703-b4-pks-t-fixes-for-GIT-TEST-LONG-v2-2-79076a7e0c62@pks.im>

Patrick Steinhardt <ps@pks.im> writes:

The subject reads "skip EXPENSIVE test that is broken without
SIZE_T_IS_32BIT", but we need to add prerequisite for 64BIT,
wouldn't it be more accurate to say without SIZE_T_IS_64BIT?

That is, the test is broken with SIZE_T_IS_32BIT, no?

> ...
> -test_expect_success EXPENSIVE 'filter large file' '
> +test_expect_success EXPENSIVE,SIZE_T_IS_64BIT 'filter large file' '
>  	test_config filter.largefile.smudge cat &&
>  	test_config filter.largefile.clean cat &&
>  	test_seq -f "%1048576d" 1 2048 >2GB &&

^ permalink raw reply

* Re: [PATCH v3 2/2] Makefile: support universal macOS builds via RUST_TARGETS
From: Shardul Natu @ 2026-07-03 17:37 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Shardul Natu via GitGitGadget, git, Kristoffer Haugsbakk,
	Koji Nakamaru
In-Reply-To: <xmqqldbsk51t.fsf@gitster.g>

> > +$(RUST_LIB): $(RUST_MEMBER_LIBS)
> > + @$(call mkdir_p_parent_template)
>
> The leading @ is a bit curious because among ~20 existing use of
> this pattern, nobody adds it to squelch "mkdir -p". In fact, the
> macro uses the standard pattern to define $(QUIET_MKDIR_P_PARENT)
> that does the squelching when $(V) is unset.

Done!

> Recipe parts in our Makefile that are written in bourne shell, the
> CodingGuidelines apply.
>
> $ git grep -n -e 'if \[' ':(glob)**/Makefile'
>
> gives empty. Probably,
>
> if test $(words $(RUST_TARGETS)) -gt 1; \
> then \
>
> would fit better.

Thank you for pointing out the CodingGuidelines rule for Bourne shell
snippets in Makefiles. I have updated the condition from "if [" to "if
test".

Thanks,
Shardul


On Thu, Jul 2, 2026 at 10:36 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> "Shardul Natu via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> > From: Shardul Natu <snatu@google.com>
> >
> > On macOS, Universal Binaries contain native executable code for
> > multiple architectures (such as Intel x86_64 and Apple Silicon arm64)
> > bundled into a single file. This is standard practice for macOS
> > distribution and CI packaging (such as internal distribution packages
> > or tooling like Burrito/Homebrew), allowing a single build artifact
> > to run natively across all Macs without Rosetta emulation or
> > maintaining separate packages.
> >
> > When building Git C code for multiple architectures on macOS, the
> > Apple toolchain (clang) natively supports universal builds via
> > CFLAGS/LDFLAGS. When "-arch x86_64 -arch arm64" is passed, clang
> > automatically compiles and links universal binaries for all C object
> > files and executables out of the box.
> >
> > Cargo and rustc, however, do not support multiple "-arch" flags or
> > emitting universal binaries in a single invocation. Instead, Cargo
> > requires invoking each target triple independently (e.g., passing
> > "--target x86_64-apple-darwin" and "--target aarch64-apple-darwin").
>
> This is much easier to understand for those of us unfamiliar with
> the macOS ecosystem.  Very much appreciated.
>
> > +$(RUST_LIB): $(RUST_MEMBER_LIBS)
> > +     @$(call mkdir_p_parent_template)
>
> The leading @ is a bit curious because among ~20 existing use of
> this pattern, nobody adds it to squelch "mkdir -p".  In fact, the
> macro uses the standard pattern to define $(QUIET_MKDIR_P_PARENT)
> that does the squelching when $(V) is unset.
>
> > +     $(QUIET_GEN)\
> > +     if [ $(words $(RUST_TARGETS)) -gt 1 ]; then \
>
> Recipe parts in our Makefile that are written in bourne shell, the
> CodingGuidelines apply.
>
>     $ git grep -n -e 'if \[' ':(glob)**/Makefile'
>
> gives empty.  Probably,
>
>         if test $(words $(RUST_TARGETS)) -gt 1; \
>         then \
>
> would fit better.
>
> > +             lipo -create $^ -output $@; \
> > +     else \
> > +             cp $< $@; \
> > +     fi
> > +endif
> >
> >  .PHONY: rust
> >  rust: $(RUST_LIB)
>
> Other than that, looking good.
>
> Thanks.

^ permalink raw reply

* Re: [PATCH v2 5/9] t7508: skip EXPENSIVE test that is broken without SIZE_T_IS_32BIT
From: Junio C Hamano @ 2026-07-03 17:38 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git, Johannes Schindelin, SZEDER Gábor, Jeff King
In-Reply-To: <20260703-b4-pks-t-fixes-for-GIT-TEST-LONG-v2-5-79076a7e0c62@pks.im>

Patrick Steinhardt <ps@pks.im> writes:

The same comment as [2/9] about 32 vs 64 in the subject "skip
EXPENSIVE test that is broken without SIZE_T_IS_32BIT" applies here,
I think.

> ...
> -test_expect_success EXPENSIVE 'status does not re-read unchanged 4 or 8 GiB file' '
> +test_expect_success EXPENSIVE,SIZE_T_IS_64BIT 'status does not re-read unchanged 4 or 8 GiB file' '
>  	(
>  		mkdir large-file &&
>  		cd large-file &&

^ permalink raw reply

* Re: [PATCH] git-diff: Introduce --index and deprecate --cached.
From: krobchai wongkamw @ 2026-07-03 18:04 UTC (permalink / raw)
  To: jakesteward0916; +Cc: ae, git, junkio



ส่งจาก iPhone ของฉัน

^ permalink raw reply

* Re: [PATCH v8 00/11] builtin/history: introduce "drop" subcommand
From: Junio C Hamano @ 2026-07-03 20:02 UTC (permalink / raw)
  To: Christian Couder
  Cc: Patrick Steinhardt, git, Pablo Sabater, Kristoffer Haugsbakk,
	Phillip Wood, Christian Couder
In-Reply-To: <CAP8UFD3OAktVQsLuqBNFH2uhEO31PH8ZF3ZT1ZW8k++XE8YLPw@mail.gmail.com>

Christian Couder <christian.couder@gmail.com> writes:

>> Changes in v8:
>>   - Pass `RESOLVE_REF_READING` to make `refs_resolve_ref_unsafe()`
>>     return a NULL pointer when it cannot resolve the reference.
>>   - Drop unneeded code that sets `head_target = "HEAD"` on detached
>>     HEAD.
>>   - Add a test case that verifies that we can drop commits with
>>     "--update-refs=head" and a detached HEAD.
>>   - Link to v7: https://patch.msgid.link/20260629-b4-pks-history-drop-v7-0-6e9392a957d8@pks.im
>
> Except for the style nit in patch 6 (spurious space character), this
> series looks good to me.

Thanks.

^ permalink raw reply

* Re: [PATCH] precompose_utf8: use a flex array for d_name
From: Ihar Hrachyshka @ 2026-07-03 20:20 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git
In-Reply-To: <akd1m6KoUh7N8yyE@pks.im>

On 7/3/26 4:40 AM, Patrick Steinhardt wrote:
> On Thu, Jul 02, 2026 at 10:35:54PM -0400, Ihar Hrachyshka wrote:
>> On macOS, git status may abort while reading a directory entry
>> whose UTF-8 name grows past NAME_MAX bytes:
>>
>>    __chk_fail_overflow
>>    __strlcpy_chk
>>    precompose_utf8_readdir
>>    read_directory_recursive
>>    wt_status_collect
>>    cmd_status
>>
>> The precompose wrapper already reallocates dirent_prec_psx for
>> long names, but d_name is declared as char[NAME_MAX + 1]. A
>> fortified libc can still see that declared object size and reject a
>> larger strlcpy bound, even though the allocation was grown.
>>
>> Make d_name a FLEX_ARRAY and size allocations from offsetof(). That
>> matches the actual object layout with the dynamic allocation, so the
>> fortified copy sees a destination whose size can grow with max_name_len.
>>
>> Add a regression test that creates a 261-byte non-ASCII basename and
>> runs status with core.precomposeunicode enabled.
> Hm. Why does macOS even allow you to create a file that has a basename
> longer than NAME_MAX? Does macOS count unicode characters specially?


Yes, macOS file names can exceed NAME_MAX bytes because the real dirent 
limit in system headers is:

#define __DARWIN_MAXPATHLEN 1024

#define __DARWIN_STRUCT_DIRENTRY { \
char d_name[__DARWIN_MAXPATHLEN]; /* entry name (up to MAXPATHLEN bytes) 
*/ \
}

(for a very old 32-bit ABI it's 256 but it's not really relevant)

This in-memory limit may be further capped by file system. For HFS+, 
it's 255 16-bit Unicode characters (as per on-disk format). For APFS, 
on-disk theoretically allows up to 1022 UTF-8 bytes, but my testing 
suggests they still enforce the same 255 character limit somewhere in 
kernel API layer. (Which means that they could later expand the maximum 
filename length further without changing the on-disk format.)

So effectively, today on Darwin, the real limit is "up to 255 2-byte 
code points", not bytes. Which is potentially beyond NAME_MAX.

...that said, Linux readdir() doesn't guarantee NAME_MAX limit either. 
 From readdir(3):

"""

         Note that while the call

             fpathconf(fd, _PC_NAME_MAX)

         returns the value 255 for most filesystems, on some filesystems
         (e.g., CIFS, Windows SMB servers), the null-terminated filename
         that is (correctly) returned in .d_name can actually exceed this
         size.  In such cases, the .d_reclen field will contain a value
         that exceeds the size of the glibc dirent structure shown above.

"""


The man page also advises against using sizeof() against dirent structs. 
(Which is what we currently do - against our own MacOS helper dirent 
struct.)


As a side note, it probably means neither Darwin nor Linux readdir() is 
POSIX compliant, because, as per:

https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/dirent.h.html


"The array d_name in each of these structures is of unspecified size, 
but shall contain a filename of at most {NAME_MAX} bytes followed by a 
terminating null byte."


>> diff --git a/compat/precompose_utf8.c b/compat/precompose_utf8.c
>> index 1711794..8077f62 100644
>> --- a/compat/precompose_utf8.c
>> +++ b/compat/precompose_utf8.c
>> @@ -19,6 +19,11 @@ typedef char *iconv_ibp;
>>   static const char *repo_encoding = "UTF-8";
>>   static const char *path_encoding = "UTF-8-MAC";
>>   
>> +static size_t dirent_prec_psx_size(size_t max_name_len)
>> +{
>> +	return st_add(offsetof(dirent_prec_psx, d_name), max_name_len);
>> +}
>> +
>>   static size_t has_non_ascii(const char *s, size_t maxlen, size_t *strlen_c)
>>   {
>>   	const uint8_t *ptr = (const uint8_t *)s;
>> @@ -114,8 +119,8 @@ const char *precompose_argv_prefix(int argc, const char **argv, const char *pref
>>   PREC_DIR *precompose_utf8_opendir(const char *dirname)
>>   {
>>   	PREC_DIR *prec_dir = xmalloc(sizeof(PREC_DIR));
>> -	prec_dir->dirent_nfc = xmalloc(sizeof(dirent_prec_psx));
>> -	prec_dir->dirent_nfc->max_name_len = sizeof(prec_dir->dirent_nfc->d_name);
>> +	prec_dir->dirent_nfc = xmalloc(dirent_prec_psx_size(NAME_MAX + 1));
>> +	prec_dir->dirent_nfc->max_name_len = NAME_MAX + 1;
> We have the `FLEX_ALLOC_MEM()` macro that would probably be a better fit
> compared to introducing `dirent_prec_psx_size()`.
>
> Also, when converting this to a flex array, can't we do better here and
> allocate the structures with the right size? Otherwise, I expect that we
> overallocate most of the entrise.


As I understand it, this is a *per-directory* buffer that starts from 
NAME_MAX + 1, then gets expanded as entries with names longer than 
NAME_MAX + 1 are encountered. It is reused for next entries.


>> @@ -145,8 +150,7 @@ struct dirent_prec_psx *precompose_utf8_readdir(PREC_DIR *prec_dir)
>>   		int ret_errno = errno;
>>   
>>   		if (new_maxlen > prec_dir->dirent_nfc->max_name_len) {
>> -			size_t new_len = sizeof(dirent_prec_psx) + new_maxlen -
>> -				sizeof(prec_dir->dirent_nfc->d_name);
>> +			size_t new_len = dirent_prec_psx_size(new_maxlen);
>>   
>>   			prec_dir->dirent_nfc = xrealloc(prec_dir->dirent_nfc, new_len);
>>   			prec_dir->dirent_nfc->max_name_len = new_maxlen;
> Okay, here we indeed have to realloc though, and thus we can't quite
> avoid `dirent_prec_psx_size()`. Too bad.
>
> Thanks!
>
> Patrick



^ permalink raw reply

* Re: Bug report - git rev-list --exclude-first-parent-only [SEC=UNOFFICIAL]
From: Junio C Hamano @ 2026-07-03 20:28 UTC (permalink / raw)
  To: Michael Hore; +Cc: Jerry Zhang, git@vger.kernel.org
In-Reply-To: <ME2PR01MB5490E3CE825C83474D1573CED1F52@ME2PR01MB5490.ausprd01.prod.outlook.com>

Michael Hore <Michael.Hore@asic.gov.au> writes:

> I believe I have found a bug -
>
> My repo has a commit structure like
>
> R2
> |\
> | F
> |/
> R1
>
> i.e.
>  - there is a merge commit R2 with parents R1 and F
>  - the parent of F is R1

IOW, R2 is a useless merge that could have been a simple
fast-forward directly to F.

> I ran "git rev-list --exclude-first-parent-only F ^R2"
>
> it gave the expected result: "F"
>
> I ran "git rev-list --exclude-first-parent-only F R1 ^R2"
>
> I expected the same result, but I got an unexpected result - nothing at all

This seems to have come from 9d505b7b49 (git-rev-list: add
--exclude-first-parent-only flag, 2022-01-11).  I do not know if the
original author is still around, but it would have been nicer to ask
for input from them (cc'ed).

A fix could be something along this line, but I've never used this
feature even once (I instead use Michael Haggerty's exellent "git
when-merged" thing), so I may very well be breaking _other_ use
cases this feature was originally intended for without knowing.

The patched part is inside a huge "while (parent)" loop.  The idea
is to break out before the loop goes on to smudge later parents when
we are in the "smudge only first parent as uninteresting, without
contaminating the history leading to other parents" mode.

 revision.c                   | 10 ++++++++--
 t/t6012-rev-list-simplify.sh | 18 ++++++++++++++++++
 2 files changed, 26 insertions(+), 2 deletions(-)

diff --git c/revision.c w/revision.c
index e91d7e1f11..1f50d42a7a 100644
--- c/revision.c
+++ w/revision.c
@@ -1151,12 +1151,18 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
 			if (p)
 				p->object.flags |= UNINTERESTING |
 						   CHILD_VISITED;
-			if (repo_parse_commit_gently(revs->repo, p, 1) < 0)
+			if (repo_parse_commit_gently(revs->repo, p, 1) < 0) {
+				if (revs->exclude_first_parent_only)
+					break;
 				continue;
+			}
 			if (p->parents)
 				mark_parents_uninteresting(revs, p);
-			if (p->object.flags & SEEN)
+			if (p->object.flags & SEEN) {
+				if (revs->exclude_first_parent_only)
+					break;
 				continue;
+			}
 			p->object.flags |= (SEEN | NOT_USER_GIVEN);
 			if (queue)
 				prio_queue_put(queue, p);
diff --git c/t/t6012-rev-list-simplify.sh w/t/t6012-rev-list-simplify.sh
index 4cecb6224c..2284bbba12 100755
--- c/t/t6012-rev-list-simplify.sh
+++ w/t/t6012-rev-list-simplify.sh
@@ -285,4 +285,22 @@ test_expect_success 'log --graph --simplify-merges --show-pulls' '
 	test_cmp expect actual
 '
 
+test_expect_success 'exclude-first-parent-only with parent already seen' '
+	git checkout --orphan test-seen &&
+	git rm -rf . &&
+	test_commit r1 &&
+	git checkout -b branch-f &&
+	test_commit f &&
+	git checkout test-seen &&
+	git merge --no-ff --no-edit -m r2 branch-f &&
+	git tag r2 &&
+
+	git rev-list --exclude-first-parent-only f ^r2 >actual &&
+	git rev-parse f >expect &&
+	test_cmp expect actual &&
+
+	git rev-list --exclude-first-parent-only f r1 ^r2 >actual2 &&
+	test_cmp expect actual2
+'
+
 test_done


^ permalink raw reply related

* Re: [PATCH v2 1/4] t1517: skip svn tests if svn is not installed
From: Junio C Hamano @ 2026-07-03 20:36 UTC (permalink / raw)
  To: Jeff King; +Cc: brian m. carlson, git
In-Reply-To: <20260702053724.GA3437623@coredump.intra.peff.net>

Jeff King <peff@peff.net> writes:

> fed to perl (no quotes around 1.1.0). We sometimes catch these cases
> automatically it results in an extra argument to test_expect_success,
> etc. But here you are unlucky enough that it does not (and anyway, we do
> not seem to have the same safety check for test_lazy_prereq; we'd just
> ignore the extra arguments).
>
> And of course being perl, it doesn't complain. I'm not sure how it is
> interpreted,

I happen to know ;-).

When you have more than two sequences of digits separated by dot,
like IP address 192.168.1.1, you are telling Perl to interpret the
sequence as a string, each byte of it is the number denoted by these
digits.  I believe this was invented primarily for IP addresses, but
it does not have to be just four digits.  To wit:

    $ perl -e 'print 65.66.67;'
    ABC
    $ perl -e 'print 65.66.67.68.69;'
    ABCDE

Of course, 65.66 is not AB, but a floating-point number that is
between integers 65 and 66:

    $ perl -e 'print 65.66;'
    65.66

> but I doubt the use of "gt" is right.

True.

^ permalink raw reply

* Re: [PATCH v2 0/4] rev-parse: exit 0 on --help
From: Junio C Hamano @ 2026-07-03 20:38 UTC (permalink / raw)
  To: Jeff King; +Cc: brian m. carlson, git
In-Reply-To: <20260702084519.GB481298@coredump.intra.peff.net>

Jeff King <peff@peff.net> writes:

> The other issue I raised in the earlier round is that a script like:
>
>   cat >git-foo <<\EOF
>   #!/bin/sh
>   git log --my-options "$@" >output || exit 1
>   do_something <output
>   EOF
>
> when invoked as "git foo --help" will now call do_something with
> nonsense input, rather than exiting from the "error" returned by
> git-log. This only affects hacky little scripts like this that are not
> otherwise parsing their own options, but sometimes those are the most
> common. ;)

Yeah, I agree that the above is a much more likely breakage scenario
than I imagined.

> I'm not convinced there will be much fallout, but it is possible.

True.

^ permalink raw reply

* Re: What's cooking in git.git (Jul 2026, #01)
From: Junio C Hamano @ 2026-07-03 20:39 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git
In-Reply-To: <akY_oce1EcsgsN9b@pks.im>

Patrick Steinhardt <ps@pks.im> writes:

> On Wed, Jul 01, 2026 at 04:40:16PM -0700, Junio C Hamano wrote:
>> * ps/odb-drop-whence (2026-06-24) 7 commits
> I'll send a small reroll to rename `sourcep` to `source_infop` based on
> Justin's feedback.
>
>> * ps/odb-generalize-prepare (2026-06-22) 3 commits
> This one should be ready.
>
>> * ps/refs-writing-subcommands (2026-06-30) 5 commits
> Likewise.

Thanks.

^ permalink raw reply

* Re: Unexpected recursion in 'git rm'
From: Junio C Hamano @ 2026-07-03 20:41 UTC (permalink / raw)
  To: Mikael Magnusson
  Cc: Евгений Плискин,
	git
In-Reply-To: <CAHYJk3RXY5-YgcYWY2y8vOcHG5Frf91ehNiZRr66sJJH5F=qLQ@mail.gmail.com>

Mikael Magnusson <mikachu@gmail.com> writes:

> ..., though you might overall get less surprised if you
> set the failglob option in bash.

Excellent suggestion.

^ 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