A blindspot that I've noticed in git is that it's not possible to properly view a stash entry that has untracked files via `git stash show`. Teach `git stash show --include-untracked` which should do this. In addition, this series also teaches `--only-untracked` and the `stash.showIncludeUntracked` config option. The first seven patches of this series are just some clean up that I've done prior to working (because it bothers me). The remaining two patches should be the meat of the change. Changes since v1: * Add a dash for <log-options> and <diff-options> * Fix the misspelling of --include-untracked in a commit message * Change the approach from concatenating diffs to using `git read-tree` Denton Liu (9): git-stash.txt: be explicit about subcommand options t3905: remove spaces after redirect operators t3905: move all commands into test cases t3905: remove nested git in command substitution t3905: replace test -s with test_file_not_empty t3905: use test_cmp() to check file contents stash: declare ref_stash as an array stash show: teach --include-untracked and --only-untracked stash show: learn stash.showIncludeUntracked Documentation/config/stash.txt | 5 + Documentation/git-stash.txt | 22 +- builtin/stash.c | 87 +++++++- contrib/completion/git-completion.bash | 2 +- t/t3905-stash-include-untracked.sh | 278 +++++++++++++++++-------- 5 files changed, 292 insertions(+), 102 deletions(-) Range-diff against v1: 1: 17675b9e4c ! 1: 5697f14f1c git-stash.txt: be explicit about subcommand options @@ Documentation/git-stash.txt: save [-p|--patch] [-k|--[no-]keep-index] [-u|--incl message. -list [<options>]:: -+list [<log options>]:: ++list [<log-options>]:: List the stash entries that you currently have. Each 'stash entry' is listed with its name (e.g. `stash@{0}` is the latest entry, `stash@{1}` is @@ Documentation/git-stash.txt: stash@{1}: On master: 9cc0589... Add git-stash command to control what is shown and how. See linkgit:git-log[1]. -show [<options>] [<stash>]:: -+show [<diff options>] [<stash>]:: ++show [<diff-options>] [<stash>]:: Show the changes recorded in the stash entry as a diff between the stashed contents and the commit back when the stash entry was first 2: 0de324e3bc = 2: 45ed17bfe2 t3905: remove spaces after redirect operators 3: 519840b1a2 = 3: 5bda09b4bd t3905: move all commands into test cases 4: 4b72d39e01 = 4: 57c21e2461 t3905: remove nested git in command substitution 5: 7fe27ab620 = 5: 2530883b6c t3905: replace test -s with test_file_not_empty 6: 4a5dd83ff4 = 6: 80194bcfa5 t3905: use test_cmp() to check file contents 7: b5f22de3fc = 7: 2f03d38b36 stash: declare ref_stash as an array 8: c2375d1fc6 ! 8: 88d4791259 stash show: teach --include-tracked and --only-untracked @@ Metadata Author: Denton Liu <liu.denton@xxxxxxxxx> ## Commit message ## - stash show: teach --include-tracked and --only-untracked + stash show: teach --include-untracked and --only-untracked Stash entries can be made with untracked files via `git stash push --include-untracked`. However, because the untracked @@ Commit message stash entry itself, running `git stash show` does not include the untracked files as part of the diff. - Teach stash the --include-tracked option, which also displays the + Teach stash the --include-untracked option, which also displays the untracked files in a stash entry from the third parent (if it exists). - Do this by just concatenating the diff of the third parent against an - empty tree. One limitation of this is that it would be possible to - manually craft a stash entry which would present duplicate entries in - the diff by duplicating a file in the stash and in the third parent. - This seems like an instance of "Doctor, it hurts when I do this! So - don't do that!" so this can be written off. + Do this via something like + + GIT_INDEX_FILE=... git read-tree stash stash^3 + + and diffing the resulting tree object against the stash base. + + One improvement that this could use for the future is performing the + action without writing anything to disk as one would expect this to be a + read-only operation. This can be fixed in the future, however. + + Another limitation of this is that it would be possible to manually + craft a stash entry where duplicate untracked files in the stash entry + will mask tracked files. This seems like an instance of "Doctor, it + hurts when I do this! So don't do that!" so this can be written off. Also, teach stash the --only-untracked option which only shows the untracked files of a stash entry. This is similar to `git show stash^3` @@ Documentation/git-stash.txt: stash@{1}: On master: 9cc0589... Add git-stash The command takes options applicable to the 'git log' command to control what is shown and how. See linkgit:git-log[1]. --show [<diff options>] [<stash>]:: -+show [-u|--include-untracked|--only-untracked] [<diff options>] [<stash>]:: +-show [<diff-options>] [<stash>]:: ++show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]:: Show the changes recorded in the stash entry as a diff between the stashed contents and the commit back when the stash entry was first @@ Documentation/git-stash.txt: up with `git clean`. This option is only valid for `pop` and `apply` commands. ## builtin/stash.c ## +@@ builtin/stash.c: static int git_stash_config(const char *var, const char *value, void *cb) + return git_diff_basic_config(var, value, cb); + } + ++static int merge_track_untracked(struct object_id *result, const struct stash_info *info) ++{ ++ int ret = 0; ++ struct index_state istate = { NULL }; ++ struct child_process cp_read_tree = CHILD_PROCESS_INIT; ++ ++ if (!info->has_u) { ++ oidcpy(result, &info->w_commit); ++ return 0; ++ } ++ ++ /* ++ * TODO: is there a way of doing this all in-memory without writing ++ * anything to disk? ++ */ ++ remove_path(stash_index_path.buf); ++ ++ cp_read_tree.git_cmd = 1; ++ strvec_push(&cp_read_tree.args, "read-tree"); ++ strvec_push(&cp_read_tree.args, oid_to_hex(&info->w_commit)); ++ strvec_push(&cp_read_tree.args, oid_to_hex(&info->u_tree)); ++ strvec_pushf(&cp_read_tree.env_array, "GIT_INDEX_FILE=%s", ++ stash_index_path.buf); ++ ++ if (run_command(&cp_read_tree)) { ++ ret = -1; ++ goto done; ++ } ++ ++ if (write_index_as_tree(result, &istate, stash_index_path.buf, 0, ++ NULL)) { ++ ret = -1; ++ goto done; ++ } ++ ++done: ++ discard_index(&istate); ++ remove_path(stash_index_path.buf); ++ return ret; ++} ++ + static int show_stash(int argc, const char **argv, const char *prefix) + { + int i; @@ builtin/stash.c: static int show_stash(int argc, const char **argv, const char *prefix) struct rev_info rev; struct strvec stash_args = STRVEC_INIT; struct strvec revision_args = STRVEC_INIT; ++ struct object_id *before = NULL; ++ struct object_id *after = NULL; ++ struct object_id untracked_merged_tree; + enum { + UNTRACKED_NONE, + UNTRACKED_INCLUDE, @@ builtin/stash.c: static int show_stash(int argc, const char **argv, const char * rev.diffopt.flags.recursive = 1; setup_diff_pager(&rev.diffopt); - diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt); -+ if (show_untracked != UNTRACKED_ONLY) -+ diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt); -+ if (show_untracked != UNTRACKED_NONE && info.has_u) -+ diff_root_tree_oid(&info.u_tree, "", &rev.diffopt); ++ switch (show_untracked) { ++ case UNTRACKED_NONE: ++ before = &info.b_commit; ++ after = &info.w_commit; ++ break; ++ case UNTRACKED_ONLY: ++ before = NULL; ++ after = &info.u_tree; ++ break; ++ case UNTRACKED_INCLUDE: ++ if (merge_track_untracked(&untracked_merged_tree, &info) < 0) ++ die(_("unable merge stash index with untracked files index")); ++ before = &info.b_commit; ++ after = &untracked_merged_tree; ++ break; ++ } ++ diff_tree_oid(before, after, "", &rev.diffopt); log_tree_diff_flush(&rev); free_stash_info(&info); 9: 2c5d5d9dd4 ! 9: ac4019f47e stash show: learn stash.showIncludeUntracked @@ Documentation/config/stash.txt: stash.useBuiltin:: option will show the stash entry in patch form. Defaults to false. ## Documentation/git-stash.txt ## -@@ Documentation/git-stash.txt: show [-u|--include-untracked|--only-untracked] [<diff options>] [<stash>]:: +@@ Documentation/git-stash.txt: show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]:: By default, the command shows the diffstat, but it will accept any format known to 'git diff' (e.g., `git stash show -p stash@{1}` to view the second most recent entry in patch form). -- 2.30.0.478.g8a0d178c01