diff mbox series

[v2,4/8] sequencer: treat error reading HEAD as unborn branch

Message ID 20240210074859.552497-5-brianmlyles@gmail.com (mailing list archive)
State New, archived
Headers show
Series None | expand

Commit Message

Brian Lyles Feb. 10, 2024, 7:43 a.m. UTC
When using git-cherry-pick(1) with `--allow-empty` while on an unborn
branch, an error is thrown. This is inconsistent with the same
cherry-pick when `--allow-empty` is not specified.

Treat a failure reading HEAD as an unborn branch in
`is_index_unchanged`. This is consistent with other sequencer logic such
as `do_pick_commit`. When on an unborn branch, use the `empty_tree` as
the tree to compare against.

Signed-off-by: Brian Lyles <brianmlyles@gmail.com>
Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk>
---

This is another new commit that was not present in v1.

See this comment[1] from Phillip for context.

[1]: https://lore.kernel.org/git/b5213705-4cd6-40ef-8c5f-32b214534b8b@gmail.com/


 sequencer.c                   | 36 ++++++++++++++++++++---------------
 t/t3501-revert-cherry-pick.sh | 11 +++++++++++
 2 files changed, 32 insertions(+), 15 deletions(-)

Comments

Phillip Wood Feb. 22, 2024, 4:34 p.m. UTC | #1
Hi Brian

On 10/02/2024 07:43, Brian Lyles wrote:
> When using git-cherry-pick(1) with `--allow-empty` while on an unborn
> branch, an error is thrown. This is inconsistent with the same
> cherry-pick when `--allow-empty` is not specified.
> 
> Treat a failure reading HEAD as an unborn branch in
> `is_index_unchanged`. This is consistent with other sequencer logic such
> as `do_pick_commit`. When on an unborn branch, use the `empty_tree` as
> the tree to compare against.
> 
> Signed-off-by: Brian Lyles <brianmlyles@gmail.com>
> Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk>
> ---
> 
> This is another new commit that was not present in v1.
> 
> See this comment[1] from Phillip for context.
> 
> [1]: https://lore.kernel.org/git/b5213705-4cd6-40ef-8c5f-32b214534b8b@gmail.com/

Thanks for fixing this and adding a test, I've left a few small comments 
below.

>   static int is_index_unchanged(struct repository *r)
>   {
> -	struct object_id head_oid, *cache_tree_oid;
> +	struct object_id head_oid, *cache_tree_oid, head_tree_oid;

I think we can make `head_tree_oid` a pointer like cache_tree_oid and 
avoid de-referencing `the_hash_algo->empty_tree` and the return value of 
`get_commit_tree_oid()`. I think the only reason to copy it would be if 
the underlying object had a shorter lifetime than `head_tree_oid` but I 
don't think that's the case.

> +test_expect_success 'cherry-pick on unborn branch with --allow-empty' '
> +	git checkout main &&

I'm a bit confused by this - are we already on the branch "unborn" and 
so need to move away from it to delete it?

> +	git branch -D unborn &&
> +	git checkout --orphan unborn &&
> +	git rm --cached -r . &&
> +	rm -rf * &&

"git switch --orphan" leaves us with an empty index and working copy 
without having to remove the files ourselves.

> +	git cherry-pick initial --allow-empty &&
> +	git diff --quiet initial &&

I'd drop "--quiet" here as it makes debugging easier if we can see the 
diff if the test fails.

> +	test_cmp_rev ! initial HEAD
> +'

Best Wishes

Phillip
Brian Lyles Feb. 23, 2024, 5:28 a.m. UTC | #2
On Thu, Feb 22, 2024 at 10:34 AM <phillip.wood123@gmail.com> wrote:

>>   static int is_index_unchanged(struct repository *r)
>>   {
>> -	struct object_id head_oid, *cache_tree_oid;
>> +	struct object_id head_oid, *cache_tree_oid, head_tree_oid;
> 
> I think we can make `head_tree_oid` a pointer like cache_tree_oid and 
> avoid de-referencing `the_hash_algo->empty_tree` and the return value of 
> `get_commit_tree_oid()`. I think the only reason to copy it would be if 
> the underlying object had a shorter lifetime than `head_tree_oid` but I 
> don't think that's the case.

Makes sense -- I'll update this in v3.

>> +test_expect_success 'cherry-pick on unborn branch with --allow-empty' '
>> +	git checkout main &&
> 
> I'm a bit confused by this - are we already on the branch "unborn" and 
> so need to move away from it to delete it?

Yes, the previous test leaves us on that branch. In v3, I will update
this to instead just use `git checkout --detach`, as that does seem a
little less confusing than switching to some other branch that is only
relevant because it's not `unborn`. If there is a cleaner way to do
this, I'd be happy to switch to it.

>> +	git branch -D unborn &&
>> +	git checkout --orphan unborn &&
>> +	git rm --cached -r . &&
>> +	rm -rf * &&
> 
> "git switch --orphan" leaves us with an empty index and working copy 
> without having to remove the files ourselves.

Thanks for pointing this out. Using git-switch(1) here instead of
git-checkout(1) allows us to drop the `rm -rf *` call form both the
existing 'cherry-pick on unborn branch' test as well as my new test. It
appears that the `git rm --cached -r .` call is still necessary in the
existing test.

>> +	git cherry-pick initial --allow-empty &&
>> +	git diff --quiet initial &&
>
> I'd drop "--quiet" here as it makes debugging easier if we can see the 
> diff if the test fails.

This makes sense. In v3, I will update this new test as well as the
existing test to not use `--quiet`.

Combining the above suggestions, here's the version of the existing and
new tests that I intend to use in v3. Let me know if this isn't what you
had in mind!

    test_expect_success 'cherry-pick on unborn branch' '
    	git switch --orphan unborn &&
    	git rm --cached -r . &&
    	git cherry-pick initial &&
    	git diff initial &&
    	test_cmp_rev ! initial HEAD
    '
    
    test_expect_success 'cherry-pick on unborn branch with --allow-empty' '
    	git checkout --detach &&
    	git branch -D unborn &&
    	git switch --orphan unborn &&
    	git cherry-pick initial --allow-empty &&
    	git diff initial &&
    	test_cmp_rev ! initial HEAD
    '
Phillip Wood Feb. 25, 2024, 4:57 p.m. UTC | #3
Hi Brian

On 23/02/2024 05:28, Brian Lyles wrote:
> On Thu, Feb 22, 2024 at 10:34 AM <phillip.wood123@gmail.com> wrote:
>>> +test_expect_success 'cherry-pick on unborn branch with --allow-empty' '
>>> +	git checkout main &&
>>
>> I'm a bit confused by this - are we already on the branch "unborn" and
>> so need to move away from it to delete it?
> 
> Yes, the previous test leaves us on that branch. In v3, I will update
> this to instead just use `git checkout --detach`, as that does seem a
> little less confusing than switching to some other branch that is only
> relevant because it's not `unborn`. If there is a cleaner way to do
> this, I'd be happy to switch to it.

I think "git checkout --detach" is probably the best we can do. It would 
be nice to be able to do "git switch -C --orphan unborn" but "-C" does 
not work with "--orphan"

>>> +	git branch -D unborn &&
>>> +	git checkout --orphan unborn &&
>>> +	git rm --cached -r . &&
>>> +	rm -rf * &&
>>
>> "git switch --orphan" leaves us with an empty index and working copy
>> without having to remove the files ourselves.
> 
> Thanks for pointing this out. Using git-switch(1) here instead of
> git-checkout(1) allows us to drop the `rm -rf *` call form both the
> existing 'cherry-pick on unborn branch' test as well as my new test. It
> appears that the `git rm --cached -r .` call is still necessary in the
> existing test.

It looks like the previous test 'revert forbidden on dirty working tree' 
fails to clean up properly and so "git switch" is carrying the 
uncommitted changes across to the new orphan branch. I think that "git 
switch --discard-changes --orphan unborn" ought to clean the worktree 
and index but it doesn't seem to work.

>>> +	git cherry-pick initial --allow-empty &&
>>> +	git diff --quiet initial &&
>>
>> I'd drop "--quiet" here as it makes debugging easier if we can see the
>> diff if the test fails.
> 
> This makes sense. In v3, I will update this new test as well as the
> existing test to not use `--quiet`.
> 
> Combining the above suggestions, here's the version of the existing and
> new tests that I intend to use in v3. Let me know if this isn't what you
> had in mind!
> 
>      test_expect_success 'cherry-pick on unborn branch' '
>      	git switch --orphan unborn &&
>      	git rm --cached -r . &&
>      	git cherry-pick initial &&
>      	git diff initial &&
>      	test_cmp_rev ! initial HEAD
>      '
>      
>      test_expect_success 'cherry-pick on unborn branch with --allow-empty' '
>      	git checkout --detach &&
>      	git branch -D unborn &&
>      	git switch --orphan unborn &&
>      	git cherry-pick initial --allow-empty &&
>      	git diff initial &&
>      	test_cmp_rev ! initial HEAD
>      '

These look good

Thanks

Phillip
diff mbox series

Patch

diff --git a/sequencer.c b/sequencer.c
index 3cc88d8a80..b1b19512de 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -769,30 +769,36 @@  static struct object_id *get_cache_tree_oid(struct index_state *istate)
 
 static int is_index_unchanged(struct repository *r)
 {
-	struct object_id head_oid, *cache_tree_oid;
+	struct object_id head_oid, *cache_tree_oid, head_tree_oid;
 	struct commit *head_commit;
 	struct index_state *istate = r->index;
 
-	if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
-		return error(_("could not resolve HEAD commit"));
+	if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL)) {
+		/*
+		 * Treat an error reading HEAD as an unborn branch.
+		 */
+		head_tree_oid = *the_hash_algo->empty_tree;
+	} else {
+		head_commit = lookup_commit(r, &head_oid);
 
-	head_commit = lookup_commit(r, &head_oid);
+		/*
+		 * If head_commit is NULL, check_commit, called from
+		 * lookup_commit, would have indicated that head_commit is not
+		 * a commit object already.  repo_parse_commit() will return failure
+		 * without further complaints in such a case.  Otherwise, if
+		 * the commit is invalid, repo_parse_commit() will complain.  So
+		 * there is nothing for us to say here.  Just return failure.
+		 */
+		if (repo_parse_commit(r, head_commit))
+			return -1;
 
-	/*
-	 * If head_commit is NULL, check_commit, called from
-	 * lookup_commit, would have indicated that head_commit is not
-	 * a commit object already.  repo_parse_commit() will return failure
-	 * without further complaints in such a case.  Otherwise, if
-	 * the commit is invalid, repo_parse_commit() will complain.  So
-	 * there is nothing for us to say here.  Just return failure.
-	 */
-	if (repo_parse_commit(r, head_commit))
-		return -1;
+		head_tree_oid = *get_commit_tree_oid(head_commit);
+	}
 
 	if (!(cache_tree_oid = get_cache_tree_oid(istate)))
 		return -1;
 
-	return oideq(cache_tree_oid, get_commit_tree_oid(head_commit));
+	return oideq(cache_tree_oid, &head_tree_oid);
 }
 
 static int write_author_script(const char *message)
diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh
index aeab689a98..390e0ed186 100755
--- a/t/t3501-revert-cherry-pick.sh
+++ b/t/t3501-revert-cherry-pick.sh
@@ -112,6 +112,17 @@  test_expect_success 'cherry-pick on unborn branch' '
 	test_cmp_rev ! initial HEAD
 '
 
+test_expect_success 'cherry-pick on unborn branch with --allow-empty' '
+	git checkout main &&
+	git branch -D unborn &&
+	git checkout --orphan unborn &&
+	git rm --cached -r . &&
+	rm -rf * &&
+	git cherry-pick initial --allow-empty &&
+	git diff --quiet initial &&
+	test_cmp_rev ! initial HEAD
+'
+
 test_expect_success 'cherry-pick "-" to pick from previous branch' '
 	git checkout unborn &&
 	test_commit to-pick actual content &&