Merge branch 'ds/sparse-index-protections'
Builds on top of the sparse-index infrastructure to mark operations that are not ready to mark with the sparse index, causing them to fall back on fully-populated index that they always have worked with. * ds/sparse-index-protections: (47 commits) name-hash: use expand_to_path() sparse-index: expand_to_path() name-hash: don't add directories to name_hash revision: ensure full index resolve-undo: ensure full index read-cache: ensure full index pathspec: ensure full index merge-recursive: ensure full index entry: ensure full index dir: ensure full index update-index: ensure full index stash: ensure full index rm: ensure full index merge-index: ensure full index ls-files: ensure full index grep: ensure full index fsck: ensure full index difftool: ensure full index commit: ensure full index checkout: ensure full index ...
This commit is contained in:
commit
8e97852919
@ -14,6 +14,11 @@ index.recordOffsetTable::
|
|||||||
Defaults to 'true' if index.threads has been explicitly enabled,
|
Defaults to 'true' if index.threads has been explicitly enabled,
|
||||||
'false' otherwise.
|
'false' otherwise.
|
||||||
|
|
||||||
|
index.sparse::
|
||||||
|
When enabled, write the index using sparse-directory entries. This
|
||||||
|
has no effect unless `core.sparseCheckout` and
|
||||||
|
`core.sparseCheckoutCone` are both enabled. Defaults to 'false'.
|
||||||
|
|
||||||
index.threads::
|
index.threads::
|
||||||
Specifies the number of threads to spawn when loading the index.
|
Specifies the number of threads to spawn when loading the index.
|
||||||
This is meant to reduce index load time on multiprocessor machines.
|
This is meant to reduce index load time on multiprocessor machines.
|
||||||
|
@ -45,6 +45,20 @@ To avoid interfering with other worktrees, it first enables the
|
|||||||
When `--cone` is provided, the `core.sparseCheckoutCone` setting is
|
When `--cone` is provided, the `core.sparseCheckoutCone` setting is
|
||||||
also set, allowing for better performance with a limited set of
|
also set, allowing for better performance with a limited set of
|
||||||
patterns (see 'CONE PATTERN SET' below).
|
patterns (see 'CONE PATTERN SET' below).
|
||||||
|
+
|
||||||
|
Use the `--[no-]sparse-index` option to toggle the use of the sparse
|
||||||
|
index format. This reduces the size of the index to be more closely
|
||||||
|
aligned with your sparse-checkout definition. This can have significant
|
||||||
|
performance advantages for commands such as `git status` or `git add`.
|
||||||
|
This feature is still experimental. Some commands might be slower with
|
||||||
|
a sparse index until they are properly integrated with the feature.
|
||||||
|
+
|
||||||
|
**WARNING:** Using a sparse index requires modifying the index in a way
|
||||||
|
that is not completely understood by external tools. If you have trouble
|
||||||
|
with this compatibility, then run `git sparse-checkout init --no-sparse-index`
|
||||||
|
to rewrite your index to not be sparse. Older versions of Git will not
|
||||||
|
understand the sparse directory entries index extension and may fail to
|
||||||
|
interact with your repository until it is disabled.
|
||||||
|
|
||||||
'set'::
|
'set'::
|
||||||
Write a set of patterns to the sparse-checkout file, as given as
|
Write a set of patterns to the sparse-checkout file, as given as
|
||||||
|
@ -44,6 +44,13 @@ Git index format
|
|||||||
localization, no special casing of directory separator '/'). Entries
|
localization, no special casing of directory separator '/'). Entries
|
||||||
with the same name are sorted by their stage field.
|
with the same name are sorted by their stage field.
|
||||||
|
|
||||||
|
An index entry typically represents a file. However, if sparse-checkout
|
||||||
|
is enabled in cone mode (`core.sparseCheckoutCone` is enabled) and the
|
||||||
|
`extensions.sparseIndex` extension is enabled, then the index may
|
||||||
|
contain entries for directories outside of the sparse-checkout definition.
|
||||||
|
These entries have mode `040000`, include the `SKIP_WORKTREE` bit, and
|
||||||
|
the path ends in a directory separator.
|
||||||
|
|
||||||
32-bit ctime seconds, the last time a file's metadata changed
|
32-bit ctime seconds, the last time a file's metadata changed
|
||||||
this is stat(2) data
|
this is stat(2) data
|
||||||
|
|
||||||
@ -385,3 +392,15 @@ The remaining data of each directory block is grouped by type:
|
|||||||
in this block of entries.
|
in this block of entries.
|
||||||
|
|
||||||
- 32-bit count of cache entries in this block
|
- 32-bit count of cache entries in this block
|
||||||
|
|
||||||
|
== Sparse Directory Entries
|
||||||
|
|
||||||
|
When using sparse-checkout in cone mode, some entire directories within
|
||||||
|
the index can be summarized by pointing to a tree object instead of the
|
||||||
|
entire expanded list of paths within that tree. An index containing such
|
||||||
|
entries is a "sparse index". Index format versions 4 and less were not
|
||||||
|
implemented with such entries in mind. Thus, for these versions, an
|
||||||
|
index containing sparse directory entries will include this extension
|
||||||
|
with signature { 's', 'd', 'i', 'r' }. Like the split-index extension,
|
||||||
|
tools should avoid interacting with a sparse index unless they understand
|
||||||
|
this extension.
|
||||||
|
208
Documentation/technical/sparse-index.txt
Normal file
208
Documentation/technical/sparse-index.txt
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
Git Sparse-Index Design Document
|
||||||
|
================================
|
||||||
|
|
||||||
|
The sparse-checkout feature allows users to focus a working directory on
|
||||||
|
a subset of the files at HEAD. The cone mode patterns, enabled by
|
||||||
|
`core.sparseCheckoutCone`, allow for very fast pattern matching to
|
||||||
|
discover which files at HEAD belong in the sparse-checkout cone.
|
||||||
|
|
||||||
|
Three important scale dimensions for a Git working directory are:
|
||||||
|
|
||||||
|
* `HEAD`: How many files are present at `HEAD`?
|
||||||
|
|
||||||
|
* Populated: How many files are within the sparse-checkout cone.
|
||||||
|
|
||||||
|
* Modified: How many files has the user modified in the working directory?
|
||||||
|
|
||||||
|
We will use big-O notation -- O(X) -- to denote how expensive certain
|
||||||
|
operations are in terms of these dimensions.
|
||||||
|
|
||||||
|
These dimensions are ordered by their magnitude: users (typically) modify
|
||||||
|
fewer files than are populated, and we can only populate files at `HEAD`.
|
||||||
|
|
||||||
|
Problems occur if there is an extreme imbalance in these dimensions. For
|
||||||
|
example, if `HEAD` contains millions of paths but the populated set has
|
||||||
|
only tens of thousands, then commands like `git status` and `git add` can
|
||||||
|
be dominated by operations that require O(`HEAD`) operations instead of
|
||||||
|
O(Populated). Primarily, the cost is in parsing and rewriting the index,
|
||||||
|
which is filled primarily with files at `HEAD` that are marked with the
|
||||||
|
`SKIP_WORKTREE` bit.
|
||||||
|
|
||||||
|
The sparse-index intends to take these commands that read and modify the
|
||||||
|
index from O(`HEAD`) to O(Populated). To do this, we need to modify the
|
||||||
|
index format in a significant way: add "sparse directory" entries.
|
||||||
|
|
||||||
|
With cone mode patterns, it is possible to detect when an entire
|
||||||
|
directory will have its contents outside of the sparse-checkout definition.
|
||||||
|
Instead of listing all of the files it contains as individual entries, a
|
||||||
|
sparse-index contains an entry with the directory name, referencing the
|
||||||
|
object ID of the tree at `HEAD` and marked with the `SKIP_WORKTREE` bit.
|
||||||
|
If we need to discover the details for paths within that directory, we
|
||||||
|
can parse trees to find that list.
|
||||||
|
|
||||||
|
At time of writing, sparse-directory entries violate expectations about the
|
||||||
|
index format and its in-memory data structure. There are many consumers in
|
||||||
|
the codebase that expect to iterate through all of the index entries and
|
||||||
|
see only files. In fact, these loops expect to see a reference to every
|
||||||
|
staged file. One way to handle this is to parse trees to replace a
|
||||||
|
sparse-directory entry with all of the files within that tree as the index
|
||||||
|
is loaded. However, parsing trees is slower than parsing the index format,
|
||||||
|
so that is a slower operation than if we left the index alone. The plan is
|
||||||
|
to make all of these integrations "sparse aware" so this expansion through
|
||||||
|
tree parsing is unnecessary and they use fewer resources than when using a
|
||||||
|
full index.
|
||||||
|
|
||||||
|
The implementation plan below follows four phases to slowly integrate with
|
||||||
|
the sparse-index. The intention is to incrementally update Git commands to
|
||||||
|
interact safely with the sparse-index without significant slowdowns. This
|
||||||
|
may not always be possible, but the hope is that the primary commands that
|
||||||
|
users need in their daily work are dramatically improved.
|
||||||
|
|
||||||
|
Phase I: Format and initial speedups
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
During this phase, Git learns to enable the sparse-index and safely parse
|
||||||
|
one. Protections are put in place so that every consumer of the in-memory
|
||||||
|
data structure can operate with its current assumption of every file at
|
||||||
|
`HEAD`.
|
||||||
|
|
||||||
|
At first, every index parse will call a helper method,
|
||||||
|
`ensure_full_index()`, which scans the index for sparse-directory entries
|
||||||
|
(pointing to trees) and replaces them with the full list of paths (with
|
||||||
|
blob contents) by parsing tree objects. This will be slower in all cases.
|
||||||
|
The only noticeable change in behavior will be that the serialized index
|
||||||
|
file contains sparse-directory entries.
|
||||||
|
|
||||||
|
To start, we use a new required index extension, `sdir`, to allow
|
||||||
|
inserting sparse-directory entries into indexes with file format
|
||||||
|
versions 2, 3, and 4. This prevents Git versions that do not understand
|
||||||
|
the sparse-index from operating on one, while allowing tools that do not
|
||||||
|
understand the sparse-index to operate on repositories as long as they do
|
||||||
|
not interact with the index. A new format, index v5, will be introduced
|
||||||
|
that includes sparse-directory entries by default. It might also
|
||||||
|
introduce other features that have been considered for improving the
|
||||||
|
index, as well.
|
||||||
|
|
||||||
|
Next, consumers of the index will be guarded against operating on a
|
||||||
|
sparse-index by inserting calls to `ensure_full_index()` or
|
||||||
|
`expand_index_to_path()`. If a specific path is requested, then those will
|
||||||
|
be protected from within the `index_file_exists()` and `index_name_pos()`
|
||||||
|
API calls: they will call `ensure_full_index()` if necessary. The
|
||||||
|
intention here is to preserve existing behavior when interacting with a
|
||||||
|
sparse-checkout. We don't want a change to happen by accident, without
|
||||||
|
tests. Many of these locations may not need any change before removing the
|
||||||
|
guards, but we should not do so without tests to ensure the expected
|
||||||
|
behavior happens.
|
||||||
|
|
||||||
|
It may be desirable to _change_ the behavior of some commands in the
|
||||||
|
presence of a sparse index or more generally in any sparse-checkout
|
||||||
|
scenario. In such cases, these should be carefully communicated and
|
||||||
|
tested. No such behavior changes are intended during this phase.
|
||||||
|
|
||||||
|
During a scan of the codebase, not every iteration of the cache entries
|
||||||
|
needs an `ensure_full_index()` check. The basic reasons include:
|
||||||
|
|
||||||
|
1. The loop is scanning for entries with non-zero stage. These entries
|
||||||
|
are not collapsed into a sparse-directory entry.
|
||||||
|
|
||||||
|
2. The loop is scanning for submodules. These entries are not collapsed
|
||||||
|
into a sparse-directory entry.
|
||||||
|
|
||||||
|
3. The loop is part of the index API, especially around reading or
|
||||||
|
writing the format.
|
||||||
|
|
||||||
|
4. The loop is checking for correct order of cache entries and that is
|
||||||
|
correct if and only if the sparse-directory entries are in the correct
|
||||||
|
location.
|
||||||
|
|
||||||
|
5. The loop ignores entries with the `SKIP_WORKTREE` bit set, or is
|
||||||
|
otherwise already aware of sparse directory entries.
|
||||||
|
|
||||||
|
6. The sparse-index is disabled at this point when using the split-index
|
||||||
|
feature, so no effort is made to protect the split-index API.
|
||||||
|
|
||||||
|
Even after inserting these guards, we will keep expanding sparse-indexes
|
||||||
|
for most Git commands using the `command_requires_full_index` repository
|
||||||
|
setting. This setting will be on by default and disabled one builtin at a
|
||||||
|
time until we have sufficient confidence that all of the index operations
|
||||||
|
are properly guarded.
|
||||||
|
|
||||||
|
To complete this phase, the commands `git status` and `git add` will be
|
||||||
|
integrated with the sparse-index so that they operate with O(Populated)
|
||||||
|
performance. They will be carefully tested for operations within and
|
||||||
|
outside the sparse-checkout definition.
|
||||||
|
|
||||||
|
Phase II: Careful integrations
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
This phase focuses on ensuring that all index extensions and APIs work
|
||||||
|
well with a sparse-index. This requires significant increases to our test
|
||||||
|
coverage, especially for operations that interact with the working
|
||||||
|
directory outside of the sparse-checkout definition. Some of these
|
||||||
|
behaviors may not be the desirable ones, such as some tests already
|
||||||
|
marked for failure in `t1092-sparse-checkout-compatibility.sh`.
|
||||||
|
|
||||||
|
The index extensions that may require special integrations are:
|
||||||
|
|
||||||
|
* FS Monitor
|
||||||
|
* Untracked cache
|
||||||
|
|
||||||
|
While integrating with these features, we should look for patterns that
|
||||||
|
might lead to better APIs for interacting with the index. Coalescing
|
||||||
|
common usage patterns into an API call can reduce the number of places
|
||||||
|
where sparse-directories need to be handled carefully.
|
||||||
|
|
||||||
|
Phase III: Important command speedups
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
At this point, the patterns for testing and implementing sparse-directory
|
||||||
|
logic should be relatively stable. This phase focuses on updating some of
|
||||||
|
the most common builtins that use the index to operate as O(Populated).
|
||||||
|
Here is a potential list of commands that could be valuable to integrate
|
||||||
|
at this point:
|
||||||
|
|
||||||
|
* `git commit`
|
||||||
|
* `git checkout`
|
||||||
|
* `git merge`
|
||||||
|
* `git rebase`
|
||||||
|
|
||||||
|
Hopefully, commands such as `git merge` and `git rebase` can benefit
|
||||||
|
instead from merge algorithms that do not use the index as a data
|
||||||
|
structure, such as the merge-ORT strategy. As these topics mature, we
|
||||||
|
may enable the ORT strategy by default for repositories using the
|
||||||
|
sparse-index feature.
|
||||||
|
|
||||||
|
Along with `git status` and `git add`, these commands cover the majority
|
||||||
|
of users' interactions with the working directory. In addition, we can
|
||||||
|
integrate with these commands:
|
||||||
|
|
||||||
|
* `git grep`
|
||||||
|
* `git rm`
|
||||||
|
|
||||||
|
These have been proposed as some whose behavior could change when in a
|
||||||
|
repo with a sparse-checkout definition. It would be good to include this
|
||||||
|
behavior automatically when using a sparse-index. Some clarity is needed
|
||||||
|
to make the behavior switch clear to the user.
|
||||||
|
|
||||||
|
This phase is the first where parallel work might be possible without too
|
||||||
|
much conflicts between topics.
|
||||||
|
|
||||||
|
Phase IV: The long tail
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
This last phase is less a "phase" and more "the new normal" after all of
|
||||||
|
the previous work.
|
||||||
|
|
||||||
|
To start, the `command_requires_full_index` option could be removed in
|
||||||
|
favor of expanding only when hitting an API guard.
|
||||||
|
|
||||||
|
There are many Git commands that could use special attention to operate as
|
||||||
|
O(Populated), while some might be so rare that it is acceptable to leave
|
||||||
|
them with additional overhead when a sparse-index is present.
|
||||||
|
|
||||||
|
Here are some commands that might be useful to update:
|
||||||
|
|
||||||
|
* `git sparse-checkout set`
|
||||||
|
* `git am`
|
||||||
|
* `git clean`
|
||||||
|
* `git stash`
|
1
Makefile
1
Makefile
@ -995,6 +995,7 @@ LIB_OBJS += setup.o
|
|||||||
LIB_OBJS += shallow.o
|
LIB_OBJS += shallow.o
|
||||||
LIB_OBJS += sideband.o
|
LIB_OBJS += sideband.o
|
||||||
LIB_OBJS += sigchain.o
|
LIB_OBJS += sigchain.o
|
||||||
|
LIB_OBJS += sparse-index.o
|
||||||
LIB_OBJS += split-index.o
|
LIB_OBJS += split-index.o
|
||||||
LIB_OBJS += stable-qsort.o
|
LIB_OBJS += stable-qsort.o
|
||||||
LIB_OBJS += strbuf.o
|
LIB_OBJS += strbuf.o
|
||||||
|
14
attr.c
14
attr.c
@ -733,7 +733,7 @@ static struct attr_stack *read_attr_from_file(const char *path, unsigned flags)
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct attr_stack *read_attr_from_index(const struct index_state *istate,
|
static struct attr_stack *read_attr_from_index(struct index_state *istate,
|
||||||
const char *path,
|
const char *path,
|
||||||
unsigned flags)
|
unsigned flags)
|
||||||
{
|
{
|
||||||
@ -763,7 +763,7 @@ static struct attr_stack *read_attr_from_index(const struct index_state *istate,
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct attr_stack *read_attr(const struct index_state *istate,
|
static struct attr_stack *read_attr(struct index_state *istate,
|
||||||
const char *path, unsigned flags)
|
const char *path, unsigned flags)
|
||||||
{
|
{
|
||||||
struct attr_stack *res = NULL;
|
struct attr_stack *res = NULL;
|
||||||
@ -855,7 +855,7 @@ static void push_stack(struct attr_stack **attr_stack_p,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bootstrap_attr_stack(const struct index_state *istate,
|
static void bootstrap_attr_stack(struct index_state *istate,
|
||||||
struct attr_stack **stack)
|
struct attr_stack **stack)
|
||||||
{
|
{
|
||||||
struct attr_stack *e;
|
struct attr_stack *e;
|
||||||
@ -894,7 +894,7 @@ static void bootstrap_attr_stack(const struct index_state *istate,
|
|||||||
push_stack(stack, e, NULL, 0);
|
push_stack(stack, e, NULL, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void prepare_attr_stack(const struct index_state *istate,
|
static void prepare_attr_stack(struct index_state *istate,
|
||||||
const char *path, int dirlen,
|
const char *path, int dirlen,
|
||||||
struct attr_stack **stack)
|
struct attr_stack **stack)
|
||||||
{
|
{
|
||||||
@ -1094,7 +1094,7 @@ static void determine_macros(struct all_attrs_item *all_attrs,
|
|||||||
* If check->check_nr is non-zero, only attributes in check[] are collected.
|
* If check->check_nr is non-zero, only attributes in check[] are collected.
|
||||||
* Otherwise all attributes are collected.
|
* Otherwise all attributes are collected.
|
||||||
*/
|
*/
|
||||||
static void collect_some_attrs(const struct index_state *istate,
|
static void collect_some_attrs(struct index_state *istate,
|
||||||
const char *path,
|
const char *path,
|
||||||
struct attr_check *check)
|
struct attr_check *check)
|
||||||
{
|
{
|
||||||
@ -1123,7 +1123,7 @@ static void collect_some_attrs(const struct index_state *istate,
|
|||||||
fill(path, pathlen, basename_offset, check->stack, check->all_attrs, rem);
|
fill(path, pathlen, basename_offset, check->stack, check->all_attrs, rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
void git_check_attr(const struct index_state *istate,
|
void git_check_attr(struct index_state *istate,
|
||||||
const char *path,
|
const char *path,
|
||||||
struct attr_check *check)
|
struct attr_check *check)
|
||||||
{
|
{
|
||||||
@ -1140,7 +1140,7 @@ void git_check_attr(const struct index_state *istate,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void git_all_attrs(const struct index_state *istate,
|
void git_all_attrs(struct index_state *istate,
|
||||||
const char *path, struct attr_check *check)
|
const char *path, struct attr_check *check)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
4
attr.h
4
attr.h
@ -190,14 +190,14 @@ void attr_check_free(struct attr_check *check);
|
|||||||
*/
|
*/
|
||||||
const char *git_attr_name(const struct git_attr *);
|
const char *git_attr_name(const struct git_attr *);
|
||||||
|
|
||||||
void git_check_attr(const struct index_state *istate,
|
void git_check_attr(struct index_state *istate,
|
||||||
const char *path, struct attr_check *check);
|
const char *path, struct attr_check *check);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Retrieve all attributes that apply to the specified path.
|
* Retrieve all attributes that apply to the specified path.
|
||||||
* check holds the attributes and their values.
|
* check holds the attributes and their values.
|
||||||
*/
|
*/
|
||||||
void git_all_attrs(const struct index_state *istate,
|
void git_all_attrs(struct index_state *istate,
|
||||||
const char *path, struct attr_check *check);
|
const char *path, struct attr_check *check);
|
||||||
|
|
||||||
enum git_attr_direction {
|
enum git_attr_direction {
|
||||||
|
@ -141,6 +141,8 @@ static int renormalize_tracked_files(const struct pathspec *pathspec, int flags)
|
|||||||
{
|
{
|
||||||
int i, retval = 0;
|
int i, retval = 0;
|
||||||
|
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(&the_index);
|
||||||
for (i = 0; i < active_nr; i++) {
|
for (i = 0; i < active_nr; i++) {
|
||||||
struct cache_entry *ce = active_cache[i];
|
struct cache_entry *ce = active_cache[i];
|
||||||
|
|
||||||
|
@ -120,6 +120,8 @@ static void checkout_all(const char *prefix, int prefix_length)
|
|||||||
int i, errs = 0;
|
int i, errs = 0;
|
||||||
struct cache_entry *last_ce = NULL;
|
struct cache_entry *last_ce = NULL;
|
||||||
|
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(&the_index);
|
||||||
for (i = 0; i < active_nr ; i++) {
|
for (i = 0; i < active_nr ; i++) {
|
||||||
struct cache_entry *ce = active_cache[i];
|
struct cache_entry *ce = active_cache[i];
|
||||||
if (ce_stage(ce) != checkout_stage
|
if (ce_stage(ce) != checkout_stage
|
||||||
|
@ -369,6 +369,9 @@ static int checkout_worktree(const struct checkout_opts *opts,
|
|||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
enable_delayed_checkout(&state);
|
enable_delayed_checkout(&state);
|
||||||
|
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(&the_index);
|
||||||
for (pos = 0; pos < active_nr; pos++) {
|
for (pos = 0; pos < active_nr; pos++) {
|
||||||
struct cache_entry *ce = active_cache[pos];
|
struct cache_entry *ce = active_cache[pos];
|
||||||
if (ce->ce_flags & CE_MATCHED) {
|
if (ce->ce_flags & CE_MATCHED) {
|
||||||
@ -513,6 +516,8 @@ static int checkout_paths(const struct checkout_opts *opts,
|
|||||||
* Make sure all pathspecs participated in locating the paths
|
* Make sure all pathspecs participated in locating the paths
|
||||||
* to be checked out.
|
* to be checked out.
|
||||||
*/
|
*/
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(&the_index);
|
||||||
for (pos = 0; pos < active_nr; pos++)
|
for (pos = 0; pos < active_nr; pos++)
|
||||||
if (opts->overlay_mode)
|
if (opts->overlay_mode)
|
||||||
mark_ce_for_checkout_overlay(active_cache[pos],
|
mark_ce_for_checkout_overlay(active_cache[pos],
|
||||||
|
@ -261,6 +261,8 @@ static int list_paths(struct string_list *list, const char *with_tree,
|
|||||||
free(max_prefix);
|
free(max_prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(&the_index);
|
||||||
for (i = 0; i < active_nr; i++) {
|
for (i = 0; i < active_nr; i++) {
|
||||||
const struct cache_entry *ce = active_cache[i];
|
const struct cache_entry *ce = active_cache[i];
|
||||||
struct string_list_item *item;
|
struct string_list_item *item;
|
||||||
@ -976,6 +978,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
|
|||||||
if (get_oid(parent, &oid)) {
|
if (get_oid(parent, &oid)) {
|
||||||
int i, ita_nr = 0;
|
int i, ita_nr = 0;
|
||||||
|
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(&the_index);
|
||||||
for (i = 0; i < active_nr; i++)
|
for (i = 0; i < active_nr; i++)
|
||||||
if (ce_intent_to_add(active_cache[i]))
|
if (ce_intent_to_add(active_cache[i]))
|
||||||
ita_nr++;
|
ita_nr++;
|
||||||
|
@ -585,6 +585,9 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
|
|||||||
setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
|
setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
|
||||||
rc = run_command_v_opt(helper_argv, flags);
|
rc = run_command_v_opt(helper_argv, flags);
|
||||||
|
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(&wtindex);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the diff includes working copy files and those
|
* If the diff includes working copy files and those
|
||||||
* files were modified during the diff, then the changes
|
* files were modified during the diff, then the changes
|
||||||
|
@ -881,6 +881,8 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
|
|||||||
verify_index_checksum = 1;
|
verify_index_checksum = 1;
|
||||||
verify_ce_order = 1;
|
verify_ce_order = 1;
|
||||||
read_cache();
|
read_cache();
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(&the_index);
|
||||||
for (i = 0; i < active_nr; i++) {
|
for (i = 0; i < active_nr; i++) {
|
||||||
unsigned int mode;
|
unsigned int mode;
|
||||||
struct blob *blob;
|
struct blob *blob;
|
||||||
|
@ -504,6 +504,8 @@ static int grep_cache(struct grep_opt *opt,
|
|||||||
if (repo_read_index(repo) < 0)
|
if (repo_read_index(repo) < 0)
|
||||||
die(_("index file corrupt"));
|
die(_("index file corrupt"));
|
||||||
|
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(repo->index);
|
||||||
for (nr = 0; nr < repo->index->cache_nr; nr++) {
|
for (nr = 0; nr < repo->index->cache_nr; nr++) {
|
||||||
const struct cache_entry *ce = repo->index->cache[nr];
|
const struct cache_entry *ce = repo->index->cache[nr];
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ static const char *tag_modified = "";
|
|||||||
static const char *tag_skip_worktree = "";
|
static const char *tag_skip_worktree = "";
|
||||||
static const char *tag_resolve_undo = "";
|
static const char *tag_resolve_undo = "";
|
||||||
|
|
||||||
static void write_eolinfo(const struct index_state *istate,
|
static void write_eolinfo(struct index_state *istate,
|
||||||
const struct cache_entry *ce, const char *path)
|
const struct cache_entry *ce, const char *path)
|
||||||
{
|
{
|
||||||
if (show_eol) {
|
if (show_eol) {
|
||||||
@ -122,7 +122,7 @@ static void print_debug(const struct cache_entry *ce)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void show_dir_entry(const struct index_state *istate,
|
static void show_dir_entry(struct index_state *istate,
|
||||||
const char *tag, struct dir_entry *ent)
|
const char *tag, struct dir_entry *ent)
|
||||||
{
|
{
|
||||||
int len = max_prefix_len;
|
int len = max_prefix_len;
|
||||||
@ -139,7 +139,7 @@ static void show_dir_entry(const struct index_state *istate,
|
|||||||
write_name(ent->name);
|
write_name(ent->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void show_other_files(const struct index_state *istate,
|
static void show_other_files(struct index_state *istate,
|
||||||
const struct dir_struct *dir)
|
const struct dir_struct *dir)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
@ -152,7 +152,7 @@ static void show_other_files(const struct index_state *istate,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void show_killed_files(const struct index_state *istate,
|
static void show_killed_files(struct index_state *istate,
|
||||||
const struct dir_struct *dir)
|
const struct dir_struct *dir)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
@ -254,7 +254,7 @@ static void show_ce(struct repository *repo, struct dir_struct *dir,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void show_ru_info(const struct index_state *istate)
|
static void show_ru_info(struct index_state *istate)
|
||||||
{
|
{
|
||||||
struct string_list_item *item;
|
struct string_list_item *item;
|
||||||
|
|
||||||
@ -317,6 +317,8 @@ static void show_files(struct repository *repo, struct dir_struct *dir)
|
|||||||
|
|
||||||
if (!(show_cached || show_stage || show_deleted || show_modified))
|
if (!(show_cached || show_stage || show_deleted || show_modified))
|
||||||
return;
|
return;
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(repo->index);
|
||||||
for (i = 0; i < repo->index->cache_nr; i++) {
|
for (i = 0; i < repo->index->cache_nr; i++) {
|
||||||
const struct cache_entry *ce = repo->index->cache[i];
|
const struct cache_entry *ce = repo->index->cache[i];
|
||||||
struct stat st;
|
struct stat st;
|
||||||
@ -494,6 +496,8 @@ void overlay_tree_on_index(struct index_state *istate,
|
|||||||
die("bad tree-ish %s", tree_name);
|
die("bad tree-ish %s", tree_name);
|
||||||
|
|
||||||
/* Hoist the unmerged entries up to stage #3 to make room */
|
/* Hoist the unmerged entries up to stage #3 to make room */
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(istate);
|
||||||
for (i = 0; i < istate->cache_nr; i++) {
|
for (i = 0; i < istate->cache_nr; i++) {
|
||||||
struct cache_entry *ce = istate->cache[i];
|
struct cache_entry *ce = istate->cache[i];
|
||||||
if (!ce_stage(ce))
|
if (!ce_stage(ce))
|
||||||
|
@ -58,6 +58,8 @@ static void merge_one_path(const char *path)
|
|||||||
static void merge_all(void)
|
static void merge_all(void)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(&the_index);
|
||||||
for (i = 0; i < active_nr; i++) {
|
for (i = 0; i < active_nr; i++) {
|
||||||
const struct cache_entry *ce = active_cache[i];
|
const struct cache_entry *ce = active_cache[i];
|
||||||
if (!ce_stage(ce))
|
if (!ce_stage(ce))
|
||||||
@ -80,6 +82,9 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)
|
|||||||
|
|
||||||
read_cache();
|
read_cache();
|
||||||
|
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(&the_index);
|
||||||
|
|
||||||
i = 1;
|
i = 1;
|
||||||
if (!strcmp(argv[i], "-o")) {
|
if (!strcmp(argv[i], "-o")) {
|
||||||
one_shot = 1;
|
one_shot = 1;
|
||||||
|
@ -293,6 +293,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
|
|||||||
|
|
||||||
seen = xcalloc(pathspec.nr, 1);
|
seen = xcalloc(pathspec.nr, 1);
|
||||||
|
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(&the_index);
|
||||||
for (i = 0; i < active_nr; i++) {
|
for (i = 0; i < active_nr; i++) {
|
||||||
const struct cache_entry *ce = active_cache[i];
|
const struct cache_entry *ce = active_cache[i];
|
||||||
if (!ce_path_match(&the_index, ce, &pathspec, seen))
|
if (!ce_path_match(&the_index, ce, &pathspec, seen))
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include "unpack-trees.h"
|
#include "unpack-trees.h"
|
||||||
#include "wt-status.h"
|
#include "wt-status.h"
|
||||||
#include "quote.h"
|
#include "quote.h"
|
||||||
|
#include "sparse-index.h"
|
||||||
|
|
||||||
static const char *empty_base = "";
|
static const char *empty_base = "";
|
||||||
|
|
||||||
@ -110,6 +111,8 @@ static int update_working_directory(struct pattern_list *pl)
|
|||||||
if (is_index_unborn(r->index))
|
if (is_index_unborn(r->index))
|
||||||
return UPDATE_SPARSITY_SUCCESS;
|
return UPDATE_SPARSITY_SUCCESS;
|
||||||
|
|
||||||
|
r->index->sparse_checkout_patterns = pl;
|
||||||
|
|
||||||
memset(&o, 0, sizeof(o));
|
memset(&o, 0, sizeof(o));
|
||||||
o.verbose_update = isatty(2);
|
o.verbose_update = isatty(2);
|
||||||
o.update = 1;
|
o.update = 1;
|
||||||
@ -138,6 +141,7 @@ static int update_working_directory(struct pattern_list *pl)
|
|||||||
else
|
else
|
||||||
rollback_lock_file(&lock_file);
|
rollback_lock_file(&lock_file);
|
||||||
|
|
||||||
|
r->index->sparse_checkout_patterns = NULL;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,16 +280,20 @@ static int set_config(enum sparse_checkout_mode mode)
|
|||||||
"core.sparseCheckoutCone",
|
"core.sparseCheckoutCone",
|
||||||
mode == MODE_CONE_PATTERNS ? "true" : NULL);
|
mode == MODE_CONE_PATTERNS ? "true" : NULL);
|
||||||
|
|
||||||
|
if (mode == MODE_NO_PATTERNS)
|
||||||
|
set_sparse_index_config(the_repository, 0);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char const * const builtin_sparse_checkout_init_usage[] = {
|
static char const * const builtin_sparse_checkout_init_usage[] = {
|
||||||
N_("git sparse-checkout init [--cone]"),
|
N_("git sparse-checkout init [--cone] [--[no-]sparse-index]"),
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct sparse_checkout_init_opts {
|
static struct sparse_checkout_init_opts {
|
||||||
int cone_mode;
|
int cone_mode;
|
||||||
|
int sparse_index;
|
||||||
} init_opts;
|
} init_opts;
|
||||||
|
|
||||||
static int sparse_checkout_init(int argc, const char **argv)
|
static int sparse_checkout_init(int argc, const char **argv)
|
||||||
@ -300,11 +308,15 @@ static int sparse_checkout_init(int argc, const char **argv)
|
|||||||
static struct option builtin_sparse_checkout_init_options[] = {
|
static struct option builtin_sparse_checkout_init_options[] = {
|
||||||
OPT_BOOL(0, "cone", &init_opts.cone_mode,
|
OPT_BOOL(0, "cone", &init_opts.cone_mode,
|
||||||
N_("initialize the sparse-checkout in cone mode")),
|
N_("initialize the sparse-checkout in cone mode")),
|
||||||
|
OPT_BOOL(0, "sparse-index", &init_opts.sparse_index,
|
||||||
|
N_("toggle the use of a sparse index")),
|
||||||
OPT_END(),
|
OPT_END(),
|
||||||
};
|
};
|
||||||
|
|
||||||
repo_read_index(the_repository);
|
repo_read_index(the_repository);
|
||||||
|
|
||||||
|
init_opts.sparse_index = -1;
|
||||||
|
|
||||||
argc = parse_options(argc, argv, NULL,
|
argc = parse_options(argc, argv, NULL,
|
||||||
builtin_sparse_checkout_init_options,
|
builtin_sparse_checkout_init_options,
|
||||||
builtin_sparse_checkout_init_usage, 0);
|
builtin_sparse_checkout_init_usage, 0);
|
||||||
@ -323,10 +335,20 @@ static int sparse_checkout_init(int argc, const char **argv)
|
|||||||
sparse_filename = get_sparse_checkout_filename();
|
sparse_filename = get_sparse_checkout_filename();
|
||||||
res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL, 0);
|
res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL, 0);
|
||||||
|
|
||||||
|
if (init_opts.sparse_index >= 0) {
|
||||||
|
if (set_sparse_index_config(the_repository, init_opts.sparse_index) < 0)
|
||||||
|
die(_("failed to modify sparse-index config"));
|
||||||
|
|
||||||
|
/* force an index rewrite */
|
||||||
|
repo_read_index(the_repository);
|
||||||
|
the_repository->index->updated_workdir = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
core_apply_sparse_checkout = 1;
|
||||||
|
|
||||||
/* If we already have a sparse-checkout file, use it. */
|
/* If we already have a sparse-checkout file, use it. */
|
||||||
if (res >= 0) {
|
if (res >= 0) {
|
||||||
free(sparse_filename);
|
free(sparse_filename);
|
||||||
core_apply_sparse_checkout = 1;
|
|
||||||
return update_working_directory(NULL);
|
return update_working_directory(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,6 +370,7 @@ static int sparse_checkout_init(int argc, const char **argv)
|
|||||||
add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);
|
add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);
|
||||||
strbuf_addstr(&pattern, "!/*/");
|
strbuf_addstr(&pattern, "!/*/");
|
||||||
add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);
|
add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);
|
||||||
|
pl.use_cone_patterns = init_opts.cone_mode;
|
||||||
|
|
||||||
return write_patterns_and_update(&pl);
|
return write_patterns_and_update(&pl);
|
||||||
}
|
}
|
||||||
@ -517,19 +540,18 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)
|
|||||||
{
|
{
|
||||||
int result;
|
int result;
|
||||||
int changed_config = 0;
|
int changed_config = 0;
|
||||||
struct pattern_list pl;
|
struct pattern_list *pl = xcalloc(1, sizeof(*pl));
|
||||||
memset(&pl, 0, sizeof(pl));
|
|
||||||
|
|
||||||
switch (m) {
|
switch (m) {
|
||||||
case ADD:
|
case ADD:
|
||||||
if (core_sparse_checkout_cone)
|
if (core_sparse_checkout_cone)
|
||||||
add_patterns_cone_mode(argc, argv, &pl);
|
add_patterns_cone_mode(argc, argv, pl);
|
||||||
else
|
else
|
||||||
add_patterns_literal(argc, argv, &pl);
|
add_patterns_literal(argc, argv, pl);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case REPLACE:
|
case REPLACE:
|
||||||
add_patterns_from_input(&pl, argc, argv);
|
add_patterns_from_input(pl, argc, argv);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -539,12 +561,13 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)
|
|||||||
changed_config = 1;
|
changed_config = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = write_patterns_and_update(&pl);
|
result = write_patterns_and_update(pl);
|
||||||
|
|
||||||
if (result && changed_config)
|
if (result && changed_config)
|
||||||
set_config(MODE_NO_PATTERNS);
|
set_config(MODE_NO_PATTERNS);
|
||||||
|
|
||||||
clear_pattern_list(&pl);
|
clear_pattern_list(pl);
|
||||||
|
free(pl);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -614,6 +637,9 @@ static int sparse_checkout_disable(int argc, const char **argv)
|
|||||||
strbuf_addstr(&match_all, "/*");
|
strbuf_addstr(&match_all, "/*");
|
||||||
add_pattern(strbuf_detach(&match_all, NULL), empty_base, 0, &pl, 0);
|
add_pattern(strbuf_detach(&match_all, NULL), empty_base, 0, &pl, 0);
|
||||||
|
|
||||||
|
prepare_repo_settings(the_repository);
|
||||||
|
the_repository->settings.sparse_index = 0;
|
||||||
|
|
||||||
if (update_working_directory(&pl))
|
if (update_working_directory(&pl))
|
||||||
die(_("error while refreshing working directory"));
|
die(_("error while refreshing working directory"));
|
||||||
|
|
||||||
|
@ -1412,6 +1412,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
|
|||||||
int i;
|
int i;
|
||||||
char *ps_matched = xcalloc(ps->nr, 1);
|
char *ps_matched = xcalloc(ps->nr, 1);
|
||||||
|
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(&the_index);
|
||||||
for (i = 0; i < active_nr; i++)
|
for (i = 0; i < active_nr; i++)
|
||||||
ce_path_match(&the_index, active_cache[i], ps,
|
ce_path_match(&the_index, active_cache[i], ps,
|
||||||
ps_matched);
|
ps_matched);
|
||||||
|
@ -745,6 +745,8 @@ static int do_reupdate(int ac, const char **av,
|
|||||||
*/
|
*/
|
||||||
has_head = 0;
|
has_head = 0;
|
||||||
redo:
|
redo:
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(&the_index);
|
||||||
for (pos = 0; pos < active_nr; pos++) {
|
for (pos = 0; pos < active_nr; pos++) {
|
||||||
const struct cache_entry *ce = active_cache[pos];
|
const struct cache_entry *ce = active_cache[pos];
|
||||||
struct cache_entry *old = NULL;
|
struct cache_entry *old = NULL;
|
||||||
|
40
cache-tree.c
40
cache-tree.c
@ -6,6 +6,7 @@
|
|||||||
#include "object-store.h"
|
#include "object-store.h"
|
||||||
#include "replace-object.h"
|
#include "replace-object.h"
|
||||||
#include "promisor-remote.h"
|
#include "promisor-remote.h"
|
||||||
|
#include "sparse-index.h"
|
||||||
|
|
||||||
#ifndef DEBUG_CACHE_TREE
|
#ifndef DEBUG_CACHE_TREE
|
||||||
#define DEBUG_CACHE_TREE 0
|
#define DEBUG_CACHE_TREE 0
|
||||||
@ -255,6 +256,24 @@ static int update_one(struct cache_tree *it,
|
|||||||
|
|
||||||
*skip_count = 0;
|
*skip_count = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the first entry of this region is a sparse directory
|
||||||
|
* entry corresponding exactly to 'base', then this cache_tree
|
||||||
|
* struct is a "leaf" in the data structure, pointing to the
|
||||||
|
* tree OID specified in the entry.
|
||||||
|
*/
|
||||||
|
if (entries > 0) {
|
||||||
|
const struct cache_entry *ce = cache[0];
|
||||||
|
|
||||||
|
if (S_ISSPARSEDIR(ce->ce_mode) &&
|
||||||
|
ce->ce_namelen == baselen &&
|
||||||
|
!strncmp(ce->name, base, baselen)) {
|
||||||
|
it->entry_count = 1;
|
||||||
|
oidcpy(&it->oid, &ce->oid);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (0 <= it->entry_count && has_object_file(&it->oid))
|
if (0 <= it->entry_count && has_object_file(&it->oid))
|
||||||
return it->entry_count;
|
return it->entry_count;
|
||||||
|
|
||||||
@ -442,6 +461,8 @@ int cache_tree_update(struct index_state *istate, int flags)
|
|||||||
if (i)
|
if (i)
|
||||||
return i;
|
return i;
|
||||||
|
|
||||||
|
ensure_full_index(istate);
|
||||||
|
|
||||||
if (!istate->cache_tree)
|
if (!istate->cache_tree)
|
||||||
istate->cache_tree = cache_tree();
|
istate->cache_tree = cache_tree();
|
||||||
|
|
||||||
@ -787,6 +808,19 @@ int cache_tree_matches_traversal(struct cache_tree *root,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void verify_one_sparse(struct repository *r,
|
||||||
|
struct index_state *istate,
|
||||||
|
struct cache_tree *it,
|
||||||
|
struct strbuf *path,
|
||||||
|
int pos)
|
||||||
|
{
|
||||||
|
struct cache_entry *ce = istate->cache[pos];
|
||||||
|
|
||||||
|
if (!S_ISSPARSEDIR(ce->ce_mode))
|
||||||
|
BUG("directory '%s' is present in index, but not sparse",
|
||||||
|
path->buf);
|
||||||
|
}
|
||||||
|
|
||||||
static void verify_one(struct repository *r,
|
static void verify_one(struct repository *r,
|
||||||
struct index_state *istate,
|
struct index_state *istate,
|
||||||
struct cache_tree *it,
|
struct cache_tree *it,
|
||||||
@ -809,6 +843,12 @@ static void verify_one(struct repository *r,
|
|||||||
|
|
||||||
if (path->len) {
|
if (path->len) {
|
||||||
pos = index_name_pos(istate, path->buf, path->len);
|
pos = index_name_pos(istate, path->buf, path->len);
|
||||||
|
|
||||||
|
if (pos >= 0) {
|
||||||
|
verify_one_sparse(r, istate, it, path, pos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
pos = -pos - 1;
|
pos = -pos - 1;
|
||||||
} else {
|
} else {
|
||||||
pos = 0;
|
pos = 0;
|
||||||
|
25
cache.h
25
cache.h
@ -204,6 +204,8 @@ struct cache_entry {
|
|||||||
#error "CE_EXTENDED_FLAGS out of range"
|
#error "CE_EXTENDED_FLAGS out of range"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define S_ISSPARSEDIR(m) ((m) == S_IFDIR)
|
||||||
|
|
||||||
/* Forward structure decls */
|
/* Forward structure decls */
|
||||||
struct pathspec;
|
struct pathspec;
|
||||||
struct child_process;
|
struct child_process;
|
||||||
@ -249,6 +251,8 @@ static inline unsigned int create_ce_mode(unsigned int mode)
|
|||||||
{
|
{
|
||||||
if (S_ISLNK(mode))
|
if (S_ISLNK(mode))
|
||||||
return S_IFLNK;
|
return S_IFLNK;
|
||||||
|
if (S_ISSPARSEDIR(mode))
|
||||||
|
return S_IFDIR;
|
||||||
if (S_ISDIR(mode) || S_ISGITLINK(mode))
|
if (S_ISDIR(mode) || S_ISGITLINK(mode))
|
||||||
return S_IFGITLINK;
|
return S_IFGITLINK;
|
||||||
return S_IFREG | ce_permissions(mode);
|
return S_IFREG | ce_permissions(mode);
|
||||||
@ -305,6 +309,7 @@ static inline unsigned int canon_mode(unsigned int mode)
|
|||||||
struct split_index;
|
struct split_index;
|
||||||
struct untracked_cache;
|
struct untracked_cache;
|
||||||
struct progress;
|
struct progress;
|
||||||
|
struct pattern_list;
|
||||||
|
|
||||||
struct index_state {
|
struct index_state {
|
||||||
struct cache_entry **cache;
|
struct cache_entry **cache;
|
||||||
@ -319,7 +324,14 @@ struct index_state {
|
|||||||
drop_cache_tree : 1,
|
drop_cache_tree : 1,
|
||||||
updated_workdir : 1,
|
updated_workdir : 1,
|
||||||
updated_skipworktree : 1,
|
updated_skipworktree : 1,
|
||||||
fsmonitor_has_run_once : 1;
|
fsmonitor_has_run_once : 1,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* sparse_index == 1 when sparse-directory
|
||||||
|
* entries exist. Requires sparse-checkout
|
||||||
|
* in cone mode.
|
||||||
|
*/
|
||||||
|
sparse_index : 1;
|
||||||
struct hashmap name_hash;
|
struct hashmap name_hash;
|
||||||
struct hashmap dir_hash;
|
struct hashmap dir_hash;
|
||||||
struct object_id oid;
|
struct object_id oid;
|
||||||
@ -329,6 +341,7 @@ struct index_state {
|
|||||||
struct mem_pool *ce_mem_pool;
|
struct mem_pool *ce_mem_pool;
|
||||||
struct progress *progress;
|
struct progress *progress;
|
||||||
struct repository *repo;
|
struct repository *repo;
|
||||||
|
struct pattern_list *sparse_checkout_patterns;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Name hashing */
|
/* Name hashing */
|
||||||
@ -337,6 +350,7 @@ void add_name_hash(struct index_state *istate, struct cache_entry *ce);
|
|||||||
void remove_name_hash(struct index_state *istate, struct cache_entry *ce);
|
void remove_name_hash(struct index_state *istate, struct cache_entry *ce);
|
||||||
void free_name_hash(struct index_state *istate);
|
void free_name_hash(struct index_state *istate);
|
||||||
|
|
||||||
|
void ensure_full_index(struct index_state *istate);
|
||||||
|
|
||||||
/* Cache entry creation and cleanup */
|
/* Cache entry creation and cleanup */
|
||||||
|
|
||||||
@ -722,6 +736,8 @@ int read_index_from(struct index_state *, const char *path,
|
|||||||
const char *gitdir);
|
const char *gitdir);
|
||||||
int is_index_unborn(struct index_state *);
|
int is_index_unborn(struct index_state *);
|
||||||
|
|
||||||
|
void ensure_full_index(struct index_state *istate);
|
||||||
|
|
||||||
/* For use with `write_locked_index()`. */
|
/* For use with `write_locked_index()`. */
|
||||||
#define COMMIT_LOCK (1 << 0)
|
#define COMMIT_LOCK (1 << 0)
|
||||||
#define SKIP_IF_UNCHANGED (1 << 1)
|
#define SKIP_IF_UNCHANGED (1 << 1)
|
||||||
@ -785,7 +801,7 @@ struct cache_entry *index_file_exists(struct index_state *istate, const char *na
|
|||||||
* index_name_pos(&index, "f", 1) -> -3
|
* index_name_pos(&index, "f", 1) -> -3
|
||||||
* index_name_pos(&index, "g", 1) -> -5
|
* index_name_pos(&index, "g", 1) -> -5
|
||||||
*/
|
*/
|
||||||
int index_name_pos(const struct index_state *, const char *name, int namelen);
|
int index_name_pos(struct index_state *, const char *name, int namelen);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Some functions return the negative complement of an insert position when a
|
* Some functions return the negative complement of an insert position when a
|
||||||
@ -835,8 +851,8 @@ int add_file_to_index(struct index_state *, const char *path, int flags);
|
|||||||
int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip);
|
int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip);
|
||||||
int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
|
int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
|
||||||
void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);
|
void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);
|
||||||
int index_name_is_other(const struct index_state *, const char *, int);
|
int index_name_is_other(struct index_state *, const char *, int);
|
||||||
void *read_blob_data_from_index(const struct index_state *, const char *, unsigned long *);
|
void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *);
|
||||||
|
|
||||||
/* do stat comparison even if CE_VALID is true */
|
/* do stat comparison even if CE_VALID is true */
|
||||||
#define CE_MATCH_IGNORE_VALID 01
|
#define CE_MATCH_IGNORE_VALID 01
|
||||||
@ -1044,6 +1060,7 @@ struct repository_format {
|
|||||||
int worktree_config;
|
int worktree_config;
|
||||||
int is_bare;
|
int is_bare;
|
||||||
int hash_algo;
|
int hash_algo;
|
||||||
|
int sparse_index;
|
||||||
char *work_tree;
|
char *work_tree;
|
||||||
struct string_list unknown_extensions;
|
struct string_list unknown_extensions;
|
||||||
struct string_list v1_only_extensions;
|
struct string_list v1_only_extensions;
|
||||||
|
20
convert.c
20
convert.c
@ -127,7 +127,7 @@ static const char *gather_convert_stats_ascii(const char *data, unsigned long si
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *get_cached_convert_stats_ascii(const struct index_state *istate,
|
const char *get_cached_convert_stats_ascii(struct index_state *istate,
|
||||||
const char *path)
|
const char *path)
|
||||||
{
|
{
|
||||||
const char *ret;
|
const char *ret;
|
||||||
@ -211,7 +211,7 @@ static void check_global_conv_flags_eol(const char *path,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int has_crlf_in_index(const struct index_state *istate, const char *path)
|
static int has_crlf_in_index(struct index_state *istate, const char *path)
|
||||||
{
|
{
|
||||||
unsigned long sz;
|
unsigned long sz;
|
||||||
void *data;
|
void *data;
|
||||||
@ -485,7 +485,7 @@ static int encode_to_worktree(const char *path, const char *src, size_t src_len,
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int crlf_to_git(const struct index_state *istate,
|
static int crlf_to_git(struct index_state *istate,
|
||||||
const char *path, const char *src, size_t len,
|
const char *path, const char *src, size_t len,
|
||||||
struct strbuf *buf,
|
struct strbuf *buf,
|
||||||
enum convert_crlf_action crlf_action, int conv_flags)
|
enum convert_crlf_action crlf_action, int conv_flags)
|
||||||
@ -1293,7 +1293,7 @@ static int git_path_check_ident(struct attr_check_item *check)
|
|||||||
|
|
||||||
static struct attr_check *check;
|
static struct attr_check *check;
|
||||||
|
|
||||||
void convert_attrs(const struct index_state *istate,
|
void convert_attrs(struct index_state *istate,
|
||||||
struct conv_attrs *ca, const char *path)
|
struct conv_attrs *ca, const char *path)
|
||||||
{
|
{
|
||||||
struct attr_check_item *ccheck = NULL;
|
struct attr_check_item *ccheck = NULL;
|
||||||
@ -1355,7 +1355,7 @@ void reset_parsed_attributes(void)
|
|||||||
user_convert_tail = NULL;
|
user_convert_tail = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int would_convert_to_git_filter_fd(const struct index_state *istate, const char *path)
|
int would_convert_to_git_filter_fd(struct index_state *istate, const char *path)
|
||||||
{
|
{
|
||||||
struct conv_attrs ca;
|
struct conv_attrs ca;
|
||||||
|
|
||||||
@ -1374,7 +1374,7 @@ int would_convert_to_git_filter_fd(const struct index_state *istate, const char
|
|||||||
return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN, NULL, NULL);
|
return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *get_convert_attr_ascii(const struct index_state *istate, const char *path)
|
const char *get_convert_attr_ascii(struct index_state *istate, const char *path)
|
||||||
{
|
{
|
||||||
struct conv_attrs ca;
|
struct conv_attrs ca;
|
||||||
|
|
||||||
@ -1400,7 +1400,7 @@ const char *get_convert_attr_ascii(const struct index_state *istate, const char
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
int convert_to_git(const struct index_state *istate,
|
int convert_to_git(struct index_state *istate,
|
||||||
const char *path, const char *src, size_t len,
|
const char *path, const char *src, size_t len,
|
||||||
struct strbuf *dst, int conv_flags)
|
struct strbuf *dst, int conv_flags)
|
||||||
{
|
{
|
||||||
@ -1434,7 +1434,7 @@ int convert_to_git(const struct index_state *istate,
|
|||||||
return ret | ident_to_git(src, len, dst, ca.ident);
|
return ret | ident_to_git(src, len, dst, ca.ident);
|
||||||
}
|
}
|
||||||
|
|
||||||
void convert_to_git_filter_fd(const struct index_state *istate,
|
void convert_to_git_filter_fd(struct index_state *istate,
|
||||||
const char *path, int fd, struct strbuf *dst,
|
const char *path, int fd, struct strbuf *dst,
|
||||||
int conv_flags)
|
int conv_flags)
|
||||||
{
|
{
|
||||||
@ -1511,7 +1511,7 @@ int convert_to_working_tree_ca(const struct conv_attrs *ca,
|
|||||||
meta, NULL);
|
meta, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
int renormalize_buffer(const struct index_state *istate, const char *path,
|
int renormalize_buffer(struct index_state *istate, const char *path,
|
||||||
const char *src, size_t len, struct strbuf *dst)
|
const char *src, size_t len, struct strbuf *dst)
|
||||||
{
|
{
|
||||||
struct conv_attrs ca;
|
struct conv_attrs ca;
|
||||||
@ -1972,7 +1972,7 @@ struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,
|
|||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct stream_filter *get_stream_filter(const struct index_state *istate,
|
struct stream_filter *get_stream_filter(struct index_state *istate,
|
||||||
const char *path,
|
const char *path,
|
||||||
const struct object_id *oid)
|
const struct object_id *oid)
|
||||||
{
|
{
|
||||||
|
22
convert.h
22
convert.h
@ -84,19 +84,19 @@ struct conv_attrs {
|
|||||||
const char *working_tree_encoding; /* Supported encoding or default encoding if NULL */
|
const char *working_tree_encoding; /* Supported encoding or default encoding if NULL */
|
||||||
};
|
};
|
||||||
|
|
||||||
void convert_attrs(const struct index_state *istate,
|
void convert_attrs(struct index_state *istate,
|
||||||
struct conv_attrs *ca, const char *path);
|
struct conv_attrs *ca, const char *path);
|
||||||
|
|
||||||
extern enum eol core_eol;
|
extern enum eol core_eol;
|
||||||
extern char *check_roundtrip_encoding;
|
extern char *check_roundtrip_encoding;
|
||||||
const char *get_cached_convert_stats_ascii(const struct index_state *istate,
|
const char *get_cached_convert_stats_ascii(struct index_state *istate,
|
||||||
const char *path);
|
const char *path);
|
||||||
const char *get_wt_convert_stats_ascii(const char *path);
|
const char *get_wt_convert_stats_ascii(const char *path);
|
||||||
const char *get_convert_attr_ascii(const struct index_state *istate,
|
const char *get_convert_attr_ascii(struct index_state *istate,
|
||||||
const char *path);
|
const char *path);
|
||||||
|
|
||||||
/* returns 1 if *dst was used */
|
/* returns 1 if *dst was used */
|
||||||
int convert_to_git(const struct index_state *istate,
|
int convert_to_git(struct index_state *istate,
|
||||||
const char *path, const char *src, size_t len,
|
const char *path, const char *src, size_t len,
|
||||||
struct strbuf *dst, int conv_flags);
|
struct strbuf *dst, int conv_flags);
|
||||||
int convert_to_working_tree_ca(const struct conv_attrs *ca,
|
int convert_to_working_tree_ca(const struct conv_attrs *ca,
|
||||||
@ -108,7 +108,7 @@ int async_convert_to_working_tree_ca(const struct conv_attrs *ca,
|
|||||||
size_t len, struct strbuf *dst,
|
size_t len, struct strbuf *dst,
|
||||||
const struct checkout_metadata *meta,
|
const struct checkout_metadata *meta,
|
||||||
void *dco);
|
void *dco);
|
||||||
static inline int convert_to_working_tree(const struct index_state *istate,
|
static inline int convert_to_working_tree(struct index_state *istate,
|
||||||
const char *path, const char *src,
|
const char *path, const char *src,
|
||||||
size_t len, struct strbuf *dst,
|
size_t len, struct strbuf *dst,
|
||||||
const struct checkout_metadata *meta)
|
const struct checkout_metadata *meta)
|
||||||
@ -117,7 +117,7 @@ static inline int convert_to_working_tree(const struct index_state *istate,
|
|||||||
convert_attrs(istate, &ca, path);
|
convert_attrs(istate, &ca, path);
|
||||||
return convert_to_working_tree_ca(&ca, path, src, len, dst, meta);
|
return convert_to_working_tree_ca(&ca, path, src, len, dst, meta);
|
||||||
}
|
}
|
||||||
static inline int async_convert_to_working_tree(const struct index_state *istate,
|
static inline int async_convert_to_working_tree(struct index_state *istate,
|
||||||
const char *path, const char *src,
|
const char *path, const char *src,
|
||||||
size_t len, struct strbuf *dst,
|
size_t len, struct strbuf *dst,
|
||||||
const struct checkout_metadata *meta,
|
const struct checkout_metadata *meta,
|
||||||
@ -129,20 +129,20 @@ static inline int async_convert_to_working_tree(const struct index_state *istate
|
|||||||
}
|
}
|
||||||
int async_query_available_blobs(const char *cmd,
|
int async_query_available_blobs(const char *cmd,
|
||||||
struct string_list *available_paths);
|
struct string_list *available_paths);
|
||||||
int renormalize_buffer(const struct index_state *istate,
|
int renormalize_buffer(struct index_state *istate,
|
||||||
const char *path, const char *src, size_t len,
|
const char *path, const char *src, size_t len,
|
||||||
struct strbuf *dst);
|
struct strbuf *dst);
|
||||||
static inline int would_convert_to_git(const struct index_state *istate,
|
static inline int would_convert_to_git(struct index_state *istate,
|
||||||
const char *path)
|
const char *path)
|
||||||
{
|
{
|
||||||
return convert_to_git(istate, path, NULL, 0, NULL, 0);
|
return convert_to_git(istate, path, NULL, 0, NULL, 0);
|
||||||
}
|
}
|
||||||
/* Precondition: would_convert_to_git_filter_fd(path) == true */
|
/* Precondition: would_convert_to_git_filter_fd(path) == true */
|
||||||
void convert_to_git_filter_fd(const struct index_state *istate,
|
void convert_to_git_filter_fd(struct index_state *istate,
|
||||||
const char *path, int fd,
|
const char *path, int fd,
|
||||||
struct strbuf *dst,
|
struct strbuf *dst,
|
||||||
int conv_flags);
|
int conv_flags);
|
||||||
int would_convert_to_git_filter_fd(const struct index_state *istate,
|
int would_convert_to_git_filter_fd(struct index_state *istate,
|
||||||
const char *path);
|
const char *path);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -176,7 +176,7 @@ void reset_parsed_attributes(void);
|
|||||||
|
|
||||||
struct stream_filter; /* opaque */
|
struct stream_filter; /* opaque */
|
||||||
|
|
||||||
struct stream_filter *get_stream_filter(const struct index_state *istate,
|
struct stream_filter *get_stream_filter(struct index_state *istate,
|
||||||
const char *path,
|
const char *path,
|
||||||
const struct object_id *);
|
const struct object_id *);
|
||||||
struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,
|
struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,
|
||||||
|
14
dir.c
14
dir.c
@ -306,7 +306,7 @@ static int do_read_blob(const struct object_id *oid, struct oid_stat *oid_stat,
|
|||||||
* [1] Only if DO_MATCH_DIRECTORY is passed; otherwise, this is NOT a match.
|
* [1] Only if DO_MATCH_DIRECTORY is passed; otherwise, this is NOT a match.
|
||||||
* [2] Only if DO_MATCH_LEADING_PATHSPEC is passed; otherwise, not a match.
|
* [2] Only if DO_MATCH_LEADING_PATHSPEC is passed; otherwise, not a match.
|
||||||
*/
|
*/
|
||||||
static int match_pathspec_item(const struct index_state *istate,
|
static int match_pathspec_item(struct index_state *istate,
|
||||||
const struct pathspec_item *item, int prefix,
|
const struct pathspec_item *item, int prefix,
|
||||||
const char *name, int namelen, unsigned flags)
|
const char *name, int namelen, unsigned flags)
|
||||||
{
|
{
|
||||||
@ -429,7 +429,7 @@ static int match_pathspec_item(const struct index_state *istate,
|
|||||||
* pathspec did not match any names, which could indicate that the
|
* pathspec did not match any names, which could indicate that the
|
||||||
* user mistyped the nth pathspec.
|
* user mistyped the nth pathspec.
|
||||||
*/
|
*/
|
||||||
static int do_match_pathspec(const struct index_state *istate,
|
static int do_match_pathspec(struct index_state *istate,
|
||||||
const struct pathspec *ps,
|
const struct pathspec *ps,
|
||||||
const char *name, int namelen,
|
const char *name, int namelen,
|
||||||
int prefix, char *seen,
|
int prefix, char *seen,
|
||||||
@ -500,7 +500,7 @@ static int do_match_pathspec(const struct index_state *istate,
|
|||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int match_pathspec_with_flags(const struct index_state *istate,
|
static int match_pathspec_with_flags(struct index_state *istate,
|
||||||
const struct pathspec *ps,
|
const struct pathspec *ps,
|
||||||
const char *name, int namelen,
|
const char *name, int namelen,
|
||||||
int prefix, char *seen, unsigned flags)
|
int prefix, char *seen, unsigned flags)
|
||||||
@ -516,7 +516,7 @@ static int match_pathspec_with_flags(const struct index_state *istate,
|
|||||||
return negative ? 0 : positive;
|
return negative ? 0 : positive;
|
||||||
}
|
}
|
||||||
|
|
||||||
int match_pathspec(const struct index_state *istate,
|
int match_pathspec(struct index_state *istate,
|
||||||
const struct pathspec *ps,
|
const struct pathspec *ps,
|
||||||
const char *name, int namelen,
|
const char *name, int namelen,
|
||||||
int prefix, char *seen, int is_dir)
|
int prefix, char *seen, int is_dir)
|
||||||
@ -529,7 +529,7 @@ int match_pathspec(const struct index_state *istate,
|
|||||||
/**
|
/**
|
||||||
* Check if a submodule is a superset of the pathspec
|
* Check if a submodule is a superset of the pathspec
|
||||||
*/
|
*/
|
||||||
int submodule_path_match(const struct index_state *istate,
|
int submodule_path_match(struct index_state *istate,
|
||||||
const struct pathspec *ps,
|
const struct pathspec *ps,
|
||||||
const char *submodule_name,
|
const char *submodule_name,
|
||||||
char *seen)
|
char *seen)
|
||||||
@ -892,7 +892,7 @@ void add_pattern(const char *string, const char *base,
|
|||||||
add_pattern_to_hashsets(pl, pattern);
|
add_pattern_to_hashsets(pl, pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int read_skip_worktree_file_from_index(const struct index_state *istate,
|
static int read_skip_worktree_file_from_index(struct index_state *istate,
|
||||||
const char *path,
|
const char *path,
|
||||||
size_t *size_out, char **data_out,
|
size_t *size_out, char **data_out,
|
||||||
struct oid_stat *oid_stat)
|
struct oid_stat *oid_stat)
|
||||||
@ -3542,6 +3542,8 @@ static void connect_wt_gitdir_in_nested(const char *sub_worktree,
|
|||||||
if (repo_read_index(&subrepo) < 0)
|
if (repo_read_index(&subrepo) < 0)
|
||||||
die(_("index file corrupt in repo %s"), subrepo.gitdir);
|
die(_("index file corrupt in repo %s"), subrepo.gitdir);
|
||||||
|
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(subrepo.index);
|
||||||
for (i = 0; i < subrepo.index->cache_nr; i++) {
|
for (i = 0; i < subrepo.index->cache_nr; i++) {
|
||||||
const struct cache_entry *ce = subrepo.index->cache[i];
|
const struct cache_entry *ce = subrepo.index->cache[i];
|
||||||
|
|
||||||
|
8
dir.h
8
dir.h
@ -354,7 +354,7 @@ int count_slashes(const char *s);
|
|||||||
int simple_length(const char *match);
|
int simple_length(const char *match);
|
||||||
int no_wildcard(const char *string);
|
int no_wildcard(const char *string);
|
||||||
char *common_prefix(const struct pathspec *pathspec);
|
char *common_prefix(const struct pathspec *pathspec);
|
||||||
int match_pathspec(const struct index_state *istate,
|
int match_pathspec(struct index_state *istate,
|
||||||
const struct pathspec *pathspec,
|
const struct pathspec *pathspec,
|
||||||
const char *name, int namelen,
|
const char *name, int namelen,
|
||||||
int prefix, char *seen, int is_dir);
|
int prefix, char *seen, int is_dir);
|
||||||
@ -493,12 +493,12 @@ int git_fnmatch(const struct pathspec_item *item,
|
|||||||
const char *pattern, const char *string,
|
const char *pattern, const char *string,
|
||||||
int prefix);
|
int prefix);
|
||||||
|
|
||||||
int submodule_path_match(const struct index_state *istate,
|
int submodule_path_match(struct index_state *istate,
|
||||||
const struct pathspec *ps,
|
const struct pathspec *ps,
|
||||||
const char *submodule_name,
|
const char *submodule_name,
|
||||||
char *seen);
|
char *seen);
|
||||||
|
|
||||||
static inline int ce_path_match(const struct index_state *istate,
|
static inline int ce_path_match(struct index_state *istate,
|
||||||
const struct cache_entry *ce,
|
const struct cache_entry *ce,
|
||||||
const struct pathspec *pathspec,
|
const struct pathspec *pathspec,
|
||||||
char *seen)
|
char *seen)
|
||||||
@ -507,7 +507,7 @@ static inline int ce_path_match(const struct index_state *istate,
|
|||||||
S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode));
|
S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int dir_path_match(const struct index_state *istate,
|
static inline int dir_path_match(struct index_state *istate,
|
||||||
const struct dir_entry *ent,
|
const struct dir_entry *ent,
|
||||||
const struct pathspec *pathspec,
|
const struct pathspec *pathspec,
|
||||||
int prefix, char *seen)
|
int prefix, char *seen)
|
||||||
|
2
entry.c
2
entry.c
@ -423,6 +423,8 @@ static void mark_colliding_entries(const struct checkout *state,
|
|||||||
|
|
||||||
ce->ce_flags |= CE_MATCHED;
|
ce->ce_flags |= CE_MATCHED;
|
||||||
|
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(state->istate);
|
||||||
for (i = 0; i < state->istate->cache_nr; i++) {
|
for (i = 0; i < state->istate->cache_nr; i++) {
|
||||||
struct cache_entry *dup = state->istate->cache[i];
|
struct cache_entry *dup = state->istate->cache[i];
|
||||||
|
|
||||||
|
@ -2564,7 +2564,7 @@ static int blob_unchanged(struct merge_options *opt,
|
|||||||
struct strbuf basebuf = STRBUF_INIT;
|
struct strbuf basebuf = STRBUF_INIT;
|
||||||
struct strbuf sidebuf = STRBUF_INIT;
|
struct strbuf sidebuf = STRBUF_INIT;
|
||||||
int ret = 0; /* assume changed for safety */
|
int ret = 0; /* assume changed for safety */
|
||||||
const struct index_state *idx = &opt->priv->attr_index;
|
struct index_state *idx = &opt->priv->attr_index;
|
||||||
|
|
||||||
if (!idx->initialized)
|
if (!idx->initialized)
|
||||||
initialize_attr_index(opt);
|
initialize_attr_index(opt);
|
||||||
|
@ -522,6 +522,8 @@ static struct string_list *get_unmerged(struct index_state *istate)
|
|||||||
|
|
||||||
unmerged->strdup_strings = 1;
|
unmerged->strdup_strings = 1;
|
||||||
|
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(istate);
|
||||||
for (i = 0; i < istate->cache_nr; i++) {
|
for (i = 0; i < istate->cache_nr; i++) {
|
||||||
struct string_list_item *item;
|
struct string_list_item *item;
|
||||||
struct stage_data *e;
|
struct stage_data *e;
|
||||||
@ -3014,7 +3016,7 @@ static int blob_unchanged(struct merge_options *opt,
|
|||||||
struct strbuf obuf = STRBUF_INIT;
|
struct strbuf obuf = STRBUF_INIT;
|
||||||
struct strbuf abuf = STRBUF_INIT;
|
struct strbuf abuf = STRBUF_INIT;
|
||||||
int ret = 0; /* assume changed for safety */
|
int ret = 0; /* assume changed for safety */
|
||||||
const struct index_state *idx = opt->repo->index;
|
struct index_state *idx = opt->repo->index;
|
||||||
|
|
||||||
if (a->mode != o->mode)
|
if (a->mode != o->mode)
|
||||||
return 0;
|
return 0;
|
||||||
|
11
name-hash.c
11
name-hash.c
@ -8,6 +8,7 @@
|
|||||||
#include "cache.h"
|
#include "cache.h"
|
||||||
#include "thread-utils.h"
|
#include "thread-utils.h"
|
||||||
#include "trace2.h"
|
#include "trace2.h"
|
||||||
|
#include "sparse-index.h"
|
||||||
|
|
||||||
struct dir_entry {
|
struct dir_entry {
|
||||||
struct hashmap_entry ent;
|
struct hashmap_entry ent;
|
||||||
@ -109,8 +110,11 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
|
|||||||
if (ce->ce_flags & CE_HASHED)
|
if (ce->ce_flags & CE_HASHED)
|
||||||
return;
|
return;
|
||||||
ce->ce_flags |= CE_HASHED;
|
ce->ce_flags |= CE_HASHED;
|
||||||
hashmap_entry_init(&ce->ent, memihash(ce->name, ce_namelen(ce)));
|
|
||||||
hashmap_add(&istate->name_hash, &ce->ent);
|
if (!S_ISSPARSEDIR(ce->ce_mode)) {
|
||||||
|
hashmap_entry_init(&ce->ent, memihash(ce->name, ce_namelen(ce)));
|
||||||
|
hashmap_add(&istate->name_hash, &ce->ent);
|
||||||
|
}
|
||||||
|
|
||||||
if (ignore_case)
|
if (ignore_case)
|
||||||
add_dir_entry(istate, ce);
|
add_dir_entry(istate, ce);
|
||||||
@ -680,6 +684,7 @@ int index_dir_exists(struct index_state *istate, const char *name, int namelen)
|
|||||||
struct dir_entry *dir;
|
struct dir_entry *dir;
|
||||||
|
|
||||||
lazy_init_name_hash(istate);
|
lazy_init_name_hash(istate);
|
||||||
|
expand_to_path(istate, name, namelen, 0);
|
||||||
dir = find_dir_entry(istate, name, namelen);
|
dir = find_dir_entry(istate, name, namelen);
|
||||||
return dir && dir->nr;
|
return dir && dir->nr;
|
||||||
}
|
}
|
||||||
@ -690,6 +695,7 @@ void adjust_dirname_case(struct index_state *istate, char *name)
|
|||||||
const char *ptr = startPtr;
|
const char *ptr = startPtr;
|
||||||
|
|
||||||
lazy_init_name_hash(istate);
|
lazy_init_name_hash(istate);
|
||||||
|
expand_to_path(istate, name, strlen(name), 0);
|
||||||
while (*ptr) {
|
while (*ptr) {
|
||||||
while (*ptr && *ptr != '/')
|
while (*ptr && *ptr != '/')
|
||||||
ptr++;
|
ptr++;
|
||||||
@ -713,6 +719,7 @@ struct cache_entry *index_file_exists(struct index_state *istate, const char *na
|
|||||||
unsigned int hash = memihash(name, namelen);
|
unsigned int hash = memihash(name, namelen);
|
||||||
|
|
||||||
lazy_init_name_hash(istate);
|
lazy_init_name_hash(istate);
|
||||||
|
expand_to_path(istate, name, namelen, icase);
|
||||||
|
|
||||||
ce = hashmap_get_entry_from_hash(&istate->name_hash, hash, NULL,
|
ce = hashmap_get_entry_from_hash(&istate->name_hash, hash, NULL,
|
||||||
struct cache_entry, ent);
|
struct cache_entry, ent);
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
* to use find_pathspecs_matching_against_index() instead.
|
* to use find_pathspecs_matching_against_index() instead.
|
||||||
*/
|
*/
|
||||||
void add_pathspec_matches_against_index(const struct pathspec *pathspec,
|
void add_pathspec_matches_against_index(const struct pathspec *pathspec,
|
||||||
const struct index_state *istate,
|
struct index_state *istate,
|
||||||
char *seen)
|
char *seen)
|
||||||
{
|
{
|
||||||
int num_unmatched = 0, i;
|
int num_unmatched = 0, i;
|
||||||
@ -36,6 +36,8 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec,
|
|||||||
num_unmatched++;
|
num_unmatched++;
|
||||||
if (!num_unmatched)
|
if (!num_unmatched)
|
||||||
return;
|
return;
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(istate);
|
||||||
for (i = 0; i < istate->cache_nr; i++) {
|
for (i = 0; i < istate->cache_nr; i++) {
|
||||||
const struct cache_entry *ce = istate->cache[i];
|
const struct cache_entry *ce = istate->cache[i];
|
||||||
ce_path_match(istate, ce, pathspec, seen);
|
ce_path_match(istate, ce, pathspec, seen);
|
||||||
@ -51,7 +53,7 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec,
|
|||||||
* given pathspecs achieves against all items in the index.
|
* given pathspecs achieves against all items in the index.
|
||||||
*/
|
*/
|
||||||
char *find_pathspecs_matching_against_index(const struct pathspec *pathspec,
|
char *find_pathspecs_matching_against_index(const struct pathspec *pathspec,
|
||||||
const struct index_state *istate)
|
struct index_state *istate)
|
||||||
{
|
{
|
||||||
char *seen = xcalloc(pathspec->nr, 1);
|
char *seen = xcalloc(pathspec->nr, 1);
|
||||||
add_pathspec_matches_against_index(pathspec, istate, seen);
|
add_pathspec_matches_against_index(pathspec, istate, seen);
|
||||||
@ -702,7 +704,7 @@ void clear_pathspec(struct pathspec *pathspec)
|
|||||||
pathspec->nr = 0;
|
pathspec->nr = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int match_pathspec_attrs(const struct index_state *istate,
|
int match_pathspec_attrs(struct index_state *istate,
|
||||||
const char *name, int namelen,
|
const char *name, int namelen,
|
||||||
const struct pathspec_item *item)
|
const struct pathspec_item *item)
|
||||||
{
|
{
|
||||||
|
@ -150,11 +150,11 @@ static inline int ps_strcmp(const struct pathspec_item *item,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void add_pathspec_matches_against_index(const struct pathspec *pathspec,
|
void add_pathspec_matches_against_index(const struct pathspec *pathspec,
|
||||||
const struct index_state *istate,
|
struct index_state *istate,
|
||||||
char *seen);
|
char *seen);
|
||||||
char *find_pathspecs_matching_against_index(const struct pathspec *pathspec,
|
char *find_pathspecs_matching_against_index(const struct pathspec *pathspec,
|
||||||
const struct index_state *istate);
|
struct index_state *istate);
|
||||||
int match_pathspec_attrs(const struct index_state *istate,
|
int match_pathspec_attrs(struct index_state *istate,
|
||||||
const char *name, int namelen,
|
const char *name, int namelen,
|
||||||
const struct pathspec_item *item);
|
const struct pathspec_item *item);
|
||||||
|
|
||||||
|
79
read-cache.c
79
read-cache.c
@ -25,6 +25,7 @@
|
|||||||
#include "fsmonitor.h"
|
#include "fsmonitor.h"
|
||||||
#include "thread-utils.h"
|
#include "thread-utils.h"
|
||||||
#include "progress.h"
|
#include "progress.h"
|
||||||
|
#include "sparse-index.h"
|
||||||
|
|
||||||
/* Mask for the name length in ce_flags in the on-disk index */
|
/* Mask for the name length in ce_flags in the on-disk index */
|
||||||
|
|
||||||
@ -47,6 +48,7 @@
|
|||||||
#define CACHE_EXT_FSMONITOR 0x46534D4E /* "FSMN" */
|
#define CACHE_EXT_FSMONITOR 0x46534D4E /* "FSMN" */
|
||||||
#define CACHE_EXT_ENDOFINDEXENTRIES 0x454F4945 /* "EOIE" */
|
#define CACHE_EXT_ENDOFINDEXENTRIES 0x454F4945 /* "EOIE" */
|
||||||
#define CACHE_EXT_INDEXENTRYOFFSETTABLE 0x49454F54 /* "IEOT" */
|
#define CACHE_EXT_INDEXENTRYOFFSETTABLE 0x49454F54 /* "IEOT" */
|
||||||
|
#define CACHE_EXT_SPARSE_DIRECTORIES 0x73646972 /* "sdir" */
|
||||||
|
|
||||||
/* changes that can be kept in $GIT_DIR/index (basically all extensions) */
|
/* changes that can be kept in $GIT_DIR/index (basically all extensions) */
|
||||||
#define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \
|
#define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \
|
||||||
@ -101,6 +103,9 @@ static const char *alternate_index_output;
|
|||||||
|
|
||||||
static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
|
static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
|
||||||
{
|
{
|
||||||
|
if (S_ISSPARSEDIR(ce->ce_mode))
|
||||||
|
istate->sparse_index = 1;
|
||||||
|
|
||||||
istate->cache[nr] = ce;
|
istate->cache[nr] = ce;
|
||||||
add_name_hash(istate, ce);
|
add_name_hash(istate, ce);
|
||||||
}
|
}
|
||||||
@ -544,7 +549,7 @@ int cache_name_stage_compare(const char *name1, int len1, int stage1, const char
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int index_name_stage_pos(const struct index_state *istate, const char *name, int namelen, int stage)
|
static int index_name_stage_pos(struct index_state *istate, const char *name, int namelen, int stage)
|
||||||
{
|
{
|
||||||
int first, last;
|
int first, last;
|
||||||
|
|
||||||
@ -562,10 +567,31 @@ static int index_name_stage_pos(const struct index_state *istate, const char *na
|
|||||||
}
|
}
|
||||||
first = next+1;
|
first = next+1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (istate->sparse_index &&
|
||||||
|
first > 0) {
|
||||||
|
/* Note: first <= istate->cache_nr */
|
||||||
|
struct cache_entry *ce = istate->cache[first - 1];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we are in a sparse-index _and_ the entry before the
|
||||||
|
* insertion position is a sparse-directory entry that is
|
||||||
|
* an ancestor of 'name', then we need to expand the index
|
||||||
|
* and search again. This will only trigger once, because
|
||||||
|
* thereafter the index is fully expanded.
|
||||||
|
*/
|
||||||
|
if (S_ISSPARSEDIR(ce->ce_mode) &&
|
||||||
|
ce_namelen(ce) < namelen &&
|
||||||
|
!strncmp(name, ce->name, ce_namelen(ce))) {
|
||||||
|
ensure_full_index(istate);
|
||||||
|
return index_name_stage_pos(istate, name, namelen, stage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return -first-1;
|
return -first-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int index_name_pos(const struct index_state *istate, const char *name, int namelen)
|
int index_name_pos(struct index_state *istate, const char *name, int namelen)
|
||||||
{
|
{
|
||||||
return index_name_stage_pos(istate, name, namelen, 0);
|
return index_name_stage_pos(istate, name, namelen, 0);
|
||||||
}
|
}
|
||||||
@ -999,8 +1025,14 @@ inside:
|
|||||||
|
|
||||||
c = *path++;
|
c = *path++;
|
||||||
if ((c == '.' && !verify_dotfile(path, mode)) ||
|
if ((c == '.' && !verify_dotfile(path, mode)) ||
|
||||||
is_dir_sep(c) || c == '\0')
|
is_dir_sep(c))
|
||||||
return 0;
|
return 0;
|
||||||
|
/*
|
||||||
|
* allow terminating directory separators for
|
||||||
|
* sparse directory entries.
|
||||||
|
*/
|
||||||
|
if (c == '\0')
|
||||||
|
return S_ISDIR(mode);
|
||||||
} else if (c == '\\' && protect_ntfs) {
|
} else if (c == '\\' && protect_ntfs) {
|
||||||
if (is_ntfs_dotgit(path))
|
if (is_ntfs_dotgit(path))
|
||||||
return 0;
|
return 0;
|
||||||
@ -1545,6 +1577,8 @@ int refresh_index(struct index_state *istate, unsigned int flags,
|
|||||||
*/
|
*/
|
||||||
preload_index(istate, pathspec, 0);
|
preload_index(istate, pathspec, 0);
|
||||||
trace2_region_enter("index", "refresh", NULL);
|
trace2_region_enter("index", "refresh", NULL);
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(istate);
|
||||||
for (i = 0; i < istate->cache_nr; i++) {
|
for (i = 0; i < istate->cache_nr; i++) {
|
||||||
struct cache_entry *ce, *new_entry;
|
struct cache_entry *ce, *new_entry;
|
||||||
int cache_errno = 0;
|
int cache_errno = 0;
|
||||||
@ -1760,6 +1794,10 @@ static int read_index_extension(struct index_state *istate,
|
|||||||
case CACHE_EXT_INDEXENTRYOFFSETTABLE:
|
case CACHE_EXT_INDEXENTRYOFFSETTABLE:
|
||||||
/* already handled in do_read_index() */
|
/* already handled in do_read_index() */
|
||||||
break;
|
break;
|
||||||
|
case CACHE_EXT_SPARSE_DIRECTORIES:
|
||||||
|
/* no content, only an indicator */
|
||||||
|
istate->sparse_index = 1;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
if (*ext < 'A' || 'Z' < *ext)
|
if (*ext < 'A' || 'Z' < *ext)
|
||||||
return error(_("index uses %.4s extension, which we do not understand"),
|
return error(_("index uses %.4s extension, which we do not understand"),
|
||||||
@ -2273,6 +2311,12 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)
|
|||||||
trace2_data_intmax("index", the_repository, "read/cache_nr",
|
trace2_data_intmax("index", the_repository, "read/cache_nr",
|
||||||
istate->cache_nr);
|
istate->cache_nr);
|
||||||
|
|
||||||
|
if (!istate->repo)
|
||||||
|
istate->repo = the_repository;
|
||||||
|
prepare_repo_settings(istate->repo);
|
||||||
|
if (istate->repo->settings.command_requires_full_index)
|
||||||
|
ensure_full_index(istate);
|
||||||
|
|
||||||
return istate->cache_nr;
|
return istate->cache_nr;
|
||||||
|
|
||||||
unmap:
|
unmap:
|
||||||
@ -2457,6 +2501,8 @@ int repo_index_has_changes(struct repository *repo,
|
|||||||
diff_flush(&opt);
|
diff_flush(&opt);
|
||||||
return opt.flags.has_changes != 0;
|
return opt.flags.has_changes != 0;
|
||||||
} else {
|
} else {
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(istate);
|
||||||
for (i = 0; sb && i < istate->cache_nr; i++) {
|
for (i = 0; sb && i < istate->cache_nr; i++) {
|
||||||
if (i)
|
if (i)
|
||||||
strbuf_addch(sb, ' ');
|
strbuf_addch(sb, ' ');
|
||||||
@ -3012,6 +3058,10 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
|
|||||||
if (err)
|
if (err)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
if (istate->sparse_index) {
|
||||||
|
if (write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_SPARSE_DIRECTORIES, 0) < 0)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* CACHE_EXT_ENDOFINDEXENTRIES must be written as the last entry before the SHA1
|
* CACHE_EXT_ENDOFINDEXENTRIES must be written as the last entry before the SHA1
|
||||||
@ -3071,6 +3121,14 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
|
|||||||
unsigned flags)
|
unsigned flags)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
int was_full = !istate->sparse_index;
|
||||||
|
|
||||||
|
ret = convert_to_sparse(istate);
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
warning(_("failed to convert to a sparse-index"));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO trace2: replace "the_repository" with the actual repo instance
|
* TODO trace2: replace "the_repository" with the actual repo instance
|
||||||
@ -3082,6 +3140,9 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
|
|||||||
trace2_region_leave_printf("index", "do_write_index", the_repository,
|
trace2_region_leave_printf("index", "do_write_index", the_repository,
|
||||||
"%s", get_lock_file_path(lock));
|
"%s", get_lock_file_path(lock));
|
||||||
|
|
||||||
|
if (was_full)
|
||||||
|
ensure_full_index(istate);
|
||||||
|
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
if (flags & COMMIT_LOCK)
|
if (flags & COMMIT_LOCK)
|
||||||
@ -3172,9 +3233,10 @@ static int write_shared_index(struct index_state *istate,
|
|||||||
struct tempfile **temp)
|
struct tempfile **temp)
|
||||||
{
|
{
|
||||||
struct split_index *si = istate->split_index;
|
struct split_index *si = istate->split_index;
|
||||||
int ret;
|
int ret, was_full = !istate->sparse_index;
|
||||||
|
|
||||||
move_cache_to_base_index(istate);
|
move_cache_to_base_index(istate);
|
||||||
|
convert_to_sparse(istate);
|
||||||
|
|
||||||
trace2_region_enter_printf("index", "shared/do_write_index",
|
trace2_region_enter_printf("index", "shared/do_write_index",
|
||||||
the_repository, "%s", get_tempfile_path(*temp));
|
the_repository, "%s", get_tempfile_path(*temp));
|
||||||
@ -3182,6 +3244,9 @@ static int write_shared_index(struct index_state *istate,
|
|||||||
trace2_region_leave_printf("index", "shared/do_write_index",
|
trace2_region_leave_printf("index", "shared/do_write_index",
|
||||||
the_repository, "%s", get_tempfile_path(*temp));
|
the_repository, "%s", get_tempfile_path(*temp));
|
||||||
|
|
||||||
|
if (was_full)
|
||||||
|
ensure_full_index(istate);
|
||||||
|
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
ret = adjust_shared_perm(get_tempfile_path(*temp));
|
ret = adjust_shared_perm(get_tempfile_path(*temp));
|
||||||
@ -3350,8 +3415,8 @@ int repo_read_index_unmerged(struct repository *repo)
|
|||||||
* We helpfully remove a trailing "/" from directories so that
|
* We helpfully remove a trailing "/" from directories so that
|
||||||
* the output of read_directory can be used as-is.
|
* the output of read_directory can be used as-is.
|
||||||
*/
|
*/
|
||||||
int index_name_is_other(const struct index_state *istate, const char *name,
|
int index_name_is_other(struct index_state *istate, const char *name,
|
||||||
int namelen)
|
int namelen)
|
||||||
{
|
{
|
||||||
int pos;
|
int pos;
|
||||||
if (namelen && name[namelen - 1] == '/')
|
if (namelen && name[namelen - 1] == '/')
|
||||||
@ -3369,7 +3434,7 @@ int index_name_is_other(const struct index_state *istate, const char *name,
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *read_blob_data_from_index(const struct index_state *istate,
|
void *read_blob_data_from_index(struct index_state *istate,
|
||||||
const char *path, unsigned long *size)
|
const char *path, unsigned long *size)
|
||||||
{
|
{
|
||||||
int pos, len;
|
int pos, len;
|
||||||
|
@ -77,4 +77,19 @@ void prepare_repo_settings(struct repository *r)
|
|||||||
UPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);
|
UPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);
|
||||||
|
|
||||||
UPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_DEFAULT);
|
UPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_DEFAULT);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This setting guards all index reads to require a full index
|
||||||
|
* over a sparse index. After suitable guards are placed in the
|
||||||
|
* codebase around uses of the index, this setting will be
|
||||||
|
* removed.
|
||||||
|
*/
|
||||||
|
r->settings.command_requires_full_index = 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize this as off.
|
||||||
|
*/
|
||||||
|
r->settings.sparse_index = 0;
|
||||||
|
if (!repo_config_get_bool(r, "index.sparse", &value) && value)
|
||||||
|
r->settings.sparse_index = 1;
|
||||||
}
|
}
|
||||||
|
11
repository.c
11
repository.c
@ -10,6 +10,7 @@
|
|||||||
#include "object.h"
|
#include "object.h"
|
||||||
#include "lockfile.h"
|
#include "lockfile.h"
|
||||||
#include "submodule-config.h"
|
#include "submodule-config.h"
|
||||||
|
#include "sparse-index.h"
|
||||||
|
|
||||||
/* The main repository */
|
/* The main repository */
|
||||||
static struct repository the_repo;
|
static struct repository the_repo;
|
||||||
@ -261,6 +262,8 @@ void repo_clear(struct repository *repo)
|
|||||||
|
|
||||||
int repo_read_index(struct repository *repo)
|
int repo_read_index(struct repository *repo)
|
||||||
{
|
{
|
||||||
|
int res;
|
||||||
|
|
||||||
if (!repo->index)
|
if (!repo->index)
|
||||||
CALLOC_ARRAY(repo->index, 1);
|
CALLOC_ARRAY(repo->index, 1);
|
||||||
|
|
||||||
@ -270,7 +273,13 @@ int repo_read_index(struct repository *repo)
|
|||||||
else if (repo->index->repo != repo)
|
else if (repo->index->repo != repo)
|
||||||
BUG("repo's index should point back at itself");
|
BUG("repo's index should point back at itself");
|
||||||
|
|
||||||
return read_index_from(repo->index, repo->index_file, repo->gitdir);
|
res = read_index_from(repo->index, repo->index_file, repo->gitdir);
|
||||||
|
|
||||||
|
prepare_repo_settings(repo);
|
||||||
|
if (repo->settings.command_requires_full_index)
|
||||||
|
ensure_full_index(repo->index);
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
int repo_hold_locked_index(struct repository *repo,
|
int repo_hold_locked_index(struct repository *repo,
|
||||||
|
@ -41,6 +41,9 @@ struct repo_settings {
|
|||||||
enum fetch_negotiation_setting fetch_negotiation_algorithm;
|
enum fetch_negotiation_setting fetch_negotiation_algorithm;
|
||||||
|
|
||||||
int core_multi_pack_index;
|
int core_multi_pack_index;
|
||||||
|
|
||||||
|
unsigned command_requires_full_index:1,
|
||||||
|
sparse_index:1;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct repository {
|
struct repository {
|
||||||
|
@ -172,6 +172,8 @@ void unmerge_marked_index(struct index_state *istate)
|
|||||||
if (!istate->resolve_undo)
|
if (!istate->resolve_undo)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(istate);
|
||||||
for (i = 0; i < istate->cache_nr; i++) {
|
for (i = 0; i < istate->cache_nr; i++) {
|
||||||
const struct cache_entry *ce = istate->cache[i];
|
const struct cache_entry *ce = istate->cache[i];
|
||||||
if (ce->ce_flags & CE_MATCHED)
|
if (ce->ce_flags & CE_MATCHED)
|
||||||
@ -186,6 +188,8 @@ void unmerge_index(struct index_state *istate, const struct pathspec *pathspec)
|
|||||||
if (!istate->resolve_undo)
|
if (!istate->resolve_undo)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(istate);
|
||||||
for (i = 0; i < istate->cache_nr; i++) {
|
for (i = 0; i < istate->cache_nr; i++) {
|
||||||
const struct cache_entry *ce = istate->cache[i];
|
const struct cache_entry *ce = istate->cache[i];
|
||||||
if (!ce_path_match(istate, ce, pathspec, NULL))
|
if (!ce_path_match(istate, ce, pathspec, NULL))
|
||||||
|
@ -1680,6 +1680,8 @@ static void do_add_index_objects_to_pending(struct rev_info *revs,
|
|||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
/* TODO: audit for interaction with sparse-index. */
|
||||||
|
ensure_full_index(istate);
|
||||||
for (i = 0; i < istate->cache_nr; i++) {
|
for (i = 0; i < istate->cache_nr; i++) {
|
||||||
struct cache_entry *ce = istate->cache[i];
|
struct cache_entry *ce = istate->cache[i];
|
||||||
struct blob *blob;
|
struct blob *blob;
|
||||||
|
358
sparse-index.c
Normal file
358
sparse-index.c
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
#include "cache.h"
|
||||||
|
#include "repository.h"
|
||||||
|
#include "sparse-index.h"
|
||||||
|
#include "tree.h"
|
||||||
|
#include "pathspec.h"
|
||||||
|
#include "trace2.h"
|
||||||
|
#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;
|
||||||
|
int dtype;
|
||||||
|
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) ||
|
||||||
|
S_ISGITLINK(ce->ce_mode) ||
|
||||||
|
!(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int set_index_sparse_config(struct repository *repo, int enable)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
prepare_repo_settings(repo);
|
||||||
|
repo->settings.sparse_index = 1;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int set_sparse_index_config(struct repository *repo, int enable)
|
||||||
|
{
|
||||||
|
int res = set_index_sparse_config(repo, enable);
|
||||||
|
|
||||||
|
prepare_repo_settings(repo);
|
||||||
|
repo->settings.sparse_index = enable;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int convert_to_sparse(struct index_state *istate)
|
||||||
|
{
|
||||||
|
int test_env;
|
||||||
|
if (istate->split_index || istate->sparse_index ||
|
||||||
|
!core_apply_sparse_checkout || !core_sparse_checkout_cone)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!istate->repo)
|
||||||
|
istate->repo = the_repository;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The GIT_TEST_SPARSE_INDEX environment variable triggers the
|
||||||
|
* index.sparse config variable to be on.
|
||||||
|
*/
|
||||||
|
test_env = git_env_bool("GIT_TEST_SPARSE_INDEX", -1);
|
||||||
|
if (test_env >= 0)
|
||||||
|
set_sparse_index_config(istate->repo, test_env);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Only convert to sparse if index.sparse is set.
|
||||||
|
*/
|
||||||
|
prepare_repo_settings(istate->repo);
|
||||||
|
if (!istate->repo->settings.sparse_index)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* Clear and recompute the cache-tree */
|
||||||
|
cache_tree_free(&istate->cache_tree);
|
||||||
|
cache_tree_update(istate, 0);
|
||||||
|
|
||||||
|
istate->sparse_index = 1;
|
||||||
|
trace2_region_leave("index", "convert_to_sparse", istate->repo);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
ce->ce_flags |= CE_SKIP_WORKTREE;
|
||||||
|
set_index_entry(istate, istate->cache_nr++, ce);
|
||||||
|
|
||||||
|
strbuf_setlen(base, len);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ensure_full_index(struct index_state *istate)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
strbuf_release(&base);
|
||||||
|
free(full);
|
||||||
|
|
||||||
|
/* Clear and recompute the cache-tree */
|
||||||
|
cache_tree_free(&istate->cache_tree);
|
||||||
|
cache_tree_update(istate, 0);
|
||||||
|
|
||||||
|
trace2_region_leave("index", "ensure_full_index", istate->repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
23
sparse-index.h
Normal file
23
sparse-index.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#ifndef SPARSE_INDEX_H__
|
||||||
|
#define SPARSE_INDEX_H__
|
||||||
|
|
||||||
|
struct index_state;
|
||||||
|
int convert_to_sparse(struct index_state *istate);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some places in the codebase expect to search for a specific path.
|
||||||
|
* This path might be outside of the sparse-checkout definition, in
|
||||||
|
* which case a sparse-index may not contain a path for that index.
|
||||||
|
*
|
||||||
|
* Given an index and a path, check to see if a leading directory for
|
||||||
|
* 'path' exists in the index as a sparse directory. In that case,
|
||||||
|
* expand that sparse directory to a full range of cache entries and
|
||||||
|
* populate the index accordingly.
|
||||||
|
*/
|
||||||
|
void expand_to_path(struct index_state *istate,
|
||||||
|
const char *path, size_t pathlen, int icase);
|
||||||
|
|
||||||
|
struct repository;
|
||||||
|
int set_sparse_index_config(struct repository *repo, int enable);
|
||||||
|
|
||||||
|
#endif
|
@ -33,7 +33,7 @@ static struct oid_array ref_tips_after_fetch;
|
|||||||
* will be disabled because we can't guess what might be configured in
|
* will be disabled because we can't guess what might be configured in
|
||||||
* .gitmodules unless the user resolves the conflict.
|
* .gitmodules unless the user resolves the conflict.
|
||||||
*/
|
*/
|
||||||
int is_gitmodules_unmerged(const struct index_state *istate)
|
int is_gitmodules_unmerged(struct index_state *istate)
|
||||||
{
|
{
|
||||||
int pos = index_name_pos(istate, GITMODULES_FILE, strlen(GITMODULES_FILE));
|
int pos = index_name_pos(istate, GITMODULES_FILE, strlen(GITMODULES_FILE));
|
||||||
if (pos < 0) { /* .gitmodules not found or isn't merged */
|
if (pos < 0) { /* .gitmodules not found or isn't merged */
|
||||||
@ -301,7 +301,7 @@ int is_submodule_populated_gently(const char *path, int *return_error_code)
|
|||||||
/*
|
/*
|
||||||
* Dies if the provided 'prefix' corresponds to an unpopulated submodule
|
* Dies if the provided 'prefix' corresponds to an unpopulated submodule
|
||||||
*/
|
*/
|
||||||
void die_in_unpopulated_submodule(const struct index_state *istate,
|
void die_in_unpopulated_submodule(struct index_state *istate,
|
||||||
const char *prefix)
|
const char *prefix)
|
||||||
{
|
{
|
||||||
int i, prefixlen;
|
int i, prefixlen;
|
||||||
@ -331,7 +331,7 @@ void die_in_unpopulated_submodule(const struct index_state *istate,
|
|||||||
/*
|
/*
|
||||||
* Dies if any paths in the provided pathspec descends into a submodule
|
* Dies if any paths in the provided pathspec descends into a submodule
|
||||||
*/
|
*/
|
||||||
void die_path_inside_submodule(const struct index_state *istate,
|
void die_path_inside_submodule(struct index_state *istate,
|
||||||
const struct pathspec *ps)
|
const struct pathspec *ps)
|
||||||
{
|
{
|
||||||
int i, j;
|
int i, j;
|
||||||
|
@ -39,7 +39,7 @@ struct submodule_update_strategy {
|
|||||||
};
|
};
|
||||||
#define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL}
|
#define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL}
|
||||||
|
|
||||||
int is_gitmodules_unmerged(const struct index_state *istate);
|
int is_gitmodules_unmerged(struct index_state *istate);
|
||||||
int is_writing_gitmodules_ok(void);
|
int is_writing_gitmodules_ok(void);
|
||||||
int is_staging_gitmodules_ok(struct index_state *istate);
|
int is_staging_gitmodules_ok(struct index_state *istate);
|
||||||
int update_path_in_gitmodules(const char *oldpath, const char *newpath);
|
int update_path_in_gitmodules(const char *oldpath, const char *newpath);
|
||||||
@ -60,9 +60,9 @@ int is_submodule_active(struct repository *repo, const char *path);
|
|||||||
* Otherwise the return error code is the same as of resolve_gitdir_gently.
|
* Otherwise the return error code is the same as of resolve_gitdir_gently.
|
||||||
*/
|
*/
|
||||||
int is_submodule_populated_gently(const char *path, int *return_error_code);
|
int is_submodule_populated_gently(const char *path, int *return_error_code);
|
||||||
void die_in_unpopulated_submodule(const struct index_state *istate,
|
void die_in_unpopulated_submodule(struct index_state *istate,
|
||||||
const char *prefix);
|
const char *prefix);
|
||||||
void die_path_inside_submodule(const struct index_state *istate,
|
void die_path_inside_submodule(struct index_state *istate,
|
||||||
const struct pathspec *ps);
|
const struct pathspec *ps);
|
||||||
enum submodule_update_type parse_submodule_update_type(const char *value);
|
enum submodule_update_type parse_submodule_update_type(const char *value);
|
||||||
int parse_submodule_update_strategy(const char *value,
|
int parse_submodule_update_strategy(const char *value,
|
||||||
|
3
t/README
3
t/README
@ -436,6 +436,9 @@ and "sha256".
|
|||||||
GIT_TEST_WRITE_REV_INDEX=<boolean>, when true enables the
|
GIT_TEST_WRITE_REV_INDEX=<boolean>, when true enables the
|
||||||
'pack.writeReverseIndex' setting.
|
'pack.writeReverseIndex' setting.
|
||||||
|
|
||||||
|
GIT_TEST_SPARSE_INDEX=<boolean>, when true enables index writes to use the
|
||||||
|
sparse-index format by default.
|
||||||
|
|
||||||
Naming Tests
|
Naming Tests
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
@ -1,36 +1,82 @@
|
|||||||
#include "test-tool.h"
|
#include "test-tool.h"
|
||||||
#include "cache.h"
|
#include "cache.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "blob.h"
|
||||||
|
#include "commit.h"
|
||||||
|
#include "tree.h"
|
||||||
|
#include "sparse-index.h"
|
||||||
|
|
||||||
|
static void print_cache_entry(struct cache_entry *ce)
|
||||||
|
{
|
||||||
|
const char *type;
|
||||||
|
printf("%06o ", ce->ce_mode & 0177777);
|
||||||
|
|
||||||
|
if (S_ISSPARSEDIR(ce->ce_mode))
|
||||||
|
type = tree_type;
|
||||||
|
else if (S_ISGITLINK(ce->ce_mode))
|
||||||
|
type = commit_type;
|
||||||
|
else
|
||||||
|
type = blob_type;
|
||||||
|
|
||||||
|
printf("%s %s\t%s\n",
|
||||||
|
type,
|
||||||
|
oid_to_hex(&ce->oid),
|
||||||
|
ce->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_cache(struct index_state *istate)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < istate->cache_nr; i++)
|
||||||
|
print_cache_entry(istate->cache[i]);
|
||||||
|
}
|
||||||
|
|
||||||
int cmd__read_cache(int argc, const char **argv)
|
int cmd__read_cache(int argc, const char **argv)
|
||||||
{
|
{
|
||||||
|
struct repository *r = the_repository;
|
||||||
int i, cnt = 1;
|
int i, cnt = 1;
|
||||||
const char *name = NULL;
|
const char *name = NULL;
|
||||||
|
int table = 0, expand = 0;
|
||||||
|
|
||||||
if (argc > 1 && skip_prefix(argv[1], "--print-and-refresh=", &name)) {
|
initialize_the_repository();
|
||||||
argc--;
|
prepare_repo_settings(r);
|
||||||
argv++;
|
r->settings.command_requires_full_index = 0;
|
||||||
|
|
||||||
|
for (++argv, --argc; *argv && starts_with(*argv, "--"); ++argv, --argc) {
|
||||||
|
if (skip_prefix(*argv, "--print-and-refresh=", &name))
|
||||||
|
continue;
|
||||||
|
if (!strcmp(*argv, "--table"))
|
||||||
|
table = 1;
|
||||||
|
else if (!strcmp(*argv, "--expand"))
|
||||||
|
expand = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argc == 2)
|
if (argc == 1)
|
||||||
cnt = strtol(argv[1], NULL, 0);
|
cnt = strtol(argv[0], NULL, 0);
|
||||||
setup_git_directory();
|
setup_git_directory();
|
||||||
git_config(git_default_config, NULL);
|
git_config(git_default_config, NULL);
|
||||||
|
|
||||||
for (i = 0; i < cnt; i++) {
|
for (i = 0; i < cnt; i++) {
|
||||||
read_cache();
|
repo_read_index(r);
|
||||||
|
|
||||||
|
if (expand)
|
||||||
|
ensure_full_index(r->index);
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
int pos;
|
int pos;
|
||||||
|
|
||||||
refresh_index(&the_index, REFRESH_QUIET,
|
refresh_index(r->index, REFRESH_QUIET,
|
||||||
NULL, NULL, NULL);
|
NULL, NULL, NULL);
|
||||||
pos = index_name_pos(&the_index, name, strlen(name));
|
pos = index_name_pos(r->index, name, strlen(name));
|
||||||
if (pos < 0)
|
if (pos < 0)
|
||||||
die("%s not in index", name);
|
die("%s not in index", name);
|
||||||
printf("%s is%s up to date\n", name,
|
printf("%s is%s up to date\n", name,
|
||||||
ce_uptodate(the_index.cache[pos]) ? "" : " not");
|
ce_uptodate(r->index->cache[pos]) ? "" : " not");
|
||||||
write_file(name, "%d\n", i);
|
write_file(name, "%d\n", i);
|
||||||
}
|
}
|
||||||
discard_cache();
|
if (table)
|
||||||
|
print_cache(r->index);
|
||||||
|
discard_index(r->index);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
101
t/perf/p2000-sparse-operations.sh
Executable file
101
t/perf/p2000-sparse-operations.sh
Executable file
@ -0,0 +1,101 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description="test performance of Git operations using the index"
|
||||||
|
|
||||||
|
. ./perf-lib.sh
|
||||||
|
|
||||||
|
test_perf_default_repo
|
||||||
|
|
||||||
|
SPARSE_CONE=f2/f4/f1
|
||||||
|
|
||||||
|
test_expect_success 'setup repo and indexes' '
|
||||||
|
git reset --hard HEAD &&
|
||||||
|
|
||||||
|
# Remove submodules from the example repo, because our
|
||||||
|
# duplication of the entire repo creates an unlikely data shape.
|
||||||
|
if git config --file .gitmodules --get-regexp "submodule.*.path" >modules
|
||||||
|
then
|
||||||
|
git rm $(awk "{print \$2}" modules) &&
|
||||||
|
git commit -m "remove submodules" || return 1
|
||||||
|
fi &&
|
||||||
|
|
||||||
|
echo bogus >a &&
|
||||||
|
cp a b &&
|
||||||
|
git add a b &&
|
||||||
|
git commit -m "level 0" &&
|
||||||
|
BLOB=$(git rev-parse HEAD:a) &&
|
||||||
|
OLD_COMMIT=$(git rev-parse HEAD) &&
|
||||||
|
OLD_TREE=$(git rev-parse HEAD^{tree}) &&
|
||||||
|
|
||||||
|
for i in $(test_seq 1 4)
|
||||||
|
do
|
||||||
|
cat >in <<-EOF &&
|
||||||
|
100755 blob $BLOB a
|
||||||
|
040000 tree $OLD_TREE f1
|
||||||
|
040000 tree $OLD_TREE f2
|
||||||
|
040000 tree $OLD_TREE f3
|
||||||
|
040000 tree $OLD_TREE f4
|
||||||
|
EOF
|
||||||
|
NEW_TREE=$(git mktree <in) &&
|
||||||
|
NEW_COMMIT=$(git commit-tree $NEW_TREE -p $OLD_COMMIT -m "level $i") &&
|
||||||
|
OLD_TREE=$NEW_TREE &&
|
||||||
|
OLD_COMMIT=$NEW_COMMIT || return 1
|
||||||
|
done &&
|
||||||
|
|
||||||
|
git sparse-checkout init --cone &&
|
||||||
|
git branch -f wide $OLD_COMMIT &&
|
||||||
|
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v3 &&
|
||||||
|
(
|
||||||
|
cd full-index-v3 &&
|
||||||
|
git sparse-checkout init --cone &&
|
||||||
|
git sparse-checkout set $SPARSE_CONE &&
|
||||||
|
git config index.version 3 &&
|
||||||
|
git update-index --index-version=3
|
||||||
|
) &&
|
||||||
|
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v4 &&
|
||||||
|
(
|
||||||
|
cd full-index-v4 &&
|
||||||
|
git sparse-checkout init --cone &&
|
||||||
|
git sparse-checkout set $SPARSE_CONE &&
|
||||||
|
git config index.version 4 &&
|
||||||
|
git update-index --index-version=4
|
||||||
|
) &&
|
||||||
|
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v3 &&
|
||||||
|
(
|
||||||
|
cd sparse-index-v3 &&
|
||||||
|
git sparse-checkout init --cone --sparse-index &&
|
||||||
|
git sparse-checkout set $SPARSE_CONE &&
|
||||||
|
git config index.version 3 &&
|
||||||
|
git update-index --index-version=3
|
||||||
|
) &&
|
||||||
|
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v4 &&
|
||||||
|
(
|
||||||
|
cd sparse-index-v4 &&
|
||||||
|
git sparse-checkout init --cone --sparse-index &&
|
||||||
|
git sparse-checkout set $SPARSE_CONE &&
|
||||||
|
git config index.version 4 &&
|
||||||
|
git update-index --index-version=4
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_perf_on_all () {
|
||||||
|
command="$@"
|
||||||
|
for repo in full-index-v3 full-index-v4 \
|
||||||
|
sparse-index-v3 sparse-index-v4
|
||||||
|
do
|
||||||
|
test_perf "$command ($repo)" "
|
||||||
|
(
|
||||||
|
cd $repo &&
|
||||||
|
echo >>$SPARSE_CONE/a &&
|
||||||
|
$command
|
||||||
|
)
|
||||||
|
"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
test_perf_on_all git status
|
||||||
|
test_perf_on_all git add -A
|
||||||
|
test_perf_on_all git add .
|
||||||
|
test_perf_on_all git commit -a -m A
|
||||||
|
|
||||||
|
test_done
|
@ -205,6 +205,19 @@ test_expect_success 'sparse-checkout disable' '
|
|||||||
check_files repo a deep folder1 folder2
|
check_files repo a deep folder1 folder2
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'sparse-index enabled and disabled' '
|
||||||
|
git -C repo sparse-checkout init --cone --sparse-index &&
|
||||||
|
test_cmp_config -C repo true index.sparse &&
|
||||||
|
test-tool -C repo read-cache --table >cache &&
|
||||||
|
grep " tree " cache &&
|
||||||
|
|
||||||
|
git -C repo sparse-checkout disable &&
|
||||||
|
test-tool -C repo read-cache --table >cache &&
|
||||||
|
! grep " tree " cache &&
|
||||||
|
git -C repo config --list >config &&
|
||||||
|
! grep index.sparse config
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'cone mode: init and set' '
|
test_expect_success 'cone mode: init and set' '
|
||||||
git -C repo sparse-checkout init --cone &&
|
git -C repo sparse-checkout init --cone &&
|
||||||
git -C repo config --list >config &&
|
git -C repo config --list >config &&
|
||||||
|
@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
test_description='compare full workdir to sparse workdir'
|
test_description='compare full workdir to sparse workdir'
|
||||||
|
|
||||||
|
GIT_TEST_SPLIT_INDEX=0
|
||||||
|
GIT_TEST_SPARSE_INDEX=
|
||||||
|
|
||||||
. ./test-lib.sh
|
. ./test-lib.sh
|
||||||
|
|
||||||
test_expect_success 'setup' '
|
test_expect_success 'setup' '
|
||||||
git init initial-repo &&
|
git init initial-repo &&
|
||||||
(
|
(
|
||||||
|
GIT_TEST_SPARSE_INDEX=0 &&
|
||||||
cd initial-repo &&
|
cd initial-repo &&
|
||||||
echo a >a &&
|
echo a >a &&
|
||||||
echo "after deep" >e &&
|
echo "after deep" >e &&
|
||||||
@ -87,39 +91,102 @@ init_repos () {
|
|||||||
|
|
||||||
cp -r initial-repo sparse-checkout &&
|
cp -r initial-repo sparse-checkout &&
|
||||||
git -C sparse-checkout reset --hard &&
|
git -C sparse-checkout reset --hard &&
|
||||||
git -C sparse-checkout sparse-checkout init --cone &&
|
|
||||||
|
cp -r initial-repo sparse-index &&
|
||||||
|
git -C sparse-index reset --hard &&
|
||||||
|
|
||||||
# initialize sparse-checkout definitions
|
# initialize sparse-checkout definitions
|
||||||
git -C sparse-checkout sparse-checkout set deep
|
git -C sparse-checkout sparse-checkout init --cone &&
|
||||||
|
git -C sparse-checkout sparse-checkout set deep &&
|
||||||
|
git -C sparse-index sparse-checkout init --cone --sparse-index &&
|
||||||
|
test_cmp_config -C sparse-index true index.sparse &&
|
||||||
|
git -C sparse-index sparse-checkout set deep
|
||||||
}
|
}
|
||||||
|
|
||||||
run_on_sparse () {
|
run_on_sparse () {
|
||||||
(
|
(
|
||||||
cd sparse-checkout &&
|
cd sparse-checkout &&
|
||||||
$* >../sparse-checkout-out 2>../sparse-checkout-err
|
"$@" >../sparse-checkout-out 2>../sparse-checkout-err
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
cd sparse-index &&
|
||||||
|
"$@" >../sparse-index-out 2>../sparse-index-err
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
run_on_all () {
|
run_on_all () {
|
||||||
(
|
(
|
||||||
cd full-checkout &&
|
cd full-checkout &&
|
||||||
$* >../full-checkout-out 2>../full-checkout-err
|
"$@" >../full-checkout-out 2>../full-checkout-err
|
||||||
) &&
|
) &&
|
||||||
run_on_sparse $*
|
run_on_sparse "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
test_all_match () {
|
test_all_match () {
|
||||||
run_on_all $* &&
|
run_on_all "$@" &&
|
||||||
test_cmp full-checkout-out sparse-checkout-out &&
|
test_cmp full-checkout-out sparse-checkout-out &&
|
||||||
test_cmp full-checkout-err sparse-checkout-err
|
test_cmp full-checkout-out sparse-index-out &&
|
||||||
|
test_cmp full-checkout-err sparse-checkout-err &&
|
||||||
|
test_cmp full-checkout-err sparse-index-err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test_sparse_match () {
|
||||||
|
run_on_sparse "$@" &&
|
||||||
|
test_cmp sparse-checkout-out sparse-index-out &&
|
||||||
|
test_cmp sparse-checkout-err sparse-index-err
|
||||||
|
}
|
||||||
|
|
||||||
|
test_expect_success 'sparse-index contents' '
|
||||||
|
init_repos &&
|
||||||
|
|
||||||
|
test-tool -C sparse-index read-cache --table >cache &&
|
||||||
|
for dir in folder1 folder2 x
|
||||||
|
do
|
||||||
|
TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
|
||||||
|
grep "040000 tree $TREE $dir/" cache \
|
||||||
|
|| return 1
|
||||||
|
done &&
|
||||||
|
|
||||||
|
git -C sparse-index sparse-checkout set folder1 &&
|
||||||
|
|
||||||
|
test-tool -C sparse-index read-cache --table >cache &&
|
||||||
|
for dir in deep folder2 x
|
||||||
|
do
|
||||||
|
TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
|
||||||
|
grep "040000 tree $TREE $dir/" cache \
|
||||||
|
|| return 1
|
||||||
|
done &&
|
||||||
|
|
||||||
|
git -C sparse-index sparse-checkout set deep/deeper1 &&
|
||||||
|
|
||||||
|
test-tool -C sparse-index read-cache --table >cache &&
|
||||||
|
for dir in deep/deeper2 folder1 folder2 x
|
||||||
|
do
|
||||||
|
TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
|
||||||
|
grep "040000 tree $TREE $dir/" cache \
|
||||||
|
|| return 1
|
||||||
|
done &&
|
||||||
|
|
||||||
|
# Disabling the sparse-index removes tree entries with full ones
|
||||||
|
git -C sparse-index sparse-checkout init --no-sparse-index &&
|
||||||
|
|
||||||
|
test-tool -C sparse-index read-cache --table >cache &&
|
||||||
|
! grep "040000 tree" cache &&
|
||||||
|
test_sparse_match test-tool read-cache --table
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'expanded in-memory index matches full index' '
|
||||||
|
init_repos &&
|
||||||
|
test_sparse_match test-tool read-cache --expand --table
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'status with options' '
|
test_expect_success 'status with options' '
|
||||||
init_repos &&
|
init_repos &&
|
||||||
|
test_sparse_match ls &&
|
||||||
test_all_match git status --porcelain=v2 &&
|
test_all_match git status --porcelain=v2 &&
|
||||||
test_all_match git status --porcelain=v2 -z -u &&
|
test_all_match git status --porcelain=v2 -z -u &&
|
||||||
test_all_match git status --porcelain=v2 -uno &&
|
test_all_match git status --porcelain=v2 -uno &&
|
||||||
run_on_all "touch README.md" &&
|
run_on_all touch README.md &&
|
||||||
test_all_match git status --porcelain=v2 &&
|
test_all_match git status --porcelain=v2 &&
|
||||||
test_all_match git status --porcelain=v2 -z -u &&
|
test_all_match git status --porcelain=v2 -z -u &&
|
||||||
test_all_match git status --porcelain=v2 -uno &&
|
test_all_match git status --porcelain=v2 -uno &&
|
||||||
@ -135,7 +202,7 @@ test_expect_success 'add, commit, checkout' '
|
|||||||
write_script edit-contents <<-\EOF &&
|
write_script edit-contents <<-\EOF &&
|
||||||
echo text >>$1
|
echo text >>$1
|
||||||
EOF
|
EOF
|
||||||
run_on_all "../edit-contents README.md" &&
|
run_on_all ../edit-contents README.md &&
|
||||||
|
|
||||||
test_all_match git add README.md &&
|
test_all_match git add README.md &&
|
||||||
test_all_match git status --porcelain=v2 &&
|
test_all_match git status --porcelain=v2 &&
|
||||||
@ -144,7 +211,7 @@ test_expect_success 'add, commit, checkout' '
|
|||||||
test_all_match git checkout HEAD~1 &&
|
test_all_match git checkout HEAD~1 &&
|
||||||
test_all_match git checkout - &&
|
test_all_match git checkout - &&
|
||||||
|
|
||||||
run_on_all "../edit-contents README.md" &&
|
run_on_all ../edit-contents README.md &&
|
||||||
|
|
||||||
test_all_match git add -A &&
|
test_all_match git add -A &&
|
||||||
test_all_match git status --porcelain=v2 &&
|
test_all_match git status --porcelain=v2 &&
|
||||||
@ -153,7 +220,7 @@ test_expect_success 'add, commit, checkout' '
|
|||||||
test_all_match git checkout HEAD~1 &&
|
test_all_match git checkout HEAD~1 &&
|
||||||
test_all_match git checkout - &&
|
test_all_match git checkout - &&
|
||||||
|
|
||||||
run_on_all "../edit-contents deep/newfile" &&
|
run_on_all ../edit-contents deep/newfile &&
|
||||||
|
|
||||||
test_all_match git status --porcelain=v2 -uno &&
|
test_all_match git status --porcelain=v2 -uno &&
|
||||||
test_all_match git status --porcelain=v2 &&
|
test_all_match git status --porcelain=v2 &&
|
||||||
@ -186,7 +253,7 @@ test_expect_success 'diff --staged' '
|
|||||||
write_script edit-contents <<-\EOF &&
|
write_script edit-contents <<-\EOF &&
|
||||||
echo text >>README.md
|
echo text >>README.md
|
||||||
EOF
|
EOF
|
||||||
run_on_all "../edit-contents" &&
|
run_on_all ../edit-contents &&
|
||||||
|
|
||||||
test_all_match git diff &&
|
test_all_match git diff &&
|
||||||
test_all_match git diff --staged &&
|
test_all_match git diff --staged &&
|
||||||
@ -252,6 +319,17 @@ test_expect_failure 'checkout and reset (mixed)' '
|
|||||||
test_all_match git reset update-folder2
|
test_all_match git reset update-folder2
|
||||||
'
|
'
|
||||||
|
|
||||||
|
# Ensure that sparse-index behaves identically to
|
||||||
|
# sparse-checkout with a full index.
|
||||||
|
test_expect_success 'checkout and reset (mixed) [sparse]' '
|
||||||
|
init_repos &&
|
||||||
|
|
||||||
|
test_sparse_match git checkout -b reset-test update-deep &&
|
||||||
|
test_sparse_match git reset deepest &&
|
||||||
|
test_sparse_match git reset update-folder1 &&
|
||||||
|
test_sparse_match git reset update-folder2
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'merge' '
|
test_expect_success 'merge' '
|
||||||
init_repos &&
|
init_repos &&
|
||||||
|
|
||||||
@ -280,7 +358,7 @@ test_expect_success 'clean' '
|
|||||||
echo bogus >>.gitignore &&
|
echo bogus >>.gitignore &&
|
||||||
run_on_all cp ../.gitignore . &&
|
run_on_all cp ../.gitignore . &&
|
||||||
test_all_match git add .gitignore &&
|
test_all_match git add .gitignore &&
|
||||||
test_all_match git commit -m ignore-bogus-files &&
|
test_all_match git commit -m "ignore bogus files" &&
|
||||||
|
|
||||||
run_on_sparse mkdir folder1 &&
|
run_on_sparse mkdir folder1 &&
|
||||||
run_on_all touch folder1/bogus &&
|
run_on_all touch folder1/bogus &&
|
||||||
@ -288,14 +366,51 @@ test_expect_success 'clean' '
|
|||||||
test_all_match git status --porcelain=v2 &&
|
test_all_match git status --porcelain=v2 &&
|
||||||
test_all_match git clean -f &&
|
test_all_match git clean -f &&
|
||||||
test_all_match git status --porcelain=v2 &&
|
test_all_match git status --porcelain=v2 &&
|
||||||
|
test_sparse_match ls &&
|
||||||
|
test_sparse_match ls folder1 &&
|
||||||
|
|
||||||
test_all_match git clean -xf &&
|
test_all_match git clean -xf &&
|
||||||
test_all_match git status --porcelain=v2 &&
|
test_all_match git status --porcelain=v2 &&
|
||||||
|
test_sparse_match ls &&
|
||||||
|
test_sparse_match ls folder1 &&
|
||||||
|
|
||||||
test_all_match git clean -xdf &&
|
test_all_match git clean -xdf &&
|
||||||
test_all_match git status --porcelain=v2 &&
|
test_all_match git status --porcelain=v2 &&
|
||||||
|
test_sparse_match ls &&
|
||||||
|
test_sparse_match ls folder1 &&
|
||||||
|
|
||||||
test_path_is_dir sparse-checkout/folder1
|
test_sparse_match test_path_is_dir folder1
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'submodule handling' '
|
||||||
|
init_repos &&
|
||||||
|
|
||||||
|
test_all_match mkdir modules &&
|
||||||
|
test_all_match touch modules/a &&
|
||||||
|
test_all_match git add modules &&
|
||||||
|
test_all_match git commit -m "add modules directory" &&
|
||||||
|
|
||||||
|
run_on_all git submodule add "$(pwd)/initial-repo" modules/sub &&
|
||||||
|
test_all_match git commit -m "add submodule" &&
|
||||||
|
|
||||||
|
# having a submodule prevents "modules" from collapse
|
||||||
|
test-tool -C sparse-index read-cache --table >cache &&
|
||||||
|
grep "100644 blob .* modules/a" cache &&
|
||||||
|
grep "160000 commit $(git -C initial-repo rev-parse HEAD) modules/sub" cache
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'sparse-index is expanded and converted back' '
|
||||||
|
init_repos &&
|
||||||
|
|
||||||
|
GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
|
||||||
|
git -C sparse-index -c core.fsmonitor="" reset --hard &&
|
||||||
|
test_region index convert_to_sparse trace2.txt &&
|
||||||
|
test_region index ensure_full_index trace2.txt &&
|
||||||
|
|
||||||
|
rm trace2.txt &&
|
||||||
|
GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
|
||||||
|
git -C sparse-index -c core.fsmonitor="" status -uno &&
|
||||||
|
test_region index ensure_full_index trace2.txt
|
||||||
'
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
@ -750,9 +750,13 @@ static int index_pos_by_traverse_info(struct name_entry *names,
|
|||||||
strbuf_make_traverse_path(&name, info, names->path, names->pathlen);
|
strbuf_make_traverse_path(&name, info, names->path, names->pathlen);
|
||||||
strbuf_addch(&name, '/');
|
strbuf_addch(&name, '/');
|
||||||
pos = index_name_pos(o->src_index, name.buf, name.len);
|
pos = index_name_pos(o->src_index, name.buf, name.len);
|
||||||
if (pos >= 0)
|
if (pos >= 0) {
|
||||||
BUG("This is a directory and should not exist in index");
|
if (!o->src_index->sparse_index ||
|
||||||
pos = -pos - 1;
|
!(o->src_index->cache[pos]->ce_flags & CE_SKIP_WORKTREE))
|
||||||
|
BUG("This is a directory and should not exist in index");
|
||||||
|
} else {
|
||||||
|
pos = -pos - 1;
|
||||||
|
}
|
||||||
if (pos >= o->src_index->cache_nr ||
|
if (pos >= o->src_index->cache_nr ||
|
||||||
!starts_with(o->src_index->cache[pos]->name, name.buf) ||
|
!starts_with(o->src_index->cache[pos]->name, name.buf) ||
|
||||||
(pos > 0 && starts_with(o->src_index->cache[pos-1]->name, name.buf)))
|
(pos > 0 && starts_with(o->src_index->cache[pos-1]->name, name.buf)))
|
||||||
@ -1571,6 +1575,7 @@ static int verify_absent(const struct cache_entry *,
|
|||||||
*/
|
*/
|
||||||
int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)
|
int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)
|
||||||
{
|
{
|
||||||
|
struct repository *repo = the_repository;
|
||||||
int i, ret;
|
int i, ret;
|
||||||
static struct cache_entry *dfc;
|
static struct cache_entry *dfc;
|
||||||
struct pattern_list pl;
|
struct pattern_list pl;
|
||||||
@ -1582,6 +1587,12 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
|
|||||||
trace_performance_enter();
|
trace_performance_enter();
|
||||||
trace2_region_enter("unpack_trees", "unpack_trees", the_repository);
|
trace2_region_enter("unpack_trees", "unpack_trees", the_repository);
|
||||||
|
|
||||||
|
prepare_repo_settings(repo);
|
||||||
|
if (repo->settings.command_requires_full_index) {
|
||||||
|
ensure_full_index(o->src_index);
|
||||||
|
ensure_full_index(o->dst_index);
|
||||||
|
}
|
||||||
|
|
||||||
if (!core_apply_sparse_checkout || !o->update)
|
if (!core_apply_sparse_checkout || !o->update)
|
||||||
o->skip_sparse_checkout = 1;
|
o->skip_sparse_checkout = 1;
|
||||||
if (!o->skip_sparse_checkout && !o->pl) {
|
if (!o->skip_sparse_checkout && !o->pl) {
|
||||||
|
Loading…
Reference in New Issue
Block a user