Re: [PATCH v3 0/3] branch: operations on orphan branches
- From
Rubén Justo <rjusto@gmail.com>
- Date
- Feb 8, 2023, 00:35 UTC
- Message-ID
- <1450c94f-b0e4-a424-edd4-8c88b72febf5@gmail.com>
- In-Reply-To
- <230207.86cz6l501v.gmgdl@evledraar.gmail.com>
On 07-feb-2023 09:33:39, Ævar Arnfjörð Bjarmason wrote:
Thank you reviewing this.
Show 21 quoted lines
> > On Tue, Feb 07 2023, Rubén Justo wrote: > > > Avoid some confusing errors operating with orphan branches when > > working with worktrees. > > > > Changes from v2: > > > > - Renamed "ishead_and_reject_rebase_or_bisect_branch()" to > > "die_if_branch_is_being_rebased_or_bisected()" > > Looking this over holistically, I think this is a great example of where > factoring something out into a function is just making readbility > worse. This function is only used in copy_or_rename_branch(), and the > overloaded name & semantics are making things quite confusing. > > Whereas if we just start by pulling it into its only caller I think this > gets much better, at the end of this message is a diff-on-top these > three patches where I do that (I kept the "target" variable to minimize > the diff with the move detection, but we probalby want the strbuf > directly instead).
I'm sorry, but I don't agree. copy_or_rename_branch() already does some heterogeneous things. IMHO, making it also iterate worktrees makes things worse, in terms of readability. I could agree with maybe the function die_if_branch_is_being_rebased_or_bisected() could be part of a more general use, maybe something in worktree.h.
Show 7 quoted lines
> > A proposed name "die_if_branch_is_is_use()" has not been used because > > it could lead to confusion. We don't yet support copying or renaming > > a branch being rebased or bisected, but we do under other uses. > > Another thing that I think could be improved in this series is if you > skip the refactoring-while-at-it of changing the existing > "if/if/die/die" into a "if/die/?:".
I'm sorry, but I don't agree with this reasoning either. The ternary op here allows the code to be more clear, IMHO, in the intention: there is no conditional die() or error(), the conditional is for the message.
Show 210 quoted lines
>
> In the below diff I have that proposed change on top, but this snippet
> here shows the diff to "origin/master":
>
> @@ -806,7 +806,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
>
> strbuf_addf(&branch_ref, "refs/heads/%s", branch_name);
> if (!ref_exists(branch_ref.buf))
> - error((!argc || !strcmp(head, branch_name))
> + error((!argc || branch_checked_out(branch_ref.buf))
> ? _("No commit on branch '%s' yet.")
> : _("No branch named '%s'."),
> branch_name);
> @@ -851,10 +851,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
> }
>
> if (!ref_exists(branch->refname)) {
> - if (!argc || !strcmp(head, branch->name))
> + if (!argc || branch_checked_out(branch->refname))
> die(_("No commit on branch '%s' yet."), branch->name);
> die(_("branch '%s' does not exist"), branch->name);
> }
>
> I.e. your refactoring of this in 2/3 turns out to in the end have just
> been inflating the code change, for no functional benefit.
>
> I wouldn't mind if this were in some pre-cleanup, or if it actually made
> the code easier to read, but IMO this pattern of using a ternary to
> select the format to "error" or "die" makes things worse for
> readability. It's a few bytes less code, but makes things harder to follow overall.
>
> And even if you disagree with that as far as the end state is concerned,
> I think it's unarguable that it makes the 2/3 harder to follow, since
> it's sticking a refactoring that's not neede dfor the end-goal here into
> an otherwise functional change.
>
> I'm aware that some of the code in the context uses this pattern, and
> you probably changed the "if" block you modified to be consistent with
> the code above, but I think in this case it's better not to follow the
> existing style (which is used in that function, but is a rare exception
> overall in this codebase).
>
> The diff-on-top, mentioned above:
>
> == BEGIN
>
> diff --git a/builtin/branch.c b/builtin/branch.c
> index 7efda622241..dc7a3e3dde1 100644
> --- a/builtin/branch.c
> +++ b/builtin/branch.c
> @@ -486,45 +486,16 @@ static void print_current_branch_name(void)
> die(_("HEAD (%s) points outside of refs/heads/"), refname);
> }
>
> -/*
> - * Dies if the specified branch is being rebased or bisected. Otherwise returns
> - * 0 or, if the branch is HEAD in any worktree, returns 1. If the branch is HEAD
> - * and also orphan, returns 2.
> - */
> -static int die_if_branch_is_being_rebased_or_bisected(const char *target)
> -{
> - struct worktree **worktrees = get_worktrees();
> - int i, ret = 0;
> -
> - for (i = 0; worktrees[i]; i++) {
> - struct worktree *wt = worktrees[i];
> -
> - if (wt->head_ref && !strcmp(target, wt->head_ref))
> - ret = is_null_oid(&wt->head_oid) ? 2 : 1;
> -
> - if (!wt->is_detached)
> - continue;
> -
> - if (is_worktree_being_rebased(wt, target))
> - die(_("Branch %s is being rebased at %s"),
> - target, wt->path);
> -
> - if (is_worktree_being_bisected(wt, target))
> - die(_("Branch %s is being bisected at %s"),
> - target, wt->path);
> - }
> -
> - free_worktrees(worktrees);
> - return ret;
> -}
> -
> static void copy_or_rename_branch(const char *oldname, const char *newname, int copy, int force)
> {
> struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
> struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
> const char *interpreted_oldname = NULL;
> const char *interpreted_newname = NULL;
> - int recovery = 0, oldref_is_head, oldref_is_orphan;
> + int recovery = 0, oldref_is_head = 0, oldref_is_orphan = 0;
> + struct worktree **worktrees;
> + int i;
> + const char *target;
>
> if (strbuf_check_branch_ref(&oldref, oldname)) {
> /*
> @@ -537,8 +508,29 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
> die(_("Invalid branch name: '%s'"), oldname);
> }
>
> - oldref_is_head = die_if_branch_is_being_rebased_or_bisected(oldref.buf);
> - oldref_is_orphan = (oldref_is_head > 1);
> + worktrees = get_worktrees();
> + target = oldref.buf;
> + for (i = 0; worktrees[i]; i++) {
> + struct worktree *wt = worktrees[i];
> +
> + if (wt->head_ref && !strcmp(target, wt->head_ref)) {
> + oldref_is_head = 1;
> + if (is_null_oid(&wt->head_oid))
> + oldref_is_orphan = 1;
> + }
> +
> + if (!wt->is_detached)
> + continue;
> +
> + if (is_worktree_being_rebased(wt, target))
> + die(_("Branch %s is being rebased at %s"),
> + target, wt->path);
> +
> + if (is_worktree_being_bisected(wt, target))
> + die(_("Branch %s is being bisected at %s"),
> + target, wt->path);
> + }
> + free_worktrees(worktrees);
>
> if ((copy || !oldref_is_head) &&
> (oldref_is_orphan || !ref_exists(oldref.buf)))
> @@ -858,10 +850,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
> die(_("no such branch '%s'"), argv[0]);
> }
>
> - if (!ref_exists(branch->refname))
> - die((!argc || branch_checked_out(branch->refname))
> - ? _("No commit on branch '%s' yet.")
> - : _("branch '%s' does not exist"), branch->name);
> + if (!ref_exists(branch->refname)) {
> + if (!argc || branch_checked_out(branch->refname))
> + die(_("No commit on branch '%s' yet."), branch->name);
> + die(_("branch '%s' does not exist"), branch->name);
> + }
> +
>
> dwim_and_setup_tracking(the_repository, branch->name,
> new_upstream, BRANCH_TRACK_OVERRIDE,
>
> == END
>
> P.S. if I were refactoring those ?: for style in that function I'd
> probably go for this on-top. The N_() followed by _() pattern is
> probably overdoing it, but included to show that one way out of this
> sort of thing with i18n is that you can pre-mark the string with N_(),
> then use it with _() to emit the message (right now the code uses
> "copy?" over "copy ?" instead to align them):
>
> diff --git a/builtin/branch.c b/builtin/branch.c
> index dc7a3e3dde1..e42f9bc4900 100644
> --- a/builtin/branch.c
> +++ b/builtin/branch.c
> @@ -805,31 +805,35 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
> }
>
> strbuf_addf(&branch_ref, "refs/heads/%s", branch_name);
> - if (!ref_exists(branch_ref.buf))
> - error((!argc || branch_checked_out(branch_ref.buf))
> - ? _("No commit on branch '%s' yet.")
> - : _("No branch named '%s'."),
> - branch_name);
> - else if (!edit_branch_description(branch_name))
> + if (!ref_exists(branch_ref.buf)) {
> + if (!argc || branch_checked_out(branch_ref.buf))
> + error(_("No commit on branch '%s' yet."),
> + branch_name);
> + else
> + error(_("No branch named '%s'."), branch_name);
> + } else if (!edit_branch_description(branch_name)) {
> ret = 0; /* happy */
> + }
>
> strbuf_release(&branch_ref);
> strbuf_release(&buf);
>
> return ret;
> } else if (copy || rename) {
> + static const char *cannot_copy = N_("cannot copy the current branch while not on any.");
> + static const char *cannot_rename = N_("cannot rename the current branch while not on any.");
> if (!argc)
> die(_("branch name required"));
> else if ((argc == 1) && filter.detached)
> - die(copy? _("cannot copy the current branch while not on any.")
> - : _("cannot rename the current branch while not on any."));
> + die("%s", copy ? _(cannot_copy) : _(cannot_rename));
> else if (argc == 1)
> copy_or_rename_branch(head, argv[0], copy, copy + rename > 1);
> else if (argc == 2)
> copy_or_rename_branch(argv[0], argv[1], copy, copy + rename > 1);
> + else if (copy)
> + die(_("too many branches for a copy operation"));
> else
> - die(copy? _("too many branches for a copy operation")
> - : _("too many arguments for a rename operation"));
> + die(_("too many arguments for a rename operation"));
> } else if (new_upstream) {
> struct branch *branch;
> struct strbuf buf = STRBUF_INIT;
>