From d3c7bf73bdb679dd98c6aff65edbce4df743ddd3 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Wed, 3 Mar 2021 03:16:42 -0800 Subject: 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 files are stored in the third parent of the stash entry and not the stash entry itself, running `git stash show` does not include the untracked files as part of the diff. 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. It is possible to manually craft a malformed stash entry where duplicate untracked files in the stash entry will mask tracked files. We detect and error out in that case via a custom unpack_trees() callback: stash_worktree_untracked_merge(). 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. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano --- builtin/stash.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) (limited to 'builtin') diff --git a/builtin/stash.c b/builtin/stash.c index 6f2b58f6ab..9b7a541cd0 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -787,6 +787,33 @@ static int git_stash_config(const char *var, const char *value, void *cb) return git_diff_basic_config(var, value, cb); } +static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt) +{ + 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; + + 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); + } + + unpack_tree_opt.head_idx = -1; + unpack_tree_opt.src_index = &the_index; + unpack_tree_opt.dst_index = &the_index; + unpack_tree_opt.merge = 1; + unpack_tree_opt.fn = stash_worktree_untracked_merge; + + if (unpack_trees(ARRAY_SIZE(tree_desc), tree_desc, &unpack_tree_opt)) + die(_("failed to unpack trees")); + + do_diff_cache(&info->b_commit, diff_opt); +} + static int show_stash(int argc, const char **argv, const char *prefix) { int i; @@ -795,7 +822,18 @@ 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; + enum { + UNTRACKED_NONE, + UNTRACKED_INCLUDE, + UNTRACKED_ONLY + } show_untracked = UNTRACKED_NONE; struct option options[] = { + OPT_SET_INT('u', "include-untracked", &show_untracked, + N_("include untracked files in the stash"), + UNTRACKED_INCLUDE), + OPT_SET_INT_F(0, "only-untracked", &show_untracked, + N_("only show untracked files in the stash"), + UNTRACKED_ONLY, PARSE_OPT_NONEG), OPT_END() }; @@ -803,6 +841,10 @@ static int show_stash(int argc, const char **argv, const char *prefix) git_config(git_diff_ui_config, NULL); init_revisions(&rev, prefix); + argc = parse_options(argc, argv, prefix, options, git_stash_show_usage, + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_DASHDASH); + strvec_push(&revision_args, argv[0]); for (i = 1; i < argc; i++) { if (argv[i][0] != '-') @@ -845,7 +887,17 @@ static int show_stash(int argc, const char **argv, const char *prefix) rev.diffopt.flags.recursive = 1; setup_diff_pager(&rev.diffopt); - diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt); + switch (show_untracked) { + case UNTRACKED_NONE: + diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt); + break; + case UNTRACKED_ONLY: + diff_root_tree_oid(&info.u_tree, "", &rev.diffopt); + break; + case UNTRACKED_INCLUDE: + diff_include_untracked(&info, &rev.diffopt); + break; + } log_tree_diff_flush(&rev); free_stash_info(&info); -- cgit v1.3-5-g9baa From 0af760e26191acd3c5957841461ff224b80b43f7 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Wed, 3 Mar 2021 03:16:43 -0800 Subject: stash show: learn stash.showIncludeUntracked The previous commit teaches `git stash show --include-untracked`. It may be desirable for a user to be able to always enable the --include-untracked behavior. Teach the stash.showIncludeUntracked config option which allows users to do this in a similar manner to stash.showPatch. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano --- Documentation/config/stash.txt | 5 +++++ Documentation/git-stash.txt | 4 ++-- builtin/stash.c | 8 ++++++++ t/t3905-stash-include-untracked.sh | 2 ++ 4 files changed, 17 insertions(+), 2 deletions(-) (limited to 'builtin') diff --git a/Documentation/config/stash.txt b/Documentation/config/stash.txt index 00eb35434e..413f907cba 100644 --- a/Documentation/config/stash.txt +++ b/Documentation/config/stash.txt @@ -5,6 +5,11 @@ stash.useBuiltin:: is always used. Setting this will emit a warning, to alert any remaining users that setting this now does nothing. +stash.showIncludeUntracked:: + If this is set to true, the `git stash show` command without an + option will show the untracked files of a stash entry. Defaults to + false. See description of 'show' command in linkgit:git-stash[1]. + stash.showPatch:: If this is set to true, the `git stash show` command without an option will show the stash entry in patch form. Defaults to false. diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 8eeb60feb1..a8c8c32f1e 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -91,8 +91,8 @@ show [-u|--include-untracked|--only-untracked] [] []:: 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). - You can use stash.showStat and/or stash.showPatch config variables - to change the default behavior. + You can use stash.showIncludeUntracked, stash.showStat, and + stash.showPatch config variables to change the default behavior. pop [--index] [-q|--quiet] []:: diff --git a/builtin/stash.c b/builtin/stash.c index 9b7a541cd0..8922a1240c 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -768,6 +768,7 @@ static int list_stash(int argc, const char **argv, const char *prefix) static int show_stat = 1; static int show_patch; +static int show_include_untracked; static int use_legacy_stash; static int git_stash_config(const char *var, const char *value, void *cb) @@ -780,6 +781,10 @@ static int git_stash_config(const char *var, const char *value, void *cb) show_patch = git_config_bool(var, value); return 0; } + if (!strcmp(var, "stash.showincludeuntracked")) { + show_include_untracked = git_config_bool(var, value); + return 0; + } if (!strcmp(var, "stash.usebuiltin")) { use_legacy_stash = !git_config_bool(var, value); return 0; @@ -869,6 +874,9 @@ static int show_stash(int argc, const char **argv, const char *prefix) if (show_patch) rev.diffopt.output_format |= DIFF_FORMAT_PATCH; + if (show_include_untracked) + show_untracked = UNTRACKED_INCLUDE; + if (!show_stat && !show_patch) { free_stash_info(&info); return 0; diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh index 08ceef6411..b470db7ef7 100755 --- a/t/t3905-stash-include-untracked.sh +++ b/t/t3905-stash-include-untracked.sh @@ -319,6 +319,8 @@ test_expect_success 'stash show --include-untracked shows untracked files' ' test_cmp expect actual && git stash show --only-untracked --include-untracked >actual && test_cmp expect actual && + git -c stash.showIncludeUntracked=true stash show >actual && + test_cmp expect actual && cat >expect <<-EOF && diff --git a/tracked b/tracked -- cgit v1.3-5-g9baa