Skip to content

Commit 7ae9eaf

Browse files
committed
Merge branch 'kh/you-still-use-whatchanged-fix'
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 2f49ec7 + 54a60e5 commit 7ae9eaf

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
@@ -241,7 +241,7 @@ These features will be removed.
241241
equivalent `git log --raw`. We have nominated the command for
242242
removal, have changed the command to refuse to work unless the
243243
`--i-still-use-this` option is given, and asked the users to report
244-
when they do so. So far there hasn't been a single complaint.
244+
when they do so.
245245
+
246246
The command will be removed.
247247

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);
@@ -591,7 +617,7 @@ static struct cmd_struct commands[] = {
591617
{ "notes", cmd_notes, RUN_SETUP },
592618
{ "pack-objects", cmd_pack_objects, RUN_SETUP },
593619
#ifndef WITH_BREAKING_CHANGES
594-
{ "pack-redundant", cmd_pack_redundant, RUN_SETUP | NO_PARSEOPT },
620+
{ "pack-redundant", cmd_pack_redundant, RUN_SETUP | NO_PARSEOPT | DEPRECATED },
595621
#endif
596622
{ "pack-refs", cmd_pack_refs, RUN_SETUP },
597623
{ "patch-id", cmd_patch_id, RUN_SETUP_GENTLY | NO_PARSEOPT },
@@ -649,7 +675,7 @@ static struct cmd_struct commands[] = {
649675
{ "verify-tag", cmd_verify_tag, RUN_SETUP },
650676
{ "version", cmd_version },
651677
#ifndef WITH_BREAKING_CHANGES
652-
{ "whatchanged", cmd_whatchanged, RUN_SETUP },
678+
{ "whatchanged", cmd_whatchanged, RUN_SETUP | DEPRECATED },
653679
#endif
654680
{ "worktree", cmd_worktree, RUN_SETUP },
655681
{ "write-tree", cmd_write_tree, RUN_SETUP },
@@ -670,11 +696,16 @@ int is_builtin(const char *s)
670696
return !!get_builtin(s);
671697
}
672698

673-
static void list_builtins(struct string_list *out, unsigned int exclude_option)
699+
static void list_builtins(struct string_list *out,
700+
unsigned int include_option,
701+
unsigned int exclude_option)
674702
{
703+
if (include_option && exclude_option)
704+
BUG("'include_option' and 'exclude_option' are mutually exclusive");
675705
for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {
676-
if (exclude_option &&
677-
(commands[i].option & exclude_option))
706+
if (include_option && !(commands[i].option & include_option))
707+
continue;
708+
if (exclude_option && (commands[i].option & exclude_option))
678709
continue;
679710
string_list_append(out, commands[i].cmd);
680711
}
@@ -795,13 +826,29 @@ static void execv_dashed_external(const char **argv)
795826
exit(128);
796827
}
797828

829+
static int is_deprecated_command(const char *cmd)
830+
{
831+
struct cmd_struct *builtin = get_builtin(cmd);
832+
return builtin && (builtin->option & DEPRECATED);
833+
}
834+
798835
static int run_argv(struct strvec *args)
799836
{
800837
int done_alias = 0;
801-
struct string_list cmd_list = STRING_LIST_INIT_DUP;
802-
struct string_list_item *seen;
838+
struct string_list expanded_aliases = STRING_LIST_INIT_DUP;
803839

804840
while (1) {
841+
/*
842+
* Allow deprecated commands to be overridden by aliases. This
843+
* creates a seamless path forward for people who want to keep
844+
* using the name after it is gone, but want to skip the
845+
* deprecation complaint in the meantime.
846+
*/
847+
if (is_deprecated_command(args->v[0]) &&
848+
handle_alias(args, &expanded_aliases)) {
849+
done_alias = 1;
850+
continue;
851+
}
805852
/*
806853
* If we tried alias and futzed with our environment,
807854
* it no longer is safe to invoke builtins directly in
@@ -851,35 +898,17 @@ static int run_argv(struct strvec *args)
851898
/* .. then try the external ones */
852899
execv_dashed_external(args->v);
853900

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

882-
string_list_clear(&cmd_list, 0);
911+
string_list_clear(&expanded_aliases, 0);
883912

884913
return done_alias;
885914
}

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)