summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2022-08-26 18:13:12 (GMT)
committerJunio C Hamano <gitster@pobox.com>2022-08-26 18:13:13 (GMT)
commit7be9f3f3353acc1e9478aaed5afe2b0778388a69 (patch)
tree34f13edbcf701407c834abba4ab944f774c71493
parente5cb51d3aa2e522a571a74a9664a538a40fc7d64 (diff)
parentb15207b8cf1a1930fe5eb076c08c6ddc92d9282d (diff)
downloadgit-7be9f3f3353acc1e9478aaed5afe2b0778388a69.zip
git-7be9f3f3353acc1e9478aaed5afe2b0778388a69.tar.gz
git-7be9f3f3353acc1e9478aaed5afe2b0778388a69.tar.bz2
Merge branch 'vd/sparse-reset-checkout-fixes' into maint
Fixes to sparse index compatibility work for "reset" and "checkout" commands. source: <pull.1312.v3.git.1659985672.gitgitgadget@gmail.com> * vd/sparse-reset-checkout-fixes: unpack-trees: unpack new trees as sparse directories cache.h: create 'index_name_pos_sparse()' oneway_diff: handle removed sparse directories checkout: fix nested sparse directory diff in sparse index
-rw-r--r--builtin/checkout.c1
-rw-r--r--cache.h9
-rw-r--r--diff-lib.c5
-rw-r--r--read-cache.c5
-rwxr-xr-xt/t1092-sparse-checkout-compatibility.sh25
-rw-r--r--unpack-trees.c106
6 files changed, 141 insertions, 10 deletions
diff --git a/builtin/checkout.c b/builtin/checkout.c
index df3f166..cdd96cd 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -626,6 +626,7 @@ static void show_local_changes(struct object *head,
repo_init_revisions(the_repository, &rev, NULL);
rev.diffopt.flags = opts->flags;
rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
+ rev.diffopt.flags.recursive = 1;
diff_setup_done(&rev.diffopt);
add_pending_object(&rev, head, NULL);
run_diff_index(&rev, 0);
diff --git a/cache.h b/cache.h
index ac5ab4e..33fc833 100644
--- a/cache.h
+++ b/cache.h
@@ -831,6 +831,15 @@ struct cache_entry *index_file_exists(struct index_state *istate, const char *na
int index_name_pos(struct index_state *, const char *name, int namelen);
/*
+ * Like index_name_pos, returns the position of an entry of the given name in
+ * the index if one exists, otherwise returns a negative value where the negated
+ * value minus 1 is the position where the index entry would be inserted. Unlike
+ * index_name_pos, however, a sparse index is not expanded to find an entry
+ * inside a sparse directory.
+ */
+int index_name_pos_sparse(struct index_state *, const char *name, int namelen);
+
+/*
* Determines whether an entry with the given name exists within the
* given index. The return value is 1 if an exact match is found, otherwise
* it is 0. Note that, unlike index_name_pos, this function does not expand
diff --git a/diff-lib.c b/diff-lib.c
index 7eb66a4..2edea41 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -466,6 +466,11 @@ static void do_oneway_diff(struct unpack_trees_options *o,
* Something removed from the tree?
*/
if (!idx) {
+ if (S_ISSPARSEDIR(tree->ce_mode)) {
+ diff_tree_oid(&tree->oid, NULL, tree->name, &revs->diffopt);
+ return;
+ }
+
diff_index_show_file(revs, "-", tree, &tree->oid, 1,
tree->ce_mode, 0);
return;
diff --git a/read-cache.c b/read-cache.c
index 4de2077..b09128b 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -620,6 +620,11 @@ int index_name_pos(struct index_state *istate, const char *name, int namelen)
return index_name_stage_pos(istate, name, namelen, 0, EXPAND_SPARSE);
}
+int index_name_pos_sparse(struct index_state *istate, const char *name, int namelen)
+{
+ return index_name_stage_pos(istate, name, namelen, 0, NO_EXPAND_SPARSE);
+}
+
int index_entry_exists(struct index_state *istate, const char *name, int namelen)
{
return index_name_stage_pos(istate, name, namelen, 0, NO_EXPAND_SPARSE) >= 0;
diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh
index f9f8c98..5dced39 100755
--- a/t/t1092-sparse-checkout-compatibility.sh
+++ b/t/t1092-sparse-checkout-compatibility.sh
@@ -372,6 +372,14 @@ test_expect_success 'deep changes during checkout' '
test_all_match git checkout base
'
+test_expect_success 'checkout with modified sparse directory' '
+ init_repos &&
+
+ test_all_match git checkout rename-in-to-out -- . &&
+ test_sparse_match git sparse-checkout reapply &&
+ test_all_match git checkout base
+'
+
test_expect_success 'add outside sparse cone' '
init_repos &&
@@ -687,6 +695,23 @@ test_expect_success 'reset with wildcard pathspec' '
test_all_match git ls-files -s -- folder1
'
+test_expect_success 'reset hard with removed sparse dir' '
+ init_repos &&
+
+ run_on_all git rm -r --sparse folder1 &&
+ test_all_match git status --porcelain=v2 &&
+
+ test_all_match git reset --hard &&
+ test_all_match git status --porcelain=v2 &&
+
+ cat >expect <<-\EOF &&
+ folder1/
+ EOF
+
+ git -C sparse-index ls-files --sparse folder1 >out &&
+ test_cmp expect out
+'
+
test_expect_success 'update-index modify outside sparse definition' '
init_repos &&
diff --git a/unpack-trees.c b/unpack-trees.c
index 8a454e0..90b9211 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -1070,6 +1070,67 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info,
}
/*
+ * Determine whether the path specified by 'p' should be unpacked as a new
+ * sparse directory in a sparse index. A new sparse directory 'A/':
+ * - must be outside the sparse cone.
+ * - must not already be in the index (i.e., no index entry with name 'A/'
+ * exists).
+ * - must not have any child entries in the index (i.e., no index entry
+ * 'A/<something>' exists).
+ * If 'p' meets the above requirements, return 1; otherwise, return 0.
+ */
+static int entry_is_new_sparse_dir(const struct traverse_info *info,
+ const struct name_entry *p)
+{
+ int res, pos;
+ struct strbuf dirpath = STRBUF_INIT;
+ struct unpack_trees_options *o = info->data;
+
+ if (!S_ISDIR(p->mode))
+ return 0;
+
+ /*
+ * If the path is inside the sparse cone, it can't be a sparse directory.
+ */
+ strbuf_add(&dirpath, info->traverse_path, info->pathlen);
+ strbuf_add(&dirpath, p->path, p->pathlen);
+ strbuf_addch(&dirpath, '/');
+ if (path_in_cone_mode_sparse_checkout(dirpath.buf, o->src_index)) {
+ res = 0;
+ goto cleanup;
+ }
+
+ pos = index_name_pos_sparse(o->src_index, dirpath.buf, dirpath.len);
+ if (pos >= 0) {
+ /* Path is already in the index, not a new sparse dir */
+ res = 0;
+ goto cleanup;
+ }
+
+ /* Where would this sparse dir be inserted into the index? */
+ pos = -pos - 1;
+ if (pos >= o->src_index->cache_nr) {
+ /*
+ * Sparse dir would be inserted at the end of the index, so we
+ * know it has no child entries.
+ */
+ res = 1;
+ goto cleanup;
+ }
+
+ /*
+ * If the dir has child entries in the index, the first would be at the
+ * position the sparse directory would be inserted. If the entry at this
+ * position is inside the dir, not a new sparse dir.
+ */
+ res = strncmp(o->src_index->cache[pos]->name, dirpath.buf, dirpath.len);
+
+cleanup:
+ strbuf_release(&dirpath);
+ return res;
+}
+
+/*
* Note that traverse_by_cache_tree() duplicates some logic in this function
* without actually calling it. If you change the logic here you may need to
* check and change there as well.
@@ -1078,21 +1139,44 @@ static int unpack_single_entry(int n, unsigned long mask,
unsigned long dirmask,
struct cache_entry **src,
const struct name_entry *names,
- const struct traverse_info *info)
+ const struct traverse_info *info,
+ int *is_new_sparse_dir)
{
int i;
struct unpack_trees_options *o = info->data;
unsigned long conflicts = info->df_conflicts | dirmask;
+ const struct name_entry *p = names;
- if (mask == dirmask && !src[0])
- return 0;
+ *is_new_sparse_dir = 0;
+ if (mask == dirmask && !src[0]) {
+ /*
+ * If we're not in a sparse index, we can't unpack a directory
+ * without recursing into it, so we return.
+ */
+ if (!o->src_index->sparse_index)
+ return 0;
+
+ /* Find first entry with a real name (we could use "mask" too) */
+ while (!p->mode)
+ p++;
+
+ /*
+ * If the directory is completely missing from the index but
+ * would otherwise be a sparse directory, we should unpack it.
+ * If not, we'll return and continue recursively traversing the
+ * tree.
+ */
+ *is_new_sparse_dir = entry_is_new_sparse_dir(info, p);
+ if (!*is_new_sparse_dir)
+ return 0;
+ }
/*
- * When we have a sparse directory entry for src[0],
- * then this isn't necessarily a directory-file conflict.
+ * When we are unpacking a sparse directory, then this isn't necessarily
+ * a directory-file conflict.
*/
- if (mask == dirmask && src[0] &&
- S_ISSPARSEDIR(src[0]->ce_mode))
+ if (mask == dirmask &&
+ (*is_new_sparse_dir || (src[0] && S_ISSPARSEDIR(src[0]->ce_mode))))
conflicts = 0;
/*
@@ -1352,7 +1436,7 @@ static int unpack_sparse_callback(int n, unsigned long mask, unsigned long dirma
{
struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
struct unpack_trees_options *o = info->data;
- int ret;
+ int ret, is_new_sparse_dir;
assert(o->merge);
@@ -1376,7 +1460,7 @@ static int unpack_sparse_callback(int n, unsigned long mask, unsigned long dirma
* "index" tree (i.e., names[0]) and adjust 'names', 'n', 'mask', and
* 'dirmask' accordingly.
*/
- ret = unpack_single_entry(n - 1, mask >> 1, dirmask >> 1, src, names + 1, info);
+ ret = unpack_single_entry(n - 1, mask >> 1, dirmask >> 1, src, names + 1, info, &is_new_sparse_dir);
if (src[0])
discard_cache_entry(src[0]);
@@ -1394,6 +1478,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
struct unpack_trees_options *o = info->data;
const struct name_entry *p = names;
+ int is_new_sparse_dir;
/* Find first entry with a real name (we could use "mask" too) */
while (!p->mode)
@@ -1440,7 +1525,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
}
}
- if (unpack_single_entry(n, mask, dirmask, src, names, info) < 0)
+ if (unpack_single_entry(n, mask, dirmask, src, names, info, &is_new_sparse_dir))
return -1;
if (o->merge && src[0]) {
@@ -1478,6 +1563,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
}
if (!is_sparse_directory_entry(src[0], names, info) &&
+ !is_new_sparse_dir &&
traverse_trees_recursive(n, dirmask, mask & ~dirmask,
names, info) < 0) {
return -1;