Merge branch 'vd/sparse-stash'

Teach "git stash" to work better with sparse index entries.

* vd/sparse-stash:
  unpack-trees: preserve index sparsity
  stash: apply stash using 'merge_ort_nonrecursive()'
  read-cache: set sparsity when index is new
  sparse-index: expose 'is_sparse_index_allowed()'
  stash: integrate with sparse index
  stash: expand sparse-checkout compatibility testing
This commit is contained in:
Junio C Hamano 2022-05-20 15:26:58 -07:00
commit 5a9253cd45
7 changed files with 131 additions and 9 deletions

View file

@ -7,6 +7,7 @@
#include "cache-tree.h"
#include "unpack-trees.h"
#include "merge-recursive.h"
#include "merge-ort-wrappers.h"
#include "strvec.h"
#include "run-command.h"
#include "dir.h"
@ -492,13 +493,13 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)
static int do_apply_stash(const char *prefix, struct stash_info *info,
int index, int quiet)
{
int ret;
int clean, ret;
int has_index = index;
struct merge_options o;
struct object_id c_tree;
struct object_id index_tree;
struct commit *result;
const struct object_id *bases[1];
struct tree *head, *merge, *merge_base;
struct lock_file lock = LOCK_INIT;
read_cache_preload(NULL);
if (refresh_and_write_cache(REFRESH_QUIET, 0, 0))
@ -541,6 +542,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
o.branch1 = "Updated upstream";
o.branch2 = "Stashed changes";
o.ancestor = "Stash base";
if (oideq(&info->b_tree, &c_tree))
o.branch1 = "Version stash was based on";
@ -551,10 +553,26 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
if (o.verbosity >= 3)
printf_ln(_("Merging %s with %s"), o.branch1, o.branch2);
bases[0] = &info->b_tree;
head = lookup_tree(o.repo, &c_tree);
merge = lookup_tree(o.repo, &info->w_tree);
merge_base = lookup_tree(o.repo, &info->b_tree);
repo_hold_locked_index(o.repo, &lock, LOCK_DIE_ON_ERROR);
clean = merge_ort_nonrecursive(&o, head, merge, merge_base);
/*
* If 'clean' >= 0, reverse the value for 'ret' so 'ret' is 0 when the
* merge was clean, and nonzero if the merge was unclean or encountered
* an error.
*/
ret = clean >= 0 ? !clean : clean;
if (ret < 0)
rollback_lock_file(&lock);
else if (write_locked_index(o.repo->index, &lock,
COMMIT_LOCK | SKIP_IF_UNCHANGED))
ret = error(_("could not write index"));
ret = merge_recursive_generic(&o, &c_tree, &info->w_tree, 1, bases,
&result);
if (ret) {
rerere(0);
@ -1770,6 +1788,9 @@ int cmd_stash(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, git_stash_usage,
PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
index_file = get_index_file();
strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file,
(uintmax_t)pid);

View file

@ -2260,6 +2260,20 @@ static unsigned long load_cache_entries_threaded(struct index_state *istate, con
return consumed;
}
static void set_new_index_sparsity(struct index_state *istate)
{
/*
* If the index's repo exists, mark it sparse according to
* repo settings.
*/
if (istate->repo) {
prepare_repo_settings(istate->repo);
if (!istate->repo->settings.command_requires_full_index &&
is_sparse_index_allowed(istate, 0))
istate->sparse_index = 1;
}
}
/* remember to discard_cache() before reading a different cache! */
int do_read_index(struct index_state *istate, const char *path, int must_exist)
{
@ -2281,8 +2295,10 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)
istate->timestamp.nsec = 0;
fd = open(path, O_RDONLY);
if (fd < 0) {
if (!must_exist && errno == ENOENT)
if (!must_exist && errno == ENOENT) {
set_new_index_sparsity(istate);
return 0;
}
die_errno(_("%s: index file open failed"), path);
}

View file

@ -118,7 +118,7 @@ static int index_has_unmerged_entries(struct index_state *istate)
return 0;
}
static int is_sparse_index_allowed(struct index_state *istate, int flags)
int is_sparse_index_allowed(struct index_state *istate, int flags)
{
if (!core_apply_sparse_checkout || !core_sparse_checkout_cone)
return 0;

View file

@ -3,6 +3,7 @@
struct index_state;
#define SPARSE_INDEX_MEMORY_ONLY (1 << 0)
int is_sparse_index_allowed(struct index_state *istate, int flags);
int convert_to_sparse(struct index_state *istate, int flags);
void ensure_correct_sparsity(struct index_state *istate);
void clear_skip_worktree_from_present_files(struct index_state *istate);

View file

@ -106,6 +106,8 @@ test_perf_on_all () {
}
test_perf_on_all git status
test_perf_on_all 'git stash && git stash pop'
test_perf_on_all 'echo >>new && git stash -u && git stash pop'
test_perf_on_all git add -A
test_perf_on_all git add .
test_perf_on_all git commit -a -m A

View file

@ -1034,6 +1034,55 @@ test_expect_success 'cherry-pick with conflicts' '
test_all_match test_must_fail git cherry-pick to-cherry-pick
'
test_expect_success 'stash' '
init_repos &&
write_script edit-contents <<-\EOF &&
echo text >>$1
EOF
# Stash a sparse directory (folder1)
test_all_match git checkout -b test-branch rename-base &&
test_all_match git reset --soft rename-out-to-out &&
test_all_match git stash &&
test_all_match git status --porcelain=v2 &&
# Apply the sparse directory stash without reinstating the index
test_all_match git stash apply -q &&
test_all_match git status --porcelain=v2 &&
# Reset to state where stash can be applied
test_sparse_match git sparse-checkout reapply &&
test_all_match git reset --hard rename-out-to-out &&
# Apply the sparse directory stash *with* reinstating the index
test_all_match git stash apply --index -q &&
test_all_match git status --porcelain=v2 &&
# Reset to state where we will get a conflict applying the stash
test_sparse_match git sparse-checkout reapply &&
test_all_match git reset --hard update-folder1 &&
# Apply the sparse directory stash with conflicts
test_all_match test_must_fail git stash apply --index -q &&
test_all_match test_must_fail git stash apply -q &&
test_all_match git status --porcelain=v2 &&
# Reset to base branch
test_sparse_match git sparse-checkout reapply &&
test_all_match git reset --hard base &&
# Stash & unstash an untracked file outside of the sparse checkout
# definition.
run_on_sparse mkdir -p folder1 &&
run_on_all ../edit-contents folder1/new &&
test_all_match git stash -u &&
test_all_match git status --porcelain=v2 &&
test_all_match git stash pop -q &&
test_all_match git status --porcelain=v2
'
test_expect_success 'checkout-index inside sparse definition' '
init_repos &&
@ -1222,7 +1271,10 @@ test_expect_success 'index.sparse disabled inline uses full index' '
ensure_not_expanded () {
rm -f trace2.txt &&
echo >>sparse-index/untracked.txt &&
if test -z "$WITHOUT_UNTRACKED_TXT"
then
echo >>sparse-index/untracked.txt
fi &&
if test "$1" = "!"
then
@ -1326,6 +1378,30 @@ test_expect_success 'sparse-index is not expanded: merge conflict in cone' '
)
'
test_expect_success 'sparse-index is not expanded: stash' '
init_repos &&
echo >>sparse-index/a &&
ensure_not_expanded stash &&
ensure_not_expanded stash list &&
ensure_not_expanded stash show stash@{0} &&
ensure_not_expanded stash apply stash@{0} &&
ensure_not_expanded stash drop stash@{0} &&
echo >>sparse-index/deep/new &&
ensure_not_expanded stash -u &&
(
WITHOUT_UNTRACKED_TXT=1 &&
ensure_not_expanded stash pop
) &&
ensure_not_expanded stash create &&
oid=$(git -C sparse-index stash create) &&
ensure_not_expanded stash store -m "test" $oid &&
ensure_not_expanded reset --hard &&
ensure_not_expanded stash pop
'
test_expect_success 'sparse index is not expanded: diff' '
init_repos &&

View file

@ -11,6 +11,7 @@
#include "refs.h"
#include "attr.h"
#include "split-index.h"
#include "sparse-index.h"
#include "submodule.h"
#include "submodule-config.h"
#include "fsmonitor.h"
@ -1839,6 +1840,11 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
o->result.fsmonitor_last_update =
xstrdup_or_null(o->src_index->fsmonitor_last_update);
if (!o->src_index->initialized &&
!repo->settings.command_requires_full_index &&
is_sparse_index_allowed(&o->result, 0))
o->result.sparse_index = 1;
/*
* Sparse checkout loop #1: set NEW_SKIP_WORKTREE on existing entries
*/