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 v3: * Incorporate Junio's SQUASH??? commits * Implement a custom unpack_trees() callback to detect the case where there are duplicate entries in worktree and untracked commits 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 | 62 +++++++++++++- contrib/completion/git-completion.bash | 2 +- t/t3905-stash-include-untracked.sh | 108 +++++++++++++++++++++++++ unpack-trees.c | 22 +++++ unpack-trees.h | 2 + 7 files changed, 214 insertions(+), 9 deletions(-) Range-diff against v3: 1: 85b81f2f06 ! 1: af3757135b stash show: teach --include-untracked and --only-untracked @@ Commit message 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. - 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. + 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. - - ## 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 -------- @@ builtin/stash.c: static int git_stash_config(const char *var, const char *value, + 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; ++ 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")); @@ t/t3905-stash-include-untracked.sh: test_expect_success 'stash -u with globs' ' + >untracked && + >tracked && + git add tracked && ++ empty_blob_oid=$(git rev-parse --short :tracked) && + git stash -u && + + cat >expect <<-EOF && @@ t/t3905-stash-include-untracked.sh: test_expect_success 'stash -u with globs' ' + cat >expect <<-EOF && + diff --git a/tracked b/tracked + new file mode 100644 -+ index 0000000..e69de29 ++ index 0000000..$empty_blob_oid + diff --git a/untracked b/untracked + new file mode 100644 -+ index 0000000..e69de29 ++ index 0000000..$empty_blob_oid + EOF + git stash show -p --include-untracked >actual && + test_cmp expect actual && @@ t/t3905-stash-include-untracked.sh: test_expect_success 'stash -u with globs' ' + >untracked && + >tracked && + git add tracked && ++ empty_blob_oid=$(git rev-parse --short :tracked) && + git stash -u && + + cat >expect <<-EOF && @@ t/t3905-stash-include-untracked.sh: test_expect_success 'stash -u with globs' ' + cat >expect <<-EOF && + diff --git a/untracked b/untracked + new file mode 100644 -+ index 0000000..e69de29 ++ index 0000000..$empty_blob_oid + EOF + git stash show -p --only-untracked >actual && + test_cmp expect actual && @@ t/t3905-stash-include-untracked.sh: test_expect_success 'stash -u with globs' ' + 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 + + ## unpack-trees.c ## +@@ unpack-trees.c: 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); ++} + + ## unpack-trees.h ## +@@ unpack-trees.h: 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 2: d19d07ec27 = 2: 3480086f1d stash show: learn stash.showIncludeUntracked -- 2.31.0.rc1.228.gb75b4e4ce2