diff options
| -rw-r--r-- | Documentation/BreakingChanges.adoc | 2 | ||||
| -rw-r--r-- | Documentation/config/alias.adoc | 3 | ||||
| -rw-r--r-- | Documentation/git-whatchanged.adoc | 8 | ||||
| -rw-r--r-- | Documentation/git.adoc | 3 | ||||
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | builtin/log.c | 8 | ||||
| -rw-r--r-- | builtin/pack-redundant.c | 2 | ||||
| -rw-r--r-- | git-compat-util.h | 2 | ||||
| -rw-r--r-- | git.c | 91 | ||||
| -rwxr-xr-x | t/t0014-alias.sh | 57 | ||||
| -rw-r--r-- | usage.c | 33 |
11 files changed, 165 insertions, 46 deletions
diff --git a/Documentation/BreakingChanges.adoc b/Documentation/BreakingChanges.adoc index 0cba20fadb..a67ad4eea1 100644 --- a/Documentation/BreakingChanges.adoc +++ b/Documentation/BreakingChanges.adoc @@ -241,7 +241,7 @@ These features will be removed. equivalent `git log --raw`. We have nominated the command for removal, have changed the command to refuse to work unless the `--i-still-use-this` option is given, and asked the users to report - when they do so. So far there hasn't been a single complaint. + when they do so. + The command will be removed. diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc index 95825354bf..80ce17d2de 100644 --- a/Documentation/config/alias.adoc +++ b/Documentation/config/alias.adoc @@ -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. + diff --git a/Documentation/git-whatchanged.adoc b/Documentation/git-whatchanged.adoc index d21484026f..436e219b7d 100644 --- a/Documentation/git-whatchanged.adoc +++ b/Documentation/git-whatchanged.adoc @@ -15,7 +15,7 @@ WARNING ------- `git whatchanged` has been deprecated and is scheduled for removal in a future version of Git, as it is merely `git log` with different -default; `whatchanged` is not even shorter to type than `log --raw`. +defaults. DESCRIPTION ----------- @@ -24,7 +24,11 @@ Shows commit logs and diff output each commit introduces. New users are encouraged to use linkgit:git-log[1] instead. The `whatchanged` command is essentially the same as linkgit:git-log[1] -but defaults to showing the raw format diff output and skipping merges. +but defaults to showing the raw format diff output and skipping merges: + +---- +git log --raw --no-merges +---- The command is primarily kept for historical reasons; fingers of many people who learned Git long before `git log` was invented by diff --git a/Documentation/git.adoc b/Documentation/git.adoc index 03e9e69d25..ce099e78b8 100644 --- a/Documentation/git.adoc +++ b/Documentation/git.adoc @@ -219,7 +219,8 @@ If you just want to run git as if it was started in `<path>` then use List commands by group. This is an internal/experimental option and may change or be removed in the future. Supported groups are: builtins, parseopt (builtin commands that use - parse-options), main (all commands in libexec directory), + parse-options), deprecated (deprecated builtins), + main (all commands in libexec directory), others (all other commands in `$PATH` that have git- prefix), list-<category> (see categories in command-list.txt), nohelpers (exclude helper commands), alias and config @@ -883,7 +883,9 @@ BUILT_INS += git-stage$X BUILT_INS += git-status$X BUILT_INS += git-switch$X BUILT_INS += git-version$X +ifndef WITH_BREAKING_CHANGES BUILT_INS += git-whatchanged$X +endif # what 'all' will build but not install in gitexecdir OTHER_PROGRAMS += git$X diff --git a/builtin/log.c b/builtin/log.c index 5f552d14c0..8aa1777940 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -543,7 +543,13 @@ int cmd_whatchanged(int argc, cmd_log_init(argc, argv, prefix, &rev, &opt, &cfg); if (!cfg.i_still_use_this) - you_still_use_that("git whatchanged"); + you_still_use_that("git whatchanged", + _("\n" + "hint: You can replace 'git whatchanged <opts>' with:\n" + "hint:\tgit log <opts> --raw --no-merges\n" + "hint: Or make an alias:\n" + "hint:\tgit config set --global alias.whatchanged 'log --raw --no-merges'\n" + "\n")); if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index fe81c293e3..5d5ae4afa2 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -626,7 +626,7 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix UNUSED, s } if (!i_still_use_this) - you_still_use_that("git pack-redundant"); + you_still_use_that("git pack-redundant", NULL); if (load_all_packs) load_all(); diff --git a/git-compat-util.h b/git-compat-util.h index 9408f463e3..398e0fac4f 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -460,7 +460,7 @@ void warning_errno(const char *err, ...) __attribute__((format (printf, 1, 2))); void show_usage_if_asked(int ac, const char **av, const char *err); -NORETURN void you_still_use_that(const char *command_name); +NORETURN void you_still_use_that(const char *command_name, const char *hint); #ifndef NO_OPENSSL #ifdef APPLE_COMMON_CRYPTO @@ -28,6 +28,7 @@ #define NEED_WORK_TREE (1<<3) #define DELAY_PAGER_CONFIG (1<<4) #define NO_PARSEOPT (1<<5) /* parse-options is not used */ +#define DEPRECATED (1<<6) struct cmd_struct { const char *cmd; @@ -51,7 +52,9 @@ const char git_more_info_string[] = static int use_pager = -1; -static void list_builtins(struct string_list *list, unsigned int exclude_option); +static void list_builtins(struct string_list *list, + unsigned int include_option, + unsigned int exclude_option); static void exclude_helpers_from_list(struct string_list *list) { @@ -88,7 +91,7 @@ static int list_cmds(const char *spec) int len = sep - spec; if (match_token(spec, len, "builtins")) - list_builtins(&list, 0); + list_builtins(&list, 0, 0); else if (match_token(spec, len, "main")) list_all_main_cmds(&list); else if (match_token(spec, len, "others")) @@ -99,6 +102,8 @@ static int list_cmds(const char *spec) list_aliases(&list); else if (match_token(spec, len, "config")) list_cmds_by_config(&list); + else if (match_token(spec, len, "deprecated")) + list_builtins(&list, DEPRECATED, 0); else if (len > 5 && !strncmp(spec, "list-", 5)) { struct strbuf sb = STRBUF_INIT; @@ -322,7 +327,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) if (!strcmp(cmd, "parseopt")) { struct string_list list = STRING_LIST_INIT_DUP; - list_builtins(&list, NO_PARSEOPT); + list_builtins(&list, 0, NO_PARSEOPT); for (size_t i = 0; i < list.nr; i++) printf("%s ", list.items[i].string); string_list_clear(&list, 0); @@ -360,7 +365,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) return (*argv) - orig_argv; } -static int handle_alias(struct strvec *args) +static int handle_alias(struct strvec *args, struct string_list *expanded_aliases) { int envchanged = 0, ret = 0, saved_errno = errno; int count, option_count; @@ -371,6 +376,8 @@ static int handle_alias(struct strvec *args) alias_command = args->v[0]; alias_string = alias_lookup(alias_command); if (alias_string) { + struct string_list_item *seen; + if (args->nr == 2 && !strcmp(args->v[1], "-h")) fprintf_ln(stderr, _("'%s' is aliased to '%s'"), alias_command, alias_string); @@ -418,6 +425,25 @@ static int handle_alias(struct strvec *args) if (!strcmp(alias_command, new_argv[0])) die(_("recursive alias: %s"), alias_command); + string_list_append(expanded_aliases, alias_command); + seen = unsorted_string_list_lookup(expanded_aliases, + new_argv[0]); + + if (seen) { + struct strbuf sb = STRBUF_INIT; + for (size_t i = 0; i < expanded_aliases->nr; i++) { + struct string_list_item *item = &expanded_aliases->items[i]; + + strbuf_addf(&sb, "\n %s", item->string); + if (item == seen) + strbuf_addstr(&sb, " <=="); + else if (i == expanded_aliases->nr - 1) + strbuf_addstr(&sb, " ==>"); + } + die(_("alias loop detected: expansion of '%s' does" + " not terminate:%s"), expanded_aliases->items[0].string, sb.buf); + } + trace_argv_printf(new_argv, "trace: alias expansion: %s =>", alias_command); @@ -591,7 +617,7 @@ static struct cmd_struct commands[] = { { "notes", cmd_notes, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, #ifndef WITH_BREAKING_CHANGES - { "pack-redundant", cmd_pack_redundant, RUN_SETUP | NO_PARSEOPT }, + { "pack-redundant", cmd_pack_redundant, RUN_SETUP | NO_PARSEOPT | DEPRECATED }, #endif { "pack-refs", cmd_pack_refs, RUN_SETUP }, { "patch-id", cmd_patch_id, RUN_SETUP_GENTLY | NO_PARSEOPT }, @@ -649,7 +675,7 @@ static struct cmd_struct commands[] = { { "verify-tag", cmd_verify_tag, RUN_SETUP }, { "version", cmd_version }, #ifndef WITH_BREAKING_CHANGES - { "whatchanged", cmd_whatchanged, RUN_SETUP }, + { "whatchanged", cmd_whatchanged, RUN_SETUP | DEPRECATED }, #endif { "worktree", cmd_worktree, RUN_SETUP }, { "write-tree", cmd_write_tree, RUN_SETUP }, @@ -670,11 +696,16 @@ int is_builtin(const char *s) return !!get_builtin(s); } -static void list_builtins(struct string_list *out, unsigned int exclude_option) +static void list_builtins(struct string_list *out, + unsigned int include_option, + unsigned int exclude_option) { + if (include_option && exclude_option) + BUG("'include_option' and 'exclude_option' are mutually exclusive"); for (size_t i = 0; i < ARRAY_SIZE(commands); i++) { - if (exclude_option && - (commands[i].option & exclude_option)) + if (include_option && !(commands[i].option & include_option)) + continue; + if (exclude_option && (commands[i].option & exclude_option)) continue; string_list_append(out, commands[i].cmd); } @@ -795,14 +826,30 @@ 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 cmd_list = STRING_LIST_INIT_DUP; - struct string_list_item *seen; + 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 * general. We have to spawn them as dashed externals. @@ -851,35 +898,17 @@ static int run_argv(struct strvec *args) /* .. then try the external ones */ execv_dashed_external(args->v); - seen = unsorted_string_list_lookup(&cmd_list, args->v[0]); - if (seen) { - struct strbuf sb = STRBUF_INIT; - for (size_t i = 0; i < cmd_list.nr; i++) { - struct string_list_item *item = &cmd_list.items[i]; - - strbuf_addf(&sb, "\n %s", item->string); - if (item == seen) - strbuf_addstr(&sb, " <=="); - else if (i == cmd_list.nr - 1) - strbuf_addstr(&sb, " ==>"); - } - die(_("alias loop detected: expansion of '%s' does" - " not terminate:%s"), cmd_list.items[0].string, sb.buf); - } - - string_list_append(&cmd_list, args->v[0]); - /* * It could be an alias -- this works around the insanity * of overriding "git log" with "git show" by having * alias.log = show */ - if (!handle_alias(args)) + if (!handle_alias(args, &expanded_aliases)) break; done_alias = 1; } - string_list_clear(&cmd_list, 0); + string_list_clear(&expanded_aliases, 0); return done_alias; } diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh index 854d59ec58..07a53e7366 100755 --- a/t/t0014-alias.sh +++ b/t/t0014-alias.sh @@ -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,47 @@ 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 +' + +cannot_alias_regular_builtin () { + cmd="$1" && + # some git(1) commands will fail... (see above) + test_might_fail git "$cmd" -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 'cannot alias-shadow a sample of regular builtins' ' + for cmd in grep check-ref-format interpret-trailers \ + checkout-index fast-import diagnose rev-list prune + do + cannot_alias_regular_builtin "$cmd" || return 1 + done +' + test_done @@ -7,6 +7,7 @@ #include "git-compat-util.h" #include "gettext.h" #include "trace2.h" +#include "strbuf.h" static void vfreportf(FILE *f, const char *prefix, const char *err, va_list params) { @@ -376,14 +377,32 @@ void bug_fl(const char *file, int line, const char *fmt, ...) va_end(ap); } -NORETURN void you_still_use_that(const char *command_name) + +NORETURN void you_still_use_that(const char *command_name, const char *hint) { + struct strbuf percent_encoded = STRBUF_INIT; + strbuf_add_percentencode(&percent_encoded, + command_name, + STRBUF_ENCODE_SLASH); + + fprintf(stderr, + _("'%s' is nominated for removal.\n"), command_name); + + if (hint) + fputs(hint, stderr); + fprintf(stderr, - _("'%s' is nominated for removal.\n" - "If you still use this command, please add an extra\n" - "option, '--i-still-use-this', on the command line\n" - "and let us know you still use it by sending an e-mail\n" - "to <git@vger.kernel.org>. Thanks.\n"), - command_name); + _("If you still use this command, here's what you can do:\n" + "\n" + "- read https://git-scm.com/docs/BreakingChanges.html\n" + "- check if anyone has discussed this on the mailing\n" + " list and if they came up with something that can\n" + " help you: https://lore.kernel.org/git/?q=%s\n" + "- send an email to <git@vger.kernel.org> to let us\n" + " know that you still use this command and were unable\n" + " to determine a suitable replacement\n" + "\n"), + percent_encoded.buf); + strbuf_release(&percent_encoded); die(_("refusing to run without --i-still-use-this")); } |
