Skip to content

Commit f5c73f6

Browse files
committed
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
2 parents dd4048d + 0af760e commit f5c73f6

File tree

7 files changed

+214
-9
lines changed

7 files changed

+214
-9
lines changed

Documentation/config/stash.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ stash.useBuiltin::
55
is always used. Setting this will emit a warning, to alert any
66
remaining users that setting this now does nothing.
77

8+
stash.showIncludeUntracked::
9+
If this is set to true, the `git stash show` command without an
10+
option will show the untracked files of a stash entry. Defaults to
11+
false. See description of 'show' command in linkgit:git-stash[1].
12+
813
stash.showPatch::
914
If this is set to true, the `git stash show` command without an
1015
option will show the stash entry in patch form. Defaults to false.

Documentation/git-stash.txt

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ SYNOPSIS
99
--------
1010
[verse]
1111
'git stash' list [<log-options>]
12-
'git stash' show [<diff-options>] [<stash>]
12+
'git stash' show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]
1313
'git stash' drop [-q|--quiet] [<stash>]
1414
'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
1515
'git stash' branch <branchname> [<stash>]
@@ -83,16 +83,16 @@ stash@{1}: On master: 9cc0589... Add git-stash
8383
The command takes options applicable to the 'git log'
8484
command to control what is shown and how. See linkgit:git-log[1].
8585

86-
show [<diff-options>] [<stash>]::
86+
show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]::
8787

8888
Show the changes recorded in the stash entry as a diff between the
8989
stashed contents and the commit back when the stash entry was first
9090
created.
9191
By default, the command shows the diffstat, but it will accept any
9292
format known to 'git diff' (e.g., `git stash show -p stash@{1}`
9393
to view the second most recent entry in patch form).
94-
You can use stash.showStat and/or stash.showPatch config variables
95-
to change the default behavior.
94+
You can use stash.showIncludeUntracked, stash.showStat, and
95+
stash.showPatch config variables to change the default behavior.
9696

9797
pop [--index] [-q|--quiet] [<stash>]::
9898

@@ -160,10 +160,18 @@ up with `git clean`.
160160

161161
-u::
162162
--include-untracked::
163-
This option is only valid for `push` and `save` commands.
163+
--no-include-untracked::
164+
When used with the `push` and `save` commands,
165+
all untracked files are also stashed and then cleaned up with
166+
`git clean`.
167+
+
168+
When used with the `show` command, show the untracked files in the stash
169+
entry as part of the diff.
170+
171+
--only-untracked::
172+
This option is only valid for the `show` command.
164173
+
165-
All untracked files are also stashed and then cleaned up with
166-
`git clean`.
174+
Show only the untracked files in the stash entry as part of the diff.
167175

168176
--index::
169177
This option is only valid for `pop` and `apply` commands.

builtin/stash.c

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,7 @@ static int list_stash(int argc, const char **argv, const char *prefix)
768768

769769
static int show_stat = 1;
770770
static int show_patch;
771+
static int show_include_untracked;
771772
static int use_legacy_stash;
772773

773774
static int git_stash_config(const char *var, const char *value, void *cb)
@@ -780,13 +781,44 @@ static int git_stash_config(const char *var, const char *value, void *cb)
780781
show_patch = git_config_bool(var, value);
781782
return 0;
782783
}
784+
if (!strcmp(var, "stash.showincludeuntracked")) {
785+
show_include_untracked = git_config_bool(var, value);
786+
return 0;
787+
}
783788
if (!strcmp(var, "stash.usebuiltin")) {
784789
use_legacy_stash = !git_config_bool(var, value);
785790
return 0;
786791
}
787792
return git_diff_basic_config(var, value, cb);
788793
}
789794

795+
static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt)
796+
{
797+
const struct object_id *oid[] = { &info->w_commit, &info->u_tree };
798+
struct tree *tree[ARRAY_SIZE(oid)];
799+
struct tree_desc tree_desc[ARRAY_SIZE(oid)];
800+
struct unpack_trees_options unpack_tree_opt = { 0 };
801+
int i;
802+
803+
for (i = 0; i < ARRAY_SIZE(oid); i++) {
804+
tree[i] = parse_tree_indirect(oid[i]);
805+
if (parse_tree(tree[i]) < 0)
806+
die(_("failed to parse tree"));
807+
init_tree_desc(&tree_desc[i], tree[i]->buffer, tree[i]->size);
808+
}
809+
810+
unpack_tree_opt.head_idx = -1;
811+
unpack_tree_opt.src_index = &the_index;
812+
unpack_tree_opt.dst_index = &the_index;
813+
unpack_tree_opt.merge = 1;
814+
unpack_tree_opt.fn = stash_worktree_untracked_merge;
815+
816+
if (unpack_trees(ARRAY_SIZE(tree_desc), tree_desc, &unpack_tree_opt))
817+
die(_("failed to unpack trees"));
818+
819+
do_diff_cache(&info->b_commit, diff_opt);
820+
}
821+
790822
static int show_stash(int argc, const char **argv, const char *prefix)
791823
{
792824
int i;
@@ -795,14 +827,29 @@ static int show_stash(int argc, const char **argv, const char *prefix)
795827
struct rev_info rev;
796828
struct strvec stash_args = STRVEC_INIT;
797829
struct strvec revision_args = STRVEC_INIT;
830+
enum {
831+
UNTRACKED_NONE,
832+
UNTRACKED_INCLUDE,
833+
UNTRACKED_ONLY
834+
} show_untracked = UNTRACKED_NONE;
798835
struct option options[] = {
836+
OPT_SET_INT('u', "include-untracked", &show_untracked,
837+
N_("include untracked files in the stash"),
838+
UNTRACKED_INCLUDE),
839+
OPT_SET_INT_F(0, "only-untracked", &show_untracked,
840+
N_("only show untracked files in the stash"),
841+
UNTRACKED_ONLY, PARSE_OPT_NONEG),
799842
OPT_END()
800843
};
801844

802845
init_diff_ui_defaults();
803846
git_config(git_diff_ui_config, NULL);
804847
init_revisions(&rev, prefix);
805848

849+
argc = parse_options(argc, argv, prefix, options, git_stash_show_usage,
850+
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
851+
PARSE_OPT_KEEP_DASHDASH);
852+
806853
strvec_push(&revision_args, argv[0]);
807854
for (i = 1; i < argc; i++) {
808855
if (argv[i][0] != '-')
@@ -827,6 +874,9 @@ static int show_stash(int argc, const char **argv, const char *prefix)
827874
if (show_patch)
828875
rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
829876

877+
if (show_include_untracked)
878+
show_untracked = UNTRACKED_INCLUDE;
879+
830880
if (!show_stat && !show_patch) {
831881
free_stash_info(&info);
832882
return 0;
@@ -845,7 +895,17 @@ static int show_stash(int argc, const char **argv, const char *prefix)
845895

846896
rev.diffopt.flags.recursive = 1;
847897
setup_diff_pager(&rev.diffopt);
848-
diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt);
898+
switch (show_untracked) {
899+
case UNTRACKED_NONE:
900+
diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt);
901+
break;
902+
case UNTRACKED_ONLY:
903+
diff_root_tree_oid(&info.u_tree, "", &rev.diffopt);
904+
break;
905+
case UNTRACKED_INCLUDE:
906+
diff_include_untracked(&info, &rev.diffopt);
907+
break;
908+
}
849909
log_tree_diff_flush(&rev);
850910

851911
free_stash_info(&info);

contrib/completion/git-completion.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3053,7 +3053,7 @@ _git_stash ()
30533053
__gitcomp "--name-status --oneline --patch-with-stat"
30543054
;;
30553055
show,--*)
3056-
__gitcomp "$__git_diff_common_options"
3056+
__gitcomp "--include-untracked --only-untracked $__git_diff_common_options"
30573057
;;
30583058
branch,--*)
30593059
;;

t/t3905-stash-include-untracked.sh

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,4 +297,112 @@ test_expect_success 'stash -u with globs' '
297297
test_path_is_missing untracked.txt
298298
'
299299

300+
test_expect_success 'stash show --include-untracked shows untracked files' '
301+
git reset --hard &&
302+
git clean -xf &&
303+
>untracked &&
304+
>tracked &&
305+
git add tracked &&
306+
empty_blob_oid=$(git rev-parse --short :tracked) &&
307+
git stash -u &&
308+
309+
cat >expect <<-EOF &&
310+
tracked | 0
311+
untracked | 0
312+
2 files changed, 0 insertions(+), 0 deletions(-)
313+
EOF
314+
git stash show --include-untracked >actual &&
315+
test_cmp expect actual &&
316+
git stash show -u >actual &&
317+
test_cmp expect actual &&
318+
git stash show --no-include-untracked --include-untracked >actual &&
319+
test_cmp expect actual &&
320+
git stash show --only-untracked --include-untracked >actual &&
321+
test_cmp expect actual &&
322+
git -c stash.showIncludeUntracked=true stash show >actual &&
323+
test_cmp expect actual &&
324+
325+
cat >expect <<-EOF &&
326+
diff --git a/tracked b/tracked
327+
new file mode 100644
328+
index 0000000..$empty_blob_oid
329+
diff --git a/untracked b/untracked
330+
new file mode 100644
331+
index 0000000..$empty_blob_oid
332+
EOF
333+
git stash show -p --include-untracked >actual &&
334+
test_cmp expect actual &&
335+
git stash show --include-untracked -p >actual &&
336+
test_cmp expect actual
337+
'
338+
339+
test_expect_success 'stash show --only-untracked only shows untracked files' '
340+
git reset --hard &&
341+
git clean -xf &&
342+
>untracked &&
343+
>tracked &&
344+
git add tracked &&
345+
empty_blob_oid=$(git rev-parse --short :tracked) &&
346+
git stash -u &&
347+
348+
cat >expect <<-EOF &&
349+
untracked | 0
350+
1 file changed, 0 insertions(+), 0 deletions(-)
351+
EOF
352+
git stash show --only-untracked >actual &&
353+
test_cmp expect actual &&
354+
git stash show --no-include-untracked --only-untracked >actual &&
355+
test_cmp expect actual &&
356+
git stash show --include-untracked --only-untracked >actual &&
357+
test_cmp expect actual &&
358+
359+
cat >expect <<-EOF &&
360+
diff --git a/untracked b/untracked
361+
new file mode 100644
362+
index 0000000..$empty_blob_oid
363+
EOF
364+
git stash show -p --only-untracked >actual &&
365+
test_cmp expect actual &&
366+
git stash show --only-untracked -p >actual &&
367+
test_cmp expect actual
368+
'
369+
370+
test_expect_success 'stash show --no-include-untracked cancels --{include,show}-untracked' '
371+
git reset --hard &&
372+
git clean -xf &&
373+
>untracked &&
374+
>tracked &&
375+
git add tracked &&
376+
git stash -u &&
377+
378+
cat >expect <<-EOF &&
379+
tracked | 0
380+
1 file changed, 0 insertions(+), 0 deletions(-)
381+
EOF
382+
git stash show --only-untracked --no-include-untracked >actual &&
383+
test_cmp expect actual &&
384+
git stash show --include-untracked --no-include-untracked >actual &&
385+
test_cmp expect actual
386+
'
387+
388+
test_expect_success 'stash show --include-untracked errors on duplicate files' '
389+
git reset --hard &&
390+
git clean -xf &&
391+
>tracked &&
392+
git add tracked &&
393+
tree=$(git write-tree) &&
394+
i_commit=$(git commit-tree -p HEAD -m "index on any-branch" "$tree") &&
395+
test_when_finished "rm -f untracked_index" &&
396+
u_commit=$(
397+
GIT_INDEX_FILE="untracked_index" &&
398+
export GIT_INDEX_FILE &&
399+
git update-index --add tracked &&
400+
u_tree=$(git write-tree) &&
401+
git commit-tree -m "untracked files on any-branch" "$u_tree"
402+
) &&
403+
w_commit=$(git commit-tree -p HEAD -p "$i_commit" -p "$u_commit" -m "WIP on any-branch" "$tree") &&
404+
test_must_fail git stash show --include-untracked "$w_commit" 2>err &&
405+
test_i18ngrep "worktree and untracked commit have duplicate entries: tracked" err
406+
'
407+
300408
test_done

unpack-trees.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2563,3 +2563,25 @@ int oneway_merge(const struct cache_entry * const *src,
25632563
}
25642564
return merged_entry(a, old, o);
25652565
}
2566+
2567+
/*
2568+
* Merge worktree and untracked entries in a stash entry.
2569+
*
2570+
* Ignore all index entries. Collapse remaining trees but make sure that they
2571+
* don't have any conflicting files.
2572+
*/
2573+
int stash_worktree_untracked_merge(const struct cache_entry * const *src,
2574+
struct unpack_trees_options *o)
2575+
{
2576+
const struct cache_entry *worktree = src[1];
2577+
const struct cache_entry *untracked = src[2];
2578+
2579+
if (o->merge_size != 2)
2580+
BUG("invalid merge_size: %d", o->merge_size);
2581+
2582+
if (worktree && untracked)
2583+
return error(_("worktree and untracked commit have duplicate entries: %s"),
2584+
super_prefixed(worktree->name));
2585+
2586+
return merged_entry(worktree ? worktree : untracked, NULL, o);
2587+
}

unpack-trees.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,5 +114,7 @@ int bind_merge(const struct cache_entry * const *src,
114114
struct unpack_trees_options *o);
115115
int oneway_merge(const struct cache_entry * const *src,
116116
struct unpack_trees_options *o);
117+
int stash_worktree_untracked_merge(const struct cache_entry * const *src,
118+
struct unpack_trees_options *o);
117119

118120
#endif

0 commit comments

Comments
 (0)