From 17986ac18b2bdc3ea1ffe5019f5d603780c15bb1 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Thu, 15 May 2025 16:16:46 +0200 Subject: [PATCH 1/5] EDITME: cover title for 306-allow-git-for-each-ref-1-to-start-from-arbitrary-pagination-token # Describe the purpose of this series. The information you put here # will be used by the project maintainer to make a decision whether # your patches should be reviewed, and in what priority order. Please be # very detailed and link to any relevant discussions or sites that the # maintainer can review to better understand your proposed changes. If you # only have a single patch in your series, the contents of the cover # letter will be appended to the "under-the-cut" portion of the patch. # Lines starting with # will be removed from the cover letter. You can # use them to add notes or reminders to yourself. If you want to use # markdown headers in your cover letter, start the line with ">#". # You can add trailers to the cover letter. Any email addresses found in # these trailers will be added to the addresses specified/generated # during the b4 send stage. You can also run "b4 prep --auto-to-cc" to # auto-populate the To: and Cc: trailers based on the code being # modified. Signed-off-by: Karthik Nayak --- b4-submit-tracking --- # This section is used internally by b4 prep for tracking purposes. { "series": { "revision": 1, "change-id": "20250515-306-allow-git-for-each-ref-1-to-start-from-arbitrary-pagination-token-73757558bee7", "prefixes": [] } } -- GitLab From 23513307c293cb7ca7640672abaf38e99c366fb6 Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Wed, 4 Jun 2025 21:57:44 +0200 Subject: [PATCH 2/5] refs: don't externally expose `refs_verify_refname_available()` The `refs_verify_refname_available()` function is currently exposed via 'refs.h', but the function is only used within the 'refs/' namespace, the only exception being its 't/helper/test-ref-store.c'. The usage of the function in a unit test doesn't warrant it being a public API, so move the function to 'refs-internal.h' which only exposes the function within the 'refs/' namespace. Signed-off-by: Karthik Nayak --- refs.h | 27 --------------------------- refs/refs-internal.h | 27 +++++++++++++++++++++++++++ t/helper/test-ref-store.c | 1 + 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/refs.h b/refs.h index 46a6008e07f..762b18688a6 100644 --- a/refs.h +++ b/refs.h @@ -114,33 +114,6 @@ int refs_read_ref(struct ref_store *refs, const char *refname, struct object_id int refs_read_symbolic_ref(struct ref_store *ref_store, const char *refname, struct strbuf *referent); -/* - * Return 0 if a reference named refname could be created without - * conflicting with the name of an existing reference. Otherwise, - * return a negative value and write an explanation to err. If extras - * is non-NULL, it is a list of additional refnames with which refname - * is not allowed to conflict. If skip is non-NULL, ignore potential - * conflicts with refs in skip (e.g., because they are scheduled for - * deletion in the same operation). Behavior is undefined if the same - * name is listed in both extras and skip. - * - * Two reference names conflict if one of them exactly matches the - * leading components of the other; e.g., "foo/bar" conflicts with - * both "foo" and with "foo/bar/baz" but not with "foo/bar" or - * "foo/barbados". - * - * If `initial_transaction` is truish, then all collision checks with - * preexisting refs are skipped. - * - * extras and skip must be sorted. - */ -enum ref_transaction_error refs_verify_refname_available(struct ref_store *refs, - const char *refname, - const struct string_list *extras, - const struct string_list *skip, - unsigned int initial_transaction, - struct strbuf *err); - int refs_ref_exists(struct ref_store *refs, const char *refname); int should_autocreate_reflog(enum log_refs_config log_all_ref_updates, diff --git a/refs/refs-internal.h b/refs/refs-internal.h index f8688708519..55325149b80 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -806,6 +806,33 @@ enum ref_transaction_error ref_update_check_old_target(const char *referent, */ int ref_update_expects_existing_old_ref(struct ref_update *update); +/* + * Return 0 if a reference named refname could be created without + * conflicting with the name of an existing reference. Otherwise, + * return a negative value and write an explanation to err. If extras + * is non-NULL, it is a list of additional refnames with which refname + * is not allowed to conflict. If skip is non-NULL, ignore potential + * conflicts with refs in skip (e.g., because they are scheduled for + * deletion in the same operation). Behavior is undefined if the same + * name is listed in both extras and skip. + * + * Two reference names conflict if one of them exactly matches the + * leading components of the other; e.g., "foo/bar" conflicts with + * both "foo" and with "foo/bar/baz" but not with "foo/bar" or + * "foo/barbados". + * + * If `initial_transaction` is truish, then all collision checks with + * preexisting refs are skipped. + * + * extras and skip must be sorted. + */ +enum ref_transaction_error refs_verify_refname_available(struct ref_store *refs, + const char *refname, + const struct string_list *extras, + const struct string_list *skip, + unsigned int initial_transaction, + struct strbuf *err); + /* * Same as `refs_verify_refname_available()`, but checking for a list of * refnames instead of only a single item. This is more efficient in the case diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index 4cfc7c90b59..fe409185f21 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -10,6 +10,7 @@ #include "repository.h" #include "strbuf.h" #include "revision.h" +#include "refs/refs-internal.h" struct flag_definition { const char *name; -- GitLab From 53b6161baf9b44ea53fcb073f96c13fff28b82ae Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Sun, 18 May 2025 20:44:25 +0200 Subject: [PATCH 3/5] refs: move ref_namespace related definition to the top Move the 'ref_namespace' related definitions to the top of 'refs.h'. This is in preparation for the next few commits, wherein the ref iteration functions will be modified and cleaned up to provide a single interface. This will also use the 'ref_namespace' definition. While it would be sufficient to only move 'enum ref_namespace' to the top, keeping related definitions together makes it easier to read/maintain, so move the whole section to the top. Signed-off-by: Karthik Nayak --- refs.h | 89 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/refs.h b/refs.h index 762b18688a6..0634903a7c8 100644 --- a/refs.h +++ b/refs.h @@ -16,6 +16,50 @@ struct worktree; enum ref_storage_format ref_storage_format_by_name(const char *name); const char *ref_storage_format_to_name(enum ref_storage_format ref_storage_format); +/* + * Some of the names specified by refs have special meaning to Git. + * Organize these namespaces in a common 'ref_namespace' array for + * reference from multiple places in the codebase. + */ +struct ref_namespace_info { + const char *ref; + enum decoration_type decoration; + + /* + * If 'exact' is true, then we must match the 'ref' exactly. + * Otherwise, use a prefix match. + * + * 'ref_updated' is for internal use. It represents whether the + * 'ref' value was replaced from its original literal version. + */ + unsigned exact:1, + ref_updated:1; +}; + +enum ref_namespace { + NAMESPACE_HEAD, + NAMESPACE_BRANCHES, + NAMESPACE_TAGS, + NAMESPACE_REMOTE_REFS, + NAMESPACE_STASH, + NAMESPACE_REPLACE, + NAMESPACE_NOTES, + NAMESPACE_PREFETCH, + NAMESPACE_REWRITTEN, + + /* Must be last */ + NAMESPACE__COUNT +}; + +/* See refs.c for the contents of this array. */ +extern struct ref_namespace_info ref_namespace[NAMESPACE__COUNT]; + +/* + * Some ref namespaces can be modified by config values or environment + * variables. Modify a namespace as specified by its ref_namespace key. + */ +void update_ref_namespace(enum ref_namespace namespace, char *ref); + enum ref_transaction_error { /* Default error code */ REF_TRANSACTION_ERROR_GENERIC = -1, @@ -1050,51 +1094,6 @@ struct ref_store *repo_get_submodule_ref_store(struct repository *repo, const char *submodule); struct ref_store *get_worktree_ref_store(const struct worktree *wt); -/* - * Some of the names specified by refs have special meaning to Git. - * Organize these namespaces in a common 'ref_namespace' array for - * reference from multiple places in the codebase. - */ - -struct ref_namespace_info { - const char *ref; - enum decoration_type decoration; - - /* - * If 'exact' is true, then we must match the 'ref' exactly. - * Otherwise, use a prefix match. - * - * 'ref_updated' is for internal use. It represents whether the - * 'ref' value was replaced from its original literal version. - */ - unsigned exact:1, - ref_updated:1; -}; - -enum ref_namespace { - NAMESPACE_HEAD, - NAMESPACE_BRANCHES, - NAMESPACE_TAGS, - NAMESPACE_REMOTE_REFS, - NAMESPACE_STASH, - NAMESPACE_REPLACE, - NAMESPACE_NOTES, - NAMESPACE_PREFETCH, - NAMESPACE_REWRITTEN, - - /* Must be last */ - NAMESPACE__COUNT -}; - -/* See refs.c for the contents of this array. */ -extern struct ref_namespace_info ref_namespace[NAMESPACE__COUNT]; - -/* - * Some ref namespaces can be modified by config values or environment - * variables. Modify a namespace as specified by its ref_namespace key. - */ -void update_ref_namespace(enum ref_namespace namespace, char *ref); - /* * Check whether the provided name names a root reference. This function only * performs a syntactic check. -- GitLab From fea41d664c2b240402bb5fa98d182b520f6ecd4f Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Mon, 19 May 2025 20:50:52 +0200 Subject: [PATCH 4/5] refs --- refs.c | 43 +++++++++++++++++++++++++++++++++++++++++++ refs.h | 10 ++++++++++ 2 files changed, 53 insertions(+) diff --git a/refs.c b/refs.c index dce5c49ca2b..0f4b1e2d479 100644 --- a/refs.c +++ b/refs.c @@ -3314,3 +3314,46 @@ int ref_update_expects_existing_old_ref(struct ref_update *update) return (update->flags & REF_HAVE_OLD) && (!is_null_oid(&update->old_oid) || update->old_target); } + +int refs_for_each(struct ref_store *refs, each_ref_fn fn, void *cb_data, + struct refs_for_each_options *options) +{ + struct ref_iterator *iter; + const char **exclude_patterns = NULL; + const char *prefix = NULL; + int trim = 0, flags = 0; + + if (!refs) + return 0; + + if (options) { + exclude_patterns = options->exclude_patterns; + + if (options->exclude_patterns) + exclude_patterns = options->exclude_patterns; + if (options->prefix) + prefix = options->prefix; + + if (options->namespace == NAMESPACE_HEAD) { + struct object_id oid; + int flag; + + if (refs_resolve_ref_unsafe(refs, "HEAD", RESOLVE_REF_READING, + &oid, &flag)) + return fn("HEAD", NULL, &oid, flag, cb_data); + else + return 0; + } + + if (options->namespace >= 0 && options->namespace <= NAMESPACE__COUNT) + prefix = ref_namespace[options->namespace].ref; + } + + if (!trim && prefix) + trim = strlen(prefix); + + iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns, trim, + flags); + + return do_for_each_ref_iterator(iter, fn, cb_data); +} diff --git a/refs.h b/refs.h index 0634903a7c8..cf9aa514584 100644 --- a/refs.h +++ b/refs.h @@ -379,6 +379,16 @@ struct ref_transaction; typedef int each_ref_fn(const char *refname, const char *referent, const struct object_id *oid, int flags, void *cb_data); +struct refs_for_each_options { + enum ref_namespace namespace; + const char **exclude_patterns; + const char *prefix; + int trim; +}; + +int refs_for_each(struct ref_store *refs, each_ref_fn fn, void *cb_data, + struct refs_for_each_options *options); + /* * The following functions invoke the specified callback function for * each reference indicated. If the function ever returns a nonzero -- GitLab From 9d7f97096879cbe1243ec226e307bfb56ad909db Mon Sep 17 00:00:00 2001 From: Karthik Nayak Date: Wed, 4 Jun 2025 22:04:44 +0200 Subject: [PATCH 5/5] revision --- revision.c | 68 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/revision.c b/revision.c index 2c36a9c179e..48d6b342bb8 100644 --- a/revision.c +++ b/revision.c @@ -1672,9 +1672,9 @@ static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs, cb->wt = NULL; } -static void handle_refs(struct ref_store *refs, - struct rev_info *revs, unsigned flags, - int (*for_each)(struct ref_store *, each_ref_fn, void *)) + +static void handle_refs(struct ref_store *refs, struct rev_info *revs, + unsigned flags, struct refs_for_each_options *options) { struct all_refs_cb cb; @@ -1684,7 +1684,7 @@ static void handle_refs(struct ref_store *refs, } init_all_refs_cb(&cb, revs, flags); - for_each(refs, handle_one_ref, &cb); + refs_for_each(refs, handle_one_ref, &cb, options); } static void handle_one_reflog_commit(struct object_id *oid, void *cb_data) @@ -2742,27 +2742,6 @@ void revision_opts_finish(struct rev_info *revs) } } -static int for_each_bisect_ref(struct ref_store *refs, each_ref_fn fn, - void *cb_data, const char *term) -{ - struct strbuf bisect_refs = STRBUF_INIT; - int status; - strbuf_addf(&bisect_refs, "refs/bisect/%s", term); - status = refs_for_each_fullref_in(refs, bisect_refs.buf, NULL, fn, cb_data); - strbuf_release(&bisect_refs); - return status; -} - -static int for_each_bad_bisect_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) -{ - return for_each_bisect_ref(refs, fn, cb_data, term_bad); -} - -static int for_each_good_bisect_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) -{ - return for_each_bisect_ref(refs, fn, cb_data, term_good); -} - static int handle_revision_pseudo_opt(struct rev_info *revs, const char **argv, int *flags) { @@ -2794,8 +2773,12 @@ static int handle_revision_pseudo_opt(struct rev_info *revs, * register it in the list at the top of handle_revision_opt. */ if (!strcmp(arg, "--all")) { - handle_refs(refs, revs, *flags, refs_for_each_ref); - handle_refs(refs, revs, *flags, refs_head_ref); + struct refs_for_each_options options; + options.namespace = NAMESPACE_HEAD; + + handle_refs(refs, revs, *flags, NULL); + handle_refs(refs, revs, *flags, &options); + if (!revs->single_worktree) { struct all_refs_cb cb; @@ -2804,28 +2787,47 @@ static int handle_revision_pseudo_opt(struct rev_info *revs, } clear_ref_exclusions(&revs->ref_excludes); } else if (!strcmp(arg, "--branches")) { + struct refs_for_each_options options; + options.namespace = NAMESPACE_BRANCHES; + if (revs->ref_excludes.hidden_refs_configured) return error(_("options '%s' and '%s' cannot be used together"), "--exclude-hidden", "--branches"); - handle_refs(refs, revs, *flags, refs_for_each_branch_ref); + handle_refs(refs, revs, *flags, &options); clear_ref_exclusions(&revs->ref_excludes); } else if (!strcmp(arg, "--bisect")) { + struct strbuf bisect_refs = STRBUF_INIT; + struct refs_for_each_options options; + read_bisect_terms(&term_bad, &term_good); - handle_refs(refs, revs, *flags, for_each_bad_bisect_ref); - handle_refs(refs, revs, *flags ^ (UNINTERESTING | BOTTOM), - for_each_good_bisect_ref); + + strbuf_insertf(&bisect_refs, 0, "refs/bisect/%s", term_good); + options.prefix = bisect_refs.buf; + handle_refs(refs, revs, *flags, &options); + + strbuf_insertf(&bisect_refs, 0, "refs/bisect/%s", term_bad); + options.prefix = bisect_refs.buf; + handle_refs(refs, revs, *flags ^ (UNINTERESTING | BOTTOM), &options); + revs->bisect = 1; + strbuf_release(&bisect_refs); } else if (!strcmp(arg, "--tags")) { + struct refs_for_each_options options; + options.namespace = NAMESPACE_TAGS; + if (revs->ref_excludes.hidden_refs_configured) return error(_("options '%s' and '%s' cannot be used together"), "--exclude-hidden", "--tags"); - handle_refs(refs, revs, *flags, refs_for_each_tag_ref); + handle_refs(refs, revs, *flags, &options); clear_ref_exclusions(&revs->ref_excludes); } else if (!strcmp(arg, "--remotes")) { + struct refs_for_each_options options; + options.namespace = NAMESPACE_REMOTE_REFS; + if (revs->ref_excludes.hidden_refs_configured) return error(_("options '%s' and '%s' cannot be used together"), "--exclude-hidden", "--remotes"); - handle_refs(refs, revs, *flags, refs_for_each_remote_ref); + handle_refs(refs, revs, *flags, &options); clear_ref_exclusions(&revs->ref_excludes); } else if ((argcount = parse_long_opt("glob", argv, &optarg))) { struct all_refs_cb cb; -- GitLab