Skip to contentSkip to conversation

you-still-use-that??: improve breaking changes troubleshooting

PATCH Superseded [email protected] 6 participants 102 messages Aug 27, 2025 – Sep 18, 2025

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

cover you-still-use-that??: improve breaking changes troubleshooting

From: Kristoffer Haugsbakk <[email protected]>

Based on the recent i-still-use-that reports about whatchanged, improve the error reporting with this command in mind:

1. Give more possible actions instead of just (only) asking them to send an email
2. Hint how to replace their git-whatchanged(1) use with git-log(1)
3. Minor documentation changes

I don’t know how much does matters now that 2.51.0 is out. I guess it depends on when the next maintenance release is and how many platforms will upgrade it? I don’t know anything about that. (But maybe they will do it straight away since they are releasing for the latest version?)

Kristoffer Haugsbakk (4):
usage: help the user help themselves
whatchanged: tell users the git-log(1) equivalent
whatchanged: remove not-even-shorter clause
BreakingChanges: remove claim about whatchanged reports

 Documentation/BreakingChanges.adoc |  2 +-
 Documentation/git-whatchanged.adoc |  8 ++++++--
 builtin/log.c                      |  8 ++++++--
 builtin/pack-redundant.c           |  2 +-
 git-compat-util.h                  |  2 +-
 usage.c                            | 31 +++++++++++++++++++++++-------
 6 files changed, 39 insertions(+), 14 deletions(-)

base-commit: c44beea485f0f2feaf460e2ac87fdd5608d63cf0

Signature · 1 line
2.51.0.11.g23cedd8a747
#1 lore raw

PATCH 1/4 usage: help the user help themselves

From: Kristoffer Haugsbakk <[email protected]>

Give the user a list of suggestions for what to do when they run a deprecated command.

The first order of action will be to check the breaking changes document;[1] this short error message says nothing about why this command is deprecated, and in any case going into any kind of detail might overwhelm the user.

Then they can find out if this has been discussed on the mailing list. Then users who e.g. are using git-whatchanged(1) can learn that this is arguably a plug-in replacement:

    git log <opts> --raw --no-merges

Finally they are invited to send an email to the mailing list.

Also drop the “please add” part in favor of just using the “refusing” die-message; these two would have been right after each other in this new version.

Also drop “Thanks” since it now would require a new paragraph.

[1]: git-scm has a disclaimer for these internal documents that says
that “This information is specific to the Git project”. That’s misleading in this particular case. But users are unlikely to get discouraged from reading about why they (or their programs) cannot run a command any more; it clearly concerns them.

---

Notes (series):
An alternative to linking to git-scm is to move this document to a regular installed man page:

gitbreaking-changes(7)

What do you think?

I would then have to base my topic on the in-flight pw/3.0-commentchar-auto-deprecation, which in turn depends on ps/config-wo-the-repository.

Or just wait a bit for these to settle in.

 usage.c | 21 ++++++++++++++++-----
 1 file changed, 16 insertions(+), 5 deletions(-)
usage.c +16 -5
index 81913236a4a..29988395f19 100644
@@ -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)
 {
@@ -377,12 +378,22 @@ void bug_fl(const char *file, int line, const char *fmt, ...)
 
 NORETURN void you_still_use_that(const char *command_name)
 {
+	struct strbuf percent_encoded = STRBUF_INIT;
+	strbuf_add_percentencode(&percent_encoded,
+				 command_name,
+				 STRBUF_ENCODE_SLASH);
+
 	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 <[email protected]>.  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 <[email protected]>\n"
+		  "\n"),
+		command_name, percent_encoded.buf);
+	strbuf_release(&percent_encoded);
 	die(_("refusing to run without --i-still-use-this"));
 }
Signature · 1 line
2.51.0.11.g23cedd8a747
#2 lore raw

Re: [PATCH 1/4] usage: help the user help themselves

Quoted 1 line

usage: help the user help themselves

I think I’ll change the area to the more pointed:

    you-still-use-that??:
Quoted 13 lines · On Wed, Aug 27, 2025, at 18:29, [email protected] wrote:
usage.c +5 -2
@@ -377,12 +378,22 @@ void bug_fl(const char *file, int line, const char *fmt, ...)
 
 NORETURN void you_still_use_that(const char *command_name)
 {
+	struct strbuf percent_encoded = STRBUF_INIT;
+	strbuf_add_percentencode(&percent_encoded,
+				 command_name,
+				 STRBUF_ENCODE_SLASH);
+
 	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"

usage.c void bug_fl(const char *file, int line, const char *fmt, ...) · line 394

Quoted 10 lines
usage.c +7 -3
-		  "and let us know you still use it by sending an e-mail\n"
-		  "to <[email protected]>.  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 <[email protected]>\n"

Maybe (thinking out loud) this should retain some part of the “let us know you still use this” spirit:

- send an email to <[email protected]> and let us know that you still use this command

Quoted 7 lines
usage.c +3 -1
+		  "\n"),
+		command_name, percent_encoded.buf);
+	strbuf_release(&percent_encoded);
 	die(_("refusing to run without --i-still-use-this"));
 }
--

2.51.0.11.g23cedd8a747

#3 lore raw

Re: [PATCH 1/4] usage: help the user help themselves

Quoted 11 lines · Kristoffer Haugsbakk

usage.c void bug_fl(const char *file, int line, const char *fmt, ...) · line 394

Quoted 9 lines · On Wed, Aug 27, 2025, at 18:29, [email protected] wrote:

fprintf(stderr,
_("'%s' is nominated for removal.\n"

usage.c +7 -0
+               "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 <[email protected]>\n"
Quoted 5 lines

Maybe (thinking out loud) this should retain some part of the “let us know you still use this” spirit:

- send an email to <[email protected]> and let us know that you still use this command

That's still inviting unnecessary emails, isn't it? It would probably be better add the qualification that people should send the email only if they were unable to find any workable replacement. Perhaps:

- send an email to <git@...> to let us know that you still use this command and were unable to determine a suitable replacement

#4 lore raw

Re: [PATCH 1/4] usage: help the user help themselves

Quoted 7 lines · Eric Sunshine

That's still inviting unnecessary emails, isn't it? It would probably be better add the qualification that people should send the email only if they were unable to find any workable replacement. Perhaps:

- send an email to <git@...> to let us know that you still use this command and were unable to determine a suitable replacement

In practice, people will respond to such an instruction by always sending an e-mail. Asking others who sound as if they are promising to give answers when asked is cheaper than investigating themselves ;-).

#5 lore raw

Re: [PATCH 1/4] usage: help the user help themselves

Quoted 5 lines · Junio C Hamano
Quoted 4 lines · Eric Sunshine

That's still inviting unnecessary emails, isn't it? It would probably be better add the qualification that people should send the email only if they were unable to find any workable replacement. Perhaps:

Quoted 7 lines
Quoted 3 lines

- send an email to <git@...> to let us know that you still use this command and were unable to determine a suitable replacement

In practice, people will respond to such an instruction by always sending an e-mail. Asking others who sound as if they are promising to give answers when asked is cheaper than investigating themselves

Thanks for pointing out that my final editing made my suggested wording too succinct. What I really had in mind -- in conjunction with Kristoffer's patch [2/4] which provides hint(s) for replacing the command being retired -- was to reference the provided hints. So, something like this:

- send an email to <...> to let us know
that you still use this command and were unable to determine a suitable replacement using the hints provided here

#6 lore raw

Re: [PATCH 1/4] usage: help the user help themselves

Quoted 7 lines · Eric Sunshine

command being retired -- was to reference the provided hints. So, something like this:

- send an email to <...> to let us know
that you still use this command and were unable to determine a suitable replacement using the hints provided here

Ah, of course, yes, with Kristoffer's update to tell what the alternative is, your phrasing is perfect.

Thanks.

#7 lore raw

Re: [PATCH 1/4] usage: help the user help themselves

Quoted 5 lines · Junio C Hamano
Quoted 3 lines · Eric Sunshine

command being retired -- was to reference the provided hints. So, something like this:

Quoted 7 lines
Quoted 4 lines

- send an email to <...> to let us know
that you still use this command and were unable to determine a suitable replacement using the hints provided here

Ah, of course, yes, with Kristoffer's update to tell what the alternative is, your phrasing is perfect.

I think so too. Just keep in mind that pack-redundant does not have that part.

I think I’ll use that suggestion verbatim.

Signature · 1 line
Kristoffer
#8 lore raw

Re: [PATCH 1/4] usage: help the user help themselves

Quoted 11 lines · Kristoffer Haugsbakk
Quoted 10 lines · Junio C Hamano
Quoted 7 lines · Eric Sunshine

command being retired -- was to reference the provided hints. So, something like this:

- send an email to <...> to let us know
that you still use this command and were unable to determine a suitable replacement using the hints provided here

Quoted 5 lines
Quoted 2 lines

Ah, of course, yes, with Kristoffer's update to tell what the alternative is, your phrasing is perfect.

I think so too. Just keep in mind that pack-redundant does not have that part.

For pack-redundant, isn't it because there is no need to find suitable replacement? That is, instead of finding a redundant pack, all of whose objects are contained in some other packs, which practically is impossible to exist in a non-toy repository, and deal with it yourself, you can let the normal "repack" to eject redundant objects from all the non-kept packs.

Quoted 1 line

I think I’ll use that suggestion verbatim.

Sounds good.

#9 lore raw

Re: [PATCH 1/4] usage: help the user help themselves

In reply to Eric Sunshine
Quoted 20 lines · Eric Sunshine
Quoted 5 lines · Junio C Hamano
Quoted 4 lines · Eric Sunshine

That's still inviting unnecessary emails, isn't it? It would probably be better add the qualification that people should send the email only if they were unable to find any workable replacement. Perhaps:

Quoted 7 lines
Quoted 3 lines

- send an email to <git@...> to let us know that you still use this command and were unable to determine a suitable replacement

In practice, people will respond to such an instruction by always sending an e-mail. Asking others who sound as if they are promising to give answers when asked is cheaper than investigating themselves

Thanks for pointing out that my final editing made my suggested wording too succinct. What I really had in mind -- in conjunction with Kristoffer's patch [2/4] which provides hint(s) for replacing the command being retired -- was to reference the provided hints. So, something like this:

Quoted 4 lines

- send an email to <...> to let us know
that you still use this command and were unable to determine a suitable replacement using the hints provided here

I realize that the changes made by this series are not in any released version yet, but from reading the emails still arriving which argue for retaining the command for reasons of muscle memory or because of its (strong) mnemonic value, I suspect that the hint(s) this series adds may not be complete enough. In particular, the advice this series adds (use git log --raw --no-merges) seems to be primarily aimed at scripted use of the command. But the muscle memory and mnemonic arguments suggest that advice should be given for interactive use, as well, such as proposing that the user can create an alias.

#10 lore raw

Re: [PATCH 1/4] usage: help the user help themselves

(Sent from mobile, might have mistakes)

Quoted 9 lines · Eric Sunshine

I realize that the changes made by this series are not in any released version yet, but from reading the emails still arriving which argue for retaining the command for reasons of muscle memory or because of its (strong) mnemonic value, I suspect that the hint(s) this series adds may not be complete enough. In particular, the advice this series adds (use git log --raw --no-merges) seems to be primarily aimed at scripted use of the command. But the muscle memory and mnemonic arguments suggest that advice should be given for interactive use, as well, such as proposing that the user can create an alias.

Good point. I would suggest discussing it in the breaking changes doc under a new "For Users" section. I would like to avoid expanding the error message too much due to the already mentioned fear of overwhelming folks.

I've seen "troubleshooting" questions from git users who got one of those long and well-described Hints like e.g. detached head and default git init branch when either the msg already spelled eveything out or the msg was purely informational.

Cheers

#11 lore raw

Re: [PATCH 1/4] usage: help the user help themselves

Quoted 11 lines · Kristoffer Haugsbakk
Quoted 9 lines · Eric Sunshine

I realize that the changes made by this series are not in any released version yet, but from reading the emails still arriving which argue for retaining the command for reasons of muscle memory or because of its (strong) mnemonic value, I suspect that the hint(s) this series adds may not be complete enough. In particular, the advice this series adds (use git log --raw --no-merges) seems to be primarily aimed at scripted use of the command. But the muscle memory and mnemonic arguments suggest that advice should be given for interactive use, as well, such as proposing that the user can create an alias.

Quoted 5 lines

Good point. I would suggest discussing it in the breaking changes doc under a new "For Users" section. I would like to avoid expanding the error message too much due to the already mentioned fear of overwhelming folks.

I have doubts that users will consult a document when presented with the message.

Quoted 5 lines

I've seen "troubleshooting" questions from git users who got one of those long and well-described Hints like e.g. detached head and default git init branch when either the msg already spelled eveything out or the msg was purely informational.

I didn't spell it out above, but what I had in mind was something very simple... not at all ong and detailed; for instance:

For interactive use, define a Git alias git whatchanged which runs git log --raw --no-merges.

#12 lore raw

Re: [PATCH 1/4] usage: help the user help themselves

Quoted 11 lines · Eric Sunshine
Quoted 5 lines

I've seen "troubleshooting" questions from git users who got one of those long and well-described Hints like e.g. detached head and default git init branch when either the msg already spelled eveything out or the msg was purely informational.

I didn't spell it out above, but what I had in mind was something very simple... not at all ong and detailed; for instance:

For interactive use, define a Git alias git whatchanged which runs git log --raw --no-merges.

But that won’t work since git-whatchanged(1) is a builtin and you cannot use an alias to shadow a builtin. Now you can suggest minor variations like whatchange. But then people will go back to talking about their finger memory.[1][2][3]

You would need Peff’s:

https://lore.kernel.org/git/[email protected]/

Quoted 2 lines

I wonder if we should loosen the "aliases cannot override builtins" rule for deprecated commands. Perhaps something like the patch below.

† 1: If people are adamant about a command staying exactly as it is for
interactive use, I don’t imagine their tolerance for a slightly
different invocation will be much higher.
† 2: And I wonder about the audience who has been using a command which
was deprecated for twelve years who also need to be reminded about
aliases.
† 3: But there is one whatchange conversion: https://lore.kernel.org/git/CAAn3O_2iHVt5TctvwLLSXm5Nw2wS8e9Xk0is1=k=-qRS=gHVMQ@mail.gmail.com/

Signature · 1 line
Kristoffer Haugsbakk
#13 lore raw

Re: [PATCH 1/4] usage: help the user help themselves

In reply to Eric Sunshine
Quoted 11 lines · Eric Sunshine
Quoted 5 lines

I've seen "troubleshooting" questions from git users who got one of those long and well-described Hints like e.g. detached head and default git init branch when either the msg already spelled eveything out or the msg was purely informational.

I didn't spell it out above, but what I had in mind was something very simple... not at all ong and detailed; for instance:

For interactive use, define a Git alias git whatchanged which runs git log --raw --no-merges.

I think that is good advice, but... it won't do anything until we actually drop the whatchanged command, since until then we'll refuse to override the command (even the crippled --i-still-use-this one).

We'd need something like the patch here:

https://lore.kernel.org/git/[email protected]/

-Peff

#14 lore raw

Re: [PATCH 1/4] usage: help the user help themselves

Quoted 13 lines · Jeff King
Quoted 11 lines · Eric Sunshine
Quoted 5 lines

I've seen "troubleshooting" questions from git users who got one of those long and well-described Hints like e.g. detached head and default git init branch when either the msg already spelled eveything out or the msg was purely informational.

I didn't spell it out above, but what I had in mind was something very simple... not at all ong and detailed; for instance:

For interactive use, define a Git alias git whatchanged which runs git log --raw --no-merges.

Quoted 7 lines

I think that is good advice, but... it won't do anything until we actually drop the whatchanged command, since until then we'll refuse to override the command (even the crippled --i-still-use-this one).

We'd need something like the patch here:

https://lore.kernel.org/git/[email protected]/

Indeed. I saw your patch in the other thread and had it in mind when composing my earlier email, even though I didn't specifically mention it (though I probably should have) since I was more focused on raising the point that -- given the recent spate of muscle-memory / mnemonic-value argument emails -- the existing hints in Kristoffer's patch series may be insufficient to quell future emails.

#15 lore raw

Re: [PATCH 1/4] usage: help the user help themselves

Quoted 2 lines · Eric Sunshine

, even though I didn't specifically mention it (though I probably should have)

I thought Sunshine was supposed to illuminate.

#16 lore raw

Re: [PATCH 1/4] usage: help the user help themselves

In reply to Eric Sunshine
Quoted 12 lines · Eric Sunshine
Quoted 12 lines
Quoted 5 lines

I didn't spell it out above, but what I had in mind was something very simple... not at all ong and detailed; for instance:

For interactive use, define a Git alias git whatchanged which runs git log --raw --no-merges.

I think that is good advice, but... it won't do anything until we actually drop the whatchanged command, since until then we'll refuse to override the command (even the crippled --i-still-use-this one).

We'd need something like the patch here:

Quoted 8 lines
Quoted 1 line

https://lore.kernel.org/git/[email protected]/

Indeed. I saw your patch in the other thread and had it in mind when composing my earlier email, even though I didn't specifically mention it (though I probably should have) since I was more focused on raising the point that -- given the recent spate of muscle-memory / mnemonic-value argument emails -- the existing hints in Kristoffer's patch series may be insufficient to quell future emails.

Heh, I should have figured. I didn't see any response in that thread, so this was my attempt to pawn it off on folks who are more interested in the topic (and working on the advice half). ;)

-Peff

#17 lore raw

Re: [PATCH 1/4] usage: help the user help themselves

In reply to Eric Sunshine
Quoted 11 lines · Eric Sunshine

I realize that the changes made by this series are not in any released version yet, but from reading the emails still arriving which argue for retaining the command for reasons of muscle memory or because of its (strong) mnemonic value, I suspect that the hint(s) this series adds may not be complete enough. In particular, the advice this series adds (use git log --raw --no-merges) seems to be primarily aimed at scripted use of the command. But the muscle memory and mnemonic arguments suggest that advice should be given for interactive use, as well,[—]

such as proposing that the user can create an alias.

Sorry, I completely missed the part (now a week ago) where you clearly were alluding to making a whatchanged-alias to begin with, which made my initial response here quite beside the point.

I shouldn’t respond to this list on mobile.

Signature · 1 line
Kristoffer Haugsbakk
#18 lore raw

Re: [PATCH 1/4] usage: help the user help themselves

In reply to Kristoffer Haugsbakk
Quoted 5 lines · Kristoffer Haugsbakk
Quoted 1 line

usage: help the user help themselves

I think I’ll change the area to the more pointed:

    you-still-use-that??:

Ah, that makes sense.

Quoted 28 lines · On Wed, Aug 27, 2025, at 18:29, [email protected] wrote:
Quoted 13 lines
usage.c +5 -2
@@ -377,12 +378,22 @@ void bug_fl(const char *file, int line, const char *fmt, ...)
 
 NORETURN void you_still_use_that(const char *command_name)
 {
+	struct strbuf percent_encoded = STRBUF_INIT;
+	strbuf_add_percentencode(&percent_encoded,
+				 command_name,
+				 STRBUF_ENCODE_SLASH);
+
 	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"

usage.c void bug_fl(const char *file, int line, const char *fmt, ...) · line 394

Quoted 10 lines
usage.c +7 -3
-		  "and let us know you still use it by sending an e-mail\n"
-		  "to <[email protected]>.  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 <[email protected]>\n"

Maybe (thinking out loud) this should retain some part of the “let us know you still use this” spirit:

Quoted 2 lines

- send an email to <[email protected]> and let us know that you still use this command

We do not actually want to bother individual users about reporting.

We may want to catch third-party tools (like we heard a problem with Jenkins from its users), so

- notify <[email protected]> mailing list if you are a lead developer of a widely used tool, especially if you heard a breakage report on your tool from your users due to this message.

perhaps? If we know that version A of tool X (or older) hasn't migrated, users of the same tool will see the same breakage, and some of them may come to this list. If we know about their problem ahead of time, we'd be better prepared to give them a definitive "Yes, we know version A of tool X (or older) still uses it, and the developer of tool X is aware of the problem."

Hopefully ;-)

#19 lore raw

PATCH 2/4 whatchanged: tell users the git-log(1) equivalent

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

There have been quite a few --i-still-use-this user reports since Git 2.51.0 was released.[1][2] And it doesn’t seem like they are reading the man page about the git-log(1) equivalent.

Tell them what options to plug into git-log(1). That template produces almost the same output[3] and is arguably a plug-in replacement. Concretely, add an optional hint argument so that we can use it right after the initial error line.

Also mention the same concrete options in the documentation while we’re at it.

[1]: E.g.,
• https://lore.kernel.org/git/[email protected]/
• https://lore.kernel.org/git/[email protected]/#t
• https://lore.kernel.org/git/[email protected]/#t
• https://lore.kernel.org/git/[email protected]/
[2] The error message on 2.51.0 does tell them to report it, unconditionally [3]: https://lore.kernel.org/git/[email protected]/

---
 Documentation/git-whatchanged.adoc |  6 +++++-
 builtin/log.c                      |  8 ++++++--
 builtin/pack-redundant.c           |  2 +-
 git-compat-util.h                  |  2 +-
 usage.c                            | 14 ++++++++++----
 5 files changed, 23 insertions(+), 9 deletions(-)
Documentation/git-whatchanged.adoc +5 -1
index d21484026fe..e71d2aa2d27 100644
@@ -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
builtin/log.c +6 -2
index c2f8bbf8630..2f9e5e5a898 100644
@@ -542,8 +542,12 @@ int cmd_whatchanged(int argc,
 	opt.revarg_opt = REVARG_COMMITTISH;
 	cmd_log_init(argc, argv, prefix, &rev, &opt, &cfg);
 
-	if (!cfg.i_still_use_this)
-		you_still_use_that("git whatchanged");
+        if (!cfg.i_still_use_this)
+		you_still_use_that("git whatchanged",
+				   _("\n"
+				     "hint: You can replace 'git whatchanged <opts>' with:\n"
+				     "    git log <opts> --raw --no-merges\n"
+				     "\n"));
 
 	if (!rev.diffopt.output_format)
 		rev.diffopt.output_format = DIFF_FORMAT_RAW;
builtin/pack-redundant.c +1 -1
index fe81c293e3a..5d5ae4afa28 100644
@@ -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();
git-compat-util.h +1 -1
index 9408f463e31..398e0fac4fa 100644
@@ -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
usage.c +10 -4
index 29988395f19..c661561b149 100644
@@ -376,7 +376,8 @@ 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,
@@ -384,8 +385,13 @@ NORETURN void you_still_use_that(const char *command_name)
 				 STRBUF_ENCODE_SLASH);
 
 	fprintf(stderr,
-		_("'%s' is nominated for removal.\n"
-		  "If you still use this command, here's what you can do:\n"
+		_("'%s' is nominated for removal.\n"), command_name);
+
+	if (hint)
+		fputs(hint, stderr);
+
+	fprintf(stderr,
+		_("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"
@@ -393,7 +399,7 @@ NORETURN void you_still_use_that(const char *command_name)
 		  "  help you: https://lore.kernel.org/git/?q=%s\n"
 		  "- send an email to <[email protected]>\n"
 		  "\n"),
-		command_name, percent_encoded.buf);
+		percent_encoded.buf);
 	strbuf_release(&percent_encoded);
 	die(_("refusing to run without --i-still-use-this"));
 }
Signature · 1 line
2.51.0.11.g23cedd8a747
#20 lore raw

Re: [PATCH 2/4] whatchanged: tell users the git-log(1) equivalent

builtin/log.c int cmd_whatchanged(int argc, · line 546

Quoted 4 lines · [email protected] writes:
builtin/log.c +2 -2
-	if (!cfg.i_still_use_this)
-		you_still_use_that("git whatchanged");
+        if (!cfg.i_still_use_this)
+		you_still_use_that("git whatchanged",

I spot a whitespace breakage here. I didn't check if there are other instances. Please check your editor settings (or run "git diff --check --cached" before committing yoru changes, or both).

Thanks.

#21 lore raw

Re: [PATCH 2/4] whatchanged: tell users the git-log(1) equivalent

Quoted 8 lines · Junio C Hamano

builtin/log.c int cmd_whatchanged(int argc, · line 546

Quoted 4 lines · [email protected] writes:
builtin/log.c +2 -2
-	if (!cfg.i_still_use_this)
-		you_still_use_that("git whatchanged");
+        if (!cfg.i_still_use_this)
+		you_still_use_that("git whatchanged",

I spot a whitespace breakage here. I didn't check if there are

Sorry, I read the patch backwards. You are fixing an existing whitespace breakage, which is very much appreciated, as this is immediate vicinity of the real change of this topic.

Thanks.

#22 lore raw

Re: [PATCH 2/4] whatchanged: tell users the git-log(1) equivalent

Quoted 11 lines · Junio C Hamano
Quoted 8 lines · Junio C Hamano

builtin/log.c int cmd_whatchanged(int argc, · line 546

Quoted 4 lines · [email protected] writes:
builtin/log.c +2 -2
-	if (!cfg.i_still_use_this)
-		you_still_use_that("git whatchanged");
+        if (!cfg.i_still_use_this)
+		you_still_use_that("git whatchanged",

I spot a whitespace breakage here. I didn't check if there are

Quoted 3 lines

Sorry, I read the patch backwards. You are fixing an existing whitespace breakage, which is very much appreciated, as this is immediate vicinity of the real change of this topic.

No, I did inadvertently replace the tab indents with spaces. I’ll have to configure my editor better. I’ll fix it.

(ci/check-whitespace.sh v2.51.0 turned out to be helpful for me here)

Signature · 1 line
Kristoffer Haugsbakk
#23 lore raw

Re: [PATCH 2/4] whatchanged: tell users the git-log(1) equivalent

In reply to [email protected]
Quoted 4 lines · On Wed, Aug 27, 2025, at 18:29, [email protected] wrote:

From: Kristoffer Haugsbakk <[email protected]>
[snip]
[1]: E.g.,
•

Quoted 8 lines

https://lore.kernel.org/git/[email protected]/
•
https://lore.kernel.org/git/[email protected]/#t
•
https://lore.kernel.org/git/[email protected]/#t
•
https://lore.kernel.org/git/[email protected]/
[2] The error message on 2.51.0 does tell them to report it, unconditionally

Missing colon.

Quoted 1 line

[3]: https://lore.kernel.org/git/[email protected]/

I’ll expand on this.

[3]: You only get different outputs if you happen to have empty
commits (no changes)[4]
[4]: https://lore.kernel.org/git/[email protected]/

Signature · 1 line
Kristoffer
#24 lore raw

PATCH 3/4 whatchanged: remove not-even-shorter clause

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

The closest equivalent is git log --raw --no-merges.

Also change to “defaults” (implicit plural).

---
 Documentation/git-whatchanged.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/git-whatchanged.adoc +1 -1
index e71d2aa2d27..436e219b7d0 100644
@@ -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
 -----------
Signature · 1 line
2.51.0.11.g23cedd8a747
#25 lore raw

PATCH 4/4 BreakingChanges: remove claim about whatchanged reports

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

This was written in e836757e14b (whatschanged: list it in BreakingChanges document, 2025-05-12) which was on the same topic that added the --i-still-use-this requirement.[1]

Maybe it was a work-in-progress comment/status.

[1]: jc/you-still-use-whatchanged

---

Notes (series):
Footnote solely to avoid awkward paragraph wrapping...

 Documentation/BreakingChanges.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/BreakingChanges.adoc +1 -1
index f8d2eba061c..c4985163c3c 100644
@@ -235,7 +235,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.
 
Signature · 1 line
2.51.0.11.g23cedd8a747
#26 lore raw
In reply to [email protected]
Quoted 9 lines · [email protected] writes:

From: Kristoffer Haugsbakk <[email protected]>

Based on the recent i-still-use-that reports about whatchanged, improve the error reporting with this command in mind:

1. Give more possible actions instead of just (only) asking them to send an email
2. Hint how to replace their git-whatchanged(1) use with git-log(1)
3. Minor documentation changes

Nice. Especially the attention to minor details shown in [3/4] and [4/4] is very much appreciated.

#27 lore raw

v2 cover you-still-use-that??: improve breaking changes troubleshooting

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

Based on the recent i-still-use-that reports about whatchanged, improve the error reporting with this command in mind:

1. Give more possible actions instead of just (only) asking them to send an email
2. Hint how to replace their git-whatchanged(1) use with git-log(1)
3. Minor documentation changes

Also:[1] an alternative to linking to www.git-scm.com (in patch 1/4) is to move this document to a regular installed man page:

gitbreaking-changes(7)

[1]: Not mentioned on the v1 cover letter

§ What the errors now look like

    $ ./git pack-redundant
    'git pack-redundant' is nominated for removal.
    If you still use this command, here's what you can do:

- read https://git-scm.com/docs/BreakingChanges.html
- check if anyone has discussed this on the mailing list and if they came up with something that can help you: https://lore.kernel.org/git/?q=git%20pack-redundant
- send an email to <[email protected]> to let us know that you still use this command and were unable to determine a suitable replacement

fatal: refusing to run without --i-still-use-this $ ./git whatchanged
'git whatchanged' is nominated for removal.

hint: You can replace 'git whatchanged <opts>' with:
git log <opts> --raw --no-merges

If you still use this command, here's what you can do:

- read https://git-scm.com/docs/BreakingChanges.html
- check if anyone has discussed this on the mailing list and if they came up with something that can help you: https://lore.kernel.org/git/?q=git%20whatchanged
- send an email to <[email protected]> to let us know that you still use this command and were unable to determine a suitable replacement

fatal: refusing to run without --i-still-use-this

§ Changes in v2

These are also posted on the patches.

• Patch 1/4:
• Change send-an-email bullet point
• Change the area
• Use www.git-scm.com, not simply git-scm
• Patch 2/4:
• Fix whitespace error (I should have used `ci/check-whitespace.sh

    v2.51.0`)

• Add missing colon (:) to footnote
• Expand on footnote; a sentence is enough to summarize the difference

Kristoffer Haugsbakk (4):
you-still-use-that??: help the user help themselves
whatchanged: tell users the git-log(1) equivalent
whatchanged: remove not-even-shorter clause
BreakingChanges: remove claim about whatchanged reports

 Documentation/BreakingChanges.adoc |  2 +-
 Documentation/git-whatchanged.adoc |  8 ++++++--
 builtin/log.c                      |  6 +++++-
 builtin/pack-redundant.c           |  2 +-
 git-compat-util.h                  |  2 +-
 usage.c                            | 33 +++++++++++++++++++++++-------
 6 files changed, 40 insertions(+), 13 deletions(-)

Interdiff against v1:

builtin/log.c +1 -1
index 2f9e5e5a898..5dbb90c014d 100644
@@ -542,7 +542,7 @@ int cmd_whatchanged(int argc,
 	opt.revarg_opt = REVARG_COMMITTISH;
 	cmd_log_init(argc, argv, prefix, &rev, &opt, &cfg);
 
-        if (!cfg.i_still_use_this)
+	if (!cfg.i_still_use_this)
 		you_still_use_that("git whatchanged",
 				   _("\n"
 				     "hint: You can replace 'git whatchanged <opts>' with:\n"
usage.c +3 -1
index c661561b149..7545a616453 100644
@@ -397,7 +397,9 @@ NORETURN void you_still_use_that(const char *command_name, const char *hint)
 		  "- 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 <[email protected]>\n"
+		  "- send an email to <[email protected]> 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);

Range-diff against v1:
1: e81023edb2d ! 1: 6803e2cc6c3 usage: help the user help themselves
@@ Metadata
Author: Kristoffer Haugsbakk <[email protected]>

## Commit message ##
- usage: help the user help themselves
+ you-still-use-that??: help the user help themselves

Give the user a list of suggestions for what to do when they run a deprecated command.
@@ Commit message

Also drop “Thanks” since it now would require a new paragraph.

- [1]: git-scm has a disclaimer for these internal documents that says
- that “This information is specific to the Git project”. That’s
+ [1]: www.git-scm.com has a disclaimer for these internal documents that
+ says that “This information is specific to the Git project”. That’s
misleading in this particular case. But users are unlikely to get
- discouraged from reading about why they (or their programs) cannot
- run a command any more; it clearly concerns them.
+ discouraged from reading about why they (or their programs) cannot run a
+ command any more; it clearly concerns them.

+ Helped-by: Eric Sunshine <[email protected]>

## Notes (series) ##
- An alternative to linking to git-scm is to move this document to a
- regular installed man page:
+ v2:
+
+ I had second thoughts about the bullet point about send-an-email.
+ Change it to the one Eric Sunshine proposed;[1] make sure to spell
+ out that you can send an email conditioned on not finding a suitable
+ replacement.
+
+ Also change the area to something more pointed.
+
+ And also use a clear URL to refer to www.git-scm.com.
+
+ [1]: https://lore.kernel.org/git/CAPig+cQkVP57n_FE6dJ0uxvai-J7usxKFp8gzfEbPY=Ytsd6=Q@mail.gmail.com/
+
+ • Change send-an-email bullet point
+ • Change the area
+ • Use www.git-scm.com, not simply git-scm
+
+ v1:
+
+ An alternative to linking to www.git-scm.com is to move this document to
+ a regular installed man page:

gitbreaking-changes(7)

@@ usage.c: void bug_fl(const char *file, int line, const char *fmt, ...)
+ "- 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 <[email protected]>\n"
++ "- send an email to <[email protected]> to let us\n"
++ " know that you still use this command and were unable\n"
++ " to determine a suitable replacement\n"
+ "\n"),
+ command_name, percent_encoded.buf);
+ strbuf_release(&percent_encoded);
2: 5407c0955af ! 2: 2f3ac952980 whatchanged: tell users the git-log(1) equivalent
@@ Commit message
• https://lore.kernel.org/git/[email protected]/#t
• https://lore.kernel.org/git/[email protected]/#t
• https://lore.kernel.org/git/[email protected]/
- [2] The error message on 2.51.0 does tell them to report it, unconditionally
- [3]: https://lore.kernel.org/git/[email protected]/
+ [2]: The error message on 2.51.0 does tell them to report it, unconditionally
+ [3]: You only get different outputs if you happen to have empty
+ commits (no changes)[4]
+ [4]: https://lore.kernel.org/git/[email protected]/

+
+ ## Notes (series) ##
+ v2:
+
+ Review found a whitespace error in the prev. patch version. I found a
+ broken footnote and wanted to expand on the last footnote.
+
+ • Fix whitespace error (I should have used `ci/check-whitespace.sh
+ v2.51.0`)
+ • Add missing colon (:) to footnote
+ • Expand on footnote; a sentence is enough to summarize the difference
+
## Documentation/git-whatchanged.adoc ##
@@ Documentation/git-whatchanged.adoc: Shows commit logs and diff output each commit introduces.

@@ Documentation/git-whatchanged.adoc: Shows commit logs and diff output each commi

## builtin/log.c ##
@@ builtin/log.c: int cmd_whatchanged(int argc,
- opt.revarg_opt = REVARG_COMMITTISH;
cmd_log_init(argc, argv, prefix, &rev, &opt, &cfg);

-- if (!cfg.i_still_use_this)
+ if (!cfg.i_still_use_this)
- you_still_use_that("git whatchanged");
-+ if (!cfg.i_still_use_this)
+ you_still_use_that("git whatchanged",
+ _("\n"
+ "hint: You can replace 'git whatchanged <opts>' with:\n"
@@ usage.c: NORETURN void you_still_use_that(const char *command_name)
"- read https://git-scm.com/docs/BreakingChanges.html\n"
"- check if anyone has discussed this on the mailing\n"
@@ usage.c: NORETURN void you_still_use_that(const char *command_name)
- " help you: https://lore.kernel.org/git/?q=%s\n"
- "- send an email to <[email protected]>\n"
+ " know that you still use this command and were unable\n"
+ " to determine a suitable replacement\n"
"\n"),
- command_name, percent_encoded.buf);
+ percent_encoded.buf);

3:  5fad164d7d1 = 3:  a074e7be422 whatchanged: remove not-even-shorter clause
4:  f1bf0ea3846 = 4:  9196c3c7e33 BreakingChanges: remove claim about whatchanged reports

base-commit: c44beea485f0f2feaf460e2ac87fdd5608d63cf0

Signature · 1 line
2.51.0.16.gcd94ab5bf81
#28 lore raw

PATCH v2 1/4 you-still-use-that??: help the user help themselves

From: Kristoffer Haugsbakk <[email protected]>

Give the user a list of suggestions for what to do when they run a deprecated command.

The first order of action will be to check the breaking changes document;[1] this short error message says nothing about why this command is deprecated, and in any case going into any kind of detail might overwhelm the user.

Then they can find out if this has been discussed on the mailing list. Then users who e.g. are using git-whatchanged(1) can learn that this is arguably a plug-in replacement:

    git log <opts> --raw --no-merges

Finally they are invited to send an email to the mailing list.

Also drop the “please add” part in favor of just using the “refusing” die-message; these two would have been right after each other in this new version.

Also drop “Thanks” since it now would require a new paragraph.

[1]: www.git-scm.com has a disclaimer for these internal documents that
says that “This information is specific to the Git project”. That’s misleading in this particular case. But users are unlikely to get discouraged from reading about why they (or their programs) cannot run a command any more; it clearly concerns them.

---

Notes (series):
v2:

I had second thoughts about the bullet point about send-an-email. Change it to the one Eric Sunshine proposed;[1] make sure to spell out that you can send an email conditioned on not finding a suitable replacement.

Also change the area to something more pointed.

And also use a clear URL to refer to www.git-scm.com.

[1]: https://lore.kernel.org/git/CAPig+cQkVP57n_FE6dJ0uxvai-J7usxKFp8gzfEbPY=Ytsd6=Q@mail.gmail.com/

• Change send-an-email bullet point
• Change the area
• Use www.git-scm.com, not simply git-scm

v1:

An alternative to linking to www.git-scm.com is to move this document to a regular installed man page:

gitbreaking-changes(7)

What do you think?

I would then have to base my topic on the in-flight pw/3.0-commentchar-auto-deprecation, which in turn depends on ps/config-wo-the-repository.

Or just wait a bit for these to settle in.

 usage.c | 23 ++++++++++++++++++-----
 1 file changed, 18 insertions(+), 5 deletions(-)
usage.c +18 -5
index 81913236a4a..35dc57eb07e 100644
@@ -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)
 {
@@ -377,12 +378,24 @@ void bug_fl(const char *file, int line, const char *fmt, ...)
 
 NORETURN void you_still_use_that(const char *command_name)
 {
+	struct strbuf percent_encoded = STRBUF_INIT;
+	strbuf_add_percentencode(&percent_encoded,
+				 command_name,
+				 STRBUF_ENCODE_SLASH);
+
 	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 <[email protected]>.  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 <[email protected]> to let us\n"
+		  "  know that you still use this command and were unable\n"
+		  "  to determine a suitable replacement\n"
+		  "\n"),
+		command_name, percent_encoded.buf);
+	strbuf_release(&percent_encoded);
 	die(_("refusing to run without --i-still-use-this"));
 }
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#29 lore raw

PATCH v2 2/4 whatchanged: tell users the git-log(1) equivalent

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

There have been quite a few --i-still-use-this user reports since Git 2.51.0 was released.[1][2] And it doesn’t seem like they are reading the man page about the git-log(1) equivalent.

Tell them what options to plug into git-log(1). That template produces almost the same output[3] and is arguably a plug-in replacement. Concretely, add an optional hint argument so that we can use it right after the initial error line.

Also mention the same concrete options in the documentation while we’re at it.

[1]: E.g.,
• https://lore.kernel.org/git/[email protected]/
• https://lore.kernel.org/git/[email protected]/#t
• https://lore.kernel.org/git/[email protected]/#t
• https://lore.kernel.org/git/[email protected]/
[2]: The error message on 2.51.0 does tell them to report it, unconditionally [3]: You only get different outputs if you happen to have empty
commits (no changes)[4]
[4]: https://lore.kernel.org/git/[email protected]/

---

Notes (series):
v2:

Review found a whitespace error in the prev. patch version. I found a broken footnote and wanted to expand on the last footnote.

• Fix whitespace error (I should have used ci/check-whitespace.sh v2.51.0)
• Add missing colon (:) to footnote
• Expand on footnote; a sentence is enough to summarize the difference

 Documentation/git-whatchanged.adoc |  6 +++++-
 builtin/log.c                      |  6 +++++-
 builtin/pack-redundant.c           |  2 +-
 git-compat-util.h                  |  2 +-
 usage.c                            | 14 ++++++++++----
 5 files changed, 22 insertions(+), 8 deletions(-)
Documentation/git-whatchanged.adoc +5 -1
index d21484026fe..e71d2aa2d27 100644
@@ -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
builtin/log.c +5 -1
index c2f8bbf8630..5dbb90c014d 100644
@@ -543,7 +543,11 @@ 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"
+				     "    git log <opts> --raw --no-merges\n"
+				     "\n"));
 
 	if (!rev.diffopt.output_format)
 		rev.diffopt.output_format = DIFF_FORMAT_RAW;
builtin/pack-redundant.c +1 -1
index fe81c293e3a..5d5ae4afa28 100644
@@ -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();
git-compat-util.h +1 -1
index 9408f463e31..398e0fac4fa 100644
@@ -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
usage.c +10 -4
index 35dc57eb07e..7545a616453 100644
@@ -376,7 +376,8 @@ 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,
@@ -384,8 +385,13 @@ NORETURN void you_still_use_that(const char *command_name)
 				 STRBUF_ENCODE_SLASH);
 
 	fprintf(stderr,
-		_("'%s' is nominated for removal.\n"
-		  "If you still use this command, here's what you can do:\n"
+		_("'%s' is nominated for removal.\n"), command_name);
+
+	if (hint)
+		fputs(hint, stderr);
+
+	fprintf(stderr,
+		_("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"
@@ -395,7 +401,7 @@ NORETURN void you_still_use_that(const char *command_name)
 		  "  know that you still use this command and were unable\n"
 		  "  to determine a suitable replacement\n"
 		  "\n"),
-		command_name, percent_encoded.buf);
+		percent_encoded.buf);
 	strbuf_release(&percent_encoded);
 	die(_("refusing to run without --i-still-use-this"));
 }
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#30 lore raw

PATCH v2 3/4 whatchanged: remove not-even-shorter clause

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

The closest equivalent is git log --raw --no-merges.

Also change to “defaults” (implicit plural).

---
 Documentation/git-whatchanged.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/git-whatchanged.adoc +1 -1
index e71d2aa2d27..436e219b7d0 100644
@@ -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
 -----------
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#31 lore raw

PATCH v2 4/4 BreakingChanges: remove claim about whatchanged reports

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

This was written in e836757e14b (whatschanged: list it in BreakingChanges document, 2025-05-12) which was on the same topic that added the --i-still-use-this requirement.[1]

Maybe it was a work-in-progress comment/status.

[1]: jc/you-still-use-whatchanged

---

Notes (series):
Footnote solely to avoid awkward paragraph wrapping...

 Documentation/BreakingChanges.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/BreakingChanges.adoc +1 -1
index f8d2eba061c..c4985163c3c 100644
@@ -235,7 +235,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.
 
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#32 lore raw

v3 cover you-still-use-that??: improve breaking changes troubleshooting

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

Based on the recent i-still-use-that reports about whatchanged, improve the error reporting with this command in mind:

1. Give more possible actions instead of just (only) asking them to send an email
2. Hint how to replace their git-whatchanged(1) use with git-log(1) or an alias whatchanged (you can alias deprecated commands now)
3. Minor documentation changes
4. Add deprecated to git --list-cmds

§ What the errors now look like

    $ /git whatchanged
    'git whatchanged' is nominated for removal.

hint: You can replace 'git whatchanged <opts>' with:
hint: git log <opts> --raw --no-merges
hint: Or make an alias:
hint: git config set --global alias.whatchanged 'log --raw --no-merges'

If you still use this command, here's what you can do:

- read https://git-scm.com/docs/BreakingChanges.html
- check if anyone has discussed this on the mailing list and if they came up with something that can help you: https://lore.kernel.org/git/?q=git%20whatchanged
- send an email to <[email protected]> to let us know that you still use this command and were unable to determine a suitable replacement

fatal: refusing to run without --i-still-use-this $ git pack-redundant
'git pack-redundant' is nominated for removal. If you still use this command, here's what you can do:

- read https://git-scm.com/docs/BreakingChanges.html
- check if anyone has discussed this on the mailing list and if they came up with something that can help you: https://lore.kernel.org/git/?q=git%20pack-redundant
- send an email to <[email protected]> to let us know that you still use this command and were unable to determine a suitable replacement

fatal: refusing to run without --i-still-use-this

§ Changes in v3

Patches 1–4 are new:

• Make aliases which shadow deprecated builtins possible (based on Peff’s patch)
• This was based on Eric’s prompt[1]
• Add deprecated category to git --list-cmds

Then the patch “whatchanged: tell users the git-log(1) equivalent” is changed to add the now-possible aliasing.

🔗 1: https://lore.kernel.org/git/CAPig+cSL=-gD5+WomF7-hYjVJ_PH0m+0i8g3F=E_U3k=QNHr8Q@mail.gmail.com/

Kristoffer Haugsbakk (8):
git: add deprecated category to --list-cmds
git: make the two loops look more symmetric
git: allow alias-shadowing deprecated builtins
t0014: test shadowing of aliases for a sample of builtins you-still-use-that??: help the user help themselves
whatchanged: tell users the git-log(1) equivalent
whatchanged: remove not-even-shorter clause
BreakingChanges: remove claim about whatchanged reports

 Documentation/BreakingChanges.adoc |  2 +-
 Documentation/config/alias.adoc    |  3 +-
 Documentation/git-whatchanged.adoc |  8 +++--
 Documentation/git.adoc             |  3 +-
 builtin/log.c                      |  8 ++++-
 builtin/pack-redundant.c           |  2 +-
 git-compat-util.h                  |  2 +-
 git.c                              | 55 ++++++++++++++++++++++++------
 t/t0014-alias.sh                   | 34 ++++++++++++++++++
 usage.c                            | 33 ++++++++++++++----
 10 files changed, 124 insertions(+), 26 deletions(-)

Interdiff against v2:

Documentation/config/alias.adoc +2 -1
index 2c5db0ad842..3c8fab3a95c 100644
@@ -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.
 +
Documentation/git.adoc +2 -1
index 743b7b00e4d..a2f0838b168 100644
@@ -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
builtin/log.c +3 -1
index 5dbb90c014d..1d1e6e9130a 100644
@@ -546,7 +546,9 @@ int cmd_whatchanged(int argc,
 		you_still_use_that("git whatchanged",
 				   _("\n"
 				     "hint: You can replace 'git whatchanged <opts>' with:\n"
-				     "    git log <opts> --raw --no-merges\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)
git.c +44 -11

116 lines not on this page · raw message

t/t0014-alias.sh +34 -0
index 854d59ec58c..bf7e6512bb1 100755
@@ -55,4 +55,38 @@ 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
+'
+
+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

Range-diff against v2:

-:  ----------- > 1:  bdc683a92b3 git: add `deprecated` category to --list-cmds
-:  ----------- > 2:  183dd68d09d git: make the two loops look more symmetric
-:  ----------- > 3:  eec01cbac16 git: allow alias-shadowing deprecated builtins
-:  ----------- > 4:  80fb02caeeb t0014: test shadowing of aliases for a sample of builtins
1:  6803e2cc6c3 = 5:  d25ee26f989 you-still-use-that??: help the user help themselves
2:  2f3ac952980 ! 6:  50621a0748f whatchanged: tell users the git-log(1) equivalent
    @@ Commit message
     
     
      ## Notes (series) ##
    +    v3:
    +
    +    Add an alias hint now that that is possible.  Also prefix each hint-line
    +    with `hint: `.
    +
         v2:
     
         Review found a whitespace error in the prev. patch version.  I found a
    @@ builtin/log.c: int cmd_whatchanged(int argc,
     +		you_still_use_that("git whatchanged",
     +				   _("\n"
     +				     "hint: You can replace 'git whatchanged <opts>' with:\n"
    -+				     "    git log <opts> --raw --no-merges\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)
3:  a074e7be422 = 7:  812c9870f1b whatchanged: remove not-even-shorter clause
4:  9196c3c7e33 = 8:  0d23a4badf0 BreakingChanges: remove claim about whatchanged reports

base-commit: c44beea485f0f2feaf460e2ac87fdd5608d63cf0

Signature · 1 line
2.51.0.16.gcd94ab5bf81
#33 lore raw

PATCH v3 1/8 git: add `deprecated` category to --list-cmds

From: Kristoffer Haugsbakk <[email protected]>

With 145 builtin commands (according to git --list-cmds=builtins), users are probably not keeping on top of which ones (if any) are deprecated.

Let’s expand the experimental --list-cmds[1] to allow users and programs to query for this information. We will also use this in an upcoming commit to assert that all deprecated commands will have been covered in some manner.

[1]: Using something which is experimental to query for deprecations is
perhaps not the most ideal approach, but it is simple to implement and better than having to scan the documentation

---

Notes (series):
v3 (new):

This is something I wanted to submit independently until the point about aliasing builtins was brought up.[1] It will help (in a small way) with the upcoming patch “git: allow alias-shadowing deprecated builtins”.

By the way: should command-list.txt be updated in some way (I didn’t know what way?)

🔗 1: https://lore.kernel.org/git/[email protected]/T/#mee19f8d39572f9021f9d3000758e87b6c32c967c

 Documentation/git.adoc |  3 ++-
 git.c                  | 38 +++++++++++++++++++++++++++-----------
 2 files changed, 29 insertions(+), 12 deletions(-)
Documentation/git.adoc +2 -1
index 743b7b00e4d..a2f0838b168 100644
@@ -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
git.c +27 -11
index 83eac0aeab7..87d61f12594 100644
@@ -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,13 @@ const char git_more_info_string[] =
 
 static int use_pager = -1;
 
-static void list_builtins(struct string_list *list, unsigned int exclude_option);
+/*
+ * 'include_option' and 'exclude_option' are mutually exclusive.
+ *
+ * The default ('!include_option') is to include everything
+ * except those filtered out by '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 +95,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 +106,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 +331,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);
@@ -590,7 +599,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 },
@@ -647,7 +656,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 },
@@ -668,13 +677,20 @@ 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)
 {
-	for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {
-		if (exclude_option &&
-		    (commands[i].option & exclude_option))
-			continue;
-		string_list_append(out, commands[i].cmd);
+	if (include_option && exclude_option)
+		BUG("'include_option' and 'exclude_option' are mutually exclusive");
+	if (include_option) {
+		for (size_t i = 0; i < ARRAY_SIZE(commands); i++)
+			if (commands[i].option & include_option)
+				string_list_append(out, commands[i].cmd);
+	} else {
+		for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {
+			if (commands[i].option & exclude_option)
+				continue;
+			string_list_append(out, commands[i].cmd);
+		}
 	}
 }
 
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#34 lore raw

Re: [PATCH v3 1/8] git: add `deprecated` category to --list-cmds

Quoted 9 lines · On Mon, Sep 08, 2025 at 05:36:12PM +0200, [email protected]…

Notes (series):
v3 (new):

This is something I wanted to submit independently until the point about aliasing builtins was brought up.[1] It will help (in a small way) with the upcoming patch “git: allow alias-shadowing deprecated builtins”.

By the way: should command-list.txt be updated in some way (I didn’t know what way?)

I think it would be a good idea to introduce a new category for deprecated commands. After all, next to planning for the removal we should also actively discourage the use of such commands. But if they are still prominently featured in git(1) then people might be misled and use them, only to get a deprecation warning thrown at them.

Quoted 6 lines
git.c +0 -0
index 83eac0aeab7..87d61f12594 100644
@@ -51,7 +52,13 @@ const char git_more_info_string[] =
 

git.c const char git_more_info_string[] = · line 61

Quoted 10 lines
git.c +7 -1
 static int use_pager = -1;
 
-static void list_builtins(struct string_list *list, unsigned int exclude_option);
+/*
+ * 'include_option' and 'exclude_option' are mutually exclusive.
+ *
+ * The default ('!include_option') is to include everything
+ * except those filtered out by 'exclude_option'.
+ */
+static void list_builtins(struct string_list *list, unsigned int include_option, unsigned int exclude_option);

Nit: let's wrap this overly long line.

git.c int is_builtin(const char *s) · line 680

Quoted 6 lines
git.c +1 -1
@@ -668,13 +677,20 @@ 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)

Same here.

Quoted 10 lines
git.c +4 -5
 {
-	for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {
-		if (exclude_option &&
-		    (commands[i].option & exclude_option))
-			continue;
-		string_list_append(out, commands[i].cmd);
+	if (include_option && exclude_option)
+		BUG("'include_option' and 'exclude_option' are mutually exclusive");
+	if (include_option) {
+		for (size_t i = 0; i < ARRAY_SIZE(commands); i++)

git.c int is_builtin(const char *s) · line 695

Quoted 10 lines
git.c +8 -0
+			if (commands[i].option & include_option)
+				string_list_append(out, commands[i].cmd);
+	} else {
+		for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {
+			if (commands[i].option & exclude_option)
+				continue;
+			string_list_append(out, commands[i].cmd);
+		}
 	}
 }

We could combine these two loops into one:

    for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {
        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);
    }

Which results in a bit less code duplication.

patrick

#35 lore raw

Re: [PATCH v3 1/8] git: add `deprecated` category to --list-cmds

Quoted 9 lines · Patrick Steinhardt
Quoted 7 lines · On Mon, Sep 08, 2025 at 05:36:12PM +0200, [email protected]…

Notes (series):
v3 (new):

This is something I wanted to submit independently until the point about aliasing builtins was brought up.[1] It will help (in a small way) with the upcoming patch “git: allow alias-shadowing deprecated builtins”.

Quoted 8 lines
Quoted 2 lines

By the way: should command-list.txt be updated in some way (I didn’t know what way?)

I think it would be a good idea to introduce a new category for deprecated commands. After all, next to planning for the removal we should also actively discourage the use of such commands. But if they are still prominently featured in git(1) then people might be misled and use them, only to get a deprecation warning thrown at them.

Thanks. I’ll add your Ack if that’s okay.

Quoted 4 lines
Quoted 2 lines

[snip]
+static void list_builtins(struct string_list *list, unsigned int include_option, unsigned int exclude_option);

Nit: let's wrap this overly long line.

I’ll do both of these, thanks. In the same commit/patch I think.

Quoted 34 lines

[snip]
Same here.
[snip]

Quoted 10 lines

{

git.c +4 -5
-	for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {
-		if (exclude_option &&
-		    (commands[i].option & exclude_option))
-			continue;
-		string_list_append(out, commands[i].cmd);
+	if (include_option && exclude_option)
+		BUG("'include_option' and 'exclude_option' are mutually exclusive");
+	if (include_option) {
+		for (size_t i = 0; i < ARRAY_SIZE(commands); i++)

git.c int is_builtin(const char *s) · line 695

Quoted 10 lines
git.c +8 -0
+			if (commands[i].option & include_option)
+				string_list_append(out, commands[i].cmd);
+	} else {
+		for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {
+			if (commands[i].option & exclude_option)
+				continue;
+			string_list_append(out, commands[i].cmd);
+		}
 	}
 }

We could combine these two loops into one:

    for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {
        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);
    }
Quoted 1 line

Which results in a bit less code duplication.

That’s more elegant. I’ll use that. And I’ll remove the function doc now that it won’t be relevant.

Cheers

Signature · 1 line
Kristoffer
#36 lore raw

PATCH v3 2/8 git: make the two loops look more symmetric

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

Rewrite the original conditional here to match the new sibling loop in structure.

---

Notes (series):
v3 (new):

This is just a refactor commit to avoid having to add a “while at it” for such small tweaks for the new context.

 git.c | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)
git.c +3 -5
index 87d61f12594..39dc9f8ec0f 100644
@@ -686,11 +686,9 @@ static void list_builtins(struct string_list *out, unsigned int include_option,
 			if (commands[i].option & include_option)
 				string_list_append(out, commands[i].cmd);
 	} else {
-		for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {
-			if (commands[i].option & exclude_option)
-				continue;
-			string_list_append(out, commands[i].cmd);
-		}
+		for (size_t i = 0; i < ARRAY_SIZE(commands); i++)
+			if (!(commands[i].option & exclude_option))
+				string_list_append(out, commands[i].cmd);
 	}
 }
 
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#37 lore raw

PATCH v3 3/8 git: allow alias-shadowing deprecated builtins

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

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/[email protected]/
[3]: https://lore.kernel.org/git/BL3P221MB0449288C8B0FA448A227FD48833AA@BL3P221MB0449.NAMP221.PROD.OUTLOOK.COM/

---

Notes (series):
v3 (new):

Prerequisite for telling the user that they can alias whatchanged to git log --raw --no-merged.

> I think that is good advice, but... it won't do anything until we > actually drop the whatchanged command, since until then we'll refuse to > override the command (even the crippled --i-still-use-this one). >
> We'd need something like the patch here:

❦

The

test_file_not_empty expect

is here because the git(1) command could fail. Make sure that it did indeed output anyhing on stdout. (Or if a previous redirect to expect outputted something it should be completely different to actual in any case)

I don’t know if this is just a waste.

 Documentation/config/alias.adoc |  3 ++-
 git.c                           | 19 +++++++++++++++++++
 t/t0014-alias.sh                | 17 +++++++++++++++++
 3 files changed, 38 insertions(+), 1 deletion(-)
Documentation/config/alias.adoc +2 -1
index 2c5db0ad842..3c8fab3a95c 100644
@@ -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.
 +
git.c +19 -0
index 39dc9f8ec0f..a452ce3f9e9 100644
@@ -807,6 +807,12 @@ static void execv_dashed_external(const char **argv)
 		exit(128);
 }
 
+static int is_deprecated_command(const char *cmd)
+{
+	return !strcmp(cmd, "whatchanged") ||
+	       !strcmp(cmd, "pack-redundant");
+}
+
 static int run_argv(struct strvec *args)
 {
 	int done_alias = 0;
@@ -814,6 +820,19 @@ static int run_argv(struct strvec *args)
 	struct string_list_item *seen;
 
 	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]) &&
+		    alias_lookup(args->v[0])) {
+			if (!handle_alias(args))
+				break;
+			done_alias = 1;
+			continue;
+		}
 		/*
 		 * If we tried alias and futzed with our environment,
 		 * it no longer is safe to invoke builtins directly in
t/t0014-alias.sh +17 -0
index 854d59ec58c..89bedb9f73b 100755
@@ -55,4 +55,21 @@ 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_done
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#38 lore raw

Re: [PATCH v3 3/8] git: allow alias-shadowing deprecated builtins

git.c static void execv_dashed_external(const char **argv) · line 814

Quoted 5 lines · [email protected] writes:
git.c +5 -0
+static int is_deprecated_command(const char *cmd)
+{
+	return !strcmp(cmd, "whatchanged") ||
+	       !strcmp(cmd, "pack-redundant");
+}

This is somewhat a shame as you introduced the DEPRECATED bit to annotate entries in "struct cmd_struct commands[]" array. Shouldn't this be moved to git.c and taught to consult that array instead?

#39 lore raw

Re: [PATCH v3 3/8] git: allow alias-shadowing deprecated builtins

Quoted 11 lines · Junio C Hamano

git.c static void execv_dashed_external(const char **argv) · line 814

Quoted 5 lines · [email protected] writes:
git.c +5 -0
+static int is_deprecated_command(const char *cmd)
+{
+	return !strcmp(cmd, "whatchanged") ||
+	       !strcmp(cmd, "pack-redundant");
+}

This is somewhat a shame as you introduced the DEPRECATED bit to annotate entries in "struct cmd_struct commands[]" array. Shouldn't this be moved to git.c and taught to consult that array instead?

Hmm, I did not see the original patch, as it does not seem to have hit the list (or maybe vger is just slow today).

But yeah, if we have a bit in the cmd_struct array, that might be a nicer spot to match. It may or may not be trivial. When I posted my "something like this" patch I first tried to do it that way, but there are a lot of ordering constraints and the parts where we resolved the cmd_struct were not the right spots. OTOH, it would probably not be that hard to just do:

static int is_deprecated_command(const char *cmd)

  {
	struct cmd_struct *builtin = get_builtin(cmd);
	return builtin && (builtin->flags & DEPRECATED);
  }

In theory we could also have a deprecated non-builtin (i.e., implemented as an external program). And then we really would have to match by name. But I kind of doubt that will happen in practice (and it would still be possible to add a string match later if it did).

-Peff

#40 lore raw

Re: [PATCH v3 3/8] git: allow alias-shadowing deprecated builtins

Quoted 9 lines · Jeff King

cmd_struct were not the right spots. OTOH, it would probably not be that hard to just do:

static int is_deprecated_command(const char *cmd)

  {
	struct cmd_struct *builtin = get_builtin(cmd);
	return builtin && (builtin->flags & DEPRECATED);
  }

That was exactly what I had in mind.

Thanks.

#41 lore raw

Re: [PATCH v3 3/8] git: allow alias-shadowing deprecated builtins

Quoted 5 lines · Junio C Hamano
Quoted 3 lines · Jeff King

cmd_struct were not the right spots. OTOH, it would probably not be that hard to just do:

Quoted 8 lines
Quoted 6 lines

static int is_deprecated_command(const char *cmd)

  {
	struct cmd_struct *builtin = get_builtin(cmd);
	return builtin && (builtin->flags & DEPRECATED);
  }

That was exactly what I had in mind.

Thanks to both. I’ll use that suggestion.

Signature · 1 line
Kristoffer Haugsbakk
#42 lore raw

PATCH v3 4/8 t0014: test shadowing of aliases for a sample of builtins

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

The previous commit added a test for shadowing deprecated builtins. Let’s make the test suite more complete by exercising a sample of the builtins and in turn test the documentation for git-config(1):

To avoid confusion and troubles with script usage, aliases that hide existing Git commands are ignored except for deprecated commands.

---

Notes (series):
v3 (new):

Only a sample of builtins since the test file takes about 600ms longer if I test all of them.

 t/t0014-alias.sh | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)
t/t0014-alias.sh +17 -0
index 89bedb9f73b..bf7e6512bb1 100755
@@ -72,4 +72,21 @@ test_expect_success 'can alias-shadow deprecated builtins' '
 	done
 '
 
+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
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#43 lore raw

PATCH v3 5/8 you-still-use-that??: help the user help themselves

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

Give the user a list of suggestions for what to do when they run a deprecated command.

The first order of action will be to check the breaking changes document;[1] this short error message says nothing about why this command is deprecated, and in any case going into any kind of detail might overwhelm the user.

Then they can find out if this has been discussed on the mailing list. Then users who e.g. are using git-whatchanged(1) can learn that this is arguably a plug-in replacement:

    git log <opts> --raw --no-merges

Finally they are invited to send an email to the mailing list.

Also drop the “please add” part in favor of just using the “refusing” die-message; these two would have been right after each other in this new version.

Also drop “Thanks” since it now would require a new paragraph.

[1]: www.git-scm.com has a disclaimer for these internal documents that
says that “This information is specific to the Git project”. That’s misleading in this particular case. But users are unlikely to get discouraged from reading about why they (or their programs) cannot run a command any more; it clearly concerns them.

---

Notes (series):
v2:

I had second thoughts about the bullet point about send-an-email. Change it to the one Eric Sunshine proposed;[1] make sure to spell out that you can send an email conditioned on not finding a suitable replacement.

Also change the area to something more pointed.

And also use a clear URL to refer to www.git-scm.com.

[1]: https://lore.kernel.org/git/CAPig+cQkVP57n_FE6dJ0uxvai-J7usxKFp8gzfEbPY=Ytsd6=Q@mail.gmail.com/

• Change send-an-email bullet point
• Change the area
• Use www.git-scm.com, not simply git-scm

v1:

An alternative to linking to www.git-scm.com is to move this document to a regular installed man page:

gitbreaking-changes(7)

What do you think?

I would then have to base my topic on the in-flight pw/3.0-commentchar-auto-deprecation, which in turn depends on ps/config-wo-the-repository.

Or just wait a bit for these to settle in.

 usage.c | 23 ++++++++++++++++++-----
 1 file changed, 18 insertions(+), 5 deletions(-)
usage.c +18 -5
index 81913236a4a..35dc57eb07e 100644
@@ -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)
 {
@@ -377,12 +378,24 @@ void bug_fl(const char *file, int line, const char *fmt, ...)
 
 NORETURN void you_still_use_that(const char *command_name)
 {
+	struct strbuf percent_encoded = STRBUF_INIT;
+	strbuf_add_percentencode(&percent_encoded,
+				 command_name,
+				 STRBUF_ENCODE_SLASH);
+
 	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 <[email protected]>.  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 <[email protected]> to let us\n"
+		  "  know that you still use this command and were unable\n"
+		  "  to determine a suitable replacement\n"
+		  "\n"),
+		command_name, percent_encoded.buf);
+	strbuf_release(&percent_encoded);
 	die(_("refusing to run without --i-still-use-this"));
 }
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#44 lore raw

PATCH v3 6/8 whatchanged: tell users the git-log(1) equivalent

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

There have been quite a few --i-still-use-this user reports since Git 2.51.0 was released.[1][2] And it doesn’t seem like they are reading the man page about the git-log(1) equivalent.

Tell them what options to plug into git-log(1). That template produces almost the same output[3] and is arguably a plug-in replacement. Concretely, add an optional hint argument so that we can use it right after the initial error line.

Also mention the same concrete options in the documentation while we’re at it.

[1]: E.g.,
• https://lore.kernel.org/git/[email protected]/
• https://lore.kernel.org/git/[email protected]/#t
• https://lore.kernel.org/git/[email protected]/#t
• https://lore.kernel.org/git/[email protected]/
[2]: The error message on 2.51.0 does tell them to report it, unconditionally [3]: You only get different outputs if you happen to have empty
commits (no changes)[4]
[4]: https://lore.kernel.org/git/[email protected]/

---

Notes (series):
v3:

Add an alias hint now that that is possible. Also prefix each hint-line with hint: .

v2:

Review found a whitespace error in the prev. patch version. I found a broken footnote and wanted to expand on the last footnote.

• Fix whitespace error (I should have used ci/check-whitespace.sh v2.51.0)
• Add missing colon (:) to footnote
• Expand on footnote; a sentence is enough to summarize the difference

 Documentation/git-whatchanged.adoc |  6 +++++-
 builtin/log.c                      |  8 +++++++-
 builtin/pack-redundant.c           |  2 +-
 git-compat-util.h                  |  2 +-
 usage.c                            | 14 ++++++++++----
 5 files changed, 24 insertions(+), 8 deletions(-)
Documentation/git-whatchanged.adoc +5 -1
index d21484026fe..e71d2aa2d27 100644
@@ -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
builtin/log.c +7 -1
index c2f8bbf8630..1d1e6e9130a 100644
@@ -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;
builtin/pack-redundant.c +1 -1
index fe81c293e3a..5d5ae4afa28 100644
@@ -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();
git-compat-util.h +1 -1
index 9408f463e31..398e0fac4fa 100644
@@ -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
usage.c +10 -4
index 35dc57eb07e..7545a616453 100644
@@ -376,7 +376,8 @@ 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,
@@ -384,8 +385,13 @@ NORETURN void you_still_use_that(const char *command_name)
 				 STRBUF_ENCODE_SLASH);
 
 	fprintf(stderr,
-		_("'%s' is nominated for removal.\n"
-		  "If you still use this command, here's what you can do:\n"
+		_("'%s' is nominated for removal.\n"), command_name);
+
+	if (hint)
+		fputs(hint, stderr);
+
+	fprintf(stderr,
+		_("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"
@@ -395,7 +401,7 @@ NORETURN void you_still_use_that(const char *command_name)
 		  "  know that you still use this command and were unable\n"
 		  "  to determine a suitable replacement\n"
 		  "\n"),
-		command_name, percent_encoded.buf);
+		percent_encoded.buf);
 	strbuf_release(&percent_encoded);
 	die(_("refusing to run without --i-still-use-this"));
 }
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#45 lore raw

PATCH v3 7/8 whatchanged: remove not-even-shorter clause

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

The closest equivalent is git log --raw --no-merges.

Also change to “defaults” (implicit plural).

---
 Documentation/git-whatchanged.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/git-whatchanged.adoc +1 -1
index e71d2aa2d27..436e219b7d0 100644
@@ -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
 -----------
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#46 lore raw

PATCH v3 8/8 BreakingChanges: remove claim about whatchanged reports

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

This was written in e836757e14b (whatschanged: list it in BreakingChanges document, 2025-05-12) which was on the same topic that added the --i-still-use-this requirement.[1]

Maybe it was a work-in-progress comment/status.

[1]: jc/you-still-use-whatchanged

---

Notes (series):
Footnote solely to avoid awkward paragraph wrapping...

 Documentation/BreakingChanges.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/BreakingChanges.adoc +1 -1
index f8d2eba061c..c4985163c3c 100644
@@ -235,7 +235,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.
 
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#47 lore raw

v4 cover you-still-use-that??: improve breaking changes troubleshooting

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

Based on the recent i-still-use-that reports about whatchanged, improve the error reporting with this command in mind:

1. Give more possible actions instead of just (only) asking them to send an email
2. Hint how to replace their git-whatchanged(1) use with git-log(1) or an alias whatchanged (you can alias deprecated commands now)
3. Minor documentation changes
4. Add deprecated to git --list-cmds

§ What the errors now look like

    $ /git whatchanged
    'git whatchanged' is nominated for removal.

hint: You can replace 'git whatchanged <opts>' with:
hint: git log <opts> --raw --no-merges
hint: Or make an alias:
hint: git config set --global alias.whatchanged 'log --raw --no-merges'

If you still use this command, here's what you can do:

- read https://git-scm.com/docs/BreakingChanges.html
- check if anyone has discussed this on the mailing list and if they came up with something that can help you: https://lore.kernel.org/git/?q=git%20whatchanged
- send an email to <[email protected]> to let us know that you still use this command and were unable to determine a suitable replacement

fatal: refusing to run without --i-still-use-this $ git pack-redundant
'git pack-redundant' is nominated for removal. If you still use this command, here's what you can do:

- read https://git-scm.com/docs/BreakingChanges.html
- check if anyone has discussed this on the mailing list and if they came up with something that can help you: https://lore.kernel.org/git/?q=git%20pack-redundant
- send an email to <[email protected]> to let us know that you still use this command and were unable to determine a suitable replacement

fatal: refusing to run without --i-still-use-this

§ Changes in v4

• Patch 1: updated with suggestion from Patrick
• Patch 2: better is_deprecated_command implementation suggested by Junio and Peff
• Minor adjustments based on the previous

Kristoffer Haugsbakk (7):
git: add deprecated category to --list-cmds
git: allow alias-shadowing deprecated builtins
t0014: test shadowing of aliases for a sample of builtins you-still-use-that??: help the user help themselves
whatchanged: tell users the git-log(1) equivalent
whatchanged: remove not-even-shorter clause
BreakingChanges: remove claim about whatchanged reports

 Documentation/BreakingChanges.adoc |  2 +-
 Documentation/config/alias.adoc    |  3 ++-
 Documentation/git-whatchanged.adoc |  8 ++++--
 Documentation/git.adoc             |  3 ++-
 builtin/log.c                      |  8 +++++-
 builtin/pack-redundant.c           |  2 +-
 git-compat-util.h                  |  2 +-
 git.c                              | 43 ++++++++++++++++++++++++------
 t/t0014-alias.sh                   | 34 +++++++++++++++++++++++
 usage.c                            | 33 ++++++++++++++++++-----
 10 files changed, 115 insertions(+), 23 deletions(-)

Interdiff against v3:

git.c +14 -20
index a452ce3f9e9..b3aafebfe4c 100644
@@ -52,13 +52,9 @@ const char git_more_info_string[] =
 
 static int use_pager = -1;
 
-/*
- * 'include_option' and 'exclude_option' are mutually exclusive.
- *
- * The default ('!include_option') is to include everything
- * except those filtered out by 'exclude_option'.
- */
-static void list_builtins(struct string_list *list, unsigned int include_option, 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)
 {
@@ -677,18 +673,16 @@ int is_builtin(const char *s)
 	return !!get_builtin(s);
 }
 
-static void list_builtins(struct string_list *out, unsigned int include_option, 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");
-	if (include_option) {
-		for (size_t i = 0; i < ARRAY_SIZE(commands); i++)
-			if (commands[i].option & include_option)
-				string_list_append(out, commands[i].cmd);
-	} else {
-		for (size_t i = 0; i < ARRAY_SIZE(commands); i++)
-			if (!(commands[i].option & exclude_option))
-				string_list_append(out, commands[i].cmd);
+	for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {
+		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);
 	}
 }
 
@@ -809,8 +803,8 @@ static void execv_dashed_external(const char **argv)
 
 static int is_deprecated_command(const char *cmd)
 {
-	return !strcmp(cmd, "whatchanged") ||
-	       !strcmp(cmd, "pack-redundant");
+	struct cmd_struct *builtin = get_builtin(cmd);
+	return builtin && (builtin->option & DEPRECATED);
 }
 
 static int run_argv(struct strvec *args)

Range-diff against v3:
1: bdc683a92b3 ! 1: 66e6a9554b1 git: add deprecated category to --list-cmds
@@ Commit message

Let’s expand the experimental --list-cmds[1] to allow users and programs to query for this information. We will also use this in an
- upcoming commit to assert that all deprecated commands will have been
- covered in some manner.
+ upcoming commit to implement is_deprecated_command.

[1]: Using something which is experimental to query for deprecations is
perhaps not the most ideal approach, but it is simple to implement and better than having to scan the documentation

+ Acked-by: Patrick Steinhardt <[email protected]>
+ Helped-by: Patrick Steinhardt <[email protected]>

## Notes (series) ##
+ v4:
+
+ Incorporate Patrick’s suggestions about the for-loop refactor and
+ formatting the overlong lines. Now drop the function doc since it
+ doesn’t apply anymore.
+
+ Also adjust the commit message now that we use it in the C source in an
+ upcoming commit, not just/only as an assert-helper (in an upcoming
+ commit).
+
v3 (new):

This is something I wanted to submit independently until the point about
@@ git.c: const char git_more_info_string[] =
static int use_pager = -1;

-static void list_builtins(struct string_list *list, unsigned int exclude_option);
-+/*
-+ * 'include_option' and 'exclude_option' are mutually exclusive.
-+ *
-+ * The default ('!include_option') is to include everything
-+ * except those filtered out by 'exclude_option'.
-+ */
-+static void list_builtins(struct string_list *list, unsigned int include_option, 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) {
@@ git.c: int is_builtin(const char *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) ++static void list_builtins(struct string_list *out,
++ unsigned int include_option,
++ unsigned int exclude_option)
{
-- for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {
+ for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {
- if (exclude_option &&
- (commands[i].option & exclude_option))
-- continue;
-- string_list_append(out, commands[i].cmd);
-+ if (include_option && exclude_option)
-+ BUG("'include_option' and 'exclude_option' are mutually exclusive");
-+ if (include_option) {
-+ for (size_t i = 0; i < ARRAY_SIZE(commands); i++)
-+ if (commands[i].option & include_option)
-+ string_list_append(out, commands[i].cmd);
-+ } else {
-+ for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {
-+ if (commands[i].option & exclude_option)
-+ continue;
-+ string_list_append(out, commands[i].cmd);
-+ }
++ 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);
}
- }
-

2:  183dd68d09d < -:  ----------- git: make the two loops look more symmetric
3:  eec01cbac16 ! 2:  672253e0e71 git: allow alias-shadowing deprecated builtins
    @@ Commit message
     
     
      ## Notes (series) ##
    +    v4:
    +
    +    Better `is_deprecated_command` implementation.
    +
         v3 (new):
     
         Prerequisite for telling the user that they can alias `whatchanged` to
    @@ git.c: static void execv_dashed_external(const char **argv)
      
     +static int is_deprecated_command(const char *cmd)
     +{
    -+	return !strcmp(cmd, "whatchanged") ||
    -+	       !strcmp(cmd, "pack-redundant");
    ++	struct cmd_struct *builtin = get_builtin(cmd);
    ++	return builtin && (builtin->option & DEPRECATED);
     +}
     +
      static int run_argv(struct strvec *args)
4:  80fb02caeeb = 3:  00108f28f82 t0014: test shadowing of aliases for a sample of builtins
5:  d25ee26f989 = 4:  6bdcaf7f80f you-still-use-that??: help the user help themselves
6:  50621a0748f = 5:  58de9767b22 whatchanged: tell users the git-log(1) equivalent
7:  812c9870f1b = 6:  407b430d02c whatchanged: remove not-even-shorter clause
8:  0d23a4badf0 = 7:  fee752d2fb0 BreakingChanges: remove claim about whatchanged reports

base-commit: c44beea485f0f2feaf460e2ac87fdd5608d63cf0

Signature · 1 line
2.51.0.16.gcd94ab5bf81
#48 lore raw

PATCH v4 1/7 git: add `deprecated` category to --list-cmds

From: Kristoffer Haugsbakk <[email protected]>

With 145 builtin commands (according to git --list-cmds=builtins), users are probably not keeping on top of which ones (if any) are deprecated.

Let’s expand the experimental --list-cmds[1] to allow users and programs to query for this information. We will also use this in an upcoming commit to implement is_deprecated_command.

[1]: Using something which is experimental to query for deprecations is
perhaps not the most ideal approach, but it is simple to implement and better than having to scan the documentation

---

Notes (series):
v4:

Incorporate Patrick’s suggestions about the for-loop refactor and formatting the overlong lines. Now drop the function doc since it doesn’t apply anymore.

Also adjust the commit message now that we use it in the C source in an upcoming commit, not just/only as an assert-helper (in an upcoming commit).

v3 (new):

This is something I wanted to submit independently until the point about aliasing builtins was brought up.[1] It will help (in a small way) with the upcoming patch “git: allow alias-shadowing deprecated builtins”.

By the way: should command-list.txt be updated in some way (I didn’t know what way?)

🔗 1: https://lore.kernel.org/git/[email protected]/T/#mee19f8d39572f9021f9d3000758e87b6c32c967c

 Documentation/git.adoc |  3 ++-
 git.c                  | 24 ++++++++++++++++--------
 2 files changed, 18 insertions(+), 9 deletions(-)
Documentation/git.adoc +2 -1
index 743b7b00e4d..a2f0838b168 100644
@@ -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
git.c +16 -8
index 83eac0aeab7..fca6ebfce3d 100644
@@ -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);
@@ -590,7 +595,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 },
@@ -647,7 +652,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 },
@@ -668,11 +673,14 @@ 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)
 {
 	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);
 	}
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#49 lore raw

Re: [PATCH v4 1/7] git: add `deprecated` category to --list-cmds

Quoted 3 lines · [email protected] writes:

Incorporate Patrick’s suggestions about the for-loop refactor and formatting the overlong lines. Now drop the function doc since it doesn’t apply anymore.

So, if "include" is specified, a command that does not match any of criteria given by those flags is not included, and if "exclude" is specified, a command that matches any of the criteria given is not included. Which probably makes sense. The only user of "include" uses the DEPRECATED flag bit without any exclude option, so it is yet to be seen which one between the previous and the current design gives an easier-to-use behaviour, but I have no objection to the updated design presented here.

Nicely done.

Will replace. I guess we can mark this round as ready for 'next'?

Thanks.

#50 lore raw

Re: [PATCH v4 1/7] git: add `deprecated` category to --list-cmds

Quoted 15 lines · Junio C Hamano
Quoted 3 lines · [email protected] writes:

Incorporate Patrick’s suggestions about the for-loop refactor and formatting the overlong lines. Now drop the function doc since it doesn’t apply anymore.

So, if "include" is specified, a command that does not match any of criteria given by those flags is not included, and if "exclude" is specified, a command that matches any of the criteria given is not included. Which probably makes sense. The only user of "include" uses the DEPRECATED flag bit without any exclude option, so it is yet to be seen which one between the previous and the current design gives an easier-to-use behaviour, but I have no objection to the updated design presented here.

Quoted 3 lines

Nicely done.

Will replace. I guess we can mark this round as ready for 'next'?

I guess so. All my review feedback was addressed, so I don't have anything else to add. Thanks!

Patrick

#51 lore raw

Re: [PATCH v4 1/7] git: add `deprecated` category to --list-cmds

In reply to Junio C Hamano
Quoted 1 line · Junio C Hamano

Will replace. I guess we can mark this round as ready for 'next'?

There's a small leak issue in patch 2. It should be a small fix, but worth doing as it is caught in CI.

-Peff

#52 lore raw

Re: [PATCH v4 1/7] git: add `deprecated` category to --list-cmds

Quoted 8 lines · Jeff King
Quoted 1 line · Junio C Hamano

Will replace. I guess we can mark this round as ready for 'next'?

There's a small leak issue in patch 2. It should be a small fix, but worth doing as it is caught in CI.

-Peff

Thanks for being careful, as always. Very much appreciated.

#53 lore raw

Re: [PATCH v4 1/7] git: add `deprecated` category to --list-cmds

In reply to Junio C Hamano
Quoted 10 lines · Junio C Hamano
Quoted 3 lines · [email protected] writes:

Incorporate Patrick’s suggestions about the for-loop refactor and formatting the overlong lines. Now drop the function doc since it doesn’t apply anymore.

So, if "include" is specified, a command that does not match any of criteria given by those flags is not included, and if "exclude" is specified, a command that matches any of the criteria given is not included. Which probably makes sense.

On second thought I want to add back the “mutually exclusive” check since using both still doesn’t make sense. (Dunno why I thought otherwise. Patrick’s change was strictly a refactoring.)

Quoted 4 lines

The only user of "include" uses the DEPRECATED flag bit without any exclude option, so it is yet to be seen which one between the previous and the current design gives an easier-to-use behaviour, but I have no objection to the updated design presented here.

I’m happy with it. :) But people can suggest an alternative if they would like only one of the include/exclude.

Us Java app. programmers are only permitted to practice bitfields in our spare time.

Signature · 1 line
Kristoffer
#54 lore raw

PATCH v4 2/7 git: allow alias-shadowing deprecated builtins

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

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/[email protected]/
[3]: https://lore.kernel.org/git/BL3P221MB0449288C8B0FA448A227FD48833AA@BL3P221MB0449.NAMP221.PROD.OUTLOOK.COM/

---

Notes (series):
v4:

Better is_deprecated_command implementation.

v3 (new):

Prerequisite for telling the user that they can alias whatchanged to git log --raw --no-merged.

> I think that is good advice, but... it won't do anything until we > actually drop the whatchanged command, since until then we'll refuse to > override the command (even the crippled --i-still-use-this one). >
> We'd need something like the patch here:

❦

The

test_file_not_empty expect

is here because the git(1) command could fail. Make sure that it did indeed output anyhing on stdout. (Or if a previous redirect to expect outputted something it should be completely different to actual in any case)

I don’t know if this is just a waste.

 Documentation/config/alias.adoc |  3 ++-
 git.c                           | 19 +++++++++++++++++++
 t/t0014-alias.sh                | 17 +++++++++++++++++
 3 files changed, 38 insertions(+), 1 deletion(-)
Documentation/config/alias.adoc +2 -1
index 2c5db0ad842..3c8fab3a95c 100644
@@ -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.
 +
git.c +19 -0
index fca6ebfce3d..b3aafebfe4c 100644
@@ -801,6 +801,12 @@ 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;
@@ -808,6 +814,19 @@ static int run_argv(struct strvec *args)
 	struct string_list_item *seen;
 
 	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]) &&
+		    alias_lookup(args->v[0])) {
+			if (!handle_alias(args))
+				break;
+			done_alias = 1;
+			continue;
+		}
 		/*
 		 * If we tried alias and futzed with our environment,
 		 * it no longer is safe to invoke builtins directly in
t/t0014-alias.sh +17 -0
index 854d59ec58c..89bedb9f73b 100755
@@ -55,4 +55,21 @@ 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_done
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#55 lore raw

Re: [PATCH v4 2/7] git: allow alias-shadowing deprecated builtins

git.c static int run_argv(struct strvec *args) · line 829

Quoted 7 lines · On Tue, Sep 09, 2025 at 09:45:52PM +0200, [email protected]…
git.c +7 -0
+		if (is_deprecated_command(args->v[0]) &&
+		    alias_lookup(args->v[0])) {
+			if (!handle_alias(args))
+				break;
+			done_alias = 1;
+			continue;
+		}

I think this is failing the SANITIZE=leak jobs, because alias_lookup() returns an allocated string with the alias. You need to capture and free it, or introduce an alias_exists() wrapper to do so.

There might also be a way to do it just by calling handle_alias() and checking its return value (it seems to indicate whether an alias was found, but I didn't look through the whole function carefully, or think about the implications of how the done_alias flag works).

-Peff

#56 lore raw

Re: [PATCH v4 2/7] git: allow alias-shadowing deprecated builtins

Quoted 14 lines · Jeff King

Review

Quoted 7 lines · On Tue, Sep 09, 2025 at 09:45:52PM +0200, [email protected]…
(quoted diff) +7 -0
+		if (is_deprecated_command(args->v[0]) &&
+		    alias_lookup(args->v[0])) {
+			if (!handle_alias(args))
+				break;
+			done_alias = 1;
+			continue;
+		}

I think this is failing the SANITIZE=leak jobs, because alias_lookup() returns an allocated string with the alias. You need to capture and free it, or introduce an alias_exists() wrapper to do so.

Quoted 4 lines

There might also be a way to do it just by calling handle_alias() and checking its return value (it seems to indicate whether an alias was found, but I didn't look through the whole function carefully, or think about the implications of how the done_alias flag works).

I _think_ this is correct:

git.c +1 -3
index 47857d280f..ea9ec77a88 100644
@@ -823,9 +823,7 @@ static int run_argv(struct strvec *args)
 		 * deprecation complaint in the meantime.
 		 */
 		if (is_deprecated_command(args->v[0]) &&
-		    alias_lookup(args->v[0])) {
-			if (!handle_alias(args))
-				break;
+		    handle_alias(args)) {
 			done_alias = 1;
 			continue;
 		}

The handle_alias() function will either:

- return 0 if it does not find an alias

- return 1 if it finds an alias and we should loop again to run that command (but set done_alias so we know to suppress in-process builtins below). The "args" array will have been updated with the new command name.

- exit itself if it's a "!" shell alias that we exec

So we just need to loop again when it told us that it found an alias (it is tempting to just continue in the loop body, but that would miss the case of a deprecated alias that resolves to another deprecated alias).

Anyway, I believe that is correct and solves the leak issue (because handle_alias() does the lookup and takes care of cleanup itself).

-Peff

#57 lore raw

Re: [PATCH v4 2/7] git: allow alias-shadowing deprecated builtins

Quoted 9 lines · Jeff King
Quoted 5 lines

[snip]
There might also be a way to do it just by calling handle_alias() and checking its return value (it seems to indicate whether an alias was found, but I didn't look through the whole function carefully, or think about the implications of how the done_alias flag works).

I _think_ this is correct:

[diff snip]

Thank you. The best I could come up was making alias.c:alias_exists. Which must be redundant given the call to handlke_alias() that you mention later here.

Quoted 8 lines

The handle_alias() function will either:

- return 0 if it does not find an alias

- return 1 if it finds an alias and we should loop again to run that command (but set done_alias so we know to suppress in-process builtins below). The "args" array will have been updated with the new command name.

This case and the continue seemed to make sense to me eventually. Not the break case though.

Quoted 1 line

- exit itself if it's a "!" shell alias that we exec

Yep.

Quoted 3 lines

So we just need to loop again when it told us that it found an alias (it is tempting to just continue in the loop body, but that would miss the case of a deprecated alias that resolves to another deprecated alias).

Yeah, that’s subtle.

Do you think this should be a test case? I guess it doesn’t hurt.

Quoted 2 lines

Anyway, I believe that is correct and solves the leak issue (because handle_alias() does the lookup and takes care of cleanup itself).

Thanks again. My local test suite passes. (Except t5801-remote-helpers.sh which never works for me.)

And thanks for pointing this out to begin with.

Signature · 1 line
Kristoffer
#58 lore raw

Re: [PATCH v4 2/7] git: allow alias-shadowing deprecated builtins

Quoted 7 lines · Kristoffer Haugsbakk
Quoted 3 lines

So we just need to loop again when it told us that it found an alias (it is tempting to just continue in the loop body, but that would miss the case of a deprecated alias that resolves to another deprecated alias).

Yeah, that’s subtle.

Do you think this should be a test case? I guess it doesn’t hurt.

Yeah, probably would be easy to add. We need two deprecated command names, but it looks like we do have that at this point. :)

Quoted 5 lines
Quoted 2 lines

Anyway, I believe that is correct and solves the leak issue (because handle_alias() does the lookup and takes care of cleanup itself).

Thanks again. My local test suite passes. (Except t5801-remote-helpers.sh which never works for me.)

I sometimes see failures there because I've checked out and built an old version which creates git-remote-testgit as a built script. That was later moved into t/t5801 via 5afb2ce4cd (remote-testgit: move it into the support directory for t5801, 2019-04-12). But if you have an old built copy lying around in the project root, then "git fetch" will find that old version first (because it prepends the root GIT_EXEC_PATH to PATH when it runs). So we run the old version, which may or may not support what the more modern test needs.

And deleting "git-remote-testgit" (or just running "git clean") makes it go away.

That may or may not be your problem, of course. ;)

-Peff

#59 lore raw

Re: [PATCH v4 2/7] git: allow alias-shadowing deprecated builtins

Quoted 6 lines · Jeff King
Quoted 4 lines · Kristoffer Haugsbakk
Quoted 3 lines

So we just need to loop again when it told us that it found an alias (it is tempting to just continue in the loop body, but that would miss the case of a deprecated alias that resolves to another deprecated alias).

Quoted 6 lines
Quoted 3 lines

Yeah, that’s subtle.

Do you think this should be a test case? I guess it doesn’t hurt.

Yeah, probably would be easy to add. We need two deprecated command names, but it looks like we do have that at this point. :)

Thanks. That works fine:

     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
     '

But it turns out there is a regression in my code with the recursion detection. Compare:

    $ ./git -c alias.one=two -c alias.two=two one -h
    'one' is aliased to 'two'
    'two' is aliased to 'two'
    fatal: recursive alias: two

with:

    $ ./git -c alias.whatchanged=pack-redundant -c alias.pack-redundant=whatchanged whatchanged -h
    'whatchanged' is aliased to 'pack-redundant'
    'pack-redundant' is aliased to 'whatchanged'
    'whatchanged' is aliased to 'pack-redundant'
    'pack-redundant' is aliased to 'whatchanged'
    'whatchanged' is aliased to 'pack-redundant'
    'pack-redundant' is aliased to 'whatchanged'
    'whatchanged' is aliased to 'pack-redundant'
    'pack-redundant' is aliased to 'whatchanged'
    [forever]

Both for your suggested change and my v4.

#60 lore raw

Re: [PATCH v4 2/7] git: allow alias-shadowing deprecated builtins

Quoted 7 lines · Kristoffer Haugsbakk

But it turns out there is a regression in my code with the recursion detection. Compare:

    $ ./git -c alias.one=two -c alias.two=two one -h
    'one' is aliased to 'two'
    'two' is aliased to 'two'
    fatal: recursive alias: two

Your example there is a little funny; "two" is recursive to itself, even without "one". We detect that case in handle_alias(), and we would still detect:

git -c alias.whatchanged=whatchanged whatchanged -h

in the same spot. I assume what you meant was:

  $ ./git -c alias.one=two -c alias.two=one one -h
  'one' is aliased to 'two'
  'two' is aliased to 'one'
  fatal: alias loop detected: expansion of 'one' does not terminate:
    one <==
    two ==>

which is handled in the caller. And indeed...

Quoted 10 lines
    $ ./git -c alias.whatchanged=pack-redundant -c alias.pack-redundant=whatchanged whatchanged -h
    'whatchanged' is aliased to 'pack-redundant'
    'pack-redundant' is aliased to 'whatchanged'
    'whatchanged' is aliased to 'pack-redundant'
    'pack-redundant' is aliased to 'whatchanged'
    'whatchanged' is aliased to 'pack-redundant'
    'pack-redundant' is aliased to 'whatchanged'
    'whatchanged' is aliased to 'pack-redundant'
    'pack-redundant' is aliased to 'whatchanged'
    [forever]

...that cannot work with where we've placed the new conditional, because it continues the loop before hitting the alias check. If we move that to the top, like the patch below, it works.

It is a little funny to do it up front even when we're going to directly run a builtin, but I don't think it harms much. OTOH I find the whole placement a little odd in the first place. We are speculatively adding to the cmd list before even finding out if we have an alias. It kind of feels like this should be part of handle_alias() in the first place. We'd need to pass in the cmd_list variable for it to add to and check.

-Peff

---

git.c +18 -18
index ca66e24639..17c566a802 100644
@@ -814,6 +814,24 @@ static int run_argv(struct strvec *args)
 	struct string_list_item *seen;
 
 	while (1) {
+		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]);
+
 		/*
 		 * Allow deprecated commands to be overridden by aliases. This
 		 * creates a seamless path forward for people who want to keep
@@ -874,24 +892,6 @@ 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
#61 lore raw

Re: [PATCH v4 2/7] git: allow alias-shadowing deprecated builtins

Quoted 6 lines · Jeff King

It is a little funny to do it up front even when we're going to directly run a builtin, but I don't think it harms much. OTOH I find the whole placement a little odd in the first place. We are speculatively adding to the cmd list before even finding out if we have an alias. It kind of feels like this should be part of handle_alias() in the first place. We'd need to pass in the cmd_list variable for it to add to and check.

IOW, something like this (perhaps as a preparatory patch):

git.c +23 -22
index ca66e24639..06de0bacf3 100644
@@ -365,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 *cmd_list)
 {
 	int envchanged = 0, ret = 0, saved_errno = errno;
 	int count, option_count;
@@ -376,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);
@@ -423,6 +425,24 @@ static int handle_alias(struct strvec *args)
 		if (!strcmp(alias_command, new_argv[0]))
 			die(_("recursive alias: %s"), alias_command);
 
+		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]);
+
 		trace_argv_printf(new_argv,
 				  "trace: alias expansion: %s =>",
 				  alias_command);
@@ -811,7 +831,6 @@ 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;
 
 	while (1) {
 		/*
@@ -821,7 +840,7 @@ static int run_argv(struct strvec *args)
 		 * deprecation complaint in the meantime.
 		 */
 		if (is_deprecated_command(args->v[0]) &&
-		    handle_alias(args)) {
+		    handle_alias(args, &cmd_list)) {
 			done_alias = 1;
 			continue;
 		}
@@ -874,30 +893,12 @@ 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, &cmd_list))
 			break;
 		done_alias = 1;
 	}

And then along with that:

- a new test at least of the looping deprecated case (we have an existing test for non-deprecated looping aliases)

- while we are moving all this code, I might consider calling cmd_list something more obvious like "expanded_aliases" or something.

- the placement above puts it next to the direct-recursion check in handle_alias(). But it does change the output a bit, since we now do the check after the "-h" expansion. So Before we'd print:

$ git -c alias.one=two -c alias.two=one one -h
'one' is aliased to 'two'
'two' is aliased to 'one'
fatal: alias loop detected: expansion of 'one' does not terminate:
one <==
two ==>

And now we'll print:

$ ./git -c alias.one=two -c alias.two=one one -h
'one' is aliased to 'two'
'two' is aliased to 'one'
'one' is aliased to 'two'
fatal: alias loop detected: expansion of 'one' does not terminate:
one <==
two ==>

with one extra "-h" line. Probably not a big deal, but that perhaps points to the fact that we could be detecting the cycle one loop sooner. I.e., as soon as we see that "two" points to "one" we know we have cycled. We could probably be checking new_argv there instead, after adding the current name to cmd_list (and in fact that would catch the direct-recursion case automatically and we would not need to do so manually, though maybe people prefer the more specific message it produces).

I'll leave that as an exercise for the reader. ;)

-Peff

#62 lore raw

Re: [PATCH v4 2/7] git: allow alias-shadowing deprecated builtins

Quoted 49 lines · Jeff King
Quoted 6 lines · Jeff King

It is a little funny to do it up front even when we're going to directly run a builtin, but I don't think it harms much. OTOH I find the whole placement a little odd in the first place. We are speculatively adding to the cmd list before even finding out if we have an alias. It kind of feels like this should be part of handle_alias() in the first place. We'd need to pass in the cmd_list variable for it to add to and check.

IOW, something like this (perhaps as a preparatory patch):
[snip diff]
And then along with that:

- a new test at least of the looping deprecated case (we have an existing test for non-deprecated looping aliases)

- while we are moving all this code, I might consider calling cmd_list something more obvious like "expanded_aliases" or something.

- the placement above puts it next to the direct-recursion check in handle_alias(). But it does change the output a bit, since we now do the check after the "-h" expansion. So Before we'd print:

$ git -c alias.one=two -c alias.two=one one -h
'one' is aliased to 'two'
'two' is aliased to 'one'
fatal: alias loop detected: expansion of 'one' does not terminate:
one <==
two ==>

And now we'll print:

$ ./git -c alias.one=two -c alias.two=one one -h
'one' is aliased to 'two'
'two' is aliased to 'one'
'one' is aliased to 'two'
fatal: alias loop detected: expansion of 'one' does not terminate:
one <==
two ==>

with one extra "-h" line. Probably not a big deal, but that perhaps points to the fact that we could be detecting the cycle one loop sooner. I.e., as soon as we see that "two" points to "one" we know we have cycled. We could probably be checking new_argv there instead, after adding the current name to cmd_list (and in fact that would catch the direct-recursion case automatically and we would not need to do so manually, though maybe people prefer the more specific message it produces).

Quoted 1 line

I'll leave that as an exercise for the reader. ;)

That’s great, thanks. I’ve added it as a preparatory patch/commit with this message.

-----

git: move seen-command bookkeeping into handle_alias(...)

We are about to complicate the command handling by allowing *deprecated* builtins to be shadowed by aliases. We need to organize the code in order to facilitate that.[1]

The code in the while(1) speculatively adds commands to the list before finding out if it’s an alias. Let’s instead move it inside handle_alias(...) and in turn only run this logic when we have found an alias.

This is not a refactor since the error output is changed; we will now print

    '<cmd1>' is aliased to '<cmd2>'

while iterating in addition to the final fatal message.

[1]: We will do that by an additional call to handle_alias(1) inside
the loop. *Not* moving this code leaves a blind spot; we still detect regular alias looping/recursion but we miss it when it is done using deprecated builtins.

Signature · 1 line
Kris
#63 lore raw

Re: [PATCH v4 2/7] git: allow alias-shadowing deprecated builtins

Quoted 11 lines · Kristoffer Haugsbakk

The code in the while(1) speculatively adds commands to the list before finding out if it’s an alias. Let’s instead move it inside handle_alias(...) and in turn only run this logic when we have found an alias.

This is not a refactor since the error output is changed; we will now print

    '<cmd1>' is aliased to '<cmd2>'

while iterating in addition to the final fatal message.

If you want to get rid of that last paragraph, I think it really is as simple as checking the expanded alias new_argv[0] as soon as we see it, like:

git.c +3 -3
index 06de0bacf3..f11ce416a3 100644
@@ -425,7 +425,9 @@ static int handle_alias(struct strvec *args, struct string_list *cmd_list)
 		if (!strcmp(alias_command, new_argv[0]))
 			die(_("recursive alias: %s"), alias_command);
 
-		seen = unsorted_string_list_lookup(cmd_list, args->v[0]);
+		string_list_append(cmd_list, args->v[0]);
+
+		seen = unsorted_string_list_lookup(cmd_list, new_argv[0]);
 		if (seen) {
 			struct strbuf sb = STRBUF_INIT;
 			for (size_t i = 0; i < cmd_list->nr; i++) {
@@ -441,8 +443,6 @@ static int handle_alias(struct strvec *args, struct string_list *cmd_list)
 			      " not terminate:%s"), cmd_list->items[0].string, sb.buf);
 		}
 
-		string_list_append(cmd_list, args->v[0]);
-
 		trace_argv_printf(new_argv,
 				  "trace: alias expansion: %s =>",
 				  alias_command);

It is important to move the append of args->v[0] up (though arguably it should be alias_command here, which is a local-variable alias that the rest of the function uses). Adding it up-front is not needed for loop detection, but it is shown as part of the output when we do find a loop.

You could _also_ ditch the "recursive alias" check above at that point, though I think it produces a slightly nicer message:

  $ ./git -c alias.foo=foo foo
  fatal: recursive alias: foo

vs:

  $ ./git -c alias.foo=foo foo
  fatal: alias loop detected: expansion of 'foo' does not terminate:
    foo <==

Though perhaps if the output code marked it as both the start and end of the loop it would be more sensible. Maybe like:

git.c +2 -5
index f11ce416a3..f1a83f2e6a 100644
@@ -422,9 +422,6 @@ static int handle_alias(struct strvec *args, struct string_list *cmd_list)
 		if (count < 1)
 			die(_("empty alias for %s"), alias_command);
 
-		if (!strcmp(alias_command, new_argv[0]))
-			die(_("recursive alias: %s"), alias_command);
-
 		string_list_append(cmd_list, args->v[0]);
 
 		seen = unsorted_string_list_lookup(cmd_list, new_argv[0]);
@@ -436,8 +433,8 @@ static int handle_alias(struct strvec *args, struct string_list *cmd_list)
 				strbuf_addf(&sb, "\n  %s", item->string);
 				if (item == seen)
 					strbuf_addstr(&sb, " <==");
-				else if (i == cmd_list->nr - 1)
-					strbuf_addstr(&sb, " ==>");
+				if (i == cmd_list->nr - 1)
+					strbuf_addstr(&sb, item == seen ? ">" : " ==>");
 			}
 			die(_("alias loop detected: expansion of '%s' does"
 			      " not terminate:%s"), cmd_list->items[0].string, sb.buf);

I dunno. Maybe it is not worth tinkering with too much. But the "this changes the output" justification from your proposed message seems to me an indication that the refactor is a little iffy.

-Peff

#64 lore raw

Re: [PATCH v4 2/7] git: allow alias-shadowing deprecated builtins

Quoted 10 lines · Jeff King
Quoted 8 lines · Kristoffer Haugsbakk

The code in the while(1) speculatively adds commands to the list before finding out if it’s an alias. Let’s instead move it inside handle_alias(...) and in turn only run this logic when we have found an alias.

This is not a refactor since the error output is changed; we will now print

Quoted 7 lines
Quoted 3 lines
    '<cmd1>' is aliased to '<cmd2>'

while iterating in addition to the final fatal message.

If you want to get rid of that last paragraph, I think it really is as simple as checking the expanded alias new_argv[0] as soon as we see it, like:

I tried using both of these changes (the patches) but the alias... test suite started failing.

Quoted 4 lines

[snip]
You could _also_ ditch the "recursive alias" check above at that point, though I think it produces a slightly nicer message:

Quoted 8 lines
  $ ./git -c alias.foo=foo foo
  fatal: recursive alias: foo

vs:

  $ ./git -c alias.foo=foo foo
  fatal: alias loop detected: expansion of 'foo' does not terminate:
    foo <==

I didn’t try these.

Quoted 4 lines

[snip]
I dunno. Maybe it is not worth tinkering with too much. But the "this changes the output" justification from your proposed message seems to me an indication that the refactor is a little iffy.

I ended up with not changing it for v5. I missed the first time around that this informational message is only “logged” in the specific case of <git cmd> -h. In turn you only get one more line of output when you are (1) chaining deprecated aliases, and (2) making a loop.

v5 still mentions this but I try to put it off to the side, in other words a footnote.

Thanks

#65 lore raw

Re: [PATCH v4 2/7] git: allow alias-shadowing deprecated builtins

Quoted 6 lines · Kristoffer Haugsbakk
Quoted 3 lines

If you want to get rid of that last paragraph, I think it really is as simple as checking the expanded alias new_argv[0] as soon as we see it, like:

I tried using both of these changes (the patches) but the alias... test suite started failing.

Hmm, it still passes for me. If I do just this on top of your v5, it likewise passes (except for your new test which expects the redundant line).

git.c +4 -3
index 10315742f8..c5fad56813 100644
@@ -425,7 +425,10 @@ static int handle_alias(struct strvec *args, struct string_list *expanded_aliase
 		if (!strcmp(alias_command, new_argv[0]))
 			die(_("recursive alias: %s"), alias_command);
 
-		seen = unsorted_string_list_lookup(expanded_aliases, args->v[0]);
+		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++) {
@@ -441,8 +444,6 @@ static int handle_alias(struct strvec *args, struct string_list *expanded_aliase
 			      " not terminate:%s"), expanded_aliases->items[0].string, sb.buf);
 		}
 
-		string_list_append(expanded_aliases, args->v[0]);
-
 		trace_argv_printf(new_argv,
 				  "trace: alias expansion: %s =>",
 				  alias_command);
Quoted 4 lines

I ended up with not changing it for v5. I missed the first time around that this informational message is only “logged” in the specific case of <git cmd> -h. In turn you only get one more line of output when you are (1) chaining deprecated aliases, and (2) making a loop.

I don't think (1) is necessary. With your v5 I get:

  $ ./git -c alias.one=two -c alias.two=one one -h
  'one' is aliased to 'two'
  'two' is aliased to 'one'
  'one' is aliased to 'two'
  fatal: alias loop detected: expansion of 'one' does not terminate:
    one <==
    two ==>

So the extra line is printed even without deprecated aliases.

-Peff

#66 lore raw

Re: [PATCH v4 2/7] git: allow alias-shadowing deprecated builtins

Quoted 6 lines · Jeff King
Quoted 4 lines · Kristoffer Haugsbakk
Quoted 3 lines

If you want to get rid of that last paragraph, I think it really is as simple as checking the expanded alias new_argv[0] as soon as we see it, like:

Quoted 8 lines
Quoted 2 lines

I tried using both of these changes (the patches) but the alias... test suite started failing.

Hmm, it still passes for me. If I do just this on top of your v5, it likewise passes (except for your new test which expects the redundant line).

[snip diff]

That works for me. Thanks! I will send the next round this evening or tomorrow.

It will be good to get rid of those footnotes.

Quoted 15 lines
Quoted 4 lines

I ended up with not changing it for v5. I missed the first time around that this informational message is only “logged” in the specific case of <git cmd> -h. In turn you only get one more line of output when you are (1) chaining deprecated aliases, and (2) making a loop.

I don't think (1) is necessary. With your v5 I get:

  $ ./git -c alias.one=two -c alias.two=one one -h
  'one' is aliased to 'two'
  'two' is aliased to 'one'
  'one' is aliased to 'two'
  fatal: alias loop detected: expansion of 'one' does not terminate:
    one <==
    two ==>
Quoted 1 line

So the extra line is printed even without deprecated aliases.

That’s right, that part’s in error.

#67 lore raw

Re: [PATCH v4 2/7] git: allow alias-shadowing deprecated builtins

In reply to Jeff King
Quoted 5 lines · Jeff King
Quoted 3 lines · Kristoffer Haugsbakk

But it turns out there is a regression in my code with the recursion detection. Compare:

Quoted 8 lines
Quoted 4 lines
    $ ./git -c alias.one=two -c alias.two=two one -h
    'one' is aliased to 'two'
    'two' is aliased to 'two'
    fatal: recursive alias: two

Your example there is a little funny; "two" is recursive to itself, even without "one". We detect that case in handle_alias(), and we would still detect:

Also, I meant to say: good catch. :) I did not even think about loop detection at all in my suggestions.

-Peff

#68 lore raw

Re: [PATCH v4 2/7] git: allow alias-shadowing deprecated builtins

Quoted 12 lines · Jeff King
Quoted 10 lines · Jeff King
Quoted 7 lines · Kristoffer Haugsbakk

But it turns out there is a regression in my code with the recursion detection. Compare:

    $ ./git -c alias.one=two -c alias.two=two one -h
    'one' is aliased to 'two'
    'two' is aliased to 'two'
    fatal: recursive alias: two
Quoted 6 lines
Quoted 3 lines

Your example there is a little funny; "two" is recursive to itself, even without "one". We detect that case in handle_alias(), and we would still detect:

Also, I meant to say: good catch. :) I did not even think about loop detection at all in my suggestions.

Neither did I. I am very glad that you two are very efficiently and effectively collaborating on this topic together. Thank you very much and kudos for both of you.

#69 lore raw

Re: [PATCH v4 2/7] git: allow alias-shadowing deprecated builtins

In reply to Jeff King
Quoted 12 lines · Jeff King
Quoted 2 lines

Thanks again. My local test suite passes. (Except t5801-remote-helpers.sh which never works for me.)

I sometimes see failures there because I've checked out and built an old version which creates git-remote-testgit as a built script. That was later moved into t/t5801 via 5afb2ce4cd (remote-testgit: move it into the support directory for t5801, 2019-04-12). But if you have an old built copy lying around in the project root, then "git fetch" will find that old version first (because it prepends the root GIT_EXEC_PATH to PATH when it runs). So we run the old version, which may or may not support what the more modern test needs.

Quoted 4 lines

And deleting "git-remote-testgit" (or just running "git clean") makes it go away.

That may or may not be your problem, of course. ;)

Thanks—that was exactly it.:)

#70 lore raw

PATCH v4 3/7 t0014: test shadowing of aliases for a sample of builtins

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

The previous commit added a test for shadowing deprecated builtins. Let’s make the test suite more complete by exercising a sample of the builtins and in turn test the documentation for git-config(1):

To avoid confusion and troubles with script usage, aliases that hide existing Git commands are ignored except for deprecated commands.

---

Notes (series):
v3 (new):

Only a sample of builtins since the test file takes about 600ms longer if I test all of them.

 t/t0014-alias.sh | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)
t/t0014-alias.sh +17 -0
index 89bedb9f73b..bf7e6512bb1 100755
@@ -72,4 +72,21 @@ test_expect_success 'can alias-shadow deprecated builtins' '
 	done
 '
 
+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
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#71 lore raw

PATCH v4 4/7 you-still-use-that??: help the user help themselves

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

Give the user a list of suggestions for what to do when they run a deprecated command.

The first order of action will be to check the breaking changes document;[1] this short error message says nothing about why this command is deprecated, and in any case going into any kind of detail might overwhelm the user.

Then they can find out if this has been discussed on the mailing list. Then users who e.g. are using git-whatchanged(1) can learn that this is arguably a plug-in replacement:

    git log <opts> --raw --no-merges

Finally they are invited to send an email to the mailing list.

Also drop the “please add” part in favor of just using the “refusing” die-message; these two would have been right after each other in this new version.

Also drop “Thanks” since it now would require a new paragraph.

[1]: www.git-scm.com has a disclaimer for these internal documents that
says that “This information is specific to the Git project”. That’s misleading in this particular case. But users are unlikely to get discouraged from reading about why they (or their programs) cannot run a command any more; it clearly concerns them.

---

Notes (series):
v2:

I had second thoughts about the bullet point about send-an-email. Change it to the one Eric Sunshine proposed;[1] make sure to spell out that you can send an email conditioned on not finding a suitable replacement.

Also change the area to something more pointed.

And also use a clear URL to refer to www.git-scm.com.

[1]: https://lore.kernel.org/git/CAPig+cQkVP57n_FE6dJ0uxvai-J7usxKFp8gzfEbPY=Ytsd6=Q@mail.gmail.com/

• Change send-an-email bullet point
• Change the area
• Use www.git-scm.com, not simply git-scm

v1:

An alternative to linking to www.git-scm.com is to move this document to a regular installed man page:

gitbreaking-changes(7)

What do you think?

I would then have to base my topic on the in-flight pw/3.0-commentchar-auto-deprecation, which in turn depends on ps/config-wo-the-repository.

Or just wait a bit for these to settle in.

 usage.c | 23 ++++++++++++++++++-----
 1 file changed, 18 insertions(+), 5 deletions(-)
usage.c +18 -5
index 81913236a4a..35dc57eb07e 100644
@@ -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)
 {
@@ -377,12 +378,24 @@ void bug_fl(const char *file, int line, const char *fmt, ...)
 
 NORETURN void you_still_use_that(const char *command_name)
 {
+	struct strbuf percent_encoded = STRBUF_INIT;
+	strbuf_add_percentencode(&percent_encoded,
+				 command_name,
+				 STRBUF_ENCODE_SLASH);
+
 	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 <[email protected]>.  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 <[email protected]> to let us\n"
+		  "  know that you still use this command and were unable\n"
+		  "  to determine a suitable replacement\n"
+		  "\n"),
+		command_name, percent_encoded.buf);
+	strbuf_release(&percent_encoded);
 	die(_("refusing to run without --i-still-use-this"));
 }
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#72 lore raw

PATCH v4 5/7 whatchanged: tell users the git-log(1) equivalent

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

There have been quite a few --i-still-use-this user reports since Git 2.51.0 was released.[1][2] And it doesn’t seem like they are reading the man page about the git-log(1) equivalent.

Tell them what options to plug into git-log(1). That template produces almost the same output[3] and is arguably a plug-in replacement. Concretely, add an optional hint argument so that we can use it right after the initial error line.

Also mention the same concrete options in the documentation while we’re at it.

[1]: E.g.,
• https://lore.kernel.org/git/[email protected]/
• https://lore.kernel.org/git/[email protected]/#t
• https://lore.kernel.org/git/[email protected]/#t
• https://lore.kernel.org/git/[email protected]/
[2]: The error message on 2.51.0 does tell them to report it, unconditionally [3]: You only get different outputs if you happen to have empty
commits (no changes)[4]
[4]: https://lore.kernel.org/git/[email protected]/

---

Notes (series):
v3:

Add an alias hint now that that is possible. Also prefix each hint-line with hint: .

v2:

Review found a whitespace error in the prev. patch version. I found a broken footnote and wanted to expand on the last footnote.

• Fix whitespace error (I should have used ci/check-whitespace.sh v2.51.0)
• Add missing colon (:) to footnote
• Expand on footnote; a sentence is enough to summarize the difference

 Documentation/git-whatchanged.adoc |  6 +++++-
 builtin/log.c                      |  8 +++++++-
 builtin/pack-redundant.c           |  2 +-
 git-compat-util.h                  |  2 +-
 usage.c                            | 14 ++++++++++----
 5 files changed, 24 insertions(+), 8 deletions(-)
Documentation/git-whatchanged.adoc +5 -1
index d21484026fe..e71d2aa2d27 100644
@@ -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
builtin/log.c +7 -1
index c2f8bbf8630..1d1e6e9130a 100644
@@ -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;
builtin/pack-redundant.c +1 -1
index fe81c293e3a..5d5ae4afa28 100644
@@ -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();
git-compat-util.h +1 -1
index 9408f463e31..398e0fac4fa 100644
@@ -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
usage.c +10 -4
index 35dc57eb07e..7545a616453 100644
@@ -376,7 +376,8 @@ 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,
@@ -384,8 +385,13 @@ NORETURN void you_still_use_that(const char *command_name)
 				 STRBUF_ENCODE_SLASH);
 
 	fprintf(stderr,
-		_("'%s' is nominated for removal.\n"
-		  "If you still use this command, here's what you can do:\n"
+		_("'%s' is nominated for removal.\n"), command_name);
+
+	if (hint)
+		fputs(hint, stderr);
+
+	fprintf(stderr,
+		_("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"
@@ -395,7 +401,7 @@ NORETURN void you_still_use_that(const char *command_name)
 		  "  know that you still use this command and were unable\n"
 		  "  to determine a suitable replacement\n"
 		  "\n"),
-		command_name, percent_encoded.buf);
+		percent_encoded.buf);
 	strbuf_release(&percent_encoded);
 	die(_("refusing to run without --i-still-use-this"));
 }
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#73 lore raw

PATCH v4 6/7 whatchanged: remove not-even-shorter clause

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

The closest equivalent is git log --raw --no-merges.

Also change to “defaults” (implicit plural).

---
 Documentation/git-whatchanged.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/git-whatchanged.adoc +1 -1
index e71d2aa2d27..436e219b7d0 100644
@@ -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
 -----------
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#74 lore raw

PATCH v4 7/7 BreakingChanges: remove claim about whatchanged reports

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

This was written in e836757e14b (whatschanged: list it in BreakingChanges document, 2025-05-12) which was on the same topic that added the --i-still-use-this requirement.[1]

Maybe it was a work-in-progress comment/status.

[1]: jc/you-still-use-whatchanged

---

Notes (series):
Footnote solely to avoid awkward paragraph wrapping...

 Documentation/BreakingChanges.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/BreakingChanges.adoc +1 -1
index f8d2eba061c..c4985163c3c 100644
@@ -235,7 +235,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.
 
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#75 lore raw

v5 cover you-still-use-that??: improve breaking changes troubleshooting

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

Based on the recent i-still-use-that reports about whatchanged, improve the error reporting with this command in mind:

1. Give more possible actions instead of just (only) asking them to send an email
2. Hint how to replace their git-whatchanged(1) use with git-log(1) or an alias whatchanged (you can alias deprecated commands now)
3. Minor documentation changes
4. Add deprecated to git --list-cmds

§ What the errors now look like

    $ ./git whatchanged
    'git whatchanged' is nominated for removal.

hint: You can replace 'git whatchanged <opts>' with:
hint: git log <opts> --raw --no-merges
hint: Or make an alias:
hint: git config set --global alias.whatchanged 'log --raw --no-merges'

If you still use this command, here's what you can do:

- read https://git-scm.com/docs/BreakingChanges.html
- check if anyone has discussed this on the mailing list and if they came up with something that can help you: https://lore.kernel.org/git/?q=git%20whatchanged
- send an email to <[email protected]> to let us know that you still use this command and were unable to determine a suitable replacement

fatal: refusing to run without --i-still-use-this $ ./git pack-redundant
'git pack-redundant' is nominated for removal. If you still use this command, here's what you can do:

- read https://git-scm.com/docs/BreakingChanges.html
- check if anyone has discussed this on the mailing list and if they came up with something that can help you: https://lore.kernel.org/git/?q=git%20pack-redundant
- send an email to <[email protected]> to let us know that you still use this command and were unable to determine a suitable replacement

fatal: refusing to run without --i-still-use-this

§ Changes in v5

Fix the leak that Peff found (with Peff’s suggestion). Also fix (with Peff’s help) a regression where you were able to get into an infinite alias loop specifically when using deprecated builtin names.

Also bring back the BUG check.

Cheers

Kristoffer Haugsbakk (8):
git: add deprecated category to --list-cmds
git: move seen-alias bookkeeping into handle_alias(...)
git: allow alias-shadowing deprecated builtins
t0014: test shadowing of aliases for a sample of builtins you-still-use-that??: help the user help themselves
whatchanged: hint about git-log(1) and aliasing
whatchanged: remove not-even-shorter clause
BreakingChanges: remove claim about whatchanged reports

 Documentation/BreakingChanges.adoc |  2 +-
 Documentation/config/alias.adoc    |  3 +-
 Documentation/git-whatchanged.adoc |  8 ++-
 Documentation/git.adoc             |  3 +-
 builtin/log.c                      |  8 ++-
 builtin/pack-redundant.c           |  2 +-
 git-compat-util.h                  |  2 +-
 git.c                              | 90 ++++++++++++++++++++----------
 t/t0014-alias.sh                   | 58 +++++++++++++++++++
 usage.c                            | 33 ++++++++---
 10 files changed, 163 insertions(+), 46 deletions(-)

Interdiff against v4:

git.c +27 -26
index b3aafebfe4c..1310363e5c4 100644
@@ -365,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;
@@ -376,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);
@@ -423,6 +425,24 @@ static int handle_alias(struct strvec *args)
 		if (!strcmp(alias_command, new_argv[0]))
 			die(_("recursive alias: %s"), alias_command);
 
+		seen = unsorted_string_list_lookup(expanded_aliases, args->v[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);
+		}
+
+		string_list_append(expanded_aliases, args->v[0]);
+
 		trace_argv_printf(new_argv,
 				  "trace: alias expansion: %s =>",
 				  alias_command);
@@ -677,6 +697,8 @@ 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 (include_option && !(commands[i].option & include_option))
 			continue;
@@ -810,8 +832,7 @@ static int is_deprecated_command(const char *cmd)
 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) {
 		/*
@@ -821,9 +842,7 @@ static int run_argv(struct strvec *args)
 		 * deprecation complaint in the meantime.
 		 */
 		if (is_deprecated_command(args->v[0]) &&
-		    alias_lookup(args->v[0])) {
-			if (!handle_alias(args))
-				break;
+		    handle_alias(args, &expanded_aliases)) {
 			done_alias = 1;
 			continue;
 		}
@@ -876,35 +895,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;
 }
t/t0014-alias.sh +24 -0
index bf7e6512bb1..1ac739a2737 100755
@@ -27,6 +27,21 @@ 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}
+	${SQ}whatchanged${SQ} is aliased to ${SQ}pack-redundant${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.
 #
@@ -72,6 +87,15 @@ test_expect_success 'can alias-shadow deprecated builtins' '
 	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)

Range-diff against v4:
1: 66e6a9554b1 ! 1: 13682553018 git: add deprecated category to --list-cmds
@@ Commit message

## Notes (series) ##
+ v5:
+
+ Add back the BUG check from v3 because I think it makes sense to only
+ populate one of the two options.
+
+ Link: https://lore.kernel.org/git/[email protected]/T/#m922b852384911511c45afd458051f52b50dce62f
+
v4:

Incorporate Patrick’s suggestions about the for-loop refactor and
@@ git.c: int is_builtin(const char *s)
+ 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))

-:  ----------- > 2:  7f82ef6e96f git: move seen-alias bookkeeping into handle_alias(...)
2:  672253e0e71 ! 3:  4bfe36cccd0 git: allow alias-shadowing deprecated builtins

@@ Commit message
the command is removed for good.

Let’s lift this limitation by allowing *deprecated* builtins to be
- shadowed by aliases.
+ shadowed by aliases.[4]

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
@@ Commit message
have empty commits (no changes)[2]
[2]: https://lore.kernel.org/git/[email protected]/
[3]: https://lore.kernel.org/git/BL3P221MB0449288C8B0FA448A227FD48833AA@BL3P221MB0449.NAMP221.PROD.OUTLOOK.COM/
+ [4]: The test “looping aliases - deprecated builtins” covers the
+ case of the extra output mentioned in footnote number 3 of the
+ previous commit

## Notes (series) ##
+ v5:
+
+ Fix a leak caused by calling alias_lookup(...) without freeing the
+ return value by not calling that function.
+
+ Add tests for deprecated builtins for infinite alias looping as well as
+ simple chaining. This is to exercise all code paths that we could think
+ of.
+
+ Expand on the infinite alias looping test on the whole error
+ output (compared to the one above). Link this change to the
+ previous patch/commit by mentioning it in a footnote.
+
+ See the last footnote and the note on the previous commit/patch. I have
+ opted not to get rid of this redundant line. But this documents the
+ change and future changes that affect it will trigger this
+ assertion (say if they remove the redundant line).
+
v4:

Better is_deprecated_command implementation.
@@ git.c: static void execv_dashed_external(const char **argv)
static int run_argv(struct strvec *args)
{
int done_alias = 0;
-@@ git.c: static int run_argv(struct strvec *args)
- struct string_list_item *seen;
+ struct string_list expanded_aliases = STRING_LIST_INIT_DUP;

while (1) {
+ /*
@@ git.c: static int run_argv(struct strvec *args)
+ * deprecation complaint in the meantime.
+ */
+ if (is_deprecated_command(args->v[0]) &&
-+ alias_lookup(args->v[0])) {
-+ if (!handle_alias(args))
-+ break;
++ handle_alias(args, &expanded_aliases)) {
+ done_alias = 1;
+ continue;
+ }
@@ git.c: static int run_argv(struct strvec *args)
* it no longer is safe to invoke builtins directly in

## t/t0014-alias.sh ##
+@@ t/t0014-alias.sh: 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}
++ ${SQ}whatchanged${SQ} is aliased to ${SQ}pack-redundant${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.
+ #
@@ t/t0014-alias.sh: test_expect_success 'tracing a shell alias with arguments shows trace of prepare
test_cmp expect actual
'
@@ t/t0014-alias.sh: test_expect_success 'tracing a shell alias with arguments show
+ 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
3: 00108f28f82 ! 4: a04c6ae968e t0014: test shadowing of aliases for a sample of builtins
@@ Metadata
## Commit message ##
t0014: test shadowing of aliases for a sample of builtins

- The previous commit added a test for shadowing deprecated builtins.
+ The previous commit added tests for shadowing deprecated builtins.
Let’s make the test suite more complete by exercising a sample of the builtins and in turn test the documentation for git-config(1):

@@ Notes (series)
if I test all of them.

## t/t0014-alias.sh ##
-@@ t/t0014-alias.sh: test_expect_success 'can alias-shadow deprecated builtins' '
- done
+@@ t/t0014-alias.sh: test_expect_success 'can alias-shadow via two deprecated builtins' '
+ test_cmp expect actual
'

+cannot_alias_regular_builtin () {

4:  6bdcaf7f80f = 5:  2f78ab2e28c you-still-use-that??: help the user help themselves
5:  58de9767b22 ! 6:  34443066645 whatchanged: tell users the git-log(1) equivalent

@@ Metadata
Author: Kristoffer Haugsbakk <[email protected]>

## Commit message ##
- whatchanged: tell users the git-log(1) equivalent
+ whatchanged: hint about git-log(1) and aliasing

There have been quite a few --i-still-use-this user reports since Git 2.51.0 was released.[1][2] And it doesn’t seem like they are reading the man page about the git-log(1) equivalent.

- Tell them what options to plug into git-log(1). That template produces
- almost the same output[3] and is arguably a plug-in replacement.
- Concretely, add an optional hint argument so that we can use it right
- after the initial error line.
+ Tell them what options to plug into git-log(1), either as a replacement
+ command or as an alias.[3] That template produces almost the same
+ output[4] and is arguably a plug-in replacement. Concretely, add
+ an optional hint argument so that we can use it right after the
+ initial error line.

Also mention the same concrete options in the documentation while we’re at it.
@@ Commit message
• https://lore.kernel.org/git/[email protected]/#t
• https://lore.kernel.org/git/[email protected]/
[2]: The error message on 2.51.0 does tell them to report it, unconditionally
- [3]: You only get different outputs if you happen to have empty
+ [3]: We allow aliasing deprecated builtins now for people who are very
+ used to the command name or just like it a lot
+ [4]: You only get different outputs if you happen to have empty
commits (no changes)[4]
- [4]: https://lore.kernel.org/git/[email protected]/
+ [5]: https://lore.kernel.org/git/[email protected]/

## Notes (series) ##
+ v5:
+
+ Mention the aliasing hint in the commit message.
+
v3:

Add an alias hint now that that is possible. Also prefix each hint-line

6:  407b430d02c = 7:  b0aabd793f0 whatchanged: remove not-even-shorter clause
7:  fee752d2fb0 = 8:  5021647c245 BreakingChanges: remove claim about whatchanged reports

base-commit: c44beea485f0f2feaf460e2ac87fdd5608d63cf0

Signature · 1 line
2.51.0.16.gcd94ab5bf81
#76 lore raw

PATCH v5 1/8 git: add `deprecated` category to --list-cmds

From: Kristoffer Haugsbakk <[email protected]>

With 145 builtin commands (according to git --list-cmds=builtins), users are probably not keeping on top of which ones (if any) are deprecated.

Let’s expand the experimental --list-cmds[1] to allow users and programs to query for this information. We will also use this in an upcoming commit to implement is_deprecated_command.

[1]: Using something which is experimental to query for deprecations is
perhaps not the most ideal approach, but it is simple to implement and better than having to scan the documentation

---

Notes (series):
v5:

Add back the BUG check from v3 because I think it makes sense to only populate one of the two options.

v4:

Incorporate Patrick’s suggestions about the for-loop refactor and formatting the overlong lines. Now drop the function doc since it doesn’t apply anymore.

Also adjust the commit message now that we use it in the C source in an upcoming commit, not just/only as an assert-helper (in an upcoming commit).

v3 (new):

This is something I wanted to submit independently until the point about aliasing builtins was brought up.[1] It will help (in a small way) with the upcoming patch “git: allow alias-shadowing deprecated builtins”.

By the way: should command-list.txt be updated in some way (I didn’t know what way?)

🔗 1: https://lore.kernel.org/git/[email protected]/T/#mee19f8d39572f9021f9d3000758e87b6c32c967c

 Documentation/git.adoc |  3 ++-
 git.c                  | 26 ++++++++++++++++++--------
 2 files changed, 20 insertions(+), 9 deletions(-)
Documentation/git.adoc +2 -1
index 743b7b00e4d..a2f0838b168 100644
@@ -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
git.c +18 -8
index 83eac0aeab7..511efdf2056 100644
@@ -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);
@@ -590,7 +595,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 },
@@ -647,7 +652,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 },
@@ -668,11 +673,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);
 	}
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#77 lore raw

PATCH v5 2/8 git: move seen-alias bookkeeping into handle_alias(...)

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

We are about to complicate the command handling by allowing *deprecated* builtins to be shadowed by aliases. We need to organize the code in order to facilitate that.[1]

The code in the while(1) speculatively adds commands to the list before finding out if it’s an alias. Let’s instead move it inside handle_alias(...)—where it conceptually belongs anyway—and in turn only run this logic when we have found an alias.[2][3]

[1]: We will do that with an additional call to handle_alias(1) inside
the loop. *Not* moving this code leaves a blind spot; we will miss alias looping crafted via deprecated builtin names
[2]: Also rename the list to a more descriptive name
[3]: The error output '<cmd1>' is aliased to '<cmd2>' is used for the
<cmd2> -h case. We will get one more (redundant) line of output in the case of alias looping; that is covered in the test “looping aliases - deprecated builtins” in the next commit

---

Notes (series):
v5 (new):

Do a preliminary code movement in order to avoid loop aliasing in the specific case of alias-shadowing deprecated builtins.

As noted in footnote 3: you get one more line of output before the loop aliasing is detected (and see the next commit):

'whatchanged' is aliased to 'pack-redundant'
'pack-redundant' is aliased to 'whatchanged'
'whatchanged' is aliased to 'pack-redundant'
fatal: alias loop detected: expansion of 'whatchanged' does not terminate:
whatchanged <==
pack-redundant ==>

This is an informational message specifically for <git cmd> -h. This output can be gotten after the next commit.

The output in this case is very incidental:

1. You are aliasing deprecated commands in a chain
2. You are using -h
3. You are going to get caught by the alias loop checker

 git.c | 47 ++++++++++++++++++++++++-----------------------
 1 file changed, 24 insertions(+), 23 deletions(-)
git.c +24 -23
index 511efdf2056..f9c2d8c8d86 100644
@@ -365,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;
@@ -376,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);
@@ -423,6 +425,24 @@ static int handle_alias(struct strvec *args)
 		if (!strcmp(alias_command, new_argv[0]))
 			die(_("recursive alias: %s"), alias_command);
 
+		seen = unsorted_string_list_lookup(expanded_aliases, args->v[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);
+		}
+
+		string_list_append(expanded_aliases, args->v[0]);
+
 		trace_argv_printf(new_argv,
 				  "trace: alias expansion: %s =>",
 				  alias_command);
@@ -806,8 +826,7 @@ static void execv_dashed_external(const char **argv)
 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) {
 		/*
@@ -859,35 +878,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;
 }
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#78 lore raw

PATCH v5 3/8 git: allow alias-shadowing deprecated builtins

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

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.[4]

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/[email protected]/
[3]: https://lore.kernel.org/git/BL3P221MB0449288C8B0FA448A227FD48833AA@BL3P221MB0449.NAMP221.PROD.OUTLOOK.COM/
[4]: The test “looping aliases - deprecated builtins” covers the
case of the extra output mentioned in footnote number 3 of the
previous commit

---

Notes (series):
v5:

Fix a leak caused by calling alias_lookup(...) without freeing the return value by not calling that function.

Add tests for deprecated builtins for infinite alias looping as well as simple chaining. This is to exercise all code paths that we could think of.

Expand on the infinite alias looping test on the whole error output (compared to the one above). Link this change to the previous patch/commit by mentioning it in a footnote.

See the last footnote and the note on the previous commit/patch. I have opted not to get rid of this redundant line. But this documents the change and future changes that affect it will trigger this assertion (say if they remove the redundant line).

v4:

Better is_deprecated_command implementation.

v3 (new):

Prerequisite for telling the user that they can alias whatchanged to git log --raw --no-merged.

> I think that is good advice, but... it won't do anything until we > actually drop the whatchanged command, since until then we'll refuse to > override the command (even the crippled --i-still-use-this one). >
> We'd need something like the patch here:

❦

The

test_file_not_empty expect

is here because the git(1) command could fail. Make sure that it did indeed output anyhing on stdout. (Or if a previous redirect to expect outputted something it should be completely different to actual in any case)

I don’t know if this is just a waste.

 Documentation/config/alias.adoc |  3 ++-
 git.c                           | 17 ++++++++++++++
 t/t0014-alias.sh                | 41 +++++++++++++++++++++++++++++++++
 3 files changed, 60 insertions(+), 1 deletion(-)
Documentation/config/alias.adoc +2 -1
index 2c5db0ad842..3c8fab3a95c 100644
@@ -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.
 +
git.c +17 -0
index f9c2d8c8d86..1310363e5c4 100644
@@ -823,12 +823,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
t/t0014-alias.sh +41 -0

51 lines not on this page · raw message

Signature · 1 line
2.51.0.16.gcd94ab5bf81
#79 lore raw

PATCH v5 4/8 t0014: test shadowing of aliases for a sample of builtins

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

The previous commit added tests for shadowing deprecated builtins. Let’s make the test suite more complete by exercising a sample of the builtins and in turn test the documentation for git-config(1):

To avoid confusion and troubles with script usage, aliases that hide existing Git commands are ignored except for deprecated commands.

---

Notes (series):
v3 (new):

Only a sample of builtins since the test file takes about 600ms longer if I test all of them.

 t/t0014-alias.sh | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)
t/t0014-alias.sh +17 -0
index 2f71c3265f0..1ac739a2737 100755
@@ -96,4 +96,21 @@ test_expect_success 'can alias-shadow via two deprecated builtins' '
 	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
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#80 lore raw

PATCH v5 5/8 you-still-use-that??: help the user help themselves

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

Give the user a list of suggestions for what to do when they run a deprecated command.

The first order of action will be to check the breaking changes document;[1] this short error message says nothing about why this command is deprecated, and in any case going into any kind of detail might overwhelm the user.

Then they can find out if this has been discussed on the mailing list. Then users who e.g. are using git-whatchanged(1) can learn that this is arguably a plug-in replacement:

    git log <opts> --raw --no-merges

Finally they are invited to send an email to the mailing list.

Also drop the “please add” part in favor of just using the “refusing” die-message; these two would have been right after each other in this new version.

Also drop “Thanks” since it now would require a new paragraph.

[1]: www.git-scm.com has a disclaimer for these internal documents that
says that “This information is specific to the Git project”. That’s misleading in this particular case. But users are unlikely to get discouraged from reading about why they (or their programs) cannot run a command any more; it clearly concerns them.

---

Notes (series):
v2:

I had second thoughts about the bullet point about send-an-email. Change it to the one Eric Sunshine proposed;[1] make sure to spell out that you can send an email conditioned on not finding a suitable replacement.

Also change the area to something more pointed.

And also use a clear URL to refer to www.git-scm.com.

[1]: https://lore.kernel.org/git/CAPig+cQkVP57n_FE6dJ0uxvai-J7usxKFp8gzfEbPY=Ytsd6=Q@mail.gmail.com/

• Change send-an-email bullet point
• Change the area
• Use www.git-scm.com, not simply git-scm

v1:

An alternative to linking to www.git-scm.com is to move this document to a regular installed man page:

gitbreaking-changes(7)

What do you think?

I would then have to base my topic on the in-flight pw/3.0-commentchar-auto-deprecation, which in turn depends on ps/config-wo-the-repository.

Or just wait a bit for these to settle in.

 usage.c | 23 ++++++++++++++++++-----
 1 file changed, 18 insertions(+), 5 deletions(-)
usage.c +18 -5
index 81913236a4a..35dc57eb07e 100644
@@ -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)
 {
@@ -377,12 +378,24 @@ void bug_fl(const char *file, int line, const char *fmt, ...)
 
 NORETURN void you_still_use_that(const char *command_name)
 {
+	struct strbuf percent_encoded = STRBUF_INIT;
+	strbuf_add_percentencode(&percent_encoded,
+				 command_name,
+				 STRBUF_ENCODE_SLASH);
+
 	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 <[email protected]>.  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 <[email protected]> to let us\n"
+		  "  know that you still use this command and were unable\n"
+		  "  to determine a suitable replacement\n"
+		  "\n"),
+		command_name, percent_encoded.buf);
+	strbuf_release(&percent_encoded);
 	die(_("refusing to run without --i-still-use-this"));
 }
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#81 lore raw

PATCH v5 6/8 whatchanged: hint about git-log(1) and aliasing

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

There have been quite a few --i-still-use-this user reports since Git 2.51.0 was released.[1][2] And it doesn’t seem like they are reading the man page about the git-log(1) equivalent.

Tell them what options to plug into git-log(1), either as a replacement command or as an alias.[3] That template produces almost the same output[4] and is arguably a plug-in replacement. Concretely, add an optional hint argument so that we can use it right after the initial error line.

Also mention the same concrete options in the documentation while we’re at it.

[1]: E.g.,
• https://lore.kernel.org/git/[email protected]/
• https://lore.kernel.org/git/[email protected]/#t
• https://lore.kernel.org/git/[email protected]/#t
• https://lore.kernel.org/git/[email protected]/
[2]: The error message on 2.51.0 does tell them to report it, unconditionally [3]: We allow aliasing deprecated builtins now for people who are very
used to the command name or just like it a lot
[4]: You only get different outputs if you happen to have empty
commits (no changes)[4]
[5]: https://lore.kernel.org/git/[email protected]/

---

Notes (series):
v5:

Mention the aliasing hint in the commit message.

v3:

Add an alias hint now that that is possible. Also prefix each hint-line with hint: .

v2:

Review found a whitespace error in the prev. patch version. I found a broken footnote and wanted to expand on the last footnote.

• Fix whitespace error (I should have used ci/check-whitespace.sh v2.51.0)
• Add missing colon (:) to footnote
• Expand on footnote; a sentence is enough to summarize the difference

 Documentation/git-whatchanged.adoc |  6 +++++-
 builtin/log.c                      |  8 +++++++-
 builtin/pack-redundant.c           |  2 +-
 git-compat-util.h                  |  2 +-
 usage.c                            | 14 ++++++++++----
 5 files changed, 24 insertions(+), 8 deletions(-)
Documentation/git-whatchanged.adoc +5 -1
index d21484026fe..e71d2aa2d27 100644
@@ -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
builtin/log.c +7 -1
index c2f8bbf8630..1d1e6e9130a 100644
@@ -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;
builtin/pack-redundant.c +1 -1
index fe81c293e3a..5d5ae4afa28 100644
@@ -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();
git-compat-util.h +1 -1
index 9408f463e31..398e0fac4fa 100644
@@ -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
usage.c +10 -4
index 35dc57eb07e..7545a616453 100644
@@ -376,7 +376,8 @@ 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,
@@ -384,8 +385,13 @@ NORETURN void you_still_use_that(const char *command_name)
 				 STRBUF_ENCODE_SLASH);
 
 	fprintf(stderr,
-		_("'%s' is nominated for removal.\n"
-		  "If you still use this command, here's what you can do:\n"
+		_("'%s' is nominated for removal.\n"), command_name);
+
+	if (hint)
+		fputs(hint, stderr);
+
+	fprintf(stderr,
+		_("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"
@@ -395,7 +401,7 @@ NORETURN void you_still_use_that(const char *command_name)
 		  "  know that you still use this command and were unable\n"
 		  "  to determine a suitable replacement\n"
 		  "\n"),
-		command_name, percent_encoded.buf);
+		percent_encoded.buf);
 	strbuf_release(&percent_encoded);
 	die(_("refusing to run without --i-still-use-this"));
 }
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#82 lore raw

PATCH v5 7/8 whatchanged: remove not-even-shorter clause

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

The closest equivalent is git log --raw --no-merges.

Also change to “defaults” (implicit plural).

---
 Documentation/git-whatchanged.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/git-whatchanged.adoc +1 -1
index e71d2aa2d27..436e219b7d0 100644
@@ -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
 -----------
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#83 lore raw

PATCH v5 8/8 BreakingChanges: remove claim about whatchanged reports

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

This was written in e836757e14b (whatschanged: list it in BreakingChanges document, 2025-05-12) which was on the same topic that added the --i-still-use-this requirement.[1]

Maybe it was a work-in-progress comment/status.

[1]: jc/you-still-use-whatchanged

---

Notes (series):
Footnote solely to avoid awkward paragraph wrapping...

 Documentation/BreakingChanges.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/BreakingChanges.adoc +1 -1
index f8d2eba061c..c4985163c3c 100644
@@ -235,7 +235,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.
 
Signature · 1 line
2.51.0.16.gcd94ab5bf81
#84 lore raw
In reply to [email protected]
Quoted 5 lines · [email protected] writes:

§ Changes in v5

Fix the leak that Peff found (with Peff’s suggestion). Also fix (with Peff’s help) a regression where you were able to get into an infinite alias loop specifically when using deprecated builtin names.

When merged to 'seen' and run with WITH_BREAKING_CHANGES=YesPlease, looping alias test would chage the behaviour, and the test needs to be prepared for it, I think. Here is how t0014 fails

expecting success of 0014.4 '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}
        ${SQ}whatchanged${SQ} is aliased to ${SQ}pack-redundant${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
actual 2025-09-15 19:16:26.360366313 +0000 renamed from expect 2025-09-15 19:16:26.340366359 +0000 +1 -6
@@ -1,6 +1 @@
-'whatchanged' is aliased to 'pack-redundant'
-'pack-redundant' is aliased to 'whatchanged'
-'whatchanged' is aliased to 'pack-redundant'
-fatal: alias loop detected: expansion of 'whatchanged' does not terminate:
-  whatchanged <==
-  pack-redundant ==>
+fatal: cannot handle whatchanged as a builtin

not ok 4 - looping aliases - deprecated builtins

#85 lore raw
Quoted 37 lines · Junio C Hamano
Quoted 5 lines · [email protected] writes:

§ Changes in v5

Fix the leak that Peff found (with Peff’s suggestion). Also fix (with Peff’s help) a regression where you were able to get into an infinite alias loop specifically when using deprecated builtin names.

When merged to 'seen' and run with WITH_BREAKING_CHANGES=YesPlease, looping alias test would chage the behaviour, and the test needs to be prepared for it, I think. Here is how t0014 fails

expecting success of 0014.4 '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}
        ${SQ}whatchanged${SQ} is aliased to ${SQ}pack-redundant${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
actual 2025-09-15 19:16:26.360366313 +0000 renamed from expect 2025-09-15 19:16:26.340366359 +0000 +1 -6
@@ -1,6 +1 @@
-'whatchanged' is aliased to 'pack-redundant'
-'pack-redundant' is aliased to 'whatchanged'
-'whatchanged' is aliased to 'pack-redundant'
-fatal: alias loop detected: expansion of 'whatchanged' does not terminate:
-  whatchanged <==
-  pack-redundant ==>
+fatal: cannot handle whatchanged as a builtin
Quoted 1 line

not ok 4 - looping aliases - deprecated builtins

Thanks. This is (maybe you surmise or know already but) a problem when on the branch itself as well. I did some compiling WITH_BREAKING_CHANGES=YesPlease and it seems that 07572f220a8 (whatchanged: remove when built with WITH_BREAKING_CHANGES, 2025-05-12) made it a hard error to call whatchanged after its death:

    $ ./bin-wrappers/git whatchanged
    fatal: cannot handle whatchanged as a builtin

This seems fine, although I would have expected it to say something more neutral like “command not found”.

What I don’t understand though is this:

    $ ./bin-wrappers/git -c alias.whatchanged=status whatchanged
    fatal: cannot handle whatchanged as a builtin

People have been saying that people can at least alias whatchanged to log <something> and then it will work after the removal. But now that it has been removed (according to the commit message) its spirit lives on and prevents reusing the name?

Am I missing something here?

#86 lore raw
Quoted 11 lines · Kristoffer Haugsbakk

Thanks. This is (maybe you surmise or know already but) a problem when on the branch itself as well. I did some compiling WITH_BREAKING_CHANGES=YesPlease and it seems that 07572f220a8 (whatchanged: remove when built with WITH_BREAKING_CHANGES, 2025-05-12) made it a hard error to call whatchanged after its death:

    $ ./bin-wrappers/git whatchanged
    fatal: cannot handle whatchanged as a builtin

This seems fine, although I would have expected it to say something more neutral like “command not found”.

I think the breaking-changes code for whatchanged did not update the Makefile appropriately. If I build with that flag:

[make sure it is not here]

  $ rm git-whatchanged

[now build]

  $ make WITH_BREAKING_CHANGES=YesPlease
    [...]
    BUILTIN git-whatchanged
    SUBDIR templates
  $ ls -l git-whatchanged
  -rwxrwxr-x 140 peff peff 12104368 Sep 16 19:15 git-whatchanged*

So we have linked it in the usual way we do for builtins, so that git-whatchanged would "work". But of course since it is not in the builtin table, it does not:

  $ ./git-whatchanged
  fatal: cannot handle whatchanged as a builtin

And then because it exists in the exec-path, "git whatchanged" thinks that it can run the dashed form, so it produces the same message.

We probably need this:

Makefile +2 -0
index e11340c1ae..9311da5bb1 100644
@@ -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

but annoyingly an old build will leave git-whatchanged in place and cause the same confusion. And git-status won't even notice that it's leftover because .gitignore mentions it. But we can't drop it from .gitignore until the non-breaking-changes builds go away entirely. Yuck.

Quoted 9 lines

What I don’t understand though is this:

    $ ./bin-wrappers/git -c alias.whatchanged=status whatchanged
    fatal: cannot handle whatchanged as a builtin

People have been saying that people can at least alias whatchanged to log <something> and then it will work after the removal. But now that it has been removed (according to the commit message) its spirit lives on and prevents reusing the name?

I'd have thought the alias took precedence over the stale executable file. But it can't, because "whatchanged" is no longer marked as deprecated in the builtin list. Because it's not in the builtin list at all! So it is just a "normal" non-deprecated external command from the perspective of git.c, and you are not allowed to override it.

So I think the alias code is working as expected, but the stale file is causing the same confusion as above. Deleting it makes everything work.

-Peff

#87 lore raw
Quoted 71 lines · Jeff King
Quoted 11 lines · Kristoffer Haugsbakk

Thanks. This is (maybe you surmise or know already but) a problem when on the branch itself as well. I did some compiling WITH_BREAKING_CHANGES=YesPlease and it seems that 07572f220a8 (whatchanged: remove when built with WITH_BREAKING_CHANGES, 2025-05-12) made it a hard error to call whatchanged after its death:

    $ ./bin-wrappers/git whatchanged
    fatal: cannot handle whatchanged as a builtin

This seems fine, although I would have expected it to say something more neutral like “command not found”.

I think the breaking-changes code for whatchanged did not update the Makefile appropriately. If I build with that flag:

[make sure it is not here]

  $ rm git-whatchanged

[now build]

  $ make WITH_BREAKING_CHANGES=YesPlease
    [...]
    BUILTIN git-whatchanged
    SUBDIR templates
  $ ls -l git-whatchanged
  -rwxrwxr-x 140 peff peff 12104368 Sep 16 19:15 git-whatchanged*

So we have linked it in the usual way we do for builtins, so that git-whatchanged would "work". But of course since it is not in the builtin table, it does not:

  $ ./git-whatchanged
  fatal: cannot handle whatchanged as a builtin

And then because it exists in the exec-path, "git whatchanged" thinks that it can run the dashed form, so it produces the same message.

We probably need this:

Makefile +2 -0
index e11340c1ae..9311da5bb1 100644
@@ -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

but annoyingly an old build will leave git-whatchanged in place and cause the same confusion. And git-status won't even notice that it's leftover because .gitignore mentions it. But we can't drop it from .gitignore until the non-breaking-changes builds go away entirely. Yuck.

Quoted 9 lines

What I don’t understand though is this:

    $ ./bin-wrappers/git -c alias.whatchanged=status whatchanged
    fatal: cannot handle whatchanged as a builtin

People have been saying that people can at least alias whatchanged to log <something> and then it will work after the removal. But now that it has been removed (according to the commit message) its spirit lives on and prevents reusing the name?

Quoted 8 lines

I'd have thought the alias took precedence over the stale executable file. But it can't, because "whatchanged" is no longer marked as deprecated in the builtin list. Because it's not in the builtin list at all! So it is just a "normal" non-deprecated external command from the perspective of git.c, and you are not allowed to override it.

So I think the alias code is working as expected, but the stale file is causing the same confusion as above. Deleting it makes everything work.

That’s great, I think I understand. It is my understanding that the change to Makefile is needed for the test to not break when WITH_BREAKING_CHANGES is active. Well I tried now with a *make* after

git fsck -d -x -f

And it fails the same way. So I’ll just steal this change... (as well) and include it in preparations for the others.

(Thanks so much!)

-----

Draft of the commit message:

-----

Makefile: don’t add whatchanged after it has been removed

07572f220a8 (whatchanged: remove when built with WITH_BREAKING_CHANGES, 2025-05-12) set up the removal of git-whatchanged(1) when WITH_BREAKING_CHANGES is active. Part of that work was removing it from commands in git.c. But the the Makefile still lists it as a builtin . That leaves it in the limbo of being linked but not being callable; you get the generic error about not being able to call it as a *builtin*:

    $ git whatchanged
    fatal: cannot handle whatchanged as a builtin

instead of the expected:

    $ git whatchanged
    git: 'whatchanged' is not a git command. See 'git --help'.
#88 lore raw
Quoted 14 lines · Kristoffer Haugsbakk

Draft of the commit message:

-----

Makefile: don’t add whatchanged after it has been removed

07572f220a8 (whatchanged: remove when built with WITH_BREAKING_CHANGES, 2025-05-12) set up the removal of git-whatchanged(1) when WITH_BREAKING_CHANGES is active. Part of that work was removing it from commands in git.c. But the the Makefile still lists it as a builtin . That leaves it in the limbo of being linked but not being callable; you get the generic error about not being able to call it as a *builtin*:

Quoted 7 lines
    $ git whatchanged
    fatal: cannot handle whatchanged as a builtin

instead of the expected:

    $ git whatchanged
    git: 'whatchanged' is not a git command. See 'git --help'.

A change very well thought out. Thanks for fixing my earlier breakage.

#89 lore raw

v6 cover you-still-use-that??: improve breaking changes troubleshooting

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

Based on the recent i-still-use-that reports about whatchanged, improve the error reporting with this command in mind:

1. Give more possible actions instead of just (only) asking them to send an email
2. Hint how to replace their git-whatchanged(1) use with git-log(1) or an alias whatchanged (you can alias deprecated commands now)
3. Minor documentation changes
4. Add deprecated to git --list-cmds
5. (new in v6) Don’t list git-whatchanged(1) as a builtin when WITH_BREAKING_CHANGES

§ What the errors now look like

    $ ./git whatchanged
    'git whatchanged' is nominated for removal.

hint: You can replace 'git whatchanged <opts>' with:
hint: git log <opts> --raw --no-merges
hint: Or make an alias:
hint: git config set --global alias.whatchanged 'log --raw --no-merges'

If you still use this command, here's what you can do:

- read https://git-scm.com/docs/BreakingChanges.html
- check if anyone has discussed this on the mailing list and if they came up with something that can help you: https://lore.kernel.org/git/?q=git%20whatchanged
- send an email to <[email protected]> to let us know that you still use this command and were unable to determine a suitable replacement

fatal: refusing to run without --i-still-use-this $ ./git pack-redundant
'git pack-redundant' is nominated for removal. If you still use this command, here's what you can do:

- read https://git-scm.com/docs/BreakingChanges.html
- check if anyone has discussed this on the mailing list and if they came up with something that can help you: https://lore.kernel.org/git/?q=git%20pack-redundant
- send an email to <[email protected]> to let us know that you still use this command and were unable to determine a suitable replacement

fatal: refusing to run without --i-still-use-this

§ Changes in v6

Use Peff’s patch to get rid of “the extra output”.[1] Update commit messages to not mention that.

Also use another fix from Peff (patch 1); don’t list git-whatchanged(1) as a builtin when WITH_BREAKING_CHANGES.

🔗 1: https://lore.kernel.org/git/[email protected]/T/#mb3ac9c32c8527675a1f9cdf7709f2c74b3dd2a6c

Thanks

Kristoffer Haugsbakk (9):
Makefile: don’t add whatchanged after it has been removed
git: add deprecated category to --list-cmds
git: move seen-alias bookkeeping into handle_alias(...)
git: allow alias-shadowing deprecated builtins
t0014: test shadowing of aliases for a sample of builtins you-still-use-that??: help the user help themselves
whatchanged: hint about git-log(1) and aliasing
whatchanged: remove not-even-shorter clause
BreakingChanges: remove claim about whatchanged reports

 Documentation/BreakingChanges.adoc |  2 +-
 Documentation/config/alias.adoc    |  3 +-
 Documentation/git-whatchanged.adoc |  8 ++-
 Documentation/git.adoc             |  3 +-
 Makefile                           |  2 +
 builtin/log.c                      |  8 ++-
 builtin/pack-redundant.c           |  2 +-
 git-compat-util.h                  |  2 +-
 git.c                              | 91 ++++++++++++++++++++----------
 t/t0014-alias.sh                   | 57 +++++++++++++++++++
 usage.c                            | 33 ++++++++---
 11 files changed, 165 insertions(+), 46 deletions(-)

Interdiff against v5:

Makefile +2 -0
index e11340c1ae7..9311da5bb15 100644
@@ -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
git.c +4 -3
index 1310363e5c4..8c85da84c30 100644
@@ -425,7 +425,10 @@ static int handle_alias(struct strvec *args, struct string_list *expanded_aliase
 		if (!strcmp(alias_command, new_argv[0]))
 			die(_("recursive alias: %s"), alias_command);
 
-		seen = unsorted_string_list_lookup(expanded_aliases, args->v[0]);
+		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++) {
@@ -441,8 +444,6 @@ static int handle_alias(struct strvec *args, struct string_list *expanded_aliase
 			      " not terminate:%s"), expanded_aliases->items[0].string, sb.buf);
 		}
 
-		string_list_append(expanded_aliases, args->v[0]);
-
 		trace_argv_printf(new_argv,
 				  "trace: alias expansion: %s =>",
 				  alias_command);
t/t0014-alias.sh +0 -1
index 1ac739a2737..07a53e7366e 100755
@@ -33,7 +33,6 @@ test_expect_success 'looping aliases - deprecated builtins' '
 	cat >expect <<-EOF &&
 	${SQ}whatchanged${SQ} is aliased to ${SQ}pack-redundant${SQ}
 	${SQ}pack-redundant${SQ} is aliased to ${SQ}whatchanged${SQ}
-	${SQ}whatchanged${SQ} is aliased to ${SQ}pack-redundant${SQ}
 	fatal: alias loop detected: expansion of ${SQ}whatchanged${SQ} does not terminate:
 	  whatchanged <==
 	  pack-redundant ==>

Range-diff against v5:

 -:  ----------- >  1:  8b34edf876e Makefile: don’t add whatchanged after it has been removed
 1:  13682553018 =  2:  c10a24018ce git: add `deprecated` category to --list-cmds
 2:  7f82ef6e96f !  3:  90e35bfc063 git: move seen-alias bookkeeping into handle_alias(...)

@@ Commit message
The code in the while(1) speculatively adds commands to the list before finding out if it’s an alias. Let’s instead move it inside handle_alias(...)—where it conceptually belongs anyway—and in turn
- only run this logic when we have found an alias.[2][3]
+ only run this logic when we have found an alias.[2]

[1]: We will do that with an additional call to handle_alias(1) inside
the loop. *Not* moving this code leaves a blind spot; we will miss alias looping crafted via deprecated builtin names
[2]: Also rename the list to a more descriptive name
- [3]: The error output '<cmd1>' is aliased to '<cmd2>' is used for the
- <cmd2> -h case. We will get one more (redundant) line of output
- in the case of alias looping; that is covered in the test “looping
- aliases - deprecated builtins” in the next commit

## Notes (series) ##
+ v6:
+
+ Apply Peff’s patch[1] in order to get rid of the extra output line.
+
+ Drop the footnote from the commit message now that we don’t have that
+ problem any more.
+
+ 🔗 1: https://lore.kernel.org/git/[email protected]/T/#mb3ac9c32c8527675a1f9cdf7709f2c74b3dd2a6c
+
v5 (new):

Do a preliminary code movement in order to avoid loop aliasing in the
@@ git.c: static int handle_alias(struct strvec *args)
if (!strcmp(alias_command, new_argv[0]))
die(_("recursive alias: %s"), alias_command);

-+ seen = unsorted_string_list_lookup(expanded_aliases, args->v[0]);
++ 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++) {
@@ git.c: static int handle_alias(struct strvec *args)
+ die(_("alias loop detected: expansion of '%s' does"
+ " not terminate:%s"), expanded_aliases->items[0].string, sb.buf);
+ }
-+
-+ string_list_append(expanded_aliases, args->v[0]);
+
trace_argv_printf(new_argv,
"trace: alias expansion: %s =>",
3: 4bfe36cccd0 ! 4: 2cac74d731c git: allow alias-shadowing deprecated builtins
@@ Commit message
the command is removed for good.

Let’s lift this limitation by allowing *deprecated* builtins to be
- shadowed by aliases.[4]
+ 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
@@ Commit message
have empty commits (no changes)[2]
[2]: https://lore.kernel.org/git/[email protected]/
[3]: https://lore.kernel.org/git/BL3P221MB0449288C8B0FA448A227FD48833AA@BL3P221MB0449.NAMP221.PROD.OUTLOOK.COM/
- [4]: The test “looping aliases - deprecated builtins” covers the
- case of the extra output mentioned in footnote number 3 of the
- previous commit

## Notes (series) ##
+ v6:
+
+ Drop footnote now that we don’t have the “extra output line” problem.
+
v5:

Fix a leak caused by calling alias_lookup(...) without freeing the
@@ t/t0014-alias.sh: test_expect_success 'looping aliases - internal execution' '
+ cat >expect <<-EOF &&
+ ${SQ}whatchanged${SQ} is aliased to ${SQ}pack-redundant${SQ}
+ ${SQ}pack-redundant${SQ} is aliased to ${SQ}whatchanged${SQ}
-+ ${SQ}whatchanged${SQ} is aliased to ${SQ}pack-redundant${SQ}
+ fatal: alias loop detected: expansion of ${SQ}whatchanged${SQ} does not terminate:
+ whatchanged <==
+ pack-redundant ==>

 4:  a04c6ae968e =  5:  3576439add5 t0014: test shadowing of aliases for a sample of builtins
 5:  2f78ab2e28c =  6:  7499890ed49 you-still-use-that??: help the user help themselves
 6:  34443066645 =  7:  b0831e8967a whatchanged: hint about git-log(1) and aliasing
 7:  b0aabd793f0 =  8:  2f281944fbf whatchanged: remove not-even-shorter clause
 8:  5021647c245 =  9:  7dc3d03d626 BreakingChanges: remove claim about whatchanged reports

base-commit: c44beea485f0f2feaf460e2ac87fdd5608d63cf0

Signature · 1 line
2.51.0.274.gdcb64e51a0f
#90 lore raw

PATCH v6 1/9 Makefile: don’t add whatchanged after it has been removed

From: Kristoffer Haugsbakk <[email protected]>

07572f220a8 (whatchanged: remove when built with WITH_BREAKING_CHANGES, 2025-05-12) set up the removal of git-whatchanged(1) when WITH_BREAKING_CHANGES is active. Part of that work was removing it from commands in git.c. But the Makefile still lists it as a builtin. That leaves it in the limbo of being linked but not being callable; you get the generic error about not being able to call it as a *builtin*:

    $ git whatchanged
    fatal: cannot handle whatchanged as a builtin

instead of the expected:

    $ git whatchanged
    git: 'whatchanged' is not a git command. See 'git --help'.

---

Notes (series):

    v6 (new):
    
    Link/Source: https://lore.kernel.org/git/[email protected]/T/#mfa023b1c1626df68d25714a5981d8662a66199dc
 Makefile | 2 ++
 1 file changed, 2 insertions(+)
Makefile +2 -0
index e11340c1ae7..9311da5bb15 100644
@@ -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
Signature · 1 line
2.51.0.274.gdcb64e51a0f
#91 lore raw

PATCH v6 2/9 git: add `deprecated` category to --list-cmds

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

With 145 builtin commands (according to git --list-cmds=builtins), users are probably not keeping on top of which ones (if any) are deprecated.

Let’s expand the experimental --list-cmds[1] to allow users and programs to query for this information. We will also use this in an upcoming commit to implement is_deprecated_command.

[1]: Using something which is experimental to query for deprecations is
perhaps not the most ideal approach, but it is simple to implement and better than having to scan the documentation

---

Notes (series):
v5:

Add back the BUG check from v3 because I think it makes sense to only populate one of the two options.

v4:

Incorporate Patrick’s suggestions about the for-loop refactor and formatting the overlong lines. Now drop the function doc since it doesn’t apply anymore.

Also adjust the commit message now that we use it in the C source in an upcoming commit, not just/only as an assert-helper (in an upcoming commit).

v3 (new):

This is something I wanted to submit independently until the point about aliasing builtins was brought up.[1] It will help (in a small way) with the upcoming patch “git: allow alias-shadowing deprecated builtins”.

By the way: should command-list.txt be updated in some way (I didn’t know what way?)

🔗 1: https://lore.kernel.org/git/[email protected]/T/#mee19f8d39572f9021f9d3000758e87b6c32c967c

 Documentation/git.adoc |  3 ++-
 git.c                  | 26 ++++++++++++++++++--------
 2 files changed, 20 insertions(+), 9 deletions(-)
Documentation/git.adoc +2 -1
index 743b7b00e4d..a2f0838b168 100644
@@ -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
git.c +18 -8
index 83eac0aeab7..511efdf2056 100644
@@ -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);
@@ -590,7 +595,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 },
@@ -647,7 +652,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 },
@@ -668,11 +673,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);
 	}
Signature · 1 line
2.51.0.274.gdcb64e51a0f
#92 lore raw

PATCH v6 3/9 git: move seen-alias bookkeeping into handle_alias(...)

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

We are about to complicate the command handling by allowing *deprecated* builtins to be shadowed by aliases. We need to organize the code in order to facilitate that.[1]

The code in the while(1) speculatively adds commands to the list before finding out if it’s an alias. Let’s instead move it inside handle_alias(...)—where it conceptually belongs anyway—and in turn only run this logic when we have found an alias.[2]

[1]: We will do that with an additional call to handle_alias(1) inside
the loop. *Not* moving this code leaves a blind spot; we will miss alias looping crafted via deprecated builtin names
[2]: Also rename the list to a more descriptive name

---

Notes (series):
v6:

Apply Peff’s patch[1] in order to get rid of the extra output line.

Drop the footnote from the commit message now that we don’t have that problem any more.

🔗 1: https://lore.kernel.org/git/[email protected]/T/#mb3ac9c32c8527675a1f9cdf7709f2c74b3dd2a6c

v5 (new):

Do a preliminary code movement in order to avoid loop aliasing in the specific case of alias-shadowing deprecated builtins.

As noted in footnote 3: you get one more line of output before the loop aliasing is detected (and see the next commit):

'whatchanged' is aliased to 'pack-redundant'
'pack-redundant' is aliased to 'whatchanged'
'whatchanged' is aliased to 'pack-redundant'
fatal: alias loop detected: expansion of 'whatchanged' does not terminate:
whatchanged <==
pack-redundant ==>

This is an informational message specifically for <git cmd> -h. This output can be gotten after the next commit.

The output in this case is very incidental:

1. You are aliasing deprecated commands in a chain
2. You are using -h
3. You are going to get caught by the alias loop checker

 git.c | 48 +++++++++++++++++++++++++-----------------------
 1 file changed, 25 insertions(+), 23 deletions(-)
git.c +25 -23
index 511efdf2056..ef1e7b205aa 100644
@@ -365,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;
@@ -376,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);
@@ -423,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);
@@ -806,8 +827,7 @@ static void execv_dashed_external(const char **argv)
 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) {
 		/*
@@ -859,35 +879,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;
 }
Signature · 1 line
2.51.0.274.gdcb64e51a0f
#93 lore raw

PATCH v6 4/9 git: allow alias-shadowing deprecated builtins

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

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/[email protected]/
[3]: https://lore.kernel.org/git/BL3P221MB0449288C8B0FA448A227FD48833AA@BL3P221MB0449.NAMP221.PROD.OUTLOOK.COM/

---

Notes (series):
v6:

Drop footnote now that we don’t have the “extra output line” problem.

v5:

Fix a leak caused by calling alias_lookup(...) without freeing the return value by not calling that function.

Add tests for deprecated builtins for infinite alias looping as well as simple chaining. This is to exercise all code paths that we could think of.

Expand on the infinite alias looping test on the whole error output (compared to the one above). Link this change to the previous patch/commit by mentioning it in a footnote.

See the last footnote and the note on the previous commit/patch. I have opted not to get rid of this redundant line. But this documents the change and future changes that affect it will trigger this assertion (say if they remove the redundant line).

v4:

Better is_deprecated_command implementation.

v3 (new):

Prerequisite for telling the user that they can alias whatchanged to git log --raw --no-merged.

> I think that is good advice, but... it won't do anything until we > actually drop the whatchanged command, since until then we'll refuse to > override the command (even the crippled --i-still-use-this one). >
> We'd need something like the patch here:

❦

The

test_file_not_empty expect

is here because the git(1) command could fail. Make sure that it did indeed output anyhing on stdout. (Or if a previous redirect to expect outputted something it should be completely different to actual in any case)

I don’t know if this is just a waste.

 Documentation/config/alias.adoc |  3 ++-
 git.c                           | 17 ++++++++++++++
 t/t0014-alias.sh                | 40 +++++++++++++++++++++++++++++++++
 3 files changed, 59 insertions(+), 1 deletion(-)
Documentation/config/alias.adoc +2 -1
index 2c5db0ad842..3c8fab3a95c 100644
@@ -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.
 +
git.c +17 -0
index ef1e7b205aa..8c85da84c30 100644
@@ -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
t/t0014-alias.sh +40 -0
index 854d59ec58c..1b196ed9d6d 100755
@@ -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
Signature · 1 line
2.51.0.274.gdcb64e51a0f
#94 lore raw

PATCH v6 5/9 t0014: test shadowing of aliases for a sample of builtins

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

The previous commit added tests for shadowing deprecated builtins. Let’s make the test suite more complete by exercising a sample of the builtins and in turn test the documentation for git-config(1):

To avoid confusion and troubles with script usage, aliases that hide existing Git commands are ignored except for deprecated commands.

---

Notes (series):
v3 (new):

Only a sample of builtins since the test file takes about 600ms longer if I test all of them.

 t/t0014-alias.sh | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)
t/t0014-alias.sh +17 -0
index 1b196ed9d6d..07a53e7366e 100755
@@ -95,4 +95,21 @@ test_expect_success 'can alias-shadow via two deprecated builtins' '
 	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
Signature · 1 line
2.51.0.274.gdcb64e51a0f
#95 lore raw

PATCH v6 6/9 you-still-use-that??: help the user help themselves

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

Give the user a list of suggestions for what to do when they run a deprecated command.

The first order of action will be to check the breaking changes document;[1] this short error message says nothing about why this command is deprecated, and in any case going into any kind of detail might overwhelm the user.

Then they can find out if this has been discussed on the mailing list. Then users who e.g. are using git-whatchanged(1) can learn that this is arguably a plug-in replacement:

    git log <opts> --raw --no-merges

Finally they are invited to send an email to the mailing list.

Also drop the “please add” part in favor of just using the “refusing” die-message; these two would have been right after each other in this new version.

Also drop “Thanks” since it now would require a new paragraph.

[1]: www.git-scm.com has a disclaimer for these internal documents that
says that “This information is specific to the Git project”. That’s misleading in this particular case. But users are unlikely to get discouraged from reading about why they (or their programs) cannot run a command any more; it clearly concerns them.

---

Notes (series):
v2:

I had second thoughts about the bullet point about send-an-email. Change it to the one Eric Sunshine proposed;[1] make sure to spell out that you can send an email conditioned on not finding a suitable replacement.

Also change the area to something more pointed.

And also use a clear URL to refer to www.git-scm.com.

[1]: https://lore.kernel.org/git/CAPig+cQkVP57n_FE6dJ0uxvai-J7usxKFp8gzfEbPY=Ytsd6=Q@mail.gmail.com/

• Change send-an-email bullet point
• Change the area
• Use www.git-scm.com, not simply git-scm

v1:

An alternative to linking to www.git-scm.com is to move this document to a regular installed man page:

gitbreaking-changes(7)

What do you think?

I would then have to base my topic on the in-flight pw/3.0-commentchar-auto-deprecation, which in turn depends on ps/config-wo-the-repository.

Or just wait a bit for these to settle in.

 usage.c | 23 ++++++++++++++++++-----
 1 file changed, 18 insertions(+), 5 deletions(-)
usage.c +18 -5
index 81913236a4a..35dc57eb07e 100644
@@ -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)
 {
@@ -377,12 +378,24 @@ void bug_fl(const char *file, int line, const char *fmt, ...)
 
 NORETURN void you_still_use_that(const char *command_name)
 {
+	struct strbuf percent_encoded = STRBUF_INIT;
+	strbuf_add_percentencode(&percent_encoded,
+				 command_name,
+				 STRBUF_ENCODE_SLASH);
+
 	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 <[email protected]>.  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 <[email protected]> to let us\n"
+		  "  know that you still use this command and were unable\n"
+		  "  to determine a suitable replacement\n"
+		  "\n"),
+		command_name, percent_encoded.buf);
+	strbuf_release(&percent_encoded);
 	die(_("refusing to run without --i-still-use-this"));
 }
Signature · 1 line
2.51.0.274.gdcb64e51a0f
#96 lore raw

PATCH v6 7/9 whatchanged: hint about git-log(1) and aliasing

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

There have been quite a few --i-still-use-this user reports since Git 2.51.0 was released.[1][2] And it doesn’t seem like they are reading the man page about the git-log(1) equivalent.

Tell them what options to plug into git-log(1), either as a replacement command or as an alias.[3] That template produces almost the same output[4] and is arguably a plug-in replacement. Concretely, add an optional hint argument so that we can use it right after the initial error line.

Also mention the same concrete options in the documentation while we’re at it.

[1]: E.g.,
• https://lore.kernel.org/git/[email protected]/
• https://lore.kernel.org/git/[email protected]/#t
• https://lore.kernel.org/git/[email protected]/#t
• https://lore.kernel.org/git/[email protected]/
[2]: The error message on 2.51.0 does tell them to report it, unconditionally [3]: We allow aliasing deprecated builtins now for people who are very
used to the command name or just like it a lot
[4]: You only get different outputs if you happen to have empty
commits (no changes)[4]
[5]: https://lore.kernel.org/git/[email protected]/

---

Notes (series):
v5:

Mention the aliasing hint in the commit message.

v3:

Add an alias hint now that that is possible. Also prefix each hint-line with hint: .

v2:

Review found a whitespace error in the prev. patch version. I found a broken footnote and wanted to expand on the last footnote.

• Fix whitespace error (I should have used ci/check-whitespace.sh v2.51.0)
• Add missing colon (:) to footnote
• Expand on footnote; a sentence is enough to summarize the difference

 Documentation/git-whatchanged.adoc |  6 +++++-
 builtin/log.c                      |  8 +++++++-
 builtin/pack-redundant.c           |  2 +-
 git-compat-util.h                  |  2 +-
 usage.c                            | 14 ++++++++++----
 5 files changed, 24 insertions(+), 8 deletions(-)
Documentation/git-whatchanged.adoc +5 -1
index d21484026fe..e71d2aa2d27 100644
@@ -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
builtin/log.c +7 -1
index c2f8bbf8630..1d1e6e9130a 100644
@@ -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;
builtin/pack-redundant.c +1 -1
index fe81c293e3a..5d5ae4afa28 100644
@@ -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();
git-compat-util.h +1 -1
index 9408f463e31..398e0fac4fa 100644
@@ -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
usage.c +10 -4
index 35dc57eb07e..7545a616453 100644
@@ -376,7 +376,8 @@ 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,
@@ -384,8 +385,13 @@ NORETURN void you_still_use_that(const char *command_name)
 				 STRBUF_ENCODE_SLASH);
 
 	fprintf(stderr,
-		_("'%s' is nominated for removal.\n"
-		  "If you still use this command, here's what you can do:\n"
+		_("'%s' is nominated for removal.\n"), command_name);
+
+	if (hint)
+		fputs(hint, stderr);
+
+	fprintf(stderr,
+		_("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"
@@ -395,7 +401,7 @@ NORETURN void you_still_use_that(const char *command_name)
 		  "  know that you still use this command and were unable\n"
 		  "  to determine a suitable replacement\n"
 		  "\n"),
-		command_name, percent_encoded.buf);
+		percent_encoded.buf);
 	strbuf_release(&percent_encoded);
 	die(_("refusing to run without --i-still-use-this"));
 }
Signature · 1 line
2.51.0.274.gdcb64e51a0f
#97 lore raw

PATCH v6 8/9 whatchanged: remove not-even-shorter clause

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

The closest equivalent is git log --raw --no-merges.

Also change to “defaults” (implicit plural).

---
 Documentation/git-whatchanged.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/git-whatchanged.adoc +1 -1
index e71d2aa2d27..436e219b7d0 100644
@@ -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
 -----------
Signature · 1 line
2.51.0.274.gdcb64e51a0f
#98 lore raw

PATCH v6 9/9 BreakingChanges: remove claim about whatchanged reports

In reply to [email protected]

From: Kristoffer Haugsbakk <[email protected]>

This was written in e836757e14b (whatschanged: list it in BreakingChanges document, 2025-05-12) which was on the same topic that added the --i-still-use-this requirement.[1]

Maybe it was a work-in-progress comment/status.

[1]: jc/you-still-use-whatchanged

---

Notes (series):
Footnote solely to avoid awkward paragraph wrapping...

 Documentation/BreakingChanges.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/BreakingChanges.adoc +1 -1
index f8d2eba061c..c4985163c3c 100644
@@ -235,7 +235,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.
 
Signature · 1 line
2.51.0.274.gdcb64e51a0f
#99 lore raw
In reply to [email protected]
Quoted 7 lines · On Wed, Sep 17, 2025 at 10:24:10PM +0200, [email protected]…

§ Changes in v6

Use Peff’s patch to get rid of “the extra output”.[1] Update commit messages to not mention that.

Also use another fix from Peff (patch 1); don’t list git-whatchanged(1) as a builtin when WITH_BREAKING_CHANGES.

Thanks, I looked over the patches that touch the things we've been discussing, and it all looks good to me.

I am a little troubled that even after your patch 1, the test suite will get confused by a stray git-whatchanged binary left over from an earlier build. That may become an irritation later, when the breaking change becomes the norm. But maybe it won't be too bad. At that point the matching entry in .gitignore should be dropped, so at least "git clean" will find it. In the meantime, building with and without the breaking-changes flag can cause confusion, but presumably people aren't swapping between them too often? And I don't have a clever idea of how to do better, short of having the breaking-changes flag explicitly delete git-whatchanged from the build directory. Which feels a little gross.

So I'd say to call it good for now, and if it comes back to bite us later, it is not harder to address then than it would be now.

-Peff

#100 lore raw
Quoted 12 lines · Jeff King

I am a little troubled that even after your patch 1, the test suite will get confused by a stray git-whatchanged binary left over from an earlier build. That may become an irritation later, when the breaking change becomes the norm. But maybe it won't be too bad. At that point the matching entry in .gitignore should be dropped, so at least "git clean" will find it. In the meantime, building with and without the breaking-changes flag can cause confusion, but presumably people aren't swapping between them too often? And I don't have a clever idea of how to do better, short of having the breaking-changes flag explicitly delete git-whatchanged from the build directory. Which feels a little gross.

Quoted 2 lines

So I'd say to call it good for now, and if it comes back to bite us later, it is not harder to address then than it would be now.

Yup, my "before pushout" local builds almost always hit a snag from leftover git-whatchanged, until I tweaked the procedure to begin with "make distclean" before doing anything else X-<, but this may be good enough for now.

Thanks, both of you.

#101 lore raw
In reply to Jeff King
Quoted 5 lines · Jeff King
Quoted 2 lines · On Wed, Sep 17, 2025 at 10:24:10PM +0200, [email protected]…

§ Changes in v6

Quoted 8 lines
Quoted 5 lines

Use Peff’s patch to get rid of “the extra output”.[1] Update commit messages to not mention that.

Also use another fix from Peff (patch 1); don’t list git-whatchanged(1) as a builtin when WITH_BREAKING_CHANGES.

Thanks, I looked over the patches that touch the things we've been discussing, and it all looks good to me.

Thanks for the review!

Quoted 6 lines

I am a little troubled that even after your patch 1, the test suite will get confused by a stray git-whatchanged binary left over from an earlier build. That may become an irritation later, when the breaking change becomes the norm. But maybe it won't be too bad. At that point the matching entry in .gitignore should be dropped, so at least "git clean" will find it.

Yeah this seems tricky. And this in-between space seems difficult to deal with even with all the tools like gitignore and whatnot.

Quoted 3 lines

In the meantime, building with and without the breaking-changes flag can cause confusion, but presumably people aren't swapping between them too often?

I for one (and honestly) only did it now when I was informed that it failed the test. I’ll keep it confined in its own worktree where I have no qualms doing git-clean(1) every run.

Quoted 4 lines

And I don't have a clever idea of how
to do better, short of having the breaking-changes flag explicitly delete git-whatchanged from the build directory. Which feels a little gross.

Not that I know anything about build systems but that doesn’t sound like a bad or unprincipled solution.

Quoted 2 lines

So I'd say to call it good for now, and if it comes back to bite us later, it is not harder to address then than it would be now.

Signature · 1 line
Kristoffer