Skip to content

Commit

Permalink
Merge branch 'dl/stash-show-untracked'
Browse files Browse the repository at this point in the history
"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
  • Loading branch information
gitster committed Mar 22, 2021
2 parents dd4048d + 0af760e commit f5c73f6
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 9 deletions.
5 changes: 5 additions & 0 deletions Documentation/config/stash.txt
Expand Up @@ -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.
Expand Down
22 changes: 15 additions & 7 deletions Documentation/git-stash.txt
Expand Up @@ -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>]
Expand Down Expand Up @@ -83,16 +83,16 @@ 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
created.
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>]::

Expand Down Expand Up @@ -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`.
+
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.
+
All untracked files are also stashed and then cleaned up with
`git clean`.
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.
Expand Down
62 changes: 61 additions & 1 deletion builtin/stash.c
Expand Up @@ -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)
Expand All @@ -780,13 +781,44 @@ 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;
}
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;
Expand All @@ -795,14 +827,29 @@ 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()
};

init_diff_ui_defaults();
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] != '-')
Expand All @@ -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;
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion contrib/completion/git-completion.bash
Expand Up @@ -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,--*)
;;
Expand Down
108 changes: 108 additions & 0 deletions t/t3905-stash-include-untracked.sh
Expand Up @@ -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
22 changes: 22 additions & 0 deletions unpack-trees.c
Expand Up @@ -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);
}
2 changes: 2 additions & 0 deletions unpack-trees.h
Expand Up @@ -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

0 comments on commit f5c73f6

Please sign in to comment.