]> git.ipfire.org Git - thirdparty/git.git/commitdiff
add-patch: classify '@' as a synonym for 'HEAD'
authorGhanshyam Thakkar <shyamthakkar001@gmail.com>
Tue, 13 Feb 2024 00:05:29 +0000 (05:35 +0530)
committerJunio C Hamano <gitster@pobox.com>
Tue, 13 Feb 2024 22:12:51 +0000 (14:12 -0800)
Currently, (restore, checkout, reset) commands correctly take '@' as a
synonym for 'HEAD'. However, in patch mode different prompts/messages
are given on command line due to patch mode machinery not considering
'@' to be a synonym for 'HEAD' due to literal string comparison with
the word 'HEAD', and therefore assigning patch_mode_($command)_nothead
and triggering reverse mode (-R in diff-index). The NEEDSWORK comment
suggested comparing commit objects to get around this. However, doing
so would also take a non-checked out branch pointing to the same commit
as HEAD, as HEAD. This would cause confusion to the user.

Therefore, after parsing '@', replace it with 'HEAD' as reasonably
early as possible. This also solves another problem of disparity
between 'git checkout HEAD' and 'git checkout @' (latter detaches at
the HEAD commit and the former does not).

Trade-offs:
- Some of the errors would show the revision argument as 'HEAD' when
  given '@'. This should be fine, as most users who probably use '@'
  would be aware that it is a shortcut for 'HEAD' and most probably
  used to use 'HEAD'. There is also relevant documentation in
  'gitrevisions' manpage about '@' being the shortcut for 'HEAD'. Also,
  the simplicity of the solution far outweighs this cost.

- Consider '@' as a shortcut for 'HEAD' even if 'refs/heads/@' exists
  at a different commit. Naming a branch '@' is an obvious foot-gun and
  many existing commands already take '@' for 'HEAD' even if
  'refs/heads/@' exists at a different commit or does not exist at all
  (e.g. 'git log @', 'git push origin @' etc.). Therefore this is an
  existing assumption and should not be a problem.

Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Phillip Wood <phillip.wood123@gmail.com>
Signed-off-by: Ghanshyam Thakkar <shyamthakkar001@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
add-patch.c
builtin/checkout.c
builtin/reset.c
t/t2016-checkout-patch.sh
t/t2020-checkout-detach.sh
t/t2071-restore-patch.sh
t/t7105-reset-patch.sh

index 79eda168ebb7cdb739720c2c0d16a44484522822..68f525b35cfe650ebf440a1d879a761d55aa02e6 100644 (file)
@@ -1729,14 +1729,6 @@ int run_add_p(struct repository *r, enum add_p_mode mode,
        if (mode == ADD_P_STASH)
                s.mode = &patch_mode_stash;
        else if (mode == ADD_P_RESET) {
-               /*
-                * NEEDSWORK: Instead of comparing to the literal "HEAD",
-                * compare the commit objects instead so that other ways of
-                * saying the same thing (such as "@") are also handled
-                * appropriately.
-                *
-                * This applies to the cases below too.
-                */
                if (!revision || !strcmp(revision, "HEAD"))
                        s.mode = &patch_mode_reset_head;
                else
index a6e30931b5c809da06c751ba6ef43e47a5a3e0ea..067c2519334db3198f4627e58469fd6ad48da16a 100644 (file)
@@ -1224,7 +1224,9 @@ static void setup_new_branch_info_and_source_tree(
        struct tree **source_tree = &opts->source_tree;
        struct object_id branch_rev;
 
-       new_branch_info->name = xstrdup(arg);
+       /* treat '@' as a shortcut for 'HEAD' */
+       new_branch_info->name = !strcmp(arg, "@") ? xstrdup("HEAD") :
+                                                   xstrdup(arg);
        setup_branch_path(new_branch_info);
 
        if (!check_refname_format(new_branch_info->path, 0) &&
index 8390bfe4c487b0c3170045f0601234c1e8278122..f0bf29a4783b81fde5fb97478b974dade7a310d0 100644 (file)
@@ -281,7 +281,9 @@ static void parse_args(struct pathspec *pathspec,
                        verify_filename(prefix, argv[0], 1);
                }
        }
-       *rev_ret = rev;
+
+       /* treat '@' as a shortcut for 'HEAD' */
+       *rev_ret = !strcmp("@", rev) ? "HEAD" : rev;
 
        parse_pathspec(pathspec, 0,
                       PATHSPEC_PREFER_FULL |
index 747eb5563eca8f1df4374f179a08019221ba1fac..c4f9bf09aa4fd8d541a858d55cfbff34e78de1c0 100755 (executable)
@@ -38,26 +38,32 @@ test_expect_success 'git checkout -p with staged changes' '
        verify_state dir/foo index index
 '
 
-test_expect_success 'git checkout -p HEAD with NO staged changes: abort' '
-       set_and_save_state dir/foo work head &&
-       test_write_lines n y n | git checkout -p HEAD &&
-       verify_saved_state bar &&
-       verify_saved_state dir/foo
-'
-
-test_expect_success 'git checkout -p HEAD with NO staged changes: apply' '
-       test_write_lines n y y | git checkout -p HEAD &&
-       verify_saved_state bar &&
-       verify_state dir/foo head head
-'
-
-test_expect_success 'git checkout -p HEAD with change already staged' '
-       set_state dir/foo index index &&
-       # the third n is to get out in case it mistakenly does not apply
-       test_write_lines n y n | git checkout -p HEAD &&
-       verify_saved_state bar &&
-       verify_state dir/foo head head
-'
+for opt in "HEAD" "@"
+do
+       test_expect_success "git checkout -p $opt with NO staged changes: abort" '
+               set_and_save_state dir/foo work head &&
+               test_write_lines n y n | git checkout -p $opt >output &&
+               verify_saved_state bar &&
+               verify_saved_state dir/foo &&
+               test_grep "Discard" output
+       '
+
+       test_expect_success "git checkout -p $opt with NO staged changes: apply" '
+               test_write_lines n y y | git checkout -p $opt >output &&
+               verify_saved_state bar &&
+               verify_state dir/foo head head &&
+               test_grep "Discard" output
+       '
+
+       test_expect_success "git checkout -p $opt with change already staged" '
+               set_state dir/foo index index &&
+               # the third n is to get out in case it mistakenly does not apply
+               test_write_lines n y n | git checkout -p $opt >output &&
+               verify_saved_state bar &&
+               verify_state dir/foo head head &&
+               test_grep "Discard" output
+       '
+done
 
 test_expect_success 'git checkout -p HEAD^...' '
        # the third n is to get out in case it mistakenly does not apply
index 8202ef8c74f639fb53a16b943fb5923ecd4e420e..bce284c2978848967ffe88764e396fad5059df54 100755 (executable)
@@ -45,6 +45,18 @@ test_expect_success 'checkout branch does not detach' '
        check_not_detached
 '
 
+for opt in "HEAD" "@"
+do
+       test_expect_success "checkout $opt no-op/don't detach" '
+               reset &&
+               cat .git/HEAD >expect &&
+               git checkout $opt &&
+               cat .git/HEAD >actual &&
+               check_not_detached &&
+               test_cmp expect actual
+       '
+done
+
 test_expect_success 'checkout tag detaches' '
        reset &&
        git checkout tag &&
index b5c5c0ff7e37ced9b6b50a06e4ec3e30c536cd53..3dc9184b4a59b3f07b2b7ef7755675492ff2d63e 100755 (executable)
@@ -44,13 +44,17 @@ test_expect_success PERL 'git restore -p with staged changes' '
        verify_state dir/foo index index
 '
 
-test_expect_success PERL 'git restore -p --source=HEAD' '
-       set_state dir/foo work index &&
-       # the third n is to get out in case it mistakenly does not apply
-       test_write_lines n y n | git restore -p --source=HEAD &&
-       verify_saved_state bar &&
-       verify_state dir/foo head index
-'
+for opt in "HEAD" "@"
+do
+       test_expect_success PERL "git restore -p --source=$opt" '
+               set_state dir/foo work index &&
+               # the third n is to get out in case it mistakenly does not apply
+               test_write_lines n y n | git restore -p --source=$opt >output &&
+               verify_saved_state bar &&
+               verify_state dir/foo head index &&
+               test_grep "Discard" output
+       '
+done
 
 test_expect_success PERL 'git restore -p --source=HEAD^' '
        set_state dir/foo work index &&
index 05079c7246482cb5a50bf1579914ef810e309cd7..453872c8ba06281bb965bc72e687c7b21138aabd 100755 (executable)
@@ -26,12 +26,16 @@ test_expect_success PERL 'saying "n" does nothing' '
        verify_saved_state bar
 '
 
-test_expect_success PERL 'git reset -p' '
-       test_write_lines n y | git reset -p >output &&
-       verify_state dir/foo work head &&
-       verify_saved_state bar &&
-       test_grep "Unstage" output
-'
+for opt in "HEAD" "@" ""
+do
+       test_expect_success PERL "git reset -p $opt" '
+               set_and_save_state dir/foo work work &&
+               test_write_lines n y | git reset -p $opt >output &&
+               verify_state dir/foo work head &&
+               verify_saved_state bar &&
+               test_grep "Unstage" output
+       '
+done
 
 test_expect_success PERL 'git reset -p HEAD^' '
        test_write_lines n y | git reset -p HEAD^ >output &&