Merge branch 'sparse-index-stuff'

This is random stuff that probably all got upstream in the meantime.
This commit is contained in:
Johannes Schindelin 2022-06-17 12:43:56 +02:00
Родитель d8e4840d54 5c92036df4
Коммит 4cee542123
35 изменённых файлов: 594 добавлений и 107 удалений

Просмотреть файл

@ -1,3 +1,9 @@
index.deleteSparseDirectories::
When enabled, the cone mode sparse-checkout feature will delete
directories that are outside of the sparse-checkout cone, unless
such a directory contains an untracked, non-ignored file. Defaults
to true.
index.recordEndOfIndexEntries::
Specifies whether the index file should include an "End Of Index
Entry" section. This reduces index load time on multiprocessor

Просмотреть файл

@ -47,6 +47,7 @@ static int chmod_pathspec(struct pathspec *pathspec, char flip, int show_only)
int err;
if (!include_sparse &&
!core_virtualfilesystem &&
(ce_skip_worktree(ce) ||
!path_in_sparse_checkout(ce->name, &the_index)))
continue;
@ -97,7 +98,8 @@ static void update_callback(struct diff_queue_struct *q,
struct diff_filepair *p = q->queue[i];
const char *path = p->one->path;
if (!include_sparse && !path_in_sparse_checkout(path, &the_index))
if (!include_sparse && !core_virtualfilesystem &&
!path_in_sparse_checkout(path, &the_index))
continue;
switch (fix_unmerged_status(p, data)) {
@ -215,8 +217,9 @@ static int refresh(int verbose, const struct pathspec *pathspec)
if (!seen[i]) {
const char *path = pathspec->items[i].original;
if (matches_skip_worktree(pathspec, i, &skip_worktree_seen) ||
!path_in_sparse_checkout(path, &the_index)) {
if (!core_virtualfilesystem &&
(matches_skip_worktree(pathspec, i, &skip_worktree_seen) ||
!path_in_sparse_checkout(path, &the_index))) {
string_list_append(&only_match_skip_worktree,
pathspec->items[i].original);
} else {
@ -226,7 +229,11 @@ static int refresh(int verbose, const struct pathspec *pathspec)
}
}
if (only_match_skip_worktree.nr) {
/*
* When using a virtual filesystem, we might re-add a path
* that is currently virtual and we want that to succeed.
*/
if (!core_virtualfilesystem && only_match_skip_worktree.nr) {
advise_on_updating_sparse_paths(&only_match_skip_worktree);
ret = 1;
}
@ -648,7 +655,11 @@ int cmd_add(int argc, const char **argv, const char *prefix)
if (seen[i])
continue;
if (!include_sparse &&
/*
* When using a virtual filesystem, we might re-add a path
* that is currently virtual and we want that to succeed.
*/
if (!include_sparse && !core_virtualfilesystem &&
matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) {
string_list_append(&only_match_skip_worktree,
pathspec.items[i].original);
@ -672,7 +683,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)
}
}
if (only_match_skip_worktree.nr) {
advise_on_updating_sparse_paths(&only_match_skip_worktree);
exit_status = 1;

Просмотреть файл

@ -1614,7 +1614,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
if (state->quiet)
o.verbosity = 0;
if (merge_recursive_generic(&o, &our_tree, &their_tree, 1, bases, &result)) {
if (merge_recursive_generic(&o, &our_tree, &their_tree, 1, bases, merge_recursive, &result)) {
repo_rerere(the_repository, state->allow_rerere_autoupdate);
free(their_tree_name);
return error(_("Failed to merge in the changes."));

Просмотреть файл

@ -1276,6 +1276,8 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)
char *lock_path = xstrfmt("%s/maintenance", r->objects->odb->path);
if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
struct stat st;
struct strbuf lock_dot_lock = STRBUF_INIT;
/*
* Another maintenance command is running.
*
@ -1286,6 +1288,25 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)
if (!opts->auto_flag && !opts->quiet)
warning(_("lock file '%s' exists, skipping maintenance"),
lock_path);
/*
* Check timestamp on .lock file to see if we should
* delete it to recover from a fail state.
*/
strbuf_addstr(&lock_dot_lock, lock_path);
strbuf_addstr(&lock_dot_lock, ".lock");
if (lstat(lock_dot_lock.buf, &st))
warning_errno(_("unable to stat '%s'"), lock_dot_lock.buf);
else {
if (st.st_mtime < time(NULL) - (6 * 60 * 60)) {
if (unlink(lock_dot_lock.buf))
warning_errno(_("unable to delete stale lock file"));
else
warning(_("deleted stale lock file"));
}
}
strbuf_release(&lock_dot_lock);
free(lock_path);
return 0;
}

Просмотреть файл

@ -669,6 +669,11 @@ int cmd_show(int argc, const char **argv, const char *prefix)
init_log_defaults();
git_config(git_log_config, NULL);
if (the_repository->gitdir) {
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
}
memset(&match_all, 0, sizeof(match_all));
repo_init_revisions(the_repository, &rev, prefix);
git_config(grep_config, &rev.grep_filter);

Просмотреть файл

@ -81,7 +81,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
if (o.verbosity >= 3)
printf(_("Merging %s with %s\n"), o.branch1, o.branch2);
failed = merge_recursive_generic(&o, &h1, &h2, bases_count, bases, &result);
failed = merge_recursive_generic(&o, &h1, &h2, bases_count, bases, merge_recursive, &result);
free(better1);
free(better2);

Просмотреть файл

@ -44,7 +44,7 @@ static char const * const builtin_multi_pack_index_usage[] = {
};
static struct opts_multi_pack_index {
const char *object_dir;
char *object_dir;
const char *preferred_pack;
const char *refs_snapshot;
unsigned long batch_size;
@ -52,9 +52,23 @@ static struct opts_multi_pack_index {
int stdin_packs;
} opts;
static int parse_object_dir(const struct option *opt, const char *arg,
int unset)
{
free(opts.object_dir);
if (unset)
opts.object_dir = xstrdup(get_object_directory());
else
opts.object_dir = real_pathdup(arg, 1);
return 0;
}
static struct option common_opts[] = {
OPT_FILENAME(0, "object-dir", &opts.object_dir,
N_("object directory containing set of packfile and pack-index pairs")),
OPT_CALLBACK(0, "object-dir", &opts.object_dir,
N_("directory"),
N_("object directory containing set of packfile and pack-index pairs"),
parse_object_dir),
OPT_END(),
};
@ -232,31 +246,40 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv)
int cmd_multi_pack_index(int argc, const char **argv,
const char *prefix)
{
int res;
struct option *builtin_multi_pack_index_options = common_opts;
git_config(git_default_config, NULL);
if (the_repository &&
the_repository->objects &&
the_repository->objects->odb)
opts.object_dir = xstrdup(the_repository->objects->odb->path);
argc = parse_options(argc, argv, prefix,
builtin_multi_pack_index_options,
builtin_multi_pack_index_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
if (!opts.object_dir)
opts.object_dir = get_object_directory();
if (!argc)
goto usage;
if (!strcmp(argv[0], "repack"))
return cmd_multi_pack_index_repack(argc, argv);
res = cmd_multi_pack_index_repack(argc, argv);
else if (!strcmp(argv[0], "write"))
return cmd_multi_pack_index_write(argc, argv);
res = cmd_multi_pack_index_write(argc, argv);
else if (!strcmp(argv[0], "verify"))
return cmd_multi_pack_index_verify(argc, argv);
res = cmd_multi_pack_index_verify(argc, argv);
else if (!strcmp(argv[0], "expire"))
return cmd_multi_pack_index_expire(argc, argv);
res = cmd_multi_pack_index_expire(argc, argv);
else {
error(_("unrecognized subcommand: %s"), argv[0]);
goto usage;
}
free(opts.object_dir);
return res;
error(_("unrecognized subcommand: %s"), argv[0]);
usage:
usage_with_options(builtin_multi_pack_index_usage,
builtin_multi_pack_index_options);

Просмотреть файл

@ -28,6 +28,8 @@
#include "dir.h"
#include "strbuf.h"
#include "quote.h"
#include "dir.h"
#include "entry.h"
#define REFRESH_INDEX_DELAY_WARNING_IN_MS (2 * 1000)
@ -141,9 +143,47 @@ static void update_index_from_diff(struct diff_queue_struct *q,
for (i = 0; i < q->nr; i++) {
int pos;
int respect_skip_worktree = 1;
struct diff_filespec *one = q->queue[i]->one;
struct diff_filespec *two = q->queue[i]->two;
int is_in_reset_tree = one->mode && !is_null_oid(&one->oid);
int is_missing = !(one->mode && !is_null_oid(&one->oid));
int was_missing = !two->mode && is_null_oid(&two->oid);
struct cache_entry *ce;
struct cache_entry *ceBefore;
struct checkout state = CHECKOUT_INIT;
/*
* When using the virtual filesystem feature, the cache entries that are
* added here will not have the skip-worktree bit set.
*
* Without this code there is data that is lost because the files that
* would normally be in the working directory are not there and show as
* deleted for the next status or in the case of added files just disappear.
* We need to create the previous version of the files in the working
* directory so that they will have the right content and the next
* status call will show modified or untracked files correctly.
*/
if (core_virtualfilesystem && !file_exists(two->path))
{
pos = cache_name_pos(two->path, strlen(two->path));
if ((pos >= 0 && ce_skip_worktree(active_cache[pos])) &&
(is_missing || !was_missing))
{
state.force = 1;
state.refresh_cache = 1;
state.istate = &the_index;
ceBefore = make_cache_entry(&the_index, two->mode,
&two->oid, two->path,
0, 0);
if (!ceBefore)
die(_("make_cache_entry failed for path '%s'"),
two->path);
checkout_entry(ceBefore, &state, NULL, NULL);
respect_skip_worktree = 0;
}
}
if (!is_in_reset_tree && !intent_to_add) {
remove_file_from_cache(one->path);
@ -162,8 +202,14 @@ static void update_index_from_diff(struct diff_queue_struct *q,
* to properly construct the reset sparse directory.
*/
pos = cache_name_pos(one->path, strlen(one->path));
if ((pos >= 0 && ce_skip_worktree(active_cache[pos])) ||
(pos < 0 && !path_in_sparse_checkout(one->path, &the_index)))
/*
* Do not add the SKIP_WORKTREE bit back if we populated the
* file on purpose in a virtual filesystem scenario.
*/
if (respect_skip_worktree &&
((pos >= 0 && ce_skip_worktree(active_cache[pos])) ||
(pos < 0 && !path_in_sparse_checkout(one->path, &the_index))))
ce->ce_flags |= CE_SKIP_WORKTREE;
if (!ce)

Просмотреть файл

@ -301,7 +301,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
for (i = 0; i < active_nr; i++) {
const struct cache_entry *ce = active_cache[i];
if (!include_sparse &&
if (!include_sparse && !core_virtualfilesystem &&
(ce_skip_worktree(ce) ||
!path_in_sparse_checkout(ce->name, &the_index)))
continue;
@ -338,7 +338,11 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
*original ? original : ".");
}
if (only_match_skip_worktree.nr) {
/*
* When using a virtual filesystem, we might re-add a path
* that is currently virtual and we want that to succeed.
*/
if (!core_virtualfilesystem && only_match_skip_worktree.nr) {
advise_on_updating_sparse_paths(&only_match_skip_worktree);
ret = 1;
}

Просмотреть файл

@ -106,7 +106,7 @@ static int sparse_checkout_list(int argc, const char **argv)
static void clean_tracked_sparse_directories(struct repository *r)
{
int i, was_full = 0;
int i, value, was_full = 0;
struct strbuf path = STRBUF_INIT;
size_t pathlen;
struct string_list_item *item;
@ -122,13 +122,20 @@ static void clean_tracked_sparse_directories(struct repository *r)
!r->index->sparse_checkout_patterns->use_cone_patterns)
return;
/*
* Users can disable this behavior.
*/
if (!repo_config_get_bool(r, "index.deletesparsedirectories", &value) &&
!value)
return;
/*
* Use the sparse index as a data structure to assist finding
* directories that are safe to delete. This conversion to a
* sparse index will not delete directories that contain
* conflicted entries or submodules.
*/
if (!r->index->sparse_index) {
if (r->index->sparse_index == COMPLETELY_FULL) {
/*
* If something, such as a merge conflict or other concern,
* prevents us from converting to a sparse index, then do
@ -413,6 +420,9 @@ static int update_modes(int *cone_mode, int *sparse_index)
/* force an index rewrite */
repo_read_index(the_repository);
the_repository->index->updated_workdir = 1;
if (!*sparse_index)
ensure_full_index(the_repository->index);
}
return 0;
@ -934,6 +944,9 @@ int cmd_sparse_checkout(int argc, const char **argv, const char *prefix)
git_config(git_default_config, NULL);
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
if (argc > 0) {
if (!strcmp(argv[0], "list"))
return sparse_checkout_list(argc, argv);

Просмотреть файл

@ -7,6 +7,7 @@
#include "cache-tree.h"
#include "unpack-trees.h"
#include "merge-recursive.h"
#include "merge-ort-wrappers.h"
#include "strvec.h"
#include "run-command.h"
#include "dir.h"
@ -554,7 +555,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
bases[0] = &info->b_tree;
ret = merge_recursive_generic(&o, &c_tree, &info->w_tree, 1, bases,
&result);
merge_ort_recursive, &result);
if (ret) {
rerere(0);
@ -1770,6 +1771,9 @@ int cmd_stash(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, git_stash_usage,
PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
index_file = get_index_file();
strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file,
(uintmax_t)pid);

Просмотреть файл

@ -102,6 +102,31 @@ struct cache_tree_sub *cache_tree_sub(struct cache_tree *it, const char *path)
return find_subtree(it, path, pathlen, 1);
}
struct cache_tree *cache_tree_find_path(struct cache_tree *it, const char *path)
{
const char *slash;
int namelen;
struct cache_tree_sub *down;
if (!it)
return NULL;
slash = strchrnul(path, '/');
namelen = slash - path;
it->entry_count = -1;
if (!*slash) {
int pos;
pos = cache_tree_subtree_pos(it, path, namelen);
if (0 <= pos) {
return it->down[pos]->cache_tree;
}
return NULL;
}
down = find_subtree(it, path, namelen, 0);
if (down)
return cache_tree_find_path(down->cache_tree, slash + 1);
return NULL;
}
static int do_invalidate_path(struct cache_tree *it, const char *path)
{
/* a/b/c

Просмотреть файл

@ -29,6 +29,8 @@ struct cache_tree_sub *cache_tree_sub(struct cache_tree *, const char *);
int cache_tree_subtree_pos(struct cache_tree *it, const char *path, int pathlen);
struct cache_tree *cache_tree_find_path(struct cache_tree *it, const char *path);
void cache_tree_write(struct strbuf *, struct cache_tree *root);
struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);

34
cache.h
Просмотреть файл

@ -310,6 +310,28 @@ struct untracked_cache;
struct progress;
struct pattern_list;
enum sparse_index_mode {
/*
* COMPLETELY_FULL: there are no sparse directories
* in the index at all.
*/
COMPLETELY_FULL = 0,
/*
* COLLAPSED: the index has already been collapsed to sparse
* directories whereever possible.
*/
COLLAPSED = 1,
/*
* PARTIALLY_SPARSE: the sparse directories that exist are
* outside the sparse-checkout boundary, but it is possible
* that some file entries could collapse to sparse directory
* entries.
*/
PARTIALLY_SPARSE = 2,
};
struct index_state {
struct cache_entry **cache;
unsigned int version;
@ -323,14 +345,8 @@ struct index_state {
drop_cache_tree : 1,
updated_workdir : 1,
updated_skipworktree : 1,
fsmonitor_has_run_once : 1,
/*
* sparse_index == 1 when sparse-directory
* entries exist. Requires sparse-checkout
* in cone mode.
*/
sparse_index : 1;
fsmonitor_has_run_once : 1;
enum sparse_index_mode sparse_index;
struct hashmap name_hash;
struct hashmap dir_hash;
struct object_id oid;
@ -566,7 +582,7 @@ extern char *git_work_tree_cfg;
int is_inside_work_tree(void);
const char *get_git_dir(void);
const char *get_git_common_dir(void);
char *get_object_directory(void);
const char *get_object_directory(void);
char *get_index_file(void);
char *get_graft_file(struct repository *r);
void set_git_dir(const char *path, int make_realpath);

7
diff.c
Просмотреть файл

@ -3893,6 +3893,13 @@ static int reuse_worktree_file(struct index_state *istate,
if (!FAST_WORKING_DIRECTORY && !want_file && has_object_pack(oid))
return 0;
/*
* If this path does not match our sparse-checkout definition,
* then the file will not be in the working directory.
*/
if (!path_in_sparse_checkout(name, istate))
return 0;
/*
* Similarly, if we'd have to convert the file contents anyway, that
* makes the optimization not worthwhile.

85
dir.c
Просмотреть файл

@ -1400,46 +1400,16 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname
return res;
}
/*
* Scan the list of patterns to determine if the ordered list
* of patterns matches on 'pathname'.
*
* Return 1 for a match, 0 for not matched and -1 for undecided.
*/
enum pattern_match_result path_matches_pattern_list(
enum pattern_match_result path_matches_cone_mode_pattern_list(
const char *pathname, int pathlen,
const char *basename, int *dtype,
struct pattern_list *pl,
struct index_state *istate)
struct pattern_list *pl)
{
struct path_pattern *pattern;
struct strbuf parent_pathname = STRBUF_INIT;
int result = NOT_MATCHED;
size_t slash_pos;
/*
* The virtual file system data is used to prevent git from traversing
* any part of the tree that is not in the virtual file system. Return
* 1 to exclude the entry if it is not found in the virtual file system,
* else fall through to the regular excludes logic as it may further exclude.
*/
if (*dtype == DT_UNKNOWN)
*dtype = resolve_dtype(DT_UNKNOWN, istate, pathname, pathlen);
if (is_excluded_from_virtualfilesystem(pathname, pathlen, *dtype) > 0)
return 1;
if (!pl->use_cone_patterns) {
pattern = last_matching_pattern_from_list(pathname, pathlen, basename,
dtype, pl, istate);
if (pattern) {
if (pattern->flags & PATTERN_FLAG_NEGATIVE)
return NOT_MATCHED;
else
return MATCHED;
}
return UNDECIDED;
}
if (!pl->use_cone_patterns)
BUG("path_matches_cone_mode_pattern_list requires cone mode patterns");
if (pl->full_cone)
return MATCHED;
@ -1492,6 +1462,46 @@ done:
return result;
}
/*
* Scan the list of patterns to determine if the ordered list
* of patterns matches on 'pathname'.
*
* Return 1 for a match, 0 for not matched and -1 for undecided.
*/
enum pattern_match_result path_matches_pattern_list(
const char *pathname, int pathlen,
const char *basename, int *dtype,
struct pattern_list *pl,
struct index_state *istate)
{
/*
* The virtual file system data is used to prevent git from traversing
* any part of the tree that is not in the virtual file system. Return
* 1 to exclude the entry if it is not found in the virtual file system,
* else fall through to the regular excludes logic as it may further exclude.
*/
if (*dtype == DT_UNKNOWN)
*dtype = resolve_dtype(DT_UNKNOWN, istate, pathname, pathlen);
if (is_excluded_from_virtualfilesystem(pathname, pathlen, *dtype) > 0)
return 1;
if (!pl->use_cone_patterns) {
struct path_pattern *pattern = last_matching_pattern_from_list(
pathname, pathlen, basename,
dtype, pl, istate);
if (pattern) {
if (pattern->flags & PATTERN_FLAG_NEGATIVE)
return NOT_MATCHED;
else
return MATCHED;
}
return UNDECIDED;
}
return path_matches_cone_mode_pattern_list(pathname, pathlen, pl);
}
int init_sparse_checkout_patterns(struct index_state *istate)
{
if (!core_apply_sparse_checkout)
@ -1517,6 +1527,13 @@ static int path_in_sparse_checkout_1(const char *path,
enum pattern_match_result match = UNDECIDED;
const char *end, *slash;
/*
* When using a virtual filesystem, there aren't really patterns
* to follow, but be extra careful to skip this check.
*/
if (core_virtualfilesystem)
return 1;
/*
* We default to accepting a path if the path is empty, there are no
* patterns, or the patterns are of the wrong type.

9
dir.h
Просмотреть файл

@ -383,6 +383,15 @@ enum pattern_match_result {
MATCHED_RECURSIVE = 2,
};
/*
* Test if a given path is contained in the given pattern list.
*
* The given pattern list _must_ use cone mode patterns.
*/
enum pattern_match_result path_matches_cone_mode_pattern_list(
const char *pathname, int pathlen,
struct pattern_list *pl);
/*
* Scan the list of patterns to determine if the ordered list
* of patterns matches on 'pathname'.

Просмотреть файл

@ -279,7 +279,7 @@ const char *get_git_work_tree(void)
return the_repository->worktree;
}
char *get_object_directory(void)
const char *get_object_directory(void)
{
if (!the_repository->objects->odb)
BUG("git environment hasn't been setup");

Просмотреть файл

@ -4737,7 +4737,8 @@ void merge_incore_recursive(struct merge_options *opt,
trace2_region_enter("merge", "incore_recursive", opt->repo);
/* We set the ancestor label based on the merge_bases */
assert(opt->ancestor == NULL);
assert(opt->ancestor == NULL ||
!strcmp(opt->ancestor, "constructed merge base"));
trace2_region_enter("merge", "merge_start", opt->repo);
merge_start(opt, result);

Просмотреть файл

@ -3806,6 +3806,7 @@ int merge_recursive_generic(struct merge_options *opt,
const struct object_id *merge,
int num_merge_bases,
const struct object_id **merge_bases,
recursive_merge_fn_t merge_fn,
struct commit **result)
{
int clean;
@ -3829,8 +3830,7 @@ int merge_recursive_generic(struct merge_options *opt,
}
repo_hold_locked_index(opt->repo, &lock, LOCK_DIE_ON_ERROR);
clean = merge_recursive(opt, head_commit, next_commit, ca,
result);
clean = merge_fn(opt, head_commit, next_commit, ca, result);
if (clean < 0) {
rollback_lock_file(&lock);
return clean;

Просмотреть файл

@ -53,6 +53,12 @@ struct merge_options {
struct merge_options_internal *priv;
};
typedef int (*recursive_merge_fn_t)(struct merge_options *opt,
struct commit *h1,
struct commit *h2,
struct commit_list *merge_bases,
struct commit **result);
void init_merge_options(struct merge_options *opt, struct repository *repo);
/* parse the option in s and update the relevant field of opt */
@ -105,7 +111,7 @@ int merge_recursive(struct merge_options *opt,
/*
* merge_recursive_generic can operate on trees instead of commits, by
* wrapping the trees into virtual commits, and calling merge_recursive().
* wrapping the trees into virtual commits, and calling the provided merge_fn.
* It also writes out the in-memory index to disk if the merge is successful.
*
* Outputs:
@ -120,6 +126,7 @@ int merge_recursive_generic(struct merge_options *opt,
const struct object_id *merge,
int num_merge_bases,
const struct object_id **merge_bases,
recursive_merge_fn_t merge_fn,
struct commit **result);
#endif

17
midx.c
Просмотреть файл

@ -1132,17 +1132,26 @@ cleanup:
static struct multi_pack_index *lookup_multi_pack_index(struct repository *r,
const char *object_dir)
{
struct multi_pack_index *result = NULL;
struct multi_pack_index *cur;
char *obj_dir_real = real_pathdup(object_dir, 1);
struct strbuf cur_path_real = STRBUF_INIT;
/* Ensure the given object_dir is local, or a known alternate. */
find_odb(r, object_dir);
find_odb(r, obj_dir_real);
for (cur = get_multi_pack_index(r); cur; cur = cur->next) {
if (!strcmp(object_dir, cur->object_dir))
return cur;
strbuf_realpath(&cur_path_real, cur->object_dir, 1);
if (!strcmp(obj_dir_real, cur_path_real.buf)) {
result = cur;
goto cleanup;
}
}
return NULL;
cleanup:
free(obj_dir_real);
strbuf_release(&cur_path_real);
return result;
}
static int write_midx_internal(const char *object_dir,

Просмотреть файл

@ -114,7 +114,7 @@ static const char *alternate_index_output;
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->sparse_index = COLLAPSED;
istate->cache[nr] = ce;
add_name_hash(istate, ce);
@ -1874,7 +1874,7 @@ static int read_index_extension(struct index_state *istate,
break;
case CACHE_EXT_SPARSE_DIRECTORIES:
/* no content, only an indicator */
istate->sparse_index = 1;
istate->sparse_index = COLLAPSED;
break;
default:
if (*ext < 'A' || 'Z' < *ext)
@ -3191,7 +3191,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
unsigned flags)
{
int ret;
int was_full = !istate->sparse_index;
int was_full = istate->sparse_index == COMPLETELY_FULL;
ret = convert_to_sparse(istate, 0);

Просмотреть файл

@ -74,7 +74,7 @@ void prepare_repo_settings(struct repository *r)
repo_cfg_bool(r, "fetch.writecommitgraph", &r->settings.fetch_write_commit_graph, 0);
repo_cfg_bool(r, "pack.usesparse", &r->settings.pack_use_sparse, 1);
repo_cfg_bool(r, "core.multipackindex", &r->settings.core_multi_pack_index, 1);
repo_cfg_bool(r, "index.sparse", &r->settings.sparse_index, 0);
repo_cfg_bool(r, "index.sparse", &r->settings.sparse_index, 1);
/*
* The GIT_TEST_MULTI_PACK_INDEX variable is special in that

Просмотреть файл

@ -640,7 +640,7 @@ static int do_recursive_merge(struct repository *r,
o.branch2 = next ? next_label : "(empty tree)";
if (is_rebase_i(opts))
o.buffer_output = 2;
o.show_rename_progress = 1;
o.show_rename_progress = isatty(2);
head_tree = parse_tree_indirect(head);
next_tree = next ? get_commit_tree(next) : empty_tree(r);

Просмотреть файл

@ -9,6 +9,11 @@
#include "dir.h"
#include "fsmonitor.h"
struct modify_index_context {
struct index_state *write;
struct pattern_list *pl;
};
static struct cache_entry *construct_sparse_dir_entry(
struct index_state *istate,
const char *sparse_dir,
@ -173,7 +178,7 @@ int convert_to_sparse(struct index_state *istate, int flags)
* If the index is already sparse, empty, or otherwise
* cannot be converted to sparse, do not convert.
*/
if (istate->sparse_index || !istate->cache_nr ||
if (istate->sparse_index == COLLAPSED || !istate->cache_nr ||
!is_sparse_index_allowed(istate, flags))
return 0;
@ -214,7 +219,7 @@ int convert_to_sparse(struct index_state *istate, int flags)
FREE_AND_NULL(istate->fsmonitor_dirty);
FREE_AND_NULL(istate->fsmonitor_last_update);
istate->sparse_index = 1;
istate->sparse_index = COLLAPSED;
trace2_region_leave("index", "convert_to_sparse", istate->repo);
return 0;
}
@ -231,47 +236,115 @@ 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 modify_index_context *ctx = (struct modify_index_context *)context;
struct cache_entry *ce;
size_t len = base->len;
if (S_ISDIR(mode))
return READ_TREE_RECURSIVE;
if (S_ISDIR(mode)) {
size_t baselen = base->len;
if (!ctx->pl)
return READ_TREE_RECURSIVE;
strbuf_addstr(base, path);
/*
* Have we expanded to a point outside of the sparse-checkout?
*/
strbuf_addstr(base, path);
strbuf_add(base, "/-", 2);
ce = make_cache_entry(istate, mode, oid, base->buf, 0, 0);
if (path_matches_cone_mode_pattern_list(base->buf, base->len, ctx->pl)) {
strbuf_setlen(base, baselen);
return READ_TREE_RECURSIVE;
}
/*
* The path "{base}{path}/" is a sparse directory. Create the correct
* name for inserting the entry into the idnex.
*/
strbuf_setlen(base, base->len - 1);
} else {
strbuf_addstr(base, path);
}
ce = make_cache_entry(ctx->write, mode, oid, base->buf, 0, 0);
ce->ce_flags |= CE_SKIP_WORKTREE | CE_EXTENDED;
set_index_entry(istate, istate->cache_nr++, ce);
set_index_entry(ctx->write, ctx->write->cache_nr++, ce);
strbuf_setlen(base, len);
return 0;
}
void ensure_full_index(struct index_state *istate)
void expand_to_pattern_list(struct index_state *istate,
struct pattern_list *pl)
{
int i;
struct index_state *full;
struct strbuf base = STRBUF_INIT;
struct modify_index_context ctx;
if (!istate || !istate->sparse_index)
/*
* If the index is already full, then keep it full. We will convert
* it to a sparse index on write, if possible.
*/
if (!istate || istate->sparse_index == COMPLETELY_FULL)
return;
/*
* If our index is sparse, but our new pattern set does not use
* cone mode patterns, then we need to expand the index before we
* continue. A NULL pattern set indicates a full expansion to a
* full index.
*/
if (pl && !pl->use_cone_patterns) {
pl = NULL;
} else {
/*
* We might contract file entries into sparse-directory
* entries, and for that we will need the cache tree to
* be recomputed.
*/
cache_tree_free(&istate->cache_tree);
/*
* If there is a problem creating the cache tree, then we
* need to expand to a full index since we cannot satisfy
* the current request as a sparse index.
*/
if (cache_tree_update(istate, WRITE_TREE_MISSING_OK))
pl = NULL;
}
/*
* A NULL pattern set indicates we are expanding a full index, so
* we use a special region name that indicates the full expansion.
* This is used by test cases, but also helps to differentiate the
* two cases.
*/
trace2_region_enter("index",
pl ? "expand_to_pattern_list" : "ensure_full_index",
istate->repo);
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));
/*
* This slightly-misnamed 'full' index might still be sparse if we
* are only modifying the list of sparse directories. This hinges
* on whether we have a non-NULL pattern list.
*/
full->sparse_index = pl ? PARTIALLY_SPARSE : COMPLETELY_FULL;
/* 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);
ctx.write = full;
ctx.pl = pl;
for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce = istate->cache[i];
struct tree *tree;
@ -281,6 +354,14 @@ void ensure_full_index(struct index_state *istate)
set_index_entry(full, full->cache_nr++, ce);
continue;
}
/* We now have a sparse directory entry. Should we expand? */
if (pl &&
path_matches_cone_mode_pattern_list(ce->name, ce->ce_namelen, pl) <= 0) {
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);
@ -297,7 +378,7 @@ void ensure_full_index(struct index_state *istate)
strbuf_add(&base, ce->name, strlen(ce->name));
read_tree_at(istate->repo, tree, &base, &ps,
add_path_to_index, full);
add_path_to_index, &ctx);
/* free directory entries. full entries are re-used */
discard_cache_entry(ce);
@ -306,7 +387,7 @@ void ensure_full_index(struct index_state *istate)
/* Copy back into original index. */
memcpy(&istate->name_hash, &full->name_hash, sizeof(full->name_hash));
memcpy(&istate->dir_hash, &full->dir_hash, sizeof(full->dir_hash));
istate->sparse_index = 0;
istate->sparse_index = pl ? PARTIALLY_SPARSE : COMPLETELY_FULL;
free(istate->cache);
istate->cache = full->cache;
istate->cache_nr = full->cache_nr;
@ -320,9 +401,16 @@ void ensure_full_index(struct index_state *istate)
/* Clear and recompute the cache-tree */
cache_tree_free(&istate->cache_tree);
cache_tree_update(istate, 0);
cache_tree_update(istate, WRITE_TREE_MISSING_OK);
trace2_region_leave("index", "ensure_full_index", istate->repo);
trace2_region_leave("index",
pl ? "expand_to_pattern_list" : "ensure_full_index",
istate->repo);
}
void ensure_full_index(struct index_state *istate)
{
expand_to_pattern_list(istate, NULL);
}
void ensure_correct_sparsity(struct index_state *istate)

Просмотреть файл

@ -7,7 +7,7 @@ int convert_to_sparse(struct index_state *istate, int flags);
void ensure_correct_sparsity(struct index_state *istate);
void clear_skip_worktree_from_present_files(struct index_state *istate);
/*
/**
* 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.
@ -23,4 +23,18 @@ void expand_to_path(struct index_state *istate,
struct repository;
int set_sparse_index_config(struct repository *repo, int enable);
struct pattern_list;
/**
* Scan the given index and compare its entries to the given pattern list.
* If the index is sparse and the pattern list uses cone mode patterns,
* then modify the index to contain the all of the file entries within that
* new pattern list. This expands sparse directories only as far as needed.
*
* If the pattern list is NULL or does not use cone mode patterns, then the
* index is expanded to a full index.
*/
void expand_to_pattern_list(struct index_state *istate,
struct pattern_list *pl);
#endif

Просмотреть файл

@ -55,7 +55,7 @@ test_expect_success 'setup repo and indexes' '
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-v3 &&
(
cd full-v3 &&
git sparse-checkout init --cone &&
git sparse-checkout init --cone --no-sparse-index &&
git sparse-checkout set $SPARSE_CONE &&
git config index.version 3 &&
git update-index --index-version=3 &&
@ -64,7 +64,7 @@ test_expect_success 'setup repo and indexes' '
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-v4 &&
(
cd full-v4 &&
git sparse-checkout init --cone &&
git sparse-checkout init --cone --no-sparse-index &&
git sparse-checkout set $SPARSE_CONE &&
git config index.version 4 &&
git update-index --index-version=4 &&
@ -106,6 +106,8 @@ test_perf_on_all () {
}
test_perf_on_all git status
test_perf_on_all 'git stash && git stash pop'
test_perf_on_all 'echo >>new && git stash -u && git stash pop'
test_perf_on_all git add -A
test_perf_on_all git add .
test_perf_on_all git commit -a -m A
@ -120,5 +122,6 @@ test_perf_on_all git blame $SPARSE_CONE/f3/a
test_perf_on_all git read-tree -mu HEAD
test_perf_on_all git checkout-index -f --all
test_perf_on_all git update-index --add --remove $SPARSE_CONE/a
test_perf_on_all git sparse-checkout reapply
test_done

Просмотреть файл

@ -2,7 +2,8 @@
test_description='git init'
TEST_PASSES_SANITIZE_LEAK=true
# Drop this leak check because it doesn't work on every platform.
# TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
check_config () {

Просмотреть файл

@ -760,6 +760,10 @@ test_expect_success 'cone mode clears ignored subdirectories' '
git -C repo status --porcelain=v2 >out &&
test_must_be_empty out &&
git -C repo -c index.deleteSparseDirectories=false sparse-checkout reapply &&
test_path_is_dir repo/folder1 &&
test_path_is_dir repo/deep/deeper2 &&
git -C repo sparse-checkout reapply &&
test_path_is_missing repo/folder1 &&
test_path_is_missing repo/deep/deeper2 &&

Просмотреть файл

@ -155,6 +155,7 @@ init_repos () {
git -C sparse-index reset --hard &&
# initialize sparse-checkout definitions
git -C sparse-checkout config index.sparse false &&
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 &&
@ -455,6 +456,43 @@ test_expect_success 'diff --cached' '
test_all_match git diff --cached
'
test_expect_success 'diff partially-staged' '
init_repos &&
write_script edit-contents <<-\EOF &&
echo text >>$1
EOF
# Add file within cone
test_all_match git sparse-checkout set deep &&
run_on_all ../edit-contents deep/testfile &&
test_all_match git add deep/testfile &&
run_on_all ../edit-contents deep/testfile &&
test_all_match git diff &&
test_all_match git diff --staged &&
# Add file outside cone
test_all_match git reset --hard &&
run_on_all mkdir newdirectory &&
run_on_all ../edit-contents newdirectory/testfile &&
test_all_match git sparse-checkout set newdirectory &&
test_all_match git add newdirectory/testfile &&
run_on_all ../edit-contents newdirectory/testfile &&
test_all_match git sparse-checkout set &&
test_all_match git diff &&
test_all_match git diff --staged &&
# Merge conflict outside cone
test_all_match git reset --hard &&
test_all_match git checkout merge-left &&
test_all_match test_must_fail git merge merge-right &&
test_all_match git diff &&
test_all_match git diff --staged
'
# NEEDSWORK: sparse-checkout behaves differently from full-checkout when
# running this test with 'df-conflict-2' after 'df-conflict-1'.
test_expect_success 'diff with renames and conflicts' '
@ -912,7 +950,9 @@ test_expect_success 'read-tree --merge with directory-file conflicts' '
test_expect_success 'merge, cherry-pick, and rebase' '
init_repos &&
for OPERATION in "merge -m merge" cherry-pick "rebase --apply" "rebase --merge"
# microsoft/git specific: we need to use "quiet" mode
# to avoid different stderr for some rebases.
for OPERATION in "merge -m merge" cherry-pick "rebase -q --apply" "rebase -q --merge"
do
test_all_match git checkout -B temp update-deep &&
test_all_match git $OPERATION update-folder1 &&
@ -1151,6 +1191,34 @@ test_expect_success 'clean' '
test_sparse_match test_path_is_dir folder1
'
test_expect_success 'show (cached blobs/trees)' '
init_repos &&
test_all_match git show :a &&
test_all_match git show :deep/a &&
test_sparse_match git show :folder1/a &&
# Asking "git show" for directories in the index
# does not work as implemented. The error message is
# different for a full checkout and a sparse checkout
# when the directory is outside of the cone.
test_all_match test_must_fail git show :deep/ &&
test_must_fail git -C full-checkout show :folder1/ &&
test_must_fail git -C sparse-checkout show :folder1/ &&
# The sparse index actually has "folder1" inside, so
# "git show :folder1/" succeeds when it did not before.
git -C sparse-index show :folder1/ >actual &&
git -C sparse-index show HEAD:folder1 >expect &&
# The output of "git show" includes the way we
# referenced the objects, so strip that out.
test_line_count = 4 actual &&
tail -n 2 actual >actual-trunc &&
tail -n 2 expect >expect-trunc &&
test_cmp expect-trunc actual-trunc
'
test_expect_success 'submodule handling' '
init_repos &&
@ -1265,6 +1333,25 @@ test_expect_success 'sparse-index is not expanded' '
echo >>sparse-index/untracked.txt &&
ensure_not_expanded add . &&
ensure_not_expanded show :a &&
ensure_not_expanded show :deep/a &&
echo >>sparse-index/a &&
ensure_not_expanded stash &&
ensure_not_expanded stash list &&
ensure_not_expanded stash show stash@{0} &&
ensure_not_expanded stash apply stash@{0} &&
ensure_not_expanded stash drop stash@{0} &&
ensure_not_expanded stash -u &&
ensure_not_expanded stash pop &&
ensure_not_expanded stash create &&
oid=$(git -C sparse-index stash create) &&
ensure_not_expanded stash store -m "test" $oid &&
ensure_not_expanded reset --hard &&
ensure_not_expanded stash pop &&
ensure_not_expanded checkout-index -f a &&
ensure_not_expanded checkout-index -f --all &&
for ref in update-deep update-folder1 update-folder2 update-deep
@ -1279,6 +1366,11 @@ test_expect_success 'sparse-index is not expanded' '
ensure_not_expanded reset --merge update-deep &&
ensure_not_expanded reset --hard &&
echo a test change >>sparse-index/README.md &&
ensure_not_expanded diff &&
git -C sparse-index add README.md &&
ensure_not_expanded diff --staged &&
ensure_not_expanded reset base -- deep/a &&
ensure_not_expanded reset base -- nonexistent-file &&
ensure_not_expanded reset deepest -- deep &&
@ -1516,6 +1608,45 @@ test_expect_success 'ls-files' '
ensure_not_expanded ls-files --sparse
'
test_expect_success 'sparse index is not expanded: sparse-checkout' '
init_repos &&
ensure_not_expanded sparse-checkout set deep/deeper2 &&
ensure_not_expanded sparse-checkout set deep/deeper1 &&
ensure_not_expanded sparse-checkout set deep &&
ensure_not_expanded sparse-checkout add folder1 &&
ensure_not_expanded sparse-checkout set deep/deeper1 &&
ensure_not_expanded sparse-checkout set folder2 &&
echo >>sparse-index/folder2/a &&
git -C sparse-index add folder2/a &&
ensure_not_expanded sparse-checkout add folder1 &&
# Skip checks here, since deep/deeper1 is inside a sparse directory
# that must be expanded to check whether `deep/deeper1` is a file
# or not.
ensure_not_expanded sparse-checkout set --skip-checks deep/deeper1 &&
ensure_not_expanded sparse-checkout set
'
# NEEDSWORK: similar to `git add`, untracked files outside of the sparse
# checkout definition are successfully stashed and unstashed.
test_expect_success 'stash -u outside sparse checkout definition' '
init_repos &&
write_script edit-contents <<-\EOF &&
echo text >>$1
EOF
run_on_sparse mkdir -p folder1 &&
run_on_all ../edit-contents folder1/new &&
test_all_match git stash -u &&
test_all_match git status --porcelain=v2 &&
test_all_match git stash pop -q &&
test_all_match git status --porcelain=v2
'
# NEEDSWORK: a sparse-checkout behaves differently from a full checkout
# in this scenario, but it shouldn't.
test_expect_success 'reset mixed and checkout orphan' '

Просмотреть файл

@ -400,7 +400,7 @@ EOF
'
test_expect_failure 'ensure deserialize -v does not crash' '
test_expect_success 'ensure deserialize -v does not crash' '
git init -b main verbose_test &&
touch verbose_test/a &&

Просмотреть файл

@ -49,7 +49,7 @@ test_expect_success 'setup' '
echo "text" >B/b &&
git add A B &&
git commit -m sub &&
git sparse-checkout init --cone &&
git sparse-checkout init --cone --no-sparse-index &&
git sparse-checkout set B
) &&

Просмотреть файл

@ -52,6 +52,23 @@ test_expect_success 'run [--auto|--quiet]' '
test_subcommand git gc --no-quiet <run-no-quiet.txt
'
test_expect_success 'lock file behavior' '
test_when_finished git config --unset maintenance.commit-graph.schedule &&
git config maintenance.commit-graph.schedule hourly &&
touch .git/objects/maintenance.lock &&
git maintenance run --schedule=hourly --no-quiet 2>err &&
grep "lock file .* exists, skipping maintenance" err &&
test-tool chmtime =-22000 .git/objects/maintenance.lock &&
git maintenance run --schedule=hourly --no-quiet 2>err &&
grep "deleted stale lock file" err &&
test_path_is_missing .git/objects/maintenance.lock &&
git maintenance run --schedule=hourly 2>err &&
test_must_be_empty err
'
test_expect_success 'maintenance.auto config option' '
GIT_TRACE2_EVENT="$(pwd)/default" git commit --quiet --allow-empty -m 1 &&
test_subcommand git maintenance run --auto --quiet <default &&

Просмотреть файл

@ -21,6 +21,7 @@
#include "promisor-remote.h"
#include "entry.h"
#include "parallel-checkout.h"
#include "sparse-index.h"
/*
* Error messages expected by scripts out of plumbing commands such as
@ -2057,6 +2058,9 @@ enum update_sparsity_result update_sparsity(struct unpack_trees_options *o)
goto skip_sparse_checkout;
}
/* Expand sparse directories as needed */
expand_to_pattern_list(o->src_index, o->pl);
/* Set NEW_SKIP_WORKTREE on existing entries. */
mark_all_ce_unused(o->src_index);
mark_new_skip_worktree(o->pl, o->src_index, 0,