git: allow alias-shadowing deprecated builtins

git-whatchanged(1) is deprecated and you need to pass
`--i-still-use-this` in order to force it to work as before.
There are two affected users, or usages:

1. people who use the command in scripts; and
2. people who are used to using it interactively.

For (1) the replacement is straightforward.[1]  But people in (2) might
like the name or be really used to typing it.[3]

An obvious first thought is to suggest aliasing `whatchanged` to the
git-log(1) equivalent.[1]  But this doesn’t work and is awkward since you
cannot shadow builtins via aliases.

Now you are left in an uncomfortable limbo; your alias won’t work until
the command is removed for good.

Let’s lift this limitation by allowing *deprecated* builtins to be
shadowed by aliases.

The only observed demand for aliasing has been for git-whatchanged(1),
not for git-pack-redundant(1).  But let’s be consistent and treat all
deprecated commands the same.

[1]:

        git log --raw --no-merges

     With a minor caveat: you get different outputs if you happen to
     have empty commits (no changes)[2]
[2]: https://lore.kernel.org/git/20250825085428.GA367101@coredump.intra.peff.net/
[3]: https://lore.kernel.org/git/BL3P221MB0449288C8B0FA448A227FD48833AA@BL3P221MB0449.NAMP221.PROD.OUTLOOK.COM/

Based-on-patch-by: Jeff King <peff@peff.net>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
maint
Kristoffer Haugsbakk 2025-09-17 22:24:14 +02:00 committed by Junio C Hamano
parent b4f9282d8d
commit bf68b11699
3 changed files with 59 additions and 1 deletions

View File

@ -3,7 +3,8 @@ alias.*::
after defining `alias.last = cat-file commit HEAD`, the invocation
`git last` is equivalent to `git cat-file commit HEAD`. To avoid
confusion and troubles with script usage, aliases that
hide existing Git commands are ignored. Arguments are split by
hide existing Git commands are ignored except for deprecated
commands. Arguments are split by
spaces, the usual shell quoting and escaping are supported.
A quote pair or a backslash can be used to quote them.
+

17
git.c
View File

@ -824,12 +824,29 @@ static void execv_dashed_external(const char **argv)
exit(128);
}

static int is_deprecated_command(const char *cmd)
{
struct cmd_struct *builtin = get_builtin(cmd);
return builtin && (builtin->option & DEPRECATED);
}

static int run_argv(struct strvec *args)
{
int done_alias = 0;
struct string_list expanded_aliases = STRING_LIST_INIT_DUP;

while (1) {
/*
* Allow deprecated commands to be overridden by aliases. This
* creates a seamless path forward for people who want to keep
* using the name after it is gone, but want to skip the
* deprecation complaint in the meantime.
*/
if (is_deprecated_command(args->v[0]) &&
handle_alias(args, &expanded_aliases)) {
done_alias = 1;
continue;
}
/*
* If we tried alias and futzed with our environment,
* it no longer is safe to invoke builtins directly in

View File

@ -27,6 +27,20 @@ test_expect_success 'looping aliases - internal execution' '
test_grep "^fatal: alias loop detected: expansion of" output
'

test_expect_success 'looping aliases - deprecated builtins' '
test_config alias.whatchanged pack-redundant &&
test_config alias.pack-redundant whatchanged &&
cat >expect <<-EOF &&
${SQ}whatchanged${SQ} is aliased to ${SQ}pack-redundant${SQ}
${SQ}pack-redundant${SQ} is aliased to ${SQ}whatchanged${SQ}
fatal: alias loop detected: expansion of ${SQ}whatchanged${SQ} does not terminate:
whatchanged <==
pack-redundant ==>
EOF
test_must_fail git whatchanged -h 2>actual &&
test_cmp expect actual
'

# This test is disabled until external loops are fixed, because would block
# the test suite for a full minute.
#
@ -55,4 +69,30 @@ test_expect_success 'tracing a shell alias with arguments shows trace of prepare
test_cmp expect actual
'

can_alias_deprecated_builtin () {
cmd="$1" &&
# some git(1) commands will fail for `-h` (the case for
# git-status as of 2025-09-07)
test_might_fail git status -h >expect &&
test_file_not_empty expect &&
test_might_fail git -c alias."$cmd"=status "$cmd" -h >actual &&
test_cmp expect actual
}

test_expect_success 'can alias-shadow deprecated builtins' '
for cmd in $(git --list-cmds=deprecated)
do
can_alias_deprecated_builtin "$cmd" || return 1
done
'

test_expect_success 'can alias-shadow via two deprecated builtins' '
# some git(1) commands will fail... (see above)
test_might_fail git status -h >expect &&
test_file_not_empty expect &&
test_might_fail git -c alias.whatchanged=pack-redundant \
-c alias.pack-redundant=status whatchanged -h >actual &&
test_cmp expect actual
'

test_done