{"thread":{"id":"65434","subject":"[PATCH] stash: infer \"push\" when push-specific options are given","startedAt":"2026-04-04T14:36:51Z","lastAt":"2026-04-21T15:28:21Z","messageCount":17,"participants":["Deveshi Dwivedi","Mirko Faina","Phillip Wood","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"540892","messageId":"20260404143640.6679-1-deveshigurgaon@gmail.com","threadId":"65434","inReplyTo":null,"subject":"[PATCH] stash: infer \"push\" when push-specific options are given","fromName":"Deveshi Dwivedi","fromEmail":"deveshigurgaon@gmail.com","sentAt":"2026-04-04T14:36:40Z","receivedAt":"2026-04-04T14:36:51Z","isPatch":true,"body":"When \"git stash\" is run without the \"push\" subcommand, the command\ntries to assume \"push\" but rejects any non-option arguments (i.e.,\npathspecs without \"--\") to avoid treating a misspelled subcommand\nname as a pathspec.  The only exception is \"-p\", which sets\nforce_assume and allows pathspecs to follow.\n\nThis means \"git stash -m foo file\" is rejected even though \"-m\" is\nunambiguously a \"push\" option, and the user's intent is clear.  The\nsame applies to other push-specific options like \"--staged\",\n\"--keep-index\", \"--include-untracked\", and \"--pathspec-from-file\".\n\nExpand the set of options that force the \"push\" assumption to\ninclude all push-specific options, so that pathspec arguments are\naccepted without requiring \"--\" or the explicit \"push\" subcommand\nwhen the command line already contains a push-only option.\n\nThis was marked as #leftoverbits in [1].\n\n[1] https://lore.kernel.org/git/xmqqtsu1jipp.fsf@gitster.g/\n\nSigned-off-by: Deveshi Dwivedi <deveshigurgaon@gmail.com>\n---\n builtin/stash.c  |  4 +++-\n t/t3903-stash.sh | 21 +++++++++++++++++++++\n 2 files changed, 24 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 95c5005b0b..197814241c 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -1877,7 +1877,9 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t\targc = parse_options(argc, argv, prefix, options,\n \t\t\t\t     push_assumed ? git_stash_usage :\n \t\t\t\t     git_stash_push_usage, flags);\n-\t\tforce_assume |= patch_mode;\n+\t\tforce_assume |= patch_mode || stash_msg ||\n+\t\t\tkeep_index != -1 || only_staged ||\n+\t\t\tinclude_untracked || pathspec_from_file;\n \t}\n \n \tif (argc) {\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 70879941c2..9812b64989 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -414,6 +414,27 @@ test_expect_success 'dont assume push with non-option args' '\n \ttest_grep -e \"subcommand wasn'\\''t specified; '\\''push'\\'' can'\\''t be assumed due to unexpected token '\\''drop'\\''\" err\n '\n \n+test_expect_success 'assume push when options imply push' '\n+\tgit reset --hard &&\n+\techo changed >file &&\n+\tgit add file &&\n+\tgit stash -m \"implied push\" file &&\n+\tgit stash pop &&\n+\n+\tgit add file &&\n+\tgit stash --staged file &&\n+\tgit stash pop &&\n+\n+\tgit add file &&\n+\tgit stash --keep-index file &&\n+\tgit stash pop &&\n+\n+\techo untracked >untracked-file &&\n+\tgit stash --include-untracked untracked-file &&\n+\ttest_path_is_missing untracked-file &&\n+\tgit stash pop\n+'\n+\n test_expect_success 'stash --invalid-option' '\n \techo bar5 >file &&\n \techo bar6 >file2 &&\n\nbase-commit: 2855562ca6a9c6b0e7bc780b050c1e83c9fcfbd0\n-- \n2.52.0.230.gd8af7cadaa\n\n"},{"id":"540894","messageId":"adEqzZognqqY6OPb@exploit","threadId":"65434","inReplyTo":"20260404143640.6679-1-deveshigurgaon@gmail.com","subject":"Re: [PATCH] stash: infer \"push\" when push-specific options are given","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-04-04T15:19:11Z","receivedAt":"2026-04-04T15:19:21Z","isPatch":true,"body":"On Sat, Apr 04, 2026 at 02:36:40PM +0000, Deveshi Dwivedi wrote:\n> +test_expect_success 'assume push when options imply push' '\n> +\tgit reset --hard &&\n> +\techo changed >file &&\n> +\tgit add file &&\n> +\tgit stash -m \"implied push\" file &&\n> +\tgit stash pop &&\n> +\n> +\tgit add file &&\n> +\tgit stash --staged file &&\n> +\tgit stash pop &&\n> +\n> +\tgit add file &&\n> +\tgit stash --keep-index file &&\n> +\tgit stash pop &&\n> +\n> +\techo untracked >untracked-file &&\n> +\tgit stash --include-untracked untracked-file &&\n> +\ttest_path_is_missing untracked-file &&\n> +\tgit stash pop\n> +'\n> +\n>  test_expect_success 'stash --invalid-option' '\n>  \techo bar5 >file &&\n>  \techo bar6 >file2 &&\n\nThe last 'untracked-file' remains in the working tree after the pop. I\nknow it doesn't affect later tests but can you clean it up anyway? I\nrecently fixed a test that failed due to poor cleanup and I would like\nto see people prevent that too.\n\nThank you\n"},{"id":"540897","messageId":"20260404160357.11534-1-deveshigurgaon@gmail.com","threadId":"65434","inReplyTo":"20260404143640.6679-1-deveshigurgaon@gmail.com","subject":"[PATCH v2] stash: infer \"push\" when push-specific options are given","fromName":"Deveshi Dwivedi","fromEmail":"deveshigurgaon@gmail.com","sentAt":"2026-04-04T16:03:57Z","receivedAt":"2026-04-04T16:04:08Z","isPatch":true,"body":"When \"git stash\" is run without the \"push\" subcommand, the command\ntries to assume \"push\" but rejects any non-option arguments (i.e.,\npathspecs without \"--\") to avoid treating a misspelled subcommand\nname as a pathspec.  The only exception is \"-p\", which sets\nforce_assume and allows pathspecs to follow.\n\nThis means \"git stash -m foo file\" is rejected even though \"-m\" is\nunambiguously a \"push\" option, and the user's intent is clear.  The\nsame applies to other push-specific options like \"--staged\",\n\"--keep-index\", \"--include-untracked\", and \"--pathspec-from-file\".\n\nExpand the set of options that force the \"push\" assumption to\ninclude all push-specific options, so that pathspec arguments are\naccepted without requiring \"--\" or the explicit \"push\" subcommand\nwhen the command line already contains a push-only option.\n\nThis was marked as #leftoverbits in [1].\n\n[1] https://lore.kernel.org/git/xmqqtsu1jipp.fsf@gitster.g/\n\nSigned-off-by: Deveshi Dwivedi <deveshigurgaon@gmail.com>\n---\n\nChanges since v1:\n  - Clean up untracked-file after stash pop in test\n\n builtin/stash.c  |  4 +++-\n t/t3903-stash.sh | 22 ++++++++++++++++++++++\n 2 files changed, 25 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 95c5005b0b..197814241c 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -1877,7 +1877,9 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t\targc = parse_options(argc, argv, prefix, options,\n \t\t\t\t     push_assumed ? git_stash_usage :\n \t\t\t\t     git_stash_push_usage, flags);\n-\t\tforce_assume |= patch_mode;\n+\t\tforce_assume |= patch_mode || stash_msg ||\n+\t\t\tkeep_index != -1 || only_staged ||\n+\t\t\tinclude_untracked || pathspec_from_file;\n \t}\n \n \tif (argc) {\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 70879941c2..07bee22c27 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -414,6 +414,28 @@ test_expect_success 'dont assume push with non-option args' '\n \ttest_grep -e \"subcommand wasn'\\''t specified; '\\''push'\\'' can'\\''t be assumed due to unexpected token '\\''drop'\\''\" err\n '\n \n+test_expect_success 'assume push when options imply push' '\n+\tgit reset --hard &&\n+\techo changed >file &&\n+\tgit add file &&\n+\tgit stash -m \"implied push\" file &&\n+\tgit stash pop &&\n+\n+\tgit add file &&\n+\tgit stash --staged file &&\n+\tgit stash pop &&\n+\n+\tgit add file &&\n+\tgit stash --keep-index file &&\n+\tgit stash pop &&\n+\n+\techo untracked >untracked-file &&\n+\tgit stash --include-untracked untracked-file &&\n+\ttest_path_is_missing untracked-file &&\n+\tgit stash pop &&\n+\trm -f untracked-file\n+'\n+\n test_expect_success 'stash --invalid-option' '\n \techo bar5 >file &&\n \techo bar6 >file2 &&\n\nbase-commit: 2855562ca6a9c6b0e7bc780b050c1e83c9fcfbd0\n-- \n2.52.0.230.gd8af7cadaa\n\n"},{"id":"540936","messageId":"adGfyHbIKixz4dnx@exploit","threadId":"65434","inReplyTo":"20260404160357.11534-1-deveshigurgaon@gmail.com","subject":"Re: [PATCH v2] stash: infer \"push\" when push-specific options are given","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-04-04T23:40:57Z","receivedAt":"2026-04-04T23:41:02Z","isPatch":true,"body":"On Sat, Apr 04, 2026 at 04:03:57PM +0000, Deveshi Dwivedi wrote:\n> +test_expect_success 'assume push when options imply push' '\n> +\tgit reset --hard &&\n> +\techo changed >file &&\n> +\tgit add file &&\n> +\tgit stash -m \"implied push\" file &&\n> +\tgit stash pop &&\n> +\n> +\tgit add file &&\n> +\tgit stash --staged file &&\n> +\tgit stash pop &&\n> +\n> +\tgit add file &&\n> +\tgit stash --keep-index file &&\n> +\tgit stash pop &&\n> +\n> +\techo untracked >untracked-file &&\n> +\tgit stash --include-untracked untracked-file &&\n> +\ttest_path_is_missing untracked-file &&\n> +\tgit stash pop &&\n> +\trm -f untracked-file\n> +'\n> +\n\nThis leaves 'file' in the staging area. Using \"git reset --hard\" like\nyou did at the start is probably the easiest way to clean up.\n\n\nApart from the testing, since the following is not true anymore...\n\n  For quickly making a snapshot, you can omit \"push\".  In this mode,\n  non-option arguments are not allowed to prevent a misspelled\n  subcommand from making an unwanted stash entry.  The two exceptions to\n  this are `stash -p` which acts as alias for `stash push -p` and\n  pathspec elements, which are allowed after a double hyphen `--` for\n  disambiguation.\n\n...you should probably change the documentation for \"git push\" as well.\n"},{"id":"540945","messageId":"CAG7UgETEGxwXDGoVHT7dPXMDk70FdJd_w+TCPi1LqSvhuqWu4w@mail.gmail.com","threadId":"65434","inReplyTo":"adGfyHbIKixz4dnx@exploit","subject":"Re: [PATCH v2] stash: infer \"push\" when push-specific options are given","fromName":"Deveshi Dwivedi","fromEmail":"deveshigurgaon@gmail.com","sentAt":"2026-04-05T07:02:41Z","receivedAt":"2026-04-05T07:02:56Z","isPatch":true,"body":"> > +test_expect_success 'assume push when options imply push' '\n> > +     git reset --hard &&\n> > +     echo changed >file &&\n> > +     git add file &&\n> > +     git stash -m \"implied push\" file &&\n> > +     git stash pop &&\n> > +\n> > +     git add file &&\n> > +     git stash --staged file &&\n> > +     git stash pop &&\n> > +\n> > +     git add file &&\n> > +     git stash --keep-index file &&\n> > +     git stash pop &&\n> > +\n> > +     echo untracked >untracked-file &&\n> > +     git stash --include-untracked untracked-file &&\n> > +     test_path_is_missing untracked-file &&\n> > +     git stash pop &&\n> > +     rm -f untracked-file\n> > +'\n> > +\n>\n> This leaves 'file' in the staging area. Using \"git reset --hard\" like\n> you did at the start is probably the easiest way to clean up.\n>\n\nUnderstood, will use \"git reset --hard\" here as well.\n\n> Apart from the testing, since the following is not true anymore...\n>\n>   For quickly making a snapshot, you can omit \"push\".  In this mode,\n>   non-option arguments are not allowed to prevent a misspelled\n>   subcommand from making an unwanted stash entry.  The two exceptions to\n>   this are `stash -p` which acts as alias for `stash push -p` and\n>   pathspec elements, which are allowed after a double hyphen `--` for\n>   disambiguation.\n>\n> ...you should probably change the documentation for \"git push\" as well.\n\nYes, I will update the documentation as well and send a v3 shortly.\n"},{"id":"540951","messageId":"20260405110953.3316-1-deveshigurgaon@gmail.com","threadId":"65434","inReplyTo":"20260404143640.6679-1-deveshigurgaon@gmail.com","subject":"[PATCH v3] stash: infer \"push\" when push-specific options are given","fromName":"Deveshi Dwivedi","fromEmail":"deveshigurgaon@gmail.com","sentAt":"2026-04-05T11:09:53Z","receivedAt":"2026-04-05T11:10:06Z","isPatch":true,"body":"When \"git stash\" is run without the \"push\" subcommand, the command\ntries to assume \"push\" but rejects any non-option arguments (i.e.,\npathspecs without \"--\") to avoid treating a misspelled subcommand\nname as a pathspec.  The only exception is \"-p\", which sets\nforce_assume and allows pathspecs to follow.\n\nThis means \"git stash -m foo file\" is rejected even though \"-m\" is\nunambiguously a \"push\" option, and the user's intent is clear.  The\nsame applies to other push-specific options like \"--staged\",\n\"--keep-index\", \"--include-untracked\", and \"--pathspec-from-file\".\n\nExpand the set of options that force the \"push\" assumption to\ninclude all push-specific options, so that pathspec arguments are\naccepted without requiring \"--\" or the explicit \"push\" subcommand\nwhen the command line already contains a push-only option.\n\nThis was marked as #leftoverbits in [1].\n\n[1] https://lore.kernel.org/git/xmqqtsu1jipp.fsf@gitster.g/\n\nSigned-off-by: Deveshi Dwivedi <deveshigurgaon@gmail.com>\n---\n\nChanges since v2:\n  - Clean up staged file with git reset --hard at end of test\n  - Update documentation to reflect new push inference behavior\n\n Documentation/git-stash.adoc |  7 ++++---\n builtin/stash.c              |  4 +++-\n t/t3903-stash.sh             | 23 +++++++++++++++++++++++\n 3 files changed, 30 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex 235d57ddd8..e4e77c2f07 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -61,9 +61,10 @@ COMMANDS\n +\n For quickly making a snapshot, you can omit \"push\".  In this mode,\n non-option arguments are not allowed to prevent a misspelled\n-subcommand from making an unwanted stash entry.  The two exceptions to this\n-are `stash -p` which acts as alias for `stash push -p` and pathspec elements,\n-which are allowed after a double hyphen `--` for disambiguation.\n+subcommand from making an unwanted stash entry.  Pathspec elements\n+are allowed after a double hyphen `--` for disambiguation.  When\n+any push-specific option is given, the \"push\" subcommand is inferred\n+and pathspec arguments are also accepted without `--`.\n \n `save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-u | --include-untracked] [-a | --all] [-q | --quiet] [<message>]`::\n \ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 95c5005b0b..197814241c 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -1877,7 +1877,9 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t\targc = parse_options(argc, argv, prefix, options,\n \t\t\t\t     push_assumed ? git_stash_usage :\n \t\t\t\t     git_stash_push_usage, flags);\n-\t\tforce_assume |= patch_mode;\n+\t\tforce_assume |= patch_mode || stash_msg ||\n+\t\t\tkeep_index != -1 || only_staged ||\n+\t\t\tinclude_untracked || pathspec_from_file;\n \t}\n \n \tif (argc) {\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 70879941c2..f021dc9068 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -414,6 +414,29 @@ test_expect_success 'dont assume push with non-option args' '\n \ttest_grep -e \"subcommand wasn'\\''t specified; '\\''push'\\'' can'\\''t be assumed due to unexpected token '\\''drop'\\''\" err\n '\n \n+test_expect_success 'assume push when options imply push' '\n+\tgit reset --hard &&\n+\techo changed >file &&\n+\tgit add file &&\n+\tgit stash -m \"implied push\" file &&\n+\tgit stash pop &&\n+\n+\tgit add file &&\n+\tgit stash --staged file &&\n+\tgit stash pop &&\n+\n+\tgit add file &&\n+\tgit stash --keep-index file &&\n+\tgit stash pop &&\n+\n+\techo untracked >untracked-file &&\n+\tgit stash --include-untracked untracked-file &&\n+\ttest_path_is_missing untracked-file &&\n+\tgit stash pop &&\n+\trm -f untracked-file &&\n+\tgit reset --hard\n+'\n+\n test_expect_success 'stash --invalid-option' '\n \techo bar5 >file &&\n \techo bar6 >file2 &&\n\nbase-commit: 2855562ca6a9c6b0e7bc780b050c1e83c9fcfbd0\n-- \n2.52.0.230.gd8af7cadaa\n\n"},{"id":"541007","messageId":"adP3JZhS7IHDuUxm@exploit","threadId":"65434","inReplyTo":"20260405110953.3316-1-deveshigurgaon@gmail.com","subject":"Re: [PATCH v3] stash: infer \"push\" when push-specific options are given","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-04-06T18:15:11Z","receivedAt":"2026-04-06T18:15:22Z","isPatch":true,"body":"On Sun, Apr 05, 2026 at 11:09:53AM +0000, Deveshi Dwivedi wrote:\n> When \"git stash\" is run without the \"push\" subcommand, the command\n> tries to assume \"push\" but rejects any non-option arguments (i.e.,\n> pathspecs without \"--\") to avoid treating a misspelled subcommand\n> name as a pathspec.  The only exception is \"-p\", which sets\n> force_assume and allows pathspecs to follow.\n> \n> This means \"git stash -m foo file\" is rejected even though \"-m\" is\n> unambiguously a \"push\" option, and the user's intent is clear.  The\n> same applies to other push-specific options like \"--staged\",\n> \"--keep-index\", \"--include-untracked\", and \"--pathspec-from-file\".\n> \n> Expand the set of options that force the \"push\" assumption to\n> include all push-specific options, so that pathspec arguments are\n> accepted without requiring \"--\" or the explicit \"push\" subcommand\n> when the command line already contains a push-only option.\n> \n> This was marked as #leftoverbits in [1].\n> \n> [1] https://lore.kernel.org/git/xmqqtsu1jipp.fsf@gitster.g/\n> \n> Signed-off-by: Deveshi Dwivedi <deveshigurgaon@gmail.com>\n\nJust realized, \"--include-untracked\" is not specific only to 'push' as\n'show' accepts it too as an argument. \"--keep-index\" as well, but since\n'save' is deprecated I don't think anyone would mind and should be fine\nto leave it as is (though this is my opinion, should wait for others to\nsee what they think).\n"},{"id":"541048","messageId":"a280c7de-1357-44a9-afdd-bd473fd4e2a4@gmail.com","threadId":"65434","inReplyTo":"adP3JZhS7IHDuUxm@exploit","subject":"Re: [PATCH v3] stash: infer \"push\" when push-specific options are given","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-07T09:36:13Z","receivedAt":"2026-04-07T09:36:18Z","isPatch":true,"body":"On 06/04/2026 19:15, Mirko Faina wrote:\n> On Sun, Apr 05, 2026 at 11:09:53AM +0000, Deveshi Dwivedi wrote:\n>> When \"git stash\" is run without the \"push\" subcommand, the command\n>> tries to assume \"push\" but rejects any non-option arguments (i.e.,\n>> pathspecs without \"--\") to avoid treating a misspelled subcommand\n>> name as a pathspec.  The only exception is \"-p\", which sets\n>> force_assume and allows pathspecs to follow.\n>>\n>> This means \"git stash -m foo file\" is rejected even though \"-m\" is\n>> unambiguously a \"push\" option, and the user's intent is clear.  The\n>> same applies to other push-specific options like \"--staged\",\n>> \"--keep-index\", \"--include-untracked\", and \"--pathspec-from-file\".\n>>\n>> Expand the set of options that force the \"push\" assumption to\n>> include all push-specific options, so that pathspec arguments are\n>> accepted without requiring \"--\" or the explicit \"push\" subcommand\n>> when the command line already contains a push-only option.\n>>\n>> This was marked as #leftoverbits in [1].\n>>\n>> [1] https://lore.kernel.org/git/xmqqtsu1jipp.fsf@gitster.g/\n>>\n>> Signed-off-by: Deveshi Dwivedi <deveshigurgaon@gmail.com>\n> \n> Just realized, \"--include-untracked\" is not specific only to 'push' as\n> 'show' accepts it too as an argument.\n\n\"create\" accepts \"-m\" as well so that's not unique either. I agree with \nJunio's suggestion in the link above that we should assume \"push\" when \nthere is no subcommand given and error out if we see an unsupported \noption. That does not require the arguments to be unique to \"push\". A \ncomplete implementation would also support negated options like \"git \nstash --no-stage [<pathspec>]\". What is implemented in this patch maybe \nsufficient in practice but it would be good to mention the limitations \nin the commit message.\n\nThanks\n\nPhillip\n\n> \"--keep-index\" as well, but since\n> 'save' is deprecated I don't think anyone would mind and should be fine\n> to leave it as is (though this is my opinion, should wait for others to\n> see what they think).\n> \n\n"},{"id":"541289","messageId":"CAG7UgESxcbKb=Jzi36osBhD51KO9GvQr8esD=6BMupLAi33nWQ@mail.gmail.com","threadId":"65434","inReplyTo":"a280c7de-1357-44a9-afdd-bd473fd4e2a4@gmail.com","subject":"Re: [PATCH v3] stash: infer \"push\" when push-specific options are given","fromName":"Deveshi Dwivedi","fromEmail":"deveshigurgaon@gmail.com","sentAt":"2026-04-09T19:22:06Z","receivedAt":"2026-04-09T19:22:23Z","isPatch":true,"body":"> \"create\" accepts \"-m\" as well so that's not unique either. I agree with\n> Junio's suggestion in the link above that we should assume \"push\" when\n> there is no subcommand given and error out if we see an unsupported\n> option. That does not require the arguments to be unique to \"push\". A\n> complete implementation would also support negated options like \"git\n> stash --no-stage [<pathspec>]\". What is implemented in this patch maybe\n> sufficient in practice but it would be good to mention the limitations\n> in the commit message.\n>\n> Thanks\n>\n> Phillip\nSure, I will send a v4 with an updated commit message. Thank you.\n"},{"id":"541290","messageId":"adf-452vT7MBZfwc@exploit","threadId":"65434","inReplyTo":"CAG7UgESxcbKb=Jzi36osBhD51KO9GvQr8esD=6BMupLAi33nWQ@mail.gmail.com","subject":"Re: [PATCH v3] stash: infer \"push\" when push-specific options are given","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-04-09T19:37:53Z","receivedAt":"2026-04-09T19:38:06Z","isPatch":true,"body":"On Fri, Apr 10, 2026 at 12:52:06AM +0530, Deveshi Dwivedi wrote:\n> > \"create\" accepts \"-m\" as well so that's not unique either. I agree with\n> > Junio's suggestion in the link above that we should assume \"push\" when\n> > there is no subcommand given and error out if we see an unsupported\n> > option. That does not require the arguments to be unique to \"push\". A\n> > complete implementation would also support negated options like \"git\n> > stash --no-stage [<pathspec>]\". What is implemented in this patch maybe\n> > sufficient in practice but it would be good to mention the limitations\n> > in the commit message.\n> >\n> > Thanks\n> >\n> > Phillip\n> Sure, I will send a v4 with an updated commit message. Thank you.\n\nI think the documentation should change as well as it can be ambiguous.\nFrom reading \"push-specific options\" I'm assuming it would work only\nwith options that are unique to push, which is not the case, while\nothers might assume the correct behaviour which works with any option\nthat push accepts.\n"},{"id":"541297","messageId":"xmqqecknsx2t.fsf@gitster.g","threadId":"65434","inReplyTo":"20260405110953.3316-1-deveshigurgaon@gmail.com","subject":"Re: [PATCH v3] stash: infer \"push\" when push-specific options are given","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-09T20:22:34Z","receivedAt":"2026-04-09T20:22:37Z","isPatch":true,"body":"Deveshi Dwivedi <deveshigurgaon@gmail.com> writes:\n\n> +test_expect_success 'assume push when options imply push' '\n> +\tgit reset --hard &&\n> +\techo changed >file &&\n> +\tgit add file &&\n> +\tgit stash -m \"implied push\" file &&\n> +\tgit stash pop &&\n> +\n> +\tgit add file &&\n> +\tgit stash --staged file &&\n> +\tgit stash pop &&\n> +\n> +\tgit add file &&\n> +\tgit stash --keep-index file &&\n> +\tgit stash pop &&\n> +\n> +\techo untracked >untracked-file &&\n> +\tgit stash --include-untracked untracked-file &&\n> +\ttest_path_is_missing untracked-file &&\n\nA comment on these three lines.\n\n> +\tgit stash pop &&\n> +\trm -f untracked-file &&\n> +\tgit reset --hard\n> +'\n> +\n\nI suspect that they are meant to be \"clean-up after we are done with\nthe test, to avoid interfering with the next test\", but if so,\n\"clean-up at the very end\" is not a very effective strategy to do\nso.  Imagine that one of the previous steps fails, breaking all\nlater commands in the &&- cascade.  Sitting at the very end, your\nclean-up sequence will not run.  Unless the tester is running this\ntest script with the \"-i\" option, the test will move on to the next\npiece.  Installing clean-up handler with test_when_finished may be\na cleaner approach.\n\n\ttest_expect_success 'do this test' '\n\t\ttest_when_finished \"git stash clear; git reset --hard\" &&\n\t\tgit reset --hard &&\n\t\t... do all the dirty things in the working tree ...\n\n\t\ttest_when_finished \"rm -f untracked-file\" &&\n                echo untracked >untracked-file &&\n                git stash --include-untracked untracked-file &&\n\t\ttest_path_is_missing untracked-file\n\t'\n\nYou can use more than one test_when_finished in a single test.  \n\nIt is often done to add an upfront blunt hammer at the beginning to\ndo a clean-up without worrying too much about where exactly in the\ncommand sequence a breakage may happen (e.g., we may fail before we\nrun our first \"git add\", or \"git stash\", and \"git reset --hard\" or\n\"git stash clear\" may be an unnecessary no-op, but we do not worry\ntoo much about the clean-up step doing potentially unnecessary\nthings.\n\nOr you would set up a clean-up handler immediately before you create\na thing that you want to make sure you clean up.  If the command\nsequence fails before you echo the string into untracked-file to\ncreate it, there is no point preparing to remove it when you are\ndone.\n\nBoth approaches are commmonly used.\n\n>  test_expect_success 'stash --invalid-option' '\n>  \techo bar5 >file &&\n>  \techo bar6 >file2 &&\n>\n> base-commit: 2855562ca6a9c6b0e7bc780b050c1e83c9fcfbd0\n"},{"id":"541299","messageId":"xmqqa4vbswnq.fsf@gitster.g","threadId":"65434","inReplyTo":"a280c7de-1357-44a9-afdd-bd473fd4e2a4@gmail.com","subject":"Re: [PATCH v3] stash: infer \"push\" when push-specific options are given","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-09T20:31:37Z","receivedAt":"2026-04-09T20:31:40Z","isPatch":true,"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> \"create\" accepts \"-m\" as well so that's not unique either. I agree with \n> Junio's suggestion in the link above that we should assume \"push\" when \n> there is no subcommand given and error out if we see an unsupported \n> option.\n\nYeah, if -X were unique for \"pop\" and -Y were unique for \"push\", it\nis tempting to DWIM \"git stash -X\" to \"git stash pop -X\" while\nDWIMming \"git stash -Y\" to \"git stash push -Y\", but the thing is\nthat the urgency of each \"stash\" subcommand is different.  As the\n\"the boss is here and tells me to work on this completely unrelated\nthing, clear the desk as quickly as possible to switch context\"\ncommand, \"push\" deserves to have more quick access than other\ncommands.\n\nIt also makes it resilient if we said \"a command line that begins\nwith an option cannot be naming any 'git stash' subcommand, so we\nwill unconditionally insert 'push' before that first option\",\nbecause 'push' may later acquire \"-X\" or 'save' may acquire \"-Y\" and\nmaking these options no longer unique to a single subcommand.  A\nversion of Git may have treated \"git stash -Y\" as \"git stash push -Y\"\nbut if the next version that has \"git stash save -Y\" stopped accepting\n\"git stash -Y\" as \"git stash push -Y\" because -Y is not unique, the\nend users will be unhappy.\n\nAnd of course it is far easier to document and teach.\n\n"},{"id":"541445","messageId":"20260412195204.4636-1-deveshigurgaon@gmail.com","threadId":"65434","inReplyTo":"20260404143640.6679-1-deveshigurgaon@gmail.com","subject":"[PATCH v4] stash: infer \"push\" when command line starts with an option","fromName":"Deveshi Dwivedi","fromEmail":"deveshigurgaon@gmail.com","sentAt":"2026-04-12T19:52:04Z","receivedAt":"2026-04-12T19:52:13Z","isPatch":true,"body":"When \"git stash\" is run without the \"push\" subcommand, the command\ntries to assume \"push\" but rejects any non-option arguments (i.e.,\npathspecs without \"--\") to avoid treating a misspelled subcommand\nname as a pathspec.  The only exception is \"-p\", which sets\nforce_assume and allows pathspecs to follow.\n\nThis means \"git stash -m foo file\" is rejected even though \"-m\" is\nclearly an option and not a subcommand name, and the user's intent\nis clear.  The same applies to any command line that begins with an\noption.\n\nA command line that begins with an option cannot be naming a \"git\nstash\" subcommand, so unconditionally assume \"push\" in that case and\nallow pathspec arguments to follow without requiring \"--\".  This is\nsimpler and more robust than checking a specific list of options,\nand remains correct even if push or other subcommands gain new\noptions in the future.\n\nNote that this does not check for negated options, so \"git stash\n--no-staged [<pathspec>]\" is still rejected.  Handling negated\noptions would require teaching the inference logic about them\nexplicitly.\n\nThis was marked as #leftoverbits in [1].\n\n[1] https://lore.kernel.org/git/xmqqtsu1jipp.fsf@gitster.g/\n\nSigned-off-by: Deveshi Dwivedi <deveshigurgaon@gmail.com>\n---\n\nChanges since v3:\n  - Rewrote the approach per Junio and Phillip's suggestion: instead of\n    checking a specific list of push-only options, unconditionally\n    assume \"push\" whenever the command line begins with any option.\n    This is simpler and robust against future option additions, and\n    sidesteps the fact that -m and --include-untracked are not unique\n    to \"push\".\n  - Updated the test to reflect the new rule and switched cleanup to\n    test_when_finished per Junio's suggestion.\n  - Updated documentation accordingly.\n\n Documentation/git-stash.adoc |  7 ++++---\n builtin/stash.c              |  6 ++++--\n t/t3903-stash.sh             | 26 ++++++++++++++++++++++++--\n 3 files changed, 32 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex 235d57ddd8..135719611a 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -61,9 +61,10 @@ COMMANDS\n +\n For quickly making a snapshot, you can omit \"push\".  In this mode,\n non-option arguments are not allowed to prevent a misspelled\n-subcommand from making an unwanted stash entry.  The two exceptions to this\n-are `stash -p` which acts as alias for `stash push -p` and pathspec elements,\n-which are allowed after a double hyphen `--` for disambiguation.\n+subcommand from making an unwanted stash entry.  Pathspec elements\n+are allowed after a double hyphen `--` for disambiguation.  When\n+the command line begins with an option, \"push\" is inferred and\n+pathspec arguments are also accepted without `--`.\n \n `save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-u | --include-untracked] [-a | --all] [-q | --quiet] [<message>]`::\n \ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 95c5005b0b..be96338d35 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -1871,13 +1871,15 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \tif (argc) {\n \t\tint flags = PARSE_OPT_KEEP_DASHDASH;\n \n-\t\tif (push_assumed)\n+\t\tif (push_assumed) {\n \t\t\tflags |= PARSE_OPT_STOP_AT_NON_OPTION;\n+\t\t\tif (argc > 1 && argv[1][0] == '-')\n+\t\t\t\tforce_assume = 1;\n+\t\t}\n \n \t\targc = parse_options(argc, argv, prefix, options,\n \t\t\t\t     push_assumed ? git_stash_usage :\n \t\t\t\t     git_stash_push_usage, flags);\n-\t\tforce_assume |= patch_mode;\n \t}\n \n \tif (argc) {\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 70879941c2..88f2b3c86b 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -410,8 +410,30 @@ test_expect_success 'stash --staged with binary file' '\n '\n \n test_expect_success 'dont assume push with non-option args' '\n-\ttest_must_fail git stash -q drop 2>err &&\n-\ttest_grep -e \"subcommand wasn'\\''t specified; '\\''push'\\'' can'\\''t be assumed due to unexpected token '\\''drop'\\''\" err\n+\ttest_must_fail git stash someunknown 2>err &&\n+\ttest_grep -e \"subcommand wasn'\\''t specified; '\\''push'\\'' can'\\''t be assumed due to unexpected token '\\''someunknown'\\''\" err\n+'\n+\n+test_expect_success 'assume push when command line starts with option' '\n+\ttest_when_finished \"git reset --hard\" &&\n+\ttest_when_finished \"rm -f untracked-file\" &&\n+\techo changed >file &&\n+\tgit add file &&\n+\tgit stash -m \"implied push\" file &&\n+\tgit stash pop &&\n+\n+\tgit add file &&\n+\tgit stash --staged file &&\n+\tgit stash pop &&\n+\n+\tgit add file &&\n+\tgit stash --keep-index file &&\n+\tgit stash pop &&\n+\n+\techo untracked >untracked-file &&\n+\tgit stash --include-untracked untracked-file &&\n+\ttest_path_is_missing untracked-file &&\n+\tgit stash pop\n '\n \n test_expect_success 'stash --invalid-option' '\n\nbase-commit: 2855562ca6a9c6b0e7bc780b050c1e83c9fcfbd0\n-- \n2.52.0.230.gd8af7cadaa\n\n"},{"id":"541451","messageId":"00b92c0b-8602-45e4-bec3-c7c538bd288b@gmail.com","threadId":"65434","inReplyTo":"20260412195204.4636-1-deveshigurgaon@gmail.com","subject":"Re: [PATCH v4] stash: infer \"push\" when command line starts with an option","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-13T09:08:13Z","receivedAt":"2026-04-13T09:08:16Z","isPatch":true,"body":"On 12/04/2026 20:52, Deveshi Dwivedi wrote:\n> When \"git stash\" is run without the \"push\" subcommand, the command\n> tries to assume \"push\" but rejects any non-option arguments (i.e.,\n> pathspecs without \"--\") to avoid treating a misspelled subcommand\n> name as a pathspec.  The only exception is \"-p\", which sets\n> force_assume and allows pathspecs to follow.\n> \n> This means \"git stash -m foo file\" is rejected even though \"-m\" is\n> clearly an option and not a subcommand name, and the user's intent\n> is clear.  The same applies to any command line that begins with an\n> option.\n> \n> A command line that begins with an option cannot be naming a \"git\n> stash\" subcommand, so unconditionally assume \"push\" in that case and\n> allow pathspec arguments to follow without requiring \"--\".  This is\n> simpler and more robust than checking a specific list of options,\n> and remains correct even if push or other subcommands gain new\n> options in the future.\n> \n> Note that this does not check for negated options, so \"git stash\n> --no-staged [<pathspec>]\" is still rejected.  Handling negated\n> options would require teaching the inference logic about them\n> explicitly.\n\nThat was true of the implementation in V3 which checked to see if any of \nthe option variables were non-zero. Looking below, it now checks if the \nfirst argument begins with \"-\" which means that force_assume will be \ntrue when \"--no-stage\" is given.\n\nThe implementation looks good, I've left a couple of comments below.\n\n> This was marked as #leftoverbits in [1].\n> \n> [1] https://lore.kernel.org/git/xmqqtsu1jipp.fsf@gitster.g/\n>  \n> Signed-off-by: Deveshi Dwivedi <deveshigurgaon@gmail.com>\n> ---\n> \n> Changes since v3:\n>    - Rewrote the approach per Junio and Phillip's suggestion: instead of\n>      checking a specific list of push-only options, unconditionally\n>      assume \"push\" whenever the command line begins with any option.\n>      This is simpler and robust against future option additions, and\n>      sidesteps the fact that -m and --include-untracked are not unique\n>      to \"push\".\n>    - Updated the test to reflect the new rule and switched cleanup to\n>      test_when_finished per Junio's suggestion.\n>    - Updated documentation accordingly.\n> \n>   Documentation/git-stash.adoc |  7 ++++---\n>   builtin/stash.c              |  6 ++++--\n>   t/t3903-stash.sh             | 26 ++++++++++++++++++++++++--\n>   3 files changed, 32 insertions(+), 7 deletions(-)\n> \n> diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\n> index 235d57ddd8..135719611a 100644\n> --- a/Documentation/git-stash.adoc\n> +++ b/Documentation/git-stash.adoc\n> @@ -61,9 +61,10 @@ COMMANDS\n>   +\n>   For quickly making a snapshot, you can omit \"push\".  In this mode,\n>   non-option arguments are not allowed to prevent a misspelled\n> -subcommand from making an unwanted stash entry.  The two exceptions to this\n> -are `stash -p` which acts as alias for `stash push -p` and pathspec elements,\n> -which are allowed after a double hyphen `--` for disambiguation.\n> +subcommand from making an unwanted stash entry.  Pathspec elements\n> +are allowed after a double hyphen `--` for disambiguation.  When\n> +the command line begins with an option, \"push\" is inferred and\n\n\"assumed\" might be easier to understand than \"inferred\"\n\n> +pathspec arguments are also accepted without `--`.\n>   \n>   `save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-u | --include-untracked] [-a | --all] [-q | --quiet] [<message>]`::\n>   \n> diff --git a/builtin/stash.c b/builtin/stash.c\n> index 95c5005b0b..be96338d35 100644\n> --- a/builtin/stash.c\n> +++ b/builtin/stash.c\n> @@ -1871,13 +1871,15 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n>   \tif (argc) {\n>   \t\tint flags = PARSE_OPT_KEEP_DASHDASH;\n>   \n> -\t\tif (push_assumed)\n> +\t\tif (push_assumed) {\n>   \t\t\tflags |= PARSE_OPT_STOP_AT_NON_OPTION;\n> +\t\t\tif (argc > 1 && argv[1][0] == '-')\n> +\t\t\t\tforce_assume = 1;\n\nWe assume push was given if the first argument starts with '-'. That \nmakes sense. If we get on invalid option we'll display the push usage as \nwe did before.\n\n> +\t\t}\n>   \n>   \t\targc = parse_options(argc, argv, prefix, options,\n>   \t\t\t\t     push_assumed ? git_stash_usage :\n>   \t\t\t\t     git_stash_push_usage, flags);\n> -\t\tforce_assume |= patch_mode;\n>   \t}\n>   \n>   \tif (argc) {\n> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n> index 70879941c2..88f2b3c86b 100755\n> --- a/t/t3903-stash.sh\n> +++ b/t/t3903-stash.sh\n> @@ -410,8 +410,30 @@ test_expect_success 'stash --staged with binary file' '\n>   '\n>   \n>   test_expect_success 'dont assume push with non-option args' '\n> -\ttest_must_fail git stash -q drop 2>err &&\n> -\ttest_grep -e \"subcommand wasn'\\''t specified; '\\''push'\\'' can'\\''t be assumed due to unexpected token '\\''drop'\\''\" err\n> +\ttest_must_fail git stash someunknown 2>err &&\n> +\ttest_grep -e \"subcommand wasn'\\''t specified; '\\''push'\\'' can'\\''t be assumed due to unexpected token '\\''someunknown'\\''\" err\n\nThis is based on the existing test, but there is no need for \"-e\" and we \nnormally match a single quote as \"${SQ}\" (which is defined by the test \nsuite) or simply \".\"\n\n> +'\n> +\n> +test_expect_success 'assume push when command line starts with option' '\n> +\ttest_when_finished \"git reset --hard\" &&\n> +\ttest_when_finished \"rm -f untracked-file\" &&\n> +\techo changed >file &&\n> +\tgit add file &&\n> +\tgit stash -m \"implied push\" file &&\n> +\tgit stash pop &&\n> +\n> +\tgit add file &&\n> +\tgit stash --staged file &&\n> +\tgit stash pop &&\n> +\n> +\tgit add file &&\n> +\tgit stash --keep-index file &&\n> +\tgit stash pop &&\n> +\n> +\techo untracked >untracked-file &&\n> +\tgit stash --include-untracked untracked-file &&\n> +\ttest_path_is_missing untracked-file &&\n> +\tgit stash pop\n>   '\n\nThis test looks good\n\nThanks\n\nPhillip\n\n>   test_expect_success 'stash --invalid-option' '\n> \n> base-commit: 2855562ca6a9c6b0e7bc780b050c1e83c9fcfbd0\n\n"},{"id":"541477","messageId":"xmqqeckilwwz.fsf@gitster.g","threadId":"65434","inReplyTo":"20260412195204.4636-1-deveshigurgaon@gmail.com","subject":"Re: [PATCH v4] stash: infer \"push\" when command line starts with an option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-13T15:09:16Z","receivedAt":"2026-04-13T15:09:19Z","isPatch":true,"body":"Deveshi Dwivedi <deveshigurgaon@gmail.com> writes:\n\n> When \"git stash\" is run without the \"push\" subcommand, the command\n> tries to assume \"push\" but rejects any non-option arguments (i.e.,\n> pathspecs without \"--\") to avoid treating a misspelled subcommand\n> name as a pathspec.\n\nI think \"run without the 'push' subcommand\" above should be \"run\nwithout any subcommand (on the command line)\".  \"git stash pop\n-- paths\" is run without the \"push\" subcommand, and obviously we do\nnot want it to assume \"push\".\n\n> A command line that begins with an option cannot be naming a \"git\n> stash\" subcommand, so unconditionally assume \"push\" in that case and\n> allow pathspec arguments to follow without requiring \"--\".  This is\n> simpler and more robust than checking a specific list of options,\n> and remains correct even if push or other subcommands gain new\n> options in the future.\n\nGood.\n\n> Note that this does not check for negated options, so \"git stash\n> --no-staged [<pathspec>]\" is still rejected.  Handling negated\n> options would require teaching the inference logic about them\n> explicitly.\n\nThat is unexpected, and unfortunate.  I would have expected, since\nwe are now sending any thing that is not unrecognised to \"push\", it\nwould largely be the matter of removing special casing code about\npush_assumed from push_stash() and adding some to its caller, which\nis cmd_stash().  It would first look at its table of subcommands and\nif it finds a hit, calls the handler.  If not, and if there is no\nargument or if the first argument begins with a dash \"if (argv[1] &&\nargv[1][0] == '-')\", then unshift \"push\" into the argv[] array, and\ncall push_stash(), which would complain if the command line (with\n\"push\" prepended) does not make sense to it.  If the above two\nconditions were not met, cmd_stash() would not call push_stash() but\ncomplain that it did not get a valid command.  Or something like that.\n\nThat way, there is nothing that makes \"--no-something\" any more\nspecial than \"--something\", no?\n\n\n\n\n"},{"id":"541886","messageId":"20260419165453.32593-1-deveshigurgaon@gmail.com","threadId":"65434","inReplyTo":"20260404143640.6679-1-deveshigurgaon@gmail.com","subject":"[PATCH v5] stash: assume \"push\" when command line starts with an option","fromName":"Deveshi Dwivedi","fromEmail":"deveshigurgaon@gmail.com","sentAt":"2026-04-19T16:54:53Z","receivedAt":"2026-04-19T16:55:03Z","isPatch":true,"body":"When \"git stash\" is run without any subcommand (on the command line),\nthe command tries to assume \"push\" but rejects non-option arguments\n(i.e., pathspecs without \"--\") to avoid treating a misspelled\nsubcommand name as a pathspec.\n\nThis means \"git stash -m foo file\" is rejected even though \"-m\" is\nclearly an option and not a subcommand name, and the user's intent\nis clear.\n\nA command line that begins with an option cannot be naming a \"git\nstash\" subcommand, so move the decision to cmd_stash(): when no\nsubcommand matches and the first argument starts with \"-\", assume\n\"push\". When the first argument does not start with \"-\", reject it as\nan unexpected token as before.\n\nThis simplifies push_stash() by removing push_assumed/force_assume\nlogic; the caller has already made the assumption decision.\n\nThis was marked as #leftoverbits in [1].\n\n[1] https://lore.kernel.org/git/xmqqtsu1jipp.fsf@gitster.g/\n\nSigned-off-by: Deveshi Dwivedi <deveshigurgaon@gmail.com>\n\n---\n\nChanges since v4:\n  - Move push-assumption logic from push_stash() to cmd_stash().\n  - Remove stale push_assumed/force_assume handling.\n  - Drop incorrect note about negated options being rejected.\n  - Use \"assumed\" (not \"inferred\") in documentation.\n  - Update tests/style (including ${SQ}) and add --no-keep-index coverage.\n---\n Documentation/git-stash.adoc |  7 +++--\n builtin/stash.c              | 53 ++++++++++++++----------------------\n t/t3903-stash.sh             | 30 ++++++++++++++++++--\n 3 files changed, 53 insertions(+), 37 deletions(-)\n\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex 235d57ddd8..946b51c7a5 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -61,9 +61,10 @@ COMMANDS\n +\n For quickly making a snapshot, you can omit \"push\".  In this mode,\n non-option arguments are not allowed to prevent a misspelled\n-subcommand from making an unwanted stash entry.  The two exceptions to this\n-are `stash -p` which acts as alias for `stash push -p` and pathspec elements,\n-which are allowed after a double hyphen `--` for disambiguation.\n+subcommand from making an unwanted stash entry.  Pathspec elements\n+are allowed after a double hyphen `--` for disambiguation.  When\n+the command line begins with an option, \"push\" is assumed and\n+pathspec arguments are also accepted without `--`.\n \n `save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-u | --include-untracked] [-a | --all] [-q | --quiet] [<message>]`::\n \ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 95c5005b0b..bf04cf58a6 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -1831,9 +1831,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n }\n \n static int push_stash(int argc, const char **argv, const char *prefix,\n-\t\t      int push_assumed)\n+\t\t      const char * const usage[])\n {\n-\tint force_assume = 0;\n \tint keep_index = -1;\n \tint only_staged = 0;\n \tint patch_mode = 0;\n@@ -1868,26 +1867,14 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n \t};\n \tint ret;\n \n-\tif (argc) {\n-\t\tint flags = PARSE_OPT_KEEP_DASHDASH;\n-\n-\t\tif (push_assumed)\n-\t\t\tflags |= PARSE_OPT_STOP_AT_NON_OPTION;\n-\n+\tif (argc)\n \t\targc = parse_options(argc, argv, prefix, options,\n-\t\t\t\t     push_assumed ? git_stash_usage :\n-\t\t\t\t     git_stash_push_usage, flags);\n-\t\tforce_assume |= patch_mode;\n-\t}\n+\t\t\t\t     usage,\n+\t\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n \n-\tif (argc) {\n-\t\tif (!strcmp(argv[0], \"--\")) {\n-\t\t\targc--;\n-\t\t\targv++;\n-\t\t} else if (push_assumed && !force_assume) {\n-\t\t\tdie(\"subcommand wasn't specified; 'push' can't be assumed due to unexpected token '%s'\",\n-\t\t\t    argv[0]);\n-\t\t}\n+\tif (argc && !strcmp(argv[0], \"--\")) {\n+\t\targc--;\n+\t\targv++;\n \t}\n \n \tparse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN,\n@@ -1935,7 +1922,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n static int push_stash_unassumed(int argc, const char **argv, const char *prefix,\n \t\t\t\tstruct repository *repo UNUSED)\n {\n-\treturn push_stash(argc, argv, prefix, 0);\n+\treturn push_stash(argc, argv, prefix, git_stash_push_usage);\n }\n \n static int save_stash(int argc, const char **argv, const char *prefix,\n@@ -2387,7 +2374,6 @@ int cmd_stash(int argc,\n {\n \tpid_t pid = getpid();\n \tconst char *index_file;\n-\tstruct strvec args = STRVEC_INIT;\n \tparse_opt_subcommand_fn *fn = NULL;\n \tstruct option options[] = {\n \t\tOPT_SUBCOMMAND(\"apply\", &fn, apply_stash),\n@@ -2427,19 +2413,22 @@ int cmd_stash(int argc,\n \telse if (!argc)\n \t\treturn !!push_stash_unassumed(0, NULL, prefix, repo);\n \n-\t/* Assume 'stash push' */\n-\tstrvec_push(&args, \"push\");\n-\tstrvec_pushv(&args, argv);\n+\tif (argv[0][0] != '-')\n+\t\tdie(\"subcommand wasn't specified; 'push' can't be assumed due to unexpected token '%s'\",\n+\t\t    argv[0]);\n \n \t/*\n-\t * `push_stash()` ends up modifying the array, which causes memory\n-\t * leaks if we didn't copy the array here.\n+\t * When the command line starts with an option, assume 'push'.\n+\t * Unshift \"push\" into argv so that parse_options() skips it\n+\t * as the subcommand name.  Use git_stash_usage so that invalid\n+\t * options show the general stash usage rather than the\n+\t * push-specific usage.\n \t */\n-\tDUP_ARRAY(args_copy, args.v, args.nr);\n-\n-\tret = !!push_stash(args.nr, args_copy, prefix, 1);\n-\n-\tstrvec_clear(&args);\n+\tALLOC_ARRAY(args_copy, argc + 1);\n+\targs_copy[0] = \"push\";\n+\tmemcpy(&args_copy[1], argv, argc * sizeof(const char *));\n+\targc++;\n+\tret = !!push_stash(argc, args_copy, prefix, git_stash_usage);\n \tfree(args_copy);\n \treturn ret;\n }\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 70879941c2..836cc29a6b 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -410,8 +410,34 @@ test_expect_success 'stash --staged with binary file' '\n '\n \n test_expect_success 'dont assume push with non-option args' '\n-\ttest_must_fail git stash -q drop 2>err &&\n-\ttest_grep -e \"subcommand wasn'\\''t specified; '\\''push'\\'' can'\\''t be assumed due to unexpected token '\\''drop'\\''\" err\n+\ttest_must_fail git stash someunknown 2>err &&\n+\ttest_grep \"subcommand wasn${SQ}t specified; ${SQ}push${SQ} can${SQ}t be assumed due to unexpected token ${SQ}someunknown${SQ}\" err\n+'\n+\n+test_expect_success 'assume push when command line starts with option' '\n+\ttest_when_finished \"git reset --hard\" &&\n+\ttest_when_finished \"rm -f untracked-file\" &&\n+\techo changed >file &&\n+\tgit add file &&\n+\tgit stash -m \"implied push\" file &&\n+\tgit stash pop &&\n+\n+\tgit add file &&\n+\tgit stash --staged file &&\n+\tgit stash pop &&\n+\n+\tgit add file &&\n+\tgit stash --keep-index file &&\n+\tgit stash pop &&\n+\n+\tgit add file &&\n+\tgit stash --no-keep-index file &&\n+\tgit stash pop &&\n+\n+\techo untracked >untracked-file &&\n+\tgit stash --include-untracked untracked-file &&\n+\ttest_path_is_missing untracked-file &&\n+\tgit stash pop\n '\n \n test_expect_success 'stash --invalid-option' '\n\nbase-commit: 2855562ca6a9c6b0e7bc780b050c1e83c9fcfbd0\n-- \n2.52.0.230.gd8af7cadaa\n\n"},{"id":"542045","messageId":"980af898-5a56-4bc1-9222-74ceb9de9fac@gmail.com","threadId":"65434","inReplyTo":"20260419165453.32593-1-deveshigurgaon@gmail.com","subject":"Re: [PATCH v5] stash: assume \"push\" when command line starts with an option","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-21T15:28:16Z","receivedAt":"2026-04-21T15:28:21Z","isPatch":true,"body":"On 19/04/2026 17:54, Deveshi Dwivedi wrote:\n\nThis has changed more than I was expecting it to, but I think the \napproach of checking if argv[0] starts with '-' in cmd_stash() makes \nsense. I've left a few comments below.\n\n> diff --git a/builtin/stash.c b/builtin/stash.c\n> index 95c5005b0b..bf04cf58a6 100644\n> --- a/builtin/stash.c\n> +++ b/builtin/stash.c\n> @@ -1831,9 +1831,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q\n>   }\n>   \n>   static int push_stash(int argc, const char **argv, const char *prefix,\n> -\t\t      int push_assumed)\n> +\t\t      const char * const usage[])\n>   {\n> -\tint force_assume = 0;\n>   \tint keep_index = -1;\n>   \tint only_staged = 0;\n>   \tint patch_mode = 0;\n> @@ -1868,26 +1867,14 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n>   \t};\n>   \tint ret;\n>   \n> -\tif (argc) {\n> -\t\tint flags = PARSE_OPT_KEEP_DASHDASH;\n> -\n> -\t\tif (push_assumed)\n> -\t\t\tflags |= PARSE_OPT_STOP_AT_NON_OPTION;\n> -\n> +\tif (argc)\n>   \t\targc = parse_options(argc, argv, prefix, options,\n> -\t\t\t\t     push_assumed ? git_stash_usage :\n> -\t\t\t\t     git_stash_push_usage, flags);\n> -\t\tforce_assume |= patch_mode;\n> -\t}\n> +\t\t\t\t     usage,\n> +\t\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n\nAs all we do with '--' is remove it now that we don't need to check if \nit was passed, there is not much point in asking parse_options() to keep \nit for us. You can just pass '0' here and drop the if statement below.\n\n>   \n> -\tif (argc) {\n> -\t\tif (!strcmp(argv[0], \"--\")) {\n> -\t\t\targc--;\n> -\t\t\targv++;\n> -\t\t} else if (push_assumed && !force_assume) {\n> -\t\t\tdie(\"subcommand wasn't specified; 'push' can't be assumed due to unexpected token '%s'\",\n> -\t\t\t    argv[0]);\n> -\t\t}\n> +\tif (argc && !strcmp(argv[0], \"--\")) {\n> +\t\targc--;\n> +\t\targv++;\n>   \t}\n>   \n>   \tparse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN,\n> @@ -1935,7 +1922,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,\n>   static int push_stash_unassumed(int argc, const char **argv, const char *prefix,\n>   \t\t\t\tstruct repository *repo UNUSED)\n>   {\n> -\treturn push_stash(argc, argv, prefix, 0);\n> +\treturn push_stash(argc, argv, prefix, git_stash_push_usage);\n>   }\n>   \n>   static int save_stash(int argc, const char **argv, const char *prefix,\n> @@ -2387,7 +2374,6 @@ int cmd_stash(int argc,\n>   {\n>   \tpid_t pid = getpid();\n>   \tconst char *index_file;\n> -\tstruct strvec args = STRVEC_INIT;\n>   \tparse_opt_subcommand_fn *fn = NULL;\n>   \tstruct option options[] = {\n>   \t\tOPT_SUBCOMMAND(\"apply\", &fn, apply_stash),\n> @@ -2427,19 +2413,22 @@ int cmd_stash(int argc,\n>   \telse if (!argc)\n>   \t\treturn !!push_stash_unassumed(0, NULL, prefix, repo);\n>   \n> -\t/* Assume 'stash push' */\n> -\tstrvec_push(&args, \"push\");\n> -\tstrvec_pushv(&args, argv);> +\tif (argv[0][0] != '-')\n> +\t\tdie(\"subcommand wasn't specified; 'push' can't be assumed due to unexpected token '%s'\",\n> +\t\t    argv[0]);\n\nIf there's no option then we die straight away which makes sense. Part \nof me wonders if we should also show the usage for \"git stash\" in case \nthe user misspelled a subcommand name. The other part of me finds the \nway git is so keen to print reams of usage at the slightest provocation \nquite annoying, but at least the \"git stash\" usage is fairly small. \nLooking at the history we used to complain about an unknown subcommand \nbut that was changed without any explanation in 8c3713cede (stash: \neliminate crude option parsing, 2020-02-17)\n\n>   \t/*\n> -\t * `push_stash()` ends up modifying the array, which causes memory\n> -\t * leaks if we didn't copy the array here.\n> +\t * When the command line starts with an option, assume 'push'.\n> +\t * Unshift \"push\" into argv so that parse_options() skips it\n> +\t * as the subcommand name.  Use git_stash_usage so that invalid\n> +\t * options show the general stash usage rather than the\n> +\t * push-specific usage.\n>   \t */\n\nI'm not really sure if this change to the usage is an improvement or \nnot. If we document that an option without a subcommand means \"push\" \nthen isn't it confusing to show the usage for \"git stash\" rather than \n\"git stash push\"?\n\n> -\tDUP_ARRAY(args_copy, args.v, args.nr);\n> -\n> -\tret = !!push_stash(args.nr, args_copy, prefix, 1);\n> -\n> -\tstrvec_clear(&args);\n> +\tALLOC_ARRAY(args_copy, argc + 1);\n> +\targs_copy[0] = \"push\";\n> +\tmemcpy(&args_copy[1], argv, argc * sizeof(const char *));\n> +\targc++;\n\nTaking a shallow copy avoids the memory leak mentioned in the comment \nyou edited above. That might be worth doing but it should be done as a \nseparate preparatory step because it is orthogonal to the other changes \nhere. We should use COPY_ARRAY() rather than memcpy() and we should also \nallocate 'argc + 2' and copy 'argc + 1' elements to keep the array NULL \nterminated as it was prior to 2e875b6cb4 (builtin/stash: fix various \ntrivial memory leaks, 2024-08-01).\n\nThanks\n\nPhillip\n\n\n> +\tret = !!push_stash(argc, args_copy, prefix, git_stash_usage);\n>   \tfree(args_copy);\n>   \treturn ret;\n>   }\n> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n> index 70879941c2..836cc29a6b 100755\n> --- a/t/t3903-stash.sh\n> +++ b/t/t3903-stash.sh\n> @@ -410,8 +410,34 @@ test_expect_success 'stash --staged with binary file' '\n>   '\n>   \n>   test_expect_success 'dont assume push with non-option args' '\n> -\ttest_must_fail git stash -q drop 2>err &&\n> -\ttest_grep -e \"subcommand wasn'\\''t specified; '\\''push'\\'' can'\\''t be assumed due to unexpected token '\\''drop'\\''\" err\n> +\ttest_must_fail git stash someunknown 2>err &&\n> +\ttest_grep \"subcommand wasn${SQ}t specified; ${SQ}push${SQ} can${SQ}t be assumed due to unexpected token ${SQ}someunknown${SQ}\" err\n> +'\n> +\n> +test_expect_success 'assume push when command line starts with option' '\n> +\ttest_when_finished \"git reset --hard\" &&\n> +\ttest_when_finished \"rm -f untracked-file\" &&\n> +\techo changed >file &&\n> +\tgit add file &&\n> +\tgit stash -m \"implied push\" file &&\n> +\tgit stash pop &&\n> +\n> +\tgit add file &&\n> +\tgit stash --staged file &&\n> +\tgit stash pop &&\n> +\n> +\tgit add file &&\n> +\tgit stash --keep-index file &&\n> +\tgit stash pop &&\n> +\n> +\tgit add file &&\n> +\tgit stash --no-keep-index file &&\n> +\tgit stash pop &&\n> +\n> +\techo untracked >untracked-file &&\n> +\tgit stash --include-untracked untracked-file &&\n> +\ttest_path_is_missing untracked-file &&\n> +\tgit stash pop\n>   '\n>   \n>   test_expect_success 'stash --invalid-option' '\n> \n> base-commit: 2855562ca6a9c6b0e7bc780b050c1e83c9fcfbd0\n\n"}]}