Merge branch 'dl/stash-show-untracked'

"git stash show" learned to optionally show untracked part of the
stash.

* dl/stash-show-untracked:
  stash show: learn stash.showIncludeUntracked
  stash show: teach --include-untracked and --only-untracked
This commit is contained in:
Junio C Hamano 2021-03-22 14:00:24 -07:00
commit f5c73f69fd
7 changed files with 214 additions and 9 deletions

View File

@ -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.

View File

@ -9,7 +9,7 @@ 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>]
@ -83,7 +83,7 @@ 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 the changes recorded in the stash entry as a diff between the
stashed contents and the commit back when the stash entry was first
@ -91,8 +91,8 @@ show [<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).
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] [<stash>]::
@ -160,10 +160,18 @@ up with `git clean`.
-u::
--include-untracked::
This option is only valid for `push` and `save` commands.
--no-include-untracked::
When used with the `push` and `save` commands,
all untracked files are also stashed and then cleaned up with
`git clean`.
+
All untracked files are also stashed and then cleaned up with
`git clean`.
When used with the `show` command, show the untracked files in the stash
entry as part of the diff.
--only-untracked::
This option is only valid for the `show` command.
+
Show only the untracked files in the stash entry as part of the diff.
--index::
This option is only valid for `pop` and `apply` commands.

View File

@ -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;
@ -787,6 +792,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 +827,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 +846,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] != '-')
@ -827,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;
@ -845,7 +895,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);

View File

@ -3053,7 +3053,7 @@ _git_stash ()
__gitcomp "--name-status --oneline --patch-with-stat"
;;
show,--*)
__gitcomp "$__git_diff_common_options"
__gitcomp "--include-untracked --only-untracked $__git_diff_common_options"
;;
branch,--*)
;;

View File

@ -297,4 +297,112 @@ test_expect_success 'stash -u with globs' '
test_path_is_missing untracked.txt
'
test_expect_success 'stash show --include-untracked shows untracked files' '
git reset --hard &&
git clean -xf &&
>untracked &&
>tracked &&
git add tracked &&
empty_blob_oid=$(git rev-parse --short :tracked) &&
git stash -u &&
cat >expect <<-EOF &&
tracked | 0
untracked | 0
2 files changed, 0 insertions(+), 0 deletions(-)
EOF
git stash show --include-untracked >actual &&
test_cmp expect actual &&
git stash show -u >actual &&
test_cmp expect actual &&
git stash show --no-include-untracked --include-untracked >actual &&
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
new file mode 100644
index 0000000..$empty_blob_oid
diff --git a/untracked b/untracked
new file mode 100644
index 0000000..$empty_blob_oid
EOF
git stash show -p --include-untracked >actual &&
test_cmp expect actual &&
git stash show --include-untracked -p >actual &&
test_cmp expect actual
'
test_expect_success 'stash show --only-untracked only shows untracked files' '
git reset --hard &&
git clean -xf &&
>untracked &&
>tracked &&
git add tracked &&
empty_blob_oid=$(git rev-parse --short :tracked) &&
git stash -u &&
cat >expect <<-EOF &&
untracked | 0
1 file changed, 0 insertions(+), 0 deletions(-)
EOF
git stash show --only-untracked >actual &&
test_cmp expect actual &&
git stash show --no-include-untracked --only-untracked >actual &&
test_cmp expect actual &&
git stash show --include-untracked --only-untracked >actual &&
test_cmp expect actual &&
cat >expect <<-EOF &&
diff --git a/untracked b/untracked
new file mode 100644
index 0000000..$empty_blob_oid
EOF
git stash show -p --only-untracked >actual &&
test_cmp expect actual &&
git stash show --only-untracked -p >actual &&
test_cmp expect actual
'
test_expect_success 'stash show --no-include-untracked cancels --{include,show}-untracked' '
git reset --hard &&
git clean -xf &&
>untracked &&
>tracked &&
git add tracked &&
git stash -u &&
cat >expect <<-EOF &&
tracked | 0
1 file changed, 0 insertions(+), 0 deletions(-)
EOF
git stash show --only-untracked --no-include-untracked >actual &&
test_cmp expect actual &&
git stash show --include-untracked --no-include-untracked >actual &&
test_cmp expect actual
'
test_expect_success 'stash show --include-untracked errors on duplicate files' '
git reset --hard &&
git clean -xf &&
>tracked &&
git add tracked &&
tree=$(git write-tree) &&
i_commit=$(git commit-tree -p HEAD -m "index on any-branch" "$tree") &&
test_when_finished "rm -f untracked_index" &&
u_commit=$(
GIT_INDEX_FILE="untracked_index" &&
export GIT_INDEX_FILE &&
git update-index --add tracked &&
u_tree=$(git write-tree) &&
git commit-tree -m "untracked files on any-branch" "$u_tree"
) &&
w_commit=$(git commit-tree -p HEAD -p "$i_commit" -p "$u_commit" -m "WIP on any-branch" "$tree") &&
test_must_fail git stash show --include-untracked "$w_commit" 2>err &&
test_i18ngrep "worktree and untracked commit have duplicate entries: tracked" err
'
test_done

View File

@ -2563,3 +2563,25 @@ int oneway_merge(const struct cache_entry * const *src,
}
return merged_entry(a, old, o);
}
/*
* Merge worktree and untracked entries in a stash entry.
*
* Ignore all index entries. Collapse remaining trees but make sure that they
* don't have any conflicting files.
*/
int stash_worktree_untracked_merge(const struct cache_entry * const *src,
struct unpack_trees_options *o)
{
const struct cache_entry *worktree = src[1];
const struct cache_entry *untracked = src[2];
if (o->merge_size != 2)
BUG("invalid merge_size: %d", o->merge_size);
if (worktree && untracked)
return error(_("worktree and untracked commit have duplicate entries: %s"),
super_prefixed(worktree->name));
return merged_entry(worktree ? worktree : untracked, NULL, o);
}

View File

@ -114,5 +114,7 @@ int bind_merge(const struct cache_entry * const *src,
struct unpack_trees_options *o);
int oneway_merge(const struct cache_entry * const *src,
struct unpack_trees_options *o);
int stash_worktree_untracked_merge(const struct cache_entry * const *src,
struct unpack_trees_options *o);
#endif