{"thread":{"id":"52440","subject":"[PATCH 00/16] Extend --pathspec-from-file to git add, checkout","startedAt":"2019-12-12T14:36:30Z","lastAt":"2019-12-19T18:03:52Z","messageCount":67,"participants":["Alexandr Miloslavskiy via GitGitGadget","Phillip Wood","Alexandr Miloslavskiy","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":16},"messages":[{"id":"388015","messageId":"pull.490.git.1576161385.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":null,"subject":"[PATCH 00/16] Extend --pathspec-from-file to git add, checkout","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-12T14:36:09Z","receivedAt":"2019-12-12T14:36:30Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"This topic continues the effort to support `--pathspec-from-file` in various\ncommands [1][2]. It also includes some refactorings that I developed while\nworking on it - previously submitted separately [3][4] which was probably a\nmistake.\n\nAnatomy of the branch:\n  checkout, restore: support the --pathspec-from-file option\n    Extends `--pathspec-from-file` to `git checkout/restore`.\n\n  t2024: cover more cases\n    Some new tests for cases that I deemed worthy while working\n    on `parse_branchname_arg()` refactoring.\n\n  parse_branchname_arg(): refactor the decision making\n  parse_branchname_arg(): update code comments\n  parse_branchname_arg(): introduce expect_commit_only\n  parse_branchname_arg(): easier to understand variables\n    These patches prepare for `|| opts->pathspec_from_file` addition in\n    `git checkout/restore` patch. Without this refactoring, I found it\n    pretty hard to modify the old code.\n\n  checkout: die() on ambiguous tracking branches\n  parse_branchname_arg(): extract part as new function\n    Initially I was trying to remove some inconsistency standing in the\n    way of `git checkout/restore` patch. Later I figured that this change\n    is worthy on its own: it prevents some pretty surprising behavior of\n    git.\n\n  doc: restore: synchronize <pathspec> description\n  doc: checkout: synchronize <pathspec> description\n  doc: checkout: fix broken text reference\n  doc: checkout: remove duplicate synopsis\n    Some polishing of docs in preparation for `git checkout/restore` patch.\n\n  add: support the --pathspec-from-file option\n  cmd_add: prepare for next patch\n    Extends `--pathspec-from-file` to `git add`.\n\n  commit: forbid --pathspec-from-file --all\n  t7107, t7526: directly test parse_pathspec_file()\n    Some polishing of merged topic [1].\n\nCC'ing people who shown interest in any of the previous topics, thanks for\nyour reviews!\n\n[1] https://public-inbox.org/git/pull.445.git.1572895605.gitgitgadget@gmail.com/\n[2] https://public-inbox.org/git/20191204203911.237056-1-emilyshaffer@google.com/\n[3] https://public-inbox.org/git/pull.477.git.1574848137.gitgitgadget@gmail.com/\n[4] https://public-inbox.org/git/pull.479.git.1574969538.gitgitgadget@gmail.com/\n\nAlexandr Miloslavskiy (16):\n  t7107, t7526: directly test parse_pathspec_file()\n  commit: forbid --pathspec-from-file --all\n  cmd_add: prepare for next patch\n  add: support the --pathspec-from-file option\n  doc: checkout: remove duplicate synopsis\n  doc: checkout: fix broken text reference\n  doc: checkout: synchronize <pathspec> description\n  doc: restore: synchronize <pathspec> description\n  parse_branchname_arg(): extract part as new function\n  checkout: die() on ambiguous tracking branches\n  parse_branchname_arg(): easier to understand variables\n  parse_branchname_arg(): introduce expect_commit_only\n  parse_branchname_arg(): update code comments\n  parse_branchname_arg(): refactor the decision making\n  t2024: cover more cases\n  checkout, restore: support the --pathspec-from-file option\n\n Documentation/git-add.txt           |  16 +-\n Documentation/git-checkout.txt      |  50 +++--\n Documentation/git-restore.txt       |  26 ++-\n Makefile                            |   1 +\n builtin/add.c                       |  60 ++++--\n builtin/checkout.c                  | 279 ++++++++++++++--------------\n builtin/commit.c                    |   3 +\n t/helper/test-parse-pathspec-file.c |  34 ++++\n t/helper/test-tool.c                |   1 +\n t/helper/test-tool.h                |   1 +\n t/t0067-parse_pathspec_file.sh      |  89 +++++++++\n t/t2024-checkout-dwim.sh            |  55 +++++-\n t/t2026-checkout-pathspec-file.sh   |  61 ++++++\n t/t2072-restore-pathspec-file.sh    | 139 ++++++++++++++\n t/t3704-add-pathspec-file.sh        |  49 +++++\n t/t7107-reset-pathspec-file.sh      | 105 +----------\n t/t7526-commit-pathspec-file.sh     |  80 +-------\n t/t9902-completion.sh               |   2 +\n 18 files changed, 694 insertions(+), 357 deletions(-)\n create mode 100644 t/helper/test-parse-pathspec-file.c\n create mode 100755 t/t0067-parse_pathspec_file.sh\n create mode 100755 t/t2026-checkout-pathspec-file.sh\n create mode 100755 t/t2072-restore-pathspec-file.sh\n create mode 100755 t/t3704-add-pathspec-file.sh\n\n\nbase-commit: ad05a3d8e5a6a06443836b5e40434262d992889a\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-490%2FSyntevoAlex%2F%230207_pathspec_from_file_2-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-490/SyntevoAlex/#0207_pathspec_from_file_2-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/490\n-- \ngitgitgadget\n"},{"id":"388016","messageId":"c0980519ed319295c273d358b0547340cc511a6b.1576161385.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.git.1576161385.gitgitgadget@gmail.com","subject":"[PATCH 02/16] commit: forbid --pathspec-from-file --all","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-12T14:36:11Z","receivedAt":"2019-12-12T14:36:31Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nI forgot this in my previous patch `--pathspec-from-file` for\n`git commit` [1]. When both `--pathspec-from-file` and `--all` were\nspecified, `--all` took precedence and `--pathspec-from-file` was\nignored. Before `--pathspec-from-file` was implemented, this case was\nprevented by this check in `parse_and_validate_options()` :\n\n    die(_(\"paths '%s ...' with -a does not make sense\"), argv[0]);\n\nIt is unfortunate that these two cases are disconnected. This came as\nresult of how the code was laid out before my patches, where `pathspec`\nis parsed outside of `parse_and_validate_options()`. This branch is\nalready full of refactoring patches and I did not dare to go for another\none.\n\nFix by mirroring `die()` for `--pathspec-from-file` as well.\n\n[1] Commit e440fc58 (\"commit: support the --pathspec-from-file option\" 2019-11-19)\n\nCo-authored-by: Phillip Wood <phillip.wood123@gmail.com>\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/commit.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 2db2ad0de4..893a9f29b2 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -347,6 +347,9 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix\n \t\tif (interactive)\n \t\t\tdie(_(\"--pathspec-from-file is incompatible with --interactive/--patch\"));\n \n+\t\tif (all)\n+\t\t\tdie(_(\"--pathspec-from-file with -a does not make sense\"));\n+\n \t\tif (pathspec.nr)\n \t\t\tdie(_(\"--pathspec-from-file is incompatible with pathspec arguments\"));\n \n-- \ngitgitgadget\n\n"},{"id":"388017","messageId":"bfdd7a4b5678a7d88c048364db5a5654b1852ca5.1576161385.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.git.1576161385.gitgitgadget@gmail.com","subject":"[PATCH 01/16] t7107, t7526: directly test parse_pathspec_file()","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-12T14:36:10Z","receivedAt":"2019-12-12T14:36:33Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nIn my previous patches, `parse_pathspec_file()` was tested indirectly by\ninvoking `git reset` and `git commit` with properly crafted inputs. This\nhas some disadvantages:\n1) A number of tests are copy&pasted for every command where\n   `--pathspec-from-file` is supported. With just two commands, it\n   wasn't too bad, but I'm going to extend support to many more\n   commands, which would make a handful of low-value tests.\n2) Tests are located in suboptimal test packages\n3) Tests are indirect\n\nFix this by testing `parse_pathspec_file()` directly via a new test\nhelper.\n\nWhile working on it, I also noticed that quotes testing via\n`\"\\\"file\\\\101.t\\\"\"` was somewhat incorrect: I escaped `\\` one time while\nI had to escape it two times! Tests still worked due to `\"` which\nprevented pathspec from matching files.\n\nFix this by properly escaping one more time.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Makefile                            |   1 +\n t/helper/test-parse-pathspec-file.c |  34 +++++++++\n t/helper/test-tool.c                |   1 +\n t/helper/test-tool.h                |   1 +\n t/t0067-parse_pathspec_file.sh      |  89 +++++++++++++++++++++++\n t/t7107-reset-pathspec-file.sh      | 105 ++--------------------------\n t/t7526-commit-pathspec-file.sh     |  80 +--------------------\n 7 files changed, 134 insertions(+), 177 deletions(-)\n create mode 100644 t/helper/test-parse-pathspec-file.c\n create mode 100755 t/t0067-parse_pathspec_file.sh\n\ndiff --git a/Makefile b/Makefile\nindex b7d7374dac..fd7bcaac9c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -721,6 +721,7 @@ TEST_BUILTINS_OBJS += test-mktemp.o\n TEST_BUILTINS_OBJS += test-oidmap.o\n TEST_BUILTINS_OBJS += test-online-cpus.o\n TEST_BUILTINS_OBJS += test-parse-options.o\n+TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\ndiff --git a/t/helper/test-parse-pathspec-file.c b/t/helper/test-parse-pathspec-file.c\nnew file mode 100644\nindex 0000000000..e7f525feb9\n--- /dev/null\n+++ b/t/helper/test-parse-pathspec-file.c\n@@ -0,0 +1,34 @@\n+#include \"test-tool.h\"\n+#include \"parse-options.h\"\n+#include \"pathspec.h\"\n+#include \"gettext.h\"\n+\n+int cmd__parse_pathspec_file(int argc, const char **argv)\n+{\n+\tstruct pathspec pathspec;\n+\tconst char *pathspec_from_file = 0;\n+\tint pathspec_file_nul = 0, i;\n+\n+\tstatic const char *const usage[] = {\n+\t\t\"test-tool parse-pathspec-file --pathspec-from-file [--pathspec-file-nul]\",\n+\t\tNULL\n+\t};\n+\n+\tstruct option options[] = {\n+\t\tOPT_PATHSPEC_FROM_FILE(&pathspec_from_file),\n+\t\tOPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),\n+\t\tOPT_END()\n+\t};\n+\n+\tparse_options(argc, argv, 0, options, usage, 0);\n+\n+\tparse_pathspec_file(&pathspec, 0, 0, 0, pathspec_from_file,\n+\t\t\t    pathspec_file_nul);\n+\n+\tfor (i = 0; i < pathspec.nr; i++) {\n+\t\tprintf(\"%s\\n\", pathspec.items[i].original);\n+\t}\n+\n+\tclear_pathspec(&pathspec);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f20989d449..c9a232d238 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -39,6 +39,7 @@ static struct test_cmd cmds[] = {\n \t{ \"oidmap\", cmd__oidmap },\n \t{ \"online-cpus\", cmd__online_cpus },\n \t{ \"parse-options\", cmd__parse_options },\n+\t{ \"parse-pathspec-file\", cmd__parse_pathspec_file },\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 8ed2af71d1..c8549fd87f 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -29,6 +29,7 @@ int cmd__mktemp(int argc, const char **argv);\n int cmd__oidmap(int argc, const char **argv);\n int cmd__online_cpus(int argc, const char **argv);\n int cmd__parse_options(int argc, const char **argv);\n+int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\ndiff --git a/t/t0067-parse_pathspec_file.sh b/t/t0067-parse_pathspec_file.sh\nnew file mode 100755\nindex 0000000000..df7b319713\n--- /dev/null\n+++ b/t/t0067-parse_pathspec_file.sh\n@@ -0,0 +1,89 @@\n+#!/bin/sh\n+\n+test_description='Test parse_pathspec_file()'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'one item from stdin' '\n+\techo fileA.t | test-tool parse-pathspec-file --pathspec-from-file=- >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'one item from file' '\n+\techo fileA.t >list &&\n+\ttest-tool parse-pathspec-file --pathspec-from-file=list >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'NUL delimiters' '\n+\tprintf \"fileA.t\\0fileB.t\\0\" | test-tool parse-pathspec-file --pathspec-from-file=- --pathspec-file-nul >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tfileB.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'LF delimiters' '\n+\tprintf \"fileA.t\\nfileB.t\\n\" | test-tool parse-pathspec-file --pathspec-from-file=- >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tfileB.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'no trailing delimiter' '\n+\tprintf \"fileA.t\\nfileB.t\" | test-tool parse-pathspec-file --pathspec-from-file=- >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tfileB.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'CRLF delimiters' '\n+\tprintf \"fileA.t\\r\\nfileB.t\\r\\n\" | test-tool parse-pathspec-file --pathspec-from-file=- >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tfileB.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'quotes' '\n+\t# shell  takes \\\\\\\\101 and spits \\\\101\n+\t# printf takes   \\\\101 and spits  \\101\n+\t# git    takes    \\101 and spits     A\n+\tprintf \"\\\"file\\\\\\\\101.t\\\"\" | test-tool parse-pathspec-file --pathspec-from-file=- >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '--pathspec-file-nul takes quotes literally' '\n+\t# shell  takes \\\\\\\\101 and spits \\\\101\n+\t# printf takes   \\\\101 and spits  \\101\n+\tprintf \"\\\"file\\\\\\\\101.t\\\"\" | test-tool parse-pathspec-file --pathspec-from-file=- --pathspec-file-nul >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\t\"file\\101.t\"\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t7107-reset-pathspec-file.sh b/t/t7107-reset-pathspec-file.sh\nindex 6b1a731fff..90dba621d6 100755\n--- a/t/t7107-reset-pathspec-file.sh\n+++ b/t/t7107-reset-pathspec-file.sh\n@@ -25,7 +25,7 @@ verify_expect () {\n \ttest_cmp expect actual\n }\n \n-test_expect_success '--pathspec-from-file from stdin' '\n+test_expect_success 'simplest' '\n \trestore_checkpoint &&\n \n \tgit rm fileA.t &&\n@@ -37,97 +37,21 @@ test_expect_success '--pathspec-from-file from stdin' '\n \tverify_expect\n '\n \n-test_expect_success '--pathspec-from-file from file' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t &&\n-\techo fileA.t >list &&\n-\tgit reset --pathspec-from-file=list &&\n-\n-\tcat >expect <<-\\EOF &&\n-\t D fileA.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'NUL delimiters' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t fileB.t &&\n-\tprintf \"fileA.t\\0fileB.t\\0\" | git reset --pathspec-from-file=- --pathspec-file-nul &&\n-\n-\tcat >expect <<-\\EOF &&\n-\t D fileA.t\n-\t D fileB.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'LF delimiters' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t fileB.t &&\n-\tprintf \"fileA.t\\nfileB.t\\n\" | git reset --pathspec-from-file=- &&\n-\n-\tcat >expect <<-\\EOF &&\n-\t D fileA.t\n-\t D fileB.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'no trailing delimiter' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t fileB.t &&\n-\tprintf \"fileA.t\\nfileB.t\" | git reset --pathspec-from-file=- &&\n-\n-\tcat >expect <<-\\EOF &&\n-\t D fileA.t\n-\t D fileB.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'CRLF delimiters' '\n+test_expect_success 'only touches what was listed' '\n \trestore_checkpoint &&\n \n-\tgit rm fileA.t fileB.t &&\n-\tprintf \"fileA.t\\r\\nfileB.t\\r\\n\" | git reset --pathspec-from-file=- &&\n+\tgit rm fileA.t fileB.t fileC.t fileD.t &&\n+\tprintf \"fileB.t\\nfileC.t\\n\" | git reset --pathspec-from-file=- &&\n \n \tcat >expect <<-\\EOF &&\n-\t D fileA.t\n+\tD  fileA.t\n \t D fileB.t\n+\t D fileC.t\n+\tD  fileD.t\n \tEOF\n \tverify_expect\n '\n \n-test_expect_success 'quotes' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t &&\n-\tprintf \"\\\"file\\\\101.t\\\"\" | git reset --pathspec-from-file=- &&\n-\n-\tcat >expect <<-\\EOF &&\n-\t D fileA.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'quotes not compatible with --pathspec-file-nul' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t &&\n-\tprintf \"\\\"file\\\\101.t\\\"\" >list &&\n-\t# Note: \"git reset\" has not yet learned to fail on wrong pathspecs\n-\tgit reset --pathspec-from-file=list --pathspec-file-nul &&\n-\n-\tcat >expect <<-\\EOF &&\n-\t D fileA.t\n-\tEOF\n-\ttest_must_fail verify_expect\n-'\n-\n test_expect_success '--pathspec-from-file is not compatible with --soft or --hard' '\n \trestore_checkpoint &&\n \n@@ -137,19 +61,4 @@ test_expect_success '--pathspec-from-file is not compatible with --soft or --har\n \ttest_must_fail git reset --hard --pathspec-from-file=list\n '\n \n-test_expect_success 'only touches what was listed' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t fileB.t fileC.t fileD.t &&\n-\tprintf \"fileB.t\\nfileC.t\\n\" | git reset --pathspec-from-file=- &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tD  fileA.t\n-\t D fileB.t\n-\t D fileC.t\n-\tD  fileD.t\n-\tEOF\n-\tverify_expect\n-'\n-\n test_done\ndiff --git a/t/t7526-commit-pathspec-file.sh b/t/t7526-commit-pathspec-file.sh\nindex a06b683534..d8fa362ace 100755\n--- a/t/t7526-commit-pathspec-file.sh\n+++ b/t/t7526-commit-pathspec-file.sh\n@@ -26,7 +26,7 @@ verify_expect () {\n \ttest_cmp expect actual\n }\n \n-test_expect_success '--pathspec-from-file from stdin' '\n+test_expect_success 'simplest' '\n \trestore_checkpoint &&\n \n \techo fileA.t | git commit --pathspec-from-file=- -m \"Commit\" &&\n@@ -37,84 +37,6 @@ test_expect_success '--pathspec-from-file from stdin' '\n \tverify_expect\n '\n \n-test_expect_success '--pathspec-from-file from file' '\n-\trestore_checkpoint &&\n-\n-\techo fileA.t >list &&\n-\tgit commit --pathspec-from-file=list -m \"Commit\" &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tA\tfileA.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'NUL delimiters' '\n-\trestore_checkpoint &&\n-\n-\tprintf \"fileA.t\\0fileB.t\\0\" | git commit --pathspec-from-file=- --pathspec-file-nul -m \"Commit\" &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tA\tfileA.t\n-\tA\tfileB.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'LF delimiters' '\n-\trestore_checkpoint &&\n-\n-\tprintf \"fileA.t\\nfileB.t\\n\" | git commit --pathspec-from-file=- -m \"Commit\" &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tA\tfileA.t\n-\tA\tfileB.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'no trailing delimiter' '\n-\trestore_checkpoint &&\n-\n-\tprintf \"fileA.t\\nfileB.t\" | git commit --pathspec-from-file=- -m \"Commit\" &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tA\tfileA.t\n-\tA\tfileB.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'CRLF delimiters' '\n-\trestore_checkpoint &&\n-\n-\tprintf \"fileA.t\\r\\nfileB.t\\r\\n\" | git commit --pathspec-from-file=- -m \"Commit\" &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tA\tfileA.t\n-\tA\tfileB.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'quotes' '\n-\trestore_checkpoint &&\n-\n-\tprintf \"\\\"file\\\\101.t\\\"\" | git commit --pathspec-from-file=- -m \"Commit\" &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tA\tfileA.t\n-\tEOF\n-\tverify_expect expect\n-'\n-\n-test_expect_success 'quotes not compatible with --pathspec-file-nul' '\n-\trestore_checkpoint &&\n-\n-\tprintf \"\\\"file\\\\101.t\\\"\" >list &&\n-\ttest_must_fail git commit --pathspec-from-file=list --pathspec-file-nul -m \"Commit\"\n-'\n-\n test_expect_success 'only touches what was listed' '\n \trestore_checkpoint &&\n \n-- \ngitgitgadget\n\n"},{"id":"388018","messageId":"2d87b6433b8965b833e300b36110b17d936403d1.1576161385.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.git.1576161385.gitgitgadget@gmail.com","subject":"[PATCH 06/16] doc: checkout: fix broken text reference","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-12T14:36:15Z","receivedAt":"2019-12-12T14:36:35Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-checkout.txt | 10 ++++------\n 1 file changed, 4 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 2011fdbb1d..d47046e050 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -95,12 +95,10 @@ using `--ours` or `--theirs`.  With `-m`, changes made to the working tree\n file can be discarded to re-create the original conflicted merge result.\n \n 'git checkout' (-p|--patch) [<tree-ish>] [--] [<pathspec>...]::\n-\tThis is similar to the \"check out paths to the working tree\n-\tfrom either the index or from a tree-ish\" mode described\n-\tabove, but lets you use the interactive interface to show\n-\tthe \"diff\" output and choose which hunks to use in the\n-\tresult.  See below for the description of `--patch` option.\n-\n+\tThis is similar to the previous mode, but lets you use the\n+\tinteractive interface to show the \"diff\" output and choose which\n+\thunks to use in the result.  See below for the description of\n+\t`--patch` option.\n \n OPTIONS\n -------\n-- \ngitgitgadget\n\n"},{"id":"388019","messageId":"369096494223222ef96e38ca27f0b82c6eb59e92.1576161385.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.git.1576161385.gitgitgadget@gmail.com","subject":"[PATCH 03/16] cmd_add: prepare for next patch","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-12T14:36:12Z","receivedAt":"2019-12-12T14:36:36Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nSome code blocks were moved down to be able to test for `pathspec.nr`\nin the next patch. Blocks are moved as is without any changes. This\nis done as separate patch to reduce the amount of diffs in next patch.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/add.c | 34 +++++++++++++++++-----------------\n 1 file changed, 17 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/add.c b/builtin/add.c\nindex d4686d5218..3d1791dd82 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -430,10 +430,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \tif (addremove && take_worktree_changes)\n \t\tdie(_(\"-A and -u are mutually incompatible\"));\n \n-\tif (!take_worktree_changes && addremove_explicit < 0 && argc)\n-\t\t/* Turn \"git add pathspec...\" to \"git add -A pathspec...\" */\n-\t\taddremove = 1;\n-\n \tif (!show_only && ignore_missing)\n \t\tdie(_(\"Option --ignore-missing can only be used together with --dry-run\"));\n \n@@ -446,19 +442,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \n \thold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);\n \n-\tflags = ((verbose ? ADD_CACHE_VERBOSE : 0) |\n-\t\t (show_only ? ADD_CACHE_PRETEND : 0) |\n-\t\t (intent_to_add ? ADD_CACHE_INTENT : 0) |\n-\t\t (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |\n-\t\t (!(addremove || take_worktree_changes)\n-\t\t  ? ADD_CACHE_IGNORE_REMOVAL : 0));\n-\n-\tif (require_pathspec && argc == 0) {\n-\t\tfprintf(stderr, _(\"Nothing specified, nothing added.\\n\"));\n-\t\tfprintf(stderr, _(\"Maybe you wanted to say 'git add .'?\\n\"));\n-\t\treturn 0;\n-\t}\n-\n \t/*\n \t * Check the \"pathspec '%s' did not match any files\" block\n \t * below before enabling new magic.\n@@ -468,6 +451,23 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \t\t       PATHSPEC_SYMLINK_LEADING_PATH,\n \t\t       prefix, argv);\n \n+\tif (require_pathspec && argc == 0) {\n+\t\tfprintf(stderr, _(\"Nothing specified, nothing added.\\n\"));\n+\t\tfprintf(stderr, _(\"Maybe you wanted to say 'git add .'?\\n\"));\n+\t\treturn 0;\n+\t}\n+\n+\tif (!take_worktree_changes && addremove_explicit < 0 && argc)\n+\t\t/* Turn \"git add pathspec...\" to \"git add -A pathspec...\" */\n+\t\taddremove = 1;\n+\n+\tflags = ((verbose ? ADD_CACHE_VERBOSE : 0) |\n+\t\t (show_only ? ADD_CACHE_PRETEND : 0) |\n+\t\t (intent_to_add ? ADD_CACHE_INTENT : 0) |\n+\t\t (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |\n+\t\t (!(addremove || take_worktree_changes)\n+\t\t  ? ADD_CACHE_IGNORE_REMOVAL : 0));\n+\n \tif (read_cache_preload(&pathspec) < 0)\n \t\tdie(_(\"index file corrupt\"));\n \n-- \ngitgitgadget\n\n"},{"id":"388020","messageId":"a0a884b384fd7ebdb824bef90acd6d6366be8cf5.1576161385.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.git.1576161385.gitgitgadget@gmail.com","subject":"[PATCH 11/16] parse_branchname_arg(): easier to understand variables","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-12T14:36:20Z","receivedAt":"2019-12-12T14:36:38Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\n`dash_dash_pos` was only calculated under `opts->accept_pathspec`. This\nis unexpected to readers and made it harder to reason about the code.\nFix this by restoring the expected meaning.\n\nSimplify the code by dropping `argcount` and useless `argc` / `argv`\nmanipulations.\n\nThis should not change behavior in any way.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c | 34 ++++++++++++++--------------------\n 1 file changed, 14 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b847695d2b..f35fe2cc26 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1152,7 +1152,6 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t\t\tstruct object_id *rev)\n {\n \tconst char **new_branch = &opts->new_branch;\n-\tint argcount = 0;\n \tconst char *arg;\n \tint dash_dash_pos;\n \tint has_dash_dash = 0;\n@@ -1211,17 +1210,21 @@ static int parse_branchname_arg(int argc, const char **argv,\n \targ = argv[0];\n \tdash_dash_pos = -1;\n \tfor (i = 0; i < argc; i++) {\n-\t\tif (opts->accept_pathspec && !strcmp(argv[i], \"--\")) {\n+\t\tif (!strcmp(argv[i], \"--\")) {\n \t\t\tdash_dash_pos = i;\n \t\t\tbreak;\n \t\t}\n \t}\n-\tif (dash_dash_pos == 0)\n-\t\treturn 1; /* case (2) */\n-\telse if (dash_dash_pos == 1)\n-\t\thas_dash_dash = 1; /* case (3) or (1) */\n-\telse if (dash_dash_pos >= 2)\n-\t\tdie(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n+\n+\tif (opts->accept_pathspec) {\n+\t    if (dash_dash_pos == 0)\n+\t\t    return 1; /* case (2) */\n+\t    else if (dash_dash_pos == 1)\n+\t\t    has_dash_dash = 1; /* case (3) or (1) */\n+\t    else if (dash_dash_pos >= 2)\n+\t\t    die(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n+\t}\n+\n \topts->count_checkout_paths = !opts->quiet && !has_dash_dash;\n \n \tif (!strcmp(arg, \"-\"))\n@@ -1268,15 +1271,10 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\tif (!recover_with_dwim) {\n \t\t\tif (has_dash_dash)\n \t\t\t\tdie(_(\"invalid reference: %s\"), arg);\n-\t\t\treturn argcount;\n+\t\t\treturn 0;\n \t\t}\n \t}\n \n-\t/* we can't end up being in (2) anymore, eat the argument */\n-\targcount++;\n-\targv++;\n-\targc--;\n-\n \tsetup_new_branch_info_and_source_tree(new_branch_info, opts, rev, arg);\n \n \tif (!opts->source_tree)                   /* case (1): want a tree */\n@@ -1289,15 +1287,11 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t * even if there happen to be a file called 'branch';\n \t\t * it would be extremely annoying.\n \t\t */\n-\t\tif (argc)\n+\t\tif (argc > 1)\n \t\t\tverify_non_filename(opts->prefix, arg);\n-\t} else if (opts->accept_pathspec) {\n-\t\targcount++;\n-\t\targv++;\n-\t\targc--;\n \t}\n \n-\treturn argcount;\n+\treturn (dash_dash_pos == 1) ? 2 : 1;\n }\n \n static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n-- \ngitgitgadget\n\n"},{"id":"388021","messageId":"d2f277bb58c6462f1c7f856b5ef2b3ac9894b830.1576161385.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.git.1576161385.gitgitgadget@gmail.com","subject":"[PATCH 08/16] doc: restore: synchronize <pathspec> description","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-12T14:36:17Z","receivedAt":"2019-12-12T14:36:39Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\n`git add` shows an example of good writing, follow it.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-restore.txt | 12 ++++++++++--\n 1 file changed, 10 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt\nindex 1ab2e40ea9..d7bf016bba 100644\n--- a/Documentation/git-restore.txt\n+++ b/Documentation/git-restore.txt\n@@ -8,8 +8,8 @@ git-restore - Restore working tree files\n SYNOPSIS\n --------\n [verse]\n-'git restore' [<options>] [--source=<tree>] [--staged] [--worktree] <pathspec>...\n-'git restore' (-p|--patch) [<options>] [--source=<tree>] [--staged] [--worktree] [<pathspec>...]\n+'git restore' [<options>] [--source=<tree>] [--staged] [--worktree] [--] <pathspec>...\n+'git restore' (-p|--patch) [<options>] [--source=<tree>] [--staged] [--worktree] [--] [<pathspec>...]\n \n DESCRIPTION\n -----------\n@@ -113,6 +113,14 @@ in linkgit:git-checkout[1] for details.\n \tappear in the `--source` tree are removed, to make them match\n \t`<tree>` exactly. The default is no-overlay mode.\n \n+\\--::\n+\tDo not interpret any more arguments as options.\n+\n+<pathspec>...::\n+\tLimits the paths affected by the operation.\n++\n+For more details, see the 'pathspec' entry in linkgit:gitglossary[7].\n+\n EXAMPLES\n --------\n \n-- \ngitgitgadget\n\n"},{"id":"388022","messageId":"271e8ed3ab1a67dbd190ebeb6fe8c675b79b47e3.1576161385.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.git.1576161385.gitgitgadget@gmail.com","subject":"[PATCH 10/16] checkout: die() on ambiguous tracking branches","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-12T14:36:19Z","receivedAt":"2019-12-12T14:36:41Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nBefore this patch, when there were multiple DWIM candidates for remote\nbranch, git decided to try the argument as pathspec instead. I believe\nthat such behavior is a surprise: adding another remote suddenly causes\ngit to discard file contents, because it was unsure which branch to\npick. There was an incomplete attempt to prevent that in [3].\n\nI understand that this was never intended:\n\n  [1] introduces the unexpected behavior. Before, there was fallback\n  from not-a-ref to pathspec. This is reasonable DWIM. After, there is\n  another fallback from ambiguous-remote to pathspec. I understand that\n  it was kind of copy&paste oversight.\n\n  [2] noticed the unexpected behavior but chose to semi-document it\n  instead of forbidding, because the goal of the patch series was\n  focused on something else.\n\n  [3] adds `die()` when there is ambiguity between branch and file. The\n  case of multiple tracking branches is seemingly overlooked.\n\nChange to complain about ambiguity instead of doing unexpected things.\n\n[1] Commit 70c9ac2f (\"DWIM \"git checkout frotz\" to \"git checkout -b frotz origin/frotz\"\" 2009-10-18)\n    https://public-inbox.org/git/7vaazpxha4.fsf_-_@alter.siamese.dyndns.org/\n[2] Commit ad8d5104 (\"checkout: add advice for ambiguous \"checkout <branch>\"\", 2018-06-05)\n    https://public-inbox.org/git/20180502105452.17583-1-avarab@gmail.com/\n[3] Commit be4908f1 (\"checkout: disambiguate dwim tracking branches and local files\", 2018-11-13)\n    https://public-inbox.org/git/20181110120707.25846-1-pclouds@gmail.com/\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c       | 56 ++++++++++++++++++----------------------\n t/t2024-checkout-dwim.sh | 28 ++++++++++++++++++--\n 2 files changed, 51 insertions(+), 33 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex e1b9df1543..b847695d2b 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1115,10 +1115,10 @@ static void setup_new_branch_info_and_source_tree(\n \n static const char *parse_remote_branch(const char *arg,\n \t\t\t\t       struct object_id *rev,\n-\t\t\t\t       int could_be_checkout_paths,\n-\t\t\t\t       int *dwim_remotes_matched)\n+\t\t\t\t       int could_be_checkout_paths)\n {\n-\tconst char *remote = unique_tracking_name(arg, rev, dwim_remotes_matched);\n+\tint num_matches = 0;\n+\tconst char *remote = unique_tracking_name(arg, rev, &num_matches);\n \t\n \tif (remote && could_be_checkout_paths) {\n \t\tdie(_(\"'%s' could be both a local file and a tracking branch.\\n\"\n@@ -1126,6 +1126,22 @@ static const char *parse_remote_branch(const char *arg,\n \t\t    arg);\n \t}\n \n+\tif (!remote && num_matches > 1) {\n+\t    if (advice_checkout_ambiguous_remote_branch_name) {\n+\t\t    advise(_(\"If you meant to check out a remote tracking branch on, e.g. 'origin',\\n\"\n+\t\t\t     \"you can do so by fully qualifying the name with the --track option:\\n\"\n+\t\t\t     \"\\n\"\n+\t\t\t     \"    git checkout --track origin/<name>\\n\"\n+\t\t\t     \"\\n\"\n+\t\t\t     \"If you'd like to always have checkouts of an ambiguous <name> prefer\\n\"\n+\t\t\t     \"one remote, e.g. the 'origin' remote, consider setting\\n\"\n+\t\t\t     \"checkout.defaultRemote=origin in your config.\"));\n+\t    }\n+\n+\t    die(_(\"'%s' matched multiple (%d) remote tracking branches\"),\n+\t\targ, num_matches);\n+\t}\n+\n \treturn remote;\n }\n \n@@ -1133,8 +1149,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t\t\tint dwim_new_local_branch_ok,\n \t\t\t\tstruct branch_info *new_branch_info,\n \t\t\t\tstruct checkout_opts *opts,\n-\t\t\t\tstruct object_id *rev,\n-\t\t\t\tint *dwim_remotes_matched)\n+\t\t\t\tstruct object_id *rev)\n {\n \tconst char **new_branch = &opts->new_branch;\n \tint argcount = 0;\n@@ -1240,8 +1255,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \n \t\tif (recover_with_dwim) {\n \t\t\tconst char *remote = parse_remote_branch(arg, rev,\n-\t\t\t\t\t\t\t\t could_be_checkout_paths,\n-\t\t\t\t\t\t\t\t dwim_remotes_matched);\n+\t\t\t\t\t\t\t\t could_be_checkout_paths);\n \t\t\tif (remote) {\n \t\t\t\t*new_branch = arg;\n \t\t\t\targ = remote;\n@@ -1505,7 +1519,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t\t\t const char * const usagestr[])\n {\n \tstruct branch_info new_branch_info;\n-\tint dwim_remotes_matched = 0;\n \tint parseopt_flags = 0;\n \n \tmemset(&new_branch_info, 0, sizeof(new_branch_info));\n@@ -1613,8 +1626,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t\t\topts->track == BRANCH_TRACK_UNSPECIFIED &&\n \t\t\t!opts->new_branch;\n \t\tint n = parse_branchname_arg(argc, argv, dwim_ok,\n-\t\t\t\t\t     &new_branch_info, opts, &rev,\n-\t\t\t\t\t     &dwim_remotes_matched);\n+\t\t\t\t\t     &new_branch_info, opts, &rev);\n \t\targv += n;\n \t\targc -= n;\n \t} else if (!opts->accept_ref && opts->from_treeish) {\n@@ -1672,28 +1684,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t}\n \n \tUNLEAK(opts);\n-\tif (opts->patch_mode || opts->pathspec.nr) {\n-\t\tint ret = checkout_paths(opts, new_branch_info.name);\n-\t\tif (ret && dwim_remotes_matched > 1 &&\n-\t\t    advice_checkout_ambiguous_remote_branch_name)\n-\t\t\tadvise(_(\"'%s' matched more than one remote tracking branch.\\n\"\n-\t\t\t\t \"We found %d remotes with a reference that matched. So we fell back\\n\"\n-\t\t\t\t \"on trying to resolve the argument as a path, but failed there too!\\n\"\n-\t\t\t\t \"\\n\"\n-\t\t\t\t \"If you meant to check out a remote tracking branch on, e.g. 'origin',\\n\"\n-\t\t\t\t \"you can do so by fully qualifying the name with the --track option:\\n\"\n-\t\t\t\t \"\\n\"\n-\t\t\t\t \"    git checkout --track origin/<name>\\n\"\n-\t\t\t\t \"\\n\"\n-\t\t\t\t \"If you'd like to always have checkouts of an ambiguous <name> prefer\\n\"\n-\t\t\t\t \"one remote, e.g. the 'origin' remote, consider setting\\n\"\n-\t\t\t\t \"checkout.defaultRemote=origin in your config.\"),\n-\t\t\t       argv[0],\n-\t\t\t       dwim_remotes_matched);\n-\t\treturn ret;\n-\t} else {\n+\tif (opts->patch_mode || opts->pathspec.nr)\n+\t\treturn checkout_paths(opts, new_branch_info.name);\n+\telse\n \t\treturn checkout_branch(opts, &new_branch_info);\n-\t}\n }\n \n int cmd_checkout(int argc, const char **argv, const char *prefix)\ndiff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh\nindex fa0718c730..c35d67b697 100755\n--- a/t/t2024-checkout-dwim.sh\n+++ b/t/t2024-checkout-dwim.sh\n@@ -37,7 +37,9 @@ test_expect_success 'setup' '\n \t\tgit checkout -b foo &&\n \t\ttest_commit a_foo &&\n \t\tgit checkout -b bar &&\n-\t\ttest_commit a_bar\n+\t\ttest_commit a_bar &&\n+\t\tgit checkout -b ambiguous_branch_and_file &&\n+\t\ttest_commit a_ambiguous_branch_and_file\n \t) &&\n \tgit init repo_b &&\n \t(\n@@ -46,7 +48,9 @@ test_expect_success 'setup' '\n \t\tgit checkout -b foo &&\n \t\ttest_commit b_foo &&\n \t\tgit checkout -b baz &&\n-\t\ttest_commit b_baz\n+\t\ttest_commit b_baz &&\n+\t\tgit checkout -b ambiguous_branch_and_file &&\n+\t\ttest_commit b_ambiguous_branch_and_file\n \t) &&\n \tgit remote add repo_a repo_a &&\n \tgit remote add repo_b repo_b &&\n@@ -75,6 +79,26 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' '\n \ttest_branch master\n '\n \n+test_expect_success 'when arg matches multiple remotes, do not fallback to interpreting as pathspec' '\n+\t# create a file with name matching remote branch name\n+\tgit checkout -b t_ambiguous_branch_and_file &&\n+\t>ambiguous_branch_and_file &&\n+\tgit add ambiguous_branch_and_file &&\n+\tgit commit -m \"ambiguous_branch_and_file\" &&\n+\n+\t# modify file to verify that it will not be touched by checkout\n+\ttest_when_finished \"git checkout -- ambiguous_branch_and_file\" &&\n+\techo \"file contents\" >ambiguous_branch_and_file &&\n+\tcp ambiguous_branch_and_file expect &&\n+\n+\ttest_must_fail git checkout ambiguous_branch_and_file 2>err &&\n+\n+\ttest_i18ngrep \"matched multiple (2) remote tracking branches\" err &&\n+\t\n+\t# file must not be altered\n+\ttest_cmp expect ambiguous_branch_and_file\n+'\n+\n test_expect_success 'checkout of branch from multiple remotes fails with advice' '\n \tgit checkout -B master &&\n \ttest_might_fail git branch -D foo &&\n-- \ngitgitgadget\n\n"},{"id":"388023","messageId":"010fd76331f9203c22353cebcfefc63c8d97e4f2.1576161385.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.git.1576161385.gitgitgadget@gmail.com","subject":"[PATCH 14/16] parse_branchname_arg(): refactor the decision making","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-12T14:36:23Z","receivedAt":"2019-12-12T14:36:42Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nMake it easier to understand which branches handle which cases.\n\nDrop obfuscated variable `has_dash_dash` which also took\n`opts->accept_pathspec` into account, making it pretty hard to reason\nabout code, especially when used together with `argc` and\n`opts->accept_pathspec` here:\n\n\tif (!(argc == 1 && !has_dash_dash) &&\n\t\t!(argc == 2 && has_dash_dash) &&\n\t\topts->accept_pathspec)\n\t\trecover_with_dwim = 0;\n\nAvoid double-negation in the code mentioned above (\"it is not OK to\nproceed if it's not one of those cases\").\n\nAvoid hard-to-understand condition `opts->accept_pathspec` in the code\nmentioned above.\n\nThere are some minor die() message changes for:\n`git switch <commit> <unexpected>`\n`git switch <commit> -- <unexpected>`\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c | 74 ++++++++++++++++++++++++++++------------------\n 1 file changed, 45 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 6072f7cef7..aa4ff14ec2 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1153,9 +1153,8 @@ static int parse_branchname_arg(int argc, const char **argv,\n {\n \tconst char **new_branch = &opts->new_branch;\n \tconst char *arg;\n-\tint dash_dash_pos;\n-\tint has_dash_dash = 0, expect_commit_only = 0;\n-\tint i;\n+\tint dash_dash_pos, i;\n+\tint recover_with_dwim, expect_commit_only;\n \n \t/*\n \t * Resolve ambiguity where argv[0] may be <pathspec> or <commit>.\n@@ -1174,15 +1173,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t *    do that and proceed with (2)(3).\n \t * 5) Otherwise, let caller proceed with <pathspec> interpretation.\n \t */\n-\tif (!argc)\n-\t\treturn 0;\n-\n-\tif (!opts->accept_pathspec) {\n-\t\tif (argc > 1)\n-\t\t\tdie(_(\"only one reference expected\"));\n-\t\thas_dash_dash = 1; /* helps disambiguate */\n-\t}\n-\n+\t \n \targ = argv[0];\n \tdash_dash_pos = -1;\n \tfor (i = 0; i < argc; i++) {\n@@ -1192,17 +1183,49 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t}\n \t}\n \n-\tif (opts->accept_pathspec) {\n-\t    if (dash_dash_pos == 0)\n-\t\t    return 1;\n-\t    else if (dash_dash_pos == 1)\n-\t\t    has_dash_dash = 1;\n-\t    else if (dash_dash_pos >= 2)\n-\t\t    die(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n-\t}\n+\tif (dash_dash_pos == -1) {\n+\t\tif (argc == 0) {\n+\t\t\t/* 'git checkout/switch/restore' */\n+\t\t\treturn 0;\n+\t\t} else if (argc == 1) {\n+\t\t\t/* 'git checkout/switch/restore <something>' */\n+\t\t\trecover_with_dwim = dwim_new_local_branch_ok;\n+\t\t} else if (!opts->accept_pathspec) {\n+\t\t\t/* 'git switch <commit> <unexpected> [...]' */\n+\t\t\tdie(_(\"only one reference expected, %d given.\"), argc);\n+\t\t} else {\n+\t\t\t/* 'git checkout/restore <something> <pathspec> [...]' */\n+\t\t\trecover_with_dwim = 0;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Absence of '--' leaves <pathspec>/<commit> ambiguity.\n+\t\t * Try to resolve it with additional knowledge about pathspec args.\n+\t\t */\n+\t\texpect_commit_only = !opts->accept_pathspec;\n+\t} else if (dash_dash_pos == 0) {\n+\t\t/* 'git checkout/switch/restore -- [...]' */\n+\t\treturn 1;  /* Eat '--' */\n+\t} else if (dash_dash_pos == 1) {\n+\t\tif (!opts->accept_pathspec) {\n+\t\t\t/* 'git switch <commit> -- [...]' */\n+\t\t\tdie(_(\"incompatible with pathspec arguments\"));\n+\t\t}\n+\n+\t\tif (argc == 2) {\n+\t\t\t/* 'git checkout/restore <commit> --' */\n+\t\t\trecover_with_dwim = dwim_new_local_branch_ok;\n+\t\t} else {\n+\t\t\t/* 'git checkout/restore <commit> -- <pathspec> [...]' */\n+\t\t\trecover_with_dwim = 0;\n+\t\t}\n \n-\tif (has_dash_dash)\n-\t    expect_commit_only = 1;\n+\t\t/* Presence of '--' makes it certain that arg is <commit> */\n+\t\texpect_commit_only = 1;\n+\t} else {\n+\t\t/* 'git checkout/switch/restore <commit> <unxpected> [...] -- [...]' */\n+\t\tdie(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n+\t}\n \n \topts->count_checkout_paths = !opts->quiet && !expect_commit_only;\n \n@@ -1210,19 +1233,12 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\targ = \"@{-1}\";\n \n \tif (get_oid_mb(arg, rev)) {\n-\t\tint recover_with_dwim = dwim_new_local_branch_ok;\n-\n \t\tint could_be_checkout_paths = !expect_commit_only &&\n \t\t\tcheck_filename(opts->prefix, arg);\n \n \t\tif (!expect_commit_only && !no_wildcard(arg))\n \t\t\trecover_with_dwim = 0;\n \n-\t\tif (!(argc == 1 && !has_dash_dash) &&\n-\t\t    !(argc == 2 && has_dash_dash) &&\n-\t\t    opts->accept_pathspec)\n-\t\t\trecover_with_dwim = 0;\n-\n \t\tif (recover_with_dwim) {\n \t\t\tconst char *remote = parse_remote_branch(arg, rev,\n \t\t\t\t\t\t\t\t could_be_checkout_paths);\n-- \ngitgitgadget\n\n"},{"id":"388024","messageId":"ffbc4059200bda33f8a5fece28b5baf93e465770.1576161385.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.git.1576161385.gitgitgadget@gmail.com","subject":"[PATCH 15/16] t2024: cover more cases","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-12T14:36:24Z","receivedAt":"2019-12-12T14:36:43Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nAfter working on `parse_branchname_arg()` I think that these cases are\nworth testing.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n t/t2024-checkout-dwim.sh | 27 +++++++++++++++++++++++++++\n 1 file changed, 27 insertions(+)\n\ndiff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh\nindex c35d67b697..fd993bf45d 100755\n--- a/t/t2024-checkout-dwim.sh\n+++ b/t/t2024-checkout-dwim.sh\n@@ -156,6 +156,33 @@ test_expect_success 'checkout of branch from a single remote succeeds #2' '\n \ttest_branch_upstream baz repo_b baz\n '\n \n+test_expect_success 'checkout of branch from a single remote succeeds with --' '\n+\tgit checkout -B master &&\n+\ttest_might_fail git branch -D baz &&\n+\n+\tgit checkout baz -- &&\n+\tstatus_uno_is_clean &&\n+\ttest_branch baz &&\n+\ttest_cmp_rev remotes/other_b/baz HEAD &&\n+\ttest_branch_upstream baz repo_b baz\n+'\n+\n+test_expect_success 'dont DWIM with pathspec #1' '\n+\tgit checkout -B master &&\n+\ttest_might_fail git branch -D baz &&\n+\n+\ttest_must_fail git checkout baz nonExistingFile 2>err &&\n+\ttest_i18ngrep \"did not match any file(s) known to git\" err\n+'\n+\n+test_expect_success 'dont DWIM with pathspec #2' '\n+\tgit checkout -B master &&\n+\ttest_might_fail git branch -D baz &&\n+\n+\ttest_must_fail git checkout baz -- nonExistingFile 2>err &&\n+\ttest_i18ngrep \"fatal: invalid reference: baz\" err\n+'\n+\n test_expect_success '--no-guess suppresses branch auto-vivification' '\n \tgit checkout -B master &&\n \tstatus_uno_is_clean &&\n-- \ngitgitgadget\n\n"},{"id":"388025","messageId":"4e2bb6663cdce7fe18e07a5a3713d9ee0c7a7c70.1576161385.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.git.1576161385.gitgitgadget@gmail.com","subject":"[PATCH 13/16] parse_branchname_arg(): update code comments","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-12T14:36:22Z","receivedAt":"2019-12-12T14:36:44Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nThese parts repeat git documentation:\n    ... if <something> is A...B <...>\n    ... remote named in checkout.defaultRemote ...\n\nSome parts repeat the code below. With next patch, code will be easier\nto understand, so this is no longer needed.\n\nThis is a separate patch to reduce the amount of diffs in next patch.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c | 86 +++++++++++-----------------------------------\n 1 file changed, 21 insertions(+), 65 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex bd0efa9140..6072f7cef7 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1158,45 +1158,21 @@ static int parse_branchname_arg(int argc, const char **argv,\n \tint i;\n \n \t/*\n-\t * case 1: git checkout <ref> -- [<paths>]\n-\t *\n-\t *   <ref> must be a valid tree, everything after the '--' must be\n-\t *   a path.\n-\t *\n-\t * case 2: git checkout -- [<paths>]\n-\t *\n-\t *   everything after the '--' must be paths.\n-\t *\n-\t * case 3: git checkout <something> [--]\n-\t *\n-\t *   (a) If <something> is a commit, that is to\n-\t *       switch to the branch or detach HEAD at it.  As a special case,\n-\t *       if <something> is A...B (missing A or B means HEAD but you can\n-\t *       omit at most one side), and if there is a unique merge base\n-\t *       between A and B, A...B names that merge base.\n-\t *\n-\t *   (b) If <something> is _not_ a commit, either \"--\" is present\n-\t *       or <something> is not a path, no -t or -b was given, and\n-\t *       and there is a tracking branch whose name is <something>\n-\t *       in one and only one remote (or if the branch exists on the\n-\t *       remote named in checkout.defaultRemote), then this is a\n-\t *       short-hand to fork local <something> from that\n-\t *       remote-tracking branch.\n-\t *\n-\t *   (c) Otherwise, if \"--\" is present, treat it like case (1).\n-\t *\n-\t *   (d) Otherwise :\n-\t *       - if it's a reference, treat it like case (1)\n-\t *       - else if it's a path, treat it like case (2)\n-\t *       - else: fail.\n-\t *\n-\t * case 4: git checkout <something> <paths>\n-\t *\n-\t *   The first argument must not be ambiguous.\n-\t *   - If it's *only* a reference, treat it like case (1).\n-\t *   - If it's only a path, treat it like case (2).\n-\t *   - else: fail.\n-\t *\n+\t * Resolve ambiguity where argv[0] may be <pathspec> or <commit>.\n+\t * High-level approach is:\n+\t * 1) Use various things to reduce ambiguity, examples:\n+\t *    * '--' is present\n+\t *    * command doesn't accept <pathspec>\n+\t *    * additional options like '-b' were given\n+\t * 2) If ambiguous and matches both existing <commit> and existing\n+\t *    file, complain. However, in 1-argument 'git checkout <arg>'\n+\t *    treat as <commit> to avoid annoying users.\n+\t * 3) Otherwise, if it matches some existing <commit>, treat as\n+\t *    <commit>.\n+\t * 4) Otherwise, if it matches a remote branch, and it's considered\n+\t *    reasonable to DWIM to create a local branch from remote branch,\n+\t *    do that and proceed with (2)(3).\n+\t * 5) Otherwise, let caller proceed with <pathspec> interpretation.\n \t */\n \tif (!argc)\n \t\treturn 0;\n@@ -1218,9 +1194,9 @@ static int parse_branchname_arg(int argc, const char **argv,\n \n \tif (opts->accept_pathspec) {\n \t    if (dash_dash_pos == 0)\n-\t\t    return 1; /* case (2) */\n+\t\t    return 1;\n \t    else if (dash_dash_pos == 1)\n-\t\t    has_dash_dash = 1; /* case (3) or (1) */\n+\t\t    has_dash_dash = 1;\n \t    else if (dash_dash_pos >= 2)\n \t\t    die(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n \t}\n@@ -1234,14 +1210,6 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\targ = \"@{-1}\";\n \n \tif (get_oid_mb(arg, rev)) {\n-\t\t/*\n-\t\t * Either case (3) or (4), with <something> not being\n-\t\t * a commit, or an attempt to use case (1) with an\n-\t\t * invalid ref.\n-\t\t *\n-\t\t * It's likely an error, but we need to find out if\n-\t\t * we should auto-create the branch, case (3).(b).\n-\t\t */\n \t\tint recover_with_dwim = dwim_new_local_branch_ok;\n \n \t\tint could_be_checkout_paths = !expect_commit_only &&\n@@ -1250,10 +1218,6 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\tif (!expect_commit_only && !no_wildcard(arg))\n \t\t\trecover_with_dwim = 0;\n \n-\t\t/*\n-\t\t * Accept \"git checkout foo\", \"git checkout foo --\"\n-\t\t * and \"git switch foo\" as candidates for dwim.\n-\t\t */\n \t\tif (!(argc == 1 && !has_dash_dash) &&\n \t\t    !(argc == 2 && has_dash_dash) &&\n \t\t    opts->accept_pathspec)\n@@ -1265,7 +1229,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t\tif (remote) {\n \t\t\t\t*new_branch = arg;\n \t\t\t\targ = remote;\n-\t\t\t\t/* DWIMmed to create local branch, case (3).(b) */\n+\t\t\t\t/* DWIMmed to create local branch */\n \t\t\t} else {\n \t\t\t\trecover_with_dwim = 0;\n \t\t\t}\n@@ -1280,19 +1244,11 @@ static int parse_branchname_arg(int argc, const char **argv,\n \n \tsetup_new_branch_info_and_source_tree(new_branch_info, opts, rev, arg);\n \n-\tif (!opts->source_tree)                   /* case (1): want a tree */\n+\tif (!opts->source_tree)\n \t\tdie(_(\"reference is not a tree: %s\"), arg);\n \n-\tif (!expect_commit_only) {\t/* case (3).(d) -> (1) */\n-\t\t/*\n-\t\t * Do not complain the most common case\n-\t\t *\tgit checkout branch\n-\t\t * even if there happen to be a file called 'branch';\n-\t\t * it would be extremely annoying.\n-\t\t */\n-\t\tif (argc > 1)\n-\t\t\tverify_non_filename(opts->prefix, arg);\n-\t}\n+\tif (!expect_commit_only && argc > 1)\n+\t\tverify_non_filename(opts->prefix, arg);\n \n \treturn (dash_dash_pos == 1) ? 2 : 1;\n }\n-- \ngitgitgadget\n\n"},{"id":"388026","messageId":"891d1b91cf8f2b5fc8af6f26061bb7967ee6f664.1576161385.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.git.1576161385.gitgitgadget@gmail.com","subject":"[PATCH 09/16] parse_branchname_arg(): extract part as new function","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-12T14:36:18Z","receivedAt":"2019-12-12T14:36:45Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nThis is done for the next commit to avoid crazy 7x tab code padding.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c | 25 +++++++++++++++++++------\n 1 file changed, 19 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 3634a3dac1..e1b9df1543 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1113,6 +1113,22 @@ static void setup_new_branch_info_and_source_tree(\n \t}\n }\n \n+static const char *parse_remote_branch(const char *arg,\n+\t\t\t\t       struct object_id *rev,\n+\t\t\t\t       int could_be_checkout_paths,\n+\t\t\t\t       int *dwim_remotes_matched)\n+{\n+\tconst char *remote = unique_tracking_name(arg, rev, dwim_remotes_matched);\n+\t\n+\tif (remote && could_be_checkout_paths) {\n+\t\tdie(_(\"'%s' could be both a local file and a tracking branch.\\n\"\n+\t\t\t\"Please use -- (and optionally --no-guess) to disambiguate\"),\n+\t\t    arg);\n+\t}\n+\n+\treturn remote;\n+}\n+\n static int parse_branchname_arg(int argc, const char **argv,\n \t\t\t\tint dwim_new_local_branch_ok,\n \t\t\t\tstruct branch_info *new_branch_info,\n@@ -1223,13 +1239,10 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t\trecover_with_dwim = 0;\n \n \t\tif (recover_with_dwim) {\n-\t\t\tconst char *remote = unique_tracking_name(arg, rev,\n-\t\t\t\t\t\t\t\t  dwim_remotes_matched);\n+\t\t\tconst char *remote = parse_remote_branch(arg, rev,\n+\t\t\t\t\t\t\t\t could_be_checkout_paths,\n+\t\t\t\t\t\t\t\t dwim_remotes_matched);\n \t\t\tif (remote) {\n-\t\t\t\tif (could_be_checkout_paths)\n-\t\t\t\t\tdie(_(\"'%s' could be both a local file and a tracking branch.\\n\"\n-\t\t\t\t\t      \"Please use -- (and optionally --no-guess) to disambiguate\"),\n-\t\t\t\t\t    arg);\n \t\t\t\t*new_branch = arg;\n \t\t\t\targ = remote;\n \t\t\t\t/* DWIMmed to create local branch, case (3).(b) */\n-- \ngitgitgadget\n\n"},{"id":"388027","messageId":"9af2de98dec456f9314121fc964df037be7f5b36.1576161385.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.git.1576161385.gitgitgadget@gmail.com","subject":"[PATCH 16/16] checkout, restore: support the --pathspec-from-file option","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-12T14:36:25Z","receivedAt":"2019-12-12T14:36:47Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nDecisions taken for simplicity:\n1) For now, `--pathspec-from-file` is declared incompatible with\n   `--patch`, even when <file> is not `stdin`. Such use case it not\n   really expected.\n2) It is not allowed to pass pathspec in both args and file.\n\n`you must specify path(s) to restore` block was moved down to be able to\ntest for `pathspec.nr` instead, because testing for `argc` is no longer\ncorrect.\n\n`git switch` does not support the new options because it doesn't expect\n`<pathspec>` arguments.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-checkout.txt    |  15 ++++\n Documentation/git-restore.txt     |  14 +++\n builtin/checkout.c                |  33 +++++--\n t/t2026-checkout-pathspec-file.sh |  61 +++++++++++++\n t/t2072-restore-pathspec-file.sh  | 139 ++++++++++++++++++++++++++++++\n t/t9902-completion.sh             |   2 +\n 6 files changed, 259 insertions(+), 5 deletions(-)\n create mode 100755 t/t2026-checkout-pathspec-file.sh\n create mode 100755 t/t2072-restore-pathspec-file.sh\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 93124f3ad9..ffe3c1bff2 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n 'git checkout' [-q] [-f] [-m] [--detach] <commit>\n 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]\n 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...\n+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul]\n 'git checkout' (-p|--patch) [<tree-ish>] [--] [<pathspec>...]\n \n DESCRIPTION\n@@ -79,6 +80,7 @@ be used to detach `HEAD` at the tip of the branch (`git checkout\n Omitting `<branch>` detaches `HEAD` at the tip of the current branch.\n \n 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...::\n+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul]::\n \n \tOverwrite the contents of the files that match the pathspec.\n \tWhen the `<tree-ish>` (most often a commit) is not given, \n@@ -306,6 +308,19 @@ Note that this option uses the no overlay mode by default (see also\n \tworking tree, but not in `<tree-ish>` are removed, to make them\n \tmatch `<tree-ish>` exactly.\n \n+--pathspec-from-file=<file>::\n+\tPathspec is passed in `<file>` instead of commandline args. If\n+\t`<file>` is exactly `-` then standard input is used. Pathspec\n+\telements are separated by LF or CR/LF. Pathspec elements can be\n+\tquoted as explained for the configuration variable `core.quotePath`\n+\t(see linkgit:git-config[1]). See also `--pathspec-file-nul` and\n+\tglobal `--literal-pathspecs`.\n+\n+--pathspec-file-nul::\n+\tOnly meaningful with `--pathspec-from-file`. Pathspec elements are\n+\tseparated with NUL character and all other characters are taken\n+\tliterally (including newlines and quotes).\n+\n <branch>::\n \tBranch to checkout; if it refers to a branch (i.e., a name that,\n \twhen prepended with \"refs/heads/\", is a valid ref), then that\ndiff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt\nindex d7bf016bba..5bf60d4943 100644\n--- a/Documentation/git-restore.txt\n+++ b/Documentation/git-restore.txt\n@@ -9,6 +9,7 @@ SYNOPSIS\n --------\n [verse]\n 'git restore' [<options>] [--source=<tree>] [--staged] [--worktree] [--] <pathspec>...\n+'git restore' [<options>] [--source=<tree>] [--staged] [--worktree] --pathspec-from-file=<file> [--pathspec-file-nul]\n 'git restore' (-p|--patch) [<options>] [--source=<tree>] [--staged] [--worktree] [--] [<pathspec>...]\n \n DESCRIPTION\n@@ -113,6 +114,19 @@ in linkgit:git-checkout[1] for details.\n \tappear in the `--source` tree are removed, to make them match\n \t`<tree>` exactly. The default is no-overlay mode.\n \n+--pathspec-from-file=<file>::\n+\tPathspec is passed in `<file>` instead of commandline args. If\n+\t`<file>` is exactly `-` then standard input is used. Pathspec\n+\telements are separated by LF or CR/LF. Pathspec elements can be\n+\tquoted as explained for the configuration variable `core.quotePath`\n+\t(see linkgit:git-config[1]). See also `--pathspec-file-nul` and\n+\tglobal `--literal-pathspecs`.\n+\n+--pathspec-file-nul::\n+\tOnly meaningful with `--pathspec-from-file`. Pathspec elements are\n+\tseparated with NUL character and all other characters are taken\n+\tliterally (including newlines and quotes).\n+\n \\--::\n \tDo not interpret any more arguments as options.\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex aa4ff14ec2..b02425f49b 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -70,6 +70,8 @@ struct checkout_opts {\n \tint checkout_worktree;\n \tconst char *ignore_unmerged_opt;\n \tint ignore_unmerged;\n+\tint pathspec_file_nul;\n+\tconst char *pathspec_from_file;\n \n \tconst char *new_branch;\n \tconst char *new_branch_force;\n@@ -1202,7 +1204,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t * Absence of '--' leaves <pathspec>/<commit> ambiguity.\n \t\t * Try to resolve it with additional knowledge about pathspec args.\n \t\t */\n-\t\texpect_commit_only = !opts->accept_pathspec;\n+\t\texpect_commit_only = !opts->accept_pathspec || opts->pathspec_from_file;\n \t} else if (dash_dash_pos == 0) {\n \t\t/* 'git checkout/switch/restore -- [...]' */\n \t\treturn 1;  /* Eat '--' */\n@@ -1476,6 +1478,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,\n \t\tOPT_BOOL('p', \"patch\", &opts->patch_mode, N_(\"select hunks interactively\")),\n \t\tOPT_BOOL(0, \"ignore-skip-worktree-bits\", &opts->ignore_skipworktree,\n \t\t\t N_(\"do not limit pathspecs to sparse entries only\")),\n+\t\tOPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),\n+\t\tOPT_PATHSPEC_FILE_NUL(&opts->pathspec_file_nul),\n \t\tOPT_END()\n \t};\n \tstruct option *newopts = parse_options_concat(prevopts, options);\n@@ -1612,10 +1616,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t\t\tdie(_(\"reference is not a tree: %s\"), opts->from_treeish);\n \t}\n \n-\tif (opts->accept_pathspec && !opts->empty_pathspec_ok && !argc &&\n-\t    !opts->patch_mode)\t/* patch mode is special */\n-\t\tdie(_(\"you must specify path(s) to restore\"));\n-\n \tif (argc) {\n \t\tparse_pathspec(&opts->pathspec, 0,\n \t\t\t       opts->patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,\n@@ -1635,10 +1635,33 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t\tif (opts->force_detach)\n \t\t\tdie(_(\"git checkout: --detach does not take a path argument '%s'\"),\n \t\t\t    argv[0]);\n+\t}\n+\n+\tif (opts->pathspec_from_file) {\n+\t\tif (opts->pathspec.nr)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with pathspec arguments\"));\n+\n+\t\tif (opts->force_detach)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with --detach\"));\n \n+\t\tif (opts->patch_mode)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with --patch\"));\n+\n+\t\tparse_pathspec_file(&opts->pathspec, 0,\n+\t\t\t\t    0,\n+\t\t\t\t    prefix, opts->pathspec_from_file, opts->pathspec_file_nul);\n+\t} else if (opts->pathspec_file_nul) {\n+\t\tdie(_(\"--pathspec-file-nul requires --pathspec-from-file\"));\n+\t}\n+\n+\tif (opts->pathspec.nr) {\n \t\tif (1 < !!opts->writeout_stage + !!opts->force + !!opts->merge)\n \t\t\tdie(_(\"git checkout: --ours/--theirs, --force and --merge are incompatible when\\n\"\n \t\t\t      \"checking out of the index.\"));\n+\t} else {\n+\t\tif (opts->accept_pathspec && !opts->empty_pathspec_ok &&\n+\t\t    !opts->patch_mode)\t/* patch mode is special */\n+\t\t\tdie(_(\"you must specify path(s) to restore\"));\n \t}\n \n \tif (opts->new_branch) {\ndiff --git a/t/t2026-checkout-pathspec-file.sh b/t/t2026-checkout-pathspec-file.sh\nnew file mode 100755\nindex 0000000000..379f1afd8e\n--- /dev/null\n+++ b/t/t2026-checkout-pathspec-file.sh\n@@ -0,0 +1,61 @@\n+#!/bin/sh\n+\n+test_description='checkout --pathspec-from-file'\n+\n+. ./test-lib.sh\n+\n+test_tick\n+\n+test_expect_success setup '\n+\ttest_commit file0 &&\n+\n+\techo 1 >fileA.t &&\n+\techo 1 >fileB.t &&\n+\techo 1 >fileC.t &&\n+\techo 1 >fileD.t &&\n+\tgit add fileA.t fileB.t fileC.t fileD.t &&\n+\tgit commit -m \"files 1\" &&\n+\n+\techo 2 >fileA.t &&\n+\techo 2 >fileB.t &&\n+\techo 2 >fileC.t &&\n+\techo 2 >fileD.t &&\n+\tgit add fileA.t fileB.t fileC.t fileD.t &&\n+\tgit commit -m \"files 2\" &&\n+\n+\tgit tag checkpoint\n+'\n+\n+restore_checkpoint () {\n+\tgit reset --hard checkpoint\n+}\n+\n+verify_expect () {\n+\tgit status --porcelain --untracked-files=no -- fileA.t fileB.t fileC.t fileD.t >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success 'simplest' '\n+\trestore_checkpoint &&\n+\n+\techo fileA.t | git checkout --pathspec-from-file=- HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tM  fileA.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'only touches what was listed' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileB.t\\nfileC.t\\n\" | git checkout --pathspec-from-file=- HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tM  fileB.t\n+\tM  fileC.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_done\ndiff --git a/t/t2072-restore-pathspec-file.sh b/t/t2072-restore-pathspec-file.sh\nnew file mode 100755\nindex 0000000000..db58e83735\n--- /dev/null\n+++ b/t/t2072-restore-pathspec-file.sh\n@@ -0,0 +1,139 @@\n+#!/bin/sh\n+\n+test_description='restore --pathspec-from-file'\n+\n+. ./test-lib.sh\n+\n+test_tick\n+\n+test_expect_success setup '\n+\ttest_commit file0 &&\n+\n+\techo 1 >fileA.t &&\n+\techo 1 >fileB.t &&\n+\techo 1 >fileC.t &&\n+\techo 1 >fileD.t &&\n+\tgit add fileA.t fileB.t fileC.t fileD.t &&\n+\tgit commit -m \"files 1\" &&\n+\n+\techo 2 >fileA.t &&\n+\techo 2 >fileB.t &&\n+\techo 2 >fileC.t &&\n+\techo 2 >fileD.t &&\n+\tgit add fileA.t fileB.t fileC.t fileD.t &&\n+\tgit commit -m \"files 2\" &&\n+\n+\tgit tag checkpoint\n+'\n+\n+restore_checkpoint () {\n+\tgit reset --hard checkpoint\n+}\n+\n+verify_expect () {\n+\tgit status --porcelain --untracked-files=no -- fileA.t fileB.t fileC.t fileD.t >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success '--pathspec-from-file from stdin' '\n+\trestore_checkpoint &&\n+\n+\techo fileA.t | git restore --pathspec-from-file=- --source=HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\t M fileA.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success '--pathspec-from-file from file' '\n+\trestore_checkpoint &&\n+\n+\techo fileA.t >list &&\n+\tgit restore --pathspec-from-file=list --source=HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\t M fileA.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'NUL delimiters' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileA.t\\0fileB.t\\0\" | git restore --pathspec-from-file=- --pathspec-file-nul --source=HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\t M fileA.t\n+\t M fileB.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'LF delimiters' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileA.t\\nfileB.t\\n\" | git restore --pathspec-from-file=- --source=HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\t M fileA.t\n+\t M fileB.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'no trailing delimiter' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileA.t\\nfileB.t\" | git restore --pathspec-from-file=- --source=HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\t M fileA.t\n+\t M fileB.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'CRLF delimiters' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileA.t\\r\\nfileB.t\\r\\n\" | git restore --pathspec-from-file=- --source=HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\t M fileA.t\n+\t M fileB.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'quotes' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"\\\"file\\\\101.t\\\"\" | git restore --pathspec-from-file=- --source=HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\t M fileA.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'quotes not compatible with --pathspec-file-nul' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"\\\"file\\\\101.t\\\"\" >list &&\n+\ttest_must_fail git restore --pathspec-from-file=list --pathspec-file-nul --source=HEAD^1\n+'\n+\n+test_expect_success 'only touches what was listed' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileB.t\\nfileC.t\\n\" | git restore --pathspec-from-file=- --source=HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\t M fileB.t\n+\t M fileC.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_done\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex ec3eccfd3d..93877ba9cd 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -1438,6 +1438,8 @@ test_expect_success 'double dash \"git checkout\"' '\n \t--no-guess Z\n \t--no-... Z\n \t--overlay Z\n+\t--pathspec-file-nul Z\n+\t--pathspec-from-file=Z\n \tEOF\n '\n \n-- \ngitgitgadget\n"},{"id":"388028","messageId":"a6b77f9b23c819401c1a066f0895bb465ce5058d.1576161385.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.git.1576161385.gitgitgadget@gmail.com","subject":"[PATCH 12/16] parse_branchname_arg(): introduce expect_commit_only","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-12T14:36:21Z","receivedAt":"2019-12-12T14:36:51Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\n`has_dash_dash` unexpectedly takes `opts->accept_pathspec` into account.\nWhile this may sound clever at first sight, it becomes pretty hard to\nreason (and not be a victim) about code, especially in combination with\n`argc` here:\n\n\tif (!(argc == 1 && !has_dash_dash) &&\n\t    !(argc == 2 && has_dash_dash) &&\n\t    opts->accept_pathspec)\n\t\trecover_with_dwim = 0;\n\nIntroduce a new non-obfuscated variable to reduce the amount of diffs in\nnext patch.\n\nThis should not change behavior in any way.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c | 15 +++++++++------\n 1 file changed, 9 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex f35fe2cc26..bd0efa9140 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1154,7 +1154,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \tconst char **new_branch = &opts->new_branch;\n \tconst char *arg;\n \tint dash_dash_pos;\n-\tint has_dash_dash = 0;\n+\tint has_dash_dash = 0, expect_commit_only = 0;\n \tint i;\n \n \t/*\n@@ -1225,7 +1225,10 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t    die(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n \t}\n \n-\topts->count_checkout_paths = !opts->quiet && !has_dash_dash;\n+\tif (has_dash_dash)\n+\t    expect_commit_only = 1;\n+\n+\topts->count_checkout_paths = !opts->quiet && !expect_commit_only;\n \n \tif (!strcmp(arg, \"-\"))\n \t\targ = \"@{-1}\";\n@@ -1241,10 +1244,10 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t */\n \t\tint recover_with_dwim = dwim_new_local_branch_ok;\n \n-\t\tint could_be_checkout_paths = !has_dash_dash &&\n+\t\tint could_be_checkout_paths = !expect_commit_only &&\n \t\t\tcheck_filename(opts->prefix, arg);\n \n-\t\tif (!has_dash_dash && !no_wildcard(arg))\n+\t\tif (!expect_commit_only && !no_wildcard(arg))\n \t\t\trecover_with_dwim = 0;\n \n \t\t/*\n@@ -1269,7 +1272,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t}\n \n \t\tif (!recover_with_dwim) {\n-\t\t\tif (has_dash_dash)\n+\t\t\tif (expect_commit_only)\n \t\t\t\tdie(_(\"invalid reference: %s\"), arg);\n \t\t\treturn 0;\n \t\t}\n@@ -1280,7 +1283,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \tif (!opts->source_tree)                   /* case (1): want a tree */\n \t\tdie(_(\"reference is not a tree: %s\"), arg);\n \n-\tif (!has_dash_dash) {\t/* case (3).(d) -> (1) */\n+\tif (!expect_commit_only) {\t/* case (3).(d) -> (1) */\n \t\t/*\n \t\t * Do not complain the most common case\n \t\t *\tgit checkout branch\n-- \ngitgitgadget\n\n"},{"id":"388029","messageId":"8677db4b9efb220f10a80d377c5d4038df860a48.1576161385.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.git.1576161385.gitgitgadget@gmail.com","subject":"[PATCH 05/16] doc: checkout: remove duplicate synopsis","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-12T14:36:14Z","receivedAt":"2019-12-12T14:36:54Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nIt was added in [1]. I understand that the duplicate change was not\nintentional and comes from an oversight.\n\nAlso, in explanation, there was only one section for two synopsis\nentries.\n\nFix both problems by removing duplicate synopsis.\n\n<paths> vs <pathspec> is resolved in next patch.\n\n[1] Commit b59698ae (\"checkout doc: clarify command line args for \"checkout paths\" mode\" 2017-10-11)\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-checkout.txt | 3 +--\n 1 file changed, 1 insertion(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex cf3cac0a2b..2011fdbb1d 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -13,7 +13,6 @@ SYNOPSIS\n 'git checkout' [-q] [-f] [-m] [--detach] <commit>\n 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]\n 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...\n-'git checkout' [<tree-ish>] [--] <pathspec>...\n 'git checkout' (-p|--patch) [<tree-ish>] [--] [<paths>...]\n \n DESCRIPTION\n@@ -79,7 +78,7 @@ be used to detach `HEAD` at the tip of the branch (`git checkout\n +\n Omitting `<branch>` detaches `HEAD` at the tip of the current branch.\n \n-'git checkout' [<tree-ish>] [--] <pathspec>...::\n+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...::\n \n \tOverwrite paths in the working tree by replacing with the\n \tcontents in the index or in the `<tree-ish>` (most often a\n-- \ngitgitgadget\n\n"},{"id":"388030","messageId":"0328fdafbb6c70ec3f3ead84183af94cd060ae0e.1576161385.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.git.1576161385.gitgitgadget@gmail.com","subject":"[PATCH 04/16] add: support the --pathspec-from-file option","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-12T14:36:13Z","receivedAt":"2019-12-12T14:36:54Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nDecisions taken for simplicity:\n1) For now, `--pathspec-from-file` is declared incompatible with\n   `--interactive/--patch/--edit`, even when <file> is not `stdin`.\n   Such use case it not really expected. Also, it would require changes\n   to `interactive_add()` and `edit_patch()`.\n2) It is not allowed to pass pathspec in both args and file.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-add.txt    | 16 +++++++++++-\n builtin/add.c                | 30 +++++++++++++++++++---\n t/t3704-add-pathspec-file.sh | 49 ++++++++++++++++++++++++++++++++++++\n 3 files changed, 90 insertions(+), 5 deletions(-)\n create mode 100755 t/t3704-add-pathspec-file.sh\n\ndiff --git a/Documentation/git-add.txt b/Documentation/git-add.txt\nindex 8b0e4c7fa8..be5e3ac54b 100644\n--- a/Documentation/git-add.txt\n+++ b/Documentation/git-add.txt\n@@ -11,7 +11,8 @@ SYNOPSIS\n 'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]\n \t  [--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]]\n \t  [--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]\n-\t  [--chmod=(+|-)x] [--] [<pathspec>...]\n+\t  [--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n+\t  [--] [<pathspec>...]\n \n DESCRIPTION\n -----------\n@@ -187,6 +188,19 @@ for \"git add --no-all <pathspec>...\", i.e. ignored removed files.\n \tbit is only changed in the index, the files on disk are left\n \tunchanged.\n \n+--pathspec-from-file=<file>::\n+\tPathspec is passed in `<file>` instead of commandline args. If\n+\t`<file>` is exactly `-` then standard input is used. Pathspec\n+\telements are separated by LF or CR/LF. Pathspec elements can be\n+\tquoted as explained for the configuration variable `core.quotePath`\n+\t(see linkgit:git-config[1]). See also `--pathspec-file-nul` and\n+\tglobal `--literal-pathspecs`.\n+\n+--pathspec-file-nul::\n+\tOnly meaningful with `--pathspec-from-file`. Pathspec elements are\n+\tseparated with NUL character and all other characters are taken\n+\tliterally (including newlines and quotes).\n+\n \\--::\n \tThis option can be used to separate command-line options from\n \tthe list of files, (useful when filenames might be mistaken\ndiff --git a/builtin/add.c b/builtin/add.c\nindex 3d1791dd82..7c21ad492b 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -29,6 +29,8 @@ static const char * const builtin_add_usage[] = {\n static int patch_interactive, add_interactive, edit_interactive;\n static int take_worktree_changes;\n static int add_renormalize;\n+static int pathspec_file_nul;\n+static const char *pathspec_from_file;\n \n struct update_callback_data {\n \tint flags;\n@@ -320,6 +322,8 @@ static struct option builtin_add_options[] = {\n \t\t   N_(\"override the executable bit of the listed files\")),\n \tOPT_HIDDEN_BOOL(0, \"warn-embedded-repo\", &warn_on_embedded_repo,\n \t\t\tN_(\"warn when adding an embedded repository\")),\n+\tOPT_PATHSPEC_FROM_FILE(&pathspec_from_file),\n+\tOPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),\n \tOPT_END(),\n };\n \n@@ -414,11 +418,17 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \t\t\t  builtin_add_usage, PARSE_OPT_KEEP_ARGV0);\n \tif (patch_interactive)\n \t\tadd_interactive = 1;\n-\tif (add_interactive)\n+\tif (add_interactive) {\n+\t\tif (pathspec_from_file)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with --interactive/--patch\"));\n \t\texit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive));\n+\t}\n \n-\tif (edit_interactive)\n+\tif (edit_interactive) {\n+\t\tif (pathspec_from_file)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with --edit\"));\n \t\treturn(edit_patch(argc, argv, prefix));\n+\t}\n \targc--;\n \targv++;\n \n@@ -451,13 +461,25 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \t\t       PATHSPEC_SYMLINK_LEADING_PATH,\n \t\t       prefix, argv);\n \n-\tif (require_pathspec && argc == 0) {\n+\tif (pathspec_from_file) {\n+\t\tif (pathspec.nr)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with pathspec arguments\"));\n+\n+\t\tparse_pathspec_file(&pathspec, PATHSPEC_ATTR,\n+\t\t\t\t    PATHSPEC_PREFER_FULL |\n+\t\t\t\t    PATHSPEC_SYMLINK_LEADING_PATH,\n+\t\t\t\t    prefix, pathspec_from_file, pathspec_file_nul);\n+\t} else if (pathspec_file_nul) {\n+\t\tdie(_(\"--pathspec-file-nul requires --pathspec-from-file\"));\n+\t}\n+\n+\tif (require_pathspec && pathspec.nr == 0) {\n \t\tfprintf(stderr, _(\"Nothing specified, nothing added.\\n\"));\n \t\tfprintf(stderr, _(\"Maybe you wanted to say 'git add .'?\\n\"));\n \t\treturn 0;\n \t}\n \n-\tif (!take_worktree_changes && addremove_explicit < 0 && argc)\n+\tif (!take_worktree_changes && addremove_explicit < 0 && pathspec.nr)\n \t\t/* Turn \"git add pathspec...\" to \"git add -A pathspec...\" */\n \t\taddremove = 1;\n \ndiff --git a/t/t3704-add-pathspec-file.sh b/t/t3704-add-pathspec-file.sh\nnew file mode 100755\nindex 0000000000..b7f3fedcb8\n--- /dev/null\n+++ b/t/t3704-add-pathspec-file.sh\n@@ -0,0 +1,49 @@\n+#!/bin/sh\n+\n+test_description='add --pathspec-from-file'\n+\n+. ./test-lib.sh\n+\n+test_tick\n+\n+test_expect_success setup '\n+\ttest_commit file0 &&\n+\techo A >fileA.t &&\n+\techo B >fileB.t &&\n+\techo C >fileC.t &&\n+\techo D >fileD.t\n+'\n+\n+restore_checkpoint () {\n+\tgit reset\n+}\n+\n+verify_expect () {\n+\tgit status --porcelain --untracked-files=no -- fileA.t fileB.t fileC.t fileD.t >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success 'simplest' '\n+\trestore_checkpoint &&\n+\n+\techo fileA.t | git add --pathspec-from-file=- &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tA  fileA.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'only touches what was listed' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileB.t\\nfileC.t\\n\" | git add --pathspec-from-file=- &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tA  fileB.t\n+\tA  fileC.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"388031","messageId":"6288a7fa36d013a6cb4864544993412b6e39eac1.1576161385.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.git.1576161385.gitgitgadget@gmail.com","subject":"[PATCH 07/16] doc: checkout: synchronize <pathspec> description","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-12T14:36:16Z","receivedAt":"2019-12-12T14:36:55Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\n`git add` shows an example of good writing, follow it.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-checkout.txt | 24 +++++++++++++++---------\n 1 file changed, 15 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex d47046e050..93124f3ad9 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -12,13 +12,13 @@ SYNOPSIS\n 'git checkout' [-q] [-f] [-m] --detach [<branch>]\n 'git checkout' [-q] [-f] [-m] [--detach] <commit>\n 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]\n-'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...\n-'git checkout' (-p|--patch) [<tree-ish>] [--] [<paths>...]\n+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...\n+'git checkout' (-p|--patch) [<tree-ish>] [--] [<pathspec>...]\n \n DESCRIPTION\n -----------\n Updates files in the working tree to match the version in the index\n-or the specified tree.  If no paths are given, 'git checkout' will\n+or the specified tree.  If no pathspec was given, 'git checkout' will\n also update `HEAD` to set the specified branch as the current\n branch.\n \n@@ -78,13 +78,13 @@ be used to detach `HEAD` at the tip of the branch (`git checkout\n +\n Omitting `<branch>` detaches `HEAD` at the tip of the current branch.\n \n-'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...::\n+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...::\n \n-\tOverwrite paths in the working tree by replacing with the\n-\tcontents in the index or in the `<tree-ish>` (most often a\n-\tcommit).  When a `<tree-ish>` is given, the paths that\n-\tmatch the `<pathspec>` are updated both in the index and in\n-\tthe working tree.\n+\tOverwrite the contents of the files that match the pathspec.\n+\tWhen the `<tree-ish>` (most often a commit) is not given, \n+\toverwrite working tree with the contents in the index.\n+\tWhen the `<tree-ish>` is given, overwrite both the index and\n+\tthe working tree with the contents at the `<tree-ish>`.\n +\n The index may contain unmerged entries because of a previous failed merge.\n By default, if you try to check out such an entry from the index, the\n@@ -336,7 +336,13 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.\n \tTree to checkout from (when paths are given). If not specified,\n \tthe index will be used.\n \n+\\--::\n+\tDo not interpret any more arguments as options.\n \n+<pathspec>...::\n+\tLimits the paths affected by the operation.\n++\n+For more details, see the 'pathspec' entry in linkgit:gitglossary[7].\n \n DETACHED HEAD\n -------------\n-- \ngitgitgadget\n\n"},{"id":"388247","messageId":"5928a257-a1f4-dad3-052e-7a0dbf1df8e3@gmail.com","threadId":"52440","inReplyTo":"c0980519ed319295c273d358b0547340cc511a6b.1576161385.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 02/16] commit: forbid --pathspec-from-file --all","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2019-12-16T12:02:35Z","receivedAt":"2019-12-16T12:02:41Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Alexandr\n\nOn 12/12/2019 14:36, Alexandr Miloslavskiy via GitGitGadget wrote:\n> From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n> \n> I forgot this in my previous patch `--pathspec-from-file` for\n> `git commit` [1]. When both `--pathspec-from-file` and `--all` were\n> specified, `--all` took precedence and `--pathspec-from-file` was\n> ignored. Before `--pathspec-from-file` was implemented, this case was\n> prevented by this check in `parse_and_validate_options()` :\n> \n>     die(_(\"paths '%s ...' with -a does not make sense\"), argv[0]);\n> \n> It is unfortunate that these two cases are disconnected. This came as\n> result of how the code was laid out before my patches, where `pathspec`\n> is parsed outside of `parse_and_validate_options()`. This branch is\n> already full of refactoring patches and I did not dare to go for another\n> one.\n> \n> Fix by mirroring `die()` for `--pathspec-from-file` as well.\n> \n> [1] Commit e440fc58 (\"commit: support the --pathspec-from-file option\" 2019-11-19)\n> \n> Co-authored-by: Phillip Wood <phillip.wood123@gmail.com>\n\nThanks for fixing this. If you want to credit me then I think\nReported-by: would be more appropriate as I didn't write this patch.\nAlso I tend to use phillip.wood@dunelm.org.uk for footers as it's a\nportable email address (I should add a mailmap entry...). It would be\nnice to have tests for the various error conditions at some point.\n\nBest Wishes\n\nPhillip\n\n> Signed-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n> ---\n>  builtin/commit.c | 3 +++\n>  1 file changed, 3 insertions(+)\n> \n> diff --git a/builtin/commit.c b/builtin/commit.c\n> index 2db2ad0de4..893a9f29b2 100644\n> --- a/builtin/commit.c\n> +++ b/builtin/commit.c\n> @@ -347,6 +347,9 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix\n>  \t\tif (interactive)\n>  \t\t\tdie(_(\"--pathspec-from-file is incompatible with --interactive/--patch\"));\n>  \n> +\t\tif (all)\n> +\t\t\tdie(_(\"--pathspec-from-file with -a does not make sense\"));\n> +\n>  \t\tif (pathspec.nr)\n>  \t\t\tdie(_(\"--pathspec-from-file is incompatible with pathspec arguments\"));\n>  \n> \n\n"},{"id":"388263","messageId":"c7cd46d3a3bcbf4a5cfe2aa6aa71b3713801892b.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 02/18] t7526: add tests for error conditions","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:47:50Z","receivedAt":"2019-12-16T15:48:12Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nSuggested-By: Phillip Wood <phillip.wood@dunelm.org.uk>\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n t/t7526-commit-pathspec-file.sh | 24 ++++++++++++++++++++++++\n 1 file changed, 24 insertions(+)\n\ndiff --git a/t/t7526-commit-pathspec-file.sh b/t/t7526-commit-pathspec-file.sh\nindex 4e592f7472..68920e8ff9 100755\n--- a/t/t7526-commit-pathspec-file.sh\n+++ b/t/t7526-commit-pathspec-file.sh\n@@ -61,4 +61,28 @@ test_expect_success 'only touches what was listed' '\n \tverify_expect\n '\n \n+test_expect_success 'error conditions' '\n+\trestore_checkpoint &&\n+\techo fileA.t >list &&\n+\t>empty_list &&\n+\n+\ttest_must_fail git commit --pathspec-from-file=- --interactive -m \"Commit\" <list 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-interactive/\\-\\-patch\" err &&\n+\n+\ttest_must_fail git commit --pathspec-from-file=- --patch -m \"Commit\" <list 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-interactive/\\-\\-patch\" err &&\n+\n+\ttest_must_fail git commit --pathspec-from-file=- -m \"Commit\" -- fileA.t <list 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n+\n+\ttest_must_fail git commit --pathspec-file-nul -m \"Commit\" 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-file-nul requires \\-\\-pathspec-from-file\" err &&\n+\n+\ttest_must_fail git commit --pathspec-from-file=- --include -m \"Commit\" <empty_list 2>err &&\n+\ttest_i18ngrep \"No paths with \\-\\-include/\\-\\-only does not make sense.\" err &&\n+\n+\ttest_must_fail git commit --pathspec-from-file=- --only -m \"Commit\" <empty_list 2>err &&\n+\ttest_i18ngrep \"No paths with \\-\\-include/\\-\\-only does not make sense.\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"388264","messageId":"deeb860a85d25e0645a5d2e1c82654653ab1e2d5.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 04/18] commit: forbid --pathspec-from-file --all","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:47:52Z","receivedAt":"2019-12-16T15:48:15Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nI forgot this in my previous patch `--pathspec-from-file` for\n`git commit` [1]. When both `--pathspec-from-file` and `--all` were\nspecified, `--all` took precedence and `--pathspec-from-file` was\nignored. Before `--pathspec-from-file` was implemented, this case was\nprevented by this check in `parse_and_validate_options()` :\n\n    die(_(\"paths '%s ...' with -a does not make sense\"), argv[0]);\n\nIt is unfortunate that these two cases are disconnected. This came as\nresult of how the code was laid out before my patches, where `pathspec`\nis parsed outside of `parse_and_validate_options()`. This branch is\nalready full of refactoring patches and I did not dare to go for another\none.\n\nFix by mirroring `die()` for `--pathspec-from-file` as well.\n\n[1] Commit e440fc58 (\"commit: support the --pathspec-from-file option\" 2019-11-19)\n\nReported-by: Phillip Wood <phillip.wood@dunelm.org.uk>\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/commit.c                | 3 +++\n t/t7526-commit-pathspec-file.sh | 3 +++\n 2 files changed, 6 insertions(+)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 2db2ad0de4..893a9f29b2 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -347,6 +347,9 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix\n \t\tif (interactive)\n \t\t\tdie(_(\"--pathspec-from-file is incompatible with --interactive/--patch\"));\n \n+\t\tif (all)\n+\t\t\tdie(_(\"--pathspec-from-file with -a does not make sense\"));\n+\n \t\tif (pathspec.nr)\n \t\t\tdie(_(\"--pathspec-from-file is incompatible with pathspec arguments\"));\n \ndiff --git a/t/t7526-commit-pathspec-file.sh b/t/t7526-commit-pathspec-file.sh\nindex 68920e8ff9..ba769e0e5d 100755\n--- a/t/t7526-commit-pathspec-file.sh\n+++ b/t/t7526-commit-pathspec-file.sh\n@@ -72,6 +72,9 @@ test_expect_success 'error conditions' '\n \ttest_must_fail git commit --pathspec-from-file=- --patch -m \"Commit\" <list 2>err &&\n \ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-interactive/\\-\\-patch\" err &&\n \n+\ttest_must_fail git commit --pathspec-from-file=- --all -m \"Commit\" <list 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-from-file with \\-a does not make sense\" err &&\n+\n \ttest_must_fail git commit --pathspec-from-file=- -m \"Commit\" -- fileA.t <list 2>err &&\n \ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n \n-- \ngitgitgadget\n\n"},{"id":"388265","messageId":"204a0a4446942ac89a59aba672a7646f55185985.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 05/18] cmd_add: prepare for next patch","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:47:53Z","receivedAt":"2019-12-16T15:48:17Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nSome code blocks were moved down to be able to test for `pathspec.nr`\nin the next patch. Blocks are moved as is without any changes. This\nis done as separate patch to reduce the amount of diffs in next patch.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/add.c | 34 +++++++++++++++++-----------------\n 1 file changed, 17 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/add.c b/builtin/add.c\nindex d4686d5218..3d1791dd82 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -430,10 +430,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \tif (addremove && take_worktree_changes)\n \t\tdie(_(\"-A and -u are mutually incompatible\"));\n \n-\tif (!take_worktree_changes && addremove_explicit < 0 && argc)\n-\t\t/* Turn \"git add pathspec...\" to \"git add -A pathspec...\" */\n-\t\taddremove = 1;\n-\n \tif (!show_only && ignore_missing)\n \t\tdie(_(\"Option --ignore-missing can only be used together with --dry-run\"));\n \n@@ -446,19 +442,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \n \thold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);\n \n-\tflags = ((verbose ? ADD_CACHE_VERBOSE : 0) |\n-\t\t (show_only ? ADD_CACHE_PRETEND : 0) |\n-\t\t (intent_to_add ? ADD_CACHE_INTENT : 0) |\n-\t\t (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |\n-\t\t (!(addremove || take_worktree_changes)\n-\t\t  ? ADD_CACHE_IGNORE_REMOVAL : 0));\n-\n-\tif (require_pathspec && argc == 0) {\n-\t\tfprintf(stderr, _(\"Nothing specified, nothing added.\\n\"));\n-\t\tfprintf(stderr, _(\"Maybe you wanted to say 'git add .'?\\n\"));\n-\t\treturn 0;\n-\t}\n-\n \t/*\n \t * Check the \"pathspec '%s' did not match any files\" block\n \t * below before enabling new magic.\n@@ -468,6 +451,23 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \t\t       PATHSPEC_SYMLINK_LEADING_PATH,\n \t\t       prefix, argv);\n \n+\tif (require_pathspec && argc == 0) {\n+\t\tfprintf(stderr, _(\"Nothing specified, nothing added.\\n\"));\n+\t\tfprintf(stderr, _(\"Maybe you wanted to say 'git add .'?\\n\"));\n+\t\treturn 0;\n+\t}\n+\n+\tif (!take_worktree_changes && addremove_explicit < 0 && argc)\n+\t\t/* Turn \"git add pathspec...\" to \"git add -A pathspec...\" */\n+\t\taddremove = 1;\n+\n+\tflags = ((verbose ? ADD_CACHE_VERBOSE : 0) |\n+\t\t (show_only ? ADD_CACHE_PRETEND : 0) |\n+\t\t (intent_to_add ? ADD_CACHE_INTENT : 0) |\n+\t\t (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |\n+\t\t (!(addremove || take_worktree_changes)\n+\t\t  ? ADD_CACHE_IGNORE_REMOVAL : 0));\n+\n \tif (read_cache_preload(&pathspec) < 0)\n \t\tdie(_(\"index file corrupt\"));\n \n-- \ngitgitgadget\n\n"},{"id":"388266","messageId":"1ea5f1784774e19987a08a6b5214000681b34ae1.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 06/18] add: support the --pathspec-from-file option","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:47:54Z","receivedAt":"2019-12-16T15:48:17Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nDecisions taken for simplicity:\n1) For now, `--pathspec-from-file` is declared incompatible with\n   `--interactive/--patch/--edit`, even when <file> is not `stdin`.\n   Such use case it not really expected. Also, it would require changes\n   to `interactive_add()` and `edit_patch()`.\n2) It is not allowed to pass pathspec in both args and file.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-add.txt    | 16 ++++++-\n builtin/add.c                | 30 +++++++++++--\n t/t3704-add-pathspec-file.sh | 86 ++++++++++++++++++++++++++++++++++++\n 3 files changed, 127 insertions(+), 5 deletions(-)\n create mode 100755 t/t3704-add-pathspec-file.sh\n\ndiff --git a/Documentation/git-add.txt b/Documentation/git-add.txt\nindex 8b0e4c7fa8..be5e3ac54b 100644\n--- a/Documentation/git-add.txt\n+++ b/Documentation/git-add.txt\n@@ -11,7 +11,8 @@ SYNOPSIS\n 'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]\n \t  [--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]]\n \t  [--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]\n-\t  [--chmod=(+|-)x] [--] [<pathspec>...]\n+\t  [--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n+\t  [--] [<pathspec>...]\n \n DESCRIPTION\n -----------\n@@ -187,6 +188,19 @@ for \"git add --no-all <pathspec>...\", i.e. ignored removed files.\n \tbit is only changed in the index, the files on disk are left\n \tunchanged.\n \n+--pathspec-from-file=<file>::\n+\tPathspec is passed in `<file>` instead of commandline args. If\n+\t`<file>` is exactly `-` then standard input is used. Pathspec\n+\telements are separated by LF or CR/LF. Pathspec elements can be\n+\tquoted as explained for the configuration variable `core.quotePath`\n+\t(see linkgit:git-config[1]). See also `--pathspec-file-nul` and\n+\tglobal `--literal-pathspecs`.\n+\n+--pathspec-file-nul::\n+\tOnly meaningful with `--pathspec-from-file`. Pathspec elements are\n+\tseparated with NUL character and all other characters are taken\n+\tliterally (including newlines and quotes).\n+\n \\--::\n \tThis option can be used to separate command-line options from\n \tthe list of files, (useful when filenames might be mistaken\ndiff --git a/builtin/add.c b/builtin/add.c\nindex 3d1791dd82..7c21ad492b 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -29,6 +29,8 @@ static const char * const builtin_add_usage[] = {\n static int patch_interactive, add_interactive, edit_interactive;\n static int take_worktree_changes;\n static int add_renormalize;\n+static int pathspec_file_nul;\n+static const char *pathspec_from_file;\n \n struct update_callback_data {\n \tint flags;\n@@ -320,6 +322,8 @@ static struct option builtin_add_options[] = {\n \t\t   N_(\"override the executable bit of the listed files\")),\n \tOPT_HIDDEN_BOOL(0, \"warn-embedded-repo\", &warn_on_embedded_repo,\n \t\t\tN_(\"warn when adding an embedded repository\")),\n+\tOPT_PATHSPEC_FROM_FILE(&pathspec_from_file),\n+\tOPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),\n \tOPT_END(),\n };\n \n@@ -414,11 +418,17 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \t\t\t  builtin_add_usage, PARSE_OPT_KEEP_ARGV0);\n \tif (patch_interactive)\n \t\tadd_interactive = 1;\n-\tif (add_interactive)\n+\tif (add_interactive) {\n+\t\tif (pathspec_from_file)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with --interactive/--patch\"));\n \t\texit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive));\n+\t}\n \n-\tif (edit_interactive)\n+\tif (edit_interactive) {\n+\t\tif (pathspec_from_file)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with --edit\"));\n \t\treturn(edit_patch(argc, argv, prefix));\n+\t}\n \targc--;\n \targv++;\n \n@@ -451,13 +461,25 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \t\t       PATHSPEC_SYMLINK_LEADING_PATH,\n \t\t       prefix, argv);\n \n-\tif (require_pathspec && argc == 0) {\n+\tif (pathspec_from_file) {\n+\t\tif (pathspec.nr)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with pathspec arguments\"));\n+\n+\t\tparse_pathspec_file(&pathspec, PATHSPEC_ATTR,\n+\t\t\t\t    PATHSPEC_PREFER_FULL |\n+\t\t\t\t    PATHSPEC_SYMLINK_LEADING_PATH,\n+\t\t\t\t    prefix, pathspec_from_file, pathspec_file_nul);\n+\t} else if (pathspec_file_nul) {\n+\t\tdie(_(\"--pathspec-file-nul requires --pathspec-from-file\"));\n+\t}\n+\n+\tif (require_pathspec && pathspec.nr == 0) {\n \t\tfprintf(stderr, _(\"Nothing specified, nothing added.\\n\"));\n \t\tfprintf(stderr, _(\"Maybe you wanted to say 'git add .'?\\n\"));\n \t\treturn 0;\n \t}\n \n-\tif (!take_worktree_changes && addremove_explicit < 0 && argc)\n+\tif (!take_worktree_changes && addremove_explicit < 0 && pathspec.nr)\n \t\t/* Turn \"git add pathspec...\" to \"git add -A pathspec...\" */\n \t\taddremove = 1;\n \ndiff --git a/t/t3704-add-pathspec-file.sh b/t/t3704-add-pathspec-file.sh\nnew file mode 100755\nindex 0000000000..d3a8947dc1\n--- /dev/null\n+++ b/t/t3704-add-pathspec-file.sh\n@@ -0,0 +1,86 @@\n+#!/bin/sh\n+\n+test_description='add --pathspec-from-file'\n+\n+. ./test-lib.sh\n+\n+test_tick\n+\n+test_expect_success setup '\n+\ttest_commit file0 &&\n+\techo A >fileA.t &&\n+\techo B >fileB.t &&\n+\techo C >fileC.t &&\n+\techo D >fileD.t\n+'\n+\n+restore_checkpoint () {\n+\tgit reset\n+}\n+\n+verify_expect () {\n+\tgit status --porcelain --untracked-files=no -- fileA.t fileB.t fileC.t fileD.t >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success 'simplest' '\n+\trestore_checkpoint &&\n+\n+\techo fileA.t | git add --pathspec-from-file=- &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tA  fileA.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success '--pathspec-file-nul' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileA.t\\0fileB.t\\0\" | git add --pathspec-from-file=- --pathspec-file-nul &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tA  fileA.t\n+\tA  fileB.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'only touches what was listed' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileB.t\\nfileC.t\\n\" | git add --pathspec-from-file=- &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tA  fileB.t\n+\tA  fileC.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'error conditions' '\n+\trestore_checkpoint &&\n+\techo fileA.t >list &&\n+\t>empty_list &&\n+\n+\ttest_must_fail git add --pathspec-from-file=- --interactive <list 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-interactive/\\-\\-patch\" err &&\n+\n+\ttest_must_fail git add --pathspec-from-file=- --patch <list 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-interactive/\\-\\-patch\" err &&\n+\n+\ttest_must_fail git add --pathspec-from-file=- --edit <list 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-edit\" err &&\n+\n+\ttest_must_fail git add --pathspec-from-file=- -- fileA.t <list 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n+\n+\ttest_must_fail git add --pathspec-file-nul 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-file-nul requires \\-\\-pathspec-from-file\" err &&\n+\t\n+\t# This case succeeds, but still prints to stderr\n+\tgit add --pathspec-from-file=- <empty_list 2>err &&\n+\ttest_i18ngrep \"Nothing specified, nothing added.\" err\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"388267","messageId":"8d5fb9f40b8fc766ef022f910529e6308d9c2d80.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 01/18] t7107, t7526: directly test parse_pathspec_file()","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:47:49Z","receivedAt":"2019-12-16T15:48:18Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nIn my previous patches, `parse_pathspec_file()` was tested indirectly by\ninvoking `git reset` and `git commit` with properly crafted inputs. This\nhas some disadvantages:\n1) A number of tests are copy&pasted for every command where\n   `--pathspec-from-file` is supported. With just two commands, it\n   wasn't too bad, but I'm going to extend support to many more\n   commands, which would make a handful of low-value tests.\n2) Tests are located in suboptimal test packages\n3) Tests are indirect\n\nFix this by testing `parse_pathspec_file()` directly via a new test\nhelper.\n\nWhile working on it, I also noticed that quotes testing via\n`\"\\\"file\\\\101.t\\\"\"` was somewhat incorrect: I escaped `\\` one time while\nI had to escape it two times! Tests still worked due to `\"` which\nprevented pathspec from matching files.\n\nFix this by properly escaping one more time.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Makefile                            |  1 +\n t/helper/test-parse-pathspec-file.c | 34 +++++++++++\n t/helper/test-tool.c                |  1 +\n t/helper/test-tool.h                |  1 +\n t/t0067-parse_pathspec_file.sh      | 89 +++++++++++++++++++++++++++\n t/t7107-reset-pathspec-file.sh      | 94 +++--------------------------\n t/t7526-commit-pathspec-file.sh     | 70 +--------------------\n 7 files changed, 136 insertions(+), 154 deletions(-)\n create mode 100644 t/helper/test-parse-pathspec-file.c\n create mode 100755 t/t0067-parse_pathspec_file.sh\n\ndiff --git a/Makefile b/Makefile\nindex b7d7374dac..fd7bcaac9c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -721,6 +721,7 @@ TEST_BUILTINS_OBJS += test-mktemp.o\n TEST_BUILTINS_OBJS += test-oidmap.o\n TEST_BUILTINS_OBJS += test-online-cpus.o\n TEST_BUILTINS_OBJS += test-parse-options.o\n+TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\ndiff --git a/t/helper/test-parse-pathspec-file.c b/t/helper/test-parse-pathspec-file.c\nnew file mode 100644\nindex 0000000000..e7f525feb9\n--- /dev/null\n+++ b/t/helper/test-parse-pathspec-file.c\n@@ -0,0 +1,34 @@\n+#include \"test-tool.h\"\n+#include \"parse-options.h\"\n+#include \"pathspec.h\"\n+#include \"gettext.h\"\n+\n+int cmd__parse_pathspec_file(int argc, const char **argv)\n+{\n+\tstruct pathspec pathspec;\n+\tconst char *pathspec_from_file = 0;\n+\tint pathspec_file_nul = 0, i;\n+\n+\tstatic const char *const usage[] = {\n+\t\t\"test-tool parse-pathspec-file --pathspec-from-file [--pathspec-file-nul]\",\n+\t\tNULL\n+\t};\n+\n+\tstruct option options[] = {\n+\t\tOPT_PATHSPEC_FROM_FILE(&pathspec_from_file),\n+\t\tOPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),\n+\t\tOPT_END()\n+\t};\n+\n+\tparse_options(argc, argv, 0, options, usage, 0);\n+\n+\tparse_pathspec_file(&pathspec, 0, 0, 0, pathspec_from_file,\n+\t\t\t    pathspec_file_nul);\n+\n+\tfor (i = 0; i < pathspec.nr; i++) {\n+\t\tprintf(\"%s\\n\", pathspec.items[i].original);\n+\t}\n+\n+\tclear_pathspec(&pathspec);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f20989d449..c9a232d238 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -39,6 +39,7 @@ static struct test_cmd cmds[] = {\n \t{ \"oidmap\", cmd__oidmap },\n \t{ \"online-cpus\", cmd__online_cpus },\n \t{ \"parse-options\", cmd__parse_options },\n+\t{ \"parse-pathspec-file\", cmd__parse_pathspec_file },\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 8ed2af71d1..c8549fd87f 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -29,6 +29,7 @@ int cmd__mktemp(int argc, const char **argv);\n int cmd__oidmap(int argc, const char **argv);\n int cmd__online_cpus(int argc, const char **argv);\n int cmd__parse_options(int argc, const char **argv);\n+int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\ndiff --git a/t/t0067-parse_pathspec_file.sh b/t/t0067-parse_pathspec_file.sh\nnew file mode 100755\nindex 0000000000..df7b319713\n--- /dev/null\n+++ b/t/t0067-parse_pathspec_file.sh\n@@ -0,0 +1,89 @@\n+#!/bin/sh\n+\n+test_description='Test parse_pathspec_file()'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'one item from stdin' '\n+\techo fileA.t | test-tool parse-pathspec-file --pathspec-from-file=- >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'one item from file' '\n+\techo fileA.t >list &&\n+\ttest-tool parse-pathspec-file --pathspec-from-file=list >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'NUL delimiters' '\n+\tprintf \"fileA.t\\0fileB.t\\0\" | test-tool parse-pathspec-file --pathspec-from-file=- --pathspec-file-nul >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tfileB.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'LF delimiters' '\n+\tprintf \"fileA.t\\nfileB.t\\n\" | test-tool parse-pathspec-file --pathspec-from-file=- >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tfileB.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'no trailing delimiter' '\n+\tprintf \"fileA.t\\nfileB.t\" | test-tool parse-pathspec-file --pathspec-from-file=- >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tfileB.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'CRLF delimiters' '\n+\tprintf \"fileA.t\\r\\nfileB.t\\r\\n\" | test-tool parse-pathspec-file --pathspec-from-file=- >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tfileB.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'quotes' '\n+\t# shell  takes \\\\\\\\101 and spits \\\\101\n+\t# printf takes   \\\\101 and spits  \\101\n+\t# git    takes    \\101 and spits     A\n+\tprintf \"\\\"file\\\\\\\\101.t\\\"\" | test-tool parse-pathspec-file --pathspec-from-file=- >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '--pathspec-file-nul takes quotes literally' '\n+\t# shell  takes \\\\\\\\101 and spits \\\\101\n+\t# printf takes   \\\\101 and spits  \\101\n+\tprintf \"\\\"file\\\\\\\\101.t\\\"\" | test-tool parse-pathspec-file --pathspec-from-file=- --pathspec-file-nul >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\t\"file\\101.t\"\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t7107-reset-pathspec-file.sh b/t/t7107-reset-pathspec-file.sh\nindex 6b1a731fff..f36fce27b9 100755\n--- a/t/t7107-reset-pathspec-file.sh\n+++ b/t/t7107-reset-pathspec-file.sh\n@@ -25,7 +25,7 @@ verify_expect () {\n \ttest_cmp expect actual\n }\n \n-test_expect_success '--pathspec-from-file from stdin' '\n+test_expect_success 'simplest' '\n \trestore_checkpoint &&\n \n \tgit rm fileA.t &&\n@@ -37,20 +37,7 @@ test_expect_success '--pathspec-from-file from stdin' '\n \tverify_expect\n '\n \n-test_expect_success '--pathspec-from-file from file' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t &&\n-\techo fileA.t >list &&\n-\tgit reset --pathspec-from-file=list &&\n-\n-\tcat >expect <<-\\EOF &&\n-\t D fileA.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'NUL delimiters' '\n+test_expect_success '--pathspec-file-nul' '\n \trestore_checkpoint &&\n \n \tgit rm fileA.t fileB.t &&\n@@ -63,71 +50,21 @@ test_expect_success 'NUL delimiters' '\n \tverify_expect\n '\n \n-test_expect_success 'LF delimiters' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t fileB.t &&\n-\tprintf \"fileA.t\\nfileB.t\\n\" | git reset --pathspec-from-file=- &&\n-\n-\tcat >expect <<-\\EOF &&\n-\t D fileA.t\n-\t D fileB.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'no trailing delimiter' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t fileB.t &&\n-\tprintf \"fileA.t\\nfileB.t\" | git reset --pathspec-from-file=- &&\n-\n-\tcat >expect <<-\\EOF &&\n-\t D fileA.t\n-\t D fileB.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'CRLF delimiters' '\n+test_expect_success 'only touches what was listed' '\n \trestore_checkpoint &&\n \n-\tgit rm fileA.t fileB.t &&\n-\tprintf \"fileA.t\\r\\nfileB.t\\r\\n\" | git reset --pathspec-from-file=- &&\n+\tgit rm fileA.t fileB.t fileC.t fileD.t &&\n+\tprintf \"fileB.t\\nfileC.t\\n\" | git reset --pathspec-from-file=- &&\n \n \tcat >expect <<-\\EOF &&\n-\t D fileA.t\n+\tD  fileA.t\n \t D fileB.t\n+\t D fileC.t\n+\tD  fileD.t\n \tEOF\n \tverify_expect\n '\n \n-test_expect_success 'quotes' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t &&\n-\tprintf \"\\\"file\\\\101.t\\\"\" | git reset --pathspec-from-file=- &&\n-\n-\tcat >expect <<-\\EOF &&\n-\t D fileA.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'quotes not compatible with --pathspec-file-nul' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t &&\n-\tprintf \"\\\"file\\\\101.t\\\"\" >list &&\n-\t# Note: \"git reset\" has not yet learned to fail on wrong pathspecs\n-\tgit reset --pathspec-from-file=list --pathspec-file-nul &&\n-\n-\tcat >expect <<-\\EOF &&\n-\t D fileA.t\n-\tEOF\n-\ttest_must_fail verify_expect\n-'\n-\n test_expect_success '--pathspec-from-file is not compatible with --soft or --hard' '\n \trestore_checkpoint &&\n \n@@ -137,19 +74,4 @@ test_expect_success '--pathspec-from-file is not compatible with --soft or --har\n \ttest_must_fail git reset --hard --pathspec-from-file=list\n '\n \n-test_expect_success 'only touches what was listed' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t fileB.t fileC.t fileD.t &&\n-\tprintf \"fileB.t\\nfileC.t\\n\" | git reset --pathspec-from-file=- &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tD  fileA.t\n-\t D fileB.t\n-\t D fileC.t\n-\tD  fileD.t\n-\tEOF\n-\tverify_expect\n-'\n-\n test_done\ndiff --git a/t/t7526-commit-pathspec-file.sh b/t/t7526-commit-pathspec-file.sh\nindex a06b683534..4e592f7472 100755\n--- a/t/t7526-commit-pathspec-file.sh\n+++ b/t/t7526-commit-pathspec-file.sh\n@@ -26,7 +26,7 @@ verify_expect () {\n \ttest_cmp expect actual\n }\n \n-test_expect_success '--pathspec-from-file from stdin' '\n+test_expect_success 'simplest' '\n \trestore_checkpoint &&\n \n \techo fileA.t | git commit --pathspec-from-file=- -m \"Commit\" &&\n@@ -37,19 +37,7 @@ test_expect_success '--pathspec-from-file from stdin' '\n \tverify_expect\n '\n \n-test_expect_success '--pathspec-from-file from file' '\n-\trestore_checkpoint &&\n-\n-\techo fileA.t >list &&\n-\tgit commit --pathspec-from-file=list -m \"Commit\" &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tA\tfileA.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'NUL delimiters' '\n+test_expect_success '--pathspec-file-nul' '\n \trestore_checkpoint &&\n \n \tprintf \"fileA.t\\0fileB.t\\0\" | git commit --pathspec-from-file=- --pathspec-file-nul -m \"Commit\" &&\n@@ -61,60 +49,6 @@ test_expect_success 'NUL delimiters' '\n \tverify_expect\n '\n \n-test_expect_success 'LF delimiters' '\n-\trestore_checkpoint &&\n-\n-\tprintf \"fileA.t\\nfileB.t\\n\" | git commit --pathspec-from-file=- -m \"Commit\" &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tA\tfileA.t\n-\tA\tfileB.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'no trailing delimiter' '\n-\trestore_checkpoint &&\n-\n-\tprintf \"fileA.t\\nfileB.t\" | git commit --pathspec-from-file=- -m \"Commit\" &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tA\tfileA.t\n-\tA\tfileB.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'CRLF delimiters' '\n-\trestore_checkpoint &&\n-\n-\tprintf \"fileA.t\\r\\nfileB.t\\r\\n\" | git commit --pathspec-from-file=- -m \"Commit\" &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tA\tfileA.t\n-\tA\tfileB.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'quotes' '\n-\trestore_checkpoint &&\n-\n-\tprintf \"\\\"file\\\\101.t\\\"\" | git commit --pathspec-from-file=- -m \"Commit\" &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tA\tfileA.t\n-\tEOF\n-\tverify_expect expect\n-'\n-\n-test_expect_success 'quotes not compatible with --pathspec-file-nul' '\n-\trestore_checkpoint &&\n-\n-\tprintf \"\\\"file\\\\101.t\\\"\" >list &&\n-\ttest_must_fail git commit --pathspec-from-file=list --pathspec-file-nul -m \"Commit\"\n-'\n-\n test_expect_success 'only touches what was listed' '\n \trestore_checkpoint &&\n \n-- \ngitgitgadget\n\n"},{"id":"388268","messageId":"db6e40d0042d01cc66b2d0354b52af211e6b5df9.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 09/18] doc: checkout: synchronize <pathspec> description","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:47:57Z","receivedAt":"2019-12-16T15:48:20Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\n`git add` shows an example of good writing, follow it.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-checkout.txt | 24 +++++++++++++++---------\n 1 file changed, 15 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex d47046e050..93124f3ad9 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -12,13 +12,13 @@ SYNOPSIS\n 'git checkout' [-q] [-f] [-m] --detach [<branch>]\n 'git checkout' [-q] [-f] [-m] [--detach] <commit>\n 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]\n-'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...\n-'git checkout' (-p|--patch) [<tree-ish>] [--] [<paths>...]\n+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...\n+'git checkout' (-p|--patch) [<tree-ish>] [--] [<pathspec>...]\n \n DESCRIPTION\n -----------\n Updates files in the working tree to match the version in the index\n-or the specified tree.  If no paths are given, 'git checkout' will\n+or the specified tree.  If no pathspec was given, 'git checkout' will\n also update `HEAD` to set the specified branch as the current\n branch.\n \n@@ -78,13 +78,13 @@ be used to detach `HEAD` at the tip of the branch (`git checkout\n +\n Omitting `<branch>` detaches `HEAD` at the tip of the current branch.\n \n-'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...::\n+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...::\n \n-\tOverwrite paths in the working tree by replacing with the\n-\tcontents in the index or in the `<tree-ish>` (most often a\n-\tcommit).  When a `<tree-ish>` is given, the paths that\n-\tmatch the `<pathspec>` are updated both in the index and in\n-\tthe working tree.\n+\tOverwrite the contents of the files that match the pathspec.\n+\tWhen the `<tree-ish>` (most often a commit) is not given, \n+\toverwrite working tree with the contents in the index.\n+\tWhen the `<tree-ish>` is given, overwrite both the index and\n+\tthe working tree with the contents at the `<tree-ish>`.\n +\n The index may contain unmerged entries because of a previous failed merge.\n By default, if you try to check out such an entry from the index, the\n@@ -336,7 +336,13 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.\n \tTree to checkout from (when paths are given). If not specified,\n \tthe index will be used.\n \n+\\--::\n+\tDo not interpret any more arguments as options.\n \n+<pathspec>...::\n+\tLimits the paths affected by the operation.\n++\n+For more details, see the 'pathspec' entry in linkgit:gitglossary[7].\n \n DETACHED HEAD\n -------------\n-- \ngitgitgadget\n\n"},{"id":"388269","messageId":"85f7ccc4e07294d8107b2f1ddd19b9b1c2a65708.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 08/18] doc: checkout: fix broken text reference","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:47:56Z","receivedAt":"2019-12-16T15:48:21Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-checkout.txt | 10 ++++------\n 1 file changed, 4 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 2011fdbb1d..d47046e050 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -95,12 +95,10 @@ using `--ours` or `--theirs`.  With `-m`, changes made to the working tree\n file can be discarded to re-create the original conflicted merge result.\n \n 'git checkout' (-p|--patch) [<tree-ish>] [--] [<pathspec>...]::\n-\tThis is similar to the \"check out paths to the working tree\n-\tfrom either the index or from a tree-ish\" mode described\n-\tabove, but lets you use the interactive interface to show\n-\tthe \"diff\" output and choose which hunks to use in the\n-\tresult.  See below for the description of `--patch` option.\n-\n+\tThis is similar to the previous mode, but lets you use the\n+\tinteractive interface to show the \"diff\" output and choose which\n+\thunks to use in the result.  See below for the description of\n+\t`--patch` option.\n \n OPTIONS\n -------\n-- \ngitgitgadget\n\n"},{"id":"388270","messageId":"24988252302780577b27e636237319724848a47a.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 13/18] parse_branchname_arg(): easier to understand variables","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:48:01Z","receivedAt":"2019-12-16T15:48:24Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\n`dash_dash_pos` was only calculated under `opts->accept_pathspec`. This\nis unexpected to readers and made it harder to reason about the code.\nFix this by restoring the expected meaning.\n\nSimplify the code by dropping `argcount` and useless `argc` / `argv`\nmanipulations.\n\nThis should not change behavior in any way.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c | 34 ++++++++++++++--------------------\n 1 file changed, 14 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b847695d2b..f35fe2cc26 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1152,7 +1152,6 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t\t\tstruct object_id *rev)\n {\n \tconst char **new_branch = &opts->new_branch;\n-\tint argcount = 0;\n \tconst char *arg;\n \tint dash_dash_pos;\n \tint has_dash_dash = 0;\n@@ -1211,17 +1210,21 @@ static int parse_branchname_arg(int argc, const char **argv,\n \targ = argv[0];\n \tdash_dash_pos = -1;\n \tfor (i = 0; i < argc; i++) {\n-\t\tif (opts->accept_pathspec && !strcmp(argv[i], \"--\")) {\n+\t\tif (!strcmp(argv[i], \"--\")) {\n \t\t\tdash_dash_pos = i;\n \t\t\tbreak;\n \t\t}\n \t}\n-\tif (dash_dash_pos == 0)\n-\t\treturn 1; /* case (2) */\n-\telse if (dash_dash_pos == 1)\n-\t\thas_dash_dash = 1; /* case (3) or (1) */\n-\telse if (dash_dash_pos >= 2)\n-\t\tdie(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n+\n+\tif (opts->accept_pathspec) {\n+\t    if (dash_dash_pos == 0)\n+\t\t    return 1; /* case (2) */\n+\t    else if (dash_dash_pos == 1)\n+\t\t    has_dash_dash = 1; /* case (3) or (1) */\n+\t    else if (dash_dash_pos >= 2)\n+\t\t    die(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n+\t}\n+\n \topts->count_checkout_paths = !opts->quiet && !has_dash_dash;\n \n \tif (!strcmp(arg, \"-\"))\n@@ -1268,15 +1271,10 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\tif (!recover_with_dwim) {\n \t\t\tif (has_dash_dash)\n \t\t\t\tdie(_(\"invalid reference: %s\"), arg);\n-\t\t\treturn argcount;\n+\t\t\treturn 0;\n \t\t}\n \t}\n \n-\t/* we can't end up being in (2) anymore, eat the argument */\n-\targcount++;\n-\targv++;\n-\targc--;\n-\n \tsetup_new_branch_info_and_source_tree(new_branch_info, opts, rev, arg);\n \n \tif (!opts->source_tree)                   /* case (1): want a tree */\n@@ -1289,15 +1287,11 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t * even if there happen to be a file called 'branch';\n \t\t * it would be extremely annoying.\n \t\t */\n-\t\tif (argc)\n+\t\tif (argc > 1)\n \t\t\tverify_non_filename(opts->prefix, arg);\n-\t} else if (opts->accept_pathspec) {\n-\t\targcount++;\n-\t\targv++;\n-\t\targc--;\n \t}\n \n-\treturn argcount;\n+\treturn (dash_dash_pos == 1) ? 2 : 1;\n }\n \n static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n-- \ngitgitgadget\n\n"},{"id":"388271","messageId":"2350dc282e15f42afaf582cbf1551d72aa17da05.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 14/18] parse_branchname_arg(): introduce expect_commit_only","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:48:02Z","receivedAt":"2019-12-16T15:48:24Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\n`has_dash_dash` unexpectedly takes `opts->accept_pathspec` into account.\nWhile this may sound clever at first sight, it becomes pretty hard to\nreason (and not be a victim) about code, especially in combination with\n`argc` here:\n\n\tif (!(argc == 1 && !has_dash_dash) &&\n\t    !(argc == 2 && has_dash_dash) &&\n\t    opts->accept_pathspec)\n\t\trecover_with_dwim = 0;\n\nIntroduce a new non-obfuscated variable to reduce the amount of diffs in\nnext patch.\n\nThis should not change behavior in any way.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c | 15 +++++++++------\n 1 file changed, 9 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex f35fe2cc26..bd0efa9140 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1154,7 +1154,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \tconst char **new_branch = &opts->new_branch;\n \tconst char *arg;\n \tint dash_dash_pos;\n-\tint has_dash_dash = 0;\n+\tint has_dash_dash = 0, expect_commit_only = 0;\n \tint i;\n \n \t/*\n@@ -1225,7 +1225,10 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t    die(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n \t}\n \n-\topts->count_checkout_paths = !opts->quiet && !has_dash_dash;\n+\tif (has_dash_dash)\n+\t    expect_commit_only = 1;\n+\n+\topts->count_checkout_paths = !opts->quiet && !expect_commit_only;\n \n \tif (!strcmp(arg, \"-\"))\n \t\targ = \"@{-1}\";\n@@ -1241,10 +1244,10 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t */\n \t\tint recover_with_dwim = dwim_new_local_branch_ok;\n \n-\t\tint could_be_checkout_paths = !has_dash_dash &&\n+\t\tint could_be_checkout_paths = !expect_commit_only &&\n \t\t\tcheck_filename(opts->prefix, arg);\n \n-\t\tif (!has_dash_dash && !no_wildcard(arg))\n+\t\tif (!expect_commit_only && !no_wildcard(arg))\n \t\t\trecover_with_dwim = 0;\n \n \t\t/*\n@@ -1269,7 +1272,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t}\n \n \t\tif (!recover_with_dwim) {\n-\t\t\tif (has_dash_dash)\n+\t\t\tif (expect_commit_only)\n \t\t\t\tdie(_(\"invalid reference: %s\"), arg);\n \t\t\treturn 0;\n \t\t}\n@@ -1280,7 +1283,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \tif (!opts->source_tree)                   /* case (1): want a tree */\n \t\tdie(_(\"reference is not a tree: %s\"), arg);\n \n-\tif (!has_dash_dash) {\t/* case (3).(d) -> (1) */\n+\tif (!expect_commit_only) {\t/* case (3).(d) -> (1) */\n \t\t/*\n \t\t * Do not complain the most common case\n \t\t *\tgit checkout branch\n-- \ngitgitgadget\n\n"},{"id":"388272","messageId":"542eb709cac341e038ef04f0107e3d2dbaddf97a.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 17/18] t2024: cover more cases","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:48:05Z","receivedAt":"2019-12-16T15:48:25Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nAfter working on `parse_branchname_arg()` I think that these cases are\nworth testing.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n t/t2024-checkout-dwim.sh | 27 +++++++++++++++++++++++++++\n 1 file changed, 27 insertions(+)\n\ndiff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh\nindex c35d67b697..fd993bf45d 100755\n--- a/t/t2024-checkout-dwim.sh\n+++ b/t/t2024-checkout-dwim.sh\n@@ -156,6 +156,33 @@ test_expect_success 'checkout of branch from a single remote succeeds #2' '\n \ttest_branch_upstream baz repo_b baz\n '\n \n+test_expect_success 'checkout of branch from a single remote succeeds with --' '\n+\tgit checkout -B master &&\n+\ttest_might_fail git branch -D baz &&\n+\n+\tgit checkout baz -- &&\n+\tstatus_uno_is_clean &&\n+\ttest_branch baz &&\n+\ttest_cmp_rev remotes/other_b/baz HEAD &&\n+\ttest_branch_upstream baz repo_b baz\n+'\n+\n+test_expect_success 'dont DWIM with pathspec #1' '\n+\tgit checkout -B master &&\n+\ttest_might_fail git branch -D baz &&\n+\n+\ttest_must_fail git checkout baz nonExistingFile 2>err &&\n+\ttest_i18ngrep \"did not match any file(s) known to git\" err\n+'\n+\n+test_expect_success 'dont DWIM with pathspec #2' '\n+\tgit checkout -B master &&\n+\ttest_might_fail git branch -D baz &&\n+\n+\ttest_must_fail git checkout baz -- nonExistingFile 2>err &&\n+\ttest_i18ngrep \"fatal: invalid reference: baz\" err\n+'\n+\n test_expect_success '--no-guess suppresses branch auto-vivification' '\n \tgit checkout -B master &&\n \tstatus_uno_is_clean &&\n-- \ngitgitgadget\n\n"},{"id":"388273","messageId":"46f676b8e09a2bfc17e2d3ef6e2202d438284fd7.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 15/18] parse_branchname_arg(): update code comments","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:48:03Z","receivedAt":"2019-12-16T15:48:26Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nThese parts repeat git documentation:\n    ... if <something> is A...B <...>\n    ... remote named in checkout.defaultRemote ...\n\nSome parts repeat the code below. With next patch, code will be easier\nto understand, so this is no longer needed.\n\nThis is a separate patch to reduce the amount of diffs in next patch.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c | 86 +++++++++++-----------------------------------\n 1 file changed, 21 insertions(+), 65 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex bd0efa9140..6072f7cef7 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1158,45 +1158,21 @@ static int parse_branchname_arg(int argc, const char **argv,\n \tint i;\n \n \t/*\n-\t * case 1: git checkout <ref> -- [<paths>]\n-\t *\n-\t *   <ref> must be a valid tree, everything after the '--' must be\n-\t *   a path.\n-\t *\n-\t * case 2: git checkout -- [<paths>]\n-\t *\n-\t *   everything after the '--' must be paths.\n-\t *\n-\t * case 3: git checkout <something> [--]\n-\t *\n-\t *   (a) If <something> is a commit, that is to\n-\t *       switch to the branch or detach HEAD at it.  As a special case,\n-\t *       if <something> is A...B (missing A or B means HEAD but you can\n-\t *       omit at most one side), and if there is a unique merge base\n-\t *       between A and B, A...B names that merge base.\n-\t *\n-\t *   (b) If <something> is _not_ a commit, either \"--\" is present\n-\t *       or <something> is not a path, no -t or -b was given, and\n-\t *       and there is a tracking branch whose name is <something>\n-\t *       in one and only one remote (or if the branch exists on the\n-\t *       remote named in checkout.defaultRemote), then this is a\n-\t *       short-hand to fork local <something> from that\n-\t *       remote-tracking branch.\n-\t *\n-\t *   (c) Otherwise, if \"--\" is present, treat it like case (1).\n-\t *\n-\t *   (d) Otherwise :\n-\t *       - if it's a reference, treat it like case (1)\n-\t *       - else if it's a path, treat it like case (2)\n-\t *       - else: fail.\n-\t *\n-\t * case 4: git checkout <something> <paths>\n-\t *\n-\t *   The first argument must not be ambiguous.\n-\t *   - If it's *only* a reference, treat it like case (1).\n-\t *   - If it's only a path, treat it like case (2).\n-\t *   - else: fail.\n-\t *\n+\t * Resolve ambiguity where argv[0] may be <pathspec> or <commit>.\n+\t * High-level approach is:\n+\t * 1) Use various things to reduce ambiguity, examples:\n+\t *    * '--' is present\n+\t *    * command doesn't accept <pathspec>\n+\t *    * additional options like '-b' were given\n+\t * 2) If ambiguous and matches both existing <commit> and existing\n+\t *    file, complain. However, in 1-argument 'git checkout <arg>'\n+\t *    treat as <commit> to avoid annoying users.\n+\t * 3) Otherwise, if it matches some existing <commit>, treat as\n+\t *    <commit>.\n+\t * 4) Otherwise, if it matches a remote branch, and it's considered\n+\t *    reasonable to DWIM to create a local branch from remote branch,\n+\t *    do that and proceed with (2)(3).\n+\t * 5) Otherwise, let caller proceed with <pathspec> interpretation.\n \t */\n \tif (!argc)\n \t\treturn 0;\n@@ -1218,9 +1194,9 @@ static int parse_branchname_arg(int argc, const char **argv,\n \n \tif (opts->accept_pathspec) {\n \t    if (dash_dash_pos == 0)\n-\t\t    return 1; /* case (2) */\n+\t\t    return 1;\n \t    else if (dash_dash_pos == 1)\n-\t\t    has_dash_dash = 1; /* case (3) or (1) */\n+\t\t    has_dash_dash = 1;\n \t    else if (dash_dash_pos >= 2)\n \t\t    die(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n \t}\n@@ -1234,14 +1210,6 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\targ = \"@{-1}\";\n \n \tif (get_oid_mb(arg, rev)) {\n-\t\t/*\n-\t\t * Either case (3) or (4), with <something> not being\n-\t\t * a commit, or an attempt to use case (1) with an\n-\t\t * invalid ref.\n-\t\t *\n-\t\t * It's likely an error, but we need to find out if\n-\t\t * we should auto-create the branch, case (3).(b).\n-\t\t */\n \t\tint recover_with_dwim = dwim_new_local_branch_ok;\n \n \t\tint could_be_checkout_paths = !expect_commit_only &&\n@@ -1250,10 +1218,6 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\tif (!expect_commit_only && !no_wildcard(arg))\n \t\t\trecover_with_dwim = 0;\n \n-\t\t/*\n-\t\t * Accept \"git checkout foo\", \"git checkout foo --\"\n-\t\t * and \"git switch foo\" as candidates for dwim.\n-\t\t */\n \t\tif (!(argc == 1 && !has_dash_dash) &&\n \t\t    !(argc == 2 && has_dash_dash) &&\n \t\t    opts->accept_pathspec)\n@@ -1265,7 +1229,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t\tif (remote) {\n \t\t\t\t*new_branch = arg;\n \t\t\t\targ = remote;\n-\t\t\t\t/* DWIMmed to create local branch, case (3).(b) */\n+\t\t\t\t/* DWIMmed to create local branch */\n \t\t\t} else {\n \t\t\t\trecover_with_dwim = 0;\n \t\t\t}\n@@ -1280,19 +1244,11 @@ static int parse_branchname_arg(int argc, const char **argv,\n \n \tsetup_new_branch_info_and_source_tree(new_branch_info, opts, rev, arg);\n \n-\tif (!opts->source_tree)                   /* case (1): want a tree */\n+\tif (!opts->source_tree)\n \t\tdie(_(\"reference is not a tree: %s\"), arg);\n \n-\tif (!expect_commit_only) {\t/* case (3).(d) -> (1) */\n-\t\t/*\n-\t\t * Do not complain the most common case\n-\t\t *\tgit checkout branch\n-\t\t * even if there happen to be a file called 'branch';\n-\t\t * it would be extremely annoying.\n-\t\t */\n-\t\tif (argc > 1)\n-\t\t\tverify_non_filename(opts->prefix, arg);\n-\t}\n+\tif (!expect_commit_only && argc > 1)\n+\t\tverify_non_filename(opts->prefix, arg);\n \n \treturn (dash_dash_pos == 1) ? 2 : 1;\n }\n-- \ngitgitgadget\n\n"},{"id":"388274","messageId":"c293d7283253859d618205cb0e87b7d4c788153e.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 18/18] checkout, restore: support the --pathspec-from-file option","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:48:06Z","receivedAt":"2019-12-16T15:48:27Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nDecisions taken for simplicity:\n1) For now, `--pathspec-from-file` is declared incompatible with\n   `--patch`, even when <file> is not `stdin`. Such use case it not\n   really expected.\n2) It is not allowed to pass pathspec in both args and file.\n\n`you must specify path(s) to restore` block was moved down to be able to\ntest for `pathspec.nr` instead, because testing for `argc` is no longer\ncorrect.\n\n`git switch` does not support the new options because it doesn't expect\n`<pathspec>` arguments.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-checkout.txt    | 15 +++++\n Documentation/git-restore.txt     | 14 +++++\n builtin/checkout.c                | 33 +++++++++--\n t/t2026-checkout-pathspec-file.sh | 90 ++++++++++++++++++++++++++++++\n t/t2072-restore-pathspec-file.sh  | 91 +++++++++++++++++++++++++++++++\n t/t9902-completion.sh             |  2 +\n 6 files changed, 240 insertions(+), 5 deletions(-)\n create mode 100755 t/t2026-checkout-pathspec-file.sh\n create mode 100755 t/t2072-restore-pathspec-file.sh\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 93124f3ad9..ffe3c1bff2 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n 'git checkout' [-q] [-f] [-m] [--detach] <commit>\n 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]\n 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...\n+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul]\n 'git checkout' (-p|--patch) [<tree-ish>] [--] [<pathspec>...]\n \n DESCRIPTION\n@@ -79,6 +80,7 @@ be used to detach `HEAD` at the tip of the branch (`git checkout\n Omitting `<branch>` detaches `HEAD` at the tip of the current branch.\n \n 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...::\n+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul]::\n \n \tOverwrite the contents of the files that match the pathspec.\n \tWhen the `<tree-ish>` (most often a commit) is not given, \n@@ -306,6 +308,19 @@ Note that this option uses the no overlay mode by default (see also\n \tworking tree, but not in `<tree-ish>` are removed, to make them\n \tmatch `<tree-ish>` exactly.\n \n+--pathspec-from-file=<file>::\n+\tPathspec is passed in `<file>` instead of commandline args. If\n+\t`<file>` is exactly `-` then standard input is used. Pathspec\n+\telements are separated by LF or CR/LF. Pathspec elements can be\n+\tquoted as explained for the configuration variable `core.quotePath`\n+\t(see linkgit:git-config[1]). See also `--pathspec-file-nul` and\n+\tglobal `--literal-pathspecs`.\n+\n+--pathspec-file-nul::\n+\tOnly meaningful with `--pathspec-from-file`. Pathspec elements are\n+\tseparated with NUL character and all other characters are taken\n+\tliterally (including newlines and quotes).\n+\n <branch>::\n \tBranch to checkout; if it refers to a branch (i.e., a name that,\n \twhen prepended with \"refs/heads/\", is a valid ref), then that\ndiff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt\nindex d7bf016bba..5bf60d4943 100644\n--- a/Documentation/git-restore.txt\n+++ b/Documentation/git-restore.txt\n@@ -9,6 +9,7 @@ SYNOPSIS\n --------\n [verse]\n 'git restore' [<options>] [--source=<tree>] [--staged] [--worktree] [--] <pathspec>...\n+'git restore' [<options>] [--source=<tree>] [--staged] [--worktree] --pathspec-from-file=<file> [--pathspec-file-nul]\n 'git restore' (-p|--patch) [<options>] [--source=<tree>] [--staged] [--worktree] [--] [<pathspec>...]\n \n DESCRIPTION\n@@ -113,6 +114,19 @@ in linkgit:git-checkout[1] for details.\n \tappear in the `--source` tree are removed, to make them match\n \t`<tree>` exactly. The default is no-overlay mode.\n \n+--pathspec-from-file=<file>::\n+\tPathspec is passed in `<file>` instead of commandline args. If\n+\t`<file>` is exactly `-` then standard input is used. Pathspec\n+\telements are separated by LF or CR/LF. Pathspec elements can be\n+\tquoted as explained for the configuration variable `core.quotePath`\n+\t(see linkgit:git-config[1]). See also `--pathspec-file-nul` and\n+\tglobal `--literal-pathspecs`.\n+\n+--pathspec-file-nul::\n+\tOnly meaningful with `--pathspec-from-file`. Pathspec elements are\n+\tseparated with NUL character and all other characters are taken\n+\tliterally (including newlines and quotes).\n+\n \\--::\n \tDo not interpret any more arguments as options.\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex aa4ff14ec2..b02425f49b 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -70,6 +70,8 @@ struct checkout_opts {\n \tint checkout_worktree;\n \tconst char *ignore_unmerged_opt;\n \tint ignore_unmerged;\n+\tint pathspec_file_nul;\n+\tconst char *pathspec_from_file;\n \n \tconst char *new_branch;\n \tconst char *new_branch_force;\n@@ -1202,7 +1204,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t * Absence of '--' leaves <pathspec>/<commit> ambiguity.\n \t\t * Try to resolve it with additional knowledge about pathspec args.\n \t\t */\n-\t\texpect_commit_only = !opts->accept_pathspec;\n+\t\texpect_commit_only = !opts->accept_pathspec || opts->pathspec_from_file;\n \t} else if (dash_dash_pos == 0) {\n \t\t/* 'git checkout/switch/restore -- [...]' */\n \t\treturn 1;  /* Eat '--' */\n@@ -1476,6 +1478,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,\n \t\tOPT_BOOL('p', \"patch\", &opts->patch_mode, N_(\"select hunks interactively\")),\n \t\tOPT_BOOL(0, \"ignore-skip-worktree-bits\", &opts->ignore_skipworktree,\n \t\t\t N_(\"do not limit pathspecs to sparse entries only\")),\n+\t\tOPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),\n+\t\tOPT_PATHSPEC_FILE_NUL(&opts->pathspec_file_nul),\n \t\tOPT_END()\n \t};\n \tstruct option *newopts = parse_options_concat(prevopts, options);\n@@ -1612,10 +1616,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t\t\tdie(_(\"reference is not a tree: %s\"), opts->from_treeish);\n \t}\n \n-\tif (opts->accept_pathspec && !opts->empty_pathspec_ok && !argc &&\n-\t    !opts->patch_mode)\t/* patch mode is special */\n-\t\tdie(_(\"you must specify path(s) to restore\"));\n-\n \tif (argc) {\n \t\tparse_pathspec(&opts->pathspec, 0,\n \t\t\t       opts->patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,\n@@ -1635,10 +1635,33 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t\tif (opts->force_detach)\n \t\t\tdie(_(\"git checkout: --detach does not take a path argument '%s'\"),\n \t\t\t    argv[0]);\n+\t}\n+\n+\tif (opts->pathspec_from_file) {\n+\t\tif (opts->pathspec.nr)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with pathspec arguments\"));\n+\n+\t\tif (opts->force_detach)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with --detach\"));\n \n+\t\tif (opts->patch_mode)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with --patch\"));\n+\n+\t\tparse_pathspec_file(&opts->pathspec, 0,\n+\t\t\t\t    0,\n+\t\t\t\t    prefix, opts->pathspec_from_file, opts->pathspec_file_nul);\n+\t} else if (opts->pathspec_file_nul) {\n+\t\tdie(_(\"--pathspec-file-nul requires --pathspec-from-file\"));\n+\t}\n+\n+\tif (opts->pathspec.nr) {\n \t\tif (1 < !!opts->writeout_stage + !!opts->force + !!opts->merge)\n \t\t\tdie(_(\"git checkout: --ours/--theirs, --force and --merge are incompatible when\\n\"\n \t\t\t      \"checking out of the index.\"));\n+\t} else {\n+\t\tif (opts->accept_pathspec && !opts->empty_pathspec_ok &&\n+\t\t    !opts->patch_mode)\t/* patch mode is special */\n+\t\t\tdie(_(\"you must specify path(s) to restore\"));\n \t}\n \n \tif (opts->new_branch) {\ndiff --git a/t/t2026-checkout-pathspec-file.sh b/t/t2026-checkout-pathspec-file.sh\nnew file mode 100755\nindex 0000000000..56220c23b3\n--- /dev/null\n+++ b/t/t2026-checkout-pathspec-file.sh\n@@ -0,0 +1,90 @@\n+#!/bin/sh\n+\n+test_description='checkout --pathspec-from-file'\n+\n+. ./test-lib.sh\n+\n+test_tick\n+\n+test_expect_success setup '\n+\ttest_commit file0 &&\n+\n+\techo 1 >fileA.t &&\n+\techo 1 >fileB.t &&\n+\techo 1 >fileC.t &&\n+\techo 1 >fileD.t &&\n+\tgit add fileA.t fileB.t fileC.t fileD.t &&\n+\tgit commit -m \"files 1\" &&\n+\n+\techo 2 >fileA.t &&\n+\techo 2 >fileB.t &&\n+\techo 2 >fileC.t &&\n+\techo 2 >fileD.t &&\n+\tgit add fileA.t fileB.t fileC.t fileD.t &&\n+\tgit commit -m \"files 2\" &&\n+\n+\tgit tag checkpoint\n+'\n+\n+restore_checkpoint () {\n+\tgit reset --hard checkpoint\n+}\n+\n+verify_expect () {\n+\tgit status --porcelain --untracked-files=no -- fileA.t fileB.t fileC.t fileD.t >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success 'simplest' '\n+\trestore_checkpoint &&\n+\n+\techo fileA.t | git checkout --pathspec-from-file=- HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tM  fileA.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success '--pathspec-file-nul' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileA.t\\0fileB.t\\0\" | git checkout --pathspec-from-file=- --pathspec-file-nul HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tM  fileA.t\n+\tM  fileB.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'only touches what was listed' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileB.t\\nfileC.t\\n\" | git checkout --pathspec-from-file=- HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tM  fileB.t\n+\tM  fileC.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'error conditions' '\n+\trestore_checkpoint &&\n+\techo fileA.t >list &&\n+\n+\ttest_must_fail git checkout --pathspec-from-file=- --detach <list 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-detach\" err &&\n+\n+\ttest_must_fail git checkout --pathspec-from-file=- --patch <list 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-patch\" err &&\n+\n+\ttest_must_fail git checkout --pathspec-from-file=- -- fileA.t <list 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n+\n+\ttest_must_fail git checkout --pathspec-file-nul 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-file-nul requires \\-\\-pathspec-from-file\" err\n+'\n+\n+test_done\ndiff --git a/t/t2072-restore-pathspec-file.sh b/t/t2072-restore-pathspec-file.sh\nnew file mode 100755\nindex 0000000000..522705bbfa\n--- /dev/null\n+++ b/t/t2072-restore-pathspec-file.sh\n@@ -0,0 +1,91 @@\n+#!/bin/sh\n+\n+test_description='restore --pathspec-from-file'\n+\n+. ./test-lib.sh\n+\n+test_tick\n+\n+test_expect_success setup '\n+\ttest_commit file0 &&\n+\n+\techo 1 >fileA.t &&\n+\techo 1 >fileB.t &&\n+\techo 1 >fileC.t &&\n+\techo 1 >fileD.t &&\n+\tgit add fileA.t fileB.t fileC.t fileD.t &&\n+\tgit commit -m \"files 1\" &&\n+\n+\techo 2 >fileA.t &&\n+\techo 2 >fileB.t &&\n+\techo 2 >fileC.t &&\n+\techo 2 >fileD.t &&\n+\tgit add fileA.t fileB.t fileC.t fileD.t &&\n+\tgit commit -m \"files 2\" &&\n+\n+\tgit tag checkpoint\n+'\n+\n+restore_checkpoint () {\n+\tgit reset --hard checkpoint\n+}\n+\n+verify_expect () {\n+\tgit status --porcelain --untracked-files=no -- fileA.t fileB.t fileC.t fileD.t >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success 'simplest' '\n+\trestore_checkpoint &&\n+\n+\techo fileA.t | git restore --pathspec-from-file=- --source=HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\t M fileA.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success '--pathspec-file-nul' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileA.t\\0fileB.t\\0\" | git restore --pathspec-from-file=- --pathspec-file-nul --source=HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\t M fileA.t\n+\t M fileB.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'only touches what was listed' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileB.t\\nfileC.t\\n\" | git restore --pathspec-from-file=- --source=HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\t M fileB.t\n+\t M fileC.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'error conditions' '\n+\trestore_checkpoint &&\n+\techo fileA.t >list &&\n+\t>empty_list &&\n+\n+\ttest_must_fail git restore --pathspec-from-file=- --patch --source=HEAD^1 <list 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-patch\" err &&\n+\n+\ttest_must_fail git restore --pathspec-from-file=- --source=HEAD^1 -- fileA.t <list 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n+\n+\ttest_must_fail git restore --pathspec-file-nul --source=HEAD^1 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-file-nul requires \\-\\-pathspec-from-file\" err &&\n+\n+\ttest_must_fail git restore --pathspec-from-file=- --source=HEAD^1 <empty_list 2>err &&\n+\ttest_i18ngrep \"you must specify path(s) to restore\" err\n+'\n+\n+test_done\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex ec3eccfd3d..93877ba9cd 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -1438,6 +1438,8 @@ test_expect_success 'double dash \"git checkout\"' '\n \t--no-guess Z\n \t--no-... Z\n \t--overlay Z\n+\t--pathspec-file-nul Z\n+\t--pathspec-from-file=Z\n \tEOF\n '\n \n-- \ngitgitgadget\n"},{"id":"388275","messageId":"efd6876874512d9ded6352c979046dffd19a5a57.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 12/18] checkout: die() on ambiguous tracking branches","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:48:00Z","receivedAt":"2019-12-16T15:48:28Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nBefore this patch, when there were multiple DWIM candidates for remote\nbranch, git decided to try the argument as pathspec instead. I believe\nthat such behavior is a surprise: adding another remote suddenly causes\ngit to discard file contents, because it was unsure which branch to\npick. There was an incomplete attempt to prevent that in [3].\n\nI understand that this was never intended:\n\n  [1] introduces the unexpected behavior. Before, there was fallback\n  from not-a-ref to pathspec. This is reasonable DWIM. After, there is\n  another fallback from ambiguous-remote to pathspec. I understand that\n  it was kind of copy&paste oversight.\n\n  [2] noticed the unexpected behavior but chose to semi-document it\n  instead of forbidding, because the goal of the patch series was\n  focused on something else.\n\n  [3] adds `die()` when there is ambiguity between branch and file. The\n  case of multiple tracking branches is seemingly overlooked.\n\nChange to complain about ambiguity instead of doing unexpected things.\n\n[1] Commit 70c9ac2f (\"DWIM \"git checkout frotz\" to \"git checkout -b frotz origin/frotz\"\" 2009-10-18)\n    https://public-inbox.org/git/7vaazpxha4.fsf_-_@alter.siamese.dyndns.org/\n[2] Commit ad8d5104 (\"checkout: add advice for ambiguous \"checkout <branch>\"\", 2018-06-05)\n    https://public-inbox.org/git/20180502105452.17583-1-avarab@gmail.com/\n[3] Commit be4908f1 (\"checkout: disambiguate dwim tracking branches and local files\", 2018-11-13)\n    https://public-inbox.org/git/20181110120707.25846-1-pclouds@gmail.com/\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c       | 56 ++++++++++++++++++----------------------\n t/t2024-checkout-dwim.sh | 28 ++++++++++++++++++--\n 2 files changed, 51 insertions(+), 33 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex e1b9df1543..b847695d2b 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1115,10 +1115,10 @@ static void setup_new_branch_info_and_source_tree(\n \n static const char *parse_remote_branch(const char *arg,\n \t\t\t\t       struct object_id *rev,\n-\t\t\t\t       int could_be_checkout_paths,\n-\t\t\t\t       int *dwim_remotes_matched)\n+\t\t\t\t       int could_be_checkout_paths)\n {\n-\tconst char *remote = unique_tracking_name(arg, rev, dwim_remotes_matched);\n+\tint num_matches = 0;\n+\tconst char *remote = unique_tracking_name(arg, rev, &num_matches);\n \t\n \tif (remote && could_be_checkout_paths) {\n \t\tdie(_(\"'%s' could be both a local file and a tracking branch.\\n\"\n@@ -1126,6 +1126,22 @@ static const char *parse_remote_branch(const char *arg,\n \t\t    arg);\n \t}\n \n+\tif (!remote && num_matches > 1) {\n+\t    if (advice_checkout_ambiguous_remote_branch_name) {\n+\t\t    advise(_(\"If you meant to check out a remote tracking branch on, e.g. 'origin',\\n\"\n+\t\t\t     \"you can do so by fully qualifying the name with the --track option:\\n\"\n+\t\t\t     \"\\n\"\n+\t\t\t     \"    git checkout --track origin/<name>\\n\"\n+\t\t\t     \"\\n\"\n+\t\t\t     \"If you'd like to always have checkouts of an ambiguous <name> prefer\\n\"\n+\t\t\t     \"one remote, e.g. the 'origin' remote, consider setting\\n\"\n+\t\t\t     \"checkout.defaultRemote=origin in your config.\"));\n+\t    }\n+\n+\t    die(_(\"'%s' matched multiple (%d) remote tracking branches\"),\n+\t\targ, num_matches);\n+\t}\n+\n \treturn remote;\n }\n \n@@ -1133,8 +1149,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t\t\tint dwim_new_local_branch_ok,\n \t\t\t\tstruct branch_info *new_branch_info,\n \t\t\t\tstruct checkout_opts *opts,\n-\t\t\t\tstruct object_id *rev,\n-\t\t\t\tint *dwim_remotes_matched)\n+\t\t\t\tstruct object_id *rev)\n {\n \tconst char **new_branch = &opts->new_branch;\n \tint argcount = 0;\n@@ -1240,8 +1255,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \n \t\tif (recover_with_dwim) {\n \t\t\tconst char *remote = parse_remote_branch(arg, rev,\n-\t\t\t\t\t\t\t\t could_be_checkout_paths,\n-\t\t\t\t\t\t\t\t dwim_remotes_matched);\n+\t\t\t\t\t\t\t\t could_be_checkout_paths);\n \t\t\tif (remote) {\n \t\t\t\t*new_branch = arg;\n \t\t\t\targ = remote;\n@@ -1505,7 +1519,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t\t\t const char * const usagestr[])\n {\n \tstruct branch_info new_branch_info;\n-\tint dwim_remotes_matched = 0;\n \tint parseopt_flags = 0;\n \n \tmemset(&new_branch_info, 0, sizeof(new_branch_info));\n@@ -1613,8 +1626,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t\t\topts->track == BRANCH_TRACK_UNSPECIFIED &&\n \t\t\t!opts->new_branch;\n \t\tint n = parse_branchname_arg(argc, argv, dwim_ok,\n-\t\t\t\t\t     &new_branch_info, opts, &rev,\n-\t\t\t\t\t     &dwim_remotes_matched);\n+\t\t\t\t\t     &new_branch_info, opts, &rev);\n \t\targv += n;\n \t\targc -= n;\n \t} else if (!opts->accept_ref && opts->from_treeish) {\n@@ -1672,28 +1684,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t}\n \n \tUNLEAK(opts);\n-\tif (opts->patch_mode || opts->pathspec.nr) {\n-\t\tint ret = checkout_paths(opts, new_branch_info.name);\n-\t\tif (ret && dwim_remotes_matched > 1 &&\n-\t\t    advice_checkout_ambiguous_remote_branch_name)\n-\t\t\tadvise(_(\"'%s' matched more than one remote tracking branch.\\n\"\n-\t\t\t\t \"We found %d remotes with a reference that matched. So we fell back\\n\"\n-\t\t\t\t \"on trying to resolve the argument as a path, but failed there too!\\n\"\n-\t\t\t\t \"\\n\"\n-\t\t\t\t \"If you meant to check out a remote tracking branch on, e.g. 'origin',\\n\"\n-\t\t\t\t \"you can do so by fully qualifying the name with the --track option:\\n\"\n-\t\t\t\t \"\\n\"\n-\t\t\t\t \"    git checkout --track origin/<name>\\n\"\n-\t\t\t\t \"\\n\"\n-\t\t\t\t \"If you'd like to always have checkouts of an ambiguous <name> prefer\\n\"\n-\t\t\t\t \"one remote, e.g. the 'origin' remote, consider setting\\n\"\n-\t\t\t\t \"checkout.defaultRemote=origin in your config.\"),\n-\t\t\t       argv[0],\n-\t\t\t       dwim_remotes_matched);\n-\t\treturn ret;\n-\t} else {\n+\tif (opts->patch_mode || opts->pathspec.nr)\n+\t\treturn checkout_paths(opts, new_branch_info.name);\n+\telse\n \t\treturn checkout_branch(opts, &new_branch_info);\n-\t}\n }\n \n int cmd_checkout(int argc, const char **argv, const char *prefix)\ndiff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh\nindex fa0718c730..c35d67b697 100755\n--- a/t/t2024-checkout-dwim.sh\n+++ b/t/t2024-checkout-dwim.sh\n@@ -37,7 +37,9 @@ test_expect_success 'setup' '\n \t\tgit checkout -b foo &&\n \t\ttest_commit a_foo &&\n \t\tgit checkout -b bar &&\n-\t\ttest_commit a_bar\n+\t\ttest_commit a_bar &&\n+\t\tgit checkout -b ambiguous_branch_and_file &&\n+\t\ttest_commit a_ambiguous_branch_and_file\n \t) &&\n \tgit init repo_b &&\n \t(\n@@ -46,7 +48,9 @@ test_expect_success 'setup' '\n \t\tgit checkout -b foo &&\n \t\ttest_commit b_foo &&\n \t\tgit checkout -b baz &&\n-\t\ttest_commit b_baz\n+\t\ttest_commit b_baz &&\n+\t\tgit checkout -b ambiguous_branch_and_file &&\n+\t\ttest_commit b_ambiguous_branch_and_file\n \t) &&\n \tgit remote add repo_a repo_a &&\n \tgit remote add repo_b repo_b &&\n@@ -75,6 +79,26 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' '\n \ttest_branch master\n '\n \n+test_expect_success 'when arg matches multiple remotes, do not fallback to interpreting as pathspec' '\n+\t# create a file with name matching remote branch name\n+\tgit checkout -b t_ambiguous_branch_and_file &&\n+\t>ambiguous_branch_and_file &&\n+\tgit add ambiguous_branch_and_file &&\n+\tgit commit -m \"ambiguous_branch_and_file\" &&\n+\n+\t# modify file to verify that it will not be touched by checkout\n+\ttest_when_finished \"git checkout -- ambiguous_branch_and_file\" &&\n+\techo \"file contents\" >ambiguous_branch_and_file &&\n+\tcp ambiguous_branch_and_file expect &&\n+\n+\ttest_must_fail git checkout ambiguous_branch_and_file 2>err &&\n+\n+\ttest_i18ngrep \"matched multiple (2) remote tracking branches\" err &&\n+\t\n+\t# file must not be altered\n+\ttest_cmp expect ambiguous_branch_and_file\n+'\n+\n test_expect_success 'checkout of branch from multiple remotes fails with advice' '\n \tgit checkout -B master &&\n \ttest_might_fail git branch -D foo &&\n-- \ngitgitgadget\n\n"},{"id":"388277","messageId":"c88cbf453adfa9184e1fcb8fdcbac7d9a1443283.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 10/18] doc: restore: synchronize <pathspec> description","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:47:58Z","receivedAt":"2019-12-16T15:48:30Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\n`git add` shows an example of good writing, follow it.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-restore.txt | 12 ++++++++++--\n 1 file changed, 10 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt\nindex 1ab2e40ea9..d7bf016bba 100644\n--- a/Documentation/git-restore.txt\n+++ b/Documentation/git-restore.txt\n@@ -8,8 +8,8 @@ git-restore - Restore working tree files\n SYNOPSIS\n --------\n [verse]\n-'git restore' [<options>] [--source=<tree>] [--staged] [--worktree] <pathspec>...\n-'git restore' (-p|--patch) [<options>] [--source=<tree>] [--staged] [--worktree] [<pathspec>...]\n+'git restore' [<options>] [--source=<tree>] [--staged] [--worktree] [--] <pathspec>...\n+'git restore' (-p|--patch) [<options>] [--source=<tree>] [--staged] [--worktree] [--] [<pathspec>...]\n \n DESCRIPTION\n -----------\n@@ -113,6 +113,14 @@ in linkgit:git-checkout[1] for details.\n \tappear in the `--source` tree are removed, to make them match\n \t`<tree>` exactly. The default is no-overlay mode.\n \n+\\--::\n+\tDo not interpret any more arguments as options.\n+\n+<pathspec>...::\n+\tLimits the paths affected by the operation.\n++\n+For more details, see the 'pathspec' entry in linkgit:gitglossary[7].\n+\n EXAMPLES\n --------\n \n-- \ngitgitgadget\n\n"},{"id":"388276","messageId":"2c23bd602d3bcc88a9671aab1659b327fc056854.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 11/18] parse_branchname_arg(): extract part as new function","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:47:59Z","receivedAt":"2019-12-16T15:48:31Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nThis is done for the next commit to avoid crazy 7x tab code padding.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c | 25 +++++++++++++++++++------\n 1 file changed, 19 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 3634a3dac1..e1b9df1543 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1113,6 +1113,22 @@ static void setup_new_branch_info_and_source_tree(\n \t}\n }\n \n+static const char *parse_remote_branch(const char *arg,\n+\t\t\t\t       struct object_id *rev,\n+\t\t\t\t       int could_be_checkout_paths,\n+\t\t\t\t       int *dwim_remotes_matched)\n+{\n+\tconst char *remote = unique_tracking_name(arg, rev, dwim_remotes_matched);\n+\t\n+\tif (remote && could_be_checkout_paths) {\n+\t\tdie(_(\"'%s' could be both a local file and a tracking branch.\\n\"\n+\t\t\t\"Please use -- (and optionally --no-guess) to disambiguate\"),\n+\t\t    arg);\n+\t}\n+\n+\treturn remote;\n+}\n+\n static int parse_branchname_arg(int argc, const char **argv,\n \t\t\t\tint dwim_new_local_branch_ok,\n \t\t\t\tstruct branch_info *new_branch_info,\n@@ -1223,13 +1239,10 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t\trecover_with_dwim = 0;\n \n \t\tif (recover_with_dwim) {\n-\t\t\tconst char *remote = unique_tracking_name(arg, rev,\n-\t\t\t\t\t\t\t\t  dwim_remotes_matched);\n+\t\t\tconst char *remote = parse_remote_branch(arg, rev,\n+\t\t\t\t\t\t\t\t could_be_checkout_paths,\n+\t\t\t\t\t\t\t\t dwim_remotes_matched);\n \t\t\tif (remote) {\n-\t\t\t\tif (could_be_checkout_paths)\n-\t\t\t\t\tdie(_(\"'%s' could be both a local file and a tracking branch.\\n\"\n-\t\t\t\t\t      \"Please use -- (and optionally --no-guess) to disambiguate\"),\n-\t\t\t\t\t    arg);\n \t\t\t\t*new_branch = arg;\n \t\t\t\targ = remote;\n \t\t\t\t/* DWIMmed to create local branch, case (3).(b) */\n-- \ngitgitgadget\n\n"},{"id":"388279","messageId":"3d0fcf6ba5afce860c6ecec4ca7d3a21ba49a3a6.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 07/18] doc: checkout: remove duplicate synopsis","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:47:55Z","receivedAt":"2019-12-16T15:48:31Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nIt was added in [1]. I understand that the duplicate change was not\nintentional and comes from an oversight.\n\nAlso, in explanation, there was only one section for two synopsis\nentries.\n\nFix both problems by removing duplicate synopsis.\n\n<paths> vs <pathspec> is resolved in next patch.\n\n[1] Commit b59698ae (\"checkout doc: clarify command line args for \"checkout paths\" mode\" 2017-10-11)\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-checkout.txt | 3 +--\n 1 file changed, 1 insertion(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex cf3cac0a2b..2011fdbb1d 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -13,7 +13,6 @@ SYNOPSIS\n 'git checkout' [-q] [-f] [-m] [--detach] <commit>\n 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]\n 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...\n-'git checkout' [<tree-ish>] [--] <pathspec>...\n 'git checkout' (-p|--patch) [<tree-ish>] [--] [<paths>...]\n \n DESCRIPTION\n@@ -79,7 +78,7 @@ be used to detach `HEAD` at the tip of the branch (`git checkout\n +\n Omitting `<branch>` detaches `HEAD` at the tip of the current branch.\n \n-'git checkout' [<tree-ish>] [--] <pathspec>...::\n+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...::\n \n \tOverwrite paths in the working tree by replacing with the\n \tcontents in the index or in the `<tree-ish>` (most often a\n-- \ngitgitgadget\n\n"},{"id":"388278","messageId":"319151e4e978298ae2a5b8321207ef53a60689d8.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 16/18] parse_branchname_arg(): refactor the decision making","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:48:04Z","receivedAt":"2019-12-16T15:48:32Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nMake it easier to understand which branches handle which cases.\n\nDrop obfuscated variable `has_dash_dash` which also took\n`opts->accept_pathspec` into account, making it pretty hard to reason\nabout code, especially when used together with `argc` and\n`opts->accept_pathspec` here:\n\n\tif (!(argc == 1 && !has_dash_dash) &&\n\t\t!(argc == 2 && has_dash_dash) &&\n\t\topts->accept_pathspec)\n\t\trecover_with_dwim = 0;\n\nAvoid double-negation in the code mentioned above (\"it is not OK to\nproceed if it's not one of those cases\").\n\nAvoid hard-to-understand condition `opts->accept_pathspec` in the code\nmentioned above.\n\nThere are some minor die() message changes for:\n`git switch <commit> <unexpected>`\n`git switch <commit> -- <unexpected>`\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c | 74 ++++++++++++++++++++++++++++------------------\n 1 file changed, 45 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 6072f7cef7..aa4ff14ec2 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1153,9 +1153,8 @@ static int parse_branchname_arg(int argc, const char **argv,\n {\n \tconst char **new_branch = &opts->new_branch;\n \tconst char *arg;\n-\tint dash_dash_pos;\n-\tint has_dash_dash = 0, expect_commit_only = 0;\n-\tint i;\n+\tint dash_dash_pos, i;\n+\tint recover_with_dwim, expect_commit_only;\n \n \t/*\n \t * Resolve ambiguity where argv[0] may be <pathspec> or <commit>.\n@@ -1174,15 +1173,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t *    do that and proceed with (2)(3).\n \t * 5) Otherwise, let caller proceed with <pathspec> interpretation.\n \t */\n-\tif (!argc)\n-\t\treturn 0;\n-\n-\tif (!opts->accept_pathspec) {\n-\t\tif (argc > 1)\n-\t\t\tdie(_(\"only one reference expected\"));\n-\t\thas_dash_dash = 1; /* helps disambiguate */\n-\t}\n-\n+\t \n \targ = argv[0];\n \tdash_dash_pos = -1;\n \tfor (i = 0; i < argc; i++) {\n@@ -1192,17 +1183,49 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t}\n \t}\n \n-\tif (opts->accept_pathspec) {\n-\t    if (dash_dash_pos == 0)\n-\t\t    return 1;\n-\t    else if (dash_dash_pos == 1)\n-\t\t    has_dash_dash = 1;\n-\t    else if (dash_dash_pos >= 2)\n-\t\t    die(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n-\t}\n+\tif (dash_dash_pos == -1) {\n+\t\tif (argc == 0) {\n+\t\t\t/* 'git checkout/switch/restore' */\n+\t\t\treturn 0;\n+\t\t} else if (argc == 1) {\n+\t\t\t/* 'git checkout/switch/restore <something>' */\n+\t\t\trecover_with_dwim = dwim_new_local_branch_ok;\n+\t\t} else if (!opts->accept_pathspec) {\n+\t\t\t/* 'git switch <commit> <unexpected> [...]' */\n+\t\t\tdie(_(\"only one reference expected, %d given.\"), argc);\n+\t\t} else {\n+\t\t\t/* 'git checkout/restore <something> <pathspec> [...]' */\n+\t\t\trecover_with_dwim = 0;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Absence of '--' leaves <pathspec>/<commit> ambiguity.\n+\t\t * Try to resolve it with additional knowledge about pathspec args.\n+\t\t */\n+\t\texpect_commit_only = !opts->accept_pathspec;\n+\t} else if (dash_dash_pos == 0) {\n+\t\t/* 'git checkout/switch/restore -- [...]' */\n+\t\treturn 1;  /* Eat '--' */\n+\t} else if (dash_dash_pos == 1) {\n+\t\tif (!opts->accept_pathspec) {\n+\t\t\t/* 'git switch <commit> -- [...]' */\n+\t\t\tdie(_(\"incompatible with pathspec arguments\"));\n+\t\t}\n+\n+\t\tif (argc == 2) {\n+\t\t\t/* 'git checkout/restore <commit> --' */\n+\t\t\trecover_with_dwim = dwim_new_local_branch_ok;\n+\t\t} else {\n+\t\t\t/* 'git checkout/restore <commit> -- <pathspec> [...]' */\n+\t\t\trecover_with_dwim = 0;\n+\t\t}\n \n-\tif (has_dash_dash)\n-\t    expect_commit_only = 1;\n+\t\t/* Presence of '--' makes it certain that arg is <commit> */\n+\t\texpect_commit_only = 1;\n+\t} else {\n+\t\t/* 'git checkout/switch/restore <commit> <unxpected> [...] -- [...]' */\n+\t\tdie(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n+\t}\n \n \topts->count_checkout_paths = !opts->quiet && !expect_commit_only;\n \n@@ -1210,19 +1233,12 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\targ = \"@{-1}\";\n \n \tif (get_oid_mb(arg, rev)) {\n-\t\tint recover_with_dwim = dwim_new_local_branch_ok;\n-\n \t\tint could_be_checkout_paths = !expect_commit_only &&\n \t\t\tcheck_filename(opts->prefix, arg);\n \n \t\tif (!expect_commit_only && !no_wildcard(arg))\n \t\t\trecover_with_dwim = 0;\n \n-\t\tif (!(argc == 1 && !has_dash_dash) &&\n-\t\t    !(argc == 2 && has_dash_dash) &&\n-\t\t    opts->accept_pathspec)\n-\t\t\trecover_with_dwim = 0;\n-\n \t\tif (recover_with_dwim) {\n \t\t\tconst char *remote = parse_remote_branch(arg, rev,\n \t\t\t\t\t\t\t\t could_be_checkout_paths);\n-- \ngitgitgadget\n\n"},{"id":"388280","messageId":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.git.1576161385.gitgitgadget@gmail.com","subject":"[PATCH v2 00/18] Extend --pathspec-from-file to git add, checkout","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:47:48Z","receivedAt":"2019-12-16T15:48:33Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"This topic continues the effort to support `--pathspec-from-file` in various\ncommands [1][2]. It also includes some refactorings that I developed while\nworking on it - previously submitted separately [3][4] which was probably a\nmistake.\n\nAnatomy of the branch:\n  checkout, restore: support the --pathspec-from-file option\n    Extends `--pathspec-from-file` to `git checkout/restore`.\n\n  t2024: cover more cases\n    Some new tests for cases that I deemed worthy while working\n    on `parse_branchname_arg()` refactoring.\n\n  parse_branchname_arg(): refactor the decision making\n  parse_branchname_arg(): update code comments\n  parse_branchname_arg(): introduce expect_commit_only\n  parse_branchname_arg(): easier to understand variables\n    These patches prepare for `|| opts->pathspec_from_file` addition in\n    `git checkout/restore` patch. Without this refactoring, I found it\n    pretty hard to modify the old code.\n\n  checkout: die() on ambiguous tracking branches\n  parse_branchname_arg(): extract part as new function\n    Initially I was trying to remove some inconsistency standing in the\n    way of `git checkout/restore` patch. Later I figured that this change\n    is worthy on its own: it prevents some pretty surprising behavior of\n    git.\n\n  doc: restore: synchronize <pathspec> description\n  doc: checkout: synchronize <pathspec> description\n  doc: checkout: fix broken text reference\n  doc: checkout: remove duplicate synopsis\n    Some polishing of docs in preparation for `git checkout/restore` patch.\n\n  add: support the --pathspec-from-file option\n  cmd_add: prepare for next patch\n    Extends `--pathspec-from-file` to `git add`.\n\n  commit: forbid --pathspec-from-file --all\n  t7107, t7526: directly test parse_pathspec_file()\n    Some polishing of merged topic [1].\n\nCC'ing people who shown interest in any of the previous topics, thanks for\nyour reviews!\n\n[1] https://public-inbox.org/git/pull.445.git.1572895605.gitgitgadget@gmail.com/\n[2] https://public-inbox.org/git/20191204203911.237056-1-emilyshaffer@google.com/\n[3] https://public-inbox.org/git/pull.477.git.1574848137.gitgitgadget@gmail.com/\n[4] https://public-inbox.org/git/pull.479.git.1574969538.gitgitgadget@gmail.com/\n\nChanges since V1:\n----------------\n@Junio please note that V1 was already substantially different from\nwhat you merged into `next`.\n\n1) Added tests for error scenarios related to --pathspec-from-file.\n2) Restored tests for --pathspec-file-nul: they are valuable for every\n   command, because they verify that specific commands handle the\n   commandline option correctly.\n3) Dropped old tests for `git restore` that I forgot to delete when I\n   made commit `t7107, t7526: directly test parse_pathspec_file()`.\n\nAlexandr Miloslavskiy (18):\n  t7107, t7526: directly test parse_pathspec_file()\n  t7526: add tests for error conditions\n  t7107: add tests for error conditions\n  commit: forbid --pathspec-from-file --all\n  cmd_add: prepare for next patch\n  add: support the --pathspec-from-file option\n  doc: checkout: remove duplicate synopsis\n  doc: checkout: fix broken text reference\n  doc: checkout: synchronize <pathspec> description\n  doc: restore: synchronize <pathspec> description\n  parse_branchname_arg(): extract part as new function\n  checkout: die() on ambiguous tracking branches\n  parse_branchname_arg(): easier to understand variables\n  parse_branchname_arg(): introduce expect_commit_only\n  parse_branchname_arg(): update code comments\n  parse_branchname_arg(): refactor the decision making\n  t2024: cover more cases\n  checkout, restore: support the --pathspec-from-file option\n\n Documentation/git-add.txt           |  16 +-\n Documentation/git-checkout.txt      |  50 +++--\n Documentation/git-restore.txt       |  26 ++-\n Makefile                            |   1 +\n builtin/add.c                       |  60 ++++--\n builtin/checkout.c                  | 279 ++++++++++++++--------------\n builtin/commit.c                    |   3 +\n t/helper/test-parse-pathspec-file.c |  34 ++++\n t/helper/test-tool.c                |   1 +\n t/helper/test-tool.h                |   1 +\n t/t0067-parse_pathspec_file.sh      |  89 +++++++++\n t/t2024-checkout-dwim.sh            |  55 +++++-\n t/t2026-checkout-pathspec-file.sh   |  90 +++++++++\n t/t2072-restore-pathspec-file.sh    |  91 +++++++++\n t/t3704-add-pathspec-file.sh        |  86 +++++++++\n t/t7107-reset-pathspec-file.sh      |  98 ++--------\n t/t7526-commit-pathspec-file.sh     |  83 +++------\n t/t9902-completion.sh               |   2 +\n 18 files changed, 743 insertions(+), 322 deletions(-)\n create mode 100644 t/helper/test-parse-pathspec-file.c\n create mode 100755 t/t0067-parse_pathspec_file.sh\n create mode 100755 t/t2026-checkout-pathspec-file.sh\n create mode 100755 t/t2072-restore-pathspec-file.sh\n create mode 100755 t/t3704-add-pathspec-file.sh\n\n\nbase-commit: ad05a3d8e5a6a06443836b5e40434262d992889a\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-490%2FSyntevoAlex%2F%230207_pathspec_from_file_2-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-490/SyntevoAlex/#0207_pathspec_from_file_2-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/490\n\nRange-diff vs v1:\n\n  1:  bfdd7a4b56 !  1:  8d5fb9f40b t7107, t7526: directly test parse_pathspec_file()\n     @@ -225,18 +225,14 @@\n      -'\n      -\n      -test_expect_success 'NUL delimiters' '\n     --\trestore_checkpoint &&\n     --\n     --\tgit rm fileA.t fileB.t &&\n     --\tprintf \"fileA.t\\0fileB.t\\0\" | git reset --pathspec-from-file=- --pathspec-file-nul &&\n     --\n     --\tcat >expect <<-\\EOF &&\n     --\t D fileA.t\n     --\t D fileB.t\n     --\tEOF\n     --\tverify_expect\n     --'\n     --\n     ++test_expect_success '--pathspec-file-nul' '\n     + \trestore_checkpoint &&\n     + \n     + \tgit rm fileA.t fileB.t &&\n     +@@\n     + \tverify_expect\n     + '\n     + \n      -test_expect_success 'LF delimiters' '\n      -\trestore_checkpoint &&\n      -\n     @@ -361,17 +357,14 @@\n      -'\n      -\n      -test_expect_success 'NUL delimiters' '\n     --\trestore_checkpoint &&\n     --\n     --\tprintf \"fileA.t\\0fileB.t\\0\" | git commit --pathspec-from-file=- --pathspec-file-nul -m \"Commit\" &&\n     --\n     --\tcat >expect <<-\\EOF &&\n     --\tA\tfileA.t\n     --\tA\tfileB.t\n     --\tEOF\n     --\tverify_expect\n     --'\n     --\n     ++test_expect_success '--pathspec-file-nul' '\n     + \trestore_checkpoint &&\n     + \n     + \tprintf \"fileA.t\\0fileB.t\\0\" | git commit --pathspec-from-file=- --pathspec-file-nul -m \"Commit\" &&\n     +@@\n     + \tverify_expect\n     + '\n     + \n      -test_expect_success 'LF delimiters' '\n      -\trestore_checkpoint &&\n      -\n  -:  ---------- >  2:  c7cd46d3a3 t7526: add tests for error conditions\n  -:  ---------- >  3:  b09d74c347 t7107: add tests for error conditions\n  2:  c0980519ed !  4:  deeb860a85 commit: forbid --pathspec-from-file --all\n     @@ -20,7 +20,7 @@\n      \n          [1] Commit e440fc58 (\"commit: support the --pathspec-from-file option\" 2019-11-19)\n      \n     -    Co-authored-by: Phillip Wood <phillip.wood123@gmail.com>\n     +    Reported-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n          Signed-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n      \n       diff --git a/builtin/commit.c b/builtin/commit.c\n     @@ -36,3 +36,17 @@\n       \t\tif (pathspec.nr)\n       \t\t\tdie(_(\"--pathspec-from-file is incompatible with pathspec arguments\"));\n       \n     +\n     + diff --git a/t/t7526-commit-pathspec-file.sh b/t/t7526-commit-pathspec-file.sh\n     + --- a/t/t7526-commit-pathspec-file.sh\n     + +++ b/t/t7526-commit-pathspec-file.sh\n     +@@\n     + \ttest_must_fail git commit --pathspec-from-file=- --patch -m \"Commit\" <list 2>err &&\n     + \ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-interactive/\\-\\-patch\" err &&\n     + \n     ++\ttest_must_fail git commit --pathspec-from-file=- --all -m \"Commit\" <list 2>err &&\n     ++\ttest_i18ngrep \"\\-\\-pathspec-from-file with \\-a does not make sense\" err &&\n     ++\n     + \ttest_must_fail git commit --pathspec-from-file=- -m \"Commit\" -- fileA.t <list 2>err &&\n     + \ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n     + \n  3:  3690964942 =  5:  204a0a4446 cmd_add: prepare for next patch\n  4:  0328fdafbb !  6:  1ea5f17847 add: support the --pathspec-from-file option\n     @@ -156,6 +156,18 @@\n      +\tverify_expect\n      +'\n      +\n     ++test_expect_success '--pathspec-file-nul' '\n     ++\trestore_checkpoint &&\n     ++\n     ++\tprintf \"fileA.t\\0fileB.t\\0\" | git add --pathspec-from-file=- --pathspec-file-nul &&\n     ++\n     ++\tcat >expect <<-\\EOF &&\n     ++\tA  fileA.t\n     ++\tA  fileB.t\n     ++\tEOF\n     ++\tverify_expect\n     ++'\n     ++\n      +test_expect_success 'only touches what was listed' '\n      +\trestore_checkpoint &&\n      +\n     @@ -168,4 +180,29 @@\n      +\tverify_expect\n      +'\n      +\n     ++test_expect_success 'error conditions' '\n     ++\trestore_checkpoint &&\n     ++\techo fileA.t >list &&\n     ++\t>empty_list &&\n     ++\n     ++\ttest_must_fail git add --pathspec-from-file=- --interactive <list 2>err &&\n     ++\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-interactive/\\-\\-patch\" err &&\n     ++\n     ++\ttest_must_fail git add --pathspec-from-file=- --patch <list 2>err &&\n     ++\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-interactive/\\-\\-patch\" err &&\n     ++\n     ++\ttest_must_fail git add --pathspec-from-file=- --edit <list 2>err &&\n     ++\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-edit\" err &&\n     ++\n     ++\ttest_must_fail git add --pathspec-from-file=- -- fileA.t <list 2>err &&\n     ++\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n     ++\n     ++\ttest_must_fail git add --pathspec-file-nul 2>err &&\n     ++\ttest_i18ngrep \"\\-\\-pathspec-file-nul requires \\-\\-pathspec-from-file\" err &&\n     ++\t\n     ++\t# This case succeeds, but still prints to stderr\n     ++\tgit add --pathspec-from-file=- <empty_list 2>err &&\n     ++\ttest_i18ngrep \"Nothing specified, nothing added.\" err\n     ++'\n     ++\n      +test_done\n  5:  8677db4b9e =  7:  3d0fcf6ba5 doc: checkout: remove duplicate synopsis\n  6:  2d87b6433b =  8:  85f7ccc4e0 doc: checkout: fix broken text reference\n  7:  6288a7fa36 =  9:  db6e40d004 doc: checkout: synchronize <pathspec> description\n  8:  d2f277bb58 = 10:  c88cbf453a doc: restore: synchronize <pathspec> description\n  9:  891d1b91cf = 11:  2c23bd602d parse_branchname_arg(): extract part as new function\n 10:  271e8ed3ab = 12:  efd6876874 checkout: die() on ambiguous tracking branches\n 11:  a0a884b384 = 13:  2498825230 parse_branchname_arg(): easier to understand variables\n 12:  a6b77f9b23 = 14:  2350dc282e parse_branchname_arg(): introduce expect_commit_only\n 13:  4e2bb6663c = 15:  46f676b8e0 parse_branchname_arg(): update code comments\n 14:  010fd76331 = 16:  319151e4e9 parse_branchname_arg(): refactor the decision making\n 15:  ffbc405920 = 17:  542eb709ca t2024: cover more cases\n 16:  9af2de98de ! 18:  c293d72832 checkout, restore: support the --pathspec-from-file option\n     @@ -218,6 +218,18 @@\n      +\tverify_expect\n      +'\n      +\n     ++test_expect_success '--pathspec-file-nul' '\n     ++\trestore_checkpoint &&\n     ++\n     ++\tprintf \"fileA.t\\0fileB.t\\0\" | git checkout --pathspec-from-file=- --pathspec-file-nul HEAD^1 &&\n     ++\n     ++\tcat >expect <<-\\EOF &&\n     ++\tM  fileA.t\n     ++\tM  fileB.t\n     ++\tEOF\n     ++\tverify_expect\n     ++'\n     ++\n      +test_expect_success 'only touches what was listed' '\n      +\trestore_checkpoint &&\n      +\n     @@ -230,6 +242,23 @@\n      +\tverify_expect\n      +'\n      +\n     ++test_expect_success 'error conditions' '\n     ++\trestore_checkpoint &&\n     ++\techo fileA.t >list &&\n     ++\n     ++\ttest_must_fail git checkout --pathspec-from-file=- --detach <list 2>err &&\n     ++\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-detach\" err &&\n     ++\n     ++\ttest_must_fail git checkout --pathspec-from-file=- --patch <list 2>err &&\n     ++\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-patch\" err &&\n     ++\n     ++\ttest_must_fail git checkout --pathspec-from-file=- -- fileA.t <list 2>err &&\n     ++\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n     ++\n     ++\ttest_must_fail git checkout --pathspec-file-nul 2>err &&\n     ++\ttest_i18ngrep \"\\-\\-pathspec-file-nul requires \\-\\-pathspec-from-file\" err\n     ++'\n     ++\n      +test_done\n      \n       diff --git a/t/t2072-restore-pathspec-file.sh b/t/t2072-restore-pathspec-file.sh\n     @@ -274,7 +303,7 @@\n      +\ttest_cmp expect actual\n      +}\n      +\n     -+test_expect_success '--pathspec-from-file from stdin' '\n     ++test_expect_success 'simplest' '\n      +\trestore_checkpoint &&\n      +\n      +\techo fileA.t | git restore --pathspec-from-file=- --source=HEAD^1 &&\n     @@ -285,19 +314,7 @@\n      +\tverify_expect\n      +'\n      +\n     -+test_expect_success '--pathspec-from-file from file' '\n     -+\trestore_checkpoint &&\n     -+\n     -+\techo fileA.t >list &&\n     -+\tgit restore --pathspec-from-file=list --source=HEAD^1 &&\n     -+\n     -+\tcat >expect <<-\\EOF &&\n     -+\t M fileA.t\n     -+\tEOF\n     -+\tverify_expect\n     -+'\n     -+\n     -+test_expect_success 'NUL delimiters' '\n     ++test_expect_success '--pathspec-file-nul' '\n      +\trestore_checkpoint &&\n      +\n      +\tprintf \"fileA.t\\0fileB.t\\0\" | git restore --pathspec-from-file=- --pathspec-file-nul --source=HEAD^1 &&\n     @@ -309,70 +326,34 @@\n      +\tverify_expect\n      +'\n      +\n     -+test_expect_success 'LF delimiters' '\n     -+\trestore_checkpoint &&\n     -+\n     -+\tprintf \"fileA.t\\nfileB.t\\n\" | git restore --pathspec-from-file=- --source=HEAD^1 &&\n     -+\n     -+\tcat >expect <<-\\EOF &&\n     -+\t M fileA.t\n     -+\t M fileB.t\n     -+\tEOF\n     -+\tverify_expect\n     -+'\n     -+\n     -+test_expect_success 'no trailing delimiter' '\n     -+\trestore_checkpoint &&\n     -+\n     -+\tprintf \"fileA.t\\nfileB.t\" | git restore --pathspec-from-file=- --source=HEAD^1 &&\n     -+\n     -+\tcat >expect <<-\\EOF &&\n     -+\t M fileA.t\n     -+\t M fileB.t\n     -+\tEOF\n     -+\tverify_expect\n     -+'\n     -+\n     -+test_expect_success 'CRLF delimiters' '\n     ++test_expect_success 'only touches what was listed' '\n      +\trestore_checkpoint &&\n      +\n     -+\tprintf \"fileA.t\\r\\nfileB.t\\r\\n\" | git restore --pathspec-from-file=- --source=HEAD^1 &&\n     ++\tprintf \"fileB.t\\nfileC.t\\n\" | git restore --pathspec-from-file=- --source=HEAD^1 &&\n      +\n      +\tcat >expect <<-\\EOF &&\n     -+\t M fileA.t\n      +\t M fileB.t\n     ++\t M fileC.t\n      +\tEOF\n      +\tverify_expect\n      +'\n      +\n     -+test_expect_success 'quotes' '\n     -+\trestore_checkpoint &&\n     -+\n     -+\tprintf \"\\\"file\\\\101.t\\\"\" | git restore --pathspec-from-file=- --source=HEAD^1 &&\n     -+\n     -+\tcat >expect <<-\\EOF &&\n     -+\t M fileA.t\n     -+\tEOF\n     -+\tverify_expect\n     -+'\n     -+\n     -+test_expect_success 'quotes not compatible with --pathspec-file-nul' '\n     ++test_expect_success 'error conditions' '\n      +\trestore_checkpoint &&\n     ++\techo fileA.t >list &&\n     ++\t>empty_list &&\n      +\n     -+\tprintf \"\\\"file\\\\101.t\\\"\" >list &&\n     -+\ttest_must_fail git restore --pathspec-from-file=list --pathspec-file-nul --source=HEAD^1\n     -+'\n     ++\ttest_must_fail git restore --pathspec-from-file=- --patch --source=HEAD^1 <list 2>err &&\n     ++\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-patch\" err &&\n      +\n     -+test_expect_success 'only touches what was listed' '\n     -+\trestore_checkpoint &&\n     ++\ttest_must_fail git restore --pathspec-from-file=- --source=HEAD^1 -- fileA.t <list 2>err &&\n     ++\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n      +\n     -+\tprintf \"fileB.t\\nfileC.t\\n\" | git restore --pathspec-from-file=- --source=HEAD^1 &&\n     ++\ttest_must_fail git restore --pathspec-file-nul --source=HEAD^1 2>err &&\n     ++\ttest_i18ngrep \"\\-\\-pathspec-file-nul requires \\-\\-pathspec-from-file\" err &&\n      +\n     -+\tcat >expect <<-\\EOF &&\n     -+\t M fileB.t\n     -+\t M fileC.t\n     -+\tEOF\n     -+\tverify_expect\n     ++\ttest_must_fail git restore --pathspec-from-file=- --source=HEAD^1 <empty_list 2>err &&\n     ++\ttest_i18ngrep \"you must specify path(s) to restore\" err\n      +'\n      +\n      +test_done\n\n-- \ngitgitgadget\n"},{"id":"388281","messageId":"b09d74c34783235f90f284af77da565e305d0b6d.1576511287.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v2 03/18] t7107: add tests for error conditions","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-16T15:47:51Z","receivedAt":"2019-12-16T15:48:38Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nSuggested-By: Phillip Wood <phillip.wood@dunelm.org.uk>\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n t/t7107-reset-pathspec-file.sh | 14 ++++++++++++++\n 1 file changed, 14 insertions(+)\n\ndiff --git a/t/t7107-reset-pathspec-file.sh b/t/t7107-reset-pathspec-file.sh\nindex f36fce27b9..da833925a6 100755\n--- a/t/t7107-reset-pathspec-file.sh\n+++ b/t/t7107-reset-pathspec-file.sh\n@@ -74,4 +74,18 @@ test_expect_success '--pathspec-from-file is not compatible with --soft or --har\n \ttest_must_fail git reset --hard --pathspec-from-file=list\n '\n \n+test_expect_success 'error conditions' '\n+\trestore_checkpoint &&\n+\techo fileA.t >list &&\n+\n+\ttest_must_fail git reset --pathspec-from-file=- --patch <list 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-patch\" err &&\n+\n+\ttest_must_fail git reset --pathspec-from-file=- -- fileA.t <list 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n+\n+\ttest_must_fail git reset --pathspec-file-nul 2>err &&\n+\ttest_i18ngrep \"\\-\\-pathspec-file-nul requires \\-\\-pathspec-from-file\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"388282","messageId":"a44bc260-c325-ce31-1a12-6c34fdbc8d39@syntevo.com","threadId":"52440","inReplyTo":"5928a257-a1f4-dad3-052e-7a0dbf1df8e3@gmail.com","subject":"Re: [PATCH 02/16] commit: forbid --pathspec-from-file --all","fromName":"Alexandr Miloslavskiy","fromEmail":"alexandr.miloslavskiy@syntevo.com","sentAt":"2019-12-16T15:53:32Z","receivedAt":"2019-12-16T15:53:40Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"On 16.12.2019 13:02, Phillip Wood wrote:\n\n> Thanks for fixing this. If you want to credit me then I think\n> Reported-by: would be more appropriate as I didn't write this patch.\n> Also I tend to use phillip.wood@dunelm.org.uk for footers as it's a\n> portable email address (I should add a mailmap entry...). It would be\n> nice to have tests for the various error conditions at some point.\n\nThanks, I have addressed both issues in V2.\n"},{"id":"388400","messageId":"195a5b2a-994a-5984-8cc7-280a698df2a0@gmail.com","threadId":"52440","inReplyTo":"deeb860a85d25e0645a5d2e1c82654653ab1e2d5.1576511287.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 04/18] commit: forbid --pathspec-from-file --all","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2019-12-17T20:00:43Z","receivedAt":"2019-12-17T20:00:49Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Alexandr\n\nThis looks good, thanks for the test\n\nBest Wishes\n\nPhillip\n\nOn 16/12/2019 15:47, Alexandr Miloslavskiy via GitGitGadget wrote:\n> From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n> \n> I forgot this in my previous patch `--pathspec-from-file` for\n> `git commit` [1]. When both `--pathspec-from-file` and `--all` were\n> specified, `--all` took precedence and `--pathspec-from-file` was\n> ignored. Before `--pathspec-from-file` was implemented, this case was\n> prevented by this check in `parse_and_validate_options()` :\n> \n>      die(_(\"paths '%s ...' with -a does not make sense\"), argv[0]);\n> \n> It is unfortunate that these two cases are disconnected. This came as\n> result of how the code was laid out before my patches, where `pathspec`\n> is parsed outside of `parse_and_validate_options()`. This branch is\n> already full of refactoring patches and I did not dare to go for another\n> one.\n> \n> Fix by mirroring `die()` for `--pathspec-from-file` as well.\n> \n> [1] Commit e440fc58 (\"commit: support the --pathspec-from-file option\" 2019-11-19)\n> \n> Reported-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> Signed-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n> ---\n>   builtin/commit.c                | 3 +++\n>   t/t7526-commit-pathspec-file.sh | 3 +++\n>   2 files changed, 6 insertions(+)\n> \n> diff --git a/builtin/commit.c b/builtin/commit.c\n> index 2db2ad0de4..893a9f29b2 100644\n> --- a/builtin/commit.c\n> +++ b/builtin/commit.c\n> @@ -347,6 +347,9 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix\n>   \t\tif (interactive)\n>   \t\t\tdie(_(\"--pathspec-from-file is incompatible with --interactive/--patch\"));\n>   \n> +\t\tif (all)\n> +\t\t\tdie(_(\"--pathspec-from-file with -a does not make sense\"));\n> +\n>   \t\tif (pathspec.nr)\n>   \t\t\tdie(_(\"--pathspec-from-file is incompatible with pathspec arguments\"));\n>   \n> diff --git a/t/t7526-commit-pathspec-file.sh b/t/t7526-commit-pathspec-file.sh\n> index 68920e8ff9..ba769e0e5d 100755\n> --- a/t/t7526-commit-pathspec-file.sh\n> +++ b/t/t7526-commit-pathspec-file.sh\n> @@ -72,6 +72,9 @@ test_expect_success 'error conditions' '\n>   \ttest_must_fail git commit --pathspec-from-file=- --patch -m \"Commit\" <list 2>err &&\n>   \ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-interactive/\\-\\-patch\" err &&\n>   \n> +\ttest_must_fail git commit --pathspec-from-file=- --all -m \"Commit\" <list 2>err &&\n> +\ttest_i18ngrep \"\\-\\-pathspec-from-file with \\-a does not make sense\" err &&\n> +\n>   \ttest_must_fail git commit --pathspec-from-file=- -m \"Commit\" -- fileA.t <list 2>err &&\n>   \ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n>   \n> \n"},{"id":"388477","messageId":"xmqqwoatcn5u.fsf@gitster-ct.c.googlers.com","threadId":"52440","inReplyTo":"8d5fb9f40b8fc766ef022f910529e6308d9c2d80.1576511287.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 01/18] t7107, t7526: directly test parse_pathspec_file()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-12-18T21:57:17Z","receivedAt":"2019-12-18T21:57:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Alexandr Miloslavskiy via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n>\n> In my previous patches, `parse_pathspec_file()` was tested indirectly by\n> invoking `git reset` and `git commit` with properly crafted inputs. This\n> has some disadvantages:\n> 1) A number of tests are copy&pasted for every command where\n>    `--pathspec-from-file` is supported. With just two commands, it\n>    wasn't too bad, but I'm going to extend support to many more\n>    commands, which would make a handful of low-value tests.\n> 2) Tests are located in suboptimal test packages\n> 3) Tests are indirect\n\nThat cuts both ways.  For a developer who is too narrowly focused\n(because s/he spent enough time staring at the code), testing the\nunderlying machinery in a more direct way does feel attractive, but\nat the same time, what matters to the end users is how well the\nfeature, when integrated into the commands they use (not the test\nscaffolding like the \"test-parse-pathspec-file\" command), works.\n\nSo \"indirect\" is not necessarily a bad thing.\n\n"},{"id":"388478","messageId":"xmqqr211cmxy.fsf@gitster-ct.c.googlers.com","threadId":"52440","inReplyTo":"c7cd46d3a3bcbf4a5cfe2aa6aa71b3713801892b.1576511287.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 02/18] t7526: add tests for error conditions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-12-18T22:02:01Z","receivedAt":"2019-12-18T22:02:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Alexandr Miloslavskiy via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> +\ttest_must_fail git commit --pathspec-from-file=- --interactive -m \"Commit\" <list 2>err &&\n> +\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-interactive/\\-\\-patc\nh\" err &&\n\nWhat's with the overly-noisy quoting of dashes here?  To match a\nstring that happens to begin with a dash, either one of\n\n\t$ grep -e \"-this string begins with and has-many-dashes-in-it\" file\n\t$ grep \"[-]this string begins with and has-many-dashes-in-it\" file\n\nwould be sufficient and more idiomatic.\n\nOr am I missing some other reason why the test is written this way?\n\n> +\ttest_must_fail git commit --pathspec-from-file=- --patch -m \"Commit\" <list 2>err &&\n> +\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-interactive/\\-\\-patch\" err &&\n> +\n> +\ttest_must_fail git commit --pathspec-from-file=- -m \"Commit\" -- fileA.t <list 2>err &&\n> +\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n> +\n> +\ttest_must_fail git commit --pathspec-file-nul -m \"Commit\" 2>err &&\n> +\ttest_i18ngrep \"\\-\\-pathspec-file-nul requires \\-\\-pathspec-from-file\" err &&\n> +\n> +\ttest_must_fail git commit --pathspec-from-file=- --include -m \"Commit\" <empty_list 2>err &&\n> +\ttest_i18ngrep \"No paths with \\-\\-include/\\-\\-only does not make sense.\" err &&\n> +\n> +\ttest_must_fail git commit --pathspec-from-file=- --only -m \"Commit\" <empty_list 2>err &&\n> +\ttest_i18ngrep \"No paths with \\-\\-include/\\-\\-only does not make sense.\" err\n> +'\n> +\n>  test_done\n"},{"id":"388479","messageId":"xmqqmubpcmtx.fsf@gitster-ct.c.googlers.com","threadId":"52440","inReplyTo":"195a5b2a-994a-5984-8cc7-280a698df2a0@gmail.com","subject":"Re: [PATCH v2 04/18] commit: forbid --pathspec-from-file --all","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-12-18T22:04:26Z","receivedAt":"2019-12-18T22:04:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> Hi Alexandr\n>\n> This looks good, thanks for the test\n>\n> Best Wishes\n>\n> Phillip\n>\n> On 16/12/2019 15:47, Alexandr Miloslavskiy via GitGitGadget wrote:\n>> From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n>>\n>> I forgot this in my previous patch `--pathspec-from-file` for\n>> `git commit` [1]. When both `--pathspec-from-file` and `--all` were\n>> ...\n>> [1] Commit e440fc58 (\"commit: support the --pathspec-from-file option\" 2019-11-19)\n\nThanks, both.  I will take this separately and queue directly on top\nof am/pathspec-from-file to fast-track it, rather than leaving it as\na part of larger topic that would take more time to mature.\n\n\n"},{"id":"388481","messageId":"xmqqimmdcmq4.fsf@gitster-ct.c.googlers.com","threadId":"52440","inReplyTo":"xmqqmubpcmtx.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 04/18] commit: forbid --pathspec-from-file --all","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-12-18T22:06:43Z","receivedAt":"2019-12-18T22:06:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n>\n>> Hi Alexandr\n>>\n>> This looks good, thanks for the test\n>>\n>> Best Wishes\n>>\n>> Phillip\n>>\n>> On 16/12/2019 15:47, Alexandr Miloslavskiy via GitGitGadget wrote:\n>>> From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n>>>\n>>> I forgot this in my previous patch `--pathspec-from-file` for\n>>> `git commit` [1]. When both `--pathspec-from-file` and `--all` were\n>>> ...\n>>> [1] Commit e440fc58 (\"commit: support the --pathspec-from-file option\" 2019-11-19)\n>\n> Thanks, both.  I will take this separately and queue directly on top\n> of am/pathspec-from-file to fast-track it, rather than leaving it as\n> a part of larger topic that would take more time to mature.\n\nSigh... the test part of this patch is taken hostage to an earlier\npatches in this series that are iffy, so I cannot quite apply this\nfix alone at this moment.\n\nYuck.\n"},{"id":"388482","messageId":"xmqqeex1cmam.fsf@gitster-ct.c.googlers.com","threadId":"52440","inReplyTo":"xmqqimmdcmq4.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 04/18] commit: forbid --pathspec-from-file --all","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-12-18T22:16:01Z","receivedAt":"2019-12-18T22:16:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> Phillip Wood <phillip.wood123@gmail.com> writes:\n>>\n>>> Hi Alexandr\n>>>\n>>> This looks good, thanks for the test\n>>>\n>>> Best Wishes\n>>>\n>>> Phillip\n>>>\n>>> On 16/12/2019 15:47, Alexandr Miloslavskiy via GitGitGadget wrote:\n>>>> From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n>>>>\n>>>> I forgot this in my previous patch `--pathspec-from-file` for\n>>>> `git commit` [1]. When both `--pathspec-from-file` and `--all` were\n>>>> ...\n>>>> [1] Commit e440fc58 (\"commit: support the --pathspec-from-file option\" 2019-11-19)\n>>\n>> Thanks, both.  I will take this separately and queue directly on top\n>> of am/pathspec-from-file to fast-track it, rather than leaving it as\n>> a part of larger topic that would take more time to mature.\n>\n> Sigh... the test part of this patch is taken hostage to an earlier\n> patches in this series that are iffy, so I cannot quite apply this\n> fix alone at this moment.\n>\n> Yuck.\n\nHere is what I'll queue directly on top of am/pathspec-from-file\ne440fc58 (\"commit: support the --pathspec-from-file option\",\n2019-11-19), to be fast-tracked.\n\n-- >8 --\nSubject: [PATCH] commit: forbid --pathspec-from-file --all\n\nI forgot this in my previous patch `--pathspec-from-file` for\n`git commit` [1]. When both `--pathspec-from-file` and `--all` were\nspecified, `--all` took precedence and `--pathspec-from-file` was\nignored. Before `--pathspec-from-file` was implemented, this case was\nprevented by this check in `parse_and_validate_options()` :\n\n    die(_(\"paths '%s ...' with -a does not make sense\"), argv[0]);\n\nIt is unfortunate that these two cases are disconnected. This came as\nresult of how the code was laid out before my patches, where `pathspec`\nis parsed outside of `parse_and_validate_options()`. This branch is\nalready full of refactoring patches and I did not dare to go for another\none.\n\nFix by mirroring `die()` for `--pathspec-from-file` as well.\n\n[1] Commit e440fc58 (\"commit: support the --pathspec-from-file option\" 2019-11-19)\n\nReported-by: Phillip Wood <phillip.wood@dunelm.org.uk>\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n[jc: adjusted test not to depend on other patches]\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/commit.c                | 3 +++\n t/t7526-commit-pathspec-file.sh | 6 ++++++\n 2 files changed, 9 insertions(+)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex ed40729355..c040dc92a4 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -347,6 +347,9 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix\n \t\tif (interactive)\n \t\t\tdie(_(\"--pathspec-from-file is incompatible with --interactive/--patch\"));\n \n+\t\tif (all)\n+\t\t\tdie(_(\"--pathspec-from-file with -a does not make sense\"));\n+\n \t\tif (pathspec.nr)\n \t\t\tdie(_(\"--pathspec-from-file is incompatible with pathspec arguments\"));\n \ndiff --git a/t/t7526-commit-pathspec-file.sh b/t/t7526-commit-pathspec-file.sh\nindex a06b683534..4b58901ed6 100755\n--- a/t/t7526-commit-pathspec-file.sh\n+++ b/t/t7526-commit-pathspec-file.sh\n@@ -127,4 +127,10 @@ test_expect_success 'only touches what was listed' '\n \tverify_expect\n '\n \n+test_expect_success '--pathspec-from-file and --all cannot be used together' '\n+\trestore_checkpoint &&\n+\ttest_must_fail git commit --pathspec-from-file=- --all -m \"Commit\" 2>err &&\n+\ttest_i18ngrep \"[-]-pathspec-from-file with -a does not make sense\" err\n+'\n+\n test_done\n-- \n2.24.1-722-g0b9e186032\n\n"},{"id":"388506","messageId":"xmqq5zicde83.fsf@gitster-ct.c.googlers.com","threadId":"52440","inReplyTo":"xmqqwoatcn5u.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 01/18] t7107, t7526: directly test parse_pathspec_file()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-12-19T06:25:00Z","receivedAt":"2019-12-19T06:25:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Alexandr Miloslavskiy via GitGitGadget\" <gitgitgadget@gmail.com>\n> writes:\n> ...\n>> 3) Tests are indirect\n>\n> That cuts both ways.  For a developer who is too narrowly focused\n> (because s/he spent enough time staring at the code), testing the\n> underlying machinery in a more direct way does feel attractive, but\n> at the same time, what matters to the end users is how well the\n> feature, when integrated into the commands they use (not the test\n> scaffolding like the \"test-parse-pathspec-file\" command), works.\n>\n> So \"indirect\" is not necessarily a bad thing.\n\nJust to avoid misunderstanding, I am not opposed to adding tests and\ntest helpers that allows direct access to the guts of the machinery\nto check the behaviour of the lower level codepath.  I am merely\nsaying that such tests would not make it unnecessary to have\nend-to-end tests that validates end-user visible effects.\n\nThanks.\n"},{"id":"388526","messageId":"0cf9d6a8-f804-c828-28f5-e60089190167@syntevo.com","threadId":"52440","inReplyTo":"xmqqwoatcn5u.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 01/18] t7107, t7526: directly test parse_pathspec_file()","fromName":"Alexandr Miloslavskiy","fromEmail":"alexandr.miloslavskiy@syntevo.com","sentAt":"2019-12-19T17:19:21Z","receivedAt":"2019-12-19T17:19:28Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"On 18.12.2019 22:57, Junio C Hamano wrote:\n\n>> In my previous patches, `parse_pathspec_file()` was tested indirectly by\n>> invoking `git reset` and `git commit` with properly crafted inputs. This\n>> has some disadvantages:\n>> 1) A number of tests are copy&pasted for every command where\n>>     `--pathspec-from-file` is supported. With just two commands, it\n>>     wasn't too bad, but I'm going to extend support to many more\n>>     commands, which would make a handful of low-value tests.\n>> 2) Tests are located in suboptimal test packages\n>> 3) Tests are indirect\n> \n> That cuts both ways.  For a developer who is too narrowly focused\n> (because s/he spent enough time staring at the code), testing the\n> underlying machinery in a more direct way does feel attractive, but\n> at the same time, what matters to the end users is how well the\n> feature, when integrated into the commands they use (not the test\n> scaffolding like the \"test-parse-pathspec-file\" command), works.\n> \n> So \"indirect\" is not necessarily a bad thing.\n\nI agree that it cuts both ways.\n\nJust recently I had an (unrelated) discussion with Johannes Schindelin \nwho forced me to drop 2 of 3 tests (where #3 also by chance covered #1 \n#2) in some other PR, because too many tests is also evil.\n\nTo verify: I see 13 git commands that could benefit from \n--pathspec-from-file. There are 6 tests that in fact test underlying \nmachinery, which can't be easily influenced by bugs in command's code. \nThat makes 12*6 = 72 tests that are copy&pasted and doesn't test \nanything new.\n\nDo you suggest to return _all_ tests back into every command? (but also \nkeep the new direct tests, I assume)\n"},{"id":"388529","messageId":"4eb804d5-863e-540d-0017-72d9402e911a@syntevo.com","threadId":"52440","inReplyTo":"xmqqeex1cmam.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 04/18] commit: forbid --pathspec-from-file --all","fromName":"Alexandr Miloslavskiy","fromEmail":"alexandr.miloslavskiy@syntevo.com","sentAt":"2019-12-19T17:38:09Z","receivedAt":"2019-12-19T17:38:15Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"On 18.12.2019 23:16, Junio C Hamano wrote:\n\n> Here is what I'll queue directly on top of am/pathspec-from-file\n> e440fc58 (\"commit: support the --pathspec-from-file option\",\n> 2019-11-19), to be fast-tracked.\n\nRoger.\n\nI will keep my branch as is currently and will rebase it on top of your \npatch once it's in master.\n\n"},{"id":"388533","messageId":"14d30dd0e17d5e017dea05e4751f378f2128be14.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 02/18] t7526: add tests for error conditions","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:39Z","receivedAt":"2019-12-19T18:02:01Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nSuggested-By: Phillip Wood <phillip.wood@dunelm.org.uk>\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n t/t7526-commit-pathspec-file.sh | 24 ++++++++++++++++++++++++\n 1 file changed, 24 insertions(+)\n\ndiff --git a/t/t7526-commit-pathspec-file.sh b/t/t7526-commit-pathspec-file.sh\nindex 4e592f7472..b71c1013e7 100755\n--- a/t/t7526-commit-pathspec-file.sh\n+++ b/t/t7526-commit-pathspec-file.sh\n@@ -61,4 +61,28 @@ test_expect_success 'only touches what was listed' '\n \tverify_expect\n '\n \n+test_expect_success 'error conditions' '\n+\trestore_checkpoint &&\n+\techo fileA.t >list &&\n+\t>empty_list &&\n+\n+\ttest_must_fail git commit --pathspec-from-file=- --interactive -m \"Commit\" <list 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --interactive/--patch\" err &&\n+\n+\ttest_must_fail git commit --pathspec-from-file=- --patch -m \"Commit\" <list 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --interactive/--patch\" err &&\n+\n+\ttest_must_fail git commit --pathspec-from-file=- -m \"Commit\" -- fileA.t <list 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with pathspec arguments\" err &&\n+\n+\ttest_must_fail git commit --pathspec-file-nul -m \"Commit\" 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-file-nul requires --pathspec-from-file\" err &&\n+\n+\ttest_must_fail git commit --pathspec-from-file=- --include -m \"Commit\" <empty_list 2>err &&\n+\ttest_i18ngrep -e \"No paths with --include/--only does not make sense.\" err &&\n+\n+\ttest_must_fail git commit --pathspec-from-file=- --only -m \"Commit\" <empty_list 2>err &&\n+\ttest_i18ngrep -e \"No paths with --include/--only does not make sense.\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"388534","messageId":"8526d1d805517ed52e2d0373368cd6d45d53f404.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 01/18] t7107, t7526: directly test parse_pathspec_file()","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:38Z","receivedAt":"2019-12-19T18:02:02Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nIn my previous patches, `parse_pathspec_file()` was tested indirectly by\ninvoking `git reset` and `git commit` with properly crafted inputs. This\nhas some disadvantages:\n1) A number of tests are copy&pasted for every command where\n   `--pathspec-from-file` is supported. With just two commands, it\n   wasn't too bad, but I'm going to extend support to many more\n   commands, which would make a handful of low-value tests.\n2) Tests are located in suboptimal test packages\n3) Tests are indirect\n\nFix this by testing `parse_pathspec_file()` directly via a new test\nhelper.\n\nWhile working on it, I also noticed that quotes testing via\n`\"\\\"file\\\\101.t\\\"\"` was somewhat incorrect: I escaped `\\` one time while\nI had to escape it two times! Tests still worked due to `\"` which\nprevented pathspec from matching files.\n\nFix this by properly escaping one more time.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Makefile                            |  1 +\n t/helper/test-parse-pathspec-file.c | 34 +++++++++++\n t/helper/test-tool.c                |  1 +\n t/helper/test-tool.h                |  1 +\n t/t0067-parse_pathspec_file.sh      | 89 +++++++++++++++++++++++++++\n t/t7107-reset-pathspec-file.sh      | 94 +++--------------------------\n t/t7526-commit-pathspec-file.sh     | 70 +--------------------\n 7 files changed, 136 insertions(+), 154 deletions(-)\n create mode 100644 t/helper/test-parse-pathspec-file.c\n create mode 100755 t/t0067-parse_pathspec_file.sh\n\ndiff --git a/Makefile b/Makefile\nindex 42a061d3fb..24d0271709 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -721,6 +721,7 @@ TEST_BUILTINS_OBJS += test-mktemp.o\n TEST_BUILTINS_OBJS += test-oidmap.o\n TEST_BUILTINS_OBJS += test-online-cpus.o\n TEST_BUILTINS_OBJS += test-parse-options.o\n+TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\ndiff --git a/t/helper/test-parse-pathspec-file.c b/t/helper/test-parse-pathspec-file.c\nnew file mode 100644\nindex 0000000000..e7f525feb9\n--- /dev/null\n+++ b/t/helper/test-parse-pathspec-file.c\n@@ -0,0 +1,34 @@\n+#include \"test-tool.h\"\n+#include \"parse-options.h\"\n+#include \"pathspec.h\"\n+#include \"gettext.h\"\n+\n+int cmd__parse_pathspec_file(int argc, const char **argv)\n+{\n+\tstruct pathspec pathspec;\n+\tconst char *pathspec_from_file = 0;\n+\tint pathspec_file_nul = 0, i;\n+\n+\tstatic const char *const usage[] = {\n+\t\t\"test-tool parse-pathspec-file --pathspec-from-file [--pathspec-file-nul]\",\n+\t\tNULL\n+\t};\n+\n+\tstruct option options[] = {\n+\t\tOPT_PATHSPEC_FROM_FILE(&pathspec_from_file),\n+\t\tOPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),\n+\t\tOPT_END()\n+\t};\n+\n+\tparse_options(argc, argv, 0, options, usage, 0);\n+\n+\tparse_pathspec_file(&pathspec, 0, 0, 0, pathspec_from_file,\n+\t\t\t    pathspec_file_nul);\n+\n+\tfor (i = 0; i < pathspec.nr; i++) {\n+\t\tprintf(\"%s\\n\", pathspec.items[i].original);\n+\t}\n+\n+\tclear_pathspec(&pathspec);\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f20989d449..c9a232d238 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -39,6 +39,7 @@ static struct test_cmd cmds[] = {\n \t{ \"oidmap\", cmd__oidmap },\n \t{ \"online-cpus\", cmd__online_cpus },\n \t{ \"parse-options\", cmd__parse_options },\n+\t{ \"parse-pathspec-file\", cmd__parse_pathspec_file },\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 8ed2af71d1..c8549fd87f 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -29,6 +29,7 @@ int cmd__mktemp(int argc, const char **argv);\n int cmd__oidmap(int argc, const char **argv);\n int cmd__online_cpus(int argc, const char **argv);\n int cmd__parse_options(int argc, const char **argv);\n+int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\ndiff --git a/t/t0067-parse_pathspec_file.sh b/t/t0067-parse_pathspec_file.sh\nnew file mode 100755\nindex 0000000000..df7b319713\n--- /dev/null\n+++ b/t/t0067-parse_pathspec_file.sh\n@@ -0,0 +1,89 @@\n+#!/bin/sh\n+\n+test_description='Test parse_pathspec_file()'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'one item from stdin' '\n+\techo fileA.t | test-tool parse-pathspec-file --pathspec-from-file=- >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'one item from file' '\n+\techo fileA.t >list &&\n+\ttest-tool parse-pathspec-file --pathspec-from-file=list >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'NUL delimiters' '\n+\tprintf \"fileA.t\\0fileB.t\\0\" | test-tool parse-pathspec-file --pathspec-from-file=- --pathspec-file-nul >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tfileB.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'LF delimiters' '\n+\tprintf \"fileA.t\\nfileB.t\\n\" | test-tool parse-pathspec-file --pathspec-from-file=- >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tfileB.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'no trailing delimiter' '\n+\tprintf \"fileA.t\\nfileB.t\" | test-tool parse-pathspec-file --pathspec-from-file=- >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tfileB.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'CRLF delimiters' '\n+\tprintf \"fileA.t\\r\\nfileB.t\\r\\n\" | test-tool parse-pathspec-file --pathspec-from-file=- >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tfileB.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'quotes' '\n+\t# shell  takes \\\\\\\\101 and spits \\\\101\n+\t# printf takes   \\\\101 and spits  \\101\n+\t# git    takes    \\101 and spits     A\n+\tprintf \"\\\"file\\\\\\\\101.t\\\"\" | test-tool parse-pathspec-file --pathspec-from-file=- >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tfileA.t\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '--pathspec-file-nul takes quotes literally' '\n+\t# shell  takes \\\\\\\\101 and spits \\\\101\n+\t# printf takes   \\\\101 and spits  \\101\n+\tprintf \"\\\"file\\\\\\\\101.t\\\"\" | test-tool parse-pathspec-file --pathspec-from-file=- --pathspec-file-nul >actual &&\n+\n+\tcat >expect <<-\\EOF &&\n+\t\"file\\101.t\"\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t7107-reset-pathspec-file.sh b/t/t7107-reset-pathspec-file.sh\nindex 6b1a731fff..f36fce27b9 100755\n--- a/t/t7107-reset-pathspec-file.sh\n+++ b/t/t7107-reset-pathspec-file.sh\n@@ -25,7 +25,7 @@ verify_expect () {\n \ttest_cmp expect actual\n }\n \n-test_expect_success '--pathspec-from-file from stdin' '\n+test_expect_success 'simplest' '\n \trestore_checkpoint &&\n \n \tgit rm fileA.t &&\n@@ -37,20 +37,7 @@ test_expect_success '--pathspec-from-file from stdin' '\n \tverify_expect\n '\n \n-test_expect_success '--pathspec-from-file from file' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t &&\n-\techo fileA.t >list &&\n-\tgit reset --pathspec-from-file=list &&\n-\n-\tcat >expect <<-\\EOF &&\n-\t D fileA.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'NUL delimiters' '\n+test_expect_success '--pathspec-file-nul' '\n \trestore_checkpoint &&\n \n \tgit rm fileA.t fileB.t &&\n@@ -63,71 +50,21 @@ test_expect_success 'NUL delimiters' '\n \tverify_expect\n '\n \n-test_expect_success 'LF delimiters' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t fileB.t &&\n-\tprintf \"fileA.t\\nfileB.t\\n\" | git reset --pathspec-from-file=- &&\n-\n-\tcat >expect <<-\\EOF &&\n-\t D fileA.t\n-\t D fileB.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'no trailing delimiter' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t fileB.t &&\n-\tprintf \"fileA.t\\nfileB.t\" | git reset --pathspec-from-file=- &&\n-\n-\tcat >expect <<-\\EOF &&\n-\t D fileA.t\n-\t D fileB.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'CRLF delimiters' '\n+test_expect_success 'only touches what was listed' '\n \trestore_checkpoint &&\n \n-\tgit rm fileA.t fileB.t &&\n-\tprintf \"fileA.t\\r\\nfileB.t\\r\\n\" | git reset --pathspec-from-file=- &&\n+\tgit rm fileA.t fileB.t fileC.t fileD.t &&\n+\tprintf \"fileB.t\\nfileC.t\\n\" | git reset --pathspec-from-file=- &&\n \n \tcat >expect <<-\\EOF &&\n-\t D fileA.t\n+\tD  fileA.t\n \t D fileB.t\n+\t D fileC.t\n+\tD  fileD.t\n \tEOF\n \tverify_expect\n '\n \n-test_expect_success 'quotes' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t &&\n-\tprintf \"\\\"file\\\\101.t\\\"\" | git reset --pathspec-from-file=- &&\n-\n-\tcat >expect <<-\\EOF &&\n-\t D fileA.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'quotes not compatible with --pathspec-file-nul' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t &&\n-\tprintf \"\\\"file\\\\101.t\\\"\" >list &&\n-\t# Note: \"git reset\" has not yet learned to fail on wrong pathspecs\n-\tgit reset --pathspec-from-file=list --pathspec-file-nul &&\n-\n-\tcat >expect <<-\\EOF &&\n-\t D fileA.t\n-\tEOF\n-\ttest_must_fail verify_expect\n-'\n-\n test_expect_success '--pathspec-from-file is not compatible with --soft or --hard' '\n \trestore_checkpoint &&\n \n@@ -137,19 +74,4 @@ test_expect_success '--pathspec-from-file is not compatible with --soft or --har\n \ttest_must_fail git reset --hard --pathspec-from-file=list\n '\n \n-test_expect_success 'only touches what was listed' '\n-\trestore_checkpoint &&\n-\n-\tgit rm fileA.t fileB.t fileC.t fileD.t &&\n-\tprintf \"fileB.t\\nfileC.t\\n\" | git reset --pathspec-from-file=- &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tD  fileA.t\n-\t D fileB.t\n-\t D fileC.t\n-\tD  fileD.t\n-\tEOF\n-\tverify_expect\n-'\n-\n test_done\ndiff --git a/t/t7526-commit-pathspec-file.sh b/t/t7526-commit-pathspec-file.sh\nindex a06b683534..4e592f7472 100755\n--- a/t/t7526-commit-pathspec-file.sh\n+++ b/t/t7526-commit-pathspec-file.sh\n@@ -26,7 +26,7 @@ verify_expect () {\n \ttest_cmp expect actual\n }\n \n-test_expect_success '--pathspec-from-file from stdin' '\n+test_expect_success 'simplest' '\n \trestore_checkpoint &&\n \n \techo fileA.t | git commit --pathspec-from-file=- -m \"Commit\" &&\n@@ -37,19 +37,7 @@ test_expect_success '--pathspec-from-file from stdin' '\n \tverify_expect\n '\n \n-test_expect_success '--pathspec-from-file from file' '\n-\trestore_checkpoint &&\n-\n-\techo fileA.t >list &&\n-\tgit commit --pathspec-from-file=list -m \"Commit\" &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tA\tfileA.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'NUL delimiters' '\n+test_expect_success '--pathspec-file-nul' '\n \trestore_checkpoint &&\n \n \tprintf \"fileA.t\\0fileB.t\\0\" | git commit --pathspec-from-file=- --pathspec-file-nul -m \"Commit\" &&\n@@ -61,60 +49,6 @@ test_expect_success 'NUL delimiters' '\n \tverify_expect\n '\n \n-test_expect_success 'LF delimiters' '\n-\trestore_checkpoint &&\n-\n-\tprintf \"fileA.t\\nfileB.t\\n\" | git commit --pathspec-from-file=- -m \"Commit\" &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tA\tfileA.t\n-\tA\tfileB.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'no trailing delimiter' '\n-\trestore_checkpoint &&\n-\n-\tprintf \"fileA.t\\nfileB.t\" | git commit --pathspec-from-file=- -m \"Commit\" &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tA\tfileA.t\n-\tA\tfileB.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'CRLF delimiters' '\n-\trestore_checkpoint &&\n-\n-\tprintf \"fileA.t\\r\\nfileB.t\\r\\n\" | git commit --pathspec-from-file=- -m \"Commit\" &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tA\tfileA.t\n-\tA\tfileB.t\n-\tEOF\n-\tverify_expect\n-'\n-\n-test_expect_success 'quotes' '\n-\trestore_checkpoint &&\n-\n-\tprintf \"\\\"file\\\\101.t\\\"\" | git commit --pathspec-from-file=- -m \"Commit\" &&\n-\n-\tcat >expect <<-\\EOF &&\n-\tA\tfileA.t\n-\tEOF\n-\tverify_expect expect\n-'\n-\n-test_expect_success 'quotes not compatible with --pathspec-file-nul' '\n-\trestore_checkpoint &&\n-\n-\tprintf \"\\\"file\\\\101.t\\\"\" >list &&\n-\ttest_must_fail git commit --pathspec-from-file=list --pathspec-file-nul -m \"Commit\"\n-'\n-\n test_expect_success 'only touches what was listed' '\n \trestore_checkpoint &&\n \n-- \ngitgitgadget\n\n"},{"id":"388538","messageId":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v2.git.1576511287.gitgitgadget@gmail.com","subject":"[PATCH v3 00/18] Extend --pathspec-from-file to git add, checkout","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:37Z","receivedAt":"2019-12-19T18:02:03Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"This topic continues the effort to support `--pathspec-from-file` in various\ncommands [1][2]. It also includes some refactorings that I developed while\nworking on it - previously submitted separately [3][4] which was probably a\nmistake.\n\nAnatomy of the branch:\n  checkout, restore: support the --pathspec-from-file option\n    Extends `--pathspec-from-file` to `git checkout/restore`.\n\n  t2024: cover more cases\n    Some new tests for cases that I deemed worthy while working\n    on `parse_branchname_arg()` refactoring.\n\n  parse_branchname_arg(): refactor the decision making\n  parse_branchname_arg(): update code comments\n  parse_branchname_arg(): introduce expect_commit_only\n  parse_branchname_arg(): easier to understand variables\n    These patches prepare for `|| opts->pathspec_from_file` addition in\n    `git checkout/restore` patch. Without this refactoring, I found it\n    pretty hard to modify the old code.\n\n  checkout: die() on ambiguous tracking branches\n  parse_branchname_arg(): extract part as new function\n    Initially I was trying to remove some inconsistency standing in the\n    way of `git checkout/restore` patch. Later I figured that this change\n    is worthy on its own: it prevents some pretty surprising behavior of\n    git.\n\n  doc: restore: synchronize <pathspec> description\n  doc: checkout: synchronize <pathspec> description\n  doc: checkout: fix broken text reference\n  doc: checkout: remove duplicate synopsis\n    Some polishing of docs in preparation for `git checkout/restore` patch.\n\n  add: support the --pathspec-from-file option\n  cmd_add: prepare for next patch\n    Extends `--pathspec-from-file` to `git add`.\n\n  commit: forbid --pathspec-from-file --all\n  t7107, t7526: directly test parse_pathspec_file()\n    Some polishing of merged topic [1].\n\nCC'ing people who shown interest in any of the previous topics, thanks for\nyour reviews!\n\n[1] https://public-inbox.org/git/pull.445.git.1572895605.gitgitgadget@gmail.com/\n[2] https://public-inbox.org/git/20191204203911.237056-1-emilyshaffer@google.com/\n[3] https://public-inbox.org/git/pull.477.git.1574848137.gitgitgadget@gmail.com/\n[4] https://public-inbox.org/git/pull.479.git.1574969538.gitgitgadget@gmail.com/\n\nChanges since V1:\n----------------\n@Junio please note that V1 was already substantially different from\nwhat you merged into `next`.\n\n1) Added tests for error scenarios related to --pathspec-from-file.\n2) Restored tests for --pathspec-file-nul: they are valuable for every\n   command, because they verify that specific commands handle the\n   commandline option correctly.\n3) Dropped old tests for `git restore` that I forgot to delete when I\n   made commit `t7107, t7526: directly test parse_pathspec_file()`.\n\nChanges since V2:\n----------------\nRebased branch on top of modern master.\nImproved code according to code review suggestions in [4] and this topic:\n1) Shuffled changes between `parse_branchname_arg()` commits\n2) New commit `parse_branchname_arg(): simplify argument eating` with a detailed commit message.\n3) Further improved commit `parse_branchname_arg(): easier to understand variables`\n4) Fixed an oversight where after refactoring, `parse_branchname_arg()` could eat `--` in `git switch` - this is more of a theoretical problem because `--` is not expected there anyway.\n5) Changed aggressive `\\-\\-` escaping in tests to use `test_i18ngrep -e` instead\n\nAlexandr Miloslavskiy (18):\n  t7107, t7526: directly test parse_pathspec_file()\n  t7526: add tests for error conditions\n  t7107: add tests for error conditions\n  commit: forbid --pathspec-from-file --all\n  cmd_add: prepare for next patch\n  add: support the --pathspec-from-file option\n  doc: checkout: remove duplicate synopsis\n  doc: checkout: fix broken text reference\n  doc: checkout: synchronize <pathspec> description\n  doc: restore: synchronize <pathspec> description\n  parse_branchname_arg(): extract part as new function\n  checkout: die() on ambiguous tracking branches\n  parse_branchname_arg(): easier to understand variables\n  parse_branchname_arg(): simplify argument eating\n  parse_branchname_arg(): update code comments\n  parse_branchname_arg(): refactor the decision making\n  t2024: cover more cases\n  checkout, restore: support the --pathspec-from-file option\n\n Documentation/git-add.txt           |  16 +-\n Documentation/git-checkout.txt      |  50 +++--\n Documentation/git-restore.txt       |  26 ++-\n Makefile                            |   1 +\n builtin/add.c                       |  60 ++++--\n builtin/checkout.c                  | 277 ++++++++++++++--------------\n builtin/commit.c                    |   3 +\n t/helper/test-parse-pathspec-file.c |  34 ++++\n t/helper/test-tool.c                |   1 +\n t/helper/test-tool.h                |   1 +\n t/t0067-parse_pathspec_file.sh      |  89 +++++++++\n t/t2024-checkout-dwim.sh            |  55 +++++-\n t/t2026-checkout-pathspec-file.sh   |  90 +++++++++\n t/t2072-restore-pathspec-file.sh    |  91 +++++++++\n t/t3704-add-pathspec-file.sh        |  86 +++++++++\n t/t7107-reset-pathspec-file.sh      |  98 ++--------\n t/t7526-commit-pathspec-file.sh     |  83 +++------\n t/t9902-completion.sh               |   2 +\n 18 files changed, 742 insertions(+), 321 deletions(-)\n create mode 100644 t/helper/test-parse-pathspec-file.c\n create mode 100755 t/t0067-parse_pathspec_file.sh\n create mode 100755 t/t2026-checkout-pathspec-file.sh\n create mode 100755 t/t2072-restore-pathspec-file.sh\n create mode 100755 t/t3704-add-pathspec-file.sh\n\n\nbase-commit: 12029dc57db23baef008e77db1909367599210ee\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-490%2FSyntevoAlex%2F%230207_pathspec_from_file_2-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-490/SyntevoAlex/#0207_pathspec_from_file_2-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/490\n\nRange-diff vs v2:\n\n  1:  8d5fb9f40b =  1:  8526d1d805 t7107, t7526: directly test parse_pathspec_file()\n  2:  c7cd46d3a3 !  2:  14d30dd0e1 t7526: add tests for error conditions\n     @@ -18,22 +18,22 @@\n      +\t>empty_list &&\n      +\n      +\ttest_must_fail git commit --pathspec-from-file=- --interactive -m \"Commit\" <list 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-interactive/\\-\\-patch\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --interactive/--patch\" err &&\n      +\n      +\ttest_must_fail git commit --pathspec-from-file=- --patch -m \"Commit\" <list 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-interactive/\\-\\-patch\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --interactive/--patch\" err &&\n      +\n      +\ttest_must_fail git commit --pathspec-from-file=- -m \"Commit\" -- fileA.t <list 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with pathspec arguments\" err &&\n      +\n      +\ttest_must_fail git commit --pathspec-file-nul -m \"Commit\" 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-file-nul requires \\-\\-pathspec-from-file\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-file-nul requires --pathspec-from-file\" err &&\n      +\n      +\ttest_must_fail git commit --pathspec-from-file=- --include -m \"Commit\" <empty_list 2>err &&\n     -+\ttest_i18ngrep \"No paths with \\-\\-include/\\-\\-only does not make sense.\" err &&\n     ++\ttest_i18ngrep -e \"No paths with --include/--only does not make sense.\" err &&\n      +\n      +\ttest_must_fail git commit --pathspec-from-file=- --only -m \"Commit\" <empty_list 2>err &&\n     -+\ttest_i18ngrep \"No paths with \\-\\-include/\\-\\-only does not make sense.\" err\n     ++\ttest_i18ngrep -e \"No paths with --include/--only does not make sense.\" err\n      +'\n      +\n       test_done\n  3:  b09d74c347 !  3:  b1cc4a960d t7107: add tests for error conditions\n     @@ -17,13 +17,13 @@\n      +\techo fileA.t >list &&\n      +\n      +\ttest_must_fail git reset --pathspec-from-file=- --patch <list 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-patch\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --patch\" err &&\n      +\n      +\ttest_must_fail git reset --pathspec-from-file=- -- fileA.t <list 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with pathspec arguments\" err &&\n      +\n      +\ttest_must_fail git reset --pathspec-file-nul 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-file-nul requires \\-\\-pathspec-from-file\" err\n     ++\ttest_i18ngrep -e \"--pathspec-file-nul requires --pathspec-from-file\" err\n      +'\n      +\n       test_done\n  4:  deeb860a85 !  4:  f0be90601e commit: forbid --pathspec-from-file --all\n     @@ -42,11 +42,11 @@\n       +++ b/t/t7526-commit-pathspec-file.sh\n      @@\n       \ttest_must_fail git commit --pathspec-from-file=- --patch -m \"Commit\" <list 2>err &&\n     - \ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-interactive/\\-\\-patch\" err &&\n     + \ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --interactive/--patch\" err &&\n       \n      +\ttest_must_fail git commit --pathspec-from-file=- --all -m \"Commit\" <list 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-from-file with \\-a does not make sense\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-from-file with -a does not make sense\" err &&\n      +\n       \ttest_must_fail git commit --pathspec-from-file=- -m \"Commit\" -- fileA.t <list 2>err &&\n     - \ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n     + \ttest_i18ngrep -e \"--pathspec-from-file is incompatible with pathspec arguments\" err &&\n       \n  5:  204a0a4446 =  5:  2153350ac4 cmd_add: prepare for next patch\n  6:  1ea5f17847 !  6:  c8e59903f7 add: support the --pathspec-from-file option\n     @@ -186,23 +186,23 @@\n      +\t>empty_list &&\n      +\n      +\ttest_must_fail git add --pathspec-from-file=- --interactive <list 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-interactive/\\-\\-patch\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --interactive/--patch\" err &&\n      +\n      +\ttest_must_fail git add --pathspec-from-file=- --patch <list 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-interactive/\\-\\-patch\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --interactive/--patch\" err &&\n      +\n      +\ttest_must_fail git add --pathspec-from-file=- --edit <list 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-edit\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --edit\" err &&\n      +\n      +\ttest_must_fail git add --pathspec-from-file=- -- fileA.t <list 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with pathspec arguments\" err &&\n      +\n      +\ttest_must_fail git add --pathspec-file-nul 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-file-nul requires \\-\\-pathspec-from-file\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-file-nul requires --pathspec-from-file\" err &&\n      +\t\n      +\t# This case succeeds, but still prints to stderr\n      +\tgit add --pathspec-from-file=- <empty_list 2>err &&\n     -+\ttest_i18ngrep \"Nothing specified, nothing added.\" err\n     ++\ttest_i18ngrep -e \"Nothing specified, nothing added.\" err\n      +'\n      +\n      +test_done\n  7:  3d0fcf6ba5 =  7:  5a4a538fa6 doc: checkout: remove duplicate synopsis\n  8:  85f7ccc4e0 =  8:  daf0d6c536 doc: checkout: fix broken text reference\n  9:  db6e40d004 =  9:  1d981b199f doc: checkout: synchronize <pathspec> description\n 10:  c88cbf453a = 10:  137b697327 doc: restore: synchronize <pathspec> description\n 11:  2c23bd602d = 11:  f53bb13e43 parse_branchname_arg(): extract part as new function\n 12:  efd6876874 = 12:  58b65d2011 checkout: die() on ambiguous tracking branches\n 14:  2350dc282e ! 13:  dd35cad0d9 parse_branchname_arg(): introduce expect_commit_only\n     @@ -1,8 +1,13 @@\n      Author: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n      \n     -    parse_branchname_arg(): introduce expect_commit_only\n     +    parse_branchname_arg(): easier to understand variables\n      \n     -    `has_dash_dash` unexpectedly takes `opts->accept_pathspec` into account.\n     +    `dash_dash_pos` was only calculated under `opts->accept_pathspec`. This\n     +    is unexpected to readers and made it harder to reason about the code.\n     +\n     +    Fix this by restoring the expected meaning.\n     +\n     +    `has_dash_dash` also takes `opts->accept_pathspec` into account.\n          While this may sound clever at first sight, it becomes pretty hard to\n          reason (and not be a victim) about code, especially in combination with\n          `argc` here:\n     @@ -12,10 +17,11 @@\n                      opts->accept_pathspec)\n                          recover_with_dwim = 0;\n      \n     -    Introduce a new non-obfuscated variable to reduce the amount of diffs in\n     -    next patch.\n     +    Fix this by giving variable a better name and rewriting the above\n     +    mentioned condition (it's easier to verify if you notice that it only\n     +    holds when `opts->accept_pathspec` is true).\n      \n     -    This should not change behavior in any way.\n     +    This patch is not expected to change behavior in any way.\n      \n          Signed-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n      \n     @@ -23,23 +29,49 @@\n       --- a/builtin/checkout.c\n       +++ b/builtin/checkout.c\n      @@\n     - \tconst char **new_branch = &opts->new_branch;\n     + \tint argcount = 0;\n       \tconst char *arg;\n       \tint dash_dash_pos;\n      -\tint has_dash_dash = 0;\n     -+\tint has_dash_dash = 0, expect_commit_only = 0;\n     ++\tint arg0_cant_be_pathspec = 0;\n       \tint i;\n       \n       \t/*\n      @@\n     - \t\t    die(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n     + \tif (!opts->accept_pathspec) {\n     + \t\tif (argc > 1)\n     + \t\t\tdie(_(\"only one reference expected\"));\n     +-\t\thas_dash_dash = 1; /* helps disambiguate */\n     ++\t\targ0_cant_be_pathspec = 1;\n       \t}\n       \n     + \targ = argv[0];\n     + \tdash_dash_pos = -1;\n     + \tfor (i = 0; i < argc; i++) {\n     +-\t\tif (opts->accept_pathspec && !strcmp(argv[i], \"--\")) {\n     ++\t\tif (!strcmp(argv[i], \"--\")) {\n     + \t\t\tdash_dash_pos = i;\n     + \t\t\tbreak;\n     + \t\t}\n     + \t}\n     +-\tif (dash_dash_pos == 0)\n     +-\t\treturn 1; /* case (2) */\n     +-\telse if (dash_dash_pos == 1)\n     +-\t\thas_dash_dash = 1; /* case (3) or (1) */\n     +-\telse if (dash_dash_pos >= 2)\n     +-\t\tdie(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n      -\topts->count_checkout_paths = !opts->quiet && !has_dash_dash;\n     -+\tif (has_dash_dash)\n     -+\t    expect_commit_only = 1;\n      +\n     -+\topts->count_checkout_paths = !opts->quiet && !expect_commit_only;\n     ++\tif (opts->accept_pathspec) {\n     ++\t\tif (dash_dash_pos == 0)\n     ++\t\t\treturn 1; /* case (2) */\n     ++\t\telse if (dash_dash_pos == 1)\n     ++\t\t\targ0_cant_be_pathspec = 1; /* case (3) or (1) */\n     ++\t\telse if (dash_dash_pos >= 2)\n     ++\t\t\tdie(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n     ++\t}\n     ++\n     ++\topts->count_checkout_paths = !opts->quiet && !arg0_cant_be_pathspec;\n       \n       \tif (!strcmp(arg, \"-\"))\n       \t\targ = \"@{-1}\";\n     @@ -48,29 +80,39 @@\n       \t\tint recover_with_dwim = dwim_new_local_branch_ok;\n       \n      -\t\tint could_be_checkout_paths = !has_dash_dash &&\n     -+\t\tint could_be_checkout_paths = !expect_commit_only &&\n     ++\t\tint could_be_checkout_paths = !arg0_cant_be_pathspec &&\n       \t\t\tcheck_filename(opts->prefix, arg);\n       \n      -\t\tif (!has_dash_dash && !no_wildcard(arg))\n     -+\t\tif (!expect_commit_only && !no_wildcard(arg))\n     ++\t\tif (!arg0_cant_be_pathspec && !no_wildcard(arg))\n       \t\t\trecover_with_dwim = 0;\n       \n       \t\t/*\n     + \t\t * Accept \"git checkout foo\", \"git checkout foo --\"\n     + \t\t * and \"git switch foo\" as candidates for dwim.\n     + \t\t */\n     +-\t\tif (!(argc == 1 && !has_dash_dash) &&\n     +-\t\t    !(argc == 2 && has_dash_dash) &&\n     ++\t\tif (!(argc == 1 && dash_dash_pos == -1) &&\n     ++\t\t    !(argc == 2 && dash_dash_pos == 1) &&\n     + \t\t    opts->accept_pathspec)\n     + \t\t\trecover_with_dwim = 0;\n     + \n      @@\n       \t\t}\n       \n       \t\tif (!recover_with_dwim) {\n      -\t\t\tif (has_dash_dash)\n     -+\t\t\tif (expect_commit_only)\n     ++\t\t\tif (arg0_cant_be_pathspec)\n       \t\t\t\tdie(_(\"invalid reference: %s\"), arg);\n     - \t\t\treturn 0;\n     + \t\t\treturn argcount;\n       \t\t}\n      @@\n       \tif (!opts->source_tree)                   /* case (1): want a tree */\n       \t\tdie(_(\"reference is not a tree: %s\"), arg);\n       \n      -\tif (!has_dash_dash) {\t/* case (3).(d) -> (1) */\n     -+\tif (!expect_commit_only) {\t/* case (3).(d) -> (1) */\n     ++\tif (!arg0_cant_be_pathspec) {\t/* case (3).(d) -> (1) */\n       \t\t/*\n       \t\t * Do not complain the most common case\n       \t\t *\tgit checkout branch\n 13:  2498825230 ! 14:  dc6e351796 parse_branchname_arg(): easier to understand variables\n     @@ -1,15 +1,27 @@\n      Author: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n      \n     -    parse_branchname_arg(): easier to understand variables\n     +    parse_branchname_arg(): simplify argument eating\n      \n     -    `dash_dash_pos` was only calculated under `opts->accept_pathspec`. This\n     -    is unexpected to readers and made it harder to reason about the code.\n     -    Fix this by restoring the expected meaning.\n     +    This patch abolishes two pieces of obfuscated code.\n      \n     -    Simplify the code by dropping `argcount` and useless `argc` / `argv`\n     -    manipulations.\n     +    First is `if (argc)` condition, which in fact means \"more then one arg\"\n     +    due to `argc++` above.\n      \n     -    This should not change behavior in any way.\n     +    Second is by far harder to grasp:\n     +        if (!arg0_cant_be_pathspec) {...} else if (opts->accept_pathspec)\n     +    that is,\n     +            if (opts->accept_pathspec && arg0_cant_be_pathspec)\n     +    which (quite unexpectedly) actually means\n     +        if (opts->accept_pathspec && dash_dash_pos == 1)\n     +    and aims to \"eat\" that `--`.\n     +\n     +    Make both pieces easier to read by rewriting obfuscated conditions.\n     +\n     +    With both solved, I could keep argcount++/argv++/argc-- in the very end\n     +    of the function, but that was obviously useless code in this case, so I\n     +    deleted them as well.\n     +\n     +    This patch is not expected to change behavior in any way.\n      \n          Signed-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n      \n     @@ -23,39 +35,10 @@\n      -\tint argcount = 0;\n       \tconst char *arg;\n       \tint dash_dash_pos;\n     - \tint has_dash_dash = 0;\n     -@@\n     - \targ = argv[0];\n     - \tdash_dash_pos = -1;\n     - \tfor (i = 0; i < argc; i++) {\n     --\t\tif (opts->accept_pathspec && !strcmp(argv[i], \"--\")) {\n     -+\t\tif (!strcmp(argv[i], \"--\")) {\n     - \t\t\tdash_dash_pos = i;\n     - \t\t\tbreak;\n     - \t\t}\n     - \t}\n     --\tif (dash_dash_pos == 0)\n     --\t\treturn 1; /* case (2) */\n     --\telse if (dash_dash_pos == 1)\n     --\t\thas_dash_dash = 1; /* case (3) or (1) */\n     --\telse if (dash_dash_pos >= 2)\n     --\t\tdie(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n     -+\n     -+\tif (opts->accept_pathspec) {\n     -+\t    if (dash_dash_pos == 0)\n     -+\t\t    return 1; /* case (2) */\n     -+\t    else if (dash_dash_pos == 1)\n     -+\t\t    has_dash_dash = 1; /* case (3) or (1) */\n     -+\t    else if (dash_dash_pos >= 2)\n     -+\t\t    die(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n     -+\t}\n     -+\n     - \topts->count_checkout_paths = !opts->quiet && !has_dash_dash;\n     - \n     - \tif (!strcmp(arg, \"-\"))\n     + \tint arg0_cant_be_pathspec = 0;\n      @@\n       \t\tif (!recover_with_dwim) {\n     - \t\t\tif (has_dash_dash)\n     + \t\t\tif (arg0_cant_be_pathspec)\n       \t\t\t\tdie(_(\"invalid reference: %s\"), arg);\n      -\t\t\treturn argcount;\n      +\t\t\treturn 0;\n     @@ -84,7 +67,7 @@\n       \t}\n       \n      -\treturn argcount;\n     -+\treturn (dash_dash_pos == 1) ? 2 : 1;\n     ++\treturn (opts->accept_pathspec && dash_dash_pos == 1) ? 2 : 1;\n       }\n       \n       static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n 15:  46f676b8e0 ! 15:  7989f0c5cf parse_branchname_arg(): update code comments\n     @@ -80,14 +80,14 @@\n      @@\n       \n       \tif (opts->accept_pathspec) {\n     - \t    if (dash_dash_pos == 0)\n     --\t\t    return 1; /* case (2) */\n     -+\t\t    return 1;\n     - \t    else if (dash_dash_pos == 1)\n     --\t\t    has_dash_dash = 1; /* case (3) or (1) */\n     -+\t\t    has_dash_dash = 1;\n     - \t    else if (dash_dash_pos >= 2)\n     - \t\t    die(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n     + \t\tif (dash_dash_pos == 0)\n     +-\t\t\treturn 1; /* case (2) */\n     ++\t\t\treturn 1;\n     + \t\telse if (dash_dash_pos == 1)\n     +-\t\t\targ0_cant_be_pathspec = 1; /* case (3) or (1) */\n     ++\t\t\targ0_cant_be_pathspec = 1;\n     + \t\telse if (dash_dash_pos >= 2)\n     + \t\t\tdie(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n       \t}\n      @@\n       \t\targ = \"@{-1}\";\n     @@ -103,17 +103,17 @@\n      -\t\t */\n       \t\tint recover_with_dwim = dwim_new_local_branch_ok;\n       \n     - \t\tint could_be_checkout_paths = !expect_commit_only &&\n     + \t\tint could_be_checkout_paths = !arg0_cant_be_pathspec &&\n      @@\n     - \t\tif (!expect_commit_only && !no_wildcard(arg))\n     + \t\tif (!arg0_cant_be_pathspec && !no_wildcard(arg))\n       \t\t\trecover_with_dwim = 0;\n       \n      -\t\t/*\n      -\t\t * Accept \"git checkout foo\", \"git checkout foo --\"\n      -\t\t * and \"git switch foo\" as candidates for dwim.\n      -\t\t */\n     - \t\tif (!(argc == 1 && !has_dash_dash) &&\n     - \t\t    !(argc == 2 && has_dash_dash) &&\n     + \t\tif (!(argc == 1 && dash_dash_pos == -1) &&\n     + \t\t    !(argc == 2 && dash_dash_pos == 1) &&\n       \t\t    opts->accept_pathspec)\n      @@\n       \t\t\tif (remote) {\n     @@ -132,7 +132,7 @@\n      +\tif (!opts->source_tree)\n       \t\tdie(_(\"reference is not a tree: %s\"), arg);\n       \n     --\tif (!expect_commit_only) {\t/* case (3).(d) -> (1) */\n     +-\tif (!arg0_cant_be_pathspec) {\t/* case (3).(d) -> (1) */\n      -\t\t/*\n      -\t\t * Do not complain the most common case\n      -\t\t *\tgit checkout branch\n     @@ -142,8 +142,8 @@\n      -\t\tif (argc > 1)\n      -\t\t\tverify_non_filename(opts->prefix, arg);\n      -\t}\n     -+\tif (!expect_commit_only && argc > 1)\n     ++\tif (!arg0_cant_be_pathspec && argc > 1)\n      +\t\tverify_non_filename(opts->prefix, arg);\n       \n     - \treturn (dash_dash_pos == 1) ? 2 : 1;\n     + \treturn (opts->accept_pathspec && dash_dash_pos == 1) ? 2 : 1;\n       }\n 16:  319151e4e9 ! 16:  e0bdd5bd1e parse_branchname_arg(): refactor the decision making\n     @@ -34,10 +34,10 @@\n       \tconst char **new_branch = &opts->new_branch;\n       \tconst char *arg;\n      -\tint dash_dash_pos;\n     --\tint has_dash_dash = 0, expect_commit_only = 0;\n     +-\tint arg0_cant_be_pathspec = 0;\n      -\tint i;\n      +\tint dash_dash_pos, i;\n     -+\tint recover_with_dwim, expect_commit_only;\n     ++\tint recover_with_dwim, arg0_cant_be_pathspec;\n       \n       \t/*\n       \t * Resolve ambiguity where argv[0] may be <pathspec> or <commit>.\n     @@ -51,25 +51,22 @@\n      -\tif (!opts->accept_pathspec) {\n      -\t\tif (argc > 1)\n      -\t\t\tdie(_(\"only one reference expected\"));\n     --\t\thas_dash_dash = 1; /* helps disambiguate */\n     +-\t\targ0_cant_be_pathspec = 1;\n      -\t}\n     --\n     -+\t \n     + \n       \targ = argv[0];\n       \tdash_dash_pos = -1;\n     - \tfor (i = 0; i < argc; i++) {\n      @@\n       \t\t}\n       \t}\n       \n      -\tif (opts->accept_pathspec) {\n     --\t    if (dash_dash_pos == 0)\n     --\t\t    return 1;\n     --\t    else if (dash_dash_pos == 1)\n     --\t\t    has_dash_dash = 1;\n     --\t    else if (dash_dash_pos >= 2)\n     --\t\t    die(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n     --\t}\n     +-\t\tif (dash_dash_pos == 0)\n     +-\t\t\treturn 1;\n     +-\t\telse if (dash_dash_pos == 1)\n     +-\t\t\targ0_cant_be_pathspec = 1;\n     +-\t\telse if (dash_dash_pos >= 2)\n     +-\t\t\tdie(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n      +\tif (dash_dash_pos == -1) {\n      +\t\tif (argc == 0) {\n      +\t\t\t/* 'git checkout/switch/restore' */\n     @@ -89,7 +86,7 @@\n      +\t\t * Absence of '--' leaves <pathspec>/<commit> ambiguity.\n      +\t\t * Try to resolve it with additional knowledge about pathspec args.\n      +\t\t */\n     -+\t\texpect_commit_only = !opts->accept_pathspec;\n     ++\t\targ0_cant_be_pathspec = !opts->accept_pathspec;\n      +\t} else if (dash_dash_pos == 0) {\n      +\t\t/* 'git checkout/switch/restore -- [...]' */\n      +\t\treturn 1;  /* Eat '--' */\n     @@ -106,32 +103,29 @@\n      +\t\t\t/* 'git checkout/restore <commit> -- <pathspec> [...]' */\n      +\t\t\trecover_with_dwim = 0;\n      +\t\t}\n     - \n     --\tif (has_dash_dash)\n     --\t    expect_commit_only = 1;\n     ++\n      +\t\t/* Presence of '--' makes it certain that arg is <commit> */\n     -+\t\texpect_commit_only = 1;\n     ++\t\targ0_cant_be_pathspec = 1;\n      +\t} else {\n      +\t\t/* 'git checkout/switch/restore <commit> <unxpected> [...] -- [...]' */\n      +\t\tdie(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n     -+\t}\n     - \n     - \topts->count_checkout_paths = !opts->quiet && !expect_commit_only;\n     + \t}\n       \n     + \topts->count_checkout_paths = !opts->quiet && !arg0_cant_be_pathspec;\n      @@\n       \t\targ = \"@{-1}\";\n       \n       \tif (get_oid_mb(arg, rev)) {\n      -\t\tint recover_with_dwim = dwim_new_local_branch_ok;\n      -\n     - \t\tint could_be_checkout_paths = !expect_commit_only &&\n     + \t\tint could_be_checkout_paths = !arg0_cant_be_pathspec &&\n       \t\t\tcheck_filename(opts->prefix, arg);\n       \n     - \t\tif (!expect_commit_only && !no_wildcard(arg))\n     + \t\tif (!arg0_cant_be_pathspec && !no_wildcard(arg))\n       \t\t\trecover_with_dwim = 0;\n       \n     --\t\tif (!(argc == 1 && !has_dash_dash) &&\n     --\t\t    !(argc == 2 && has_dash_dash) &&\n     +-\t\tif (!(argc == 1 && dash_dash_pos == -1) &&\n     +-\t\t    !(argc == 2 && dash_dash_pos == 1) &&\n      -\t\t    opts->accept_pathspec)\n      -\t\t\trecover_with_dwim = 0;\n      -\n 17:  542eb709ca = 17:  121d3f06a6 t2024: cover more cases\n 18:  c293d72832 ! 18:  7324e091ba checkout, restore: support the --pathspec-from-file option\n     @@ -105,8 +105,8 @@\n       \t\t * Absence of '--' leaves <pathspec>/<commit> ambiguity.\n       \t\t * Try to resolve it with additional knowledge about pathspec args.\n       \t\t */\n     --\t\texpect_commit_only = !opts->accept_pathspec;\n     -+\t\texpect_commit_only = !opts->accept_pathspec || opts->pathspec_from_file;\n     +-\t\targ0_cant_be_pathspec = !opts->accept_pathspec;\n     ++\t\targ0_cant_be_pathspec = !opts->accept_pathspec || opts->pathspec_from_file;\n       \t} else if (dash_dash_pos == 0) {\n       \t\t/* 'git checkout/switch/restore -- [...]' */\n       \t\treturn 1;  /* Eat '--' */\n     @@ -247,16 +247,16 @@\n      +\techo fileA.t >list &&\n      +\n      +\ttest_must_fail git checkout --pathspec-from-file=- --detach <list 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-detach\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --detach\" err &&\n      +\n      +\ttest_must_fail git checkout --pathspec-from-file=- --patch <list 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-patch\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --patch\" err &&\n      +\n      +\ttest_must_fail git checkout --pathspec-from-file=- -- fileA.t <list 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with pathspec arguments\" err &&\n      +\n      +\ttest_must_fail git checkout --pathspec-file-nul 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-file-nul requires \\-\\-pathspec-from-file\" err\n     ++\ttest_i18ngrep -e \"--pathspec-file-nul requires --pathspec-from-file\" err\n      +'\n      +\n      +test_done\n     @@ -344,16 +344,16 @@\n      +\t>empty_list &&\n      +\n      +\ttest_must_fail git restore --pathspec-from-file=- --patch --source=HEAD^1 <list 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-patch\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --patch\" err &&\n      +\n      +\ttest_must_fail git restore --pathspec-from-file=- --source=HEAD^1 -- fileA.t <list 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with pathspec arguments\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with pathspec arguments\" err &&\n      +\n      +\ttest_must_fail git restore --pathspec-file-nul --source=HEAD^1 2>err &&\n     -+\ttest_i18ngrep \"\\-\\-pathspec-file-nul requires \\-\\-pathspec-from-file\" err &&\n     ++\ttest_i18ngrep -e \"--pathspec-file-nul requires --pathspec-from-file\" err &&\n      +\n      +\ttest_must_fail git restore --pathspec-from-file=- --source=HEAD^1 <empty_list 2>err &&\n     -+\ttest_i18ngrep \"you must specify path(s) to restore\" err\n     ++\ttest_i18ngrep -e \"you must specify path(s) to restore\" err\n      +'\n      +\n      +test_done\n\n-- \ngitgitgadget\n"},{"id":"388535","messageId":"b1cc4a960d052f43f259f2149518c77712ccd0fb.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 03/18] t7107: add tests for error conditions","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:40Z","receivedAt":"2019-12-19T18:02:04Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nSuggested-By: Phillip Wood <phillip.wood@dunelm.org.uk>\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n t/t7107-reset-pathspec-file.sh | 14 ++++++++++++++\n 1 file changed, 14 insertions(+)\n\ndiff --git a/t/t7107-reset-pathspec-file.sh b/t/t7107-reset-pathspec-file.sh\nindex f36fce27b9..e91b0ff62d 100755\n--- a/t/t7107-reset-pathspec-file.sh\n+++ b/t/t7107-reset-pathspec-file.sh\n@@ -74,4 +74,18 @@ test_expect_success '--pathspec-from-file is not compatible with --soft or --har\n \ttest_must_fail git reset --hard --pathspec-from-file=list\n '\n \n+test_expect_success 'error conditions' '\n+\trestore_checkpoint &&\n+\techo fileA.t >list &&\n+\n+\ttest_must_fail git reset --pathspec-from-file=- --patch <list 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --patch\" err &&\n+\n+\ttest_must_fail git reset --pathspec-from-file=- -- fileA.t <list 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with pathspec arguments\" err &&\n+\n+\ttest_must_fail git reset --pathspec-file-nul 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-file-nul requires --pathspec-from-file\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"388536","messageId":"f0be90601ea019dea0bd647059cf5b2705c7193e.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 04/18] commit: forbid --pathspec-from-file --all","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:41Z","receivedAt":"2019-12-19T18:02:05Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nI forgot this in my previous patch `--pathspec-from-file` for\n`git commit` [1]. When both `--pathspec-from-file` and `--all` were\nspecified, `--all` took precedence and `--pathspec-from-file` was\nignored. Before `--pathspec-from-file` was implemented, this case was\nprevented by this check in `parse_and_validate_options()` :\n\n    die(_(\"paths '%s ...' with -a does not make sense\"), argv[0]);\n\nIt is unfortunate that these two cases are disconnected. This came as\nresult of how the code was laid out before my patches, where `pathspec`\nis parsed outside of `parse_and_validate_options()`. This branch is\nalready full of refactoring patches and I did not dare to go for another\none.\n\nFix by mirroring `die()` for `--pathspec-from-file` as well.\n\n[1] Commit e440fc58 (\"commit: support the --pathspec-from-file option\" 2019-11-19)\n\nReported-by: Phillip Wood <phillip.wood@dunelm.org.uk>\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/commit.c                | 3 +++\n t/t7526-commit-pathspec-file.sh | 3 +++\n 2 files changed, 6 insertions(+)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex e48c1fd90a..aa1332308a 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -347,6 +347,9 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix\n \t\tif (interactive)\n \t\t\tdie(_(\"--pathspec-from-file is incompatible with --interactive/--patch\"));\n \n+\t\tif (all)\n+\t\t\tdie(_(\"--pathspec-from-file with -a does not make sense\"));\n+\n \t\tif (pathspec.nr)\n \t\t\tdie(_(\"--pathspec-from-file is incompatible with pathspec arguments\"));\n \ndiff --git a/t/t7526-commit-pathspec-file.sh b/t/t7526-commit-pathspec-file.sh\nindex b71c1013e7..bc65125741 100755\n--- a/t/t7526-commit-pathspec-file.sh\n+++ b/t/t7526-commit-pathspec-file.sh\n@@ -72,6 +72,9 @@ test_expect_success 'error conditions' '\n \ttest_must_fail git commit --pathspec-from-file=- --patch -m \"Commit\" <list 2>err &&\n \ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --interactive/--patch\" err &&\n \n+\ttest_must_fail git commit --pathspec-from-file=- --all -m \"Commit\" <list 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-from-file with -a does not make sense\" err &&\n+\n \ttest_must_fail git commit --pathspec-from-file=- -m \"Commit\" -- fileA.t <list 2>err &&\n \ttest_i18ngrep -e \"--pathspec-from-file is incompatible with pathspec arguments\" err &&\n \n-- \ngitgitgadget\n\n"},{"id":"388537","messageId":"2153350ac4af3dce12f5bc330208f028293e0dd6.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 05/18] cmd_add: prepare for next patch","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:42Z","receivedAt":"2019-12-19T18:02:07Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nSome code blocks were moved down to be able to test for `pathspec.nr`\nin the next patch. Blocks are moved as is without any changes. This\nis done as separate patch to reduce the amount of diffs in next patch.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/add.c | 34 +++++++++++++++++-----------------\n 1 file changed, 17 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/add.c b/builtin/add.c\nindex d4686d5218..3d1791dd82 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -430,10 +430,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \tif (addremove && take_worktree_changes)\n \t\tdie(_(\"-A and -u are mutually incompatible\"));\n \n-\tif (!take_worktree_changes && addremove_explicit < 0 && argc)\n-\t\t/* Turn \"git add pathspec...\" to \"git add -A pathspec...\" */\n-\t\taddremove = 1;\n-\n \tif (!show_only && ignore_missing)\n \t\tdie(_(\"Option --ignore-missing can only be used together with --dry-run\"));\n \n@@ -446,19 +442,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \n \thold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);\n \n-\tflags = ((verbose ? ADD_CACHE_VERBOSE : 0) |\n-\t\t (show_only ? ADD_CACHE_PRETEND : 0) |\n-\t\t (intent_to_add ? ADD_CACHE_INTENT : 0) |\n-\t\t (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |\n-\t\t (!(addremove || take_worktree_changes)\n-\t\t  ? ADD_CACHE_IGNORE_REMOVAL : 0));\n-\n-\tif (require_pathspec && argc == 0) {\n-\t\tfprintf(stderr, _(\"Nothing specified, nothing added.\\n\"));\n-\t\tfprintf(stderr, _(\"Maybe you wanted to say 'git add .'?\\n\"));\n-\t\treturn 0;\n-\t}\n-\n \t/*\n \t * Check the \"pathspec '%s' did not match any files\" block\n \t * below before enabling new magic.\n@@ -468,6 +451,23 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \t\t       PATHSPEC_SYMLINK_LEADING_PATH,\n \t\t       prefix, argv);\n \n+\tif (require_pathspec && argc == 0) {\n+\t\tfprintf(stderr, _(\"Nothing specified, nothing added.\\n\"));\n+\t\tfprintf(stderr, _(\"Maybe you wanted to say 'git add .'?\\n\"));\n+\t\treturn 0;\n+\t}\n+\n+\tif (!take_worktree_changes && addremove_explicit < 0 && argc)\n+\t\t/* Turn \"git add pathspec...\" to \"git add -A pathspec...\" */\n+\t\taddremove = 1;\n+\n+\tflags = ((verbose ? ADD_CACHE_VERBOSE : 0) |\n+\t\t (show_only ? ADD_CACHE_PRETEND : 0) |\n+\t\t (intent_to_add ? ADD_CACHE_INTENT : 0) |\n+\t\t (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |\n+\t\t (!(addremove || take_worktree_changes)\n+\t\t  ? ADD_CACHE_IGNORE_REMOVAL : 0));\n+\n \tif (read_cache_preload(&pathspec) < 0)\n \t\tdie(_(\"index file corrupt\"));\n \n-- \ngitgitgadget\n\n"},{"id":"388539","messageId":"5a4a538fa624c5d06f504bd81e103e8068744aab.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 07/18] doc: checkout: remove duplicate synopsis","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:44Z","receivedAt":"2019-12-19T18:02:10Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nIt was added in [1]. I understand that the duplicate change was not\nintentional and comes from an oversight.\n\nAlso, in explanation, there was only one section for two synopsis\nentries.\n\nFix both problems by removing duplicate synopsis.\n\n<paths> vs <pathspec> is resolved in next patch.\n\n[1] Commit b59698ae (\"checkout doc: clarify command line args for \"checkout paths\" mode\" 2017-10-11)\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-checkout.txt | 3 +--\n 1 file changed, 1 insertion(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex cf3cac0a2b..2011fdbb1d 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -13,7 +13,6 @@ SYNOPSIS\n 'git checkout' [-q] [-f] [-m] [--detach] <commit>\n 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]\n 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...\n-'git checkout' [<tree-ish>] [--] <pathspec>...\n 'git checkout' (-p|--patch) [<tree-ish>] [--] [<paths>...]\n \n DESCRIPTION\n@@ -79,7 +78,7 @@ be used to detach `HEAD` at the tip of the branch (`git checkout\n +\n Omitting `<branch>` detaches `HEAD` at the tip of the current branch.\n \n-'git checkout' [<tree-ish>] [--] <pathspec>...::\n+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...::\n \n \tOverwrite paths in the working tree by replacing with the\n \tcontents in the index or in the `<tree-ish>` (most often a\n-- \ngitgitgadget\n\n"},{"id":"388540","messageId":"daf0d6c5364ba44f83d0be69e12f4bdf042c5f3d.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 08/18] doc: checkout: fix broken text reference","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:45Z","receivedAt":"2019-12-19T18:02:10Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-checkout.txt | 10 ++++------\n 1 file changed, 4 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 2011fdbb1d..d47046e050 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -95,12 +95,10 @@ using `--ours` or `--theirs`.  With `-m`, changes made to the working tree\n file can be discarded to re-create the original conflicted merge result.\n \n 'git checkout' (-p|--patch) [<tree-ish>] [--] [<pathspec>...]::\n-\tThis is similar to the \"check out paths to the working tree\n-\tfrom either the index or from a tree-ish\" mode described\n-\tabove, but lets you use the interactive interface to show\n-\tthe \"diff\" output and choose which hunks to use in the\n-\tresult.  See below for the description of `--patch` option.\n-\n+\tThis is similar to the previous mode, but lets you use the\n+\tinteractive interface to show the \"diff\" output and choose which\n+\thunks to use in the result.  See below for the description of\n+\t`--patch` option.\n \n OPTIONS\n -------\n-- \ngitgitgadget\n\n"},{"id":"388541","messageId":"1d981b199f2564093d6d04e1eb8be9fa30c1d1da.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 09/18] doc: checkout: synchronize <pathspec> description","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:46Z","receivedAt":"2019-12-19T18:02:13Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\n`git add` shows an example of good writing, follow it.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-checkout.txt | 24 +++++++++++++++---------\n 1 file changed, 15 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex d47046e050..93124f3ad9 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -12,13 +12,13 @@ SYNOPSIS\n 'git checkout' [-q] [-f] [-m] --detach [<branch>]\n 'git checkout' [-q] [-f] [-m] [--detach] <commit>\n 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]\n-'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...\n-'git checkout' (-p|--patch) [<tree-ish>] [--] [<paths>...]\n+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...\n+'git checkout' (-p|--patch) [<tree-ish>] [--] [<pathspec>...]\n \n DESCRIPTION\n -----------\n Updates files in the working tree to match the version in the index\n-or the specified tree.  If no paths are given, 'git checkout' will\n+or the specified tree.  If no pathspec was given, 'git checkout' will\n also update `HEAD` to set the specified branch as the current\n branch.\n \n@@ -78,13 +78,13 @@ be used to detach `HEAD` at the tip of the branch (`git checkout\n +\n Omitting `<branch>` detaches `HEAD` at the tip of the current branch.\n \n-'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...::\n+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...::\n \n-\tOverwrite paths in the working tree by replacing with the\n-\tcontents in the index or in the `<tree-ish>` (most often a\n-\tcommit).  When a `<tree-ish>` is given, the paths that\n-\tmatch the `<pathspec>` are updated both in the index and in\n-\tthe working tree.\n+\tOverwrite the contents of the files that match the pathspec.\n+\tWhen the `<tree-ish>` (most often a commit) is not given, \n+\toverwrite working tree with the contents in the index.\n+\tWhen the `<tree-ish>` is given, overwrite both the index and\n+\tthe working tree with the contents at the `<tree-ish>`.\n +\n The index may contain unmerged entries because of a previous failed merge.\n By default, if you try to check out such an entry from the index, the\n@@ -336,7 +336,13 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.\n \tTree to checkout from (when paths are given). If not specified,\n \tthe index will be used.\n \n+\\--::\n+\tDo not interpret any more arguments as options.\n \n+<pathspec>...::\n+\tLimits the paths affected by the operation.\n++\n+For more details, see the 'pathspec' entry in linkgit:gitglossary[7].\n \n DETACHED HEAD\n -------------\n-- \ngitgitgadget\n\n"},{"id":"388542","messageId":"c8e59903f74d068983b136424b9a429cce821b17.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 06/18] add: support the --pathspec-from-file option","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:43Z","receivedAt":"2019-12-19T18:02:13Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nDecisions taken for simplicity:\n1) For now, `--pathspec-from-file` is declared incompatible with\n   `--interactive/--patch/--edit`, even when <file> is not `stdin`.\n   Such use case it not really expected. Also, it would require changes\n   to `interactive_add()` and `edit_patch()`.\n2) It is not allowed to pass pathspec in both args and file.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-add.txt    | 16 ++++++-\n builtin/add.c                | 30 +++++++++++--\n t/t3704-add-pathspec-file.sh | 86 ++++++++++++++++++++++++++++++++++++\n 3 files changed, 127 insertions(+), 5 deletions(-)\n create mode 100755 t/t3704-add-pathspec-file.sh\n\ndiff --git a/Documentation/git-add.txt b/Documentation/git-add.txt\nindex 8b0e4c7fa8..be5e3ac54b 100644\n--- a/Documentation/git-add.txt\n+++ b/Documentation/git-add.txt\n@@ -11,7 +11,8 @@ SYNOPSIS\n 'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]\n \t  [--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]]\n \t  [--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]\n-\t  [--chmod=(+|-)x] [--] [<pathspec>...]\n+\t  [--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n+\t  [--] [<pathspec>...]\n \n DESCRIPTION\n -----------\n@@ -187,6 +188,19 @@ for \"git add --no-all <pathspec>...\", i.e. ignored removed files.\n \tbit is only changed in the index, the files on disk are left\n \tunchanged.\n \n+--pathspec-from-file=<file>::\n+\tPathspec is passed in `<file>` instead of commandline args. If\n+\t`<file>` is exactly `-` then standard input is used. Pathspec\n+\telements are separated by LF or CR/LF. Pathspec elements can be\n+\tquoted as explained for the configuration variable `core.quotePath`\n+\t(see linkgit:git-config[1]). See also `--pathspec-file-nul` and\n+\tglobal `--literal-pathspecs`.\n+\n+--pathspec-file-nul::\n+\tOnly meaningful with `--pathspec-from-file`. Pathspec elements are\n+\tseparated with NUL character and all other characters are taken\n+\tliterally (including newlines and quotes).\n+\n \\--::\n \tThis option can be used to separate command-line options from\n \tthe list of files, (useful when filenames might be mistaken\ndiff --git a/builtin/add.c b/builtin/add.c\nindex 3d1791dd82..7c21ad492b 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -29,6 +29,8 @@ static const char * const builtin_add_usage[] = {\n static int patch_interactive, add_interactive, edit_interactive;\n static int take_worktree_changes;\n static int add_renormalize;\n+static int pathspec_file_nul;\n+static const char *pathspec_from_file;\n \n struct update_callback_data {\n \tint flags;\n@@ -320,6 +322,8 @@ static struct option builtin_add_options[] = {\n \t\t   N_(\"override the executable bit of the listed files\")),\n \tOPT_HIDDEN_BOOL(0, \"warn-embedded-repo\", &warn_on_embedded_repo,\n \t\t\tN_(\"warn when adding an embedded repository\")),\n+\tOPT_PATHSPEC_FROM_FILE(&pathspec_from_file),\n+\tOPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),\n \tOPT_END(),\n };\n \n@@ -414,11 +418,17 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \t\t\t  builtin_add_usage, PARSE_OPT_KEEP_ARGV0);\n \tif (patch_interactive)\n \t\tadd_interactive = 1;\n-\tif (add_interactive)\n+\tif (add_interactive) {\n+\t\tif (pathspec_from_file)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with --interactive/--patch\"));\n \t\texit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive));\n+\t}\n \n-\tif (edit_interactive)\n+\tif (edit_interactive) {\n+\t\tif (pathspec_from_file)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with --edit\"));\n \t\treturn(edit_patch(argc, argv, prefix));\n+\t}\n \targc--;\n \targv++;\n \n@@ -451,13 +461,25 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \t\t       PATHSPEC_SYMLINK_LEADING_PATH,\n \t\t       prefix, argv);\n \n-\tif (require_pathspec && argc == 0) {\n+\tif (pathspec_from_file) {\n+\t\tif (pathspec.nr)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with pathspec arguments\"));\n+\n+\t\tparse_pathspec_file(&pathspec, PATHSPEC_ATTR,\n+\t\t\t\t    PATHSPEC_PREFER_FULL |\n+\t\t\t\t    PATHSPEC_SYMLINK_LEADING_PATH,\n+\t\t\t\t    prefix, pathspec_from_file, pathspec_file_nul);\n+\t} else if (pathspec_file_nul) {\n+\t\tdie(_(\"--pathspec-file-nul requires --pathspec-from-file\"));\n+\t}\n+\n+\tif (require_pathspec && pathspec.nr == 0) {\n \t\tfprintf(stderr, _(\"Nothing specified, nothing added.\\n\"));\n \t\tfprintf(stderr, _(\"Maybe you wanted to say 'git add .'?\\n\"));\n \t\treturn 0;\n \t}\n \n-\tif (!take_worktree_changes && addremove_explicit < 0 && argc)\n+\tif (!take_worktree_changes && addremove_explicit < 0 && pathspec.nr)\n \t\t/* Turn \"git add pathspec...\" to \"git add -A pathspec...\" */\n \t\taddremove = 1;\n \ndiff --git a/t/t3704-add-pathspec-file.sh b/t/t3704-add-pathspec-file.sh\nnew file mode 100755\nindex 0000000000..c229d21866\n--- /dev/null\n+++ b/t/t3704-add-pathspec-file.sh\n@@ -0,0 +1,86 @@\n+#!/bin/sh\n+\n+test_description='add --pathspec-from-file'\n+\n+. ./test-lib.sh\n+\n+test_tick\n+\n+test_expect_success setup '\n+\ttest_commit file0 &&\n+\techo A >fileA.t &&\n+\techo B >fileB.t &&\n+\techo C >fileC.t &&\n+\techo D >fileD.t\n+'\n+\n+restore_checkpoint () {\n+\tgit reset\n+}\n+\n+verify_expect () {\n+\tgit status --porcelain --untracked-files=no -- fileA.t fileB.t fileC.t fileD.t >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success 'simplest' '\n+\trestore_checkpoint &&\n+\n+\techo fileA.t | git add --pathspec-from-file=- &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tA  fileA.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success '--pathspec-file-nul' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileA.t\\0fileB.t\\0\" | git add --pathspec-from-file=- --pathspec-file-nul &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tA  fileA.t\n+\tA  fileB.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'only touches what was listed' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileB.t\\nfileC.t\\n\" | git add --pathspec-from-file=- &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tA  fileB.t\n+\tA  fileC.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'error conditions' '\n+\trestore_checkpoint &&\n+\techo fileA.t >list &&\n+\t>empty_list &&\n+\n+\ttest_must_fail git add --pathspec-from-file=- --interactive <list 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --interactive/--patch\" err &&\n+\n+\ttest_must_fail git add --pathspec-from-file=- --patch <list 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --interactive/--patch\" err &&\n+\n+\ttest_must_fail git add --pathspec-from-file=- --edit <list 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --edit\" err &&\n+\n+\ttest_must_fail git add --pathspec-from-file=- -- fileA.t <list 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with pathspec arguments\" err &&\n+\n+\ttest_must_fail git add --pathspec-file-nul 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-file-nul requires --pathspec-from-file\" err &&\n+\t\n+\t# This case succeeds, but still prints to stderr\n+\tgit add --pathspec-from-file=- <empty_list 2>err &&\n+\ttest_i18ngrep -e \"Nothing specified, nothing added.\" err\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"388543","messageId":"f53bb13e4328a74c37cf92df4a23364a427f2f27.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 11/18] parse_branchname_arg(): extract part as new function","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:48Z","receivedAt":"2019-12-19T18:02:14Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nThis is done for the next commit to avoid crazy 7x tab code padding.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c | 25 +++++++++++++++++++------\n 1 file changed, 19 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 3634a3dac1..e1b9df1543 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1113,6 +1113,22 @@ static void setup_new_branch_info_and_source_tree(\n \t}\n }\n \n+static const char *parse_remote_branch(const char *arg,\n+\t\t\t\t       struct object_id *rev,\n+\t\t\t\t       int could_be_checkout_paths,\n+\t\t\t\t       int *dwim_remotes_matched)\n+{\n+\tconst char *remote = unique_tracking_name(arg, rev, dwim_remotes_matched);\n+\t\n+\tif (remote && could_be_checkout_paths) {\n+\t\tdie(_(\"'%s' could be both a local file and a tracking branch.\\n\"\n+\t\t\t\"Please use -- (and optionally --no-guess) to disambiguate\"),\n+\t\t    arg);\n+\t}\n+\n+\treturn remote;\n+}\n+\n static int parse_branchname_arg(int argc, const char **argv,\n \t\t\t\tint dwim_new_local_branch_ok,\n \t\t\t\tstruct branch_info *new_branch_info,\n@@ -1223,13 +1239,10 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t\trecover_with_dwim = 0;\n \n \t\tif (recover_with_dwim) {\n-\t\t\tconst char *remote = unique_tracking_name(arg, rev,\n-\t\t\t\t\t\t\t\t  dwim_remotes_matched);\n+\t\t\tconst char *remote = parse_remote_branch(arg, rev,\n+\t\t\t\t\t\t\t\t could_be_checkout_paths,\n+\t\t\t\t\t\t\t\t dwim_remotes_matched);\n \t\t\tif (remote) {\n-\t\t\t\tif (could_be_checkout_paths)\n-\t\t\t\t\tdie(_(\"'%s' could be both a local file and a tracking branch.\\n\"\n-\t\t\t\t\t      \"Please use -- (and optionally --no-guess) to disambiguate\"),\n-\t\t\t\t\t    arg);\n \t\t\t\t*new_branch = arg;\n \t\t\t\targ = remote;\n \t\t\t\t/* DWIMmed to create local branch, case (3).(b) */\n-- \ngitgitgadget\n\n"},{"id":"388544","messageId":"137b697327aaf09384c660e4671bd55635c147c1.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 10/18] doc: restore: synchronize <pathspec> description","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:47Z","receivedAt":"2019-12-19T18:02:16Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\n`git add` shows an example of good writing, follow it.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-restore.txt | 12 ++++++++++--\n 1 file changed, 10 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt\nindex 1ab2e40ea9..d7bf016bba 100644\n--- a/Documentation/git-restore.txt\n+++ b/Documentation/git-restore.txt\n@@ -8,8 +8,8 @@ git-restore - Restore working tree files\n SYNOPSIS\n --------\n [verse]\n-'git restore' [<options>] [--source=<tree>] [--staged] [--worktree] <pathspec>...\n-'git restore' (-p|--patch) [<options>] [--source=<tree>] [--staged] [--worktree] [<pathspec>...]\n+'git restore' [<options>] [--source=<tree>] [--staged] [--worktree] [--] <pathspec>...\n+'git restore' (-p|--patch) [<options>] [--source=<tree>] [--staged] [--worktree] [--] [<pathspec>...]\n \n DESCRIPTION\n -----------\n@@ -113,6 +113,14 @@ in linkgit:git-checkout[1] for details.\n \tappear in the `--source` tree are removed, to make them match\n \t`<tree>` exactly. The default is no-overlay mode.\n \n+\\--::\n+\tDo not interpret any more arguments as options.\n+\n+<pathspec>...::\n+\tLimits the paths affected by the operation.\n++\n+For more details, see the 'pathspec' entry in linkgit:gitglossary[7].\n+\n EXAMPLES\n --------\n \n-- \ngitgitgadget\n\n"},{"id":"388545","messageId":"dd35cad0d9ff9a0247d0a99bb4cdc294ef46ba96.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 13/18] parse_branchname_arg(): easier to understand variables","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:50Z","receivedAt":"2019-12-19T18:02:18Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\n`dash_dash_pos` was only calculated under `opts->accept_pathspec`. This\nis unexpected to readers and made it harder to reason about the code.\n\nFix this by restoring the expected meaning.\n\n`has_dash_dash` also takes `opts->accept_pathspec` into account.\nWhile this may sound clever at first sight, it becomes pretty hard to\nreason (and not be a victim) about code, especially in combination with\n`argc` here:\n\n\tif (!(argc == 1 && !has_dash_dash) &&\n\t    !(argc == 2 && has_dash_dash) &&\n\t    opts->accept_pathspec)\n\t\trecover_with_dwim = 0;\n\nFix this by giving variable a better name and rewriting the above\nmentioned condition (it's easier to verify if you notice that it only\nholds when `opts->accept_pathspec` is true).\n\nThis patch is not expected to change behavior in any way.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c | 36 ++++++++++++++++++++----------------\n 1 file changed, 20 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b847695d2b..9144770b21 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1155,7 +1155,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \tint argcount = 0;\n \tconst char *arg;\n \tint dash_dash_pos;\n-\tint has_dash_dash = 0;\n+\tint arg0_cant_be_pathspec = 0;\n \tint i;\n \n \t/*\n@@ -1205,24 +1205,28 @@ static int parse_branchname_arg(int argc, const char **argv,\n \tif (!opts->accept_pathspec) {\n \t\tif (argc > 1)\n \t\t\tdie(_(\"only one reference expected\"));\n-\t\thas_dash_dash = 1; /* helps disambiguate */\n+\t\targ0_cant_be_pathspec = 1;\n \t}\n \n \targ = argv[0];\n \tdash_dash_pos = -1;\n \tfor (i = 0; i < argc; i++) {\n-\t\tif (opts->accept_pathspec && !strcmp(argv[i], \"--\")) {\n+\t\tif (!strcmp(argv[i], \"--\")) {\n \t\t\tdash_dash_pos = i;\n \t\t\tbreak;\n \t\t}\n \t}\n-\tif (dash_dash_pos == 0)\n-\t\treturn 1; /* case (2) */\n-\telse if (dash_dash_pos == 1)\n-\t\thas_dash_dash = 1; /* case (3) or (1) */\n-\telse if (dash_dash_pos >= 2)\n-\t\tdie(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n-\topts->count_checkout_paths = !opts->quiet && !has_dash_dash;\n+\n+\tif (opts->accept_pathspec) {\n+\t\tif (dash_dash_pos == 0)\n+\t\t\treturn 1; /* case (2) */\n+\t\telse if (dash_dash_pos == 1)\n+\t\t\targ0_cant_be_pathspec = 1; /* case (3) or (1) */\n+\t\telse if (dash_dash_pos >= 2)\n+\t\t\tdie(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n+\t}\n+\n+\topts->count_checkout_paths = !opts->quiet && !arg0_cant_be_pathspec;\n \n \tif (!strcmp(arg, \"-\"))\n \t\targ = \"@{-1}\";\n@@ -1238,18 +1242,18 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t */\n \t\tint recover_with_dwim = dwim_new_local_branch_ok;\n \n-\t\tint could_be_checkout_paths = !has_dash_dash &&\n+\t\tint could_be_checkout_paths = !arg0_cant_be_pathspec &&\n \t\t\tcheck_filename(opts->prefix, arg);\n \n-\t\tif (!has_dash_dash && !no_wildcard(arg))\n+\t\tif (!arg0_cant_be_pathspec && !no_wildcard(arg))\n \t\t\trecover_with_dwim = 0;\n \n \t\t/*\n \t\t * Accept \"git checkout foo\", \"git checkout foo --\"\n \t\t * and \"git switch foo\" as candidates for dwim.\n \t\t */\n-\t\tif (!(argc == 1 && !has_dash_dash) &&\n-\t\t    !(argc == 2 && has_dash_dash) &&\n+\t\tif (!(argc == 1 && dash_dash_pos == -1) &&\n+\t\t    !(argc == 2 && dash_dash_pos == 1) &&\n \t\t    opts->accept_pathspec)\n \t\t\trecover_with_dwim = 0;\n \n@@ -1266,7 +1270,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t}\n \n \t\tif (!recover_with_dwim) {\n-\t\t\tif (has_dash_dash)\n+\t\t\tif (arg0_cant_be_pathspec)\n \t\t\t\tdie(_(\"invalid reference: %s\"), arg);\n \t\t\treturn argcount;\n \t\t}\n@@ -1282,7 +1286,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \tif (!opts->source_tree)                   /* case (1): want a tree */\n \t\tdie(_(\"reference is not a tree: %s\"), arg);\n \n-\tif (!has_dash_dash) {\t/* case (3).(d) -> (1) */\n+\tif (!arg0_cant_be_pathspec) {\t/* case (3).(d) -> (1) */\n \t\t/*\n \t\t * Do not complain the most common case\n \t\t *\tgit checkout branch\n-- \ngitgitgadget\n\n"},{"id":"388546","messageId":"121d3f06a6341fd9b3970d60b2b5f04a808d2187.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 17/18] t2024: cover more cases","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:54Z","receivedAt":"2019-12-19T18:02:19Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nAfter working on `parse_branchname_arg()` I think that these cases are\nworth testing.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n t/t2024-checkout-dwim.sh | 27 +++++++++++++++++++++++++++\n 1 file changed, 27 insertions(+)\n\ndiff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh\nindex c35d67b697..fd993bf45d 100755\n--- a/t/t2024-checkout-dwim.sh\n+++ b/t/t2024-checkout-dwim.sh\n@@ -156,6 +156,33 @@ test_expect_success 'checkout of branch from a single remote succeeds #2' '\n \ttest_branch_upstream baz repo_b baz\n '\n \n+test_expect_success 'checkout of branch from a single remote succeeds with --' '\n+\tgit checkout -B master &&\n+\ttest_might_fail git branch -D baz &&\n+\n+\tgit checkout baz -- &&\n+\tstatus_uno_is_clean &&\n+\ttest_branch baz &&\n+\ttest_cmp_rev remotes/other_b/baz HEAD &&\n+\ttest_branch_upstream baz repo_b baz\n+'\n+\n+test_expect_success 'dont DWIM with pathspec #1' '\n+\tgit checkout -B master &&\n+\ttest_might_fail git branch -D baz &&\n+\n+\ttest_must_fail git checkout baz nonExistingFile 2>err &&\n+\ttest_i18ngrep \"did not match any file(s) known to git\" err\n+'\n+\n+test_expect_success 'dont DWIM with pathspec #2' '\n+\tgit checkout -B master &&\n+\ttest_might_fail git branch -D baz &&\n+\n+\ttest_must_fail git checkout baz -- nonExistingFile 2>err &&\n+\ttest_i18ngrep \"fatal: invalid reference: baz\" err\n+'\n+\n test_expect_success '--no-guess suppresses branch auto-vivification' '\n \tgit checkout -B master &&\n \tstatus_uno_is_clean &&\n-- \ngitgitgadget\n\n"},{"id":"388547","messageId":"e0bdd5bd1e156e68ec8e8a1a6c1376d7e69150be.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 16/18] parse_branchname_arg(): refactor the decision making","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:53Z","receivedAt":"2019-12-19T18:02:21Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nMake it easier to understand which branches handle which cases.\n\nDrop obfuscated variable `has_dash_dash` which also took\n`opts->accept_pathspec` into account, making it pretty hard to reason\nabout code, especially when used together with `argc` and\n`opts->accept_pathspec` here:\n\n\tif (!(argc == 1 && !has_dash_dash) &&\n\t\t!(argc == 2 && has_dash_dash) &&\n\t\topts->accept_pathspec)\n\t\trecover_with_dwim = 0;\n\nAvoid double-negation in the code mentioned above (\"it is not OK to\nproceed if it's not one of those cases\").\n\nAvoid hard-to-understand condition `opts->accept_pathspec` in the code\nmentioned above.\n\nThere are some minor die() message changes for:\n`git switch <commit> <unexpected>`\n`git switch <commit> -- <unexpected>`\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c | 69 +++++++++++++++++++++++++++++-----------------\n 1 file changed, 44 insertions(+), 25 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 95a8e08793..9a85a3e4dc 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1153,9 +1153,8 @@ static int parse_branchname_arg(int argc, const char **argv,\n {\n \tconst char **new_branch = &opts->new_branch;\n \tconst char *arg;\n-\tint dash_dash_pos;\n-\tint arg0_cant_be_pathspec = 0;\n-\tint i;\n+\tint dash_dash_pos, i;\n+\tint recover_with_dwim, arg0_cant_be_pathspec;\n \n \t/*\n \t * Resolve ambiguity where argv[0] may be <pathspec> or <commit>.\n@@ -1174,14 +1173,6 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t *    do that and proceed with (2)(3).\n \t * 5) Otherwise, let caller proceed with <pathspec> interpretation.\n \t */\n-\tif (!argc)\n-\t\treturn 0;\n-\n-\tif (!opts->accept_pathspec) {\n-\t\tif (argc > 1)\n-\t\t\tdie(_(\"only one reference expected\"));\n-\t\targ0_cant_be_pathspec = 1;\n-\t}\n \n \targ = argv[0];\n \tdash_dash_pos = -1;\n@@ -1192,13 +1183,48 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t}\n \t}\n \n-\tif (opts->accept_pathspec) {\n-\t\tif (dash_dash_pos == 0)\n-\t\t\treturn 1;\n-\t\telse if (dash_dash_pos == 1)\n-\t\t\targ0_cant_be_pathspec = 1;\n-\t\telse if (dash_dash_pos >= 2)\n-\t\t\tdie(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n+\tif (dash_dash_pos == -1) {\n+\t\tif (argc == 0) {\n+\t\t\t/* 'git checkout/switch/restore' */\n+\t\t\treturn 0;\n+\t\t} else if (argc == 1) {\n+\t\t\t/* 'git checkout/switch/restore <something>' */\n+\t\t\trecover_with_dwim = dwim_new_local_branch_ok;\n+\t\t} else if (!opts->accept_pathspec) {\n+\t\t\t/* 'git switch <commit> <unexpected> [...]' */\n+\t\t\tdie(_(\"only one reference expected, %d given.\"), argc);\n+\t\t} else {\n+\t\t\t/* 'git checkout/restore <something> <pathspec> [...]' */\n+\t\t\trecover_with_dwim = 0;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Absence of '--' leaves <pathspec>/<commit> ambiguity.\n+\t\t * Try to resolve it with additional knowledge about pathspec args.\n+\t\t */\n+\t\targ0_cant_be_pathspec = !opts->accept_pathspec;\n+\t} else if (dash_dash_pos == 0) {\n+\t\t/* 'git checkout/switch/restore -- [...]' */\n+\t\treturn 1;  /* Eat '--' */\n+\t} else if (dash_dash_pos == 1) {\n+\t\tif (!opts->accept_pathspec) {\n+\t\t\t/* 'git switch <commit> -- [...]' */\n+\t\t\tdie(_(\"incompatible with pathspec arguments\"));\n+\t\t}\n+\n+\t\tif (argc == 2) {\n+\t\t\t/* 'git checkout/restore <commit> --' */\n+\t\t\trecover_with_dwim = dwim_new_local_branch_ok;\n+\t\t} else {\n+\t\t\t/* 'git checkout/restore <commit> -- <pathspec> [...]' */\n+\t\t\trecover_with_dwim = 0;\n+\t\t}\n+\n+\t\t/* Presence of '--' makes it certain that arg is <commit> */\n+\t\targ0_cant_be_pathspec = 1;\n+\t} else {\n+\t\t/* 'git checkout/switch/restore <commit> <unxpected> [...] -- [...]' */\n+\t\tdie(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n \t}\n \n \topts->count_checkout_paths = !opts->quiet && !arg0_cant_be_pathspec;\n@@ -1207,19 +1233,12 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\targ = \"@{-1}\";\n \n \tif (get_oid_mb(arg, rev)) {\n-\t\tint recover_with_dwim = dwim_new_local_branch_ok;\n-\n \t\tint could_be_checkout_paths = !arg0_cant_be_pathspec &&\n \t\t\tcheck_filename(opts->prefix, arg);\n \n \t\tif (!arg0_cant_be_pathspec && !no_wildcard(arg))\n \t\t\trecover_with_dwim = 0;\n \n-\t\tif (!(argc == 1 && dash_dash_pos == -1) &&\n-\t\t    !(argc == 2 && dash_dash_pos == 1) &&\n-\t\t    opts->accept_pathspec)\n-\t\t\trecover_with_dwim = 0;\n-\n \t\tif (recover_with_dwim) {\n \t\t\tconst char *remote = parse_remote_branch(arg, rev,\n \t\t\t\t\t\t\t\t could_be_checkout_paths);\n-- \ngitgitgadget\n\n"},{"id":"388548","messageId":"dc6e351796a89803a7b3dfdb7373b9c9f1ac3d8c.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 14/18] parse_branchname_arg(): simplify argument eating","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:51Z","receivedAt":"2019-12-19T18:02:21Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nThis patch abolishes two pieces of obfuscated code.\n\nFirst is `if (argc)` condition, which in fact means \"more then one arg\"\ndue to `argc++` above.\n\nSecond is by far harder to grasp:\n    if (!arg0_cant_be_pathspec) {...} else if (opts->accept_pathspec)\nthat is,\n\tif (opts->accept_pathspec && arg0_cant_be_pathspec)\nwhich (quite unexpectedly) actually means\n    if (opts->accept_pathspec && dash_dash_pos == 1)\nand aims to \"eat\" that `--`.\n\nMake both pieces easier to read by rewriting obfuscated conditions.\n\nWith both solved, I could keep argcount++/argv++/argc-- in the very end\nof the function, but that was obviously useless code in this case, so I\ndeleted them as well.\n\nThis patch is not expected to change behavior in any way.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c | 16 +++-------------\n 1 file changed, 3 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 9144770b21..63f4bb4da6 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1152,7 +1152,6 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t\t\tstruct object_id *rev)\n {\n \tconst char **new_branch = &opts->new_branch;\n-\tint argcount = 0;\n \tconst char *arg;\n \tint dash_dash_pos;\n \tint arg0_cant_be_pathspec = 0;\n@@ -1272,15 +1271,10 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\tif (!recover_with_dwim) {\n \t\t\tif (arg0_cant_be_pathspec)\n \t\t\t\tdie(_(\"invalid reference: %s\"), arg);\n-\t\t\treturn argcount;\n+\t\t\treturn 0;\n \t\t}\n \t}\n \n-\t/* we can't end up being in (2) anymore, eat the argument */\n-\targcount++;\n-\targv++;\n-\targc--;\n-\n \tsetup_new_branch_info_and_source_tree(new_branch_info, opts, rev, arg);\n \n \tif (!opts->source_tree)                   /* case (1): want a tree */\n@@ -1293,15 +1287,11 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t * even if there happen to be a file called 'branch';\n \t\t * it would be extremely annoying.\n \t\t */\n-\t\tif (argc)\n+\t\tif (argc > 1)\n \t\t\tverify_non_filename(opts->prefix, arg);\n-\t} else if (opts->accept_pathspec) {\n-\t\targcount++;\n-\t\targv++;\n-\t\targc--;\n \t}\n \n-\treturn argcount;\n+\treturn (opts->accept_pathspec && dash_dash_pos == 1) ? 2 : 1;\n }\n \n static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n-- \ngitgitgadget\n\n"},{"id":"388549","messageId":"58b65d2011a7978d8e5e39bb3029dae571f4085c.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 12/18] checkout: die() on ambiguous tracking branches","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:49Z","receivedAt":"2019-12-19T18:02:22Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nBefore this patch, when there were multiple DWIM candidates for remote\nbranch, git decided to try the argument as pathspec instead. I believe\nthat such behavior is a surprise: adding another remote suddenly causes\ngit to discard file contents, because it was unsure which branch to\npick. There was an incomplete attempt to prevent that in [3].\n\nI understand that this was never intended:\n\n  [1] introduces the unexpected behavior. Before, there was fallback\n  from not-a-ref to pathspec. This is reasonable DWIM. After, there is\n  another fallback from ambiguous-remote to pathspec. I understand that\n  it was kind of copy&paste oversight.\n\n  [2] noticed the unexpected behavior but chose to semi-document it\n  instead of forbidding, because the goal of the patch series was\n  focused on something else.\n\n  [3] adds `die()` when there is ambiguity between branch and file. The\n  case of multiple tracking branches is seemingly overlooked.\n\nChange to complain about ambiguity instead of doing unexpected things.\n\n[1] Commit 70c9ac2f (\"DWIM \"git checkout frotz\" to \"git checkout -b frotz origin/frotz\"\" 2009-10-18)\n    https://public-inbox.org/git/7vaazpxha4.fsf_-_@alter.siamese.dyndns.org/\n[2] Commit ad8d5104 (\"checkout: add advice for ambiguous \"checkout <branch>\"\", 2018-06-05)\n    https://public-inbox.org/git/20180502105452.17583-1-avarab@gmail.com/\n[3] Commit be4908f1 (\"checkout: disambiguate dwim tracking branches and local files\", 2018-11-13)\n    https://public-inbox.org/git/20181110120707.25846-1-pclouds@gmail.com/\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c       | 56 ++++++++++++++++++----------------------\n t/t2024-checkout-dwim.sh | 28 ++++++++++++++++++--\n 2 files changed, 51 insertions(+), 33 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex e1b9df1543..b847695d2b 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1115,10 +1115,10 @@ static void setup_new_branch_info_and_source_tree(\n \n static const char *parse_remote_branch(const char *arg,\n \t\t\t\t       struct object_id *rev,\n-\t\t\t\t       int could_be_checkout_paths,\n-\t\t\t\t       int *dwim_remotes_matched)\n+\t\t\t\t       int could_be_checkout_paths)\n {\n-\tconst char *remote = unique_tracking_name(arg, rev, dwim_remotes_matched);\n+\tint num_matches = 0;\n+\tconst char *remote = unique_tracking_name(arg, rev, &num_matches);\n \t\n \tif (remote && could_be_checkout_paths) {\n \t\tdie(_(\"'%s' could be both a local file and a tracking branch.\\n\"\n@@ -1126,6 +1126,22 @@ static const char *parse_remote_branch(const char *arg,\n \t\t    arg);\n \t}\n \n+\tif (!remote && num_matches > 1) {\n+\t    if (advice_checkout_ambiguous_remote_branch_name) {\n+\t\t    advise(_(\"If you meant to check out a remote tracking branch on, e.g. 'origin',\\n\"\n+\t\t\t     \"you can do so by fully qualifying the name with the --track option:\\n\"\n+\t\t\t     \"\\n\"\n+\t\t\t     \"    git checkout --track origin/<name>\\n\"\n+\t\t\t     \"\\n\"\n+\t\t\t     \"If you'd like to always have checkouts of an ambiguous <name> prefer\\n\"\n+\t\t\t     \"one remote, e.g. the 'origin' remote, consider setting\\n\"\n+\t\t\t     \"checkout.defaultRemote=origin in your config.\"));\n+\t    }\n+\n+\t    die(_(\"'%s' matched multiple (%d) remote tracking branches\"),\n+\t\targ, num_matches);\n+\t}\n+\n \treturn remote;\n }\n \n@@ -1133,8 +1149,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t\t\tint dwim_new_local_branch_ok,\n \t\t\t\tstruct branch_info *new_branch_info,\n \t\t\t\tstruct checkout_opts *opts,\n-\t\t\t\tstruct object_id *rev,\n-\t\t\t\tint *dwim_remotes_matched)\n+\t\t\t\tstruct object_id *rev)\n {\n \tconst char **new_branch = &opts->new_branch;\n \tint argcount = 0;\n@@ -1240,8 +1255,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \n \t\tif (recover_with_dwim) {\n \t\t\tconst char *remote = parse_remote_branch(arg, rev,\n-\t\t\t\t\t\t\t\t could_be_checkout_paths,\n-\t\t\t\t\t\t\t\t dwim_remotes_matched);\n+\t\t\t\t\t\t\t\t could_be_checkout_paths);\n \t\t\tif (remote) {\n \t\t\t\t*new_branch = arg;\n \t\t\t\targ = remote;\n@@ -1505,7 +1519,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t\t\t const char * const usagestr[])\n {\n \tstruct branch_info new_branch_info;\n-\tint dwim_remotes_matched = 0;\n \tint parseopt_flags = 0;\n \n \tmemset(&new_branch_info, 0, sizeof(new_branch_info));\n@@ -1613,8 +1626,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t\t\topts->track == BRANCH_TRACK_UNSPECIFIED &&\n \t\t\t!opts->new_branch;\n \t\tint n = parse_branchname_arg(argc, argv, dwim_ok,\n-\t\t\t\t\t     &new_branch_info, opts, &rev,\n-\t\t\t\t\t     &dwim_remotes_matched);\n+\t\t\t\t\t     &new_branch_info, opts, &rev);\n \t\targv += n;\n \t\targc -= n;\n \t} else if (!opts->accept_ref && opts->from_treeish) {\n@@ -1672,28 +1684,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t}\n \n \tUNLEAK(opts);\n-\tif (opts->patch_mode || opts->pathspec.nr) {\n-\t\tint ret = checkout_paths(opts, new_branch_info.name);\n-\t\tif (ret && dwim_remotes_matched > 1 &&\n-\t\t    advice_checkout_ambiguous_remote_branch_name)\n-\t\t\tadvise(_(\"'%s' matched more than one remote tracking branch.\\n\"\n-\t\t\t\t \"We found %d remotes with a reference that matched. So we fell back\\n\"\n-\t\t\t\t \"on trying to resolve the argument as a path, but failed there too!\\n\"\n-\t\t\t\t \"\\n\"\n-\t\t\t\t \"If you meant to check out a remote tracking branch on, e.g. 'origin',\\n\"\n-\t\t\t\t \"you can do so by fully qualifying the name with the --track option:\\n\"\n-\t\t\t\t \"\\n\"\n-\t\t\t\t \"    git checkout --track origin/<name>\\n\"\n-\t\t\t\t \"\\n\"\n-\t\t\t\t \"If you'd like to always have checkouts of an ambiguous <name> prefer\\n\"\n-\t\t\t\t \"one remote, e.g. the 'origin' remote, consider setting\\n\"\n-\t\t\t\t \"checkout.defaultRemote=origin in your config.\"),\n-\t\t\t       argv[0],\n-\t\t\t       dwim_remotes_matched);\n-\t\treturn ret;\n-\t} else {\n+\tif (opts->patch_mode || opts->pathspec.nr)\n+\t\treturn checkout_paths(opts, new_branch_info.name);\n+\telse\n \t\treturn checkout_branch(opts, &new_branch_info);\n-\t}\n }\n \n int cmd_checkout(int argc, const char **argv, const char *prefix)\ndiff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh\nindex fa0718c730..c35d67b697 100755\n--- a/t/t2024-checkout-dwim.sh\n+++ b/t/t2024-checkout-dwim.sh\n@@ -37,7 +37,9 @@ test_expect_success 'setup' '\n \t\tgit checkout -b foo &&\n \t\ttest_commit a_foo &&\n \t\tgit checkout -b bar &&\n-\t\ttest_commit a_bar\n+\t\ttest_commit a_bar &&\n+\t\tgit checkout -b ambiguous_branch_and_file &&\n+\t\ttest_commit a_ambiguous_branch_and_file\n \t) &&\n \tgit init repo_b &&\n \t(\n@@ -46,7 +48,9 @@ test_expect_success 'setup' '\n \t\tgit checkout -b foo &&\n \t\ttest_commit b_foo &&\n \t\tgit checkout -b baz &&\n-\t\ttest_commit b_baz\n+\t\ttest_commit b_baz &&\n+\t\tgit checkout -b ambiguous_branch_and_file &&\n+\t\ttest_commit b_ambiguous_branch_and_file\n \t) &&\n \tgit remote add repo_a repo_a &&\n \tgit remote add repo_b repo_b &&\n@@ -75,6 +79,26 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' '\n \ttest_branch master\n '\n \n+test_expect_success 'when arg matches multiple remotes, do not fallback to interpreting as pathspec' '\n+\t# create a file with name matching remote branch name\n+\tgit checkout -b t_ambiguous_branch_and_file &&\n+\t>ambiguous_branch_and_file &&\n+\tgit add ambiguous_branch_and_file &&\n+\tgit commit -m \"ambiguous_branch_and_file\" &&\n+\n+\t# modify file to verify that it will not be touched by checkout\n+\ttest_when_finished \"git checkout -- ambiguous_branch_and_file\" &&\n+\techo \"file contents\" >ambiguous_branch_and_file &&\n+\tcp ambiguous_branch_and_file expect &&\n+\n+\ttest_must_fail git checkout ambiguous_branch_and_file 2>err &&\n+\n+\ttest_i18ngrep \"matched multiple (2) remote tracking branches\" err &&\n+\t\n+\t# file must not be altered\n+\ttest_cmp expect ambiguous_branch_and_file\n+'\n+\n test_expect_success 'checkout of branch from multiple remotes fails with advice' '\n \tgit checkout -B master &&\n \ttest_might_fail git branch -D foo &&\n-- \ngitgitgadget\n\n"},{"id":"388550","messageId":"7324e091ba7f48e286e6c35c7b7c40490e5c85d1.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 18/18] checkout, restore: support the --pathspec-from-file option","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:55Z","receivedAt":"2019-12-19T18:02:23Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nDecisions taken for simplicity:\n1) For now, `--pathspec-from-file` is declared incompatible with\n   `--patch`, even when <file> is not `stdin`. Such use case it not\n   really expected.\n2) It is not allowed to pass pathspec in both args and file.\n\n`you must specify path(s) to restore` block was moved down to be able to\ntest for `pathspec.nr` instead, because testing for `argc` is no longer\ncorrect.\n\n`git switch` does not support the new options because it doesn't expect\n`<pathspec>` arguments.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n Documentation/git-checkout.txt    | 15 +++++\n Documentation/git-restore.txt     | 14 +++++\n builtin/checkout.c                | 33 +++++++++--\n t/t2026-checkout-pathspec-file.sh | 90 ++++++++++++++++++++++++++++++\n t/t2072-restore-pathspec-file.sh  | 91 +++++++++++++++++++++++++++++++\n t/t9902-completion.sh             |  2 +\n 6 files changed, 240 insertions(+), 5 deletions(-)\n create mode 100755 t/t2026-checkout-pathspec-file.sh\n create mode 100755 t/t2072-restore-pathspec-file.sh\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 93124f3ad9..ffe3c1bff2 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n 'git checkout' [-q] [-f] [-m] [--detach] <commit>\n 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]\n 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...\n+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul]\n 'git checkout' (-p|--patch) [<tree-ish>] [--] [<pathspec>...]\n \n DESCRIPTION\n@@ -79,6 +80,7 @@ be used to detach `HEAD` at the tip of the branch (`git checkout\n Omitting `<branch>` detaches `HEAD` at the tip of the current branch.\n \n 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...::\n+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul]::\n \n \tOverwrite the contents of the files that match the pathspec.\n \tWhen the `<tree-ish>` (most often a commit) is not given, \n@@ -306,6 +308,19 @@ Note that this option uses the no overlay mode by default (see also\n \tworking tree, but not in `<tree-ish>` are removed, to make them\n \tmatch `<tree-ish>` exactly.\n \n+--pathspec-from-file=<file>::\n+\tPathspec is passed in `<file>` instead of commandline args. If\n+\t`<file>` is exactly `-` then standard input is used. Pathspec\n+\telements are separated by LF or CR/LF. Pathspec elements can be\n+\tquoted as explained for the configuration variable `core.quotePath`\n+\t(see linkgit:git-config[1]). See also `--pathspec-file-nul` and\n+\tglobal `--literal-pathspecs`.\n+\n+--pathspec-file-nul::\n+\tOnly meaningful with `--pathspec-from-file`. Pathspec elements are\n+\tseparated with NUL character and all other characters are taken\n+\tliterally (including newlines and quotes).\n+\n <branch>::\n \tBranch to checkout; if it refers to a branch (i.e., a name that,\n \twhen prepended with \"refs/heads/\", is a valid ref), then that\ndiff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt\nindex d7bf016bba..5bf60d4943 100644\n--- a/Documentation/git-restore.txt\n+++ b/Documentation/git-restore.txt\n@@ -9,6 +9,7 @@ SYNOPSIS\n --------\n [verse]\n 'git restore' [<options>] [--source=<tree>] [--staged] [--worktree] [--] <pathspec>...\n+'git restore' [<options>] [--source=<tree>] [--staged] [--worktree] --pathspec-from-file=<file> [--pathspec-file-nul]\n 'git restore' (-p|--patch) [<options>] [--source=<tree>] [--staged] [--worktree] [--] [<pathspec>...]\n \n DESCRIPTION\n@@ -113,6 +114,19 @@ in linkgit:git-checkout[1] for details.\n \tappear in the `--source` tree are removed, to make them match\n \t`<tree>` exactly. The default is no-overlay mode.\n \n+--pathspec-from-file=<file>::\n+\tPathspec is passed in `<file>` instead of commandline args. If\n+\t`<file>` is exactly `-` then standard input is used. Pathspec\n+\telements are separated by LF or CR/LF. Pathspec elements can be\n+\tquoted as explained for the configuration variable `core.quotePath`\n+\t(see linkgit:git-config[1]). See also `--pathspec-file-nul` and\n+\tglobal `--literal-pathspecs`.\n+\n+--pathspec-file-nul::\n+\tOnly meaningful with `--pathspec-from-file`. Pathspec elements are\n+\tseparated with NUL character and all other characters are taken\n+\tliterally (including newlines and quotes).\n+\n \\--::\n \tDo not interpret any more arguments as options.\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 9a85a3e4dc..3eb4301a80 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -70,6 +70,8 @@ struct checkout_opts {\n \tint checkout_worktree;\n \tconst char *ignore_unmerged_opt;\n \tint ignore_unmerged;\n+\tint pathspec_file_nul;\n+\tconst char *pathspec_from_file;\n \n \tconst char *new_branch;\n \tconst char *new_branch_force;\n@@ -1202,7 +1204,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t * Absence of '--' leaves <pathspec>/<commit> ambiguity.\n \t\t * Try to resolve it with additional knowledge about pathspec args.\n \t\t */\n-\t\targ0_cant_be_pathspec = !opts->accept_pathspec;\n+\t\targ0_cant_be_pathspec = !opts->accept_pathspec || opts->pathspec_from_file;\n \t} else if (dash_dash_pos == 0) {\n \t\t/* 'git checkout/switch/restore -- [...]' */\n \t\treturn 1;  /* Eat '--' */\n@@ -1476,6 +1478,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,\n \t\tOPT_BOOL('p', \"patch\", &opts->patch_mode, N_(\"select hunks interactively\")),\n \t\tOPT_BOOL(0, \"ignore-skip-worktree-bits\", &opts->ignore_skipworktree,\n \t\t\t N_(\"do not limit pathspecs to sparse entries only\")),\n+\t\tOPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),\n+\t\tOPT_PATHSPEC_FILE_NUL(&opts->pathspec_file_nul),\n \t\tOPT_END()\n \t};\n \tstruct option *newopts = parse_options_concat(prevopts, options);\n@@ -1612,10 +1616,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t\t\tdie(_(\"reference is not a tree: %s\"), opts->from_treeish);\n \t}\n \n-\tif (opts->accept_pathspec && !opts->empty_pathspec_ok && !argc &&\n-\t    !opts->patch_mode)\t/* patch mode is special */\n-\t\tdie(_(\"you must specify path(s) to restore\"));\n-\n \tif (argc) {\n \t\tparse_pathspec(&opts->pathspec, 0,\n \t\t\t       opts->patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,\n@@ -1635,10 +1635,33 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t\tif (opts->force_detach)\n \t\t\tdie(_(\"git checkout: --detach does not take a path argument '%s'\"),\n \t\t\t    argv[0]);\n+\t}\n+\n+\tif (opts->pathspec_from_file) {\n+\t\tif (opts->pathspec.nr)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with pathspec arguments\"));\n+\n+\t\tif (opts->force_detach)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with --detach\"));\n \n+\t\tif (opts->patch_mode)\n+\t\t\tdie(_(\"--pathspec-from-file is incompatible with --patch\"));\n+\n+\t\tparse_pathspec_file(&opts->pathspec, 0,\n+\t\t\t\t    0,\n+\t\t\t\t    prefix, opts->pathspec_from_file, opts->pathspec_file_nul);\n+\t} else if (opts->pathspec_file_nul) {\n+\t\tdie(_(\"--pathspec-file-nul requires --pathspec-from-file\"));\n+\t}\n+\n+\tif (opts->pathspec.nr) {\n \t\tif (1 < !!opts->writeout_stage + !!opts->force + !!opts->merge)\n \t\t\tdie(_(\"git checkout: --ours/--theirs, --force and --merge are incompatible when\\n\"\n \t\t\t      \"checking out of the index.\"));\n+\t} else {\n+\t\tif (opts->accept_pathspec && !opts->empty_pathspec_ok &&\n+\t\t    !opts->patch_mode)\t/* patch mode is special */\n+\t\t\tdie(_(\"you must specify path(s) to restore\"));\n \t}\n \n \tif (opts->new_branch) {\ndiff --git a/t/t2026-checkout-pathspec-file.sh b/t/t2026-checkout-pathspec-file.sh\nnew file mode 100755\nindex 0000000000..eb84d65546\n--- /dev/null\n+++ b/t/t2026-checkout-pathspec-file.sh\n@@ -0,0 +1,90 @@\n+#!/bin/sh\n+\n+test_description='checkout --pathspec-from-file'\n+\n+. ./test-lib.sh\n+\n+test_tick\n+\n+test_expect_success setup '\n+\ttest_commit file0 &&\n+\n+\techo 1 >fileA.t &&\n+\techo 1 >fileB.t &&\n+\techo 1 >fileC.t &&\n+\techo 1 >fileD.t &&\n+\tgit add fileA.t fileB.t fileC.t fileD.t &&\n+\tgit commit -m \"files 1\" &&\n+\n+\techo 2 >fileA.t &&\n+\techo 2 >fileB.t &&\n+\techo 2 >fileC.t &&\n+\techo 2 >fileD.t &&\n+\tgit add fileA.t fileB.t fileC.t fileD.t &&\n+\tgit commit -m \"files 2\" &&\n+\n+\tgit tag checkpoint\n+'\n+\n+restore_checkpoint () {\n+\tgit reset --hard checkpoint\n+}\n+\n+verify_expect () {\n+\tgit status --porcelain --untracked-files=no -- fileA.t fileB.t fileC.t fileD.t >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success 'simplest' '\n+\trestore_checkpoint &&\n+\n+\techo fileA.t | git checkout --pathspec-from-file=- HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tM  fileA.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success '--pathspec-file-nul' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileA.t\\0fileB.t\\0\" | git checkout --pathspec-from-file=- --pathspec-file-nul HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tM  fileA.t\n+\tM  fileB.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'only touches what was listed' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileB.t\\nfileC.t\\n\" | git checkout --pathspec-from-file=- HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\tM  fileB.t\n+\tM  fileC.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'error conditions' '\n+\trestore_checkpoint &&\n+\techo fileA.t >list &&\n+\n+\ttest_must_fail git checkout --pathspec-from-file=- --detach <list 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --detach\" err &&\n+\n+\ttest_must_fail git checkout --pathspec-from-file=- --patch <list 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --patch\" err &&\n+\n+\ttest_must_fail git checkout --pathspec-from-file=- -- fileA.t <list 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with pathspec arguments\" err &&\n+\n+\ttest_must_fail git checkout --pathspec-file-nul 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-file-nul requires --pathspec-from-file\" err\n+'\n+\n+test_done\ndiff --git a/t/t2072-restore-pathspec-file.sh b/t/t2072-restore-pathspec-file.sh\nnew file mode 100755\nindex 0000000000..bce617abfb\n--- /dev/null\n+++ b/t/t2072-restore-pathspec-file.sh\n@@ -0,0 +1,91 @@\n+#!/bin/sh\n+\n+test_description='restore --pathspec-from-file'\n+\n+. ./test-lib.sh\n+\n+test_tick\n+\n+test_expect_success setup '\n+\ttest_commit file0 &&\n+\n+\techo 1 >fileA.t &&\n+\techo 1 >fileB.t &&\n+\techo 1 >fileC.t &&\n+\techo 1 >fileD.t &&\n+\tgit add fileA.t fileB.t fileC.t fileD.t &&\n+\tgit commit -m \"files 1\" &&\n+\n+\techo 2 >fileA.t &&\n+\techo 2 >fileB.t &&\n+\techo 2 >fileC.t &&\n+\techo 2 >fileD.t &&\n+\tgit add fileA.t fileB.t fileC.t fileD.t &&\n+\tgit commit -m \"files 2\" &&\n+\n+\tgit tag checkpoint\n+'\n+\n+restore_checkpoint () {\n+\tgit reset --hard checkpoint\n+}\n+\n+verify_expect () {\n+\tgit status --porcelain --untracked-files=no -- fileA.t fileB.t fileC.t fileD.t >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success 'simplest' '\n+\trestore_checkpoint &&\n+\n+\techo fileA.t | git restore --pathspec-from-file=- --source=HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\t M fileA.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success '--pathspec-file-nul' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileA.t\\0fileB.t\\0\" | git restore --pathspec-from-file=- --pathspec-file-nul --source=HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\t M fileA.t\n+\t M fileB.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'only touches what was listed' '\n+\trestore_checkpoint &&\n+\n+\tprintf \"fileB.t\\nfileC.t\\n\" | git restore --pathspec-from-file=- --source=HEAD^1 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\t M fileB.t\n+\t M fileC.t\n+\tEOF\n+\tverify_expect\n+'\n+\n+test_expect_success 'error conditions' '\n+\trestore_checkpoint &&\n+\techo fileA.t >list &&\n+\t>empty_list &&\n+\n+\ttest_must_fail git restore --pathspec-from-file=- --patch --source=HEAD^1 <list 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with --patch\" err &&\n+\n+\ttest_must_fail git restore --pathspec-from-file=- --source=HEAD^1 -- fileA.t <list 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-from-file is incompatible with pathspec arguments\" err &&\n+\n+\ttest_must_fail git restore --pathspec-file-nul --source=HEAD^1 2>err &&\n+\ttest_i18ngrep -e \"--pathspec-file-nul requires --pathspec-from-file\" err &&\n+\n+\ttest_must_fail git restore --pathspec-from-file=- --source=HEAD^1 <empty_list 2>err &&\n+\ttest_i18ngrep -e \"you must specify path(s) to restore\" err\n+'\n+\n+test_done\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex ec3eccfd3d..93877ba9cd 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -1438,6 +1438,8 @@ test_expect_success 'double dash \"git checkout\"' '\n \t--no-guess Z\n \t--no-... Z\n \t--overlay Z\n+\t--pathspec-file-nul Z\n+\t--pathspec-from-file=Z\n \tEOF\n '\n \n-- \ngitgitgadget\n"},{"id":"388551","messageId":"7989f0c5cf813010d8034e932ee78959c7b39c95.1576778515.git.gitgitgadget@gmail.com","threadId":"52440","inReplyTo":"pull.490.v3.git.1576778515.gitgitgadget@gmail.com","subject":"[PATCH v3 15/18] parse_branchname_arg(): update code comments","fromName":"Alexandr Miloslavskiy via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-19T18:01:52Z","receivedAt":"2019-12-19T18:02:23Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"From: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n\nThese parts repeat git documentation:\n    ... if <something> is A...B <...>\n    ... remote named in checkout.defaultRemote ...\n\nSome parts repeat the code below. With next patch, code will be easier\nto understand, so this is no longer needed.\n\nThis is a separate patch to reduce the amount of diffs in next patch.\n\nSigned-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>\n---\n builtin/checkout.c | 86 +++++++++++-----------------------------------\n 1 file changed, 21 insertions(+), 65 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 63f4bb4da6..95a8e08793 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1158,45 +1158,21 @@ static int parse_branchname_arg(int argc, const char **argv,\n \tint i;\n \n \t/*\n-\t * case 1: git checkout <ref> -- [<paths>]\n-\t *\n-\t *   <ref> must be a valid tree, everything after the '--' must be\n-\t *   a path.\n-\t *\n-\t * case 2: git checkout -- [<paths>]\n-\t *\n-\t *   everything after the '--' must be paths.\n-\t *\n-\t * case 3: git checkout <something> [--]\n-\t *\n-\t *   (a) If <something> is a commit, that is to\n-\t *       switch to the branch or detach HEAD at it.  As a special case,\n-\t *       if <something> is A...B (missing A or B means HEAD but you can\n-\t *       omit at most one side), and if there is a unique merge base\n-\t *       between A and B, A...B names that merge base.\n-\t *\n-\t *   (b) If <something> is _not_ a commit, either \"--\" is present\n-\t *       or <something> is not a path, no -t or -b was given, and\n-\t *       and there is a tracking branch whose name is <something>\n-\t *       in one and only one remote (or if the branch exists on the\n-\t *       remote named in checkout.defaultRemote), then this is a\n-\t *       short-hand to fork local <something> from that\n-\t *       remote-tracking branch.\n-\t *\n-\t *   (c) Otherwise, if \"--\" is present, treat it like case (1).\n-\t *\n-\t *   (d) Otherwise :\n-\t *       - if it's a reference, treat it like case (1)\n-\t *       - else if it's a path, treat it like case (2)\n-\t *       - else: fail.\n-\t *\n-\t * case 4: git checkout <something> <paths>\n-\t *\n-\t *   The first argument must not be ambiguous.\n-\t *   - If it's *only* a reference, treat it like case (1).\n-\t *   - If it's only a path, treat it like case (2).\n-\t *   - else: fail.\n-\t *\n+\t * Resolve ambiguity where argv[0] may be <pathspec> or <commit>.\n+\t * High-level approach is:\n+\t * 1) Use various things to reduce ambiguity, examples:\n+\t *    * '--' is present\n+\t *    * command doesn't accept <pathspec>\n+\t *    * additional options like '-b' were given\n+\t * 2) If ambiguous and matches both existing <commit> and existing\n+\t *    file, complain. However, in 1-argument 'git checkout <arg>'\n+\t *    treat as <commit> to avoid annoying users.\n+\t * 3) Otherwise, if it matches some existing <commit>, treat as\n+\t *    <commit>.\n+\t * 4) Otherwise, if it matches a remote branch, and it's considered\n+\t *    reasonable to DWIM to create a local branch from remote branch,\n+\t *    do that and proceed with (2)(3).\n+\t * 5) Otherwise, let caller proceed with <pathspec> interpretation.\n \t */\n \tif (!argc)\n \t\treturn 0;\n@@ -1218,9 +1194,9 @@ static int parse_branchname_arg(int argc, const char **argv,\n \n \tif (opts->accept_pathspec) {\n \t\tif (dash_dash_pos == 0)\n-\t\t\treturn 1; /* case (2) */\n+\t\t\treturn 1;\n \t\telse if (dash_dash_pos == 1)\n-\t\t\targ0_cant_be_pathspec = 1; /* case (3) or (1) */\n+\t\t\targ0_cant_be_pathspec = 1;\n \t\telse if (dash_dash_pos >= 2)\n \t\t\tdie(_(\"only one reference expected, %d given.\"), dash_dash_pos);\n \t}\n@@ -1231,14 +1207,6 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\targ = \"@{-1}\";\n \n \tif (get_oid_mb(arg, rev)) {\n-\t\t/*\n-\t\t * Either case (3) or (4), with <something> not being\n-\t\t * a commit, or an attempt to use case (1) with an\n-\t\t * invalid ref.\n-\t\t *\n-\t\t * It's likely an error, but we need to find out if\n-\t\t * we should auto-create the branch, case (3).(b).\n-\t\t */\n \t\tint recover_with_dwim = dwim_new_local_branch_ok;\n \n \t\tint could_be_checkout_paths = !arg0_cant_be_pathspec &&\n@@ -1247,10 +1215,6 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\tif (!arg0_cant_be_pathspec && !no_wildcard(arg))\n \t\t\trecover_with_dwim = 0;\n \n-\t\t/*\n-\t\t * Accept \"git checkout foo\", \"git checkout foo --\"\n-\t\t * and \"git switch foo\" as candidates for dwim.\n-\t\t */\n \t\tif (!(argc == 1 && dash_dash_pos == -1) &&\n \t\t    !(argc == 2 && dash_dash_pos == 1) &&\n \t\t    opts->accept_pathspec)\n@@ -1262,7 +1226,7 @@ static int parse_branchname_arg(int argc, const char **argv,\n \t\t\tif (remote) {\n \t\t\t\t*new_branch = arg;\n \t\t\t\targ = remote;\n-\t\t\t\t/* DWIMmed to create local branch, case (3).(b) */\n+\t\t\t\t/* DWIMmed to create local branch */\n \t\t\t} else {\n \t\t\t\trecover_with_dwim = 0;\n \t\t\t}\n@@ -1277,19 +1241,11 @@ static int parse_branchname_arg(int argc, const char **argv,\n \n \tsetup_new_branch_info_and_source_tree(new_branch_info, opts, rev, arg);\n \n-\tif (!opts->source_tree)                   /* case (1): want a tree */\n+\tif (!opts->source_tree)\n \t\tdie(_(\"reference is not a tree: %s\"), arg);\n \n-\tif (!arg0_cant_be_pathspec) {\t/* case (3).(d) -> (1) */\n-\t\t/*\n-\t\t * Do not complain the most common case\n-\t\t *\tgit checkout branch\n-\t\t * even if there happen to be a file called 'branch';\n-\t\t * it would be extremely annoying.\n-\t\t */\n-\t\tif (argc > 1)\n-\t\t\tverify_non_filename(opts->prefix, arg);\n-\t}\n+\tif (!arg0_cant_be_pathspec && argc > 1)\n+\t\tverify_non_filename(opts->prefix, arg);\n \n \treturn (opts->accept_pathspec && dash_dash_pos == 1) ? 2 : 1;\n }\n-- \ngitgitgadget\n\n"},{"id":"388554","messageId":"8b300527-0800-72dc-b901-74ecbe15622a@syntevo.com","threadId":"52440","inReplyTo":"xmqqr211cmxy.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 02/18] t7526: add tests for error conditions","fromName":"Alexandr Miloslavskiy","fromEmail":"alexandr.miloslavskiy@syntevo.com","sentAt":"2019-12-19T18:03:49Z","receivedAt":"2019-12-19T18:03:52Z","isPatch":true,"sender":{"key":"alexandr.miloslavskiy@syntevo.com","avatar":null},"body":"On 18.12.2019 23:02, Junio C Hamano wrote:\n\n>> +\ttest_must_fail git commit --pathspec-from-file=- --interactive -m \"Commit\" <list 2>err &&\n>> +\ttest_i18ngrep \"\\-\\-pathspec-from-file is incompatible with \\-\\-interactive/\\-\\-patc\n> h\" err &&\n> \n> What's with the overly-noisy quoting of dashes here?  To match a\n> string that happens to begin with a dash, either one of\n> \n> \t$ grep -e \"-this string begins with and has-many-dashes-in-it\" file\n> \t$ grep \"[-]this string begins with and has-many-dashes-in-it\" file\n> \n> would be sufficient and more idiomatic.\n> \n> Or am I missing some other reason why the test is written this way?\n\nNo, it's just my lack of experience. Thanks for your fix! I have applied \nit in V3.\n"}]}