Skip to content

Commit b4c2504

Browse files
committed
Merge branch 'kh/you-still-use-whatchanged-fix' into maint-2.51
The "do you still use it?" message given by a command that is deeply deprecated and allow us to suggest alternatives has been updated. * kh/you-still-use-whatchanged-fix: BreakingChanges: remove claim about whatchanged reports whatchanged: remove not-even-shorter clause whatchanged: hint about git-log(1) and aliasing you-still-use-that??: help the user help themselves t0014: test shadowing of aliases for a sample of builtins git: allow alias-shadowing deprecated builtins git: move seen-alias bookkeeping into handle_alias(...) git: add `deprecated` category to --list-cmds Makefile: don’t add whatchanged after it has been removed
2 parents c44beea + 54a60e5 commit b4c2504

File tree

11 files changed

+165
-46
lines changed

11 files changed

+165
-46
lines changed

Documentation/BreakingChanges.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ These features will be removed.
235235
equivalent `git log --raw`. We have nominated the command for
236236
removal, have changed the command to refuse to work unless the
237237
`--i-still-use-this` option is given, and asked the users to report
238-
when they do so. So far there hasn't been a single complaint.
238+
when they do so.
239239
+
240240
The command will be removed.
241241

Documentation/config/alias.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ alias.*::
33
after defining `alias.last = cat-file commit HEAD`, the invocation
44
`git last` is equivalent to `git cat-file commit HEAD`. To avoid
55
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
78
spaces, the usual shell quoting and escaping are supported.
89
A quote pair or a backslash can be used to quote them.
910
+

Documentation/git-whatchanged.adoc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ WARNING
1515
-------
1616
`git whatchanged` has been deprecated and is scheduled for removal in
1717
a future version of Git, as it is merely `git log` with different
18-
default; `whatchanged` is not even shorter to type than `log --raw`.
18+
defaults.
1919

2020
DESCRIPTION
2121
-----------
@@ -24,7 +24,11 @@ Shows commit logs and diff output each commit introduces.
2424

2525
New users are encouraged to use linkgit:git-log[1] instead. The
2626
`whatchanged` command is essentially the same as linkgit:git-log[1]
27-
but defaults to showing the raw format diff output and skipping merges.
27+
but defaults to showing the raw format diff output and skipping merges:
28+
29+
----
30+
git log --raw --no-merges
31+
----
2832

2933
The command is primarily kept for historical reasons; fingers of
3034
many people who learned Git long before `git log` was invented by

Documentation/git.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,8 @@ If you just want to run git as if it was started in `<path>` then use
219219
List commands by group. This is an internal/experimental
220220
option and may change or be removed in the future. Supported
221221
groups are: builtins, parseopt (builtin commands that use
222-
parse-options), main (all commands in libexec directory),
222+
parse-options), deprecated (deprecated builtins),
223+
main (all commands in libexec directory),
223224
others (all other commands in `$PATH` that have git- prefix),
224225
list-<category> (see categories in command-list.txt),
225226
nohelpers (exclude helper commands), alias and config

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,9 @@ BUILT_INS += git-stage$X
883883
BUILT_INS += git-status$X
884884
BUILT_INS += git-switch$X
885885
BUILT_INS += git-version$X
886+
ifndef WITH_BREAKING_CHANGES
886887
BUILT_INS += git-whatchanged$X
888+
endif
887889

888890
# what 'all' will build but not install in gitexecdir
889891
OTHER_PROGRAMS += git$X

builtin/log.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,13 @@ int cmd_whatchanged(int argc,
543543
cmd_log_init(argc, argv, prefix, &rev, &opt, &cfg);
544544

545545
if (!cfg.i_still_use_this)
546-
you_still_use_that("git whatchanged");
546+
you_still_use_that("git whatchanged",
547+
_("\n"
548+
"hint: You can replace 'git whatchanged <opts>' with:\n"
549+
"hint:\tgit log <opts> --raw --no-merges\n"
550+
"hint: Or make an alias:\n"
551+
"hint:\tgit config set --global alias.whatchanged 'log --raw --no-merges'\n"
552+
"\n"));
547553

548554
if (!rev.diffopt.output_format)
549555
rev.diffopt.output_format = DIFF_FORMAT_RAW;

builtin/pack-redundant.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix UNUSED, s
626626
}
627627

628628
if (!i_still_use_this)
629-
you_still_use_that("git pack-redundant");
629+
you_still_use_that("git pack-redundant", NULL);
630630

631631
if (load_all_packs)
632632
load_all();

git-compat-util.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ void warning_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));
460460

461461
void show_usage_if_asked(int ac, const char **av, const char *err);
462462

463-
NORETURN void you_still_use_that(const char *command_name);
463+
NORETURN void you_still_use_that(const char *command_name, const char *hint);
464464

465465
#ifndef NO_OPENSSL
466466
#ifdef APPLE_COMMON_CRYPTO

git.c

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#define NEED_WORK_TREE (1<<3)
2929
#define DELAY_PAGER_CONFIG (1<<4)
3030
#define NO_PARSEOPT (1<<5) /* parse-options is not used */
31+
#define DEPRECATED (1<<6)
3132

3233
struct cmd_struct {
3334
const char *cmd;
@@ -51,7 +52,9 @@ const char git_more_info_string[] =
5152

5253
static int use_pager = -1;
5354

54-
static void list_builtins(struct string_list *list, unsigned int exclude_option);
55+
static void list_builtins(struct string_list *list,
56+
unsigned int include_option,
57+
unsigned int exclude_option);
5558

5659
static void exclude_helpers_from_list(struct string_list *list)
5760
{
@@ -88,7 +91,7 @@ static int list_cmds(const char *spec)
8891
int len = sep - spec;
8992

9093
if (match_token(spec, len, "builtins"))
91-
list_builtins(&list, 0);
94+
list_builtins(&list, 0, 0);
9295
else if (match_token(spec, len, "main"))
9396
list_all_main_cmds(&list);
9497
else if (match_token(spec, len, "others"))
@@ -99,6 +102,8 @@ static int list_cmds(const char *spec)
99102
list_aliases(&list);
100103
else if (match_token(spec, len, "config"))
101104
list_cmds_by_config(&list);
105+
else if (match_token(spec, len, "deprecated"))
106+
list_builtins(&list, DEPRECATED, 0);
102107
else if (len > 5 && !strncmp(spec, "list-", 5)) {
103108
struct strbuf sb = STRBUF_INIT;
104109

@@ -322,7 +327,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
322327
if (!strcmp(cmd, "parseopt")) {
323328
struct string_list list = STRING_LIST_INIT_DUP;
324329

325-
list_builtins(&list, NO_PARSEOPT);
330+
list_builtins(&list, 0, NO_PARSEOPT);
326331
for (size_t i = 0; i < list.nr; i++)
327332
printf("%s ", list.items[i].string);
328333
string_list_clear(&list, 0);
@@ -360,7 +365,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
360365
return (*argv) - orig_argv;
361366
}
362367

363-
static int handle_alias(struct strvec *args)
368+
static int handle_alias(struct strvec *args, struct string_list *expanded_aliases)
364369
{
365370
int envchanged = 0, ret = 0, saved_errno = errno;
366371
int count, option_count;
@@ -371,6 +376,8 @@ static int handle_alias(struct strvec *args)
371376
alias_command = args->v[0];
372377
alias_string = alias_lookup(alias_command);
373378
if (alias_string) {
379+
struct string_list_item *seen;
380+
374381
if (args->nr == 2 && !strcmp(args->v[1], "-h"))
375382
fprintf_ln(stderr, _("'%s' is aliased to '%s'"),
376383
alias_command, alias_string);
@@ -418,6 +425,25 @@ static int handle_alias(struct strvec *args)
418425
if (!strcmp(alias_command, new_argv[0]))
419426
die(_("recursive alias: %s"), alias_command);
420427

428+
string_list_append(expanded_aliases, alias_command);
429+
seen = unsorted_string_list_lookup(expanded_aliases,
430+
new_argv[0]);
431+
432+
if (seen) {
433+
struct strbuf sb = STRBUF_INIT;
434+
for (size_t i = 0; i < expanded_aliases->nr; i++) {
435+
struct string_list_item *item = &expanded_aliases->items[i];
436+
437+
strbuf_addf(&sb, "\n %s", item->string);
438+
if (item == seen)
439+
strbuf_addstr(&sb, " <==");
440+
else if (i == expanded_aliases->nr - 1)
441+
strbuf_addstr(&sb, " ==>");
442+
}
443+
die(_("alias loop detected: expansion of '%s' does"
444+
" not terminate:%s"), expanded_aliases->items[0].string, sb.buf);
445+
}
446+
421447
trace_argv_printf(new_argv,
422448
"trace: alias expansion: %s =>",
423449
alias_command);
@@ -590,7 +616,7 @@ static struct cmd_struct commands[] = {
590616
{ "notes", cmd_notes, RUN_SETUP },
591617
{ "pack-objects", cmd_pack_objects, RUN_SETUP },
592618
#ifndef WITH_BREAKING_CHANGES
593-
{ "pack-redundant", cmd_pack_redundant, RUN_SETUP | NO_PARSEOPT },
619+
{ "pack-redundant", cmd_pack_redundant, RUN_SETUP | NO_PARSEOPT | DEPRECATED },
594620
#endif
595621
{ "pack-refs", cmd_pack_refs, RUN_SETUP },
596622
{ "patch-id", cmd_patch_id, RUN_SETUP_GENTLY | NO_PARSEOPT },
@@ -647,7 +673,7 @@ static struct cmd_struct commands[] = {
647673
{ "verify-tag", cmd_verify_tag, RUN_SETUP },
648674
{ "version", cmd_version },
649675
#ifndef WITH_BREAKING_CHANGES
650-
{ "whatchanged", cmd_whatchanged, RUN_SETUP },
676+
{ "whatchanged", cmd_whatchanged, RUN_SETUP | DEPRECATED },
651677
#endif
652678
{ "worktree", cmd_worktree, RUN_SETUP },
653679
{ "write-tree", cmd_write_tree, RUN_SETUP },
@@ -668,11 +694,16 @@ int is_builtin(const char *s)
668694
return !!get_builtin(s);
669695
}
670696

671-
static void list_builtins(struct string_list *out, unsigned int exclude_option)
697+
static void list_builtins(struct string_list *out,
698+
unsigned int include_option,
699+
unsigned int exclude_option)
672700
{
701+
if (include_option && exclude_option)
702+
BUG("'include_option' and 'exclude_option' are mutually exclusive");
673703
for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {
674-
if (exclude_option &&
675-
(commands[i].option & exclude_option))
704+
if (include_option && !(commands[i].option & include_option))
705+
continue;
706+
if (exclude_option && (commands[i].option & exclude_option))
676707
continue;
677708
string_list_append(out, commands[i].cmd);
678709
}
@@ -793,13 +824,29 @@ static void execv_dashed_external(const char **argv)
793824
exit(128);
794825
}
795826

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+
796833
static int run_argv(struct strvec *args)
797834
{
798835
int done_alias = 0;
799-
struct string_list cmd_list = STRING_LIST_INIT_DUP;
800-
struct string_list_item *seen;
836+
struct string_list expanded_aliases = STRING_LIST_INIT_DUP;
801837

802838
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+
}
803850
/*
804851
* If we tried alias and futzed with our environment,
805852
* it no longer is safe to invoke builtins directly in
@@ -849,35 +896,17 @@ static int run_argv(struct strvec *args)
849896
/* .. then try the external ones */
850897
execv_dashed_external(args->v);
851898

852-
seen = unsorted_string_list_lookup(&cmd_list, args->v[0]);
853-
if (seen) {
854-
struct strbuf sb = STRBUF_INIT;
855-
for (size_t i = 0; i < cmd_list.nr; i++) {
856-
struct string_list_item *item = &cmd_list.items[i];
857-
858-
strbuf_addf(&sb, "\n %s", item->string);
859-
if (item == seen)
860-
strbuf_addstr(&sb, " <==");
861-
else if (i == cmd_list.nr - 1)
862-
strbuf_addstr(&sb, " ==>");
863-
}
864-
die(_("alias loop detected: expansion of '%s' does"
865-
" not terminate:%s"), cmd_list.items[0].string, sb.buf);
866-
}
867-
868-
string_list_append(&cmd_list, args->v[0]);
869-
870899
/*
871900
* It could be an alias -- this works around the insanity
872901
* of overriding "git log" with "git show" by having
873902
* alias.log = show
874903
*/
875-
if (!handle_alias(args))
904+
if (!handle_alias(args, &expanded_aliases))
876905
break;
877906
done_alias = 1;
878907
}
879908

880-
string_list_clear(&cmd_list, 0);
909+
string_list_clear(&expanded_aliases, 0);
881910

882911
return done_alias;
883912
}

t/t0014-alias.sh

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,20 @@ test_expect_success 'looping aliases - internal execution' '
2727
test_grep "^fatal: alias loop detected: expansion of" output
2828
'
2929

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+
3044
# This test is disabled until external loops are fixed, because would block
3145
# the test suite for a full minute.
3246
#
@@ -55,4 +69,47 @@ test_expect_success 'tracing a shell alias with arguments shows trace of prepare
5569
test_cmp expect actual
5670
'
5771

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 &&
95+
test_cmp expect actual
96+
'
97+
98+
cannot_alias_regular_builtin () {
99+
cmd="$1" &&
100+
# some git(1) commands will fail... (see above)
101+
test_might_fail git "$cmd" -h >expect &&
102+
test_file_not_empty expect &&
103+
test_might_fail git -c alias."$cmd"=status "$cmd" -h >actual &&
104+
test_cmp expect actual
105+
}
106+
107+
test_expect_success 'cannot alias-shadow a sample of regular builtins' '
108+
for cmd in grep check-ref-format interpret-trailers \
109+
checkout-index fast-import diagnose rev-list prune
110+
do
111+
cannot_alias_regular_builtin "$cmd" || return 1
112+
done
113+
'
114+
58115
test_done

0 commit comments

Comments
 (0)