On 6/7/2022 10:14 PM, Derrick Stolee wrote: > On 6/7/2022 6:09 PM, Junio C Hamano wrote: >> >> I wonder if we rather want to rewrite find_shared_symref() *not* to >> take the target parameter at all, and instead introduce a new >> function that takes a worktree, and report the branch that is >> checked out (or being operated on via rebase or bisect). Then we >> can >> >> - create a strset out of its result, i.e. set of branches that >> should not be touched; >> >> - iterate over refs that point into the history being rebased >> (using for_each_decoration()), and consult that strset to see if >> any of them is being rewritten. >> >> With the API of find_shared_symref(), we'd need to iterate over all >> worktrees for each decoration. With such a restructuring, we can >> iterate over all worktrees just once, and match the result with >> decoration, so the problem becomes O(N)+O(M) and not O(N*M) for >> number of worktrees N and number of decorations M. > > Yes, that's a good plan. I'll take a look. Here is a fixup for this patch that should work. I'll put it in v3. diff --git a/branch.c b/branch.c index 2e6419cdfa5..514212f5619 100644 --- a/branch.c +++ b/branch.c @@ -10,6 +10,7 @@ #include "worktree.h" #include "submodule-config.h" #include "run-command.h" +#include "strmap.h" struct tracking { struct refspec_item spec; @@ -369,17 +370,44 @@ int validate_branchname(const char *name, struct strbuf *ref) return ref_exists(ref->buf); } -int branch_checked_out(const char *refname, char **path) +static int initialized_checked_out_branches; +static struct strmap current_checked_out_branches = STRMAP_INIT; + +static void prepare_checked_out_branches(void) { - struct worktree **worktrees = get_worktrees(); - const struct worktree *wt = find_shared_symref(worktrees, "HEAD", refname); - int result = wt && !wt->is_bare; + int i = 0; + struct worktree **worktrees; + + if (initialized_checked_out_branches) + return; + initialized_checked_out_branches = 1; + + worktrees = get_worktrees(); + + while (worktrees[i]) { + struct worktree *wt = worktrees[i]; - if (result && path) - *path = xstrdup(wt->path); + if (!wt->is_bare && wt->head_ref) + strmap_put(¤t_checked_out_branches, + wt->head_ref, + wt->path); + + i++; + } free_worktrees(worktrees); - return result; +} + +int branch_checked_out(const char *refname, char **path) +{ + const char *path_in_set; + prepare_checked_out_branches(); + + path_in_set = strmap_get(¤t_checked_out_branches, refname); + if (path_in_set && path) + *path = xstrdup(path_in_set); + + return !!path_in_set; } /* >> There also was another topic >> >> https://lore.kernel.org/git/pull.1266.v2.git.git.1652690501963.gitgitgadget@xxxxxxxxx/ >> >> that was triggered by find_shared_symref() being relatively >> heavy-weight, which suggests a more involved refactoring. The patch there was this: diff --git a/builtin/fetch.c b/builtin/fetch.c index e3791f09ed5..eeee5ac8f15 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1440,6 +1440,7 @@ static void check_not_current_branch(struct ref *ref_map, const struct worktree *wt; for (; ref_map; ref_map = ref_map->next) if (ref_map->peer_ref && + starts_with(ref_map->peer_ref->name, "refs/heads/") && (wt = find_shared_symref(worktrees, "HEAD", ref_map->peer_ref->name)) && !wt->is_bare) And there is another use of find_shared_symref() in the same file, allowing us to do the following: diff --git a/builtin/fetch.c b/builtin/fetch.c index ac29c2b1ae3..3933c482839 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -885,7 +885,7 @@ static int update_local_ref(struct ref *ref, struct worktree **worktrees) { struct commit *current = NULL, *updated; - const struct worktree *wt; + char *path = NULL; const char *pretty_ref = prettify_refname(ref->name); int fast_forward = 0; @@ -900,17 +900,17 @@ static int update_local_ref(struct ref *ref, } if (!update_head_ok && - (wt = find_shared_symref(worktrees, "HEAD", ref->name)) && - !wt->is_bare && !is_null_oid(&ref->old_oid)) { + !is_null_oid(&ref->old_oid) && + branch_checked_out(ref->name, &path)) { /* * If this is the head, and it's not okay to update * the head, and the old value of the head isn't empty... */ format_display(display, '!', _("[rejected]"), - wt->is_current ? - _("can't fetch in current branch") : - _("checked out in another worktree"), + path ? _("can't fetch in current branch") : + _("checked out in another worktree"), remote, pretty_ref, summary_width); + free(path); return 1; } @@ -1437,16 +1437,14 @@ static int prune_refs(struct refspec *rs, static void check_not_current_branch(struct ref *ref_map, struct worktree **worktrees) { - const struct worktree *wt; + char *path; for (; ref_map; ref_map = ref_map->next) if (ref_map->peer_ref && starts_with(ref_map->peer_ref->name, "refs/heads/") && - (wt = find_shared_symref(worktrees, "HEAD", - ref_map->peer_ref->name)) && - !wt->is_bare) + branch_checked_out(ref_map->peer_ref->name, &path)) die(_("refusing to fetch into branch '%s' " "checked out at '%s'"), - ref_map->peer_ref->name, wt->path); + ref_map->peer_ref->name, path); } static int truncate_fetch_head(void) I can extract these as patches in their own series if we think that's something we worth fast-tracking. Thanks, -Stolee