Git fork

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>

authored by

Kristoffer Haugsbakk and committed by
Junio C Hamano
bf68b116 b4f9282d

+59 -1
+2 -1
Documentation/config/alias.adoc
··· 3 3 after defining `alias.last = cat-file commit HEAD`, the invocation 4 4 `git last` is equivalent to `git cat-file commit HEAD`. To avoid 5 5 confusion and troubles with script usage, aliases that 6 - hide existing Git commands are ignored. Arguments are split by 6 + hide existing Git commands are ignored except for deprecated 7 + commands. Arguments are split by 7 8 spaces, the usual shell quoting and escaping are supported. 8 9 A quote pair or a backslash can be used to quote them. 9 10 +
+17
git.c
··· 824 824 exit(128); 825 825 } 826 826 827 + static int is_deprecated_command(const char *cmd) 828 + { 829 + struct cmd_struct *builtin = get_builtin(cmd); 830 + return builtin && (builtin->option & DEPRECATED); 831 + } 832 + 827 833 static int run_argv(struct strvec *args) 828 834 { 829 835 int done_alias = 0; 830 836 struct string_list expanded_aliases = STRING_LIST_INIT_DUP; 831 837 832 838 while (1) { 839 + /* 840 + * Allow deprecated commands to be overridden by aliases. This 841 + * creates a seamless path forward for people who want to keep 842 + * using the name after it is gone, but want to skip the 843 + * deprecation complaint in the meantime. 844 + */ 845 + if (is_deprecated_command(args->v[0]) && 846 + handle_alias(args, &expanded_aliases)) { 847 + done_alias = 1; 848 + continue; 849 + } 833 850 /* 834 851 * If we tried alias and futzed with our environment, 835 852 * it no longer is safe to invoke builtins directly in
+40
t/t0014-alias.sh
··· 27 27 test_grep "^fatal: alias loop detected: expansion of" output 28 28 ' 29 29 30 + test_expect_success 'looping aliases - deprecated builtins' ' 31 + test_config alias.whatchanged pack-redundant && 32 + test_config alias.pack-redundant whatchanged && 33 + cat >expect <<-EOF && 34 + ${SQ}whatchanged${SQ} is aliased to ${SQ}pack-redundant${SQ} 35 + ${SQ}pack-redundant${SQ} is aliased to ${SQ}whatchanged${SQ} 36 + fatal: alias loop detected: expansion of ${SQ}whatchanged${SQ} does not terminate: 37 + whatchanged <== 38 + pack-redundant ==> 39 + EOF 40 + test_must_fail git whatchanged -h 2>actual && 41 + test_cmp expect actual 42 + ' 43 + 30 44 # This test is disabled until external loops are fixed, because would block 31 45 # the test suite for a full minute. 32 46 # ··· 52 66 env GIT_TRACE=1 git echo arg 2>output && 53 67 # redact platform differences 54 68 sed -n -e "s/^\(trace: start_command:\) .* -c /\1 SHELL -c /p" output >actual && 69 + test_cmp expect actual 70 + ' 71 + 72 + can_alias_deprecated_builtin () { 73 + cmd="$1" && 74 + # some git(1) commands will fail for `-h` (the case for 75 + # git-status as of 2025-09-07) 76 + test_might_fail git status -h >expect && 77 + test_file_not_empty expect && 78 + test_might_fail git -c alias."$cmd"=status "$cmd" -h >actual && 79 + test_cmp expect actual 80 + } 81 + 82 + test_expect_success 'can alias-shadow deprecated builtins' ' 83 + for cmd in $(git --list-cmds=deprecated) 84 + do 85 + can_alias_deprecated_builtin "$cmd" || return 1 86 + done 87 + ' 88 + 89 + test_expect_success 'can alias-shadow via two deprecated builtins' ' 90 + # some git(1) commands will fail... (see above) 91 + test_might_fail git status -h >expect && 92 + test_file_not_empty expect && 93 + test_might_fail git -c alias.whatchanged=pack-redundant \ 94 + -c alias.pack-redundant=status whatchanged -h >actual && 55 95 test_cmp expect actual 56 96 ' 57 97