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. This series is based on 'dl/stash-cleanup'. Changes since v2: * Base this series on top of 'dl/stash-cleanup' * Attempt to replicate the read-tree code to merge the untracked tree 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 (2): 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 | 61 +++++++++++++++++- contrib/completion/git-completion.bash | 2 +- t/t3905-stash-include-untracked.sh | 86 ++++++++++++++++++++++++++ 5 files changed, 167 insertions(+), 9 deletions(-) Range-diff against v2: 1: 88d4791259 ! 1: 85b81f2f06 stash show: teach --include-untracked and --only-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-untracked option, which also displays the - untracked files in a stash entry from the third parent (if it exists). - Do this via something like + With --include-untracked, untracked paths, which are recorded in the + third-parent if it exists, are shown in addition to the paths that have + modifications between the stash base and the working tree in the stash. - 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. + One 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` but it is nice to provide a convenient abstraction for it so that users do not have to think about the underlying implementation. + + ## Notes ## + I am not familiar with the read-tree code so this attempt at replicating + the read-tree code may in diff_include_untracked() may be incorrect + (particularly the use of the_index?). + + Also, I could not figure out how to make unpack_trees() error out in the + case where untracked tree entry contains duplicate entries with the + worktree entry. + ## Documentation/git-stash.txt ## +@@ Documentation/git-stash.txt: SYNOPSIS + -------- + [verse] + 'git stash' list [<log-options>] +-'git stash' show [<diff-options>] [<stash>] ++'git stash' show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>] + 'git stash' drop [-q|--quiet] [<stash>] + 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>] + 'git stash' branch <branchname> [<stash>] @@ 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]. @@ builtin/stash.c: static int git_stash_config(const char *var, const char *value, return git_diff_basic_config(var, value, cb); } -+static int merge_track_untracked(struct object_id *result, const struct stash_info *info) ++static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt) +{ -+ int ret = 0; -+ struct index_state istate = { NULL }; -+ struct child_process cp_read_tree = CHILD_PROCESS_INIT; ++ const struct object_id *oid[] = { &info->w_commit, &info->u_tree }; ++ struct tree *tree[ARRAY_SIZE(oid)]; ++ struct tree_desc tree_desc[ARRAY_SIZE(oid)]; ++ struct unpack_trees_options unpack_tree_opt = { 0 }; ++ int i; + -+ if (!info->has_u) { -+ oidcpy(result, &info->w_commit); -+ return 0; ++ for (i = 0; i < ARRAY_SIZE(oid); i++) { ++ tree[i] = parse_tree_indirect(oid[i]); ++ if (parse_tree(tree[i]) < 0) ++ die(_("failed to parse tree")); ++ init_tree_desc(&tree_desc[i], tree[i]->buffer, tree[i]->size); + } + -+ /* -+ * TODO: is there a way of doing this all in-memory without writing -+ * anything to disk? -+ */ -+ remove_path(stash_index_path.buf); ++ unpack_tree_opt.head_idx = -1; ++ unpack_tree_opt.src_index = &the_index; ++ unpack_tree_opt.dst_index = &the_index; ++ unpack_tree_opt.fn = twoway_merge; + -+ 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 (unpack_trees(ARRAY_SIZE(tree_desc), tree_desc, &unpack_tree_opt)) ++ die(_("failed to unpack trees")); + -+ 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; ++ do_diff_cache(&info->b_commit, diff_opt); +} + static int show_stash(int argc, const char **argv, const char *prefix) @@ builtin/stash.c: static int show_stash(int argc, const char **argv, const char * 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 * - diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt); + switch (show_untracked) { + case UNTRACKED_NONE: -+ before = &info.b_commit; -+ after = &info.w_commit; ++ diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt); + break; + case UNTRACKED_ONLY: -+ before = NULL; -+ after = &info.u_tree; ++ diff_root_tree_oid(&info.u_tree, "", &rev.diffopt); + 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; ++ diff_include_untracked(&info, &rev.diffopt); + break; + } -+ diff_tree_oid(before, after, "", &rev.diffopt); log_tree_diff_flush(&rev); free_stash_info(&info); 2: ac4019f47e = 2: d19d07ec27 stash show: learn stash.showIncludeUntracked -- 2.30.0.478.g8a0d178c01