Git development
 help / color / mirror / Atom feed
* [PATCH v3 07/12] builtin/show-ref: stop using global vars for `show_one()`
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw)
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys
In-Reply-To: <cover.1698739941.git.ps@pks.im>

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

The `show_one()` function implicitly receives a bunch of options which
are tracked via global variables. This makes it hard to see which
subcommands of git-show-ref(1) actually make use of these options.

Introduce a `show_one_options` structure that gets passed down to this
function. This allows us to get rid of more global state and makes it
more explicit which subcommands use those options.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 62 ++++++++++++++++++++++++++++++----------------
 1 file changed, 40 insertions(+), 22 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index d0de69e29dd..fb0960ac507 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -18,10 +18,17 @@ static const char * const show_ref_usage[] = {
 	NULL
 };
 
-static int deref_tags, show_head, tags_only, heads_only, verify,
-	   quiet, hash_only, abbrev;
+static int show_head, tags_only, heads_only, verify;
 
-static void show_one(const char *refname, const struct object_id *oid)
+struct show_one_options {
+	int quiet;
+	int hash_only;
+	int abbrev;
+	int deref_tags;
+};
+
+static void show_one(const struct show_one_options *opts,
+		     const char *refname, const struct object_id *oid)
 {
 	const char *hex;
 	struct object_id peeled;
@@ -30,25 +37,26 @@ static void show_one(const char *refname, const struct object_id *oid)
 		die("git show-ref: bad ref %s (%s)", refname,
 		    oid_to_hex(oid));
 
-	if (quiet)
+	if (opts->quiet)
 		return;
 
-	hex = repo_find_unique_abbrev(the_repository, oid, abbrev);
-	if (hash_only)
+	hex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev);
+	if (opts->hash_only)
 		printf("%s\n", hex);
 	else
 		printf("%s %s\n", hex, refname);
 
-	if (!deref_tags)
+	if (!opts->deref_tags)
 		return;
 
 	if (!peel_iterated_oid(oid, &peeled)) {
-		hex = repo_find_unique_abbrev(the_repository, &peeled, abbrev);
+		hex = repo_find_unique_abbrev(the_repository, &peeled, opts->abbrev);
 		printf("%s %s^{}\n", hex, refname);
 	}
 }
 
 struct show_ref_data {
+	const struct show_one_options *show_one_opts;
 	const char **patterns;
 	int found_match;
 };
@@ -81,7 +89,7 @@ static int show_ref(const char *refname, const struct object_id *oid,
 match:
 	data->found_match++;
 
-	show_one(refname, oid);
+	show_one(data->show_one_opts, refname, oid);
 
 	return 0;
 }
@@ -153,7 +161,8 @@ static int cmd_show_ref__exclude_existing(const struct exclude_existing_options
 	return 0;
 }
 
-static int cmd_show_ref__verify(const char **refs)
+static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,
+				const char **refs)
 {
 	if (!refs || !*refs)
 		die("--verify requires a reference");
@@ -163,9 +172,9 @@ static int cmd_show_ref__verify(const char **refs)
 
 		if ((starts_with(*refs, "refs/") || !strcmp(*refs, "HEAD")) &&
 		    !read_ref(*refs, &oid)) {
-			show_one(*refs, &oid);
+			show_one(show_one_opts, *refs, &oid);
 		}
-		else if (!quiet)
+		else if (!show_one_opts->quiet)
 			die("'%s' - not a valid ref", *refs);
 		else
 			return 1;
@@ -175,9 +184,12 @@ static int cmd_show_ref__verify(const char **refs)
 	return 0;
 }
 
-static int cmd_show_ref__patterns(const char **patterns)
+static int cmd_show_ref__patterns(const struct show_one_options *show_one_opts,
+				  const char **patterns)
 {
-	struct show_ref_data show_ref_data = {0};
+	struct show_ref_data show_ref_data = {
+		.show_one_opts = show_one_opts,
+	};
 
 	if (patterns && *patterns)
 		show_ref_data.patterns = patterns;
@@ -200,11 +212,16 @@ static int cmd_show_ref__patterns(const char **patterns)
 
 static int hash_callback(const struct option *opt, const char *arg, int unset)
 {
-	hash_only = 1;
+	struct show_one_options *opts = opt->value;
+	struct option abbrev_opt = *opt;
+
+	opts->hash_only = 1;
 	/* Use full length SHA1 if no argument */
 	if (!arg)
 		return 0;
-	return parse_opt_abbrev_cb(opt, arg, unset);
+
+	abbrev_opt.value = &opts->abbrev;
+	return parse_opt_abbrev_cb(&abbrev_opt, arg, unset);
 }
 
 static int exclude_existing_callback(const struct option *opt, const char *arg,
@@ -220,6 +237,7 @@ static int exclude_existing_callback(const struct option *opt, const char *arg,
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
 	struct exclude_existing_options exclude_existing_opts = {0};
+	struct show_one_options show_one_opts = {0};
 	const struct option show_ref_options[] = {
 		OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
 		OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
@@ -229,13 +247,13 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 				N_("show the HEAD reference, even if it would be filtered out")),
 		OPT_BOOL(0, "head", &show_head,
 		  N_("show the HEAD reference, even if it would be filtered out")),
-		OPT_BOOL('d', "dereference", &deref_tags,
+		OPT_BOOL('d', "dereference", &show_one_opts.deref_tags,
 			    N_("dereference tags into object IDs")),
-		OPT_CALLBACK_F('s', "hash", &abbrev, N_("n"),
+		OPT_CALLBACK_F('s', "hash", &show_one_opts, N_("n"),
 			       N_("only show SHA1 hash using <n> digits"),
 			       PARSE_OPT_OPTARG, &hash_callback),
-		OPT__ABBREV(&abbrev),
-		OPT__QUIET(&quiet,
+		OPT__ABBREV(&show_one_opts.abbrev),
+		OPT__QUIET(&show_one_opts.quiet,
 			   N_("do not print results to stdout (useful with --verify)")),
 		OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_opts,
 			       N_("pattern"), N_("show refs from stdin that aren't in local repository"),
@@ -251,7 +269,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	if (exclude_existing_opts.enabled)
 		return cmd_show_ref__exclude_existing(&exclude_existing_opts);
 	else if (verify)
-		return cmd_show_ref__verify(argv);
+		return cmd_show_ref__verify(&show_one_opts, argv);
 	else
-		return cmd_show_ref__patterns(argv);
+		return cmd_show_ref__patterns(&show_one_opts, argv);
 }
-- 
2.42.0


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

^ permalink raw reply related

* [PATCH v3 06/12] builtin/show-ref: stop using global variable to count matches
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw)
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys
In-Reply-To: <cover.1698739941.git.ps@pks.im>

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

When passing patterns to git-show-ref(1) we're checking whether any
reference matches -- if none do, we indicate this condition via an
unsuccessful exit code.

We're using a global variable to count these matches, which is required
because the counter is getting incremented in a callback function. But
now that we have the `struct show_ref_data` in place, we can get rid of
the global variable and put the counter in there instead.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 5aa6016376a..d0de69e29dd 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -18,7 +18,7 @@ static const char * const show_ref_usage[] = {
 	NULL
 };
 
-static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
+static int deref_tags, show_head, tags_only, heads_only, verify,
 	   quiet, hash_only, abbrev;
 
 static void show_one(const char *refname, const struct object_id *oid)
@@ -50,6 +50,7 @@ static void show_one(const char *refname, const struct object_id *oid)
 
 struct show_ref_data {
 	const char **patterns;
+	int found_match;
 };
 
 static int show_ref(const char *refname, const struct object_id *oid,
@@ -78,7 +79,7 @@ static int show_ref(const char *refname, const struct object_id *oid,
 	}
 
 match:
-	found_match++;
+	data->found_match++;
 
 	show_one(refname, oid);
 
@@ -191,7 +192,7 @@ static int cmd_show_ref__patterns(const char **patterns)
 	} else {
 		for_each_ref(show_ref, &show_ref_data);
 	}
-	if (!found_match)
+	if (!show_ref_data.found_match)
 		return 1;
 
 	return 0;
-- 
2.42.0


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

^ permalink raw reply related

* [PATCH v3 05/12] builtin/show-ref: refactor `--exclude-existing` options
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw)
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys
In-Reply-To: <cover.1698739941.git.ps@pks.im>

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

It's not immediately obvious options which options are applicable to
what subcommand in git-show-ref(1) because all options exist as global
state. This can easily cause confusion for the reader.

Refactor options for the `--exclude-existing` subcommand to be contained
in a separate structure. This structure is stored on the stack and
passed down as required. Consequently, it clearly delimits the scope of
those options and requires the reader to worry less about global state.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 78 ++++++++++++++++++++++++++--------------------
 1 file changed, 44 insertions(+), 34 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index f95418d3d16..5aa6016376a 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -19,8 +19,7 @@ static const char * const show_ref_usage[] = {
 };
 
 static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
-	   quiet, hash_only, abbrev, exclude_arg;
-static const char *exclude_existing_arg;
+	   quiet, hash_only, abbrev;
 
 static void show_one(const char *refname, const struct object_id *oid)
 {
@@ -95,6 +94,15 @@ static int add_existing(const char *refname,
 	return 0;
 }
 
+struct exclude_existing_options {
+	/*
+	 * We need an explicit `enabled` field because it is perfectly valid
+	 * for `pattern` to be `NULL` even if `--exclude-existing` was given.
+	 */
+	int enabled;
+	const char *pattern;
+};
+
 /*
  * read "^(?:<anything>\s)?<refname>(?:\^\{\})?$" from the standard input,
  * and
@@ -104,11 +112,11 @@ static int add_existing(const char *refname,
  * (4) ignore if refname is a ref that exists in the local repository;
  * (5) otherwise output the line.
  */
-static int cmd_show_ref__exclude_existing(const char *match)
+static int cmd_show_ref__exclude_existing(const struct exclude_existing_options *opts)
 {
 	struct string_list existing_refs = STRING_LIST_INIT_DUP;
 	char buf[1024];
-	int matchlen = match ? strlen(match) : 0;
+	int patternlen = opts->pattern ? strlen(opts->pattern) : 0;
 
 	for_each_ref(add_existing, &existing_refs);
 	while (fgets(buf, sizeof(buf), stdin)) {
@@ -124,11 +132,11 @@ static int cmd_show_ref__exclude_existing(const char *match)
 		for (ref = buf + len; buf < ref; ref--)
 			if (isspace(ref[-1]))
 				break;
-		if (match) {
+		if (opts->pattern) {
 			int reflen = buf + len - ref;
-			if (reflen < matchlen)
+			if (reflen < patternlen)
 				continue;
-			if (strncmp(ref, match, matchlen))
+			if (strncmp(ref, opts->pattern, patternlen))
 				continue;
 		}
 		if (check_refname_format(ref, 0)) {
@@ -201,44 +209,46 @@ static int hash_callback(const struct option *opt, const char *arg, int unset)
 static int exclude_existing_callback(const struct option *opt, const char *arg,
 				     int unset)
 {
+	struct exclude_existing_options *opts = opt->value;
 	BUG_ON_OPT_NEG(unset);
-	exclude_arg = 1;
-	*(const char **)opt->value = arg;
+	opts->enabled = 1;
+	opts->pattern = arg;
 	return 0;
 }
 
-static const struct option show_ref_options[] = {
-	OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
-	OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
-	OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
-		    "requires exact ref path")),
-	OPT_HIDDEN_BOOL('h', NULL, &show_head,
-			N_("show the HEAD reference, even if it would be filtered out")),
-	OPT_BOOL(0, "head", &show_head,
-	  N_("show the HEAD reference, even if it would be filtered out")),
-	OPT_BOOL('d', "dereference", &deref_tags,
-		    N_("dereference tags into object IDs")),
-	OPT_CALLBACK_F('s', "hash", &abbrev, N_("n"),
-		       N_("only show SHA1 hash using <n> digits"),
-		       PARSE_OPT_OPTARG, &hash_callback),
-	OPT__ABBREV(&abbrev),
-	OPT__QUIET(&quiet,
-		   N_("do not print results to stdout (useful with --verify)")),
-	OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_arg,
-		       N_("pattern"), N_("show refs from stdin that aren't in local repository"),
-		       PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback),
-	OPT_END()
-};
-
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
+	struct exclude_existing_options exclude_existing_opts = {0};
+	const struct option show_ref_options[] = {
+		OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
+		OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
+		OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
+			    "requires exact ref path")),
+		OPT_HIDDEN_BOOL('h', NULL, &show_head,
+				N_("show the HEAD reference, even if it would be filtered out")),
+		OPT_BOOL(0, "head", &show_head,
+		  N_("show the HEAD reference, even if it would be filtered out")),
+		OPT_BOOL('d', "dereference", &deref_tags,
+			    N_("dereference tags into object IDs")),
+		OPT_CALLBACK_F('s', "hash", &abbrev, N_("n"),
+			       N_("only show SHA1 hash using <n> digits"),
+			       PARSE_OPT_OPTARG, &hash_callback),
+		OPT__ABBREV(&abbrev),
+		OPT__QUIET(&quiet,
+			   N_("do not print results to stdout (useful with --verify)")),
+		OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_opts,
+			       N_("pattern"), N_("show refs from stdin that aren't in local repository"),
+			       PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback),
+		OPT_END()
+	};
+
 	git_config(git_default_config, NULL);
 
 	argc = parse_options(argc, argv, prefix, show_ref_options,
 			     show_ref_usage, 0);
 
-	if (exclude_arg)
-		return cmd_show_ref__exclude_existing(exclude_existing_arg);
+	if (exclude_existing_opts.enabled)
+		return cmd_show_ref__exclude_existing(&exclude_existing_opts);
 	else if (verify)
 		return cmd_show_ref__verify(argv);
 	else
-- 
2.42.0


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

^ permalink raw reply related

* [PATCH v3 04/12] builtin/show-ref: fix dead code when passing patterns
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw)
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys
In-Reply-To: <cover.1698739941.git.ps@pks.im>

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

When passing patterns to `git show-ref` we have some code that will
cause us to die if `verify && !quiet` is true. But because `verify`
indicates a different subcommand of git-show-ref(1) that causes us to
execute `cmd_show_ref__verify()` and not `cmd_show_ref__patterns()`, the
condition cannot ever be true.

Let's remove this dead code.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index e55c38af478..f95418d3d16 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -183,11 +183,8 @@ static int cmd_show_ref__patterns(const char **patterns)
 	} else {
 		for_each_ref(show_ref, &show_ref_data);
 	}
-	if (!found_match) {
-		if (verify && !quiet)
-			die("No match");
+	if (!found_match)
 		return 1;
-	}
 
 	return 0;
 }
-- 
2.42.0


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

^ permalink raw reply related

* [PATCH v3 03/12] builtin/show-ref: fix leaking string buffer
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw)
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys
In-Reply-To: <cover.1698739941.git.ps@pks.im>

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

Fix a leaking string buffer in `git show-ref --exclude-existing`. While
the buffer is technically not leaking because its variable is declared
as static, there is no inherent reason why it should be.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index cad5b8b5066..e55c38af478 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -106,7 +106,7 @@ static int add_existing(const char *refname,
  */
 static int cmd_show_ref__exclude_existing(const char *match)
 {
-	static struct string_list existing_refs = STRING_LIST_INIT_DUP;
+	struct string_list existing_refs = STRING_LIST_INIT_DUP;
 	char buf[1024];
 	int matchlen = match ? strlen(match) : 0;
 
@@ -139,6 +139,8 @@ static int cmd_show_ref__exclude_existing(const char *match)
 			printf("%s\n", buf);
 		}
 	}
+
+	string_list_clear(&existing_refs, 0);
 	return 0;
 }
 
-- 
2.42.0


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

^ permalink raw reply related

* [PATCH v3 02/12] builtin/show-ref: split up different subcommands
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw)
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys
In-Reply-To: <cover.1698739941.git.ps@pks.im>

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

While not immediately obvious, git-show-ref(1) actually implements three
different subcommands:

    - `git show-ref <patterns>` can be used to list references that
      match a specific pattern.

    - `git show-ref --verify <refs>` can be used to list references.
      These are _not_ patterns.

    - `git show-ref --exclude-existing` can be used as a filter that
      reads references from standard input, performing some conversions
      on each of them.

Let's make this more explicit in the code by splitting up the three
subcommands into separate functions. This also allows us to address the
confusingly named `patterns` variable, which may hold either patterns or
reference names.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 101 ++++++++++++++++++++++++---------------------
 1 file changed, 54 insertions(+), 47 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 7efab14b96c..cad5b8b5066 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -104,7 +104,7 @@ static int add_existing(const char *refname,
  * (4) ignore if refname is a ref that exists in the local repository;
  * (5) otherwise output the line.
  */
-static int exclude_existing(const char *match)
+static int cmd_show_ref__exclude_existing(const char *match)
 {
 	static struct string_list existing_refs = STRING_LIST_INIT_DUP;
 	char buf[1024];
@@ -142,6 +142,54 @@ static int exclude_existing(const char *match)
 	return 0;
 }
 
+static int cmd_show_ref__verify(const char **refs)
+{
+	if (!refs || !*refs)
+		die("--verify requires a reference");
+
+	while (*refs) {
+		struct object_id oid;
+
+		if ((starts_with(*refs, "refs/") || !strcmp(*refs, "HEAD")) &&
+		    !read_ref(*refs, &oid)) {
+			show_one(*refs, &oid);
+		}
+		else if (!quiet)
+			die("'%s' - not a valid ref", *refs);
+		else
+			return 1;
+		refs++;
+	}
+
+	return 0;
+}
+
+static int cmd_show_ref__patterns(const char **patterns)
+{
+	struct show_ref_data show_ref_data = {0};
+
+	if (patterns && *patterns)
+		show_ref_data.patterns = patterns;
+
+	if (show_head)
+		head_ref(show_ref, &show_ref_data);
+	if (heads_only || tags_only) {
+		if (heads_only)
+			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
+		if (tags_only)
+			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
+	} else {
+		for_each_ref(show_ref, &show_ref_data);
+	}
+	if (!found_match) {
+		if (verify && !quiet)
+			die("No match");
+		return 1;
+	}
+
+	return 0;
+}
+
 static int hash_callback(const struct option *opt, const char *arg, int unset)
 {
 	hash_only = 1;
@@ -185,56 +233,15 @@ static const struct option show_ref_options[] = {
 
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
-	struct show_ref_data show_ref_data = {0};
-	const char **patterns;
-
 	git_config(git_default_config, NULL);
 
 	argc = parse_options(argc, argv, prefix, show_ref_options,
 			     show_ref_usage, 0);
 
 	if (exclude_arg)
-		return exclude_existing(exclude_existing_arg);
-
-	patterns = argv;
-	if (!*patterns)
-		patterns = NULL;
-
-	if (verify) {
-		if (!patterns)
-			die("--verify requires a reference");
-		while (*patterns) {
-			struct object_id oid;
-
-			if ((starts_with(*patterns, "refs/") || !strcmp(*patterns, "HEAD")) &&
-			    !read_ref(*patterns, &oid)) {
-				show_one(*patterns, &oid);
-			}
-			else if (!quiet)
-				die("'%s' - not a valid ref", *patterns);
-			else
-				return 1;
-			patterns++;
-		}
-		return 0;
-	}
-
-	show_ref_data.patterns = patterns;
-
-	if (show_head)
-		head_ref(show_ref, &show_ref_data);
-	if (heads_only || tags_only) {
-		if (heads_only)
-			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
-		if (tags_only)
-			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
-	} else {
-		for_each_ref(show_ref, &show_ref_data);
-	}
-	if (!found_match) {
-		if (verify && !quiet)
-			die("No match");
-		return 1;
-	}
-	return 0;
+		return cmd_show_ref__exclude_existing(exclude_existing_arg);
+	else if (verify)
+		return cmd_show_ref__verify(argv);
+	else
+		return cmd_show_ref__patterns(argv);
 }
-- 
2.42.0


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

^ permalink raw reply related

* [PATCH v3 01/12] builtin/show-ref: convert pattern to a local variable
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw)
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys
In-Reply-To: <cover.1698739941.git.ps@pks.im>

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

The `pattern` variable is a global variable that tracks either the
reference names (not patterns!) for the `--verify` mode or the patterns
for the non-verify mode. This is a bit confusing due to the slightly
different meanings.

Convert the variable to be local. While this does not yet fix the double
meaning of the variable, this change allows us to address it in a
subsequent patch more easily by explicitly splitting up the different
subcommands of git-show-ref(1).

Note that we introduce a `struct show_ref_data` to pass the patterns to
`show_ref()`. While this is overengineered now, we will extend this
structure in a subsequent patch.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 46 ++++++++++++++++++++++++++++------------------
 1 file changed, 28 insertions(+), 18 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 5110814f796..7efab14b96c 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -20,7 +20,6 @@ static const char * const show_ref_usage[] = {
 
 static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
 	   quiet, hash_only, abbrev, exclude_arg;
-static const char **pattern;
 static const char *exclude_existing_arg;
 
 static void show_one(const char *refname, const struct object_id *oid)
@@ -50,15 +49,21 @@ static void show_one(const char *refname, const struct object_id *oid)
 	}
 }
 
+struct show_ref_data {
+	const char **patterns;
+};
+
 static int show_ref(const char *refname, const struct object_id *oid,
-		    int flag UNUSED, void *cbdata UNUSED)
+		    int flag UNUSED, void *cbdata)
 {
+	struct show_ref_data *data = cbdata;
+
 	if (show_head && !strcmp(refname, "HEAD"))
 		goto match;
 
-	if (pattern) {
+	if (data->patterns) {
 		int reflen = strlen(refname);
-		const char **p = pattern, *m;
+		const char **p = data->patterns, *m;
 		while ((m = *p++) != NULL) {
 			int len = strlen(m);
 			if (len > reflen)
@@ -180,6 +185,9 @@ static const struct option show_ref_options[] = {
 
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
+	struct show_ref_data show_ref_data = {0};
+	const char **patterns;
+
 	git_config(git_default_config, NULL);
 
 	argc = parse_options(argc, argv, prefix, show_ref_options,
@@ -188,38 +196,40 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	if (exclude_arg)
 		return exclude_existing(exclude_existing_arg);
 
-	pattern = argv;
-	if (!*pattern)
-		pattern = NULL;
+	patterns = argv;
+	if (!*patterns)
+		patterns = NULL;
 
 	if (verify) {
-		if (!pattern)
+		if (!patterns)
 			die("--verify requires a reference");
-		while (*pattern) {
+		while (*patterns) {
 			struct object_id oid;
 
-			if ((starts_with(*pattern, "refs/") || !strcmp(*pattern, "HEAD")) &&
-			    !read_ref(*pattern, &oid)) {
-				show_one(*pattern, &oid);
+			if ((starts_with(*patterns, "refs/") || !strcmp(*patterns, "HEAD")) &&
+			    !read_ref(*patterns, &oid)) {
+				show_one(*patterns, &oid);
 			}
 			else if (!quiet)
-				die("'%s' - not a valid ref", *pattern);
+				die("'%s' - not a valid ref", *patterns);
 			else
 				return 1;
-			pattern++;
+			patterns++;
 		}
 		return 0;
 	}
 
+	show_ref_data.patterns = patterns;
+
 	if (show_head)
-		head_ref(show_ref, NULL);
+		head_ref(show_ref, &show_ref_data);
 	if (heads_only || tags_only) {
 		if (heads_only)
-			for_each_fullref_in("refs/heads/", show_ref, NULL);
+			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
 		if (tags_only)
-			for_each_fullref_in("refs/tags/", show_ref, NULL);
+			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
 	} else {
-		for_each_ref(show_ref, NULL);
+		for_each_ref(show_ref, &show_ref_data);
 	}
 	if (!found_match) {
 		if (verify && !quiet)
-- 
2.42.0


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

^ permalink raw reply related

* [PATCH v3 00/12] builtin/show-ref: introduce mode to check for ref existence
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw)
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys
In-Reply-To: <cover.1698152926.git.ps@pks.im>

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

Hi,

this is the third version of my patch series that introduces a new `git
show-ref --exists` mode to check for reference existence.

Changes compared to v2:

    - Patch 5: Document why we need `exclude_existing_options.enabled`,
      which isn't exactly obvious.

    - Patch 6: Fix a grammar issue in the commit message.

    - Patch 9: Switch to `test_cmp` instead of grep(1).

Thanks!

Patrick

Patrick Steinhardt (12):
  builtin/show-ref: convert pattern to a local variable
  builtin/show-ref: split up different subcommands
  builtin/show-ref: fix leaking string buffer
  builtin/show-ref: fix dead code when passing patterns
  builtin/show-ref: refactor `--exclude-existing` options
  builtin/show-ref: stop using global variable to count matches
  builtin/show-ref: stop using global vars for `show_one()`
  builtin/show-ref: refactor options for patterns subcommand
  builtin/show-ref: ensure mutual exclusiveness of subcommands
  builtin/show-ref: explicitly spell out different modes in synopsis
  builtin/show-ref: add new mode to check for reference existence
  t: use git-show-ref(1) to check for ref existence

 Documentation/git-show-ref.txt |  20 ++-
 builtin/show-ref.c             | 284 ++++++++++++++++++++++-----------
 t/t1403-show-ref.sh            |  70 ++++++++
 t/t1430-bad-ref-name.sh        |  27 ++--
 t/t3200-branch.sh              |  33 ++--
 t/t5521-pull-options.sh        |   4 +-
 t/t5605-clone-local.sh         |   2 +-
 t/test-lib-functions.sh        |  55 +++++++
 8 files changed, 373 insertions(+), 122 deletions(-)

Range-diff against v2:
 1:  78163accbd2 =  1:  9570ad63924 builtin/show-ref: convert pattern to a local variable
 2:  9a234622d99 =  2:  773c6119750 builtin/show-ref: split up different subcommands
 3:  bb0d656a0b4 =  3:  b6f4c0325bf builtin/show-ref: fix leaking string buffer
 4:  87afcee830c =  4:  4605c6f0ac9 builtin/show-ref: fix dead code when passing patterns
 5:  bed2a8a0769 !  5:  b47440089b6 builtin/show-ref: refactor `--exclude-existing` options
    @@ builtin/show-ref.c: static int add_existing(const char *refname,
      }
      
     +struct exclude_existing_options {
    ++	/*
    ++	 * We need an explicit `enabled` field because it is perfectly valid
    ++	 * for `pattern` to be `NULL` even if `--exclude-existing` was given.
    ++	 */
     +	int enabled;
     +	const char *pattern;
     +};
 6:  d52a5e8ced2 !  6:  6172888e465 builtin/show-ref: stop using global variable to count matches
    @@ Commit message
         builtin/show-ref: stop using global variable to count matches
     
         When passing patterns to git-show-ref(1) we're checking whether any
    -    reference matches -- if none does, we indicate this condition via an
    +    reference matches -- if none do, we indicate this condition via an
         unsuccessful exit code.
     
         We're using a global variable to count these matches, which is required
 7:  63f1dadf4c2 =  7:  bc528db7667 builtin/show-ref: stop using global vars for `show_one()`
 8:  88dfeaa4871 =  8:  e3882c07dfc builtin/show-ref: refactor options for patterns subcommand
 9:  5ba566723e8 !  9:  a095decd778 builtin/show-ref: ensure mutual exclusiveness of subcommands
    @@ t/t1403-show-ref.sh: test_expect_success 'show-ref --verify with dangling ref' '
      '
      
     +test_expect_success 'show-ref sub-modes are mutually exclusive' '
    ++	cat >expect <<-EOF &&
    ++	fatal: only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given
    ++	EOF
    ++
     +	test_must_fail git show-ref --verify --exclude-existing 2>err &&
    -+	grep "only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given" err
    ++	test_cmp expect err
     +'
     +
      test_done
10:  b78ccc5f692 = 10:  087384fd2fd builtin/show-ref: explicitly spell out different modes in synopsis
11:  327942b1162 ! 11:  ca5187bb18a builtin/show-ref: add new mode to check for reference existence
    @@ builtin/show-ref.c: int cmd_show_ref(int argc, const char **argv, const char *pr
     
      ## t/t1403-show-ref.sh ##
     @@ t/t1403-show-ref.sh: test_expect_success 'show-ref --verify with dangling ref' '
    - '
      
      test_expect_success 'show-ref sub-modes are mutually exclusive' '
    -+	cat >expect <<-EOF &&
    + 	cat >expect <<-EOF &&
    +-	fatal: only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given
     +	fatal: only one of ${SQ}--exclude-existing${SQ}, ${SQ}--verify${SQ} or ${SQ}--exists${SQ} can be given
    -+	EOF
    -+
    + 	EOF
    + 
      	test_must_fail git show-ref --verify --exclude-existing 2>err &&
    --	grep "only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given" err
     +	test_cmp expect err &&
     +
     +	test_must_fail git show-ref --verify --exists 2>err &&
    @@ t/t1403-show-ref.sh: test_expect_success 'show-ref --verify with dangling ref' '
     +	error: failed to look up reference: Is a directory
     +	EOF
     +	test_expect_code 1 git show-ref --exists refs/heads 2>err &&
    -+	test_cmp expect err
    + 	test_cmp expect err
      '
      
    - test_done
12:  226731c5f18 = 12:  ea9919fe899 t: use git-show-ref(1) to check for ref existence

base-commit: a9ecda2788e229afc9b611acaa26d0d9d4da53ed
-- 
2.42.0


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

^ permalink raw reply

* Re: [PATCH v2 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands
From: Patrick Steinhardt @ 2023-10-31  8:10 UTC (permalink / raw)
  To: Taylor Blau; +Cc: git, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys
In-Reply-To: <ZUAElIb7mjoBBRcn@nand.local>

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

On Mon, Oct 30, 2023 at 03:31:32PM -0400, Taylor Blau wrote:
> On Thu, Oct 26, 2023 at 11:56:57AM +0200, Patrick Steinhardt wrote:
> > The git-show-ref(1) command has three different modes, of which one is
> > implicit and the other two can be chosen explicitly by passing a flag.
> > But while these modes are standalone and cause us to execute completely
> > separate code paths, we gladly accept the case where a user asks for
> > both `--exclude-existing` and `--verify` at the same time even though it
> > is not obvious what will happen. Spoiler: we ignore `--verify` and
> > execute the `--exclude-existing` mode.
> >
> > Let's explicitly detect this invalid usage and die in case both modes
> > were requested.
> >
> > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > ---
> >  builtin/show-ref.c  | 4 ++++
> >  t/t1403-show-ref.sh | 5 +++++
> >  2 files changed, 9 insertions(+)
> >
> > diff --git a/builtin/show-ref.c b/builtin/show-ref.c
> > index 87bc45d2d13..1768aef77b3 100644
> > --- a/builtin/show-ref.c
> > +++ b/builtin/show-ref.c
> > @@ -271,6 +271,10 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
> >  	argc = parse_options(argc, argv, prefix, show_ref_options,
> >  			     show_ref_usage, 0);
> >
> > +	if ((!!exclude_existing_opts.enabled + !!verify) > 1)
> > +		die(_("only one of '%s' or '%s' can be given"),
> > +		    "--exclude-existing", "--verify");
> > +
> 
> This is technically correct, but I was surprised to see it written this
> way instead of
> 
>     if (exclude_existing_opts.enabled && verify)
>         die(...);
> 
> I don't think it's a big deal either way, I was just curious why you
> chose one over the other.

Here it doesn't make a lot of sense yet, agreed. But once we add
`exists` as a third mutually-exclusive option it does because of
combinatorial explosion.

> > +test_expect_success 'show-ref sub-modes are mutually exclusive' '
> > +	test_must_fail git show-ref --verify --exclude-existing 2>err &&
> > +	grep "only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given" err
> > +'
> 
> grepping is fine here, but since you have the exact error message, it
> may be worth switching to test_cmp.

Good point. Doubly so because I switch to `test_cmp` in a later patch.
Will change.

Patrick

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

^ permalink raw reply

* Re: [PATCH v2 05/12] builtin/show-ref: refactor `--exclude-existing` options
From: Patrick Steinhardt @ 2023-10-31  8:10 UTC (permalink / raw)
  To: Taylor Blau; +Cc: git, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys
In-Reply-To: <ZT/8GbzAmBq0aRIK@nand.local>

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

On Mon, Oct 30, 2023 at 02:55:21PM -0400, Taylor Blau wrote:
> On Thu, Oct 26, 2023 at 11:56:37AM +0200, Patrick Steinhardt wrote:
> > @@ -95,6 +94,11 @@ static int add_existing(const char *refname,
> >  	return 0;
> >  }
> >
> > +struct exclude_existing_options {
> > +	int enabled;
> > +	const char *pattern;
> > +};
> > +
> 
> Thinking on my earlier suggestion more, I wondered if using the
> OPT_SUBCOMMAND() function might make things easier to organize and
> eliminate the need for things like .enabled or having to define structs
> for each of the sub-commands.
> 
> But I don't think that this is (easily) possible to do, since
> `--exclude-existing` is behind a command-line option, not a separate
> mode (e.g. "commit-graph verify", not "commit-graph --verify"). So I
> think you *could* make it work with some combination of OPT_SUBCOMMAND
> and callbacks to set the function pointer yourself when given the
> `--exclude-existing` option. But I think that's sufficiently gross as to
> not be worth it.

Yeah, agreed. Honestly, while working on this series I had the dream of
just discarding git-show-ref(1) in favor of a new command with proper
subcommands as we tend to use them nowadays:

    - `git references list <patterns>...` replaces `git show-ref
      <pattern>`.

    - `git references filter <pattern>` replaces `git show-ref
      --exclude-existing` and filters references from stdin.

    - `git references exists <ref>` checks whether a reference exists or
      not and replaces `git show-ref --exists`.

This would make for a much more enjoyable UX. It'd also be a more
natural home for potential future additions:

    - `git references show <ref>` allows you to show the contents of the
      reference without resolving it, regardless of whether it's a
      direct or a symbolic reference.

    - `git references count <patterns>...` allows you to count refs
      patching the pattern.

I shied away though because it would be a much more controversial topic
that would potentially result in lots of bikeshedding. Now if everyone
was enthusiastic about this idea I'd still be happy to do it, even
though it derails the topic even further from its original intent to
just fix a bunch of tests. But unless that happens, I'll continue to
stick with the mediocre UI we have in git-show-ref(1).

Patrick

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

^ permalink raw reply

* Re: [PATCH v2 05/12] builtin/show-ref: refactor `--exclude-existing` options
From: Patrick Steinhardt @ 2023-10-31  8:10 UTC (permalink / raw)
  To: Taylor Blau; +Cc: git, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys
In-Reply-To: <ZT/32jI62GQKPlcp@nand.local>

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

On Mon, Oct 30, 2023 at 02:37:14PM -0400, Taylor Blau wrote:
> On Thu, Oct 26, 2023 at 11:56:37AM +0200, Patrick Steinhardt wrote:
> > It's not immediately obvious options which options are applicable to
> > what subcommand in git-show-ref(1) because all options exist as global
> > state. This can easily cause confusion for the reader.
> >
> > Refactor options for the `--exclude-existing` subcommand to be contained
> > in a separate structure. This structure is stored on the stack and
> > passed down as required. Consequently, it clearly delimits the scope of
> > those options and requires the reader to worry less about global state.
> >
> > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> 
> All makes sense, but...
> 
> > @@ -19,8 +19,7 @@ static const char * const show_ref_usage[] = {
> >  };
> >
> >  static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
> > -	   quiet, hash_only, abbrev, exclude_arg;
> > -static const char *exclude_existing_arg;
> > +	   quiet, hash_only, abbrev;
> >
> >  static void show_one(const char *refname, const struct object_id *oid)
> >  {
> > @@ -95,6 +94,11 @@ static int add_existing(const char *refname,
> >  	return 0;
> >  }
> >
> > +struct exclude_existing_options {
> > +	int enabled;
> 
> ...do we need an .enabled here? I think checking whether or not .pattern
> is NULL is sufficient, but perhaps there is another use of .enabled
> later on in the series...

This is the second time that this question comes up, which is likely not
all that surprising. Quoting my first reply:

On Wed, Oct 25, 2023 at 01:50:44PM +0200, Patrick Steinhardt wrote:
> Yeah, we do. It's perfectly valid to pass `--exclude-existing` without
> the optional pattern argument. We still want to use this mode in that
> case, but don't populate the pattern.
> 
> An alternative would be to assign something like a sentinel value in
> here. But I'd think that it's clearer to instead have an explicit
> separate field for this.

Anyway, the fact that this question comes up again indicates that I need
to comment this better.

Patrick

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

^ permalink raw reply

* Re: [PATCH 0/5] ci: add GitLab CI definition
From: Patrick Steinhardt @ 2023-10-31  7:46 UTC (permalink / raw)
  To: Taylor Blau; +Cc: git
In-Reply-To: <ZT/P5Bl9lD9V6ID9@nand.local>

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

On Mon, Oct 30, 2023 at 11:46:44AM -0400, Taylor Blau wrote:
> On Thu, Oct 26, 2023 at 09:59:59AM +0200, Patrick Steinhardt wrote:
> > And this is exactly what this patch series does: it adds GitLab-specific
> > knowledge to our CI scripts and adds a CI definition that builds on top
> > of those scripts. This is rather straight forward, as the scripts
> > already know to discern Azure Pipelines and GitHub Actions, and adding
> > a third item to this list feels quite natural. And by building on top of
> > the preexisting infra, the actual ".gitlab-ci.yml" is really quite
> > small.
> >
> > I acknowledge that the Git project may not be willing to fully support
> > GitLab CI, and that's fine with me. If we want to further stress that
> > point then I'd also be perfectly happy to move the definitions into the
> > "contrib/" directory -- it would still be a huge win for our workflow.
> > In any case, I'm happy to keep on maintaining the intgeration with
> > GitLab CI, and if things break I'll do my best to fix them fast.
> 
> I don't have any strong opinions here, but my preference would probably
> be to keep any GitLab-specific CI configuration limited to "contrib", if
> it lands in the tree at all.

As mentioned, I would not mind at all if we wanted to instead carry this
as part of "contrib/".

> We already have a rather complicated CI setup on GitHub, which I think
> we generally consider authoritative in terms of determining whether "CI"
> is green. I know we have some Azure remnants in "ci", but I'm not aware
> of any of the details there.
> 
> So I have some hesitation about trying to mirror this rather complicated
> set of build rules in another CI environment. My primary concern would
> be that the two might fall out of sync and a series that is green on
> GitHub would be red on GitLab, or vice-versa. Importantly, this can
> happen even without changes to the build definitions, since (AFAICT)
> both forges distribute new images automatically, so the set of packages
> installed in GitHub may not exactly match what's in GitLab (and
> vice-versa).

Yup, that's a valid concern. As mentioned, this patch series does not
have the intent to make GitLab CI a second authoritative CI platform.
GitHub Actions should remain the source of truth of whether a pipeline
passes or not. Most importantly, I do not want to require the maintainer
to now watch both pipelines on GitHub and GitLab. This might be another
indicator that the pipeline should rather be in "contrib/", so that
people don't start to treat it as authoritative.

> My other concern is that we're doubling the cost of any new changes to
> our CI definition. Perhaps this is more of an academic concern, but I
> think my fear would be that one or the other would fall behind on in
> implementation leading to further divergence between the two.
> 
> I think having the new CI definition live in "contrib" somewhat
> addresses the "which CI is authoritative?" problem, but that it doesn't
> address the "we have two of these" problem.

I do see that this requires us to be a bit more careful with future
changes to our CI definitions. But I think the additional work that this
creates is really very limited. Except for the `.gitlab-ci.yml`, there
are only 54 lines specific to GitLab in our CI scripts now, which I
think should be rather manageable.

I also think that it is sensible to ensure that our CI scripts are as
agnostic to the CI platform as possible, as it ensures that we continue
to be agile here in the future if we are ever forced to switch due to
whatever reason. In the best case, our CI scripts would allow a user to
also easily run the tests locally via e.g. Docker. We're not there yet,
but this patch series is a good step into that direction already.

Last but not least, I actually think that having multiple supported CI
platforms also has the benefit that people can more readily set it up
for themselves. In theory, this has the potential to broaden the set of
people willing to contribute to our `ci/` scripts, which would in the
end also benefit GitHub Actions.

In my opinion, this benefit is demonstrated by this patch series
already: besides all the changes that aim to prepare for GitLab CI,
there are also patches that deduplicate code and improve test coverage
for Alpine Linux. These changes likely wouldn't have happened if it
wasn't for the GitLab CI.

> So my preference would probably to have this live out of Junio's tree,
> but I'm curious to hear what others think.

I understand your points, and especially the point about not having a
second authoritative CI platform. I'm very much on the same page as you
are here, and would be happy to move the definitions to "contrib/" if
you want me to.

But I think we should also see the potential benefit of having a second
CI platform, as it enables a more diverse set of people to contribute.
which can ultimately end up benefitting our CI infra for both GitHub
Actions and GitLab CI.

Patrick

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

^ permalink raw reply

* [PATCH v3 2/2] commit: detect commits that exist in commit-graph but not in the ODB
From: Patrick Steinhardt @ 2023-10-31  7:16 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak, Junio C Hamano, Taylor Blau, Jeff King
In-Reply-To: <cover.1698736363.git.ps@pks.im>

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

Commit graphs can become stale and contain references to commits that do
not exist in the object database anymore. Theoretically, this can lead
to a scenario where we are able to successfully look up any such commit
via the commit graph even though such a lookup would fail if done via
the object database directly.

As the commit graph is mostly intended as a sort of cache to speed up
parsing of commits we do not want to have diverging behaviour in a
repository with and a repository without commit graphs, no matter
whether they are stale or not. As commits are otherwise immutable, the
only thing that we really need to care about is thus the presence or
absence of a commit.

To address potentially stale commit data that may exist in the graph,
our `lookup_commit_in_graph()` function will check for the commit's
existence in both the commit graph, but also in the object database. So
even if we were able to look up the commit's data in the graph, we would
still pretend as if the commit didn't exist if it is missing in the
object database.

We don't have the same safety net in `parse_commit_in_graph_one()`
though. This function is mostly used internally in "commit-graph.c"
itself to validate the commit graph, and this usage is fine. We do
expose its functionality via `parse_commit_in_graph()` though, which
gets called by `repo_parse_commit_internal()`, and that function is in
turn used in many places in our codebase.

For all I can see this function is never used to directly turn an object
ID into a commit object without additional safety checks before or after
this lookup. What it is being used for though is to walk history via the
parent chain of commits. So when commits in the parent chain of a graph
walk are missing it is possible that we wouldn't notice if that missing
commit was part of the commit graph. Thus, a query like `git rev-parse
HEAD~2` can succeed even if the intermittent commit is missing.

It's unclear whether there are additional ways in which such stale
commit graphs can lead to problems. In any case, it feels like this is a
bigger bug waiting to happen when we gain additional direct or indirect
callers of `repo_parse_commit_internal()`. So let's fix the inconsistent
behaviour by checking for object existence via the object database, as
well.

This check of course comes with a performance penalty. The following
benchmarks have been executed in a clone of linux.git with stable tags
added:

    Benchmark 1: git -c core.commitGraph=true rev-list --topo-order --all (git = master)
      Time (mean ± σ):      2.913 s ±  0.018 s    [User: 2.363 s, System: 0.548 s]
      Range (min … max):    2.894 s …  2.950 s    10 runs

    Benchmark 2: git -c core.commitGraph=true rev-list --topo-order --all (git = pks-commit-graph-inconsistency)
      Time (mean ± σ):      3.834 s ±  0.052 s    [User: 3.276 s, System: 0.556 s]
      Range (min … max):    3.780 s …  3.961 s    10 runs

    Benchmark 3: git -c core.commitGraph=false rev-list --topo-order --all (git = master)
      Time (mean ± σ):     13.841 s ±  0.084 s    [User: 13.152 s, System: 0.687 s]
      Range (min … max):   13.714 s … 13.995 s    10 runs

    Benchmark 4: git -c core.commitGraph=false rev-list --topo-order --all (git = pks-commit-graph-inconsistency)
      Time (mean ± σ):     13.762 s ±  0.116 s    [User: 13.094 s, System: 0.667 s]
      Range (min … max):   13.645 s … 14.038 s    10 runs

    Summary
      git -c core.commitGraph=true rev-list --topo-order --all (git = master) ran
        1.32 ± 0.02 times faster than git -c core.commitGraph=true rev-list --topo-order --all (git = pks-commit-graph-inconsistency)
        4.72 ± 0.05 times faster than git -c core.commitGraph=false rev-list --topo-order --all (git = pks-commit-graph-inconsistency)
        4.75 ± 0.04 times faster than git -c core.commitGraph=false rev-list --topo-order --all (git = master)

We look at a ~30% regression in general, but in general we're still a
whole lot faster than without the commit graph. To counteract this, the
new check can be turned off with the `GIT_COMMIT_GRAPH_PARANOIA` envvar.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 commit.c                | 16 +++++++++++++++-
 t/t5318-commit-graph.sh | 27 +++++++++++++++++++++++++++
 2 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/commit.c b/commit.c
index b3223478bc..8405d7c3fc 100644
--- a/commit.c
+++ b/commit.c
@@ -28,6 +28,7 @@
 #include "shallow.h"
 #include "tree.h"
 #include "hook.h"
+#include "parse.h"
 
 static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
 
@@ -572,8 +573,21 @@ int repo_parse_commit_internal(struct repository *r,
 		return -1;
 	if (item->object.parsed)
 		return 0;
-	if (use_commit_graph && parse_commit_in_graph(r, item))
+	if (use_commit_graph && parse_commit_in_graph(r, item)) {
+		static int commit_graph_paranoia = -1;
+
+		if (commit_graph_paranoia == -1)
+			commit_graph_paranoia = git_env_bool(GIT_COMMIT_GRAPH_PARANOIA, 1);
+
+		if (commit_graph_paranoia && !has_object(r, &item->object.oid, 0)) {
+			unparse_commit(r, &item->object.oid);
+			return quiet_on_missing ? -1 :
+				error(_("commit %s exists in commit-graph but not in the object database"),
+				      oid_to_hex(&item->object.oid));
+		}
+
 		return 0;
+	}
 
 	if (oid_object_info_extended(r, &item->object.oid, &oi, flags) < 0)
 		return quiet_on_missing ? -1 :
diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh
index c0cc454538..7b1c331b07 100755
--- a/t/t5318-commit-graph.sh
+++ b/t/t5318-commit-graph.sh
@@ -842,4 +842,31 @@ test_expect_success 'stale commit cannot be parsed when given directly' '
 	)
 '
 
+test_expect_success 'stale commit cannot be parsed when traversing graph' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+
+		test_commit A &&
+		test_commit B &&
+		test_commit C &&
+		git commit-graph write --reachable &&
+
+		# Corrupt the repository by deleting the intermediate commit
+		# object. Commands should notice that this object is absent and
+		# thus that the repository is corrupt even if the commit graph
+		# exists.
+		oid=$(git rev-parse B) &&
+		rm .git/objects/"$(test_oid_to_path "$oid")" &&
+
+		# Again, we should be able to parse the commit when not
+		# being paranoid about commit graph staleness...
+		GIT_COMMIT_GRAPH_PARANOIA=false git rev-parse HEAD~2 &&
+		# ... but fail when we are paranoid.
+		test_must_fail git rev-parse HEAD~2 2>error &&
+		grep "error: commit $oid exists in commit-graph but not in the object database" error
+	)
+'
+
 test_done
-- 
2.42.0


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

^ permalink raw reply related

* [PATCH v3 1/2] commit-graph: introduce envvar to disable commit existence checks
From: Patrick Steinhardt @ 2023-10-31  7:16 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak, Junio C Hamano, Taylor Blau, Jeff King
In-Reply-To: <cover.1698736363.git.ps@pks.im>

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

Our `lookup_commit_in_graph()` helper tries to look up commits from the
commit graph and, if it doesn't exist there, falls back to parsing it
from the object database instead. This is intended to speed up the
lookup of any such commit that exists in the database. There is an edge
case though where the commit exists in the graph, but not in the object
database. To avoid returning such stale commits the helper function thus
double checks that any such commit parsed from the graph also exists in
the object database. This makes the function safe to use even when
commit graphs aren't updated regularly.

We're about to introduce the same pattern into other parts of our code
base though, namely `repo_parse_commit_internal()`. Here the extra
sanity check is a bit of a tougher sell: `lookup_commit_in_graph()` was
a newly introduced helper, and as such there was no performance hit by
adding this sanity check. If we added `repo_parse_commit_internal()`
with that sanity check right from the beginning as well, this would
probably never have been an issue to begin with. But by retrofitting it
with this sanity check now we do add a performance regression to
preexisting code, and thus there is a desire to avoid this or at least
give an escape hatch.

In practice, there is no inherent reason why either of those functions
should have the sanity check whereas the other one does not: either both
of them are able to detect this issue or none of them should be. This
also means that the default of whether we do the check should likely be
the same for both. To err on the side of caution, we thus rather want to
make `repo_parse_commit_internal()` stricter than to loosen the checks
that we already have in `lookup_commit_in_graph()`.

The escape hatch is added in the form of a new GIT_COMMIT_GRAPH_PARANOIA
environment variable that mirrors GIT_REF_PARANOIA. If enabled, which is
the default, we will double check that commits looked up in the commit
graph via `lookup_commit_in_graph()` also exist in the object database.
This same check will also be added in `repo_parse_commit_internal()`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git.txt   | 10 ++++++++++
 commit-graph.c          |  6 +++++-
 commit-graph.h          |  6 ++++++
 t/t5318-commit-graph.sh | 21 +++++++++++++++++++++
 4 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/Documentation/git.txt b/Documentation/git.txt
index 11228956cd..3bac24cf8a 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -911,6 +911,16 @@ for full details.
 	should not normally need to set this to `0`, but it may be
 	useful when trying to salvage data from a corrupted repository.
 
+`GIT_COMMIT_GRAPH_PARANOIA`::
+	When loading a commit object from the commit-graph, Git performs an
+	existence check on the object in the object database. This is done to
+	avoid issues with stale commit-graphs that contain references to
+	already-deleted commits, but comes with a performance penalty.
++
+The default is "true", which enables the aforementioned behavior.
+Setting this to "false" disables the existence check. This can lead to
+a performance improvement at the cost of consistency.
+
 `GIT_ALLOW_PROTOCOL`::
 	If set to a colon-separated list of protocols, behave as if
 	`protocol.allow` is set to `never`, and each of the listed
diff --git a/commit-graph.c b/commit-graph.c
index fd2f700b2e..6d21ea6301 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -939,14 +939,18 @@ int repo_find_commit_pos_in_graph(struct repository *r, struct commit *c,
 
 struct commit *lookup_commit_in_graph(struct repository *repo, const struct object_id *id)
 {
+	static int commit_graph_paranoia = -1;
 	struct commit *commit;
 	uint32_t pos;
 
+	if (commit_graph_paranoia == -1)
+		commit_graph_paranoia = git_env_bool(GIT_COMMIT_GRAPH_PARANOIA, 1);
+
 	if (!prepare_commit_graph(repo))
 		return NULL;
 	if (!search_commit_pos_in_graph(id, repo->objects->commit_graph, &pos))
 		return NULL;
-	if (!has_object(repo, id, 0))
+	if (commit_graph_paranoia && !has_object(repo, id, 0))
 		return NULL;
 
 	commit = lookup_commit(repo, id);
diff --git a/commit-graph.h b/commit-graph.h
index 20ada7e891..bd4289620c 100644
--- a/commit-graph.h
+++ b/commit-graph.h
@@ -8,6 +8,12 @@
 #define GIT_TEST_COMMIT_GRAPH_DIE_ON_PARSE "GIT_TEST_COMMIT_GRAPH_DIE_ON_PARSE"
 #define GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS "GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS"
 
+/*
+ * This environment variable controls whether commits looked up via the
+ * commit graph will be double checked to exist in the object database.
+ */
+#define GIT_COMMIT_GRAPH_PARANOIA "GIT_COMMIT_GRAPH_PARANOIA"
+
 /*
  * This method is only used to enhance coverage of the commit-graph
  * feature in the test suite with the GIT_TEST_COMMIT_GRAPH and
diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh
index ba65f17dd9..c0cc454538 100755
--- a/t/t5318-commit-graph.sh
+++ b/t/t5318-commit-graph.sh
@@ -821,4 +821,25 @@ test_expect_success 'overflow during generation version upgrade' '
 	)
 '
 
+test_expect_success 'stale commit cannot be parsed when given directly' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit A &&
+		test_commit B &&
+		git commit-graph write --reachable &&
+
+		oid=$(git rev-parse B) &&
+		rm .git/objects/"$(test_oid_to_path "$oid")" &&
+
+		# Verify that it is possible to read the commit from the
+		# commit graph when not being paranoid, ...
+		GIT_COMMIT_GRAPH_PARANOIA=false git rev-list B &&
+		# ... but parsing the commit when double checking that
+		# it actually exists in the object database should fail.
+		test_must_fail git rev-list -1 B
+	)
+'
+
 test_done
-- 
2.42.0


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

^ permalink raw reply related

* [PATCH v3 0/2] commit-graph: detect commits missing in ODB
From: Patrick Steinhardt @ 2023-10-31  7:16 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak, Junio C Hamano, Taylor Blau, Jeff King
In-Reply-To: <cover.1698060036.git.ps@pks.im>

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

Hi,

this is version 3 of my patch series to more readily detect commits
parsed from the commit graph which are missing in the object database.

Changes compared to v2:

    - Rewrote the help text for `GIT_COMMIT_GRAPH_PARANOIA` to be more
      accessible.

    - Renamed the `object_paranoia` variable to `commit_graph_paranoia`.

    - Fixed a typo.

Thanks!

Patrick

Patrick Steinhardt (2):
  commit-graph: introduce envvar to disable commit existence checks
  commit: detect commits that exist in commit-graph but not in the ODB

 Documentation/git.txt   | 10 +++++++++
 commit-graph.c          |  6 +++++-
 commit-graph.h          |  6 ++++++
 commit.c                | 16 +++++++++++++-
 t/t5318-commit-graph.sh | 48 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 84 insertions(+), 2 deletions(-)

Range-diff against v2:
1:  a89c435528 ! 1:  c433ec1254 commit-graph: introduce envvar to disable commit existence checks
    @@ Documentation/git.txt: for full details.
      	useful when trying to salvage data from a corrupted repository.
      
     +`GIT_COMMIT_GRAPH_PARANOIA`::
    -+	If this Boolean environment variable is set to false, ignore the
    -+	case where commits exist in the commit graph but not in the
    -+	object database. Normally, Git will check whether commits loaded
    -+	from the commit graph exist in the object database to avoid
    -+	issues with stale commit graphs, but this check comes with a
    -+	performance penalty. The default is `1` (i.e., be paranoid about
    -+	stale commits in the commit graph).
    ++	When loading a commit object from the commit-graph, Git performs an
    ++	existence check on the object in the object database. This is done to
    ++	avoid issues with stale commit-graphs that contain references to
    ++	already-deleted commits, but comes with a performance penalty.
    +++
    ++The default is "true", which enables the aforementioned behavior.
    ++Setting this to "false" disables the existence check. This can lead to
    ++a performance improvement at the cost of consistency.
     +
      `GIT_ALLOW_PROTOCOL`::
      	If set to a colon-separated list of protocols, behave as if
    @@ commit-graph.c: int repo_find_commit_pos_in_graph(struct repository *r, struct c
      
      struct commit *lookup_commit_in_graph(struct repository *repo, const struct object_id *id)
      {
    -+	static int object_paranoia = -1;
    ++	static int commit_graph_paranoia = -1;
      	struct commit *commit;
      	uint32_t pos;
      
    -+	if (object_paranoia == -1)
    -+		object_paranoia = git_env_bool(GIT_COMMIT_GRAPH_PARANOIA, 1);
    ++	if (commit_graph_paranoia == -1)
    ++		commit_graph_paranoia = git_env_bool(GIT_COMMIT_GRAPH_PARANOIA, 1);
     +
      	if (!prepare_commit_graph(repo))
      		return NULL;
      	if (!search_commit_pos_in_graph(id, repo->objects->commit_graph, &pos))
      		return NULL;
     -	if (!has_object(repo, id, 0))
    -+	if (object_paranoia && !has_object(repo, id, 0))
    ++	if (commit_graph_paranoia && !has_object(repo, id, 0))
      		return NULL;
      
      	commit = lookup_commit(repo, id);
2:  0476d48555 ! 2:  8629fd0892 commit: detect commits that exist in commit-graph but not in the ODB
    @@ commit.c: int repo_parse_commit_internal(struct repository *r,
      		return 0;
     -	if (use_commit_graph && parse_commit_in_graph(r, item))
     +	if (use_commit_graph && parse_commit_in_graph(r, item)) {
    -+		static int object_paranoia = -1;
    ++		static int commit_graph_paranoia = -1;
     +
    -+		if (object_paranoia == -1)
    -+			object_paranoia = git_env_bool(GIT_COMMIT_GRAPH_PARANOIA, 1);
    ++		if (commit_graph_paranoia == -1)
    ++			commit_graph_paranoia = git_env_bool(GIT_COMMIT_GRAPH_PARANOIA, 1);
     +
    -+		if (object_paranoia && !has_object(r, &item->object.oid, 0)) {
    ++		if (commit_graph_paranoia && !has_object(r, &item->object.oid, 0)) {
     +			unparse_commit(r, &item->object.oid);
     +			return quiet_on_missing ? -1 :
     +				error(_("commit %s exists in commit-graph but not in the object database"),
    @@ t/t5318-commit-graph.sh: test_expect_success 'stale commit cannot be parsed when
     +		test_commit C &&
     +		git commit-graph write --reachable &&
     +
    -+		# Corrupt the repository by deleting the intermittent commit
    ++		# Corrupt the repository by deleting the intermediate commit
     +		# object. Commands should notice that this object is absent and
     +		# thus that the repository is corrupt even if the commit graph
     +		# exists.
-- 
2.42.0


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

^ permalink raw reply

* Re: [PATCH v2 0/2] sequencer: remove use of hardcoded comment char
From: Elijah Newren @ 2023-10-31  6:55 UTC (permalink / raw)
  To: Tony Tung via GitGitGadget; +Cc: git, Tony Tung
In-Reply-To: <pull.1603.v2.git.1698728952.gitgitgadget@gmail.com>

Hi,

On Mon, Oct 30, 2023 at 10:09 PM Tony Tung via GitGitGadget
<gitgitgadget@gmail.com> wrote:
>
> Instead of using the hardcoded # , use the user-defined comment_line_char.
> Adds a test to prevent regressions.
>
> Tony Tung (2):
>   sequencer: remove use of comment character
>   sequencer: fix remaining hardcoded comment char

The second commit message seems to suggest that the two commits should
just be squashed; there's no explicit or even implicit reason provided
for why the two small patches are logically independent.  After
reading them carefully, and digging through the particular changes
being made and what part of the code they touch, I think I can guess
at a potential reason, but I feel like I'm crossing into the territory
of mind reading trying to articulate that reason.  (Besides, my
rationale would argue that the two patches should be split
differently.)  Perhaps a comment could be added, to either the second
commit message or the cover letter, to explain that better?

More importantly, though, I think the second commit message is
actually wrong.  Before and after applying this series:

$ git grep -c -e '".*#' -e "'#'" -- sequencer.c
sequencer.c:16

$ b4 am c9f4ff34dbdb7ba221e4203bb6551b80948dc71d.1698728953.git.gitgitgadget@gmail.com
$ git am ./v2_20231031_gitgitgadget_sequencer_remove_use_of_hardcoded_comment_char.mbx

$ git grep -c -e '".*#' -e "'#'" -- sequencer.c
sequencer.c:12

Granted, four of those lines are code comments, but that still leaves
8 hard coded references to '#' in the code at the end (i.e. the
majority are still left), meaning your second patch doesn't do what
its subject line claims.

And, most important of all is still the first patch.  As I stated
elsewhere in this thread (at
CABPp-BFY7m_g+sT131_Ubxqo5FsHGKOPMng7=90_0-+xCS9NEQ@mail.gmail.com):

"""
I think supporting comment_line_char for the TODO file provides no
value, and I think the easier fix would be undoing the uses of
comment_line_char relative to the TODO file (perhaps also leaving
in-code comments to the effect that comment_line_char just doesn't
apply to the TODO file).

However, if someone prefers to make the TODO file also respect
comment_line_char, despite its dubious value, then I expect any patch
should
  1) audit *every* reference found via git grep -e '".*#' -e "'#'" sequencer.c
  2) add a test case (or cases) involving --rebase-merges -i that
trigger the relevant code paths
If they don't do that, then I fear we might make the bug more likely
to be triggered rather than less.
"""

Personally, I would rather not accept patches changing the handling of
the TODO script relative to comment_line_char until the above is done,
and I worry that half measures _might_ end up being more hurtful than
helpful.

I feel quite differently about patches that make COMMIT_EDITMSG
handling use comment_line_char more consistently since that code
simply writes the file without re-parsing it; although fixing
everything would be best, even fixing some of them to use
comment_line_char would be welcome.  I think the first two hunks of
your second patch happen to fall into this category, so if those were
split out, then I'd say those are good partial solutions.

^ permalink raw reply

* Re: [PATCH 0/3] Add `-p' option to `git-mv', inspired by `mkdir'
From: Dragan Simic @ 2023-10-31  6:38 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Hugo Sales, git, Derrick Stolee, Shaoxuan Yuan
In-Reply-To: <xmqq5y2n49la.fsf@gitster.g>

On 2023-10-31 06:58, Junio C Hamano wrote:
> Dragan Simic <dsimic@manjaro.org> writes:
> 
>> A quite similar ambiguity exists in cp(1) in mv(1), which is also
>> resolved by the use of the trailing slash character.  However, I've
>> encountered only one person aware of that disambiguation, and in cp(1)
>> only, but in the "I always include the trailing slash" way, without
>> actually understanding it fully.  Maybe I need to encounter more
>> people, I don't know.
> 
> If the majority of (perhaps new) users you already know find such
> disambiguation method unfamiliar, that already is a good anecdata
> without any need for you to meet more people to tell us that it is
> not a very easy-to-understand thing for them, no?

Quite frankly, I'm divided there.  On the one hand, you're right that 
this disambiguation method is a bit confusing and it's absolutely not 
very well known.  On the other hand, it's already out there in the wild, 
including git, and it's actually quite useful when used properly.

If I had to vote, I'd give my vote to embracing this disambiguation 
method, but only with good documentation and some kind of education 
through an article or two.  I believe that proper education simply isn't 
present, or at least not in a user-friendly manner, which should be 
corrected, regardless of the new "git mv -p" feature being accepted or 
not.

^ permalink raw reply

* Re: Method for Calculating Statistics of Developer Contribution to a Specified Branch.
From: Junio C Hamano @ 2023-10-31  6:25 UTC (permalink / raw)
  To: Hongyi Zhao; +Cc: Taylor Blau, brian m. carlson, Git List
In-Reply-To: <CAGP6POK2yABRhJHQYfOFZ2h6BSy9XU6aZbnBaA11TJiEnBAa6g@mail.gmail.com>

Hongyi Zhao <hongyi.zhao@gmail.com> writes:

>> But I think that there is a slightly cleaner way to compute the result
>> you're after, like so:
>> ...
> So, your method and my original one give exactly the same result.
> Therefore, I can't see what their fundamental difference is.

I think Taylor offered a "slightly cleaner way", and not a
"different way that computes better result".  So it is not
surprising, at least to me who is watching from the sideline, that
you cannot see any fundamental difference.

^ permalink raw reply

* Re: [PATCH] sequencer: remove use of comment character
From: Elijah Newren @ 2023-10-31  6:20 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Tony Tung via GitGitGadget, git, Tony Tung
In-Reply-To: <xmqqcywv4ar2.fsf@gitster.g>

On Mon, Oct 30, 2023 at 10:33 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Junio C Hamano <gitster@pobox.com> writes:
>
> > Elijah Newren <newren@gmail.com> writes:
> >
> >> I thought the point of the comment_line_char was so that commit
> >> messages could have lines starting with '#'.  That rationale doesn't
> >> apply to the TODO list generation or parsing, and I'm not sure if we
> >> want to add the same complexity there.
>
> Earlier I said
>
> > Thanks for a healthy dose of sanity.  I noticed existing use of
> > comment_line_char everywhere in sequencer.c and assumed we would
> > want to be consistent, but you are right to point out that they are
> > all about the COMMIT_EDITMSG kind of thing, and not about what
> > appears in "sequencer/todo".
>
> but with something as simple as
>
>     $ git -c core.commentchar='@' rebase -i master seen^2
>
> I can see that the references to comment_line_char in sequencer.c
> are about the commented lines after the list of insn in the
> generated sequencer/todo file, so even though the rationale does not
> apply, isn't this already "broken" in the current code anyway?

Yes, I believe it is.  However, I remember specifically looking at
cases with --rebase-merges about a year and a half ago, and noted that
there was a mixture of hardcoded '#' references along with
comment_line_char.  I noted at the time that changing
comment_line_char looked like it had a bug, and that the parsing in
particular would be fooled and do wrong things if it changed.
Unfortunately, I can't find any notes from the time with the details,
so I don't remember exactly what or how it was triggered.

However, I do suspect that the references to comment_line_char in the
`rebase -i` codepaths was not for any actual intended purpose, but
just noting that they were used elsewhere in the file (for
COMMIT_EDITMSG, where it made sense) and just mimicking that code
without realizing the lack of rationale.  That would have been mere
wasted effort had the comment_line_char been consistently supported in
the TODO file editing and parsing, but it wasn't, which left TODO
editing & parsing somewhat broken.

I think supporting comment_line_char for the TODO file provides no
value, and I think the easier fix would be undoing the uses of
comment_line_char relative to the TODO file (perhaps also leaving
in-code comments to the effect that comment_line_char just doesn't
apply to the TODO file).

However, if someone prefers to make the TODO file also respect
comment_line_char, despite its dubious value, then I expect any patch
should
  1) audit *every* reference found via git grep -e '".*#' -e "'#'" sequencer.c
  2) add a test case (or cases) involving --rebase-merges -i that
trigger the relevant code paths
If they don't do that, then I fear we might make the bug more likely
to be triggered rather than less.

^ permalink raw reply

* Re: [PATCH v2 1/2] commit-graph: introduce envvar to disable commit existence checks
From: Patrick Steinhardt @ 2023-10-31  6:19 UTC (permalink / raw)
  To: Taylor Blau; +Cc: git, Karthik Nayak, Junio C Hamano, Jeff King
In-Reply-To: <ZUAgPVR8HxgEZEWo@nand.local>

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

On Mon, Oct 30, 2023 at 05:29:33PM -0400, Taylor Blau wrote:
> On Mon, Oct 23, 2023 at 01:27:16PM +0200, Patrick Steinhardt wrote:
> > Our `lookup_commit_in_graph()` helper tries to look up commits from the
> > commit graph and, if it doesn't exist there, falls back to parsing it
> > from the object database instead. This is intended to speed up the
> > lookup of any such commit that exists in the database. There is an edge
> > case though where the commit exists in the graph, but not in the object
> > database. To avoid returning such stale commits the helper function thus
> > double checks that any such commit parsed from the graph also exists in
> > the object database. This makes the function safe to use even when
> > commit graphs aren't updated regularly.
> >
> > We're about to introduce the same pattern into other parts of our code
> > base though, namely `repo_parse_commit_internal()`. Here the extra
> > sanity check is a bit of a tougher sell: `lookup_commit_in_graph()` was
> > a newly introduced helper, and as such there was no performance hit by
> > adding this sanity check. If we added `repo_parse_commit_internal()`
> > with that sanity check right from the beginning as well, this would
> > probably never have been an issue to begin with. But by retrofitting it
> > with this sanity check now we do add a performance regression to
> > preexisting code, and thus there is a desire to avoid this or at least
> > give an escape hatch.
> >
> > In practice, there is no inherent reason why either of those functions
> > should have the sanity check whereas the other one does not: either both
> > of them are able to detect this issue or none of them should be. This
> > also means that the default of whether we do the check should likely be
> > the same for both. To err on the side of caution, we thus rather want to
> > make `repo_parse_commit_internal()` stricter than to loosen the checks
> > that we already have in `lookup_commit_in_graph()`.
> 
> All well reasoned. I think the most compelling reason is that we're
> already doing this extra check in lookup_commit_in_graph(), and having
> that be somewhat inconsistent with repo_parse_commit_internal() feels
> error-prone to me.
> 
> > The escape hatch is added in the form of a new GIT_COMMIT_GRAPH_PARANOIA
> > environment variable that mirrors GIT_REF_PARANOIA. If enabled, which is
> > the default, we will double check that commits looked up in the commit
> > graph via `lookup_commit_in_graph()` also exist in the object database.
> > This same check will also be added in `repo_parse_commit_internal()`.
> 
> Sounds good.
> 
> > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > ---
> >  Documentation/git.txt   |  9 +++++++++
> >  commit-graph.c          |  6 +++++-
> >  commit-graph.h          |  6 ++++++
> >  t/t5318-commit-graph.sh | 21 +++++++++++++++++++++
> >  4 files changed, 41 insertions(+), 1 deletion(-)
> >
> > diff --git a/Documentation/git.txt b/Documentation/git.txt
> > index 11228956cd..22c2b537aa 100644
> > --- a/Documentation/git.txt
> > +++ b/Documentation/git.txt
> > @@ -911,6 +911,15 @@ for full details.
> >  	should not normally need to set this to `0`, but it may be
> >  	useful when trying to salvage data from a corrupted repository.
> >
> > +`GIT_COMMIT_GRAPH_PARANOIA`::
> > +	If this Boolean environment variable is set to false, ignore the
> > +	case where commits exist in the commit graph but not in the
> > +	object database. Normally, Git will check whether commits loaded
> > +	from the commit graph exist in the object database to avoid
> > +	issues with stale commit graphs, but this check comes with a
> > +	performance penalty. The default is `1` (i.e., be paranoid about
> > +	stale commits in the commit graph).
> > +
> 
> The first two sentences seem to be flipped. Perhaps:
> 
>     When loading a commit object from the commit-graph, Git will perform
>     an existence check on the object in the ODB before parsing it out of
>     the commit-graph. The default is "true", which enables the
>     aforementioned behavior. Setting this to "false" disables the
>     existential check when parsing commits from a commit-graph.

I was modelling this after the text we had in `GIT_REF_PARANOIA`, but I
like your version more indeed. I'll massage it a bit to mention _why_
one would want to disable this.

> >  `GIT_ALLOW_PROTOCOL`::
> >  	If set to a colon-separated list of protocols, behave as if
> >  	`protocol.allow` is set to `never`, and each of the listed
> > diff --git a/commit-graph.c b/commit-graph.c
> > index fd2f700b2e..12ec31902e 100644
> > --- a/commit-graph.c
> > +++ b/commit-graph.c
> > @@ -939,14 +939,18 @@ int repo_find_commit_pos_in_graph(struct repository *r, struct commit *c,
> >
> >  struct commit *lookup_commit_in_graph(struct repository *repo, const struct object_id *id)
> >  {
> > +	static int object_paranoia = -1;
> >  	struct commit *commit;
> >  	uint32_t pos;
> >
> > +	if (object_paranoia == -1)
> > +		object_paranoia = git_env_bool(GIT_COMMIT_GRAPH_PARANOIA, 1);
> > +
> 
> I don't think that this is a reroll-able issue, but calling this
> variable object_paranoia to store a setting for *graph* paranoia feels
> like a good itch to scratch. But obviously not a big deal ;-).

Ugh, yeah. I first had the envvar as "GIT_OBJECT_PARANOIA", but
discarded that name because I feared that it might become overloaded
with semi-related checks.

Will fix.

> > @@ -821,4 +821,25 @@ test_expect_success 'overflow during generation version upgrade' '
> >  	)
> >  '
> >
> > +test_expect_success 'stale commit cannot be parsed when given directly' '
> > +	test_when_finished "rm -rf repo" &&
> > +	git init repo &&
> > +	(
> > +		cd repo &&
> > +		test_commit A &&
> > +		test_commit B &&
> > +		git commit-graph write --reachable &&
> > +
> > +		oid=$(git rev-parse B) &&
> > +		rm .git/objects/"$(test_oid_to_path "$oid")" &&
> > +
> > +		# Verify that it is possible to read the commit from the
> > +		# commit graph when not being paranoid, ...
> > +		GIT_COMMIT_GRAPH_PARANOIA=false git rev-list B &&
> > +		# ... but parsing the commit when double checking that
> > +		# it actually exists in the object database should fail.
> > +		test_must_fail git rev-list -1 B
> 
> Would "cat-file -p" be more direct here than "rev-list -1"?

No, because it doesn't use `lookup_commit_in_graph()`. I had to go
searching a bit to find something that exposes this inconsistency, and
git-rev-list(1) was the easiest one I found.

Patrick

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

^ permalink raw reply

* Re: [PATCH] merge: --ff-one-only to apply FF if commit is one
From: Ruslan Yakauleu @ 2023-10-31  6:01 UTC (permalink / raw)
  To: Taylor Blau; +Cc: git
In-Reply-To: <ZUALkdSJZ70+KBYq@nand.local>

Hi Taylor,

Thank you so much for the review and feedback.

We use a mixed environment (Linux, MacOS and Windows) on
multiple projects. And some members can work some days from home and
some days from the office (on different PC).
So, using of scripts a bit uncomfortable.

Better to apply once for everybody in the domain
git config --global merge.ff one-only

I'll try to fill the proper test a bit later.

--
Ruslan



^ permalink raw reply

* Re: [PATCH 0/3] Add `-p' option to `git-mv', inspired by `mkdir'
From: Junio C Hamano @ 2023-10-31  5:58 UTC (permalink / raw)
  To: Dragan Simic; +Cc: Hugo Sales, git, Derrick Stolee, Shaoxuan Yuan
In-Reply-To: <ffbb04b363938e4a487906134ce4f3c6@manjaro.org>

Dragan Simic <dsimic@manjaro.org> writes:

> A quite similar ambiguity exists in cp(1) in mv(1), which is also
> resolved by the use of the trailing slash character.  However, I've
> encountered only one person aware of that disambiguation, and in cp(1)
> only, but in the "I always include the trailing slash" way, without
> actually understanding it fully.  Maybe I need to encounter more
> people, I don't know.

If the majority of (perhaps new) users you already know find such
disambiguation method unfamiliar, that already is a good anecdata
without any need for you to meet more people to tell us that it is
not a very easy-to-understand thing for them, no?

^ permalink raw reply

* Re: [PATCH] merge: --ff-one-only to apply FF if commit is one
From: Junio C Hamano @ 2023-10-31  6:07 UTC (permalink / raw)
  To: Ruslan Yakauleu; +Cc: git
In-Reply-To: <9f584927-7506-421e-a363-953eebb0ef90@gmail.com>

Ruslan Yakauleu <ruslan.yakauleu@gmail.com> writes:

> Command of developers can decide which policy use and why.
> If a team decides to avoid extra merges - why not?

I never said "--ff" is a bad option that should not exist, and I am
not sure why I had to be thrown such a rhetorical question at.

^ permalink raw reply

* Re: [RFC PATCH 1/3] strbuf: make add_lines() public
From: Junio C Hamano @ 2023-10-31  6:01 UTC (permalink / raw)
  To: Jonathan Tan; +Cc: git, Phillip Wood, Dragan Simic
In-Reply-To: <xmqqy1fj8y5m.fsf@gitster.g>

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

> Jonathan Tan <jonathantanmy@google.com> writes:
>
>> Subsequent patches will require the ability to add different prefixes
>> to different lines (depending on their contents), so make this
>> functionality available from outside strbuf.c.
>
> I do not think it is a good idea to force almost everybody to repeat
> themselves.  As we can see here, all but just a single caller of
> strbuf_add_lines() with this patch pass the same prefix for both
> parameters.  If we need to make the current strbuf.c:add_lines()
> also available to some specific callers, that is fine, but let's
> keep the simpler version that almost everybody uses as-is, and give
> the more complex and featureful one that is used only by selected
> callers a longer and more cumbersome name.
>
> Thanks.

Another practical downside of this patch is that it breaks other
in-flight topics that adds new users of strbuf_add_lines(), and that
breakage is totally unnecessary.



^ permalink raw reply

* Re: [PATCH] reflog: fix expire --single-worktree
From: René Scharfe @ 2023-10-31  6:16 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git List, John Cai
In-Reply-To: <xmqqlebjbt9y.fsf@gitster.g>

Am 31.10.23 um 00:11 schrieb Junio C Hamano:
> René Scharfe <l.s.r@web.de> writes:
>
>> Am 29.10.23 um 23:31 schrieb Junio C Hamano:
>>> René Scharfe <l.s.r@web.de> writes:
>>>
>>> diff --git i/parse-options.c w/parse-options.c
>>> index 093eaf2db8..be8bedba29 100644
>>> --- i/parse-options.c
>>> +++ w/parse-options.c
>>> @@ -469,7 +469,8 @@ static void parse_options_check(const struct option *opts)
>>>  			optbug(opts, "uses incompatible flags "
>>>  			       "LASTARG_DEFAULT and OPTARG");
>>>  		if (opts->short_name) {
>>> -			if (0x7F <= opts->short_name)
>>> +			if (opts->short_name &&
>>> +			    (opts->short_name < 0x21 || 0x7F <= opts->short_name))
>>
>> Good idea.  This is equivalent to !isprint(opts->short_name), which I
>> find to be more readable here.
>
> Thanks---I didn't think of using !isprint() but you are right.  It
> is much shorter.
>
> I am not absolutely certain if it is easier to read, though.  I get
> always confused when asking myself if SP, HT, and LF are printables.
> (in other words, I cannot immediately answer "does 'printable' mean
> 'can be sent to a teletype and have it do what is expected to be
> done?"---the question I should be asking myself is "is 'printable'
> synonym to 'when printed, some ink is consumed'?").

isprint() accepts SP, but not HT or LF.  Go figure.  And thus I made an
off-by-one error by suggesting this macro, because your version rejects
SP (0x20).  Am I unintentionally making a point here for using the
is-macros because I can't read numeric comparisons? O_o

isalnum() and ispunct() could be used instead.

>> Seeing why "char short_opts[128];" a
>> few lines up is big enough would become a bit harder, though.
>
> Sorry, but I do not quite follow.  We used to allow anything below
> 0x7e; now we clip that range further to reject anything below 0x21.
> If [128] was big enough, it still is big enough, no?
>
> Because the type of .short_name member is "int", we could have had
> negative number in there and access to short_opts[] on the next line
> would have been out of bounds.  By clipping the lower bound, we get
> rid of that risk, no?

Yes, but if the allowed range is hidden behind macro invocations then
the boundaries are no longer as obvious as in your version.

>>>  				optbug(opts, "invalid short name");
>>>  			else if (short_opts[opts->short_name]++)
>>>  				optbug(opts, "short name already used");

^ 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