git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
From: "Ævar Arnfjörð Bjarmason" <avarab@gmail.com>
To: John Cai via GitGitGadget <gitgitgadget@gmail.com>
Cc: git@vger.kernel.org, Phillip Wood <phillip.wood123@gmail.com>,
	Junio C Hamano <gitster@pobox.com>,
	John Cai <johncai86@gmail.com>
Subject: Re: [PATCH v3 2/2] rebase: set REF_HEAD_DETACH in checkout_up_to_date()
Date: Thu, 17 Mar 2022 14:42:53 +0100	[thread overview]
Message-ID: <220317.86r170d6zs.gmgdl@evledraar.gmail.com> (raw)
In-Reply-To: <bd1c9537ffc503707690ed173b9e6e808d58ce5d.1647487001.git.gitgitgadget@gmail.com>


On Thu, Mar 17 2022, John Cai via GitGitGadget wrote:

> From: John Cai <johncai86@gmail.com>
>
> Fixes a bug whereby rebase updates the deferenced reference HEAD points
> to instead of HEAD directly.
>
> If HEAD is on main and if the following is a fast-forward operation,
>
> git rebase $(git rev-parse main) $(git rev-parse topic)
>
> Instead of HEAD being set to $(git rev-parse topic), rebase erroneously
> dereferences HEAD and sets main to $(git rev-parse topic). See [1] for
> the original bug report.
>
> The callstack from checkout_up_to_date() is the following:
>
> cmd_rebase() -> checkout_up_to_date() -> reset_head() -> update_refs()
>  -> update_ref()
>
> When <branch> is not a valid branch but a sha, rebase sets the head_name

..but an OID...

> of rebase_options to NULL. This value gets passed down this call chain
> through the branch member of reset_head_opts also getting set to NULL
> all the way to update_refs(). update_refs() checks ropts.branch to
> decide whether or not to switch brancheds. If ropts.branch is NULL, it

brancheds -> branches.

And maybe a new paragraph before "update_refs()"? I.e. "\n\nThen
update_refs() checks..." ?

> calls update_ref() to update HEAD. At this point however, from rebase's
> point of view, we want a detached HEAD. But, since checkout_up_to_date()
> does not set the RESET_HEAD_DETACH flag, the update_ref() call will
> deference HEAD and update the branch its pointing to, which in the above
> example is main.
>
> The correct behavior is that git rebase should update HEAD to $(git
> rev-parse topic) without dereferencing it.
>
> Fix this bug by adding the RESET_HEAD_DETACH flag in checkout_up_to_date
> if <branch> is not a valid branch. so that once reset_head() calls
> update_refs(), it calls update_ref() with REF_NO_DEREF which updates
> HEAD directly intead of deferencing it and updating the branch that HEAD
> points to.

But on the "tell" v.s. show... (more below)...
>
> Also add a test to ensure this behavior.
>
> 1. https://lore.kernel.org/git/xmqqsfrpbepd.fsf@gitster.g/
>
> Reported-by: Michael McClimon <michael@mcclimon.org>
> Signed-off-by: John Cai <johncai86@gmail.com>
> ---
>  builtin/rebase.c  | 5 ++++-
>  t/t3400-rebase.sh | 9 +++++++++
>  2 files changed, 13 insertions(+), 1 deletion(-)
>
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index b29ad2b65e7..5ae7fa2a169 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -827,8 +827,11 @@ static int checkout_up_to_date(struct rebase_options *options)
>  		    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
>  		    options->switch_to);
>  	ropts.oid = &options->orig_head;
> -	ropts.branch = options->head_name;
>  	ropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
> +	if (options->head_name)
> +		ropts.branch = options->head_name;
> +	else
> +		ropts.flags |=  RESET_HEAD_DETACH;
>  	ropts.head_msg = buf.buf;
>  	if (reset_head(the_repository, &ropts) < 0)
>  		ret = error(_("could not switch to %s"), options->switch_to);

In this case a smaller change of:

    if (!ropts.branch)
		ropts.flags |=  RESET_HEAD_DETACH;

will do the same.

I wonder if just converting it to a designated initializer while we're
at it (or a pre-cleanup commit) would be better, i.e.:

	
	diff --git a/builtin/rebase.c b/builtin/rebase.c
	index 5ae7fa2a169..bf4fd4d2920 100644
	--- a/builtin/rebase.c
	+++ b/builtin/rebase.c
	@@ -820,18 +820,18 @@ static int rebase_config(const char *var, const char *value, void *data)
	 static int checkout_up_to_date(struct rebase_options *options)
	 {
	 	struct strbuf buf = STRBUF_INIT;
	-	struct reset_head_opts ropts = { 0 };
	+	struct reset_head_opts ropts = {
	+		.oid = &options->orig_head,
	+		.branch = options->head_name,
	+		.flags = (RESET_HEAD_RUN_POST_CHECKOUT_HOOK |
	+			  (options->head_name ? 0 : RESET_HEAD_DETACH)),
	+	};
	 	int ret = 0;
	 
	 	strbuf_addf(&buf, "%s: checkout %s",
	 		    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
	 		    options->switch_to);
	-	ropts.oid = &options->orig_head;
	-	ropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
	-	if (options->head_name)
	-		ropts.branch = options->head_name;
	-	else
	-		ropts.flags |=  RESET_HEAD_DETACH;
	+
	 	ropts.head_msg = buf.buf;
	 	if (reset_head(the_repository, &ropts) < 0)
	 		ret = error(_("could not switch to %s"), options->switch_to);

But in any case the functionality will be the same, so this is just
bikeshedding.
	
> diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
> index 0643d015255..d5a8ee39fc4 100755
> --- a/t/t3400-rebase.sh
> +++ b/t/t3400-rebase.sh
> @@ -394,6 +394,15 @@ test_expect_success 'switch to branch not checked out' '
>  	git rebase main other
>  '
>  
> +test_expect_success 'switch to non-branch detaches HEAD' '
> +	git checkout main &&
> +	old_main=$(git rev-parse HEAD) &&
> +	git rebase First Second^0 &&
> +	test_cmp_rev HEAD Second &&
> +	test_cmp_rev main $old_main &&
> +	test_must_fail git symbolic-ref HEAD
> +'
> +
>  test_expect_success 'refuse to switch to branch checked out elsewhere' '
>  	git checkout main &&
>  	git worktree add wt &&

I think it's *much* easier to review these sorts of changes where
there's a preceding commit that positively asserts what we do now, and
we'll then in the "fix" commit change the behavior.

So more "show" v.s. "tell".

I.e. in this case do the "test_cmp_rev" to the "wrong" tip with a TODO
comment or whatever, then in the fix just adjust it, then it's clear
what we had before/after.

  reply	other threads:[~2022-03-17 13:49 UTC|newest]

Thread overview: 41+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-03-11  5:05 [PATCH] rebase: set REF_HEAD_DETACH in checkout_up_to_date() John Cai via GitGitGadget
2022-03-11  5:33 ` Junio C Hamano
2022-03-11  5:55 ` Junio C Hamano
2022-03-11 14:14   ` John Cai
2022-03-11 15:05 ` Phillip Wood
2022-03-11 15:28   ` John Cai
2022-03-11 19:42   ` John Cai
2022-03-11 21:31     ` Phillip Wood
2022-03-11 17:24 ` [PATCH v2 0/2] rebase: update HEAD when is an oid John Cai via GitGitGadget
2022-03-11 17:24   ` [PATCH v2 1/2] rebase: use test_commit helper in setup John Cai via GitGitGadget
2022-03-13  7:50     ` Junio C Hamano
2022-03-14 10:52       ` Phillip Wood
2022-03-14 21:47         ` Junio C Hamano
2022-03-11 17:24   ` [PATCH v2 2/2] rebase: set REF_HEAD_DETACH in checkout_up_to_date() John Cai via GitGitGadget
2022-03-13  7:58     ` Junio C Hamano
2022-03-14 10:54       ` Phillip Wood
2022-03-14 14:05         ` Phillip Wood
2022-03-14 14:11       ` John Cai
2022-03-14 14:25         ` Phillip Wood
2022-03-17  3:16   ` [PATCH v3 0/2] rebase: update HEAD when is an oid John Cai via GitGitGadget
2022-03-17  3:16     ` [PATCH v3 1/2] rebase: use test_commit helper in setup John Cai via GitGitGadget
2022-03-17 13:37       ` Ævar Arnfjörð Bjarmason
2022-03-17  3:16     ` [PATCH v3 2/2] rebase: set REF_HEAD_DETACH in checkout_up_to_date() John Cai via GitGitGadget
2022-03-17 13:42       ` Ævar Arnfjörð Bjarmason [this message]
2022-03-17 15:34         ` Junio C Hamano
2022-03-17 19:53     ` [PATCH v4 0/3] rebase: update HEAD when is an oid John Cai via GitGitGadget
2022-03-17 19:53       ` [PATCH v4 1/3] rebase: test showing bug in rebase with non-branch John Cai via GitGitGadget
2022-03-17 21:10         ` Junio C Hamano
2022-03-17 21:37           ` Junio C Hamano
2022-03-17 22:44           ` John Cai
2022-03-17 19:53       ` [PATCH v4 2/3] rebase: use test_commit helper in setup John Cai via GitGitGadget
2022-03-18 11:14         ` Phillip Wood
2022-03-18 13:06           ` John Cai
2022-03-17 19:53       ` [PATCH v4 3/3] rebase: set REF_HEAD_DETACH in checkout_up_to_date() John Cai via GitGitGadget
2022-03-17 21:36         ` Junio C Hamano
2022-03-18  0:30           ` John Cai
2022-03-18 13:54       ` [PATCH v5 0/2] rebase: update HEAD when is an oid John Cai via GitGitGadget
2022-03-18 13:54         ` [PATCH v5 1/2] rebase: use test_commit helper in setup John Cai via GitGitGadget
2022-03-18 16:51           ` Junio C Hamano
2022-03-18 13:54         ` [PATCH v5 2/2] rebase: set REF_HEAD_DETACH in checkout_up_to_date() John Cai via GitGitGadget
2022-03-18 16:55         ` [PATCH v5 0/2] rebase: update HEAD when is an oid Junio C Hamano

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: http://vger.kernel.org/majordomo-info.html

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=220317.86r170d6zs.gmgdl@evledraar.gmail.com \
    --to=avarab@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=gitgitgadget@gmail.com \
    --cc=gitster@pobox.com \
    --cc=johncai86@gmail.com \
    --cc=phillip.wood123@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).