{"thread":{"id":"55091","subject":"[PATCH 0/9] stash show: learn --include-untracked and --only-untracked","startedAt":"2021-02-02T09:34:35Z","lastAt":"2021-03-04T01:43:46Z","messageCount":45,"participants":["Denton Liu","Eric Sunshine","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":9},"messages":[{"id":"415897","messageId":"cover.1612258145.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":null,"subject":"[PATCH 0/9] stash show: learn --include-untracked and --only-untracked","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-02T09:31:50Z","receivedAt":"2021-02-02T09:34:35Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"A blindspot that I've noticed in git is that it's not possible to\nproperly view a stash entry that has untracked files via `git stash\nshow`. Teach `git stash show --include-untracked` which should do this.\nIn addition, this series also teaches `--only-untracked` and the\n`stash.showIncludeUntracked` config option.\n\nThe first seven patches of this series are just some clean up that I've\ndone prior to working (because it bothers me). The remaining two patches\nshould be the meat of the change.\n\nDenton Liu (9):\n  git-stash.txt: be explicit about subcommand options\n  t3905: remove spaces after redirect operators\n  t3905: move all commands into test cases\n  t3905: remove nested git in command substitution\n  t3905: replace test -s with test_file_not_empty\n  t3905: use test_cmp() to check file contents\n  stash: declare ref_stash as an array\n  stash show: teach --include-tracked and --only-untracked\n  stash show: learn stash.showIncludeUntracked\n\n Documentation/config/stash.txt         |   5 +\n Documentation/git-stash.txt            |  22 +-\n builtin/stash.c                        |  30 ++-\n contrib/completion/git-completion.bash |   2 +-\n t/t3905-stash-include-untracked.sh     | 278 +++++++++++++++++--------\n 5 files changed, 235 insertions(+), 102 deletions(-)\n\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"415898","messageId":"17675b9e4c0c50e6bfd5219c933b5ba1148ab86c.1612258145.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612258145.git.liu.denton@gmail.com","subject":"[PATCH 1/9] git-stash.txt: be explicit about subcommand options","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-02T09:33:18Z","receivedAt":"2021-02-02T09:34:54Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Currently, the options for the `list` and `show` subcommands are just\nlisted as `<options>`. This seems to imply, from a cursory glance at the\nsummary, that they take the stash options listed below. However, reading\nmore carefully, we see that they take log options and diff options\nrespectively.\n\nMake it more obvious that they take log and diff options by explicitly\nstating this in the subcommand summary.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/git-stash.txt | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 31f1beb65b..46ee37b35a 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -67,7 +67,7 @@ save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q\n \tInstead, all non-option arguments are concatenated to form the stash\n \tmessage.\n \n-list [<options>]::\n+list [<log options>]::\n \n \tList the stash entries that you currently have.  Each 'stash entry' is\n \tlisted with its name (e.g. `stash@{0}` is the latest entry, `stash@{1}` is\n@@ -83,7 +83,7 @@ stash@{1}: On master: 9cc0589... Add git-stash\n The command takes options applicable to the 'git log'\n command to control what is shown and how. See linkgit:git-log[1].\n \n-show [<options>] [<stash>]::\n+show [<diff options>] [<stash>]::\n \n \tShow the changes recorded in the stash entry as a diff between the\n \tstashed contents and the commit back when the stash entry was first\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"415899","messageId":"4b72d39e01ab674cd6c9a15332021950b4e35b23.1612258145.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612258145.git.liu.denton@gmail.com","subject":"[PATCH 4/9] t3905: remove nested git in command substitution","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-02T09:33:21Z","receivedAt":"2021-02-02T09:36:10Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"If a git command in a nested command substitution fails, it will be\nsilently ignored since only the return code of the outer command\nsubstitutions is reported. Factor out nested command substitutions so\nthat the error codes of those commands are reported.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3905-stash-include-untracked.sh | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex 892a2c8057..f008e5d945 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -36,8 +36,10 @@ test_expect_success 'stash save --include-untracked cleaned the untracked files'\n '\n \n test_expect_success 'stash save --include-untracked stashed the untracked files' '\n-\ttracked=$(git rev-parse --short $(echo 1 | git hash-object --stdin)) &&\n-\tuntracked=$(git rev-parse --short $(echo untracked | git hash-object --stdin)) &&\n+\tone_blob=$(echo 1 | git hash-object --stdin) &&\n+\ttracked=$(git rev-parse --short \"$one_blob\") &&\n+\tuntracked_blob=$(echo untracked | git hash-object --stdin) &&\n+\tuntracked=$(git rev-parse --short \"$untracked_blob\") &&\n \tcat >expect.diff <<-EOF &&\n \tdiff --git a/HEAD b/HEAD\n \tnew file mode 100644\n@@ -117,7 +119,8 @@ test_expect_success 'stash save -u dirty index' '\n '\n \n test_expect_success 'stash save --include-untracked dirty index got stashed' '\n-\tblob=$(git rev-parse --short $(echo 4 | git hash-object --stdin)) &&\n+\tfour_blob=$(echo 4 | git hash-object --stdin) &&\n+\tblob=$(git rev-parse --short \"$four_blob\") &&\n \tcat >expect <<-EOF &&\n \tdiff --git a/file3 b/file3\n \tnew file mode 100644\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"415900","messageId":"0de324e3bc9b900a0e4badd9eba58d196d4f4f0a.1612258145.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612258145.git.liu.denton@gmail.com","subject":"[PATCH 2/9] t3905: remove spaces after redirect operators","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-02T09:33:19Z","receivedAt":"2021-02-02T09:36:38Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"For shell scripts, the usual convention is for there to be no space\nafter redirection operators, (e.g. `>file`, not `> file`). Remove these\nspaces wherever they appear.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3905-stash-include-untracked.sh | 40 +++++++++++++++---------------\n 1 file changed, 20 insertions(+), 20 deletions(-)\n\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex f075c7f1f3..1d416944b7 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -8,16 +8,16 @@ test_description='Test git stash --include-untracked'\n . ./test-lib.sh\n \n test_expect_success 'stash save --include-untracked some dirty working directory' '\n-\techo 1 > file &&\n+\techo 1 >file &&\n \tgit add file &&\n \ttest_tick &&\n \tgit commit -m initial &&\n-\techo 2 > file &&\n+\techo 2 >file &&\n \tgit add file &&\n-\techo 3 > file &&\n+\techo 3 >file &&\n \ttest_tick &&\n-\techo 1 > file2 &&\n-\techo 1 > HEAD &&\n+\techo 1 >file2 &&\n+\techo 1 >HEAD &&\n \tmkdir untracked &&\n \techo untracked >untracked/untracked &&\n \tgit stash --include-untracked &&\n@@ -25,7 +25,7 @@ test_expect_success 'stash save --include-untracked some dirty working directory\n \tgit diff-index --cached --quiet HEAD\n '\n \n-cat > expect <<EOF\n+cat >expect <<EOF\n ?? actual\n ?? expect\n EOF\n@@ -37,7 +37,7 @@ test_expect_success 'stash save --include-untracked cleaned the untracked files'\n \n tracked=$(git rev-parse --short $(echo 1 | git hash-object --stdin))\n untracked=$(git rev-parse --short $(echo untracked | git hash-object --stdin))\n-cat > expect.diff <<EOF\n+cat >expect.diff <<EOF\n diff --git a/HEAD b/HEAD\n new file mode 100644\n index 0000000..$tracked\n@@ -60,7 +60,7 @@ index 0000000..$untracked\n @@ -0,0 +1 @@\n +untracked\n EOF\n-cat > expect.lstree <<EOF\n+cat >expect.lstree <<EOF\n HEAD\n file2\n untracked\n@@ -85,7 +85,7 @@ test_expect_success 'stash save --patch --all fails' '\n \n git clean --force --quiet\n \n-cat > expect <<EOF\n+cat >expect <<EOF\n  M file\n ?? HEAD\n ?? actual\n@@ -105,14 +105,14 @@ test_expect_success 'stash pop after save --include-untracked leaves files untra\n git clean --force --quiet -d\n \n test_expect_success 'stash save -u dirty index' '\n-\techo 4 > file3 &&\n+\techo 4 >file3 &&\n \tgit add file3 &&\n \ttest_tick &&\n \tgit stash -u\n '\n \n blob=$(git rev-parse --short $(echo 4 | git hash-object --stdin))\n-cat > expect <<EOF\n+cat >expect <<EOF\n diff --git a/file3 b/file3\n new file mode 100644\n index 0000000..$blob\n@@ -128,12 +128,12 @@ test_expect_success 'stash save --include-untracked dirty index got stashed' '\n \ttest_cmp expect actual\n '\n \n-git reset > /dev/null\n+git reset >/dev/null\n \n # Must direct output somewhere where it won't be considered an untracked file\n test_expect_success 'stash save --include-untracked -q is quiet' '\n-\techo 1 > file5 &&\n-\tgit stash save --include-untracked --quiet > .git/stash-output.out 2>&1 &&\n+\techo 1 >file5 &&\n+\tgit stash save --include-untracked --quiet >.git/stash-output.out 2>&1 &&\n \ttest_line_count = 0 .git/stash-output.out &&\n \trm -f .git/stash-output.out\n '\n@@ -141,7 +141,7 @@ test_expect_success 'stash save --include-untracked -q is quiet' '\n test_expect_success 'stash save --include-untracked removed files' '\n \trm -f file &&\n \tgit stash save --include-untracked &&\n-\techo 1 > expect &&\n+\techo 1 >expect &&\n \ttest_cmp expect file\n '\n \n@@ -152,14 +152,14 @@ test_expect_success 'stash save --include-untracked removed files got stashed' '\n \ttest_path_is_missing file\n '\n \n-cat > .gitignore <<EOF\n+cat >.gitignore <<EOF\n .gitignore\n ignored\n ignored.d/\n EOF\n \n test_expect_success 'stash save --include-untracked respects .gitignore' '\n-\techo ignored > ignored &&\n+\techo ignored >ignored &&\n \tmkdir ignored.d &&\n \techo ignored >ignored.d/untracked &&\n \tgit stash -u &&\n@@ -169,7 +169,7 @@ test_expect_success 'stash save --include-untracked respects .gitignore' '\n '\n \n test_expect_success 'stash save -u can stash with only untracked files different' '\n-\techo 4 > file4 &&\n+\techo 4 >file4 &&\n \tgit stash -u &&\n \ttest_path_is_missing file4\n '\n@@ -214,7 +214,7 @@ test_expect_success 'stash push with $IFS character' '\n \ttest_path_is_file bar\n '\n \n-cat > .gitignore <<EOF\n+cat >.gitignore <<EOF\n ignored\n ignored.d/*\n EOF\n@@ -224,7 +224,7 @@ test_expect_success 'stash previously ignored file' '\n \tgit add .gitignore &&\n \tgit commit -m \"Add .gitignore\" &&\n \t>ignored.d/foo &&\n-\techo \"!ignored.d/foo\" >> .gitignore &&\n+\techo \"!ignored.d/foo\" >>.gitignore &&\n \tgit stash save --include-untracked &&\n \ttest_path_is_missing ignored.d/foo &&\n \tgit stash pop &&\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"415901","messageId":"519840b1a280fd1a47c028ba9de776727ed77608.1612258145.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612258145.git.liu.denton@gmail.com","subject":"[PATCH 3/9] t3905: move all commands into test cases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-02T09:33:20Z","receivedAt":"2021-02-02T09:37:00Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"In order to modernize the tests, move commands that currently run\noutside of test cases into a test case. Where possible, clean up files\nthat are produced using test_when_finished() but in the case where files\npersist over multiple test cases, create a new test case to perform\ncleanup.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3905-stash-include-untracked.sh | 147 +++++++++++++++--------------\n 1 file changed, 75 insertions(+), 72 deletions(-)\n\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex 1d416944b7..892a2c8057 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -25,48 +25,48 @@ test_expect_success 'stash save --include-untracked some dirty working directory\n \tgit diff-index --cached --quiet HEAD\n '\n \n-cat >expect <<EOF\n-?? actual\n-?? expect\n-EOF\n-\n test_expect_success 'stash save --include-untracked cleaned the untracked files' '\n+\tcat >expect <<-EOF &&\n+\t?? actual\n+\t?? expect\n+\tEOF\n+\n \tgit status --porcelain >actual &&\n \ttest_cmp expect actual\n '\n \n-tracked=$(git rev-parse --short $(echo 1 | git hash-object --stdin))\n-untracked=$(git rev-parse --short $(echo untracked | git hash-object --stdin))\n-cat >expect.diff <<EOF\n-diff --git a/HEAD b/HEAD\n-new file mode 100644\n-index 0000000..$tracked\n---- /dev/null\n-+++ b/HEAD\n-@@ -0,0 +1 @@\n-+1\n-diff --git a/file2 b/file2\n-new file mode 100644\n-index 0000000..$tracked\n---- /dev/null\n-+++ b/file2\n-@@ -0,0 +1 @@\n-+1\n-diff --git a/untracked/untracked b/untracked/untracked\n-new file mode 100644\n-index 0000000..$untracked\n---- /dev/null\n-+++ b/untracked/untracked\n-@@ -0,0 +1 @@\n-+untracked\n-EOF\n-cat >expect.lstree <<EOF\n-HEAD\n-file2\n-untracked\n-EOF\n-\n test_expect_success 'stash save --include-untracked stashed the untracked files' '\n+\ttracked=$(git rev-parse --short $(echo 1 | git hash-object --stdin)) &&\n+\tuntracked=$(git rev-parse --short $(echo untracked | git hash-object --stdin)) &&\n+\tcat >expect.diff <<-EOF &&\n+\tdiff --git a/HEAD b/HEAD\n+\tnew file mode 100644\n+\tindex 0000000..$tracked\n+\t--- /dev/null\n+\t+++ b/HEAD\n+\t@@ -0,0 +1 @@\n+\t+1\n+\tdiff --git a/file2 b/file2\n+\tnew file mode 100644\n+\tindex 0000000..$tracked\n+\t--- /dev/null\n+\t+++ b/file2\n+\t@@ -0,0 +1 @@\n+\t+1\n+\tdiff --git a/untracked/untracked b/untracked/untracked\n+\tnew file mode 100644\n+\tindex 0000000..$untracked\n+\t--- /dev/null\n+\t+++ b/untracked/untracked\n+\t@@ -0,0 +1 @@\n+\t+untracked\n+\tEOF\n+\tcat >expect.lstree <<-EOF &&\n+\tHEAD\n+\tfile2\n+\tuntracked\n+\tEOF\n+\n \ttest_path_is_missing file2 &&\n \ttest_path_is_missing untracked &&\n \ttest_path_is_missing HEAD &&\n@@ -83,18 +83,21 @@ test_expect_success 'stash save --patch --all fails' '\n \ttest_must_fail git stash --patch --all\n '\n \n-git clean --force --quiet\n+test_expect_success 'clean up untracked/untracked file to prepare for next tests' '\n+\tgit clean --force --quiet\n \n-cat >expect <<EOF\n- M file\n-?? HEAD\n-?? actual\n-?? expect\n-?? file2\n-?? untracked/\n-EOF\n+'\n \n test_expect_success 'stash pop after save --include-untracked leaves files untracked again' '\n+\tcat >expect <<-EOF &&\n+\t M file\n+\t?? HEAD\n+\t?? actual\n+\t?? expect\n+\t?? file2\n+\t?? untracked/\n+\tEOF\n+\n \tgit stash pop &&\n \tgit status --porcelain >actual &&\n \ttest_cmp expect actual &&\n@@ -102,7 +105,9 @@ test_expect_success 'stash pop after save --include-untracked leaves files untra\n \ttest untracked = \"$(cat untracked/untracked)\"\n '\n \n-git clean --force --quiet -d\n+test_expect_success 'clean up untracked/ directory to prepare for next tests' '\n+\tgit clean --force --quiet -d\n+'\n \n test_expect_success 'stash save -u dirty index' '\n \techo 4 >file3 &&\n@@ -111,25 +116,24 @@ test_expect_success 'stash save -u dirty index' '\n \tgit stash -u\n '\n \n-blob=$(git rev-parse --short $(echo 4 | git hash-object --stdin))\n-cat >expect <<EOF\n-diff --git a/file3 b/file3\n-new file mode 100644\n-index 0000000..$blob\n---- /dev/null\n-+++ b/file3\n-@@ -0,0 +1 @@\n-+4\n-EOF\n-\n test_expect_success 'stash save --include-untracked dirty index got stashed' '\n+\tblob=$(git rev-parse --short $(echo 4 | git hash-object --stdin)) &&\n+\tcat >expect <<-EOF &&\n+\tdiff --git a/file3 b/file3\n+\tnew file mode 100644\n+\tindex 0000000..$blob\n+\t--- /dev/null\n+\t+++ b/file3\n+\t@@ -0,0 +1 @@\n+\t+4\n+\tEOF\n+\n \tgit stash pop --index &&\n+\ttest_when_finished \"git reset\" &&\n \tgit diff --cached >actual &&\n \ttest_cmp expect actual\n '\n \n-git reset >/dev/null\n-\n # Must direct output somewhere where it won't be considered an untracked file\n test_expect_success 'stash save --include-untracked -q is quiet' '\n \techo 1 >file5 &&\n@@ -142,23 +146,22 @@ test_expect_success 'stash save --include-untracked removed files' '\n \trm -f file &&\n \tgit stash save --include-untracked &&\n \techo 1 >expect &&\n+\ttest_when_finished \"rm -f expect\" &&\n \ttest_cmp expect file\n '\n \n-rm -f expect\n-\n test_expect_success 'stash save --include-untracked removed files got stashed' '\n \tgit stash pop &&\n \ttest_path_is_missing file\n '\n \n-cat >.gitignore <<EOF\n-.gitignore\n-ignored\n-ignored.d/\n-EOF\n-\n test_expect_success 'stash save --include-untracked respects .gitignore' '\n+\tcat >.gitignore <<-EOF &&\n+\t.gitignore\n+\tignored\n+\tignored.d/\n+\tEOF\n+\n \techo ignored >ignored &&\n \tmkdir ignored.d &&\n \techo ignored >ignored.d/untracked &&\n@@ -214,12 +217,12 @@ test_expect_success 'stash push with $IFS character' '\n \ttest_path_is_file bar\n '\n \n-cat >.gitignore <<EOF\n-ignored\n-ignored.d/*\n-EOF\n-\n test_expect_success 'stash previously ignored file' '\n+\tcat >.gitignore <<-EOF &&\n+\tignored\n+\tignored.d/*\n+\tEOF\n+\n \tgit reset HEAD &&\n \tgit add .gitignore &&\n \tgit commit -m \"Add .gitignore\" &&\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"415902","messageId":"7fe27ab620457af9b777f6087dc5c235fb87eb01.1612258145.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612258145.git.liu.denton@gmail.com","subject":"[PATCH 5/9] t3905: replace test -s with test_file_not_empty","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-02T09:33:22Z","receivedAt":"2021-02-02T09:38:55Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"In order to modernize the test script, replace `test -s` with\ntest_file_not_empty(), which provides better diagnostic output in the\ncase of failure.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3905-stash-include-untracked.sh | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex f008e5d945..c87ac24042 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -169,9 +169,9 @@ test_expect_success 'stash save --include-untracked respects .gitignore' '\n \tmkdir ignored.d &&\n \techo ignored >ignored.d/untracked &&\n \tgit stash -u &&\n-\ttest -s ignored &&\n-\ttest -s ignored.d/untracked &&\n-\ttest -s .gitignore\n+\ttest_file_not_empty ignored &&\n+\ttest_file_not_empty ignored.d/untracked &&\n+\ttest_file_not_empty .gitignore\n '\n \n test_expect_success 'stash save -u can stash with only untracked files different' '\n@@ -189,9 +189,9 @@ test_expect_success 'stash save --all does not respect .gitignore' '\n \n test_expect_success 'stash save --all is stash poppable' '\n \tgit stash pop &&\n-\ttest -s ignored &&\n-\ttest -s ignored.d/untracked &&\n-\ttest -s .gitignore\n+\ttest_file_not_empty ignored &&\n+\ttest_file_not_empty ignored.d/untracked &&\n+\ttest_file_not_empty .gitignore\n '\n \n test_expect_success 'stash push --include-untracked with pathspec' '\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"415903","messageId":"c2375d1fc6c5fe7a36d2f868b172e83749d67b45.1612258145.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612258145.git.liu.denton@gmail.com","subject":"[PATCH 8/9] stash show: teach --include-tracked and --only-untracked","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-02T09:33:25Z","receivedAt":"2021-02-02T09:38:57Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Stash entries can be made with untracked files via\n`git stash push --include-untracked`. However, because the untracked\nfiles are stored in the third parent of the stash entry and not the\nstash entry itself, running `git stash show` does not include the\nuntracked files as part of the diff.\n\nTeach stash the --include-tracked option, which also displays the\nuntracked files in a stash entry from the third parent (if it exists).\nDo this by just concatenating the diff of the third parent against an\nempty tree. One limitation of this is that it would be possible to\nmanually craft a stash entry which would present duplicate entries in\nthe diff by duplicating a file in the stash and in the third parent.\nThis seems like an instance of \"Doctor, it hurts when I do this! So\ndon't do that!\" so this can be written off.\n\nAlso, teach stash the --only-untracked option which only shows the\nuntracked files of a stash entry. This is similar to `git show stash^3`\nbut it is nice to provide a convenient abstraction for it so that users\ndo not have to think about the underlying implementation.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/git-stash.txt            | 16 +++--\n builtin/stash.c                        | 20 +++++-\n contrib/completion/git-completion.bash |  2 +-\n t/t3905-stash-include-untracked.sh     | 84 ++++++++++++++++++++++++++\n 4 files changed, 116 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 46ee37b35a..a9956e5c51 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -83,7 +83,7 @@ stash@{1}: On master: 9cc0589... Add git-stash\n The command takes options applicable to the 'git log'\n command to control what is shown and how. See linkgit:git-log[1].\n \n-show [<diff options>] [<stash>]::\n+show [-u|--include-untracked|--only-untracked] [<diff options>] [<stash>]::\n \n \tShow the changes recorded in the stash entry as a diff between the\n \tstashed contents and the commit back when the stash entry was first\n@@ -160,10 +160,18 @@ up with `git clean`.\n \n -u::\n --include-untracked::\n-\tThis option is only valid for `push` and `save` commands.\n+--no-include-untracked::\n+\tWhen used with the `push` and `save` commands,\n+\tall untracked files are also stashed and then cleaned up with\n+\t`git clean`.\n +\n-All untracked files are also stashed and then cleaned up with\n-`git clean`.\n+When used with the `show` command, show the untracked files in the stash\n+entry as part of the diff.\n+\n+--only-untracked::\n+\tThis option is only valid for the `show` command.\n++\n+Show only the untracked files in the stash entry as part of the diff.\n \n --index::\n \tThis option is only valid for `pop` and `apply` commands.\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 6f2b58f6ab..7a8770676b 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -795,7 +795,18 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \tstruct rev_info rev;\n \tstruct strvec stash_args = STRVEC_INIT;\n \tstruct strvec revision_args = STRVEC_INIT;\n+\tenum {\n+\t\tUNTRACKED_NONE,\n+\t\tUNTRACKED_INCLUDE,\n+\t\tUNTRACKED_ONLY\n+\t} show_untracked = UNTRACKED_NONE;\n \tstruct option options[] = {\n+\t\tOPT_SET_INT('u', \"include-untracked\", &show_untracked,\n+\t\t\t    N_(\"include untracked files in the stash\"),\n+\t\t\t    UNTRACKED_INCLUDE),\n+\t\tOPT_SET_INT_F(0, \"only-untracked\", &show_untracked,\n+\t\t\t      N_(\"only show untracked files in the stash\"),\n+\t\t\t      UNTRACKED_ONLY, PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \n@@ -803,6 +814,10 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \tgit_config(git_diff_ui_config, NULL);\n \tinit_revisions(&rev, prefix);\n \n+\targc = parse_options(argc, argv, prefix, options, git_stash_show_usage,\n+\t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n \tstrvec_push(&revision_args, argv[0]);\n \tfor (i = 1; i < argc; i++) {\n \t\tif (argv[i][0] != '-')\n@@ -845,7 +860,10 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \n \trev.diffopt.flags.recursive = 1;\n \tsetup_diff_pager(&rev.diffopt);\n-\tdiff_tree_oid(&info.b_commit, &info.w_commit, \"\", &rev.diffopt);\n+\tif (show_untracked != UNTRACKED_ONLY)\n+\t\tdiff_tree_oid(&info.b_commit, &info.w_commit, \"\", &rev.diffopt);\n+\tif (show_untracked != UNTRACKED_NONE && info.has_u)\n+\t\tdiff_root_tree_oid(&info.u_tree, \"\", &rev.diffopt);\n \tlog_tree_diff_flush(&rev);\n \n \tfree_stash_info(&info);\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 4b1f4264a6..64ef6ffa21 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -3051,7 +3051,7 @@ _git_stash ()\n \t\t\t__gitcomp \"--name-status --oneline --patch-with-stat\"\n \t\t\t;;\n \t\tshow,--*)\n-\t\t\t__gitcomp \"$__git_diff_common_options\"\n+\t\t\t__gitcomp \"--include-untracked --only-untracked $__git_diff_common_options\"\n \t\t\t;;\n \t\tbranch,--*)\n \t\t\t;;\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex b26a97aef4..978bc97baf 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -297,4 +297,88 @@ test_expect_success 'stash -u with globs' '\n \ttest_path_is_missing untracked.txt\n '\n \n+test_expect_success 'stash show --include-untracked shows untracked files' '\n+\tgit reset --hard &&\n+\tgit clean -xf &&\n+\t>untracked &&\n+\t>tracked &&\n+\tgit add tracked &&\n+\tgit stash -u &&\n+\n+\tcat >expect <<-EOF &&\n+\t tracked   | 0\n+\t untracked | 0\n+\t 2 files changed, 0 insertions(+), 0 deletions(-)\n+\tEOF\n+\tgit stash show --include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show -u >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --no-include-untracked --include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --only-untracked --include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tcat >expect <<-EOF &&\n+\tdiff --git a/tracked b/tracked\n+\tnew file mode 100644\n+\tindex 0000000..e69de29\n+\tdiff --git a/untracked b/untracked\n+\tnew file mode 100644\n+\tindex 0000000..e69de29\n+\tEOF\n+\tgit stash show -p --include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --include-untracked -p >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stash show --only-untracked only shows untracked files' '\n+\tgit reset --hard &&\n+\tgit clean -xf &&\n+\t>untracked &&\n+\t>tracked &&\n+\tgit add tracked &&\n+\tgit stash -u &&\n+\n+\tcat >expect <<-EOF &&\n+\t untracked | 0\n+\t 1 file changed, 0 insertions(+), 0 deletions(-)\n+\tEOF\n+\tgit stash show --only-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --no-include-untracked --only-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --include-untracked --only-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tcat >expect <<-EOF &&\n+\tdiff --git a/untracked b/untracked\n+\tnew file mode 100644\n+\tindex 0000000..e69de29\n+\tEOF\n+\tgit stash show -p --only-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --only-untracked -p >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stash show --no-include-untracked cancels --{include,show}-untracked' '\n+\tgit reset --hard &&\n+\tgit clean -xf &&\n+\t>untracked &&\n+\t>tracked &&\n+\tgit add tracked &&\n+\tgit stash -u &&\n+\n+\tcat >expect <<-EOF &&\n+\t tracked | 0\n+\t 1 file changed, 0 insertions(+), 0 deletions(-)\n+\tEOF\n+\tgit stash show --only-untracked --no-include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --include-untracked --no-include-untracked >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"415904","messageId":"2c5d5d9dd47f1c6ba49312204d155d404d5fdc4f.1612258145.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612258145.git.liu.denton@gmail.com","subject":"[PATCH 9/9] stash show: learn stash.showIncludeUntracked","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-02T09:33:26Z","receivedAt":"2021-02-02T09:39:25Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"The previous commit teaches `git stash show --include-untracked`. It\nmay be desirable for a user to be able to always enable the\n--include-untracked behavior. Teach the stash.showIncludeUntracked\nconfig option which allows users to do this in a similar manner to\nstash.showPatch.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/config/stash.txt     | 5 +++++\n Documentation/git-stash.txt        | 4 ++--\n builtin/stash.c                    | 8 ++++++++\n t/t3905-stash-include-untracked.sh | 2 ++\n 4 files changed, 17 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/config/stash.txt b/Documentation/config/stash.txt\nindex 00eb35434e..413f907cba 100644\n--- a/Documentation/config/stash.txt\n+++ b/Documentation/config/stash.txt\n@@ -5,6 +5,11 @@ stash.useBuiltin::\n \tis always used. Setting this will emit a warning, to alert any\n \tremaining users that setting this now does nothing.\n \n+stash.showIncludeUntracked::\n+\tIf this is set to true, the `git stash show` command without an\n+\toption will show the untracked files of a stash entry.  Defaults to\n+\tfalse. See description of 'show' command in linkgit:git-stash[1].\n+\n stash.showPatch::\n \tIf this is set to true, the `git stash show` command without an\n \toption will show the stash entry in patch form.  Defaults to false.\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex a9956e5c51..e18c1deb97 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -91,8 +91,8 @@ show [-u|--include-untracked|--only-untracked] [<diff options>] [<stash>]::\n \tBy default, the command shows the diffstat, but it will accept any\n \tformat known to 'git diff' (e.g., `git stash show -p stash@{1}`\n \tto view the second most recent entry in patch form).\n-\tYou can use stash.showStat and/or stash.showPatch config variables\n-\tto change the default behavior.\n+\tYou can use stash.showIncludeUntracked, stash.showStat, and\n+\tstash.showPatch config variables to change the default behavior.\n \n pop [--index] [-q|--quiet] [<stash>]::\n \ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 7a8770676b..784fb518be 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -768,6 +768,7 @@ static int list_stash(int argc, const char **argv, const char *prefix)\n \n static int show_stat = 1;\n static int show_patch;\n+static int show_include_untracked;\n static int use_legacy_stash;\n \n static int git_stash_config(const char *var, const char *value, void *cb)\n@@ -780,6 +781,10 @@ static int git_stash_config(const char *var, const char *value, void *cb)\n \t\tshow_patch = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"stash.showincludeuntracked\")) {\n+\t\tshow_include_untracked = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(var, \"stash.usebuiltin\")) {\n \t\tuse_legacy_stash = !git_config_bool(var, value);\n \t\treturn 0;\n@@ -842,6 +847,9 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \t\tif (show_patch)\n \t\t\trev.diffopt.output_format |= DIFF_FORMAT_PATCH;\n \n+\t\tif (show_include_untracked)\n+\t\t\tshow_untracked = UNTRACKED_INCLUDE;\n+\n \t\tif (!show_stat && !show_patch) {\n \t\t\tfree_stash_info(&info);\n \t\t\treturn 0;\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex 978bc97baf..8bcd4c5ca8 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -318,6 +318,8 @@ test_expect_success 'stash show --include-untracked shows untracked files' '\n \ttest_cmp expect actual &&\n \tgit stash show --only-untracked --include-untracked >actual &&\n \ttest_cmp expect actual &&\n+\tgit -c stash.showIncludeUntracked=true stash show >actual &&\n+\ttest_cmp expect actual &&\n \n \tcat >expect <<-EOF &&\n \tdiff --git a/tracked b/tracked\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"415905","messageId":"b5f22de3fc320d23ba2c5342776b56770266ca5f.1612258145.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612258145.git.liu.denton@gmail.com","subject":"[PATCH 7/9] stash: declare ref_stash as an array","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-02T09:33:24Z","receivedAt":"2021-02-02T09:39:26Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Save sizeof(const char *) bytes by declaring ref_stash as an array\ninstead of having a redundant pointer to an array.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/stash.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 9bc85f91cd..6f2b58f6ab 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -87,7 +87,7 @@ static const char * const git_stash_save_usage[] = {\n \tNULL\n };\n \n-static const char *ref_stash = \"refs/stash\";\n+static const char ref_stash[] = \"refs/stash\";\n static struct strbuf stash_index_path = STRBUF_INIT;\n \n /*\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"415906","messageId":"4a5dd83ff4269c10e20e0af4867d3c574b04edf8.1612258145.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612258145.git.liu.denton@gmail.com","subject":"[PATCH 6/9] t3905: use test_cmp() to check file contents","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-02T09:33:23Z","receivedAt":"2021-02-02T09:40:13Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Modernize the script by doing file content comparisons using test_cmp()\ninstead of `test x = \"$(cat file)\"`.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3905-stash-include-untracked.sh | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex c87ac24042..b26a97aef4 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -103,8 +103,10 @@ test_expect_success 'stash pop after save --include-untracked leaves files untra\n \tgit stash pop &&\n \tgit status --porcelain >actual &&\n \ttest_cmp expect actual &&\n-\ttest \"1\" = \"$(cat file2)\" &&\n-\ttest untracked = \"$(cat untracked/untracked)\"\n+\techo 1 >expect_file2 &&\n+\ttest_cmp expect_file2 file2 &&\n+\techo untracked >untracked_expect &&\n+\ttest_cmp untracked_expect untracked/untracked\n '\n \n test_expect_success 'clean up untracked/ directory to prepare for next tests' '\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"415943","messageId":"CAPig+cRuD19xwfmkn2E4669n+0BPJg24NBOp-Zjv9uKDTXedFA@mail.gmail.com","threadId":"55091","inReplyTo":"17675b9e4c0c50e6bfd5219c933b5ba1148ab86c.1612258145.git.liu.denton@gmail.com","subject":"Re: [PATCH 1/9] git-stash.txt: be explicit about subcommand options","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-02-02T17:37:07Z","receivedAt":"2021-02-02T17:41:25Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Feb 2, 2021 at 4:36 AM Denton Liu <liu.denton@gmail.com> wrote:\n> Currently, the options for the `list` and `show` subcommands are just\n> listed as `<options>`. This seems to imply, from a cursory glance at the\n> summary, that they take the stash options listed below. However, reading\n> more carefully, we see that they take log options and diff options\n> respectively.\n>\n> Make it more obvious that they take log and diff options by explicitly\n> stating this in the subcommand summary.\n>\n> Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> ---\n> diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\n> @@ -67,7 +67,7 @@ save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q\n> -list [<options>]::\n> +list [<log options>]::\n>\n> -show [<options>] [<stash>]::\n> +show [<diff options>] [<stash>]::\n\nI might suggest that it is more common to hyphenate these words than\nto separate them with spaces:\n\n    list [<log-options>]::\n    show [<diff-options>] [<stash>]::\n"},{"id":"415964","messageId":"xmqqft2ef8qq.fsf@gitster.c.googlers.com","threadId":"55091","inReplyTo":"519840b1a280fd1a47c028ba9de776727ed77608.1612258145.git.liu.denton@gmail.com","subject":"Re: [PATCH 3/9] t3905: move all commands into test cases","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-02T21:41:17Z","receivedAt":"2021-02-02T21:42:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n>  test_expect_success 'stash save --include-untracked stashed the untracked files' '\n> +\ttracked=$(git rev-parse --short $(echo 1 | git hash-object --stdin)) &&\n> +\tuntracked=$(git rev-parse --short $(echo untracked | git hash-object --stdin)) &&\n\nNot a new issue introduced by this patch, but\n\n * these will fail if blobs that record \"1\\n\" and \"untracked\\n\" do\n   not exist in the repository already, because the hash-object\n   command lacks the \"-w\" option.\n\n * the reason why they do not fail is because there are these blobs\n   already; grabbing them using extended SHA-1 expression may be\n   simpler to read, e.g.\n\n\ttracked=$(git rev-parse --short HEAD:file)\n\n * even if it is not trivial to get to such a blob object, it\n   probably is easier to read the test if a file that has the\n   desired contents in it is used, not an \"echo\", e.g.\n\n\tuntracked=$(git rev-parse --short $(git hash-object -w untracked/untracked))\n\nWe may want to clean these up someday, but it does not have to be\npart of this topic (#leftoverbits).\n\n> +\tcat >expect.diff <<-EOF &&\n> +\tdiff --git a/HEAD b/HEAD\n> +\tnew file mode 100644\n> +\tindex 0000000..$tracked\n> +\t--- /dev/null\n> +\t+++ b/HEAD\n> +\t@@ -0,0 +1 @@\n> +\t+1\n> +\tdiff --git a/file2 b/file2\n> +\tnew file mode 100644\n> +\tindex 0000000..$tracked\n> +\t--- /dev/null\n> +\t+++ b/file2\n> +\t@@ -0,0 +1 @@\n> +\t+1\n> +\tdiff --git a/untracked/untracked b/untracked/untracked\n> +\tnew file mode 100644\n> +\tindex 0000000..$untracked\n> +\t--- /dev/null\n> +\t+++ b/untracked/untracked\n> +\t@@ -0,0 +1 @@\n> +\t+untracked\n> +\tEOF\n\n"},{"id":"415965","messageId":"xmqqa6smf8i8.fsf@gitster.c.googlers.com","threadId":"55091","inReplyTo":"b5f22de3fc320d23ba2c5342776b56770266ca5f.1612258145.git.liu.denton@gmail.com","subject":"Re: [PATCH 7/9] stash: declare ref_stash as an array","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-02T21:46:23Z","receivedAt":"2021-02-02T21:47:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> Save sizeof(const char *) bytes by declaring ref_stash as an array\n> instead of having a redundant pointer to an array.\n\nOne important prerequisite for this rewrite to work is that nobody\nin the code repoints ref_stash pointer to point at a different\nstring, which has been the case for the past number of years and\nwill continue to be so.\n\nLooks good.\n\n> Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> ---\n>  builtin/stash.c | 2 +-\n>  1 file changed, 1 insertion(+), 1 deletion(-)\n>\n> diff --git a/builtin/stash.c b/builtin/stash.c\n> index 9bc85f91cd..6f2b58f6ab 100644\n> --- a/builtin/stash.c\n> +++ b/builtin/stash.c\n> @@ -87,7 +87,7 @@ static const char * const git_stash_save_usage[] = {\n>  \tNULL\n>  };\n>  \n> -static const char *ref_stash = \"refs/stash\";\n> +static const char ref_stash[] = \"refs/stash\";\n>  static struct strbuf stash_index_path = STRBUF_INIT;\n>  \n>  /*\n"},{"id":"415966","messageId":"xmqq5z3af81v.fsf@gitster.c.googlers.com","threadId":"55091","inReplyTo":"c2375d1fc6c5fe7a36d2f868b172e83749d67b45.1612258145.git.liu.denton@gmail.com","subject":"Re: [PATCH 8/9] stash show: teach --include-tracked and --only-untracked","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-02T21:56:12Z","receivedAt":"2021-02-02T21:57:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> Stash entries can be made with untracked files via\n> `git stash push --include-untracked`. However, because the untracked\n> files are stored in the third parent of the stash entry and not the\n> stash entry itself, running `git stash show` does not include the\n> untracked files as part of the diff.\n>\n> Teach stash the --include-tracked option, which also displays the\n\nIs that \"tracked\" or \"untracked\"?\n\n> untracked files in a stash entry from the third parent (if it exists).\n> Do this by just concatenating the diff of the third parent against an\n> empty tree. One limitation of this is that it would be possible to\n> manually craft a stash entry which would present duplicate entries in\n> the diff by duplicating a file in the stash and in the third parent.\n\nIn other words, a broken \"stash\" that cannot have be taken with\n\"stash save -u\" may show nonsense?  I wouldn't be so worried about\nit, as long as we won't crash in \"git stash show\".\n\nBut a larger downside is that you will have to see all diffs from\nthe tracked paths from A to Z before you start seeing untracked\npaths from A to Z, which is not what people would expect how\n\"include\" behave---it is more like \"append as afterthought\".  If we\ncannot do a good job showing both in a sensible way, I'd rather not\nto see us introduce such an incomplete \"--include-untracked\" option\nuntil we can do so.\n\nThe \"only-untracked\" one does not have such problem, so it is\nprobably a good feature to add at this moment, though.\n\nThis is not in the scope of this topic, but I wonder if it people\nwant to have the \"--only-untracked\" option on the \"stash apply\"\ncommand.\n"},{"id":"416477","messageId":"cover.1612855690.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612258145.git.liu.denton@gmail.com","subject":"[PATCH v2 0/9] stash show: learn --include-untracked and --only-untracked","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-09T07:28:46Z","receivedAt":"2021-02-09T07:30:00Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"A blindspot that I've noticed in git is that it's not possible to\nproperly view a stash entry that has untracked files via `git stash\nshow`. Teach `git stash show --include-untracked` which should do this.\nIn addition, this series also teaches `--only-untracked` and the\n`stash.showIncludeUntracked` config option.\n\nThe first seven patches of this series are just some clean up that I've\ndone prior to working (because it bothers me). The remaining two patches\nshould be the meat of the change.\n\nChanges since v1:\n\n* Add a dash for <log-options> and <diff-options>\n\n* Fix the misspelling of --include-untracked in a commit message\n\n* Change the approach from concatenating diffs to using `git read-tree`\n\nDenton Liu (9):\n  git-stash.txt: be explicit about subcommand options\n  t3905: remove spaces after redirect operators\n  t3905: move all commands into test cases\n  t3905: remove nested git in command substitution\n  t3905: replace test -s with test_file_not_empty\n  t3905: use test_cmp() to check file contents\n  stash: declare ref_stash as an array\n  stash show: teach --include-untracked and --only-untracked\n  stash show: learn stash.showIncludeUntracked\n\n Documentation/config/stash.txt         |   5 +\n Documentation/git-stash.txt            |  22 +-\n builtin/stash.c                        |  87 +++++++-\n contrib/completion/git-completion.bash |   2 +-\n t/t3905-stash-include-untracked.sh     | 278 +++++++++++++++++--------\n 5 files changed, 292 insertions(+), 102 deletions(-)\n\nRange-diff against v1:\n 1:  17675b9e4c !  1:  5697f14f1c git-stash.txt: be explicit about subcommand options\n    @@ Documentation/git-stash.txt: save [-p|--patch] [-k|--[no-]keep-index] [-u|--incl\n      \tmessage.\n      \n     -list [<options>]::\n    -+list [<log options>]::\n    ++list [<log-options>]::\n      \n      \tList the stash entries that you currently have.  Each 'stash entry' is\n      \tlisted with its name (e.g. `stash@{0}` is the latest entry, `stash@{1}` is\n    @@ Documentation/git-stash.txt: stash@{1}: On master: 9cc0589... Add git-stash\n      command to control what is shown and how. See linkgit:git-log[1].\n      \n     -show [<options>] [<stash>]::\n    -+show [<diff options>] [<stash>]::\n    ++show [<diff-options>] [<stash>]::\n      \n      \tShow the changes recorded in the stash entry as a diff between the\n      \tstashed contents and the commit back when the stash entry was first\n 2:  0de324e3bc =  2:  45ed17bfe2 t3905: remove spaces after redirect operators\n 3:  519840b1a2 =  3:  5bda09b4bd t3905: move all commands into test cases\n 4:  4b72d39e01 =  4:  57c21e2461 t3905: remove nested git in command substitution\n 5:  7fe27ab620 =  5:  2530883b6c t3905: replace test -s with test_file_not_empty\n 6:  4a5dd83ff4 =  6:  80194bcfa5 t3905: use test_cmp() to check file contents\n 7:  b5f22de3fc =  7:  2f03d38b36 stash: declare ref_stash as an array\n 8:  c2375d1fc6 !  8:  88d4791259 stash show: teach --include-tracked and --only-untracked\n    @@ Metadata\n     Author: Denton Liu <liu.denton@gmail.com>\n     \n      ## Commit message ##\n    -    stash show: teach --include-tracked and --only-untracked\n    +    stash show: teach --include-untracked and --only-untracked\n     \n         Stash entries can be made with untracked files via\n         `git stash push --include-untracked`. However, because the untracked\n    @@ Commit message\n         stash entry itself, running `git stash show` does not include the\n         untracked files as part of the diff.\n     \n    -    Teach stash the --include-tracked option, which also displays the\n    +    Teach stash the --include-untracked option, which also displays the\n         untracked files in a stash entry from the third parent (if it exists).\n    -    Do this by just concatenating the diff of the third parent against an\n    -    empty tree. One limitation of this is that it would be possible to\n    -    manually craft a stash entry which would present duplicate entries in\n    -    the diff by duplicating a file in the stash and in the third parent.\n    -    This seems like an instance of \"Doctor, it hurts when I do this! So\n    -    don't do that!\" so this can be written off.\n    +    Do this via something like\n    +\n    +            GIT_INDEX_FILE=... git read-tree stash stash^3\n    +\n    +    and diffing the resulting tree object against the stash base.\n    +\n    +    One improvement that this could use for the future is performing the\n    +    action without writing anything to disk as one would expect this to be a\n    +    read-only operation. This can be fixed in the future, however.\n    +\n    +    Another limitation of this is that it would be possible to manually\n    +    craft a stash entry where duplicate untracked files in the stash entry\n    +    will mask tracked files. This seems like an instance of \"Doctor, it\n    +    hurts when I do this! So don't do that!\" so this can be written off.\n     \n         Also, teach stash the --only-untracked option which only shows the\n         untracked files of a stash entry. This is similar to `git show stash^3`\n    @@ Documentation/git-stash.txt: stash@{1}: On master: 9cc0589... Add git-stash\n      The command takes options applicable to the 'git log'\n      command to control what is shown and how. See linkgit:git-log[1].\n      \n    --show [<diff options>] [<stash>]::\n    -+show [-u|--include-untracked|--only-untracked] [<diff options>] [<stash>]::\n    +-show [<diff-options>] [<stash>]::\n    ++show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]::\n      \n      \tShow the changes recorded in the stash entry as a diff between the\n      \tstashed contents and the commit back when the stash entry was first\n    @@ Documentation/git-stash.txt: up with `git clean`.\n      \tThis option is only valid for `pop` and `apply` commands.\n     \n      ## builtin/stash.c ##\n    +@@ builtin/stash.c: static int git_stash_config(const char *var, const char *value, void *cb)\n    + \treturn git_diff_basic_config(var, value, cb);\n    + }\n    + \n    ++static int merge_track_untracked(struct object_id *result, const struct stash_info *info)\n    ++{\n    ++\tint ret = 0;\n    ++\tstruct index_state istate = { NULL };\n    ++\tstruct child_process cp_read_tree = CHILD_PROCESS_INIT;\n    ++\n    ++\tif (!info->has_u) {\n    ++\t\toidcpy(result, &info->w_commit);\n    ++\t\treturn 0;\n    ++\t}\n    ++\n    ++\t/*\n    ++\t * TODO: is there a way of doing this all in-memory without writing\n    ++\t * anything to disk?\n    ++\t */\n    ++\tremove_path(stash_index_path.buf);\n    ++\n    ++\tcp_read_tree.git_cmd = 1;\n    ++\tstrvec_push(&cp_read_tree.args, \"read-tree\");\n    ++\tstrvec_push(&cp_read_tree.args, oid_to_hex(&info->w_commit));\n    ++\tstrvec_push(&cp_read_tree.args, oid_to_hex(&info->u_tree));\n    ++\tstrvec_pushf(&cp_read_tree.env_array, \"GIT_INDEX_FILE=%s\",\n    ++\t\t     stash_index_path.buf);\n    ++\n    ++\tif (run_command(&cp_read_tree)) {\n    ++\t\tret = -1;\n    ++\t\tgoto done;\n    ++\t}\n    ++\n    ++\tif (write_index_as_tree(result, &istate, stash_index_path.buf, 0,\n    ++\t\t\t\tNULL)) {\n    ++\t\tret = -1;\n    ++\t\tgoto done;\n    ++\t}\n    ++\n    ++done:\n    ++\tdiscard_index(&istate);\n    ++\tremove_path(stash_index_path.buf);\n    ++\treturn ret;\n    ++}\n    ++\n    + static int show_stash(int argc, const char **argv, const char *prefix)\n    + {\n    + \tint i;\n     @@ builtin/stash.c: static int show_stash(int argc, const char **argv, const char *prefix)\n      \tstruct rev_info rev;\n      \tstruct strvec stash_args = STRVEC_INIT;\n      \tstruct strvec revision_args = STRVEC_INIT;\n    ++\tstruct object_id *before = NULL;\n    ++\tstruct object_id *after = NULL;\n    ++\tstruct object_id untracked_merged_tree;\n     +\tenum {\n     +\t\tUNTRACKED_NONE,\n     +\t\tUNTRACKED_INCLUDE,\n    @@ builtin/stash.c: static int show_stash(int argc, const char **argv, const char *\n      \trev.diffopt.flags.recursive = 1;\n      \tsetup_diff_pager(&rev.diffopt);\n     -\tdiff_tree_oid(&info.b_commit, &info.w_commit, \"\", &rev.diffopt);\n    -+\tif (show_untracked != UNTRACKED_ONLY)\n    -+\t\tdiff_tree_oid(&info.b_commit, &info.w_commit, \"\", &rev.diffopt);\n    -+\tif (show_untracked != UNTRACKED_NONE && info.has_u)\n    -+\t\tdiff_root_tree_oid(&info.u_tree, \"\", &rev.diffopt);\n    ++\tswitch (show_untracked) {\n    ++\tcase UNTRACKED_NONE:\n    ++\t\tbefore = &info.b_commit;\n    ++\t\tafter = &info.w_commit;\n    ++\t\tbreak;\n    ++\tcase UNTRACKED_ONLY:\n    ++\t\tbefore = NULL;\n    ++\t\tafter = &info.u_tree;\n    ++\t\tbreak;\n    ++\tcase UNTRACKED_INCLUDE:\n    ++\t\tif (merge_track_untracked(&untracked_merged_tree, &info) < 0)\n    ++\t\t\tdie(_(\"unable merge stash index with untracked files index\"));\n    ++\t\tbefore = &info.b_commit;\n    ++\t\tafter = &untracked_merged_tree;\n    ++\t\tbreak;\n    ++\t}\n    ++\tdiff_tree_oid(before, after, \"\", &rev.diffopt);\n      \tlog_tree_diff_flush(&rev);\n      \n      \tfree_stash_info(&info);\n 9:  2c5d5d9dd4 !  9:  ac4019f47e stash show: learn stash.showIncludeUntracked\n    @@ Documentation/config/stash.txt: stash.useBuiltin::\n      \toption will show the stash entry in patch form.  Defaults to false.\n     \n      ## Documentation/git-stash.txt ##\n    -@@ Documentation/git-stash.txt: show [-u|--include-untracked|--only-untracked] [<diff options>] [<stash>]::\n    +@@ Documentation/git-stash.txt: show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]::\n      \tBy default, the command shows the diffstat, but it will accept any\n      \tformat known to 'git diff' (e.g., `git stash show -p stash@{1}`\n      \tto view the second most recent entry in patch form).\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"416478","messageId":"5697f14f1c67abbb529b450c8f2a02f2bc59963f.1612855690.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612855690.git.liu.denton@gmail.com","subject":"[PATCH v2 1/9] git-stash.txt: be explicit about subcommand options","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-09T07:28:47Z","receivedAt":"2021-02-09T07:30:03Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Currently, the options for the `list` and `show` subcommands are just\nlisted as `<options>`. This seems to imply, from a cursory glance at the\nsummary, that they take the stash options listed below. However, reading\nmore carefully, we see that they take log options and diff options\nrespectively.\n\nMake it more obvious that they take log and diff options by explicitly\nstating this in the subcommand summary.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/git-stash.txt | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 31f1beb65b..04e55eb826 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -67,7 +67,7 @@ save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q\n \tInstead, all non-option arguments are concatenated to form the stash\n \tmessage.\n \n-list [<options>]::\n+list [<log-options>]::\n \n \tList the stash entries that you currently have.  Each 'stash entry' is\n \tlisted with its name (e.g. `stash@{0}` is the latest entry, `stash@{1}` is\n@@ -83,7 +83,7 @@ stash@{1}: On master: 9cc0589... Add git-stash\n The command takes options applicable to the 'git log'\n command to control what is shown and how. See linkgit:git-log[1].\n \n-show [<options>] [<stash>]::\n+show [<diff-options>] [<stash>]::\n \n \tShow the changes recorded in the stash entry as a diff between the\n \tstashed contents and the commit back when the stash entry was first\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"416479","messageId":"45ed17bfe2dad4702904a07a4a77a8d219df8379.1612855690.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612855690.git.liu.denton@gmail.com","subject":"[PATCH v2 2/9] t3905: remove spaces after redirect operators","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-09T07:28:48Z","receivedAt":"2021-02-09T07:30:21Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"For shell scripts, the usual convention is for there to be no space\nafter redirection operators, (e.g. `>file`, not `> file`). Remove these\nspaces wherever they appear.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3905-stash-include-untracked.sh | 40 +++++++++++++++---------------\n 1 file changed, 20 insertions(+), 20 deletions(-)\n\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex f075c7f1f3..1d416944b7 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -8,16 +8,16 @@ test_description='Test git stash --include-untracked'\n . ./test-lib.sh\n \n test_expect_success 'stash save --include-untracked some dirty working directory' '\n-\techo 1 > file &&\n+\techo 1 >file &&\n \tgit add file &&\n \ttest_tick &&\n \tgit commit -m initial &&\n-\techo 2 > file &&\n+\techo 2 >file &&\n \tgit add file &&\n-\techo 3 > file &&\n+\techo 3 >file &&\n \ttest_tick &&\n-\techo 1 > file2 &&\n-\techo 1 > HEAD &&\n+\techo 1 >file2 &&\n+\techo 1 >HEAD &&\n \tmkdir untracked &&\n \techo untracked >untracked/untracked &&\n \tgit stash --include-untracked &&\n@@ -25,7 +25,7 @@ test_expect_success 'stash save --include-untracked some dirty working directory\n \tgit diff-index --cached --quiet HEAD\n '\n \n-cat > expect <<EOF\n+cat >expect <<EOF\n ?? actual\n ?? expect\n EOF\n@@ -37,7 +37,7 @@ test_expect_success 'stash save --include-untracked cleaned the untracked files'\n \n tracked=$(git rev-parse --short $(echo 1 | git hash-object --stdin))\n untracked=$(git rev-parse --short $(echo untracked | git hash-object --stdin))\n-cat > expect.diff <<EOF\n+cat >expect.diff <<EOF\n diff --git a/HEAD b/HEAD\n new file mode 100644\n index 0000000..$tracked\n@@ -60,7 +60,7 @@ index 0000000..$untracked\n @@ -0,0 +1 @@\n +untracked\n EOF\n-cat > expect.lstree <<EOF\n+cat >expect.lstree <<EOF\n HEAD\n file2\n untracked\n@@ -85,7 +85,7 @@ test_expect_success 'stash save --patch --all fails' '\n \n git clean --force --quiet\n \n-cat > expect <<EOF\n+cat >expect <<EOF\n  M file\n ?? HEAD\n ?? actual\n@@ -105,14 +105,14 @@ test_expect_success 'stash pop after save --include-untracked leaves files untra\n git clean --force --quiet -d\n \n test_expect_success 'stash save -u dirty index' '\n-\techo 4 > file3 &&\n+\techo 4 >file3 &&\n \tgit add file3 &&\n \ttest_tick &&\n \tgit stash -u\n '\n \n blob=$(git rev-parse --short $(echo 4 | git hash-object --stdin))\n-cat > expect <<EOF\n+cat >expect <<EOF\n diff --git a/file3 b/file3\n new file mode 100644\n index 0000000..$blob\n@@ -128,12 +128,12 @@ test_expect_success 'stash save --include-untracked dirty index got stashed' '\n \ttest_cmp expect actual\n '\n \n-git reset > /dev/null\n+git reset >/dev/null\n \n # Must direct output somewhere where it won't be considered an untracked file\n test_expect_success 'stash save --include-untracked -q is quiet' '\n-\techo 1 > file5 &&\n-\tgit stash save --include-untracked --quiet > .git/stash-output.out 2>&1 &&\n+\techo 1 >file5 &&\n+\tgit stash save --include-untracked --quiet >.git/stash-output.out 2>&1 &&\n \ttest_line_count = 0 .git/stash-output.out &&\n \trm -f .git/stash-output.out\n '\n@@ -141,7 +141,7 @@ test_expect_success 'stash save --include-untracked -q is quiet' '\n test_expect_success 'stash save --include-untracked removed files' '\n \trm -f file &&\n \tgit stash save --include-untracked &&\n-\techo 1 > expect &&\n+\techo 1 >expect &&\n \ttest_cmp expect file\n '\n \n@@ -152,14 +152,14 @@ test_expect_success 'stash save --include-untracked removed files got stashed' '\n \ttest_path_is_missing file\n '\n \n-cat > .gitignore <<EOF\n+cat >.gitignore <<EOF\n .gitignore\n ignored\n ignored.d/\n EOF\n \n test_expect_success 'stash save --include-untracked respects .gitignore' '\n-\techo ignored > ignored &&\n+\techo ignored >ignored &&\n \tmkdir ignored.d &&\n \techo ignored >ignored.d/untracked &&\n \tgit stash -u &&\n@@ -169,7 +169,7 @@ test_expect_success 'stash save --include-untracked respects .gitignore' '\n '\n \n test_expect_success 'stash save -u can stash with only untracked files different' '\n-\techo 4 > file4 &&\n+\techo 4 >file4 &&\n \tgit stash -u &&\n \ttest_path_is_missing file4\n '\n@@ -214,7 +214,7 @@ test_expect_success 'stash push with $IFS character' '\n \ttest_path_is_file bar\n '\n \n-cat > .gitignore <<EOF\n+cat >.gitignore <<EOF\n ignored\n ignored.d/*\n EOF\n@@ -224,7 +224,7 @@ test_expect_success 'stash previously ignored file' '\n \tgit add .gitignore &&\n \tgit commit -m \"Add .gitignore\" &&\n \t>ignored.d/foo &&\n-\techo \"!ignored.d/foo\" >> .gitignore &&\n+\techo \"!ignored.d/foo\" >>.gitignore &&\n \tgit stash save --include-untracked &&\n \ttest_path_is_missing ignored.d/foo &&\n \tgit stash pop &&\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"416480","messageId":"5bda09b4bd55168f6e69f20026d0112b1a43f45f.1612855690.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612855690.git.liu.denton@gmail.com","subject":"[PATCH v2 3/9] t3905: move all commands into test cases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-09T07:28:49Z","receivedAt":"2021-02-09T07:30:27Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"In order to modernize the tests, move commands that currently run\noutside of test cases into a test case. Where possible, clean up files\nthat are produced using test_when_finished() but in the case where files\npersist over multiple test cases, create a new test case to perform\ncleanup.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3905-stash-include-untracked.sh | 147 +++++++++++++++--------------\n 1 file changed, 75 insertions(+), 72 deletions(-)\n\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex 1d416944b7..892a2c8057 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -25,48 +25,48 @@ test_expect_success 'stash save --include-untracked some dirty working directory\n \tgit diff-index --cached --quiet HEAD\n '\n \n-cat >expect <<EOF\n-?? actual\n-?? expect\n-EOF\n-\n test_expect_success 'stash save --include-untracked cleaned the untracked files' '\n+\tcat >expect <<-EOF &&\n+\t?? actual\n+\t?? expect\n+\tEOF\n+\n \tgit status --porcelain >actual &&\n \ttest_cmp expect actual\n '\n \n-tracked=$(git rev-parse --short $(echo 1 | git hash-object --stdin))\n-untracked=$(git rev-parse --short $(echo untracked | git hash-object --stdin))\n-cat >expect.diff <<EOF\n-diff --git a/HEAD b/HEAD\n-new file mode 100644\n-index 0000000..$tracked\n---- /dev/null\n-+++ b/HEAD\n-@@ -0,0 +1 @@\n-+1\n-diff --git a/file2 b/file2\n-new file mode 100644\n-index 0000000..$tracked\n---- /dev/null\n-+++ b/file2\n-@@ -0,0 +1 @@\n-+1\n-diff --git a/untracked/untracked b/untracked/untracked\n-new file mode 100644\n-index 0000000..$untracked\n---- /dev/null\n-+++ b/untracked/untracked\n-@@ -0,0 +1 @@\n-+untracked\n-EOF\n-cat >expect.lstree <<EOF\n-HEAD\n-file2\n-untracked\n-EOF\n-\n test_expect_success 'stash save --include-untracked stashed the untracked files' '\n+\ttracked=$(git rev-parse --short $(echo 1 | git hash-object --stdin)) &&\n+\tuntracked=$(git rev-parse --short $(echo untracked | git hash-object --stdin)) &&\n+\tcat >expect.diff <<-EOF &&\n+\tdiff --git a/HEAD b/HEAD\n+\tnew file mode 100644\n+\tindex 0000000..$tracked\n+\t--- /dev/null\n+\t+++ b/HEAD\n+\t@@ -0,0 +1 @@\n+\t+1\n+\tdiff --git a/file2 b/file2\n+\tnew file mode 100644\n+\tindex 0000000..$tracked\n+\t--- /dev/null\n+\t+++ b/file2\n+\t@@ -0,0 +1 @@\n+\t+1\n+\tdiff --git a/untracked/untracked b/untracked/untracked\n+\tnew file mode 100644\n+\tindex 0000000..$untracked\n+\t--- /dev/null\n+\t+++ b/untracked/untracked\n+\t@@ -0,0 +1 @@\n+\t+untracked\n+\tEOF\n+\tcat >expect.lstree <<-EOF &&\n+\tHEAD\n+\tfile2\n+\tuntracked\n+\tEOF\n+\n \ttest_path_is_missing file2 &&\n \ttest_path_is_missing untracked &&\n \ttest_path_is_missing HEAD &&\n@@ -83,18 +83,21 @@ test_expect_success 'stash save --patch --all fails' '\n \ttest_must_fail git stash --patch --all\n '\n \n-git clean --force --quiet\n+test_expect_success 'clean up untracked/untracked file to prepare for next tests' '\n+\tgit clean --force --quiet\n \n-cat >expect <<EOF\n- M file\n-?? HEAD\n-?? actual\n-?? expect\n-?? file2\n-?? untracked/\n-EOF\n+'\n \n test_expect_success 'stash pop after save --include-untracked leaves files untracked again' '\n+\tcat >expect <<-EOF &&\n+\t M file\n+\t?? HEAD\n+\t?? actual\n+\t?? expect\n+\t?? file2\n+\t?? untracked/\n+\tEOF\n+\n \tgit stash pop &&\n \tgit status --porcelain >actual &&\n \ttest_cmp expect actual &&\n@@ -102,7 +105,9 @@ test_expect_success 'stash pop after save --include-untracked leaves files untra\n \ttest untracked = \"$(cat untracked/untracked)\"\n '\n \n-git clean --force --quiet -d\n+test_expect_success 'clean up untracked/ directory to prepare for next tests' '\n+\tgit clean --force --quiet -d\n+'\n \n test_expect_success 'stash save -u dirty index' '\n \techo 4 >file3 &&\n@@ -111,25 +116,24 @@ test_expect_success 'stash save -u dirty index' '\n \tgit stash -u\n '\n \n-blob=$(git rev-parse --short $(echo 4 | git hash-object --stdin))\n-cat >expect <<EOF\n-diff --git a/file3 b/file3\n-new file mode 100644\n-index 0000000..$blob\n---- /dev/null\n-+++ b/file3\n-@@ -0,0 +1 @@\n-+4\n-EOF\n-\n test_expect_success 'stash save --include-untracked dirty index got stashed' '\n+\tblob=$(git rev-parse --short $(echo 4 | git hash-object --stdin)) &&\n+\tcat >expect <<-EOF &&\n+\tdiff --git a/file3 b/file3\n+\tnew file mode 100644\n+\tindex 0000000..$blob\n+\t--- /dev/null\n+\t+++ b/file3\n+\t@@ -0,0 +1 @@\n+\t+4\n+\tEOF\n+\n \tgit stash pop --index &&\n+\ttest_when_finished \"git reset\" &&\n \tgit diff --cached >actual &&\n \ttest_cmp expect actual\n '\n \n-git reset >/dev/null\n-\n # Must direct output somewhere where it won't be considered an untracked file\n test_expect_success 'stash save --include-untracked -q is quiet' '\n \techo 1 >file5 &&\n@@ -142,23 +146,22 @@ test_expect_success 'stash save --include-untracked removed files' '\n \trm -f file &&\n \tgit stash save --include-untracked &&\n \techo 1 >expect &&\n+\ttest_when_finished \"rm -f expect\" &&\n \ttest_cmp expect file\n '\n \n-rm -f expect\n-\n test_expect_success 'stash save --include-untracked removed files got stashed' '\n \tgit stash pop &&\n \ttest_path_is_missing file\n '\n \n-cat >.gitignore <<EOF\n-.gitignore\n-ignored\n-ignored.d/\n-EOF\n-\n test_expect_success 'stash save --include-untracked respects .gitignore' '\n+\tcat >.gitignore <<-EOF &&\n+\t.gitignore\n+\tignored\n+\tignored.d/\n+\tEOF\n+\n \techo ignored >ignored &&\n \tmkdir ignored.d &&\n \techo ignored >ignored.d/untracked &&\n@@ -214,12 +217,12 @@ test_expect_success 'stash push with $IFS character' '\n \ttest_path_is_file bar\n '\n \n-cat >.gitignore <<EOF\n-ignored\n-ignored.d/*\n-EOF\n-\n test_expect_success 'stash previously ignored file' '\n+\tcat >.gitignore <<-EOF &&\n+\tignored\n+\tignored.d/*\n+\tEOF\n+\n \tgit reset HEAD &&\n \tgit add .gitignore &&\n \tgit commit -m \"Add .gitignore\" &&\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"416481","messageId":"57c21e2461eea8a37456d211f38fb668278cb2ed.1612855690.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612855690.git.liu.denton@gmail.com","subject":"[PATCH v2 4/9] t3905: remove nested git in command substitution","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-09T07:28:50Z","receivedAt":"2021-02-09T07:30:30Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"If a git command in a nested command substitution fails, it will be\nsilently ignored since only the return code of the outer command\nsubstitutions is reported. Factor out nested command substitutions so\nthat the error codes of those commands are reported.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3905-stash-include-untracked.sh | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex 892a2c8057..f008e5d945 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -36,8 +36,10 @@ test_expect_success 'stash save --include-untracked cleaned the untracked files'\n '\n \n test_expect_success 'stash save --include-untracked stashed the untracked files' '\n-\ttracked=$(git rev-parse --short $(echo 1 | git hash-object --stdin)) &&\n-\tuntracked=$(git rev-parse --short $(echo untracked | git hash-object --stdin)) &&\n+\tone_blob=$(echo 1 | git hash-object --stdin) &&\n+\ttracked=$(git rev-parse --short \"$one_blob\") &&\n+\tuntracked_blob=$(echo untracked | git hash-object --stdin) &&\n+\tuntracked=$(git rev-parse --short \"$untracked_blob\") &&\n \tcat >expect.diff <<-EOF &&\n \tdiff --git a/HEAD b/HEAD\n \tnew file mode 100644\n@@ -117,7 +119,8 @@ test_expect_success 'stash save -u dirty index' '\n '\n \n test_expect_success 'stash save --include-untracked dirty index got stashed' '\n-\tblob=$(git rev-parse --short $(echo 4 | git hash-object --stdin)) &&\n+\tfour_blob=$(echo 4 | git hash-object --stdin) &&\n+\tblob=$(git rev-parse --short \"$four_blob\") &&\n \tcat >expect <<-EOF &&\n \tdiff --git a/file3 b/file3\n \tnew file mode 100644\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"416482","messageId":"2530883b6ca664d22ec54c4dd4fd3137c52bcd6c.1612855690.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612855690.git.liu.denton@gmail.com","subject":"[PATCH v2 5/9] t3905: replace test -s with test_file_not_empty","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-09T07:28:51Z","receivedAt":"2021-02-09T07:31:03Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"In order to modernize the test script, replace `test -s` with\ntest_file_not_empty(), which provides better diagnostic output in the\ncase of failure.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3905-stash-include-untracked.sh | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex f008e5d945..c87ac24042 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -169,9 +169,9 @@ test_expect_success 'stash save --include-untracked respects .gitignore' '\n \tmkdir ignored.d &&\n \techo ignored >ignored.d/untracked &&\n \tgit stash -u &&\n-\ttest -s ignored &&\n-\ttest -s ignored.d/untracked &&\n-\ttest -s .gitignore\n+\ttest_file_not_empty ignored &&\n+\ttest_file_not_empty ignored.d/untracked &&\n+\ttest_file_not_empty .gitignore\n '\n \n test_expect_success 'stash save -u can stash with only untracked files different' '\n@@ -189,9 +189,9 @@ test_expect_success 'stash save --all does not respect .gitignore' '\n \n test_expect_success 'stash save --all is stash poppable' '\n \tgit stash pop &&\n-\ttest -s ignored &&\n-\ttest -s ignored.d/untracked &&\n-\ttest -s .gitignore\n+\ttest_file_not_empty ignored &&\n+\ttest_file_not_empty ignored.d/untracked &&\n+\ttest_file_not_empty .gitignore\n '\n \n test_expect_success 'stash push --include-untracked with pathspec' '\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"416483","messageId":"80194bcfa5bc250e64a0d197790dd8eac93d395d.1612855690.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612855690.git.liu.denton@gmail.com","subject":"[PATCH v2 6/9] t3905: use test_cmp() to check file contents","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-09T07:28:52Z","receivedAt":"2021-02-09T07:31:52Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Modernize the script by doing file content comparisons using test_cmp()\ninstead of `test x = \"$(cat file)\"`.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3905-stash-include-untracked.sh | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex c87ac24042..b26a97aef4 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -103,8 +103,10 @@ test_expect_success 'stash pop after save --include-untracked leaves files untra\n \tgit stash pop &&\n \tgit status --porcelain >actual &&\n \ttest_cmp expect actual &&\n-\ttest \"1\" = \"$(cat file2)\" &&\n-\ttest untracked = \"$(cat untracked/untracked)\"\n+\techo 1 >expect_file2 &&\n+\ttest_cmp expect_file2 file2 &&\n+\techo untracked >untracked_expect &&\n+\ttest_cmp untracked_expect untracked/untracked\n '\n \n test_expect_success 'clean up untracked/ directory to prepare for next tests' '\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"416484","messageId":"88d47912595b5650fbca595a6dd5b7b943a93301.1612855690.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612855690.git.liu.denton@gmail.com","subject":"[PATCH v2 8/9] stash show: teach --include-untracked and --only-untracked","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-09T07:28:54Z","receivedAt":"2021-02-09T07:31:52Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Stash entries can be made with untracked files via\n`git stash push --include-untracked`. However, because the untracked\nfiles are stored in the third parent of the stash entry and not the\nstash entry itself, running `git stash show` does not include the\nuntracked files as part of the diff.\n\nTeach stash the --include-untracked option, which also displays the\nuntracked files in a stash entry from the third parent (if it exists).\nDo this via something like\n\n\tGIT_INDEX_FILE=... git read-tree stash stash^3\n\nand diffing the resulting tree object against the stash base.\n\nOne improvement that this could use for the future is performing the\naction without writing anything to disk as one would expect this to be a\nread-only operation. This can be fixed in the future, however.\n\nAnother limitation of this is that it would be possible to manually\ncraft a stash entry where duplicate untracked files in the stash entry\nwill mask tracked files. This seems like an instance of \"Doctor, it\nhurts when I do this! So don't do that!\" so this can be written off.\n\nAlso, teach stash the --only-untracked option which only shows the\nuntracked files of a stash entry. This is similar to `git show stash^3`\nbut it is nice to provide a convenient abstraction for it so that users\ndo not have to think about the underlying implementation.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/git-stash.txt            | 16 +++--\n builtin/stash.c                        | 77 ++++++++++++++++++++++-\n contrib/completion/git-completion.bash |  2 +-\n t/t3905-stash-include-untracked.sh     | 84 ++++++++++++++++++++++++++\n 4 files changed, 173 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 04e55eb826..9d4b9f0b5c 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -83,7 +83,7 @@ stash@{1}: On master: 9cc0589... Add git-stash\n The command takes options applicable to the 'git log'\n command to control what is shown and how. See linkgit:git-log[1].\n \n-show [<diff-options>] [<stash>]::\n+show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]::\n \n \tShow the changes recorded in the stash entry as a diff between the\n \tstashed contents and the commit back when the stash entry was first\n@@ -160,10 +160,18 @@ up with `git clean`.\n \n -u::\n --include-untracked::\n-\tThis option is only valid for `push` and `save` commands.\n+--no-include-untracked::\n+\tWhen used with the `push` and `save` commands,\n+\tall untracked files are also stashed and then cleaned up with\n+\t`git clean`.\n +\n-All untracked files are also stashed and then cleaned up with\n-`git clean`.\n+When used with the `show` command, show the untracked files in the stash\n+entry as part of the diff.\n+\n+--only-untracked::\n+\tThis option is only valid for the `show` command.\n++\n+Show only the untracked files in the stash entry as part of the diff.\n \n --index::\n \tThis option is only valid for `pop` and `apply` commands.\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 6f2b58f6ab..f7220fad56 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -787,6 +787,47 @@ static int git_stash_config(const char *var, const char *value, void *cb)\n \treturn git_diff_basic_config(var, value, cb);\n }\n \n+static int merge_track_untracked(struct object_id *result, const struct stash_info *info)\n+{\n+\tint ret = 0;\n+\tstruct index_state istate = { NULL };\n+\tstruct child_process cp_read_tree = CHILD_PROCESS_INIT;\n+\n+\tif (!info->has_u) {\n+\t\toidcpy(result, &info->w_commit);\n+\t\treturn 0;\n+\t}\n+\n+\t/*\n+\t * TODO: is there a way of doing this all in-memory without writing\n+\t * anything to disk?\n+\t */\n+\tremove_path(stash_index_path.buf);\n+\n+\tcp_read_tree.git_cmd = 1;\n+\tstrvec_push(&cp_read_tree.args, \"read-tree\");\n+\tstrvec_push(&cp_read_tree.args, oid_to_hex(&info->w_commit));\n+\tstrvec_push(&cp_read_tree.args, oid_to_hex(&info->u_tree));\n+\tstrvec_pushf(&cp_read_tree.env_array, \"GIT_INDEX_FILE=%s\",\n+\t\t     stash_index_path.buf);\n+\n+\tif (run_command(&cp_read_tree)) {\n+\t\tret = -1;\n+\t\tgoto done;\n+\t}\n+\n+\tif (write_index_as_tree(result, &istate, stash_index_path.buf, 0,\n+\t\t\t\tNULL)) {\n+\t\tret = -1;\n+\t\tgoto done;\n+\t}\n+\n+done:\n+\tdiscard_index(&istate);\n+\tremove_path(stash_index_path.buf);\n+\treturn ret;\n+}\n+\n static int show_stash(int argc, const char **argv, const char *prefix)\n {\n \tint i;\n@@ -795,7 +836,21 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \tstruct rev_info rev;\n \tstruct strvec stash_args = STRVEC_INIT;\n \tstruct strvec revision_args = STRVEC_INIT;\n+\tstruct object_id *before = NULL;\n+\tstruct object_id *after = NULL;\n+\tstruct object_id untracked_merged_tree;\n+\tenum {\n+\t\tUNTRACKED_NONE,\n+\t\tUNTRACKED_INCLUDE,\n+\t\tUNTRACKED_ONLY\n+\t} show_untracked = UNTRACKED_NONE;\n \tstruct option options[] = {\n+\t\tOPT_SET_INT('u', \"include-untracked\", &show_untracked,\n+\t\t\t    N_(\"include untracked files in the stash\"),\n+\t\t\t    UNTRACKED_INCLUDE),\n+\t\tOPT_SET_INT_F(0, \"only-untracked\", &show_untracked,\n+\t\t\t      N_(\"only show untracked files in the stash\"),\n+\t\t\t      UNTRACKED_ONLY, PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \n@@ -803,6 +858,10 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \tgit_config(git_diff_ui_config, NULL);\n \tinit_revisions(&rev, prefix);\n \n+\targc = parse_options(argc, argv, prefix, options, git_stash_show_usage,\n+\t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n \tstrvec_push(&revision_args, argv[0]);\n \tfor (i = 1; i < argc; i++) {\n \t\tif (argv[i][0] != '-')\n@@ -845,7 +904,23 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \n \trev.diffopt.flags.recursive = 1;\n \tsetup_diff_pager(&rev.diffopt);\n-\tdiff_tree_oid(&info.b_commit, &info.w_commit, \"\", &rev.diffopt);\n+\tswitch (show_untracked) {\n+\tcase UNTRACKED_NONE:\n+\t\tbefore = &info.b_commit;\n+\t\tafter = &info.w_commit;\n+\t\tbreak;\n+\tcase UNTRACKED_ONLY:\n+\t\tbefore = NULL;\n+\t\tafter = &info.u_tree;\n+\t\tbreak;\n+\tcase UNTRACKED_INCLUDE:\n+\t\tif (merge_track_untracked(&untracked_merged_tree, &info) < 0)\n+\t\t\tdie(_(\"unable merge stash index with untracked files index\"));\n+\t\tbefore = &info.b_commit;\n+\t\tafter = &untracked_merged_tree;\n+\t\tbreak;\n+\t}\n+\tdiff_tree_oid(before, after, \"\", &rev.diffopt);\n \tlog_tree_diff_flush(&rev);\n \n \tfree_stash_info(&info);\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 4b1f4264a6..64ef6ffa21 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -3051,7 +3051,7 @@ _git_stash ()\n \t\t\t__gitcomp \"--name-status --oneline --patch-with-stat\"\n \t\t\t;;\n \t\tshow,--*)\n-\t\t\t__gitcomp \"$__git_diff_common_options\"\n+\t\t\t__gitcomp \"--include-untracked --only-untracked $__git_diff_common_options\"\n \t\t\t;;\n \t\tbranch,--*)\n \t\t\t;;\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex b26a97aef4..978bc97baf 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -297,4 +297,88 @@ test_expect_success 'stash -u with globs' '\n \ttest_path_is_missing untracked.txt\n '\n \n+test_expect_success 'stash show --include-untracked shows untracked files' '\n+\tgit reset --hard &&\n+\tgit clean -xf &&\n+\t>untracked &&\n+\t>tracked &&\n+\tgit add tracked &&\n+\tgit stash -u &&\n+\n+\tcat >expect <<-EOF &&\n+\t tracked   | 0\n+\t untracked | 0\n+\t 2 files changed, 0 insertions(+), 0 deletions(-)\n+\tEOF\n+\tgit stash show --include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show -u >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --no-include-untracked --include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --only-untracked --include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tcat >expect <<-EOF &&\n+\tdiff --git a/tracked b/tracked\n+\tnew file mode 100644\n+\tindex 0000000..e69de29\n+\tdiff --git a/untracked b/untracked\n+\tnew file mode 100644\n+\tindex 0000000..e69de29\n+\tEOF\n+\tgit stash show -p --include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --include-untracked -p >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stash show --only-untracked only shows untracked files' '\n+\tgit reset --hard &&\n+\tgit clean -xf &&\n+\t>untracked &&\n+\t>tracked &&\n+\tgit add tracked &&\n+\tgit stash -u &&\n+\n+\tcat >expect <<-EOF &&\n+\t untracked | 0\n+\t 1 file changed, 0 insertions(+), 0 deletions(-)\n+\tEOF\n+\tgit stash show --only-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --no-include-untracked --only-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --include-untracked --only-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tcat >expect <<-EOF &&\n+\tdiff --git a/untracked b/untracked\n+\tnew file mode 100644\n+\tindex 0000000..e69de29\n+\tEOF\n+\tgit stash show -p --only-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --only-untracked -p >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stash show --no-include-untracked cancels --{include,show}-untracked' '\n+\tgit reset --hard &&\n+\tgit clean -xf &&\n+\t>untracked &&\n+\t>tracked &&\n+\tgit add tracked &&\n+\tgit stash -u &&\n+\n+\tcat >expect <<-EOF &&\n+\t tracked | 0\n+\t 1 file changed, 0 insertions(+), 0 deletions(-)\n+\tEOF\n+\tgit stash show --only-untracked --no-include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --include-untracked --no-include-untracked >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"416485","messageId":"2f03d38b36665fe258444ce58f1e138be7b97e13.1612855690.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612855690.git.liu.denton@gmail.com","subject":"[PATCH v2 7/9] stash: declare ref_stash as an array","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-09T07:28:53Z","receivedAt":"2021-02-09T07:31:59Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Save sizeof(const char *) bytes by declaring ref_stash as an array\ninstead of having a redundant pointer to an array.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/stash.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 9bc85f91cd..6f2b58f6ab 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -87,7 +87,7 @@ static const char * const git_stash_save_usage[] = {\n \tNULL\n };\n \n-static const char *ref_stash = \"refs/stash\";\n+static const char ref_stash[] = \"refs/stash\";\n static struct strbuf stash_index_path = STRBUF_INIT;\n \n /*\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"416486","messageId":"ac4019f47e389b6c9d02f49734361d52cc640a05.1612855690.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612855690.git.liu.denton@gmail.com","subject":"[PATCH v2 9/9] stash show: learn stash.showIncludeUntracked","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-09T07:28:55Z","receivedAt":"2021-02-09T07:32:14Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"The previous commit teaches `git stash show --include-untracked`. It\nmay be desirable for a user to be able to always enable the\n--include-untracked behavior. Teach the stash.showIncludeUntracked\nconfig option which allows users to do this in a similar manner to\nstash.showPatch.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/config/stash.txt     | 5 +++++\n Documentation/git-stash.txt        | 4 ++--\n builtin/stash.c                    | 8 ++++++++\n t/t3905-stash-include-untracked.sh | 2 ++\n 4 files changed, 17 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/config/stash.txt b/Documentation/config/stash.txt\nindex 00eb35434e..413f907cba 100644\n--- a/Documentation/config/stash.txt\n+++ b/Documentation/config/stash.txt\n@@ -5,6 +5,11 @@ stash.useBuiltin::\n \tis always used. Setting this will emit a warning, to alert any\n \tremaining users that setting this now does nothing.\n \n+stash.showIncludeUntracked::\n+\tIf this is set to true, the `git stash show` command without an\n+\toption will show the untracked files of a stash entry.  Defaults to\n+\tfalse. See description of 'show' command in linkgit:git-stash[1].\n+\n stash.showPatch::\n \tIf this is set to true, the `git stash show` command without an\n \toption will show the stash entry in patch form.  Defaults to false.\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 9d4b9f0b5c..d004e9abdb 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -91,8 +91,8 @@ show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]::\n \tBy default, the command shows the diffstat, but it will accept any\n \tformat known to 'git diff' (e.g., `git stash show -p stash@{1}`\n \tto view the second most recent entry in patch form).\n-\tYou can use stash.showStat and/or stash.showPatch config variables\n-\tto change the default behavior.\n+\tYou can use stash.showIncludeUntracked, stash.showStat, and\n+\tstash.showPatch config variables to change the default behavior.\n \n pop [--index] [-q|--quiet] [<stash>]::\n \ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex f7220fad56..53428e9e64 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -768,6 +768,7 @@ static int list_stash(int argc, const char **argv, const char *prefix)\n \n static int show_stat = 1;\n static int show_patch;\n+static int show_include_untracked;\n static int use_legacy_stash;\n \n static int git_stash_config(const char *var, const char *value, void *cb)\n@@ -780,6 +781,10 @@ static int git_stash_config(const char *var, const char *value, void *cb)\n \t\tshow_patch = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"stash.showincludeuntracked\")) {\n+\t\tshow_include_untracked = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(var, \"stash.usebuiltin\")) {\n \t\tuse_legacy_stash = !git_config_bool(var, value);\n \t\treturn 0;\n@@ -886,6 +891,9 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \t\tif (show_patch)\n \t\t\trev.diffopt.output_format |= DIFF_FORMAT_PATCH;\n \n+\t\tif (show_include_untracked)\n+\t\t\tshow_untracked = UNTRACKED_INCLUDE;\n+\n \t\tif (!show_stat && !show_patch) {\n \t\t\tfree_stash_info(&info);\n \t\t\treturn 0;\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex 978bc97baf..8bcd4c5ca8 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -318,6 +318,8 @@ test_expect_success 'stash show --include-untracked shows untracked files' '\n \ttest_cmp expect actual &&\n \tgit stash show --only-untracked --include-untracked >actual &&\n \ttest_cmp expect actual &&\n+\tgit -c stash.showIncludeUntracked=true stash show >actual &&\n+\ttest_cmp expect actual &&\n \n \tcat >expect <<-EOF &&\n \tdiff --git a/tracked b/tracked\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"416604","messageId":"xmqqv9b04cil.fsf@gitster.c.googlers.com","threadId":"55091","inReplyTo":"5697f14f1c67abbb529b450c8f2a02f2bc59963f.1612855690.git.liu.denton@gmail.com","subject":"Re: [PATCH v2 1/9] git-stash.txt: be explicit about subcommand options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-10T07:17:54Z","receivedAt":"2021-02-10T07:19:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> Currently, the options for the `list` and `show` subcommands are just\n> listed as `<options>`. This seems to imply, from a cursory glance at the\n> summary, that they take the stash options listed below. However, reading\n> more carefully, we see that they take log options and diff options\n> respectively.\n>\n> Make it more obvious that they take log and diff options by explicitly\n> stating this in the subcommand summary.\n\nMakes sense.\n\n> diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\n> index 31f1beb65b..04e55eb826 100644\n> --- a/Documentation/git-stash.txt\n> +++ b/Documentation/git-stash.txt\n> @@ -67,7 +67,7 @@ save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q\n>  \tInstead, all non-option arguments are concatenated to form the stash\n>  \tmessage.\n>  \n> -list [<options>]::\n> +list [<log-options>]::\n>  \n>  \tList the stash entries that you currently have.  Each 'stash entry' is\n>  \tlisted with its name (e.g. `stash@{0}` is the latest entry, `stash@{1}` is\n> @@ -83,7 +83,7 @@ stash@{1}: On master: 9cc0589... Add git-stash\n>  The command takes options applicable to the 'git log'\n>  command to control what is shown and how. See linkgit:git-log[1].\n>  \n> -show [<options>] [<stash>]::\n> +show [<diff-options>] [<stash>]::\n>  \n>  \tShow the changes recorded in the stash entry as a diff between the\n>  \tstashed contents and the commit back when the stash entry was first\n\nThis makes me wonder if we should also update the SYNOPSIS that\ntalks about the \"list\" and \"show\" operations taking a generic\n\"<options>\", though.\n"},{"id":"416605","messageId":"xmqqr1lo4cha.fsf@gitster.c.googlers.com","threadId":"55091","inReplyTo":"45ed17bfe2dad4702904a07a4a77a8d219df8379.1612855690.git.liu.denton@gmail.com","subject":"Re: [PATCH v2 2/9] t3905: remove spaces after redirect operators","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-10T07:18:41Z","receivedAt":"2021-02-10T07:19:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> For shell scripts, the usual convention is for there to be no space\n> after redirection operators, (e.g. `>file`, not `> file`). Remove these\n> spaces wherever they appear.\n\nOK.\n"},{"id":"416606","messageId":"xmqqk0rg4c6y.fsf@gitster.c.googlers.com","threadId":"55091","inReplyTo":"57c21e2461eea8a37456d211f38fb668278cb2ed.1612855690.git.liu.denton@gmail.com","subject":"Re: [PATCH v2 4/9] t3905: remove nested git in command substitution","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-10T07:24:53Z","receivedAt":"2021-02-10T07:27:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> If a git command in a nested command substitution fails, it will be\n> silently ignored since only the return code of the outer command\n> substitutions is reported. Factor out nested command substitutions so\n> that the error codes of those commands are reported.\n\nLocally this makes sense.  We may want to clean it up further by\nusing existing blob objects, instead of using hash-objects, but that\nis for some other day.\n"},{"id":"416607","messageId":"xmqqft244c1j.fsf@gitster.c.googlers.com","threadId":"55091","inReplyTo":"2f03d38b36665fe258444ce58f1e138be7b97e13.1612855690.git.liu.denton@gmail.com","subject":"Re: [PATCH v2 7/9] stash: declare ref_stash as an array","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-10T07:28:08Z","receivedAt":"2021-02-10T07:34:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> Save sizeof(const char *) bytes by declaring ref_stash as an array\n> instead of having a redundant pointer to an array.\n>\n> Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> ---\n>  builtin/stash.c | 2 +-\n>  1 file changed, 1 insertion(+), 1 deletion(-)\n\nOK, up to this point there is nothing new that can be controversial\nin design.  I suspect that we may want to fast-track these early\nclean-up steps as a separate topic rather quickly while the new\nfeature(s) are worked out.\n\nThanks.\n"},{"id":"416608","messageId":"xmqqa6sc4avx.fsf@gitster.c.googlers.com","threadId":"55091","inReplyTo":"88d47912595b5650fbca595a6dd5b7b943a93301.1612855690.git.liu.denton@gmail.com","subject":"Re: [PATCH v2 8/9] stash show: teach --include-untracked and --only-untracked","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-10T07:53:06Z","receivedAt":"2021-02-10T07:54:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> Stash entries can be made with untracked files via\n> `git stash push --include-untracked`. However, because the untracked\n> files are stored in the third parent of the stash entry and not the\n> stash entry itself, running `git stash show` does not include the\n> untracked files as part of the diff.\n\n> Teach stash the --include-untracked option, which also displays the\n> untracked files in a stash entry from the third parent (if it exists).\n\nA few points:\n\n - \"Teach stash the --include-untracked option\"?  (some part of)\n   \"stash\" knows --include-untracked already.  Perhaps \"Teach 'stash\n   show' the '--include-untracked' option\"?\n\n - \"which also displays\"?  Let's spell it out that untracked paths\n   are shown in addition to what.  \"With this option, untracked\n   paths recorded in the third-parent (if exists) are shown, in\n   addition to the paths whose modifications between the stash base\n   and the working tree are stashed\", or something like that,\n   perhaps?\n\n> Do this via something like\n>\n> \tGIT_INDEX_FILE=... git read-tree stash stash^3\n>\n> and diffing the resulting tree object against the stash base.\n\nThat explains the implementation, but does not make it clear what\nthe implementation wants to achieve.  So we read the tree from stash\n(i.e. working tree) into a temporary index, and then overlay the\ntree of stash^3 (i.e. untracked) on top---which means the resulting\n\"index\" has the state of the working tree plus the untracked cruft\nin it.  And comparing that with \"stash base\" (by the way is that a\nterm well understood?  I borrowed it for the above review comment,\nwhich shows that there certainly is need for such a term) would show\nthe diff between the \"HEAD\" and the state that would have result if\nyou were to do an \"git add .\" in the working tree.  OK.\n\n> One improvement that this could use for the future is performing the\n> action without writing anything to disk as one would expect this to be a\n> read-only operation. This can be fixed in the future, however.\n\nIs it so difficult that we have to delay the fix for \"the future\"?\nAfter reading two trees into an in-core index, without writing it\nout to any file, all that remains to be done is just a matter of\nrunning diff-lib.c::do_diff_cache(), no?  I must be missing something.q\n\n> Another limitation of this is that it would be possible to manually\n> craft a stash entry where duplicate untracked files in the stash entry\n> will mask tracked files. This seems like an instance of \"Doctor, it\n> hurts when I do this! So don't do that!\" so this can be written off.\n\nWell, when you read the second tree into the in-core index to\noverlay what you read from the working tree state, you can certainly\nreport the collision and error it out.\n\n> Also, teach stash the --only-untracked option which only shows the\n> untracked files of a stash entry. This is similar to `git show stash^3`\n> but it is nice to provide a convenient abstraction for it so that users\n> do not have to think about the underlying implementation.\n\nOK.\n\n>  -u::\n>  --include-untracked::\n> -\tThis option is only valid for `push` and `save` commands.\n> +--no-include-untracked::\n> +\tWhen used with the `push` and `save` commands,\n> +\tall untracked files are also stashed and then cleaned up with\n> +\t`git clean`.\n\nBack when \"--include-untracked\" was there and no \"--only-untracked\"\nexisted, it made sense for the former to squat on a short-and-sweet\n\"-u\".  Now it comes back to bite us ;-)\n\n>  +\n> -All untracked files are also stashed and then cleaned up with\n> -`git clean`.\n> +When used with the `show` command, show the untracked files in the stash\n> +entry as part of the diff.\n\nOK.\n\n> +--only-untracked::\n> +\tThis option is only valid for the `show` command.\n> ++\n> +Show only the untracked files in the stash entry as part of the diff.\n\nOK.\n\n> diff --git a/builtin/stash.c b/builtin/stash.c\n> index 6f2b58f6ab..f7220fad56 100644\n> --- a/builtin/stash.c\n> +++ b/builtin/stash.c\n> @@ -787,6 +787,47 @@ static int git_stash_config(const char *var, const char *value, void *cb)\n>  \treturn git_diff_basic_config(var, value, cb);\n>  }\n>  \n> +static int merge_track_untracked(struct object_id *result, const struct stash_info *info)\n> +{\n> +\tint ret = 0;\n> +\tstruct index_state istate = { NULL };\n> +\tstruct child_process cp_read_tree = CHILD_PROCESS_INIT;\n> +\n> +\tif (!info->has_u) {\n> +\t\toidcpy(result, &info->w_commit);\n> +\t\treturn 0;\n> +\t}\n> +\n> +\t/*\n> +\t * TODO: is there a way of doing this all in-memory without writing\n> +\t * anything to disk?\n> +\t */\n\nOf course.  Read and study what read-tree does, which boils down to\na call to unpack_trees() without .merge option.\n"},{"id":"416754","messageId":"54f6fe119c61517d92a0aa43921ef9c1a52bb080.1613070418.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"xmqqv9b04cil.fsf@gitster.c.googlers.com","subject":"[PATCH] fixup! git-stash.txt: be explicit about subcommand options","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-11T19:07:53Z","receivedAt":"2021-02-11T19:14:56Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Signed-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/git-stash.txt | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 04e55eb826..f1197d641b 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -8,8 +8,8 @@ git-stash - Stash the changes in a dirty working directory away\n SYNOPSIS\n --------\n [verse]\n-'git stash' list [<options>]\n-'git stash' show [<options>] [<stash>]\n+'git stash' list [<log-options>]\n+'git stash' show [<diff-options>] [<stash>]\n 'git stash' drop [-q|--quiet] [<stash>]\n 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]\n 'git stash' branch <branchname> [<stash>]\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"417002","messageId":"YCs46VzamjuDrQR9@generichostname","threadId":"55091","inReplyTo":"xmqqa6sc4avx.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v2 8/9] stash show: teach --include-untracked and --only-untracked","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-16T03:15:53Z","receivedAt":"2021-02-16T03:16:44Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi Junio,\n\nOn Tue, Feb 09, 2021 at 11:53:06PM -0800, Junio C Hamano wrote:\n> > Do this via something like\n> >\n> > \tGIT_INDEX_FILE=... git read-tree stash stash^3\n> >\n> > and diffing the resulting tree object against the stash base.\n> \n> That explains the implementation, but does not make it clear what\n> the implementation wants to achieve.  So we read the tree from stash\n> (i.e. working tree) into a temporary index, and then overlay the\n> tree of stash^3 (i.e. untracked) on top---which means the resulting\n> \"index\" has the state of the working tree plus the untracked cruft\n> in it.  And comparing that with \"stash base\" (by the way is that a\n> term well understood?  I borrowed it for the above review comment,\n> which shows that there certainly is need for such a term) would show\n\nI'm not sure if it's a well-understood term but I can't think of any\nother meanings for the term so it doesn't seem very ambiguous.\n\n> the diff between the \"HEAD\" and the state that would have result if\n> you were to do an \"git add .\" in the working tree.  OK.\n> \n> > One improvement that this could use for the future is performing the\n> > action without writing anything to disk as one would expect this to be a\n> > read-only operation. This can be fixed in the future, however.\n> \n> Is it so difficult that we have to delay the fix for \"the future\"?\n> After reading two trees into an in-core index, without writing it\n> out to any file, all that remains to be done is just a matter of\n> running diff-lib.c::do_diff_cache(), no?  I must be missing something.q\n\nNo, I don't think it's difficult. It's just my inexperience with this\narea of the code.\n\n> > Another limitation of this is that it would be possible to manually\n> > craft a stash entry where duplicate untracked files in the stash entry\n> > will mask tracked files. This seems like an instance of \"Doctor, it\n> > hurts when I do this! So don't do that!\" so this can be written off.\n> \n> Well, when you read the second tree into the in-core index to\n> overlay what you read from the working tree state, you can certainly\n> report the collision and error it out.\n\nI'll send out my revised patch later today and I was unable to figure\nout an easy way of doing this.\n\nThanks,\nDenton\n"},{"id":"417005","messageId":"xmqq8s7osecg.fsf@gitster.c.googlers.com","threadId":"55091","inReplyTo":"YCs46VzamjuDrQR9@generichostname","subject":"Re: [PATCH v2 8/9] stash show: teach --include-untracked and --only-untracked","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-16T06:42:39Z","receivedAt":"2021-02-16T06:43:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> Hi Junio,\n>\n> On Tue, Feb 09, 2021 at 11:53:06PM -0800, Junio C Hamano wrote:\n>> > Do this via something like\n>> >\n>> > \tGIT_INDEX_FILE=... git read-tree stash stash^3\n>> >\n>> > and diffing the resulting tree object against the stash base.\n>> \n>> That explains the implementation, but does not make it clear what\n>> the implementation wants to achieve.  So we read the tree from stash\n>> (i.e. working tree) into a temporary index, and then overlay the\n>> tree of stash^3 (i.e. untracked) on top---which means the resulting\n>> \"index\" has the state of the working tree plus the untracked cruft\n>> in it.  And comparing that with \"stash base\" (by the way is that a\n>> term well understood?  I borrowed it for the above review comment,\n>> which shows that there certainly is need for such a term) would show\n>\n> I'm not sure if it's a well-understood term but I can't think of any\n> other meanings for the term so it doesn't seem very ambiguous.\n\nThanks.  I was hoping to hear either \"Yes, glossary defines it like\nthis\" or \"I believe it is an unambiguous good term; let's add it to\nthe glossary\".\n\n> I'll send out my revised patch later today and I was unable to figure\n> out an easy way of doing this.\n\nOK.\n\n"},{"id":"417006","messageId":"cover.1613459474.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1612855690.git.liu.denton@gmail.com","subject":"[PATCH v3 0/2] stash show: learn --include-untracked and --only-untracked","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-16T07:11:55Z","receivedAt":"2021-02-16T07:13:14Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"A blindspot that I've noticed in git is that it's not possible to\nproperly view a stash entry that has untracked files via `git stash\nshow`. Teach `git stash show --include-untracked` which should do this.\nIn addition, this series also teaches `--only-untracked` and the\n`stash.showIncludeUntracked` config option.\n\nThis series is based on 'dl/stash-cleanup'.\n\nChanges since v2:\n\n* Base this series on top of 'dl/stash-cleanup'\n\n* Attempt to replicate the read-tree code to merge the untracked tree\n\nChanges since v1:\n\n* Add a dash for <log-options> and <diff-options>\n\n* Fix the misspelling of --include-untracked in a commit message\n\n* Change the approach from concatenating diffs to using `git read-tree`\n\nDenton Liu (2):\n  stash show: teach --include-untracked and --only-untracked\n  stash show: learn stash.showIncludeUntracked\n\n Documentation/config/stash.txt         |  5 ++\n Documentation/git-stash.txt            | 22 ++++---\n builtin/stash.c                        | 61 +++++++++++++++++-\n contrib/completion/git-completion.bash |  2 +-\n t/t3905-stash-include-untracked.sh     | 86 ++++++++++++++++++++++++++\n 5 files changed, 167 insertions(+), 9 deletions(-)\n\nRange-diff against v2:\n1:  88d4791259 ! 1:  85b81f2f06 stash show: teach --include-untracked and --only-untracked\n    @@ Commit message\n         stash entry itself, running `git stash show` does not include the\n         untracked files as part of the diff.\n     \n    -    Teach stash the --include-untracked option, which also displays the\n    -    untracked files in a stash entry from the third parent (if it exists).\n    -    Do this via something like\n    +    With --include-untracked, untracked paths, which are recorded in the\n    +    third-parent if it exists, are shown in addition to the paths that have\n    +    modifications between the stash base and the working tree in the stash.\n     \n    -            GIT_INDEX_FILE=... git read-tree stash stash^3\n    -\n    -    and diffing the resulting tree object against the stash base.\n    -\n    -    One improvement that this could use for the future is performing the\n    -    action without writing anything to disk as one would expect this to be a\n    -    read-only operation. This can be fixed in the future, however.\n    -\n    -    Another limitation of this is that it would be possible to manually\n    -    craft a stash entry where duplicate untracked files in the stash entry\n    -    will mask tracked files. This seems like an instance of \"Doctor, it\n    -    hurts when I do this! So don't do that!\" so this can be written off.\n    +    One limitation of this is that it would be possible to manually craft a\n    +    stash entry where duplicate untracked files in the stash entry will mask\n    +    tracked files. This seems like an instance of \"Doctor, it hurts when I\n    +    do this! So don't do that!\" so this can be written off.\n     \n         Also, teach stash the --only-untracked option which only shows the\n         untracked files of a stash entry. This is similar to `git show stash^3`\n         but it is nice to provide a convenient abstraction for it so that users\n         do not have to think about the underlying implementation.\n     \n    +\n    + ## Notes ##\n    +    I am not familiar with the read-tree code so this attempt at replicating\n    +    the read-tree code may in diff_include_untracked() may be incorrect\n    +    (particularly the use of the_index?).\n    +\n    +    Also, I could not figure out how to make unpack_trees() error out in the\n    +    case where untracked tree entry contains duplicate entries with the\n    +    worktree entry.\n    +\n      ## Documentation/git-stash.txt ##\n    +@@ Documentation/git-stash.txt: SYNOPSIS\n    + --------\n    + [verse]\n    + 'git stash' list [<log-options>]\n    +-'git stash' show [<diff-options>] [<stash>]\n    ++'git stash' show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]\n    + 'git stash' drop [-q|--quiet] [<stash>]\n    + 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]\n    + 'git stash' branch <branchname> [<stash>]\n     @@ Documentation/git-stash.txt: stash@{1}: On master: 9cc0589... Add git-stash\n      The command takes options applicable to the 'git log'\n      command to control what is shown and how. See linkgit:git-log[1].\n    @@ builtin/stash.c: static int git_stash_config(const char *var, const char *value,\n      \treturn git_diff_basic_config(var, value, cb);\n      }\n      \n    -+static int merge_track_untracked(struct object_id *result, const struct stash_info *info)\n    ++static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt)\n     +{\n    -+\tint ret = 0;\n    -+\tstruct index_state istate = { NULL };\n    -+\tstruct child_process cp_read_tree = CHILD_PROCESS_INIT;\n    ++\tconst struct object_id *oid[] = { &info->w_commit, &info->u_tree };\n    ++\tstruct tree *tree[ARRAY_SIZE(oid)];\n    ++\tstruct tree_desc tree_desc[ARRAY_SIZE(oid)];\n    ++\tstruct unpack_trees_options unpack_tree_opt = { 0 };\n    ++\tint i;\n     +\n    -+\tif (!info->has_u) {\n    -+\t\toidcpy(result, &info->w_commit);\n    -+\t\treturn 0;\n    ++\tfor (i = 0; i < ARRAY_SIZE(oid); i++) {\n    ++\t\ttree[i] = parse_tree_indirect(oid[i]);\n    ++\t\tif (parse_tree(tree[i]) < 0)\n    ++\t\t\tdie(_(\"failed to parse tree\"));\n    ++\t\tinit_tree_desc(&tree_desc[i], tree[i]->buffer, tree[i]->size);\n     +\t}\n     +\n    -+\t/*\n    -+\t * TODO: is there a way of doing this all in-memory without writing\n    -+\t * anything to disk?\n    -+\t */\n    -+\tremove_path(stash_index_path.buf);\n    ++\tunpack_tree_opt.head_idx = -1;\n    ++\tunpack_tree_opt.src_index = &the_index;\n    ++\tunpack_tree_opt.dst_index = &the_index;\n    ++\tunpack_tree_opt.fn = twoway_merge;\n     +\n    -+\tcp_read_tree.git_cmd = 1;\n    -+\tstrvec_push(&cp_read_tree.args, \"read-tree\");\n    -+\tstrvec_push(&cp_read_tree.args, oid_to_hex(&info->w_commit));\n    -+\tstrvec_push(&cp_read_tree.args, oid_to_hex(&info->u_tree));\n    -+\tstrvec_pushf(&cp_read_tree.env_array, \"GIT_INDEX_FILE=%s\",\n    -+\t\t     stash_index_path.buf);\n    ++\tif (unpack_trees(ARRAY_SIZE(tree_desc), tree_desc, &unpack_tree_opt))\n    ++\t\tdie(_(\"failed to unpack trees\"));\n     +\n    -+\tif (run_command(&cp_read_tree)) {\n    -+\t\tret = -1;\n    -+\t\tgoto done;\n    -+\t}\n    -+\n    -+\tif (write_index_as_tree(result, &istate, stash_index_path.buf, 0,\n    -+\t\t\t\tNULL)) {\n    -+\t\tret = -1;\n    -+\t\tgoto done;\n    -+\t}\n    -+\n    -+done:\n    -+\tdiscard_index(&istate);\n    -+\tremove_path(stash_index_path.buf);\n    -+\treturn ret;\n    ++\tdo_diff_cache(&info->b_commit, diff_opt);\n     +}\n     +\n      static int show_stash(int argc, const char **argv, const char *prefix)\n    @@ builtin/stash.c: static int show_stash(int argc, const char **argv, const char *\n      \tstruct rev_info rev;\n      \tstruct strvec stash_args = STRVEC_INIT;\n      \tstruct strvec revision_args = STRVEC_INIT;\n    -+\tstruct object_id *before = NULL;\n    -+\tstruct object_id *after = NULL;\n    -+\tstruct object_id untracked_merged_tree;\n     +\tenum {\n     +\t\tUNTRACKED_NONE,\n     +\t\tUNTRACKED_INCLUDE,\n    @@ builtin/stash.c: static int show_stash(int argc, const char **argv, const char *\n     -\tdiff_tree_oid(&info.b_commit, &info.w_commit, \"\", &rev.diffopt);\n     +\tswitch (show_untracked) {\n     +\tcase UNTRACKED_NONE:\n    -+\t\tbefore = &info.b_commit;\n    -+\t\tafter = &info.w_commit;\n    ++\t\tdiff_tree_oid(&info.b_commit, &info.w_commit, \"\", &rev.diffopt);\n     +\t\tbreak;\n     +\tcase UNTRACKED_ONLY:\n    -+\t\tbefore = NULL;\n    -+\t\tafter = &info.u_tree;\n    ++\t\tdiff_root_tree_oid(&info.u_tree, \"\", &rev.diffopt);\n     +\t\tbreak;\n     +\tcase UNTRACKED_INCLUDE:\n    -+\t\tif (merge_track_untracked(&untracked_merged_tree, &info) < 0)\n    -+\t\t\tdie(_(\"unable merge stash index with untracked files index\"));\n    -+\t\tbefore = &info.b_commit;\n    -+\t\tafter = &untracked_merged_tree;\n    ++\t\tdiff_include_untracked(&info, &rev.diffopt);\n     +\t\tbreak;\n     +\t}\n    -+\tdiff_tree_oid(before, after, \"\", &rev.diffopt);\n      \tlog_tree_diff_flush(&rev);\n      \n      \tfree_stash_info(&info);\n2:  ac4019f47e = 2:  d19d07ec27 stash show: learn stash.showIncludeUntracked\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"417007","messageId":"85b81f2f06bd1b40ee2de220cc84dd74b425daf3.1613459475.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1613459474.git.liu.denton@gmail.com","subject":"[PATCH v3 1/2] stash show: teach --include-untracked and --only-untracked","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-16T07:11:56Z","receivedAt":"2021-02-16T07:13:14Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Stash entries can be made with untracked files via\n`git stash push --include-untracked`. However, because the untracked\nfiles are stored in the third parent of the stash entry and not the\nstash entry itself, running `git stash show` does not include the\nuntracked files as part of the diff.\n\nWith --include-untracked, untracked paths, which are recorded in the\nthird-parent if it exists, are shown in addition to the paths that have\nmodifications between the stash base and the working tree in the stash.\n\nOne limitation of this is that it would be possible to manually craft a\nstash entry where duplicate untracked files in the stash entry will mask\ntracked files. This seems like an instance of \"Doctor, it hurts when I\ndo this! So don't do that!\" so this can be written off.\n\nAlso, teach stash the --only-untracked option which only shows the\nuntracked files of a stash entry. This is similar to `git show stash^3`\nbut it is nice to provide a convenient abstraction for it so that users\ndo not have to think about the underlying implementation.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n\nNotes:\n    I am not familiar with the read-tree code so this attempt at replicating\n    the read-tree code may in diff_include_untracked() may be incorrect\n    (particularly the use of the_index?).\n    \n    Also, I could not figure out how to make unpack_trees() error out in the\n    case where untracked tree entry contains duplicate entries with the\n    worktree entry.\n\n Documentation/git-stash.txt            | 18 ++++--\n builtin/stash.c                        | 53 +++++++++++++++-\n contrib/completion/git-completion.bash |  2 +-\n t/t3905-stash-include-untracked.sh     | 84 ++++++++++++++++++++++++++\n 4 files changed, 150 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex f1197d641b..8eeb60feb1 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -9,7 +9,7 @@ SYNOPSIS\n --------\n [verse]\n 'git stash' list [<log-options>]\n-'git stash' show [<diff-options>] [<stash>]\n+'git stash' show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]\n 'git stash' drop [-q|--quiet] [<stash>]\n 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]\n 'git stash' branch <branchname> [<stash>]\n@@ -83,7 +83,7 @@ stash@{1}: On master: 9cc0589... Add git-stash\n The command takes options applicable to the 'git log'\n command to control what is shown and how. See linkgit:git-log[1].\n \n-show [<diff-options>] [<stash>]::\n+show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]::\n \n \tShow the changes recorded in the stash entry as a diff between the\n \tstashed contents and the commit back when the stash entry was first\n@@ -160,10 +160,18 @@ up with `git clean`.\n \n -u::\n --include-untracked::\n-\tThis option is only valid for `push` and `save` commands.\n+--no-include-untracked::\n+\tWhen used with the `push` and `save` commands,\n+\tall untracked files are also stashed and then cleaned up with\n+\t`git clean`.\n +\n-All untracked files are also stashed and then cleaned up with\n-`git clean`.\n+When used with the `show` command, show the untracked files in the stash\n+entry as part of the diff.\n+\n+--only-untracked::\n+\tThis option is only valid for the `show` command.\n++\n+Show only the untracked files in the stash entry as part of the diff.\n \n --index::\n \tThis option is only valid for `pop` and `apply` commands.\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 6f2b58f6ab..417ed2b4a1 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -787,6 +787,32 @@ static int git_stash_config(const char *var, const char *value, void *cb)\n \treturn git_diff_basic_config(var, value, cb);\n }\n \n+static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt)\n+{\n+\tconst struct object_id *oid[] = { &info->w_commit, &info->u_tree };\n+\tstruct tree *tree[ARRAY_SIZE(oid)];\n+\tstruct tree_desc tree_desc[ARRAY_SIZE(oid)];\n+\tstruct unpack_trees_options unpack_tree_opt = { 0 };\n+\tint i;\n+\n+\tfor (i = 0; i < ARRAY_SIZE(oid); i++) {\n+\t\ttree[i] = parse_tree_indirect(oid[i]);\n+\t\tif (parse_tree(tree[i]) < 0)\n+\t\t\tdie(_(\"failed to parse tree\"));\n+\t\tinit_tree_desc(&tree_desc[i], tree[i]->buffer, tree[i]->size);\n+\t}\n+\n+\tunpack_tree_opt.head_idx = -1;\n+\tunpack_tree_opt.src_index = &the_index;\n+\tunpack_tree_opt.dst_index = &the_index;\n+\tunpack_tree_opt.fn = twoway_merge;\n+\n+\tif (unpack_trees(ARRAY_SIZE(tree_desc), tree_desc, &unpack_tree_opt))\n+\t\tdie(_(\"failed to unpack trees\"));\n+\n+\tdo_diff_cache(&info->b_commit, diff_opt);\n+}\n+\n static int show_stash(int argc, const char **argv, const char *prefix)\n {\n \tint i;\n@@ -795,7 +821,18 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \tstruct rev_info rev;\n \tstruct strvec stash_args = STRVEC_INIT;\n \tstruct strvec revision_args = STRVEC_INIT;\n+\tenum {\n+\t\tUNTRACKED_NONE,\n+\t\tUNTRACKED_INCLUDE,\n+\t\tUNTRACKED_ONLY\n+\t} show_untracked = UNTRACKED_NONE;\n \tstruct option options[] = {\n+\t\tOPT_SET_INT('u', \"include-untracked\", &show_untracked,\n+\t\t\t    N_(\"include untracked files in the stash\"),\n+\t\t\t    UNTRACKED_INCLUDE),\n+\t\tOPT_SET_INT_F(0, \"only-untracked\", &show_untracked,\n+\t\t\t      N_(\"only show untracked files in the stash\"),\n+\t\t\t      UNTRACKED_ONLY, PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \n@@ -803,6 +840,10 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \tgit_config(git_diff_ui_config, NULL);\n \tinit_revisions(&rev, prefix);\n \n+\targc = parse_options(argc, argv, prefix, options, git_stash_show_usage,\n+\t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n \tstrvec_push(&revision_args, argv[0]);\n \tfor (i = 1; i < argc; i++) {\n \t\tif (argv[i][0] != '-')\n@@ -845,7 +886,17 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \n \trev.diffopt.flags.recursive = 1;\n \tsetup_diff_pager(&rev.diffopt);\n-\tdiff_tree_oid(&info.b_commit, &info.w_commit, \"\", &rev.diffopt);\n+\tswitch (show_untracked) {\n+\tcase UNTRACKED_NONE:\n+\t\tdiff_tree_oid(&info.b_commit, &info.w_commit, \"\", &rev.diffopt);\n+\t\tbreak;\n+\tcase UNTRACKED_ONLY:\n+\t\tdiff_root_tree_oid(&info.u_tree, \"\", &rev.diffopt);\n+\t\tbreak;\n+\tcase UNTRACKED_INCLUDE:\n+\t\tdiff_include_untracked(&info, &rev.diffopt);\n+\t\tbreak;\n+\t}\n \tlog_tree_diff_flush(&rev);\n \n \tfree_stash_info(&info);\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 4b1f4264a6..64ef6ffa21 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -3051,7 +3051,7 @@ _git_stash ()\n \t\t\t__gitcomp \"--name-status --oneline --patch-with-stat\"\n \t\t\t;;\n \t\tshow,--*)\n-\t\t\t__gitcomp \"$__git_diff_common_options\"\n+\t\t\t__gitcomp \"--include-untracked --only-untracked $__git_diff_common_options\"\n \t\t\t;;\n \t\tbranch,--*)\n \t\t\t;;\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex b26a97aef4..978bc97baf 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -297,4 +297,88 @@ test_expect_success 'stash -u with globs' '\n \ttest_path_is_missing untracked.txt\n '\n \n+test_expect_success 'stash show --include-untracked shows untracked files' '\n+\tgit reset --hard &&\n+\tgit clean -xf &&\n+\t>untracked &&\n+\t>tracked &&\n+\tgit add tracked &&\n+\tgit stash -u &&\n+\n+\tcat >expect <<-EOF &&\n+\t tracked   | 0\n+\t untracked | 0\n+\t 2 files changed, 0 insertions(+), 0 deletions(-)\n+\tEOF\n+\tgit stash show --include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show -u >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --no-include-untracked --include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --only-untracked --include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tcat >expect <<-EOF &&\n+\tdiff --git a/tracked b/tracked\n+\tnew file mode 100644\n+\tindex 0000000..e69de29\n+\tdiff --git a/untracked b/untracked\n+\tnew file mode 100644\n+\tindex 0000000..e69de29\n+\tEOF\n+\tgit stash show -p --include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --include-untracked -p >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stash show --only-untracked only shows untracked files' '\n+\tgit reset --hard &&\n+\tgit clean -xf &&\n+\t>untracked &&\n+\t>tracked &&\n+\tgit add tracked &&\n+\tgit stash -u &&\n+\n+\tcat >expect <<-EOF &&\n+\t untracked | 0\n+\t 1 file changed, 0 insertions(+), 0 deletions(-)\n+\tEOF\n+\tgit stash show --only-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --no-include-untracked --only-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --include-untracked --only-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tcat >expect <<-EOF &&\n+\tdiff --git a/untracked b/untracked\n+\tnew file mode 100644\n+\tindex 0000000..e69de29\n+\tEOF\n+\tgit stash show -p --only-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --only-untracked -p >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stash show --no-include-untracked cancels --{include,show}-untracked' '\n+\tgit reset --hard &&\n+\tgit clean -xf &&\n+\t>untracked &&\n+\t>tracked &&\n+\tgit add tracked &&\n+\tgit stash -u &&\n+\n+\tcat >expect <<-EOF &&\n+\t tracked | 0\n+\t 1 file changed, 0 insertions(+), 0 deletions(-)\n+\tEOF\n+\tgit stash show --only-untracked --no-include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --include-untracked --no-include-untracked >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"417008","messageId":"d19d07ec27d094175621eb400f74717c596c609d.1613459475.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1613459474.git.liu.denton@gmail.com","subject":"[PATCH v3 2/2] stash show: learn stash.showIncludeUntracked","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-16T07:11:57Z","receivedAt":"2021-02-16T07:13:14Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"The previous commit teaches `git stash show --include-untracked`. It\nmay be desirable for a user to be able to always enable the\n--include-untracked behavior. Teach the stash.showIncludeUntracked\nconfig option which allows users to do this in a similar manner to\nstash.showPatch.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/config/stash.txt     | 5 +++++\n Documentation/git-stash.txt        | 4 ++--\n builtin/stash.c                    | 8 ++++++++\n t/t3905-stash-include-untracked.sh | 2 ++\n 4 files changed, 17 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/config/stash.txt b/Documentation/config/stash.txt\nindex 00eb35434e..413f907cba 100644\n--- a/Documentation/config/stash.txt\n+++ b/Documentation/config/stash.txt\n@@ -5,6 +5,11 @@ stash.useBuiltin::\n \tis always used. Setting this will emit a warning, to alert any\n \tremaining users that setting this now does nothing.\n \n+stash.showIncludeUntracked::\n+\tIf this is set to true, the `git stash show` command without an\n+\toption will show the untracked files of a stash entry.  Defaults to\n+\tfalse. See description of 'show' command in linkgit:git-stash[1].\n+\n stash.showPatch::\n \tIf this is set to true, the `git stash show` command without an\n \toption will show the stash entry in patch form.  Defaults to false.\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 8eeb60feb1..a8c8c32f1e 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -91,8 +91,8 @@ show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]::\n \tBy default, the command shows the diffstat, but it will accept any\n \tformat known to 'git diff' (e.g., `git stash show -p stash@{1}`\n \tto view the second most recent entry in patch form).\n-\tYou can use stash.showStat and/or stash.showPatch config variables\n-\tto change the default behavior.\n+\tYou can use stash.showIncludeUntracked, stash.showStat, and\n+\tstash.showPatch config variables to change the default behavior.\n \n pop [--index] [-q|--quiet] [<stash>]::\n \ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 417ed2b4a1..c788a3e236 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -768,6 +768,7 @@ static int list_stash(int argc, const char **argv, const char *prefix)\n \n static int show_stat = 1;\n static int show_patch;\n+static int show_include_untracked;\n static int use_legacy_stash;\n \n static int git_stash_config(const char *var, const char *value, void *cb)\n@@ -780,6 +781,10 @@ static int git_stash_config(const char *var, const char *value, void *cb)\n \t\tshow_patch = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"stash.showincludeuntracked\")) {\n+\t\tshow_include_untracked = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(var, \"stash.usebuiltin\")) {\n \t\tuse_legacy_stash = !git_config_bool(var, value);\n \t\treturn 0;\n@@ -868,6 +873,9 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \t\tif (show_patch)\n \t\t\trev.diffopt.output_format |= DIFF_FORMAT_PATCH;\n \n+\t\tif (show_include_untracked)\n+\t\t\tshow_untracked = UNTRACKED_INCLUDE;\n+\n \t\tif (!show_stat && !show_patch) {\n \t\t\tfree_stash_info(&info);\n \t\t\treturn 0;\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex 978bc97baf..8bcd4c5ca8 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -318,6 +318,8 @@ test_expect_success 'stash show --include-untracked shows untracked files' '\n \ttest_cmp expect actual &&\n \tgit stash show --only-untracked --include-untracked >actual &&\n \ttest_cmp expect actual &&\n+\tgit -c stash.showIncludeUntracked=true stash show >actual &&\n+\ttest_cmp expect actual &&\n \n \tcat >expect <<-EOF &&\n \tdiff --git a/tracked b/tracked\n-- \n2.30.0.478.g8a0d178c01\n\n"},{"id":"417103","messageId":"xmqqczwzpxsz.fsf@gitster.c.googlers.com","threadId":"55091","inReplyTo":"85b81f2f06bd1b40ee2de220cc84dd74b425daf3.1613459475.git.liu.denton@gmail.com","subject":"Re: [PATCH v3 1/2] stash show: teach --include-untracked and --only-untracked","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-16T20:22:52Z","receivedAt":"2021-02-16T20:23:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> +static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt)\n> +{\n> +\tconst struct object_id *oid[] = { &info->w_commit, &info->u_tree };\n> +\tstruct tree *tree[ARRAY_SIZE(oid)];\n> +\tstruct tree_desc tree_desc[ARRAY_SIZE(oid)];\n> +\tstruct unpack_trees_options unpack_tree_opt = { 0 };\n> +\tint i;\n> +\n> +\tfor (i = 0; i < ARRAY_SIZE(oid); i++) {\n> +\t\ttree[i] = parse_tree_indirect(oid[i]);\n> +\t\tif (parse_tree(tree[i]) < 0)\n> +\t\t\tdie(_(\"failed to parse tree\"));\n> +\t\tinit_tree_desc(&tree_desc[i], tree[i]->buffer, tree[i]->size);\n> +\t}\n> +\n> +\tunpack_tree_opt.head_idx = -1;\n> +\tunpack_tree_opt.src_index = &the_index;\n> +\tunpack_tree_opt.dst_index = &the_index;\n> +\tunpack_tree_opt.fn = twoway_merge;\n\nOK, it looks like this was borrowed from read_tree implementation\nfor reading two trees into the index, sort-of, but is a bit funny.\n\nThe setting of .fn to twoway_merge is misleading.  The .fn callback\nis to be used when .merge is set (otherwise nothing should call it\ninside unpack-trees.c), but nobody seems to set opt.merge to true.\n\n> +\tif (unpack_trees(ARRAY_SIZE(tree_desc), tree_desc, &unpack_tree_opt))\n> +\t\tdie(_(\"failed to unpack trees\"));\n> +\n> +\tdo_diff_cache(&info->b_commit, diff_opt);\n> +}\n\nNice to see that it was just a simple matter of programming ;-)\n\n\n\n builtin/stash.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git c/builtin/stash.c w/builtin/stash.c\nindex c788a3e236..7e0204bd8a 100644\n--- c/builtin/stash.c\n+++ w/builtin/stash.c\n@@ -807,10 +807,11 @@ static void diff_include_untracked(const struct stash_info *info, struct diff_op\n \t\tinit_tree_desc(&tree_desc[i], tree[i]->buffer, tree[i]->size);\n \t}\n \n+\t/* mimic \"git read-tree W U\" without \"-m\" */\n \tunpack_tree_opt.head_idx = -1;\n \tunpack_tree_opt.src_index = &the_index;\n \tunpack_tree_opt.dst_index = &the_index;\n-\tunpack_tree_opt.fn = twoway_merge;\n+\tunpack_tree_opt.fn = NULL;\n \n \tif (unpack_trees(ARRAY_SIZE(tree_desc), tree_desc, &unpack_tree_opt))\n \t\tdie(_(\"failed to unpack trees\"));\n"},{"id":"417140","messageId":"xmqqeehfmnmg.fsf@gitster.g","threadId":"55091","inReplyTo":"85b81f2f06bd1b40ee2de220cc84dd74b425daf3.1613459475.git.liu.denton@gmail.com","subject":"Re: [PATCH v3 1/2] stash show: teach --include-untracked and --only-untracked","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-17T02:31:03Z","receivedAt":"2021-02-17T02:32:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\n> index b26a97aef4..978bc97baf 100755\n> --- a/t/t3905-stash-include-untracked.sh\n> +++ b/t/t3905-stash-include-untracked.sh\n> @@ -297,4 +297,88 @@ test_expect_success 'stash -u with globs' '\n>  \ttest_path_is_missing untracked.txt\n>  '\n>  \n> +test_expect_success 'stash show --include-untracked shows untracked files' '\n> + ...\n> +\tcat >expect <<-EOF &&\n> +\tdiff --git a/tracked b/tracked\n> +\tnew file mode 100644\n> +\tindex 0000000..e69de29\n> +\tdiff --git a/untracked b/untracked\n> +\tnew file mode 100644\n> +\tindex 0000000..e69de29\n> +\tEOF\n\nWe'd need this on top.\n\nThanks.\n\n----- >8 ---------- >8 ---------- >8 ---------- >8 -----\nSubject: [PATCH] SQUASH???\n\n t/t3905-stash-include-untracked.sh | 8 +++++---\n 1 file changed, 5 insertions(+), 3 deletions(-)\n\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex 8bcd4c5ca8..a706ab80a5 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -303,6 +303,7 @@ test_expect_success 'stash show --include-untracked shows untracked files' '\n \t>untracked &&\n \t>tracked &&\n \tgit add tracked &&\n+\tempty_blob_oid=$(git rev-parse --short :tracked) &&\n \tgit stash -u &&\n \n \tcat >expect <<-EOF &&\n@@ -324,10 +325,10 @@ test_expect_success 'stash show --include-untracked shows untracked files' '\n \tcat >expect <<-EOF &&\n \tdiff --git a/tracked b/tracked\n \tnew file mode 100644\n-\tindex 0000000..e69de29\n+\tindex 0000000..$empty_blob_oid\n \tdiff --git a/untracked b/untracked\n \tnew file mode 100644\n-\tindex 0000000..e69de29\n+\tindex 0000000..$empty_blob_oid\n \tEOF\n \tgit stash show -p --include-untracked >actual &&\n \ttest_cmp expect actual &&\n@@ -341,6 +342,7 @@ test_expect_success 'stash show --only-untracked only shows untracked files' '\n \t>untracked &&\n \t>tracked &&\n \tgit add tracked &&\n+\tempty_blob_oid=$(git rev-parse --short :tracked) &&\n \tgit stash -u &&\n \n \tcat >expect <<-EOF &&\n@@ -357,7 +359,7 @@ test_expect_success 'stash show --only-untracked only shows untracked files' '\n \tcat >expect <<-EOF &&\n \tdiff --git a/untracked b/untracked\n \tnew file mode 100644\n-\tindex 0000000..e69de29\n+\tindex 0000000..$empty_blob_oid\n \tEOF\n \tgit stash show -p --only-untracked >actual &&\n \ttest_cmp expect actual &&\n-- \n2.30.1-684-g310d03ec1a\n\n"},{"id":"417154","messageId":"YCz7gOlXDTTd5urZ@generichostname","threadId":"55091","inReplyTo":"xmqqczwzpxsz.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v3 1/2] stash show: teach --include-untracked and --only-untracked","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-02-17T11:18:24Z","receivedAt":"2021-02-17T11:19:09Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi Junio,\n\nOn Tue, Feb 16, 2021 at 12:22:52PM -0800, Junio C Hamano wrote:\n>  builtin/stash.c | 3 ++-\n>  1 file changed, 2 insertions(+), 1 deletion(-)\n> \n> diff --git c/builtin/stash.c w/builtin/stash.c\n> index c788a3e236..7e0204bd8a 100644\n> --- c/builtin/stash.c\n> +++ w/builtin/stash.c\n> @@ -807,10 +807,11 @@ static void diff_include_untracked(const struct stash_info *info, struct diff_op\n>  \t\tinit_tree_desc(&tree_desc[i], tree[i]->buffer, tree[i]->size);\n>  \t}\n>  \n> +\t/* mimic \"git read-tree W U\" without \"-m\" */\n>  \tunpack_tree_opt.head_idx = -1;\n>  \tunpack_tree_opt.src_index = &the_index;\n>  \tunpack_tree_opt.dst_index = &the_index;\n> -\tunpack_tree_opt.fn = twoway_merge;\n> +\tunpack_tree_opt.fn = NULL;\n\nPerhaps it would be even more clear if we just removed this line\nentirely, otherwise it may give future readers a false impression that\n.fn is significant in any way.\n\nAside from that, both of your SQUASH??? commits look good to me. Thanks\nfor tying up the loose ends.\n\n-Denton\n\n>  \tif (unpack_trees(ARRAY_SIZE(tree_desc), tree_desc, &unpack_tree_opt))\n>  \t\tdie(_(\"failed to unpack trees\"));\n"},{"id":"417164","messageId":"xmqq4kiamvmg.fsf@gitster.g","threadId":"55091","inReplyTo":"YCz7gOlXDTTd5urZ@generichostname","subject":"Re: [PATCH v3 1/2] stash show: teach --include-untracked and --only-untracked","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-17T17:50:31Z","receivedAt":"2021-02-17T17:51:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> Hi Junio,\n>\n> On Tue, Feb 16, 2021 at 12:22:52PM -0800, Junio C Hamano wrote:\n>>  builtin/stash.c | 3 ++-\n>>  1 file changed, 2 insertions(+), 1 deletion(-)\n>> \n>> diff --git c/builtin/stash.c w/builtin/stash.c\n>> index c788a3e236..7e0204bd8a 100644\n>> --- c/builtin/stash.c\n>> +++ w/builtin/stash.c\n>> @@ -807,10 +807,11 @@ static void diff_include_untracked(const struct stash_info *info, struct diff_op\n>>  \t\tinit_tree_desc(&tree_desc[i], tree[i]->buffer, tree[i]->size);\n>>  \t}\n>>  \n>> +\t/* mimic \"git read-tree W U\" without \"-m\" */\n>>  \tunpack_tree_opt.head_idx = -1;\n>>  \tunpack_tree_opt.src_index = &the_index;\n>>  \tunpack_tree_opt.dst_index = &the_index;\n>> -\tunpack_tree_opt.fn = twoway_merge;\n>> +\tunpack_tree_opt.fn = NULL;\n>\n> Perhaps it would be even more clear if we just removed this line\n> entirely, otherwise it may give future readers a false impression that\n> .fn is significant in any way.\n\nAssignment of something concrete like \"twoway_merge\" to .fn when it\nis a no-op was misleading, but between NULL or uninitialized, both\nstate clear that it is not used, so I am OK with either.\n\n> Aside from that, both of your SQUASH??? commits look good to me. Thanks\n> for tying up the loose ends.\n\nWe may want to write a custom unpack_trees() callback for this, and\nuse it here to catch \"this third tree claims to be untracked, but\nwhy does it have an entry that overlaps and/or D/F-conflicts with\nthe entry from the working tree side?\" that you talked about in the\nlog message, but I think it is better to leave it for future [*], as\nthe feature should already be usable in its current shape.\n\nThanks.\n\n\n[Footnote]\n\n * Yes, I didn't say \"we can leave it\", but said \"it is better to\n   leave it\"; as long as we do not declare victory too early and end\n   up shipping a half-baked unusable mess, I think it is better to\n   wrap a topic early and plan to make it nicer in a future\n   follow-up.\n\n   To encourage such future refinements, however, you may add a\n   NEEDSWORK comment in the code as a reminder for our future\n   selves.\n"},{"id":"418141","messageId":"3480086f1dc3d3dc290ff80563b83bd5378fe33e.1614770171.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1614770171.git.liu.denton@gmail.com","subject":"[PATCH v4 2/2] stash show: learn stash.showIncludeUntracked","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-03-03T11:16:43Z","receivedAt":"2021-03-04T00:22:54Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"The previous commit teaches `git stash show --include-untracked`. It\nmay be desirable for a user to be able to always enable the\n--include-untracked behavior. Teach the stash.showIncludeUntracked\nconfig option which allows users to do this in a similar manner to\nstash.showPatch.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/config/stash.txt     | 5 +++++\n Documentation/git-stash.txt        | 4 ++--\n builtin/stash.c                    | 8 ++++++++\n t/t3905-stash-include-untracked.sh | 2 ++\n 4 files changed, 17 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/config/stash.txt b/Documentation/config/stash.txt\nindex 00eb35434e..413f907cba 100644\n--- a/Documentation/config/stash.txt\n+++ b/Documentation/config/stash.txt\n@@ -5,6 +5,11 @@ stash.useBuiltin::\n \tis always used. Setting this will emit a warning, to alert any\n \tremaining users that setting this now does nothing.\n \n+stash.showIncludeUntracked::\n+\tIf this is set to true, the `git stash show` command without an\n+\toption will show the untracked files of a stash entry.  Defaults to\n+\tfalse. See description of 'show' command in linkgit:git-stash[1].\n+\n stash.showPatch::\n \tIf this is set to true, the `git stash show` command without an\n \toption will show the stash entry in patch form.  Defaults to false.\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex 8eeb60feb1..a8c8c32f1e 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -91,8 +91,8 @@ show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]::\n \tBy default, the command shows the diffstat, but it will accept any\n \tformat known to 'git diff' (e.g., `git stash show -p stash@{1}`\n \tto view the second most recent entry in patch form).\n-\tYou can use stash.showStat and/or stash.showPatch config variables\n-\tto change the default behavior.\n+\tYou can use stash.showIncludeUntracked, stash.showStat, and\n+\tstash.showPatch config variables to change the default behavior.\n \n pop [--index] [-q|--quiet] [<stash>]::\n \ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 9b7a541cd0..8922a1240c 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -768,6 +768,7 @@ static int list_stash(int argc, const char **argv, const char *prefix)\n \n static int show_stat = 1;\n static int show_patch;\n+static int show_include_untracked;\n static int use_legacy_stash;\n \n static int git_stash_config(const char *var, const char *value, void *cb)\n@@ -780,6 +781,10 @@ static int git_stash_config(const char *var, const char *value, void *cb)\n \t\tshow_patch = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"stash.showincludeuntracked\")) {\n+\t\tshow_include_untracked = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(var, \"stash.usebuiltin\")) {\n \t\tuse_legacy_stash = !git_config_bool(var, value);\n \t\treturn 0;\n@@ -869,6 +874,9 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \t\tif (show_patch)\n \t\t\trev.diffopt.output_format |= DIFF_FORMAT_PATCH;\n \n+\t\tif (show_include_untracked)\n+\t\t\tshow_untracked = UNTRACKED_INCLUDE;\n+\n \t\tif (!show_stat && !show_patch) {\n \t\t\tfree_stash_info(&info);\n \t\t\treturn 0;\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex 08ceef6411..b470db7ef7 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -319,6 +319,8 @@ test_expect_success 'stash show --include-untracked shows untracked files' '\n \ttest_cmp expect actual &&\n \tgit stash show --only-untracked --include-untracked >actual &&\n \ttest_cmp expect actual &&\n+\tgit -c stash.showIncludeUntracked=true stash show >actual &&\n+\ttest_cmp expect actual &&\n \n \tcat >expect <<-EOF &&\n \tdiff --git a/tracked b/tracked\n-- \n2.31.0.rc1.228.gb75b4e4ce2\n\n"},{"id":"418144","messageId":"af3757135bd66a75f89cdd559917073f7235da25.1614770171.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1614770171.git.liu.denton@gmail.com","subject":"[PATCH v4 1/2] stash show: teach --include-untracked and --only-untracked","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-03-03T11:16:42Z","receivedAt":"2021-03-04T00:22:54Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Stash entries can be made with untracked files via\n`git stash push --include-untracked`. However, because the untracked\nfiles are stored in the third parent of the stash entry and not the\nstash entry itself, running `git stash show` does not include the\nuntracked files as part of the diff.\n\nWith --include-untracked, untracked paths, which are recorded in the\nthird-parent if it exists, are shown in addition to the paths that have\nmodifications between the stash base and the working tree in the stash.\n\nIt is possible to manually craft a malformed stash entry where duplicate\nuntracked files in the stash entry will mask tracked files. We detect\nand error out in that case via a custom unpack_trees() callback:\nstash_worktree_untracked_merge().\n\nAlso, teach stash the --only-untracked option which only shows the\nuntracked files of a stash entry. This is similar to `git show stash^3`\nbut it is nice to provide a convenient abstraction for it so that users\ndo not have to think about the underlying implementation.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/git-stash.txt            |  18 +++--\n builtin/stash.c                        |  54 ++++++++++++-\n contrib/completion/git-completion.bash |   2 +-\n t/t3905-stash-include-untracked.sh     | 106 +++++++++++++++++++++++++\n unpack-trees.c                         |  22 +++++\n unpack-trees.h                         |   2 +\n 6 files changed, 197 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt\nindex f1197d641b..8eeb60feb1 100644\n--- a/Documentation/git-stash.txt\n+++ b/Documentation/git-stash.txt\n@@ -9,7 +9,7 @@ SYNOPSIS\n --------\n [verse]\n 'git stash' list [<log-options>]\n-'git stash' show [<diff-options>] [<stash>]\n+'git stash' show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]\n 'git stash' drop [-q|--quiet] [<stash>]\n 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]\n 'git stash' branch <branchname> [<stash>]\n@@ -83,7 +83,7 @@ stash@{1}: On master: 9cc0589... Add git-stash\n The command takes options applicable to the 'git log'\n command to control what is shown and how. See linkgit:git-log[1].\n \n-show [<diff-options>] [<stash>]::\n+show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]::\n \n \tShow the changes recorded in the stash entry as a diff between the\n \tstashed contents and the commit back when the stash entry was first\n@@ -160,10 +160,18 @@ up with `git clean`.\n \n -u::\n --include-untracked::\n-\tThis option is only valid for `push` and `save` commands.\n+--no-include-untracked::\n+\tWhen used with the `push` and `save` commands,\n+\tall untracked files are also stashed and then cleaned up with\n+\t`git clean`.\n +\n-All untracked files are also stashed and then cleaned up with\n-`git clean`.\n+When used with the `show` command, show the untracked files in the stash\n+entry as part of the diff.\n+\n+--only-untracked::\n+\tThis option is only valid for the `show` command.\n++\n+Show only the untracked files in the stash entry as part of the diff.\n \n --index::\n \tThis option is only valid for `pop` and `apply` commands.\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 6f2b58f6ab..9b7a541cd0 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -787,6 +787,33 @@ static int git_stash_config(const char *var, const char *value, void *cb)\n \treturn git_diff_basic_config(var, value, cb);\n }\n \n+static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt)\n+{\n+\tconst struct object_id *oid[] = { &info->w_commit, &info->u_tree };\n+\tstruct tree *tree[ARRAY_SIZE(oid)];\n+\tstruct tree_desc tree_desc[ARRAY_SIZE(oid)];\n+\tstruct unpack_trees_options unpack_tree_opt = { 0 };\n+\tint i;\n+\n+\tfor (i = 0; i < ARRAY_SIZE(oid); i++) {\n+\t\ttree[i] = parse_tree_indirect(oid[i]);\n+\t\tif (parse_tree(tree[i]) < 0)\n+\t\t\tdie(_(\"failed to parse tree\"));\n+\t\tinit_tree_desc(&tree_desc[i], tree[i]->buffer, tree[i]->size);\n+\t}\n+\n+\tunpack_tree_opt.head_idx = -1;\n+\tunpack_tree_opt.src_index = &the_index;\n+\tunpack_tree_opt.dst_index = &the_index;\n+\tunpack_tree_opt.merge = 1;\n+\tunpack_tree_opt.fn = stash_worktree_untracked_merge;\n+\n+\tif (unpack_trees(ARRAY_SIZE(tree_desc), tree_desc, &unpack_tree_opt))\n+\t\tdie(_(\"failed to unpack trees\"));\n+\n+\tdo_diff_cache(&info->b_commit, diff_opt);\n+}\n+\n static int show_stash(int argc, const char **argv, const char *prefix)\n {\n \tint i;\n@@ -795,7 +822,18 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \tstruct rev_info rev;\n \tstruct strvec stash_args = STRVEC_INIT;\n \tstruct strvec revision_args = STRVEC_INIT;\n+\tenum {\n+\t\tUNTRACKED_NONE,\n+\t\tUNTRACKED_INCLUDE,\n+\t\tUNTRACKED_ONLY\n+\t} show_untracked = UNTRACKED_NONE;\n \tstruct option options[] = {\n+\t\tOPT_SET_INT('u', \"include-untracked\", &show_untracked,\n+\t\t\t    N_(\"include untracked files in the stash\"),\n+\t\t\t    UNTRACKED_INCLUDE),\n+\t\tOPT_SET_INT_F(0, \"only-untracked\", &show_untracked,\n+\t\t\t      N_(\"only show untracked files in the stash\"),\n+\t\t\t      UNTRACKED_ONLY, PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \n@@ -803,6 +841,10 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \tgit_config(git_diff_ui_config, NULL);\n \tinit_revisions(&rev, prefix);\n \n+\targc = parse_options(argc, argv, prefix, options, git_stash_show_usage,\n+\t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n \tstrvec_push(&revision_args, argv[0]);\n \tfor (i = 1; i < argc; i++) {\n \t\tif (argv[i][0] != '-')\n@@ -845,7 +887,17 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \n \trev.diffopt.flags.recursive = 1;\n \tsetup_diff_pager(&rev.diffopt);\n-\tdiff_tree_oid(&info.b_commit, &info.w_commit, \"\", &rev.diffopt);\n+\tswitch (show_untracked) {\n+\tcase UNTRACKED_NONE:\n+\t\tdiff_tree_oid(&info.b_commit, &info.w_commit, \"\", &rev.diffopt);\n+\t\tbreak;\n+\tcase UNTRACKED_ONLY:\n+\t\tdiff_root_tree_oid(&info.u_tree, \"\", &rev.diffopt);\n+\t\tbreak;\n+\tcase UNTRACKED_INCLUDE:\n+\t\tdiff_include_untracked(&info, &rev.diffopt);\n+\t\tbreak;\n+\t}\n \tlog_tree_diff_flush(&rev);\n \n \tfree_stash_info(&info);\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 4b1f4264a6..64ef6ffa21 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -3051,7 +3051,7 @@ _git_stash ()\n \t\t\t__gitcomp \"--name-status --oneline --patch-with-stat\"\n \t\t\t;;\n \t\tshow,--*)\n-\t\t\t__gitcomp \"$__git_diff_common_options\"\n+\t\t\t__gitcomp \"--include-untracked --only-untracked $__git_diff_common_options\"\n \t\t\t;;\n \t\tbranch,--*)\n \t\t\t;;\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex b26a97aef4..08ceef6411 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -297,4 +297,110 @@ test_expect_success 'stash -u with globs' '\n \ttest_path_is_missing untracked.txt\n '\n \n+test_expect_success 'stash show --include-untracked shows untracked files' '\n+\tgit reset --hard &&\n+\tgit clean -xf &&\n+\t>untracked &&\n+\t>tracked &&\n+\tgit add tracked &&\n+\tempty_blob_oid=$(git rev-parse --short :tracked) &&\n+\tgit stash -u &&\n+\n+\tcat >expect <<-EOF &&\n+\t tracked   | 0\n+\t untracked | 0\n+\t 2 files changed, 0 insertions(+), 0 deletions(-)\n+\tEOF\n+\tgit stash show --include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show -u >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --no-include-untracked --include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --only-untracked --include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tcat >expect <<-EOF &&\n+\tdiff --git a/tracked b/tracked\n+\tnew file mode 100644\n+\tindex 0000000..$empty_blob_oid\n+\tdiff --git a/untracked b/untracked\n+\tnew file mode 100644\n+\tindex 0000000..$empty_blob_oid\n+\tEOF\n+\tgit stash show -p --include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --include-untracked -p >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stash show --only-untracked only shows untracked files' '\n+\tgit reset --hard &&\n+\tgit clean -xf &&\n+\t>untracked &&\n+\t>tracked &&\n+\tgit add tracked &&\n+\tempty_blob_oid=$(git rev-parse --short :tracked) &&\n+\tgit stash -u &&\n+\n+\tcat >expect <<-EOF &&\n+\t untracked | 0\n+\t 1 file changed, 0 insertions(+), 0 deletions(-)\n+\tEOF\n+\tgit stash show --only-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --no-include-untracked --only-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --include-untracked --only-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tcat >expect <<-EOF &&\n+\tdiff --git a/untracked b/untracked\n+\tnew file mode 100644\n+\tindex 0000000..$empty_blob_oid\n+\tEOF\n+\tgit stash show -p --only-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --only-untracked -p >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stash show --no-include-untracked cancels --{include,show}-untracked' '\n+\tgit reset --hard &&\n+\tgit clean -xf &&\n+\t>untracked &&\n+\t>tracked &&\n+\tgit add tracked &&\n+\tgit stash -u &&\n+\n+\tcat >expect <<-EOF &&\n+\t tracked | 0\n+\t 1 file changed, 0 insertions(+), 0 deletions(-)\n+\tEOF\n+\tgit stash show --only-untracked --no-include-untracked >actual &&\n+\ttest_cmp expect actual &&\n+\tgit stash show --include-untracked --no-include-untracked >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stash show --include-untracked errors on duplicate files' '\n+\tgit reset --hard &&\n+\tgit clean -xf &&\n+\t>tracked &&\n+\tgit add tracked &&\n+\ttree=$(git write-tree) &&\n+\ti_commit=$(git commit-tree -p HEAD -m \"index on any-branch\" \"$tree\") &&\n+\ttest_when_finished \"rm -f untracked_index\" &&\n+\tu_commit=$(\n+\t\tGIT_INDEX_FILE=\"untracked_index\" &&\n+\t\texport GIT_INDEX_FILE &&\n+\t\tgit update-index --add tracked &&\n+\t\tu_tree=$(git write-tree) &&\n+\t\tgit commit-tree -m \"untracked files on any-branch\" \"$u_tree\"\n+\t) &&\n+\tw_commit=$(git commit-tree -p HEAD -p \"$i_commit\" -p \"$u_commit\" -m \"WIP on any-branch\" \"$tree\") &&\n+\ttest_must_fail git stash show --include-untracked \"$w_commit\" 2>err &&\n+\ttest_i18ngrep \"worktree and untracked commit have duplicate entries: tracked\" err\n+'\n+\n test_done\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex af6e9b9c2f..dde8c320c5 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -2566,3 +2566,25 @@ int oneway_merge(const struct cache_entry * const *src,\n \t}\n \treturn merged_entry(a, old, o);\n }\n+\n+/*\n+ * Merge worktree and untracked entries in a stash entry.\n+ *\n+ * Ignore all index entries. Collapse remaining trees but make sure that they\n+ * don't have any conflicting files.\n+ */\n+int stash_worktree_untracked_merge(const struct cache_entry * const *src,\n+\t\t\t\t   struct unpack_trees_options *o)\n+{\n+\tconst struct cache_entry *worktree = src[1];\n+\tconst struct cache_entry *untracked = src[2];\n+\n+\tif (o->merge_size != 2)\n+\t\tBUG(\"invalid merge_size: %d\", o->merge_size);\n+\n+\tif (worktree && untracked)\n+\t\treturn error(_(\"worktree and untracked commit have duplicate entries: %s\"),\n+\t\t\t     super_prefixed(worktree->name));\n+\n+\treturn merged_entry(worktree ? worktree : untracked, NULL, o);\n+}\ndiff --git a/unpack-trees.h b/unpack-trees.h\nindex 2e87875b15..2d88b19dca 100644\n--- a/unpack-trees.h\n+++ b/unpack-trees.h\n@@ -114,5 +114,7 @@ int bind_merge(const struct cache_entry * const *src,\n \t       struct unpack_trees_options *o);\n int oneway_merge(const struct cache_entry * const *src,\n \t\t struct unpack_trees_options *o);\n+int stash_worktree_untracked_merge(const struct cache_entry * const *src,\n+\t\t\t\t   struct unpack_trees_options *o);\n \n #endif\n-- \n2.31.0.rc1.228.gb75b4e4ce2\n\n"},{"id":"418142","messageId":"cover.1614770171.git.liu.denton@gmail.com","threadId":"55091","inReplyTo":"cover.1613459474.git.liu.denton@gmail.com","subject":"[PATCH v4 0/2] stash show: learn --include-untracked and --only-untracked","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-03-03T11:16:41Z","receivedAt":"2021-03-04T00:22:55Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"A blindspot that I've noticed in git is that it's not possible to\nproperly view a stash entry that has untracked files via `git stash\nshow`. Teach `git stash show --include-untracked` which should do this.\nIn addition, this series also teaches `--only-untracked` and the\n`stash.showIncludeUntracked` config option.\n\nThis series is based on 'dl/stash-cleanup'.\n\nChanges since v3:\n\n* Incorporate Junio's SQUASH??? commits\n\n* Implement a custom unpack_trees() callback to detect the case where\n  there are duplicate entries in worktree and untracked commits\n\nChanges since v2:\n\n* Base this series on top of 'dl/stash-cleanup'\n\n* Attempt to replicate the read-tree code to merge the untracked tree\n\nChanges since v1:\n\n* Add a dash for <log-options> and <diff-options>\n\n* Fix the misspelling of --include-untracked in a commit message\n\n* Change the approach from concatenating diffs to using `git read-tree`\n\nDenton Liu (2):\n  stash show: teach --include-untracked and --only-untracked\n  stash show: learn stash.showIncludeUntracked\n\n Documentation/config/stash.txt         |   5 ++\n Documentation/git-stash.txt            |  22 +++--\n builtin/stash.c                        |  62 +++++++++++++-\n contrib/completion/git-completion.bash |   2 +-\n t/t3905-stash-include-untracked.sh     | 108 +++++++++++++++++++++++++\n unpack-trees.c                         |  22 +++++\n unpack-trees.h                         |   2 +\n 7 files changed, 214 insertions(+), 9 deletions(-)\n\nRange-diff against v3:\n1:  85b81f2f06 ! 1:  af3757135b stash show: teach --include-untracked and --only-untracked\n    @@ Commit message\n         third-parent if it exists, are shown in addition to the paths that have\n         modifications between the stash base and the working tree in the stash.\n     \n    -    One limitation of this is that it would be possible to manually craft a\n    -    stash entry where duplicate untracked files in the stash entry will mask\n    -    tracked files. This seems like an instance of \"Doctor, it hurts when I\n    -    do this! So don't do that!\" so this can be written off.\n    +    It is possible to manually craft a malformed stash entry where duplicate\n    +    untracked files in the stash entry will mask tracked files. We detect\n    +    and error out in that case via a custom unpack_trees() callback:\n    +    stash_worktree_untracked_merge().\n     \n         Also, teach stash the --only-untracked option which only shows the\n         untracked files of a stash entry. This is similar to `git show stash^3`\n         but it is nice to provide a convenient abstraction for it so that users\n         do not have to think about the underlying implementation.\n     \n    -\n    - ## Notes ##\n    -    I am not familiar with the read-tree code so this attempt at replicating\n    -    the read-tree code may in diff_include_untracked() may be incorrect\n    -    (particularly the use of the_index?).\n    -\n    -    Also, I could not figure out how to make unpack_trees() error out in the\n    -    case where untracked tree entry contains duplicate entries with the\n    -    worktree entry.\n    -\n      ## Documentation/git-stash.txt ##\n     @@ Documentation/git-stash.txt: SYNOPSIS\n      --------\n    @@ builtin/stash.c: static int git_stash_config(const char *var, const char *value,\n     +\tunpack_tree_opt.head_idx = -1;\n     +\tunpack_tree_opt.src_index = &the_index;\n     +\tunpack_tree_opt.dst_index = &the_index;\n    -+\tunpack_tree_opt.fn = twoway_merge;\n    ++\tunpack_tree_opt.merge = 1;\n    ++\tunpack_tree_opt.fn = stash_worktree_untracked_merge;\n     +\n     +\tif (unpack_trees(ARRAY_SIZE(tree_desc), tree_desc, &unpack_tree_opt))\n     +\t\tdie(_(\"failed to unpack trees\"));\n    @@ t/t3905-stash-include-untracked.sh: test_expect_success 'stash -u with globs' '\n     +\t>untracked &&\n     +\t>tracked &&\n     +\tgit add tracked &&\n    ++\tempty_blob_oid=$(git rev-parse --short :tracked) &&\n     +\tgit stash -u &&\n     +\n     +\tcat >expect <<-EOF &&\n    @@ t/t3905-stash-include-untracked.sh: test_expect_success 'stash -u with globs' '\n     +\tcat >expect <<-EOF &&\n     +\tdiff --git a/tracked b/tracked\n     +\tnew file mode 100644\n    -+\tindex 0000000..e69de29\n    ++\tindex 0000000..$empty_blob_oid\n     +\tdiff --git a/untracked b/untracked\n     +\tnew file mode 100644\n    -+\tindex 0000000..e69de29\n    ++\tindex 0000000..$empty_blob_oid\n     +\tEOF\n     +\tgit stash show -p --include-untracked >actual &&\n     +\ttest_cmp expect actual &&\n    @@ t/t3905-stash-include-untracked.sh: test_expect_success 'stash -u with globs' '\n     +\t>untracked &&\n     +\t>tracked &&\n     +\tgit add tracked &&\n    ++\tempty_blob_oid=$(git rev-parse --short :tracked) &&\n     +\tgit stash -u &&\n     +\n     +\tcat >expect <<-EOF &&\n    @@ t/t3905-stash-include-untracked.sh: test_expect_success 'stash -u with globs' '\n     +\tcat >expect <<-EOF &&\n     +\tdiff --git a/untracked b/untracked\n     +\tnew file mode 100644\n    -+\tindex 0000000..e69de29\n    ++\tindex 0000000..$empty_blob_oid\n     +\tEOF\n     +\tgit stash show -p --only-untracked >actual &&\n     +\ttest_cmp expect actual &&\n    @@ t/t3905-stash-include-untracked.sh: test_expect_success 'stash -u with globs' '\n     +\tgit stash show --include-untracked --no-include-untracked >actual &&\n     +\ttest_cmp expect actual\n     +'\n    ++\n    ++test_expect_success 'stash show --include-untracked errors on duplicate files' '\n    ++\tgit reset --hard &&\n    ++\tgit clean -xf &&\n    ++\t>tracked &&\n    ++\tgit add tracked &&\n    ++\ttree=$(git write-tree) &&\n    ++\ti_commit=$(git commit-tree -p HEAD -m \"index on any-branch\" \"$tree\") &&\n    ++\ttest_when_finished \"rm -f untracked_index\" &&\n    ++\tu_commit=$(\n    ++\t\tGIT_INDEX_FILE=\"untracked_index\" &&\n    ++\t\texport GIT_INDEX_FILE &&\n    ++\t\tgit update-index --add tracked &&\n    ++\t\tu_tree=$(git write-tree) &&\n    ++\t\tgit commit-tree -m \"untracked files on any-branch\" \"$u_tree\"\n    ++\t) &&\n    ++\tw_commit=$(git commit-tree -p HEAD -p \"$i_commit\" -p \"$u_commit\" -m \"WIP on any-branch\" \"$tree\") &&\n    ++\ttest_must_fail git stash show --include-untracked \"$w_commit\" 2>err &&\n    ++\ttest_i18ngrep \"worktree and untracked commit have duplicate entries: tracked\" err\n    ++'\n     +\n      test_done\n    +\n    + ## unpack-trees.c ##\n    +@@ unpack-trees.c: int oneway_merge(const struct cache_entry * const *src,\n    + \t}\n    + \treturn merged_entry(a, old, o);\n    + }\n    ++\n    ++/*\n    ++ * Merge worktree and untracked entries in a stash entry.\n    ++ *\n    ++ * Ignore all index entries. Collapse remaining trees but make sure that they\n    ++ * don't have any conflicting files.\n    ++ */\n    ++int stash_worktree_untracked_merge(const struct cache_entry * const *src,\n    ++\t\t\t\t   struct unpack_trees_options *o)\n    ++{\n    ++\tconst struct cache_entry *worktree = src[1];\n    ++\tconst struct cache_entry *untracked = src[2];\n    ++\n    ++\tif (o->merge_size != 2)\n    ++\t\tBUG(\"invalid merge_size: %d\", o->merge_size);\n    ++\n    ++\tif (worktree && untracked)\n    ++\t\treturn error(_(\"worktree and untracked commit have duplicate entries: %s\"),\n    ++\t\t\t     super_prefixed(worktree->name));\n    ++\n    ++\treturn merged_entry(worktree ? worktree : untracked, NULL, o);\n    ++}\n    +\n    + ## unpack-trees.h ##\n    +@@ unpack-trees.h: int bind_merge(const struct cache_entry * const *src,\n    + \t       struct unpack_trees_options *o);\n    + int oneway_merge(const struct cache_entry * const *src,\n    + \t\t struct unpack_trees_options *o);\n    ++int stash_worktree_untracked_merge(const struct cache_entry * const *src,\n    ++\t\t\t\t   struct unpack_trees_options *o);\n    + \n    + #endif\n2:  d19d07ec27 = 2:  3480086f1d stash show: learn stash.showIncludeUntracked\n-- \n2.31.0.rc1.228.gb75b4e4ce2\n\n"},{"id":"418193","messageId":"xmqq4khrhhx4.fsf@gitster.c.googlers.com","threadId":"55091","inReplyTo":"cover.1614770171.git.liu.denton@gmail.com","subject":"Re: [PATCH v4 0/2] stash show: learn --include-untracked and --only-untracked","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-04T00:38:31Z","receivedAt":"2021-03-04T01:09:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> A blindspot that I've noticed in git is that it's not possible to\n> properly view a stash entry that has untracked files via `git stash\n> show`. Teach `git stash show --include-untracked` which should do this.\n> In addition, this series also teaches `--only-untracked` and the\n> `stash.showIncludeUntracked` config option.\n>\n> This series is based on 'dl/stash-cleanup'.\n>\n> Changes since v3:\n>\n> * Incorporate Junio's SQUASH??? commits\n>\n> * Implement a custom unpack_trees() callback to detect the case where\n>   there are duplicate entries in worktree and untracked commits\n\nI actually expected the latter enhancement to be done outside the\nscope of this series.  I briefly looked at the callback but I am not\nconvinced that it is correct (e.g. how do you notice and barf when\nthe untracked tree records foo/bar.txt and the index or the working\ntree records foo as a file?).\n\nThanks.\n"},{"id":"418203","messageId":"YEA43VRbgdA36MV1@generichostname","threadId":"55091","inReplyTo":"xmqq4khrhhx4.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v4 0/2] stash show: learn --include-untracked and --only-untracked","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-03-04T01:33:17Z","receivedAt":"2021-03-04T01:35:45Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi Junio,\n\nOn Wed, Mar 03, 2021 at 04:38:31PM -0800, Junio C Hamano wrote:\n> Denton Liu <liu.denton@gmail.com> writes:\n> \n> > A blindspot that I've noticed in git is that it's not possible to\n> > properly view a stash entry that has untracked files via `git stash\n> > show`. Teach `git stash show --include-untracked` which should do this.\n> > In addition, this series also teaches `--only-untracked` and the\n> > `stash.showIncludeUntracked` config option.\n> >\n> > This series is based on 'dl/stash-cleanup'.\n> >\n> > Changes since v3:\n> >\n> > * Incorporate Junio's SQUASH??? commits\n> >\n> > * Implement a custom unpack_trees() callback to detect the case where\n> >   there are duplicate entries in worktree and untracked commits\n> \n> I actually expected the latter enhancement to be done outside the\n> scope of this series.  I briefly looked at the callback but I am not\n> convinced that it is correct (e.g. how do you notice and barf when\n> the untracked tree records foo/bar.txt and the index or the working\n> tree records foo as a file?).\n\nFrom my testing, the conflict is detected just fine. The following\ntest-case should confirm it:\n\n-- >8 --\nFrom: Denton Liu <liu.denton@gmail.com>\nSubject: [PATCH] fixup! stash show: teach --include-untracked and --only-untracked\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3905-stash-include-untracked.sh | 23 +++++++++++++++++++++++\n 1 file changed, 23 insertions(+)\n\ndiff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\nindex b470db7ef7..470aa65b44 100755\n--- a/t/t3905-stash-include-untracked.sh\n+++ b/t/t3905-stash-include-untracked.sh\n@@ -405,4 +405,27 @@ test_expect_success 'stash show --include-untracked errors on duplicate files' '\n \ttest_i18ngrep \"worktree and untracked commit have duplicate entries: tracked\" err\n '\n \n+test_expect_success 'stash show --include-untracked errors on directory/file conflict' '\n+\tgit reset --hard &&\n+\tgit clean -xf &&\n+\t>tracked &&\n+\tgit add tracked &&\n+\ttree=$(git write-tree) &&\n+\ti_commit=$(git commit-tree -p HEAD -m \"index on any-branch\" \"$tree\") &&\n+\ttest_when_finished \"rm -f untracked_index\" &&\n+\tu_commit=$(\n+\t\tGIT_INDEX_FILE=\"untracked_index\" &&\n+\t\texport GIT_INDEX_FILE &&\n+\t\trm tracked &&\n+\t\tmkdir tracked &&\n+\t\t>tracked/file &&\n+\t\tgit update-index --add tracked/file &&\n+\t\tu_tree=$(git write-tree) &&\n+\t\tgit commit-tree -m \"untracked files on any-branch\" \"$u_tree\"\n+\t) &&\n+\tw_commit=$(git commit-tree -p HEAD -p \"$i_commit\" -p \"$u_commit\" -m \"WIP on any-branch\" \"$tree\") &&\n+\ttest_must_fail git stash show --include-untracked \"$w_commit\" 2>err &&\n+\ttest_i18ngrep \"worktree and untracked commit have duplicate entries: tracked\" err\n+'\n+\n test_done\n-- \n2.31.0.rc1.228.gb75b4e4ce2\n\n"},{"id":"418204","messageId":"YEA7B2TKb6hgizZ4@generichostname","threadId":"55091","inReplyTo":"YEA43VRbgdA36MV1@generichostname","subject":"Re: [PATCH v4 0/2] stash show: learn --include-untracked and --only-untracked","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2021-03-04T01:42:31Z","receivedAt":"2021-03-04T01:43:46Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"On Wed, Mar 03, 2021 at 05:33:17PM -0800, Denton Liu wrote:\n> Hi Junio,\n> \n> On Wed, Mar 03, 2021 at 04:38:31PM -0800, Junio C Hamano wrote:\n> > Denton Liu <liu.denton@gmail.com> writes:\n> > \n> > > A blindspot that I've noticed in git is that it's not possible to\n> > > properly view a stash entry that has untracked files via `git stash\n> > > show`. Teach `git stash show --include-untracked` which should do this.\n> > > In addition, this series also teaches `--only-untracked` and the\n> > > `stash.showIncludeUntracked` config option.\n> > >\n> > > This series is based on 'dl/stash-cleanup'.\n> > >\n> > > Changes since v3:\n> > >\n> > > * Incorporate Junio's SQUASH??? commits\n> > >\n> > > * Implement a custom unpack_trees() callback to detect the case where\n> > >   there are duplicate entries in worktree and untracked commits\n> > \n> > I actually expected the latter enhancement to be done outside the\n> > scope of this series.\n\nI decided to squash it into the current series because we have a lot of\ntime until it will be merged (since it's definitely not going in now\nduring the rc-period). If you'd like, though, I can reroll this series\nwith the callback as a commit on top.\n\n> > I briefly looked at the callback but I am not\n> > convinced that it is correct (e.g. how do you notice and barf when\n> > the untracked tree records foo/bar.txt and the index or the working\n> > tree records foo as a file?).\n> \n> From my testing, the conflict is detected just fine. The following\n> test-case should confirm it:\n> \n> -- >8 --\n> From: Denton Liu <liu.denton@gmail.com>\n> Subject: [PATCH] fixup! stash show: teach --include-untracked and --only-untracked\n> \n> Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> ---\n>  t/t3905-stash-include-untracked.sh | 23 +++++++++++++++++++++++\n>  1 file changed, 23 insertions(+)\n> \n> diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh\n> index b470db7ef7..470aa65b44 100755\n> --- a/t/t3905-stash-include-untracked.sh\n> +++ b/t/t3905-stash-include-untracked.sh\n> @@ -405,4 +405,27 @@ test_expect_success 'stash show --include-untracked errors on duplicate files' '\n>  \ttest_i18ngrep \"worktree and untracked commit have duplicate entries: tracked\" err\n>  '\n>  \n> +test_expect_success 'stash show --include-untracked errors on directory/file conflict' '\n> +\tgit reset --hard &&\n> +\tgit clean -xf &&\n> +\t>tracked &&\n> +\tgit add tracked &&\n> +\ttree=$(git write-tree) &&\n> +\ti_commit=$(git commit-tree -p HEAD -m \"index on any-branch\" \"$tree\") &&\n> +\ttest_when_finished \"rm -f untracked_index\" &&\n> +\tu_commit=$(\n> +\t\tGIT_INDEX_FILE=\"untracked_index\" &&\n> +\t\texport GIT_INDEX_FILE &&\n> +\t\trm tracked &&\n> +\t\tmkdir tracked &&\n> +\t\t>tracked/file &&\n> +\t\tgit update-index --add tracked/file &&\n> +\t\tu_tree=$(git write-tree) &&\n> +\t\tgit commit-tree -m \"untracked files on any-branch\" \"$u_tree\"\n> +\t) &&\n> +\tw_commit=$(git commit-tree -p HEAD -p \"$i_commit\" -p \"$u_commit\" -m \"WIP on any-branch\" \"$tree\") &&\n> +\ttest_must_fail git stash show --include-untracked \"$w_commit\" 2>err &&\n> +\ttest_i18ngrep \"worktree and untracked commit have duplicate entries: tracked\" err\n> +'\n> +\n>  test_done\n> -- \n> 2.31.0.rc1.228.gb75b4e4ce2\n> \n"}]}