2021-03-30 13:10:47 +00:00
|
|
|
#include "cache.h"
|
|
|
|
#include "repository.h"
|
|
|
|
#include "sparse-index.h"
|
2021-03-30 13:10:48 +00:00
|
|
|
#include "tree.h"
|
|
|
|
#include "pathspec.h"
|
|
|
|
#include "trace2.h"
|
sparse-index: convert from full to sparse
If we have a full index, then we can convert it to a sparse index by
replacing directories outside of the sparse cone with sparse directory
entries. The convert_to_sparse() method does this, when the situation is
appropriate.
For now, we avoid converting the index to a sparse index if:
1. the index is split.
2. the index is already sparse.
3. sparse-checkout is disabled.
4. sparse-checkout does not use cone mode.
Finally, we currently limit the conversion to when the
GIT_TEST_SPARSE_INDEX environment variable is enabled. A mode using Git
config will be added in a later change.
The trickiest thing about this conversion is that we might not be able
to mark a directory as a sparse directory just because it is outside the
sparse cone. There might be unmerged files within that directory, so we
need to look for those. Also, if there is some strange reason why a file
is not marked with CE_SKIP_WORKTREE, then we should give up on
converting that directory. There is still hope that some of its
subdirectories might be able to convert to sparse, so we keep looking
deeper.
The conversion process is assisted by the cache-tree extension. This is
calculated from the full index if it does not already exist. We then
abandon the cache-tree as it no longer applies to the newly-sparse
index. Thus, this cache-tree will be recalculated in every
sparse-full-sparse round-trip until we integrate the cache-tree
extension with the sparse index.
Some Git commands use the index after writing it. For example, 'git add'
will update the index, then write it to disk, then read its entries to
report information. To keep the in-memory index in a full state after
writing, we re-expand it to a full one after the write. This is wasteful
for commands that only write the index and do not read from it again,
but that is only the case until we make those commands "sparse aware."
We can compare the behavior of the sparse-index in
t1092-sparse-checkout-compability.sh by using GIT_TEST_SPARSE_INDEX=1
when operating on the 'sparse-index' repo. We can also compare the two
sparse repos directly, such as comparing their indexes (when expanded to
full in the case of the 'sparse-index' repo). We also verify that the
index is actually populated with sparse directory entries.
The 'checkout and reset (mixed)' test is marked for failure when
comparing a sparse repo to a full repo, but we can compare the two
sparse-checkout cases directly to ensure that we are not changing the
behavior when using a sparse index.
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-03-30 13:10:55 +00:00
|
|
|
#include "cache-tree.h"
|
|
|
|
#include "config.h"
|
|
|
|
#include "dir.h"
|
|
|
|
#include "fsmonitor.h"
|
|
|
|
|
|
|
|
static struct cache_entry *construct_sparse_dir_entry(
|
|
|
|
struct index_state *istate,
|
|
|
|
const char *sparse_dir,
|
|
|
|
struct cache_tree *tree)
|
|
|
|
{
|
|
|
|
struct cache_entry *de;
|
|
|
|
|
|
|
|
de = make_cache_entry(istate, S_IFDIR, &tree->oid, sparse_dir, 0, 0);
|
|
|
|
|
|
|
|
de->ce_flags |= CE_SKIP_WORKTREE;
|
|
|
|
return de;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Returns the number of entries "inserted" into the index.
|
|
|
|
*/
|
|
|
|
static int convert_to_sparse_rec(struct index_state *istate,
|
|
|
|
int num_converted,
|
|
|
|
int start, int end,
|
|
|
|
const char *ct_path, size_t ct_pathlen,
|
|
|
|
struct cache_tree *ct)
|
|
|
|
{
|
|
|
|
int i, can_convert = 1;
|
|
|
|
int start_converted = num_converted;
|
|
|
|
enum pattern_match_result match;
|
2021-05-17 12:22:17 +00:00
|
|
|
int dtype = DT_UNKNOWN;
|
sparse-index: convert from full to sparse
If we have a full index, then we can convert it to a sparse index by
replacing directories outside of the sparse cone with sparse directory
entries. The convert_to_sparse() method does this, when the situation is
appropriate.
For now, we avoid converting the index to a sparse index if:
1. the index is split.
2. the index is already sparse.
3. sparse-checkout is disabled.
4. sparse-checkout does not use cone mode.
Finally, we currently limit the conversion to when the
GIT_TEST_SPARSE_INDEX environment variable is enabled. A mode using Git
config will be added in a later change.
The trickiest thing about this conversion is that we might not be able
to mark a directory as a sparse directory just because it is outside the
sparse cone. There might be unmerged files within that directory, so we
need to look for those. Also, if there is some strange reason why a file
is not marked with CE_SKIP_WORKTREE, then we should give up on
converting that directory. There is still hope that some of its
subdirectories might be able to convert to sparse, so we keep looking
deeper.
The conversion process is assisted by the cache-tree extension. This is
calculated from the full index if it does not already exist. We then
abandon the cache-tree as it no longer applies to the newly-sparse
index. Thus, this cache-tree will be recalculated in every
sparse-full-sparse round-trip until we integrate the cache-tree
extension with the sparse index.
Some Git commands use the index after writing it. For example, 'git add'
will update the index, then write it to disk, then read its entries to
report information. To keep the in-memory index in a full state after
writing, we re-expand it to a full one after the write. This is wasteful
for commands that only write the index and do not read from it again,
but that is only the case until we make those commands "sparse aware."
We can compare the behavior of the sparse-index in
t1092-sparse-checkout-compability.sh by using GIT_TEST_SPARSE_INDEX=1
when operating on the 'sparse-index' repo. We can also compare the two
sparse repos directly, such as comparing their indexes (when expanded to
full in the case of the 'sparse-index' repo). We also verify that the
index is actually populated with sparse directory entries.
The 'checkout and reset (mixed)' test is marked for failure when
comparing a sparse repo to a full repo, but we can compare the two
sparse-checkout cases directly to ensure that we are not changing the
behavior when using a sparse index.
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-03-30 13:10:55 +00:00
|
|
|
struct strbuf child_path = STRBUF_INIT;
|
|
|
|
struct pattern_list *pl = istate->sparse_checkout_patterns;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Is the current path outside of the sparse cone?
|
|
|
|
* Then check if the region can be replaced by a sparse
|
|
|
|
* directory entry (everything is sparse and merged).
|
|
|
|
*/
|
|
|
|
match = path_matches_pattern_list(ct_path, ct_pathlen,
|
|
|
|
NULL, &dtype, pl, istate);
|
|
|
|
if (match != NOT_MATCHED)
|
|
|
|
can_convert = 0;
|
|
|
|
|
|
|
|
for (i = start; can_convert && i < end; i++) {
|
|
|
|
struct cache_entry *ce = istate->cache[i];
|
|
|
|
|
|
|
|
if (ce_stage(ce) ||
|
2021-03-30 13:10:56 +00:00
|
|
|
S_ISGITLINK(ce->ce_mode) ||
|
sparse-index: convert from full to sparse
If we have a full index, then we can convert it to a sparse index by
replacing directories outside of the sparse cone with sparse directory
entries. The convert_to_sparse() method does this, when the situation is
appropriate.
For now, we avoid converting the index to a sparse index if:
1. the index is split.
2. the index is already sparse.
3. sparse-checkout is disabled.
4. sparse-checkout does not use cone mode.
Finally, we currently limit the conversion to when the
GIT_TEST_SPARSE_INDEX environment variable is enabled. A mode using Git
config will be added in a later change.
The trickiest thing about this conversion is that we might not be able
to mark a directory as a sparse directory just because it is outside the
sparse cone. There might be unmerged files within that directory, so we
need to look for those. Also, if there is some strange reason why a file
is not marked with CE_SKIP_WORKTREE, then we should give up on
converting that directory. There is still hope that some of its
subdirectories might be able to convert to sparse, so we keep looking
deeper.
The conversion process is assisted by the cache-tree extension. This is
calculated from the full index if it does not already exist. We then
abandon the cache-tree as it no longer applies to the newly-sparse
index. Thus, this cache-tree will be recalculated in every
sparse-full-sparse round-trip until we integrate the cache-tree
extension with the sparse index.
Some Git commands use the index after writing it. For example, 'git add'
will update the index, then write it to disk, then read its entries to
report information. To keep the in-memory index in a full state after
writing, we re-expand it to a full one after the write. This is wasteful
for commands that only write the index and do not read from it again,
but that is only the case until we make those commands "sparse aware."
We can compare the behavior of the sparse-index in
t1092-sparse-checkout-compability.sh by using GIT_TEST_SPARSE_INDEX=1
when operating on the 'sparse-index' repo. We can also compare the two
sparse repos directly, such as comparing their indexes (when expanded to
full in the case of the 'sparse-index' repo). We also verify that the
index is actually populated with sparse directory entries.
The 'checkout and reset (mixed)' test is marked for failure when
comparing a sparse repo to a full repo, but we can compare the two
sparse-checkout cases directly to ensure that we are not changing the
behavior when using a sparse index.
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-03-30 13:10:55 +00:00
|
|
|
!(ce->ce_flags & CE_SKIP_WORKTREE))
|
|
|
|
can_convert = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (can_convert) {
|
|
|
|
struct cache_entry *se;
|
|
|
|
se = construct_sparse_dir_entry(istate, ct_path, ct);
|
|
|
|
|
|
|
|
istate->cache[num_converted++] = se;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = start; i < end; ) {
|
|
|
|
int count, span, pos = -1;
|
|
|
|
const char *base, *slash;
|
|
|
|
struct cache_entry *ce = istate->cache[i];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Detect if this is a normal entry outside of any subtree
|
|
|
|
* entry.
|
|
|
|
*/
|
|
|
|
base = ce->name + ct_pathlen;
|
|
|
|
slash = strchr(base, '/');
|
|
|
|
|
|
|
|
if (slash)
|
|
|
|
pos = cache_tree_subtree_pos(ct, base, slash - base);
|
|
|
|
|
|
|
|
if (pos < 0) {
|
|
|
|
istate->cache[num_converted++] = ce;
|
|
|
|
i++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
strbuf_setlen(&child_path, 0);
|
|
|
|
strbuf_add(&child_path, ce->name, slash - ce->name + 1);
|
|
|
|
|
|
|
|
span = ct->down[pos]->cache_tree->entry_count;
|
|
|
|
count = convert_to_sparse_rec(istate,
|
|
|
|
num_converted, i, i + span,
|
|
|
|
child_path.buf, child_path.len,
|
|
|
|
ct->down[pos]->cache_tree);
|
|
|
|
num_converted += count;
|
|
|
|
i += span;
|
|
|
|
}
|
|
|
|
|
|
|
|
strbuf_release(&child_path);
|
|
|
|
return num_converted - start_converted;
|
|
|
|
}
|
|
|
|
|
2021-05-05 12:11:58 +00:00
|
|
|
int set_sparse_index_config(struct repository *repo, int enable)
|
2021-03-30 13:10:59 +00:00
|
|
|
{
|
2021-03-30 13:11:00 +00:00
|
|
|
int res;
|
|
|
|
char *config_path = repo_git_path(repo, "config.worktree");
|
|
|
|
res = git_config_set_in_file_gently(config_path,
|
|
|
|
"index.sparse",
|
|
|
|
enable ? "true" : NULL);
|
|
|
|
free(config_path);
|
2021-03-30 13:10:59 +00:00
|
|
|
|
2021-03-30 13:11:00 +00:00
|
|
|
prepare_repo_settings(repo);
|
|
|
|
repo->settings.sparse_index = enable;
|
|
|
|
return res;
|
2021-03-30 13:10:59 +00:00
|
|
|
}
|
|
|
|
|
sparse-index: skip indexes with unmerged entries
The sparse-index format is designed to be compatible with merge
conflicts, even those outside the sparse-checkout definition. The reason
is that when converting a full index to a sparse one, a cache entry with
nonzero stage will not be collapsed into a sparse directory entry.
However, this behavior was not tested, and a different behavior within
convert_to_sparse() fails in this scenario. Specifically,
cache_tree_update() will fail when unmerged entries exist.
convert_to_sparse_rec() uses the cache-tree data to recursively walk the
tree structure, but also to compute the OIDs used in the
sparse-directory entries.
Add an index scan to convert_to_sparse() that will detect if these merge
conflict entries exist and skip the conversion before trying to update
the cache-tree. This is marked as NEEDSWORK because this can be removed
with a suitable update to cache_tree_update() or a similar method that
can construct a cache-tree with invalid nodes, but still allow creating
the nodes necessary for creating sparse directory entries.
It is possible that in the future we will not need to make such an
update, since if we do not expand a sparse-index into a full one, this
conversion does not need to happen. Thus, this can be deferred until the
merge machinery is made to integrate with the sparse-index.
Reviewed-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-07-14 13:12:25 +00:00
|
|
|
static int index_has_unmerged_entries(struct index_state *istate)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < istate->cache_nr; i++) {
|
|
|
|
if (ce_stage(istate->cache[i]))
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
sparse-index: convert from full to sparse
If we have a full index, then we can convert it to a sparse index by
replacing directories outside of the sparse cone with sparse directory
entries. The convert_to_sparse() method does this, when the situation is
appropriate.
For now, we avoid converting the index to a sparse index if:
1. the index is split.
2. the index is already sparse.
3. sparse-checkout is disabled.
4. sparse-checkout does not use cone mode.
Finally, we currently limit the conversion to when the
GIT_TEST_SPARSE_INDEX environment variable is enabled. A mode using Git
config will be added in a later change.
The trickiest thing about this conversion is that we might not be able
to mark a directory as a sparse directory just because it is outside the
sparse cone. There might be unmerged files within that directory, so we
need to look for those. Also, if there is some strange reason why a file
is not marked with CE_SKIP_WORKTREE, then we should give up on
converting that directory. There is still hope that some of its
subdirectories might be able to convert to sparse, so we keep looking
deeper.
The conversion process is assisted by the cache-tree extension. This is
calculated from the full index if it does not already exist. We then
abandon the cache-tree as it no longer applies to the newly-sparse
index. Thus, this cache-tree will be recalculated in every
sparse-full-sparse round-trip until we integrate the cache-tree
extension with the sparse index.
Some Git commands use the index after writing it. For example, 'git add'
will update the index, then write it to disk, then read its entries to
report information. To keep the in-memory index in a full state after
writing, we re-expand it to a full one after the write. This is wasteful
for commands that only write the index and do not read from it again,
but that is only the case until we make those commands "sparse aware."
We can compare the behavior of the sparse-index in
t1092-sparse-checkout-compability.sh by using GIT_TEST_SPARSE_INDEX=1
when operating on the 'sparse-index' repo. We can also compare the two
sparse repos directly, such as comparing their indexes (when expanded to
full in the case of the 'sparse-index' repo). We also verify that the
index is actually populated with sparse directory entries.
The 'checkout and reset (mixed)' test is marked for failure when
comparing a sparse repo to a full repo, but we can compare the two
sparse-checkout cases directly to ensure that we are not changing the
behavior when using a sparse index.
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-03-30 13:10:55 +00:00
|
|
|
int convert_to_sparse(struct index_state *istate)
|
|
|
|
{
|
2021-03-30 13:11:00 +00:00
|
|
|
int test_env;
|
sparse-index: convert from full to sparse
If we have a full index, then we can convert it to a sparse index by
replacing directories outside of the sparse cone with sparse directory
entries. The convert_to_sparse() method does this, when the situation is
appropriate.
For now, we avoid converting the index to a sparse index if:
1. the index is split.
2. the index is already sparse.
3. sparse-checkout is disabled.
4. sparse-checkout does not use cone mode.
Finally, we currently limit the conversion to when the
GIT_TEST_SPARSE_INDEX environment variable is enabled. A mode using Git
config will be added in a later change.
The trickiest thing about this conversion is that we might not be able
to mark a directory as a sparse directory just because it is outside the
sparse cone. There might be unmerged files within that directory, so we
need to look for those. Also, if there is some strange reason why a file
is not marked with CE_SKIP_WORKTREE, then we should give up on
converting that directory. There is still hope that some of its
subdirectories might be able to convert to sparse, so we keep looking
deeper.
The conversion process is assisted by the cache-tree extension. This is
calculated from the full index if it does not already exist. We then
abandon the cache-tree as it no longer applies to the newly-sparse
index. Thus, this cache-tree will be recalculated in every
sparse-full-sparse round-trip until we integrate the cache-tree
extension with the sparse index.
Some Git commands use the index after writing it. For example, 'git add'
will update the index, then write it to disk, then read its entries to
report information. To keep the in-memory index in a full state after
writing, we re-expand it to a full one after the write. This is wasteful
for commands that only write the index and do not read from it again,
but that is only the case until we make those commands "sparse aware."
We can compare the behavior of the sparse-index in
t1092-sparse-checkout-compability.sh by using GIT_TEST_SPARSE_INDEX=1
when operating on the 'sparse-index' repo. We can also compare the two
sparse repos directly, such as comparing their indexes (when expanded to
full in the case of the 'sparse-index' repo). We also verify that the
index is actually populated with sparse directory entries.
The 'checkout and reset (mixed)' test is marked for failure when
comparing a sparse repo to a full repo, but we can compare the two
sparse-checkout cases directly to ensure that we are not changing the
behavior when using a sparse index.
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-03-30 13:10:55 +00:00
|
|
|
if (istate->split_index || istate->sparse_index ||
|
|
|
|
!core_apply_sparse_checkout || !core_sparse_checkout_cone)
|
|
|
|
return 0;
|
|
|
|
|
2021-03-30 13:10:59 +00:00
|
|
|
if (!istate->repo)
|
|
|
|
istate->repo = the_repository;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The GIT_TEST_SPARSE_INDEX environment variable triggers the
|
|
|
|
* index.sparse config variable to be on.
|
|
|
|
*/
|
2021-03-30 13:11:00 +00:00
|
|
|
test_env = git_env_bool("GIT_TEST_SPARSE_INDEX", -1);
|
|
|
|
if (test_env >= 0)
|
|
|
|
set_sparse_index_config(istate->repo, test_env);
|
2021-03-30 13:10:59 +00:00
|
|
|
|
sparse-index: convert from full to sparse
If we have a full index, then we can convert it to a sparse index by
replacing directories outside of the sparse cone with sparse directory
entries. The convert_to_sparse() method does this, when the situation is
appropriate.
For now, we avoid converting the index to a sparse index if:
1. the index is split.
2. the index is already sparse.
3. sparse-checkout is disabled.
4. sparse-checkout does not use cone mode.
Finally, we currently limit the conversion to when the
GIT_TEST_SPARSE_INDEX environment variable is enabled. A mode using Git
config will be added in a later change.
The trickiest thing about this conversion is that we might not be able
to mark a directory as a sparse directory just because it is outside the
sparse cone. There might be unmerged files within that directory, so we
need to look for those. Also, if there is some strange reason why a file
is not marked with CE_SKIP_WORKTREE, then we should give up on
converting that directory. There is still hope that some of its
subdirectories might be able to convert to sparse, so we keep looking
deeper.
The conversion process is assisted by the cache-tree extension. This is
calculated from the full index if it does not already exist. We then
abandon the cache-tree as it no longer applies to the newly-sparse
index. Thus, this cache-tree will be recalculated in every
sparse-full-sparse round-trip until we integrate the cache-tree
extension with the sparse index.
Some Git commands use the index after writing it. For example, 'git add'
will update the index, then write it to disk, then read its entries to
report information. To keep the in-memory index in a full state after
writing, we re-expand it to a full one after the write. This is wasteful
for commands that only write the index and do not read from it again,
but that is only the case until we make those commands "sparse aware."
We can compare the behavior of the sparse-index in
t1092-sparse-checkout-compability.sh by using GIT_TEST_SPARSE_INDEX=1
when operating on the 'sparse-index' repo. We can also compare the two
sparse repos directly, such as comparing their indexes (when expanded to
full in the case of the 'sparse-index' repo). We also verify that the
index is actually populated with sparse directory entries.
The 'checkout and reset (mixed)' test is marked for failure when
comparing a sparse repo to a full repo, but we can compare the two
sparse-checkout cases directly to ensure that we are not changing the
behavior when using a sparse index.
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-03-30 13:10:55 +00:00
|
|
|
/*
|
2021-03-30 13:10:59 +00:00
|
|
|
* Only convert to sparse if index.sparse is set.
|
sparse-index: convert from full to sparse
If we have a full index, then we can convert it to a sparse index by
replacing directories outside of the sparse cone with sparse directory
entries. The convert_to_sparse() method does this, when the situation is
appropriate.
For now, we avoid converting the index to a sparse index if:
1. the index is split.
2. the index is already sparse.
3. sparse-checkout is disabled.
4. sparse-checkout does not use cone mode.
Finally, we currently limit the conversion to when the
GIT_TEST_SPARSE_INDEX environment variable is enabled. A mode using Git
config will be added in a later change.
The trickiest thing about this conversion is that we might not be able
to mark a directory as a sparse directory just because it is outside the
sparse cone. There might be unmerged files within that directory, so we
need to look for those. Also, if there is some strange reason why a file
is not marked with CE_SKIP_WORKTREE, then we should give up on
converting that directory. There is still hope that some of its
subdirectories might be able to convert to sparse, so we keep looking
deeper.
The conversion process is assisted by the cache-tree extension. This is
calculated from the full index if it does not already exist. We then
abandon the cache-tree as it no longer applies to the newly-sparse
index. Thus, this cache-tree will be recalculated in every
sparse-full-sparse round-trip until we integrate the cache-tree
extension with the sparse index.
Some Git commands use the index after writing it. For example, 'git add'
will update the index, then write it to disk, then read its entries to
report information. To keep the in-memory index in a full state after
writing, we re-expand it to a full one after the write. This is wasteful
for commands that only write the index and do not read from it again,
but that is only the case until we make those commands "sparse aware."
We can compare the behavior of the sparse-index in
t1092-sparse-checkout-compability.sh by using GIT_TEST_SPARSE_INDEX=1
when operating on the 'sparse-index' repo. We can also compare the two
sparse repos directly, such as comparing their indexes (when expanded to
full in the case of the 'sparse-index' repo). We also verify that the
index is actually populated with sparse directory entries.
The 'checkout and reset (mixed)' test is marked for failure when
comparing a sparse repo to a full repo, but we can compare the two
sparse-checkout cases directly to ensure that we are not changing the
behavior when using a sparse index.
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-03-30 13:10:55 +00:00
|
|
|
*/
|
2021-03-30 13:10:59 +00:00
|
|
|
prepare_repo_settings(istate->repo);
|
|
|
|
if (!istate->repo->settings.sparse_index)
|
sparse-index: convert from full to sparse
If we have a full index, then we can convert it to a sparse index by
replacing directories outside of the sparse cone with sparse directory
entries. The convert_to_sparse() method does this, when the situation is
appropriate.
For now, we avoid converting the index to a sparse index if:
1. the index is split.
2. the index is already sparse.
3. sparse-checkout is disabled.
4. sparse-checkout does not use cone mode.
Finally, we currently limit the conversion to when the
GIT_TEST_SPARSE_INDEX environment variable is enabled. A mode using Git
config will be added in a later change.
The trickiest thing about this conversion is that we might not be able
to mark a directory as a sparse directory just because it is outside the
sparse cone. There might be unmerged files within that directory, so we
need to look for those. Also, if there is some strange reason why a file
is not marked with CE_SKIP_WORKTREE, then we should give up on
converting that directory. There is still hope that some of its
subdirectories might be able to convert to sparse, so we keep looking
deeper.
The conversion process is assisted by the cache-tree extension. This is
calculated from the full index if it does not already exist. We then
abandon the cache-tree as it no longer applies to the newly-sparse
index. Thus, this cache-tree will be recalculated in every
sparse-full-sparse round-trip until we integrate the cache-tree
extension with the sparse index.
Some Git commands use the index after writing it. For example, 'git add'
will update the index, then write it to disk, then read its entries to
report information. To keep the in-memory index in a full state after
writing, we re-expand it to a full one after the write. This is wasteful
for commands that only write the index and do not read from it again,
but that is only the case until we make those commands "sparse aware."
We can compare the behavior of the sparse-index in
t1092-sparse-checkout-compability.sh by using GIT_TEST_SPARSE_INDEX=1
when operating on the 'sparse-index' repo. We can also compare the two
sparse repos directly, such as comparing their indexes (when expanded to
full in the case of the 'sparse-index' repo). We also verify that the
index is actually populated with sparse directory entries.
The 'checkout and reset (mixed)' test is marked for failure when
comparing a sparse repo to a full repo, but we can compare the two
sparse-checkout cases directly to ensure that we are not changing the
behavior when using a sparse index.
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-03-30 13:10:55 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!istate->sparse_checkout_patterns) {
|
|
|
|
istate->sparse_checkout_patterns = xcalloc(1, sizeof(struct pattern_list));
|
|
|
|
if (get_sparse_checkout_patterns(istate->sparse_checkout_patterns) < 0)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!istate->sparse_checkout_patterns->use_cone_patterns) {
|
|
|
|
warning(_("attempting to use sparse-index without cone mode"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
sparse-index: skip indexes with unmerged entries
The sparse-index format is designed to be compatible with merge
conflicts, even those outside the sparse-checkout definition. The reason
is that when converting a full index to a sparse one, a cache entry with
nonzero stage will not be collapsed into a sparse directory entry.
However, this behavior was not tested, and a different behavior within
convert_to_sparse() fails in this scenario. Specifically,
cache_tree_update() will fail when unmerged entries exist.
convert_to_sparse_rec() uses the cache-tree data to recursively walk the
tree structure, but also to compute the OIDs used in the
sparse-directory entries.
Add an index scan to convert_to_sparse() that will detect if these merge
conflict entries exist and skip the conversion before trying to update
the cache-tree. This is marked as NEEDSWORK because this can be removed
with a suitable update to cache_tree_update() or a similar method that
can construct a cache-tree with invalid nodes, but still allow creating
the nodes necessary for creating sparse directory entries.
It is possible that in the future we will not need to make such an
update, since if we do not expand a sparse-index into a full one, this
conversion does not need to happen. Thus, this can be deferred until the
merge machinery is made to integrate with the sparse-index.
Reviewed-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-07-14 13:12:25 +00:00
|
|
|
/*
|
|
|
|
* NEEDSWORK: If we have unmerged entries, then stay full.
|
|
|
|
* Unmerged entries prevent the cache-tree extension from working.
|
|
|
|
*/
|
|
|
|
if (index_has_unmerged_entries(istate))
|
|
|
|
return 0;
|
|
|
|
|
2021-06-29 02:13:05 +00:00
|
|
|
/* Clear and recompute the cache-tree */
|
|
|
|
cache_tree_free(&istate->cache_tree);
|
sparse-index: convert from full to sparse
If we have a full index, then we can convert it to a sparse index by
replacing directories outside of the sparse cone with sparse directory
entries. The convert_to_sparse() method does this, when the situation is
appropriate.
For now, we avoid converting the index to a sparse index if:
1. the index is split.
2. the index is already sparse.
3. sparse-checkout is disabled.
4. sparse-checkout does not use cone mode.
Finally, we currently limit the conversion to when the
GIT_TEST_SPARSE_INDEX environment variable is enabled. A mode using Git
config will be added in a later change.
The trickiest thing about this conversion is that we might not be able
to mark a directory as a sparse directory just because it is outside the
sparse cone. There might be unmerged files within that directory, so we
need to look for those. Also, if there is some strange reason why a file
is not marked with CE_SKIP_WORKTREE, then we should give up on
converting that directory. There is still hope that some of its
subdirectories might be able to convert to sparse, so we keep looking
deeper.
The conversion process is assisted by the cache-tree extension. This is
calculated from the full index if it does not already exist. We then
abandon the cache-tree as it no longer applies to the newly-sparse
index. Thus, this cache-tree will be recalculated in every
sparse-full-sparse round-trip until we integrate the cache-tree
extension with the sparse index.
Some Git commands use the index after writing it. For example, 'git add'
will update the index, then write it to disk, then read its entries to
report information. To keep the in-memory index in a full state after
writing, we re-expand it to a full one after the write. This is wasteful
for commands that only write the index and do not read from it again,
but that is only the case until we make those commands "sparse aware."
We can compare the behavior of the sparse-index in
t1092-sparse-checkout-compability.sh by using GIT_TEST_SPARSE_INDEX=1
when operating on the 'sparse-index' repo. We can also compare the two
sparse repos directly, such as comparing their indexes (when expanded to
full in the case of the 'sparse-index' repo). We also verify that the
index is actually populated with sparse directory entries.
The 'checkout and reset (mixed)' test is marked for failure when
comparing a sparse repo to a full repo, but we can compare the two
sparse-checkout cases directly to ensure that we are not changing the
behavior when using a sparse index.
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-03-30 13:10:55 +00:00
|
|
|
if (cache_tree_update(istate, 0)) {
|
|
|
|
warning(_("unable to update cache-tree, staying full"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
remove_fsmonitor(istate);
|
|
|
|
|
|
|
|
trace2_region_enter("index", "convert_to_sparse", istate->repo);
|
|
|
|
istate->cache_nr = convert_to_sparse_rec(istate,
|
|
|
|
0, 0, istate->cache_nr,
|
|
|
|
"", 0, istate->cache_tree);
|
2021-03-30 13:11:02 +00:00
|
|
|
|
|
|
|
/* Clear and recompute the cache-tree */
|
|
|
|
cache_tree_free(&istate->cache_tree);
|
|
|
|
cache_tree_update(istate, 0);
|
|
|
|
|
2021-07-14 13:12:39 +00:00
|
|
|
istate->fsmonitor_has_run_once = 0;
|
|
|
|
FREE_AND_NULL(istate->fsmonitor_dirty);
|
|
|
|
FREE_AND_NULL(istate->fsmonitor_last_update);
|
|
|
|
|
sparse-index: convert from full to sparse
If we have a full index, then we can convert it to a sparse index by
replacing directories outside of the sparse cone with sparse directory
entries. The convert_to_sparse() method does this, when the situation is
appropriate.
For now, we avoid converting the index to a sparse index if:
1. the index is split.
2. the index is already sparse.
3. sparse-checkout is disabled.
4. sparse-checkout does not use cone mode.
Finally, we currently limit the conversion to when the
GIT_TEST_SPARSE_INDEX environment variable is enabled. A mode using Git
config will be added in a later change.
The trickiest thing about this conversion is that we might not be able
to mark a directory as a sparse directory just because it is outside the
sparse cone. There might be unmerged files within that directory, so we
need to look for those. Also, if there is some strange reason why a file
is not marked with CE_SKIP_WORKTREE, then we should give up on
converting that directory. There is still hope that some of its
subdirectories might be able to convert to sparse, so we keep looking
deeper.
The conversion process is assisted by the cache-tree extension. This is
calculated from the full index if it does not already exist. We then
abandon the cache-tree as it no longer applies to the newly-sparse
index. Thus, this cache-tree will be recalculated in every
sparse-full-sparse round-trip until we integrate the cache-tree
extension with the sparse index.
Some Git commands use the index after writing it. For example, 'git add'
will update the index, then write it to disk, then read its entries to
report information. To keep the in-memory index in a full state after
writing, we re-expand it to a full one after the write. This is wasteful
for commands that only write the index and do not read from it again,
but that is only the case until we make those commands "sparse aware."
We can compare the behavior of the sparse-index in
t1092-sparse-checkout-compability.sh by using GIT_TEST_SPARSE_INDEX=1
when operating on the 'sparse-index' repo. We can also compare the two
sparse repos directly, such as comparing their indexes (when expanded to
full in the case of the 'sparse-index' repo). We also verify that the
index is actually populated with sparse directory entries.
The 'checkout and reset (mixed)' test is marked for failure when
comparing a sparse repo to a full repo, but we can compare the two
sparse-checkout cases directly to ensure that we are not changing the
behavior when using a sparse index.
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-03-30 13:10:55 +00:00
|
|
|
istate->sparse_index = 1;
|
|
|
|
trace2_region_leave("index", "convert_to_sparse", istate->repo);
|
|
|
|
return 0;
|
|
|
|
}
|
2021-03-30 13:10:48 +00:00
|
|
|
|
|
|
|
static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
|
|
|
|
{
|
|
|
|
ALLOC_GROW(istate->cache, nr + 1, istate->cache_alloc);
|
|
|
|
|
|
|
|
istate->cache[nr] = ce;
|
|
|
|
add_name_hash(istate, ce);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int add_path_to_index(const struct object_id *oid,
|
|
|
|
struct strbuf *base, const char *path,
|
|
|
|
unsigned int mode, void *context)
|
|
|
|
{
|
|
|
|
struct index_state *istate = (struct index_state *)context;
|
|
|
|
struct cache_entry *ce;
|
|
|
|
size_t len = base->len;
|
|
|
|
|
|
|
|
if (S_ISDIR(mode))
|
|
|
|
return READ_TREE_RECURSIVE;
|
|
|
|
|
|
|
|
strbuf_addstr(base, path);
|
|
|
|
|
|
|
|
ce = make_cache_entry(istate, mode, oid, base->buf, 0, 0);
|
2021-07-14 13:12:26 +00:00
|
|
|
ce->ce_flags |= CE_SKIP_WORKTREE | CE_EXTENDED;
|
2021-03-30 13:10:48 +00:00
|
|
|
set_index_entry(istate, istate->cache_nr++, ce);
|
|
|
|
|
|
|
|
strbuf_setlen(base, len);
|
|
|
|
return 0;
|
|
|
|
}
|
2021-03-30 13:10:47 +00:00
|
|
|
|
|
|
|
void ensure_full_index(struct index_state *istate)
|
|
|
|
{
|
2021-03-30 13:10:48 +00:00
|
|
|
int i;
|
|
|
|
struct index_state *full;
|
|
|
|
struct strbuf base = STRBUF_INIT;
|
|
|
|
|
|
|
|
if (!istate || !istate->sparse_index)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!istate->repo)
|
|
|
|
istate->repo = the_repository;
|
|
|
|
|
|
|
|
trace2_region_enter("index", "ensure_full_index", istate->repo);
|
|
|
|
|
|
|
|
/* initialize basics of new index */
|
|
|
|
full = xcalloc(1, sizeof(struct index_state));
|
|
|
|
memcpy(full, istate, sizeof(struct index_state));
|
|
|
|
|
|
|
|
/* then change the necessary things */
|
|
|
|
full->sparse_index = 0;
|
|
|
|
full->cache_alloc = (3 * istate->cache_alloc) / 2;
|
|
|
|
full->cache_nr = 0;
|
|
|
|
ALLOC_ARRAY(full->cache, full->cache_alloc);
|
|
|
|
|
|
|
|
for (i = 0; i < istate->cache_nr; i++) {
|
|
|
|
struct cache_entry *ce = istate->cache[i];
|
|
|
|
struct tree *tree;
|
|
|
|
struct pathspec ps;
|
|
|
|
|
|
|
|
if (!S_ISSPARSEDIR(ce->ce_mode)) {
|
|
|
|
set_index_entry(full, full->cache_nr++, ce);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!(ce->ce_flags & CE_SKIP_WORKTREE))
|
|
|
|
warning(_("index entry is a directory, but not sparse (%08x)"),
|
|
|
|
ce->ce_flags);
|
|
|
|
|
|
|
|
/* recursively walk into cd->name */
|
|
|
|
tree = lookup_tree(istate->repo, &ce->oid);
|
|
|
|
|
|
|
|
memset(&ps, 0, sizeof(ps));
|
|
|
|
ps.recursive = 1;
|
|
|
|
ps.has_wildcard = 1;
|
|
|
|
ps.max_depth = -1;
|
|
|
|
|
|
|
|
strbuf_setlen(&base, 0);
|
|
|
|
strbuf_add(&base, ce->name, strlen(ce->name));
|
|
|
|
|
|
|
|
read_tree_at(istate->repo, tree, &base, &ps,
|
|
|
|
add_path_to_index, full);
|
|
|
|
|
|
|
|
/* free directory entries. full entries are re-used */
|
|
|
|
discard_cache_entry(ce);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Copy back into original index. */
|
|
|
|
memcpy(&istate->name_hash, &full->name_hash, sizeof(full->name_hash));
|
|
|
|
istate->sparse_index = 0;
|
|
|
|
free(istate->cache);
|
|
|
|
istate->cache = full->cache;
|
|
|
|
istate->cache_nr = full->cache_nr;
|
|
|
|
istate->cache_alloc = full->cache_alloc;
|
2021-07-14 13:12:39 +00:00
|
|
|
istate->fsmonitor_has_run_once = 0;
|
|
|
|
FREE_AND_NULL(istate->fsmonitor_dirty);
|
|
|
|
FREE_AND_NULL(istate->fsmonitor_last_update);
|
2021-03-30 13:10:48 +00:00
|
|
|
|
|
|
|
strbuf_release(&base);
|
|
|
|
free(full);
|
|
|
|
|
2021-03-30 13:11:02 +00:00
|
|
|
/* Clear and recompute the cache-tree */
|
|
|
|
cache_tree_free(&istate->cache_tree);
|
|
|
|
cache_tree_update(istate, 0);
|
|
|
|
|
2021-03-30 13:10:48 +00:00
|
|
|
trace2_region_leave("index", "ensure_full_index", istate->repo);
|
2021-03-30 13:10:47 +00:00
|
|
|
}
|
sparse-index: expand_to_path()
Some users of the index API have a specific path they are looking for,
but choose to use index_file_exists() to rely on the name-hash hashtable
instead of doing binary search with index_name_pos(). These users only
need to know a yes/no answer, not a position within the cache array.
When the index is sparse, the name-hash hash table does not contain the
full list of paths within sparse directories. It _does_ contain the
directory names for the sparse-directory entries.
Create a helper function, expand_to_path(), for intended use with the
name-hash hashtable functions. The integration with name-hash.c will
follow in a later change.
The solution here is to use ensure_full_index() when we determine that
the requested path is within a sparse directory entry. This will
populate the name-hash hashtable as the index is recomputed from
scratch.
There may be cases where the caller is trying to find an untracked path
that is not in the index but also is not within a sparse directory
entry. We want to minimize the overhead for these requests. If we used
index_name_pos() to find the insertion order of the path, then we could
determine from that position if a sparse-directory exists. (In fact,
just calling index_name_pos() in that case would lead to expanding the
index to a full index.) However, this takes O(log N) time where N is the
number of cache entries.
To keep the performance of this call based mostly on the input string,
use index_file_exists() to look for the ancestors of the path. Using the
heuristic that a sparse directory is likely to have a small number of
parent directories, we start from the bottom and build up. Use a string
buffer to allow mutating the path name to terminate after each slash for
each hashset test.
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Reviewed-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-04-12 21:08:16 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* This static global helps avoid infinite recursion between
|
|
|
|
* expand_to_path() and index_file_exists().
|
|
|
|
*/
|
|
|
|
static int in_expand_to_path = 0;
|
|
|
|
|
|
|
|
void expand_to_path(struct index_state *istate,
|
|
|
|
const char *path, size_t pathlen, int icase)
|
|
|
|
{
|
|
|
|
struct strbuf path_mutable = STRBUF_INIT;
|
|
|
|
size_t substr_len;
|
|
|
|
|
|
|
|
/* prevent extra recursion */
|
|
|
|
if (in_expand_to_path)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!istate || !istate->sparse_index)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!istate->repo)
|
|
|
|
istate->repo = the_repository;
|
|
|
|
|
|
|
|
in_expand_to_path = 1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We only need to actually expand a region if the
|
|
|
|
* following are both true:
|
|
|
|
*
|
|
|
|
* 1. 'path' is not already in the index.
|
|
|
|
* 2. Some parent directory of 'path' is a sparse directory.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (index_file_exists(istate, path, pathlen, icase))
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
strbuf_add(&path_mutable, path, pathlen);
|
|
|
|
strbuf_addch(&path_mutable, '/');
|
|
|
|
|
|
|
|
/* Check the name hash for all parent directories */
|
|
|
|
substr_len = 0;
|
|
|
|
while (substr_len < pathlen) {
|
|
|
|
char temp;
|
|
|
|
char *replace = strchr(path_mutable.buf + substr_len, '/');
|
|
|
|
|
|
|
|
if (!replace)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* replace the character _after_ the slash */
|
|
|
|
replace++;
|
|
|
|
temp = *replace;
|
|
|
|
*replace = '\0';
|
|
|
|
if (index_file_exists(istate, path_mutable.buf,
|
|
|
|
path_mutable.len, icase)) {
|
|
|
|
/*
|
|
|
|
* We found a parent directory in the name-hash
|
|
|
|
* hashtable, because only sparse directory entries
|
|
|
|
* have a trailing '/' character. Since "path" wasn't
|
|
|
|
* in the index, perhaps it exists within this
|
|
|
|
* sparse-directory. Expand accordingly.
|
|
|
|
*/
|
|
|
|
ensure_full_index(istate);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
*replace = temp;
|
|
|
|
substr_len = replace - path_mutable.buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
strbuf_release(&path_mutable);
|
|
|
|
in_expand_to_path = 0;
|
|
|
|
}
|