Merge branch 'ds/sparse-cone'

Management of sparsely checked-out working tree has gained a
dedicated "sparse-checkout" command.

* ds/sparse-cone: (21 commits)
  sparse-checkout: improve OS ls compatibility
  sparse-checkout: respect core.ignoreCase in cone mode
  sparse-checkout: check for dirty status
  sparse-checkout: update working directory in-process for 'init'
  sparse-checkout: cone mode should not interact with .gitignore
  sparse-checkout: write using lockfile
  sparse-checkout: use in-process update for disable subcommand
  sparse-checkout: update working directory in-process
  sparse-checkout: sanitize for nested folders
  unpack-trees: add progress to clear_ce_flags()
  unpack-trees: hash less in cone mode
  sparse-checkout: init and set in cone mode
  sparse-checkout: use hashmaps for cone patterns
  sparse-checkout: add 'cone' mode
  trace2: add region in clear_ce_flags
  sparse-checkout: create 'disable' subcommand
  sparse-checkout: add '--stdin' option to set subcommand
  sparse-checkout: 'set' subcommand
  clone: add --sparse mode
  sparse-checkout: create 'init' subcommand
  ...
This commit is contained in:
Junio C Hamano 2019-12-25 11:21:58 -08:00
commit bd72a08d6c
20 changed files with 1375 additions and 47 deletions

1
.gitignore vendored
View File

@ -158,6 +158,7 @@
/git-show-branch
/git-show-index
/git-show-ref
/git-sparse-checkout
/git-stage
/git-stash
/git-status

View File

@ -599,8 +599,14 @@ core.multiPackIndex::
multi-pack-index design document].
core.sparseCheckout::
Enable "sparse checkout" feature. See section "Sparse checkout" in
linkgit:git-read-tree[1] for more information.
Enable "sparse checkout" feature. See linkgit:git-sparse-checkout[1]
for more information.
core.sparseCheckoutCone::
Enables the "cone mode" of the sparse checkout feature. When the
sparse-checkout file contains a limited set of patterns, then this
mode provides significant performance advantages. See
linkgit:git-sparse-checkout[1] for more information.
core.abbrev::
Set the length object names are abbreviated to. If

View File

@ -15,7 +15,7 @@ SYNOPSIS
[--dissociate] [--separate-git-dir <git dir>]
[--depth <depth>] [--[no-]single-branch] [--no-tags]
[--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]
[--[no-]remote-submodules] [--jobs <n>] [--] <repository>
[--[no-]remote-submodules] [--jobs <n>] [--sparse] [--] <repository>
[<directory>]
DESCRIPTION
@ -156,6 +156,12 @@ objects from the source repository into a pack in the cloned repository.
used, neither remote-tracking branches nor the related
configuration variables are created.
--sparse::
Initialize the sparse-checkout file so the working
directory starts with only the files in the root
of the repository. The sparse-checkout file can be
modified to grow the working directory as needed.
--mirror::
Set up a mirror of the source repository. This implies `--bare`.
Compared to `--bare`, `--mirror` not only maps local branches of the

View File

@ -436,7 +436,7 @@ support.
SEE ALSO
--------
linkgit:git-write-tree[1]; linkgit:git-ls-files[1];
linkgit:gitignore[5]
linkgit:gitignore[5]; linkgit:git-sparse-checkout[1];
GIT
---

View File

@ -0,0 +1,166 @@
git-sparse-checkout(1)
======================
NAME
----
git-sparse-checkout - Initialize and modify the sparse-checkout
configuration, which reduces the checkout to a set of paths
given by a list of atterns.
SYNOPSIS
--------
[verse]
'git sparse-checkout <subcommand> [options]'
DESCRIPTION
-----------
Initialize and modify the sparse-checkout configuration, which reduces
the checkout to a set of paths given by a list of patterns.
THIS COMMAND IS EXPERIMENTAL. ITS BEHAVIOR, AND THE BEHAVIOR OF OTHER
COMMANDS IN THE PRESENCE OF SPARSE-CHECKOUTS, WILL LIKELY CHANGE IN
THE FUTURE.
COMMANDS
--------
'list'::
Provide a list of the contents in the sparse-checkout file.
'init'::
Enable the `core.sparseCheckout` setting. If the
sparse-checkout file does not exist, then populate it with
patterns that match every file in the root directory and
no other directories, then will remove all directories tracked
by Git. Add patterns to the sparse-checkout file to
repopulate the working directory.
+
To avoid interfering with other worktrees, it first enables the
`extensions.worktreeConfig` setting and makes sure to set the
`core.sparseCheckout` setting in the worktree-specific config file.
'set'::
Write a set of patterns to the sparse-checkout file, as given as
a list of arguments following the 'set' subcommand. Update the
working directory to match the new patterns. Enable the
core.sparseCheckout config setting if it is not already enabled.
+
When the `--stdin` option is provided, the patterns are read from
standard in as a newline-delimited list instead of from the arguments.
'disable'::
Disable the `core.sparseCheckout` config setting, and restore the
working directory to include all files. Leaves the sparse-checkout
file intact so a later 'git sparse-checkout init' command may
return the working directory to the same state.
SPARSE CHECKOUT
---------------
"Sparse checkout" allows populating the working directory sparsely.
It uses the skip-worktree bit (see linkgit:git-update-index[1]) to tell
Git whether a file in the working directory is worth looking at. If
the skip-worktree bit is set, then the file is ignored in the working
directory. Git will not populate the contents of those files, which
makes a sparse checkout helpful when working in a repository with many
files, but only a few are important to the current user.
The `$GIT_DIR/info/sparse-checkout` file is used to define the
skip-worktree reference bitmap. When Git updates the working
directory, it updates the skip-worktree bits in the index based
on this file. The files matching the patterns in the file will
appear in the working directory, and the rest will not.
To enable the sparse-checkout feature, run `git sparse-checkout init` to
initialize a simple sparse-checkout file and enable the `core.sparseCheckout`
config setting. Then, run `git sparse-checkout set` to modify the patterns in
the sparse-checkout file.
To repopulate the working directory with all files, use the
`git sparse-checkout disable` command.
FULL PATTERN SET
----------------
By default, the sparse-checkout file uses the same syntax as `.gitignore`
files.
While `$GIT_DIR/info/sparse-checkout` is usually used to specify what
files are included, you can also specify what files are _not_ included,
using negative patterns. For example, to remove the file `unwanted`:
----------------
/*
!unwanted
----------------
CONE PATTERN SET
----------------
The full pattern set allows for arbitrary pattern matches and complicated
inclusion/exclusion rules. These can result in O(N*M) pattern matches when
updating the index, where N is the number of patterns and M is the number
of paths in the index. To combat this performance issue, a more restricted
pattern set is allowed when `core.spareCheckoutCone` is enabled.
The accepted patterns in the cone pattern set are:
1. *Recursive:* All paths inside a directory are included.
2. *Parent:* All files immediately inside a directory are included.
In addition to the above two patterns, we also expect that all files in the
root directory are included. If a recursive pattern is added, then all
leading directories are added as parent patterns.
By default, when running `git sparse-checkout init`, the root directory is
added as a parent pattern. At this point, the sparse-checkout file contains
the following patterns:
----------------
/*
!/*/
----------------
This says "include everything in root, but nothing two levels below root."
If we then add the folder `A/B/C` as a recursive pattern, the folders `A` and
`A/B` are added as parent patterns. The resulting sparse-checkout file is
now
----------------
/*
!/*/
/A/
!/A/*/
/A/B/
!/A/B/*/
/A/B/C/
----------------
Here, order matters, so the negative patterns are overridden by the positive
patterns that appear lower in the file.
If `core.sparseCheckoutCone=true`, then Git will parse the sparse-checkout file
expecting patterns of these types. Git will warn if the patterns do not match.
If the patterns do match the expected format, then Git will use faster hash-
based algorithms to compute inclusion in the sparse-checkout.
If `core.ignoreCase=true`, then the pattern-matching algorithm will use a
case-insensitive check. This corrects for case mismatched filenames in the
'git sparse-checkout set' command to reflect the expected cone in the working
directory.
SEE ALSO
--------
linkgit:git-read-tree[1]
linkgit:gitignore[5]
GIT
---
Part of the linkgit:git[1] suite

View File

@ -1127,6 +1127,7 @@ BUILTIN_OBJS += builtin/shortlog.o
BUILTIN_OBJS += builtin/show-branch.o
BUILTIN_OBJS += builtin/show-index.o
BUILTIN_OBJS += builtin/show-ref.o
BUILTIN_OBJS += builtin/sparse-checkout.o
BUILTIN_OBJS += builtin/stash.o
BUILTIN_OBJS += builtin/stripspace.o
BUILTIN_OBJS += builtin/submodule--helper.o

View File

@ -225,6 +225,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix);
int cmd_show(int argc, const char **argv, const char *prefix);
int cmd_show_branch(int argc, const char **argv, const char *prefix);
int cmd_show_index(int argc, const char **argv, const char *prefix);
int cmd_sparse_checkout(int argc, const char **argv, const char *prefix);
int cmd_status(int argc, const char **argv, const char *prefix);
int cmd_stash(int argc, const char **argv, const char *prefix);
int cmd_stripspace(int argc, const char **argv, const char *prefix);

View File

@ -59,6 +59,7 @@ static const char *real_git_dir;
static char *option_upload_pack = "git-upload-pack";
static int option_verbosity;
static int option_progress = -1;
static int option_sparse_checkout;
static enum transport_family family;
static struct string_list option_config = STRING_LIST_INIT_NODUP;
static struct string_list option_required_reference = STRING_LIST_INIT_NODUP;
@ -146,6 +147,8 @@ static struct option builtin_clone_options[] = {
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
OPT_BOOL(0, "remote-submodules", &option_remote_submodules,
N_("any cloned submodules will use their remote-tracking branch")),
OPT_BOOL(0, "sparse", &option_sparse_checkout,
N_("initialize sparse-checkout file to include only files at root")),
OPT_END()
};
@ -733,6 +736,27 @@ static void update_head(const struct ref *our, const struct ref *remote,
}
}
static int git_sparse_checkout_init(const char *repo)
{
struct argv_array argv = ARGV_ARRAY_INIT;
int result = 0;
argv_array_pushl(&argv, "-C", repo, "sparse-checkout", "init", NULL);
/*
* We must apply the setting in the current process
* for the later checkout to use the sparse-checkout file.
*/
core_apply_sparse_checkout = 1;
if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
error(_("failed to initialize sparse-checkout"));
result = 1;
}
argv_array_clear(&argv);
return result;
}
static int checkout(int submodule_progress)
{
struct object_id oid;
@ -1104,6 +1128,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (option_required_reference.nr || option_optional_reference.nr)
setup_reference();
if (option_sparse_checkout && git_sparse_checkout_init(repo))
return 1;
remote = remote_get(option_origin);
strbuf_addf(&default_refspec, "+%s*:%s*", src_ref_prefix,

View File

@ -185,7 +185,7 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
if (opts.reset || opts.merge || opts.prefix) {
if (read_cache_unmerged() && (opts.prefix || opts.merge))
die("You need to resolve your current index first");
die(_("You need to resolve your current index first"));
stage = opts.merge = 1;
}
resolve_undo_clear();

495
builtin/sparse-checkout.c Normal file
View File

@ -0,0 +1,495 @@
#include "builtin.h"
#include "config.h"
#include "dir.h"
#include "parse-options.h"
#include "pathspec.h"
#include "repository.h"
#include "run-command.h"
#include "strbuf.h"
#include "string-list.h"
#include "cache.h"
#include "cache-tree.h"
#include "lockfile.h"
#include "resolve-undo.h"
#include "unpack-trees.h"
#include "wt-status.h"
static const char *empty_base = "";
static char const * const builtin_sparse_checkout_usage[] = {
N_("git sparse-checkout (init|list|set|disable) <options>"),
NULL
};
static char *get_sparse_checkout_filename(void)
{
return git_pathdup("info/sparse-checkout");
}
static void write_patterns_to_file(FILE *fp, struct pattern_list *pl)
{
int i;
for (i = 0; i < pl->nr; i++) {
struct path_pattern *p = pl->patterns[i];
if (p->flags & PATTERN_FLAG_NEGATIVE)
fprintf(fp, "!");
fprintf(fp, "%s", p->pattern);
if (p->flags & PATTERN_FLAG_MUSTBEDIR)
fprintf(fp, "/");
fprintf(fp, "\n");
}
}
static int sparse_checkout_list(int argc, const char **argv)
{
struct pattern_list pl;
char *sparse_filename;
int res;
memset(&pl, 0, sizeof(pl));
sparse_filename = get_sparse_checkout_filename();
res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL);
free(sparse_filename);
if (res < 0) {
warning(_("this worktree is not sparse (sparse-checkout file may not exist)"));
return 0;
}
write_patterns_to_file(stdout, &pl);
clear_pattern_list(&pl);
return 0;
}
static int update_working_directory(struct pattern_list *pl)
{
int result = 0;
struct unpack_trees_options o;
struct lock_file lock_file = LOCK_INIT;
struct object_id oid;
struct tree *tree;
struct tree_desc t;
struct repository *r = the_repository;
if (repo_read_index_unmerged(r))
die(_("you need to resolve your current index first"));
if (get_oid("HEAD", &oid))
return 0;
tree = parse_tree_indirect(&oid);
parse_tree(tree);
init_tree_desc(&t, tree->buffer, tree->size);
memset(&o, 0, sizeof(o));
o.verbose_update = isatty(2);
o.merge = 1;
o.update = 1;
o.fn = oneway_merge;
o.head_idx = -1;
o.src_index = r->index;
o.dst_index = r->index;
o.skip_sparse_checkout = 0;
o.pl = pl;
o.keep_pattern_list = !!pl;
resolve_undo_clear_index(r->index);
setup_work_tree();
cache_tree_free(&r->index->cache_tree);
repo_hold_locked_index(r, &lock_file, LOCK_DIE_ON_ERROR);
core_apply_sparse_checkout = 1;
result = unpack_trees(1, &t, &o);
if (!result) {
prime_cache_tree(r, r->index, tree);
write_locked_index(r->index, &lock_file, COMMIT_LOCK);
} else
rollback_lock_file(&lock_file);
return result;
}
static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
{
int i;
struct pattern_entry *pe;
struct hashmap_iter iter;
struct string_list sl = STRING_LIST_INIT_DUP;
struct strbuf parent_pattern = STRBUF_INIT;
hashmap_for_each_entry(&pl->parent_hashmap, &iter, pe, ent) {
if (hashmap_get_entry(&pl->recursive_hashmap, pe, ent, NULL))
continue;
if (!hashmap_contains_parent(&pl->recursive_hashmap,
pe->pattern,
&parent_pattern))
string_list_insert(&sl, pe->pattern);
}
string_list_sort(&sl);
string_list_remove_duplicates(&sl, 0);
fprintf(fp, "/*\n!/*/\n");
for (i = 0; i < sl.nr; i++) {
char *pattern = sl.items[i].string;
if (strlen(pattern))
fprintf(fp, "%s/\n!%s/*/\n", pattern, pattern);
}
string_list_clear(&sl, 0);
hashmap_for_each_entry(&pl->recursive_hashmap, &iter, pe, ent) {
if (!hashmap_contains_parent(&pl->recursive_hashmap,
pe->pattern,
&parent_pattern))
string_list_insert(&sl, pe->pattern);
}
strbuf_release(&parent_pattern);
string_list_sort(&sl);
string_list_remove_duplicates(&sl, 0);
for (i = 0; i < sl.nr; i++) {
char *pattern = sl.items[i].string;
fprintf(fp, "%s/\n", pattern);
}
}
static int write_patterns_and_update(struct pattern_list *pl)
{
char *sparse_filename;
FILE *fp;
int fd;
struct lock_file lk = LOCK_INIT;
int result;
sparse_filename = get_sparse_checkout_filename();
fd = hold_lock_file_for_update(&lk, sparse_filename,
LOCK_DIE_ON_ERROR);
result = update_working_directory(pl);
if (result) {
rollback_lock_file(&lk);
free(sparse_filename);
clear_pattern_list(pl);
update_working_directory(NULL);
return result;
}
fp = xfdopen(fd, "w");
if (core_sparse_checkout_cone)
write_cone_to_file(fp, pl);
else
write_patterns_to_file(fp, pl);
fflush(fp);
commit_lock_file(&lk);
free(sparse_filename);
clear_pattern_list(pl);
return 0;
}
enum sparse_checkout_mode {
MODE_NO_PATTERNS = 0,
MODE_ALL_PATTERNS = 1,
MODE_CONE_PATTERNS = 2,
};
static int set_config(enum sparse_checkout_mode mode)
{
const char *config_path;
if (git_config_set_gently("extensions.worktreeConfig", "true")) {
error(_("failed to set extensions.worktreeConfig setting"));
return 1;
}
config_path = git_path("config.worktree");
git_config_set_in_file_gently(config_path,
"core.sparseCheckout",
mode ? "true" : NULL);
git_config_set_in_file_gently(config_path,
"core.sparseCheckoutCone",
mode == MODE_CONE_PATTERNS ? "true" : NULL);
return 0;
}
static char const * const builtin_sparse_checkout_init_usage[] = {
N_("git sparse-checkout init [--cone]"),
NULL
};
static struct sparse_checkout_init_opts {
int cone_mode;
} init_opts;
static int sparse_checkout_init(int argc, const char **argv)
{
struct pattern_list pl;
char *sparse_filename;
int res;
struct object_id oid;
int mode;
struct strbuf pattern = STRBUF_INIT;
static struct option builtin_sparse_checkout_init_options[] = {
OPT_BOOL(0, "cone", &init_opts.cone_mode,
N_("initialize the sparse-checkout in cone mode")),
OPT_END(),
};
repo_read_index(the_repository);
require_clean_work_tree(the_repository,
N_("initialize sparse-checkout"), NULL, 1, 0);
argc = parse_options(argc, argv, NULL,
builtin_sparse_checkout_init_options,
builtin_sparse_checkout_init_usage, 0);
if (init_opts.cone_mode) {
mode = MODE_CONE_PATTERNS;
core_sparse_checkout_cone = 1;
} else
mode = MODE_ALL_PATTERNS;
if (set_config(mode))
return 1;
memset(&pl, 0, sizeof(pl));
sparse_filename = get_sparse_checkout_filename();
res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL);
/* If we already have a sparse-checkout file, use it. */
if (res >= 0) {
free(sparse_filename);
core_apply_sparse_checkout = 1;
return update_working_directory(NULL);
}
if (get_oid("HEAD", &oid)) {
FILE *fp;
/* assume we are in a fresh repo, but update the sparse-checkout file */
fp = xfopen(sparse_filename, "w");
if (!fp)
die(_("failed to open '%s'"), sparse_filename);
free(sparse_filename);
fprintf(fp, "/*\n!/*/\n");
fclose(fp);
return 0;
}
strbuf_addstr(&pattern, "/*");
add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);
strbuf_addstr(&pattern, "!/*/");
add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);
return write_patterns_and_update(&pl);
}
static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *path)
{
struct pattern_entry *e = xmalloc(sizeof(*e));
e->patternlen = path->len;
e->pattern = strbuf_detach(path, NULL);
hashmap_entry_init(&e->ent,
ignore_case ?
strihash(e->pattern) :
strhash(e->pattern));
hashmap_add(&pl->recursive_hashmap, &e->ent);
while (e->patternlen) {
char *slash = strrchr(e->pattern, '/');
char *oldpattern = e->pattern;
size_t newlen;
if (slash == e->pattern)
break;
newlen = slash - e->pattern;
e = xmalloc(sizeof(struct pattern_entry));
e->patternlen = newlen;
e->pattern = xstrndup(oldpattern, newlen);
hashmap_entry_init(&e->ent,
ignore_case ?
strihash(e->pattern) :
strhash(e->pattern));
if (!hashmap_get_entry(&pl->parent_hashmap, e, ent, NULL))
hashmap_add(&pl->parent_hashmap, &e->ent);
}
}
static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl)
{
strbuf_trim(line);
strbuf_trim_trailing_dir_sep(line);
if (!line->len)
return;
if (line->buf[0] != '/')
strbuf_insert(line, 0, "/", 1);
insert_recursive_pattern(pl, line);
}
static char const * const builtin_sparse_checkout_set_usage[] = {
N_("git sparse-checkout set (--stdin | <patterns>)"),
NULL
};
static struct sparse_checkout_set_opts {
int use_stdin;
} set_opts;
static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
{
int i;
struct pattern_list pl;
int result;
int changed_config = 0;
static struct option builtin_sparse_checkout_set_options[] = {
OPT_BOOL(0, "stdin", &set_opts.use_stdin,
N_("read patterns from standard in")),
OPT_END(),
};
repo_read_index(the_repository);
require_clean_work_tree(the_repository,
N_("set sparse-checkout patterns"), NULL, 1, 0);
memset(&pl, 0, sizeof(pl));
argc = parse_options(argc, argv, prefix,
builtin_sparse_checkout_set_options,
builtin_sparse_checkout_set_usage,
PARSE_OPT_KEEP_UNKNOWN);
if (core_sparse_checkout_cone) {
struct strbuf line = STRBUF_INIT;
hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0);
hashmap_init(&pl.parent_hashmap, pl_hashmap_cmp, NULL, 0);
pl.use_cone_patterns = 1;
if (set_opts.use_stdin) {
while (!strbuf_getline(&line, stdin))
strbuf_to_cone_pattern(&line, &pl);
} else {
for (i = 0; i < argc; i++) {
strbuf_setlen(&line, 0);
strbuf_addstr(&line, argv[i]);
strbuf_to_cone_pattern(&line, &pl);
}
}
} else {
if (set_opts.use_stdin) {
struct strbuf line = STRBUF_INIT;
while (!strbuf_getline(&line, stdin)) {
size_t len;
char *buf = strbuf_detach(&line, &len);
add_pattern(buf, empty_base, 0, &pl, 0);
}
} else {
for (i = 0; i < argc; i++)
add_pattern(argv[i], empty_base, 0, &pl, 0);
}
}
if (!core_apply_sparse_checkout) {
set_config(MODE_ALL_PATTERNS);
core_apply_sparse_checkout = 1;
changed_config = 1;
}
result = write_patterns_and_update(&pl);
if (result && changed_config)
set_config(MODE_NO_PATTERNS);
clear_pattern_list(&pl);
return result;
}
static int sparse_checkout_disable(int argc, const char **argv)
{
struct pattern_list pl;
struct strbuf match_all = STRBUF_INIT;
repo_read_index(the_repository);
require_clean_work_tree(the_repository,
N_("disable sparse-checkout"), NULL, 1, 0);
memset(&pl, 0, sizeof(pl));
hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0);
hashmap_init(&pl.parent_hashmap, pl_hashmap_cmp, NULL, 0);
pl.use_cone_patterns = 0;
core_apply_sparse_checkout = 1;
strbuf_addstr(&match_all, "/*");
add_pattern(strbuf_detach(&match_all, NULL), empty_base, 0, &pl, 0);
if (update_working_directory(&pl))
die(_("error while refreshing working directory"));
clear_pattern_list(&pl);
return set_config(MODE_NO_PATTERNS);
}
int cmd_sparse_checkout(int argc, const char **argv, const char *prefix)
{
static struct option builtin_sparse_checkout_options[] = {
OPT_END(),
};
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_sparse_checkout_usage,
builtin_sparse_checkout_options);
argc = parse_options(argc, argv, prefix,
builtin_sparse_checkout_options,
builtin_sparse_checkout_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
git_config(git_default_config, NULL);
if (argc > 0) {
if (!strcmp(argv[0], "list"))
return sparse_checkout_list(argc, argv);
if (!strcmp(argv[0], "init"))
return sparse_checkout_init(argc, argv);
if (!strcmp(argv[0], "set"))
return sparse_checkout_set(argc, argv, prefix);
if (!strcmp(argv[0], "disable"))
return sparse_checkout_disable(argc, argv);
}
usage_with_options(builtin_sparse_checkout_usage,
builtin_sparse_checkout_options);
}

View File

@ -304,6 +304,7 @@ static inline unsigned int canon_mode(unsigned int mode)
struct split_index;
struct untracked_cache;
struct progress;
struct index_state {
struct cache_entry **cache;
@ -326,6 +327,7 @@ struct index_state {
uint64_t fsmonitor_last_update;
struct ewah_bitmap *fsmonitor_dirty;
struct mem_pool *ce_mem_pool;
struct progress *progress;
};
/* Name hashing */
@ -951,12 +953,14 @@ extern char *git_replace_ref_base;
extern int fsync_object_files;
extern int core_preload_index;
extern int core_apply_sparse_checkout;
extern int precomposed_unicode;
extern int protect_hfs;
extern int protect_ntfs;
extern const char *core_fsmonitor;
int core_apply_sparse_checkout;
int core_sparse_checkout_cone;
/*
* Include broken refs in all ref iterations, which will
* generally choke dangerous operations rather than letting

View File

@ -166,6 +166,7 @@ git-show-index plumbinginterrogators
git-show-ref plumbinginterrogators
git-sh-i18n purehelpers
git-sh-setup purehelpers
git-sparse-checkout mainporcelain worktree
git-stash mainporcelain
git-stage complete
git-status mainporcelain info

View File

@ -1364,6 +1364,11 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
return 0;
}
if (!strcmp(var, "core.sparsecheckoutcone")) {
core_sparse_checkout_cone = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "core.precomposeunicode")) {
precomposed_unicode = git_config_bool(var, value);
return 0;

216
dir.c
View File

@ -609,6 +609,159 @@ void parse_path_pattern(const char **pattern,
*patternlen = len;
}
int pl_hashmap_cmp(const void *unused_cmp_data,
const struct hashmap_entry *a,
const struct hashmap_entry *b,
const void *key)
{
const struct pattern_entry *ee1 =
container_of(a, struct pattern_entry, ent);
const struct pattern_entry *ee2 =
container_of(b, struct pattern_entry, ent);
size_t min_len = ee1->patternlen <= ee2->patternlen
? ee1->patternlen
: ee2->patternlen;
if (ignore_case)
return strncasecmp(ee1->pattern, ee2->pattern, min_len);
return strncmp(ee1->pattern, ee2->pattern, min_len);
}
static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern *given)
{
struct pattern_entry *translated;
char *truncated;
char *data = NULL;
if (!pl->use_cone_patterns)
return;
if (given->flags & PATTERN_FLAG_NEGATIVE &&
given->flags & PATTERN_FLAG_MUSTBEDIR &&
!strcmp(given->pattern, "/*")) {
pl->full_cone = 0;
return;
}
if (!given->flags && !strcmp(given->pattern, "/*")) {
pl->full_cone = 1;
return;
}
if (given->patternlen > 2 &&
!strcmp(given->pattern + given->patternlen - 2, "/*")) {
if (!(given->flags & PATTERN_FLAG_NEGATIVE)) {
/* Not a cone pattern. */
pl->use_cone_patterns = 0;
warning(_("unrecognized pattern: '%s'"), given->pattern);
goto clear_hashmaps;
}
truncated = xstrdup(given->pattern);
truncated[given->patternlen - 2] = 0;
translated = xmalloc(sizeof(struct pattern_entry));
translated->pattern = truncated;
translated->patternlen = given->patternlen - 2;
hashmap_entry_init(&translated->ent,
ignore_case ?
strihash(translated->pattern) :
strhash(translated->pattern));
if (!hashmap_get_entry(&pl->recursive_hashmap,
translated, ent, NULL)) {
/* We did not see the "parent" included */
warning(_("unrecognized negative pattern: '%s'"),
given->pattern);
free(truncated);
free(translated);
goto clear_hashmaps;
}
hashmap_add(&pl->parent_hashmap, &translated->ent);
hashmap_remove(&pl->recursive_hashmap, &translated->ent, &data);
free(data);
return;
}
if (given->flags & PATTERN_FLAG_NEGATIVE) {
warning(_("unrecognized negative pattern: '%s'"),
given->pattern);
goto clear_hashmaps;
}
translated = xmalloc(sizeof(struct pattern_entry));
translated->pattern = xstrdup(given->pattern);
translated->patternlen = given->patternlen;
hashmap_entry_init(&translated->ent,
ignore_case ?
strihash(translated->pattern) :
strhash(translated->pattern));
hashmap_add(&pl->recursive_hashmap, &translated->ent);
if (hashmap_get_entry(&pl->parent_hashmap, translated, ent, NULL)) {
/* we already included this at the parent level */
warning(_("your sparse-checkout file may have issues: pattern '%s' is repeated"),
given->pattern);
hashmap_remove(&pl->parent_hashmap, &translated->ent, &data);
free(data);
free(translated);
}
return;
clear_hashmaps:
warning(_("disabling cone pattern matching"));
hashmap_free_entries(&pl->parent_hashmap, struct pattern_entry, ent);
hashmap_free_entries(&pl->recursive_hashmap, struct pattern_entry, ent);
pl->use_cone_patterns = 0;
}
static int hashmap_contains_path(struct hashmap *map,
struct strbuf *pattern)
{
struct pattern_entry p;
/* Check straight mapping */
p.pattern = pattern->buf;
p.patternlen = pattern->len;
hashmap_entry_init(&p.ent,
ignore_case ?
strihash(p.pattern) :
strhash(p.pattern));
return !!hashmap_get_entry(map, &p, ent, NULL);
}
int hashmap_contains_parent(struct hashmap *map,
const char *path,
struct strbuf *buffer)
{
char *slash_pos;
strbuf_setlen(buffer, 0);
if (path[0] != '/')
strbuf_addch(buffer, '/');
strbuf_addstr(buffer, path);
slash_pos = strrchr(buffer->buf, '/');
while (slash_pos > buffer->buf) {
strbuf_setlen(buffer, slash_pos - buffer->buf);
if (hashmap_contains_path(map, buffer))
return 1;
slash_pos = strrchr(buffer->buf, '/');
}
return 0;
}
void add_pattern(const char *string, const char *base,
int baselen, struct pattern_list *pl, int srcpos)
{
@ -633,6 +786,8 @@ void add_pattern(const char *string, const char *base,
ALLOC_GROW(pl->patterns, pl->nr + 1, pl->alloc);
pl->patterns[pl->nr++] = pattern;
pattern->pl = pl;
add_pattern_to_hashsets(pl, pattern);
}
static int read_skip_worktree_file_from_index(const struct index_state *istate,
@ -858,6 +1013,9 @@ static int add_patterns_from_buffer(char *buf, size_t size,
int i, lineno = 1;
char *entry;
hashmap_init(&pl->recursive_hashmap, pl_hashmap_cmp, NULL, 0);
hashmap_init(&pl->parent_hashmap, pl_hashmap_cmp, NULL, 0);
pl->filebuf = buf;
if (skip_utf8_bom(&buf, size))
@ -1094,16 +1252,58 @@ enum pattern_match_result path_matches_pattern_list(
struct index_state *istate)
{
struct path_pattern *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;
struct strbuf parent_pathname = STRBUF_INIT;
int result = NOT_MATCHED;
const char *slash_pos;
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;
}
return UNDECIDED;
if (pl->full_cone)
return MATCHED;
strbuf_addch(&parent_pathname, '/');
strbuf_add(&parent_pathname, pathname, pathlen);
if (hashmap_contains_path(&pl->recursive_hashmap,
&parent_pathname)) {
result = MATCHED_RECURSIVE;
goto done;
}
slash_pos = strrchr(parent_pathname.buf, '/');
if (slash_pos == parent_pathname.buf) {
/* include every file in root */
result = MATCHED;
goto done;
}
strbuf_setlen(&parent_pathname, slash_pos - parent_pathname.buf);
if (hashmap_contains_path(&pl->parent_hashmap, &parent_pathname)) {
result = MATCHED;
goto done;
}
if (hashmap_contains_parent(&pl->recursive_hashmap,
pathname,
&parent_pathname))
result = MATCHED_RECURSIVE;
done:
strbuf_release(&parent_pathname);
return result;
}
static struct path_pattern *last_matching_pattern_from_lists(

36
dir.h
View File

@ -2,6 +2,7 @@
#define DIR_H
#include "cache.h"
#include "hashmap.h"
#include "strbuf.h"
/**
@ -70,6 +71,13 @@ struct path_pattern {
int srcpos;
};
/* used for hashmaps for cone patterns */
struct pattern_entry {
struct hashmap_entry ent;
char *pattern;
size_t patternlen;
};
/*
* Each excludes file will be parsed into a fresh exclude_list which
* is appended to the relevant exclude_list_group (either EXC_DIRS or
@ -88,6 +96,26 @@ struct pattern_list {
const char *src;
struct path_pattern **patterns;
/*
* While scanning the excludes, we attempt to match the patterns
* with a more restricted set that allows us to use hashsets for
* matching logic, which is faster than the linear lookup in the
* excludes array above. If non-zero, that check succeeded.
*/
unsigned use_cone_patterns;
unsigned full_cone;
/*
* Stores paths where everything starting with those paths
* is included.
*/
struct hashmap recursive_hashmap;
/*
* Used to check single-level parents of blobs.
*/
struct hashmap parent_hashmap;
};
/*
@ -345,6 +373,7 @@ enum pattern_match_result {
UNDECIDED = -1,
NOT_MATCHED = 0,
MATCHED = 1,
MATCHED_RECURSIVE = 2,
};
/*
@ -380,6 +409,13 @@ int is_excluded(struct dir_struct *dir,
struct index_state *istate,
const char *name, int *dtype);
int pl_hashmap_cmp(const void *unused_cmp_data,
const struct hashmap_entry *a,
const struct hashmap_entry *b,
const void *key);
int hashmap_contains_parent(struct hashmap *map,
const char *path,
struct strbuf *buffer);
struct pattern_list *add_pattern_list(struct dir_struct *dir,
int group_type, const char *src);
int add_patterns_from_file_to_list(const char *fname, const char *base, int baselen,

View File

@ -67,6 +67,7 @@ enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;
char *notes_ref_name;
int grafts_replace_parents = 1;
int core_apply_sparse_checkout;
int core_sparse_checkout_cone;
int merge_log_config = -1;
int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
unsigned long pack_size_limit_cfg;

1
git.c
View File

@ -572,6 +572,7 @@ static struct cmd_struct commands[] = {
{ "show-branch", cmd_show_branch, RUN_SETUP },
{ "show-index", cmd_show_index },
{ "show-ref", cmd_show_ref, RUN_SETUP },
{ "sparse-checkout", cmd_sparse_checkout, RUN_SETUP | NEED_WORK_TREE },
{ "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
/*
* NEEDSWORK: Until the builtin stash is thoroughly robust and no

View File

@ -0,0 +1,332 @@
#!/bin/sh
test_description='sparse checkout builtin tests'
. ./test-lib.sh
list_files() {
# Do not replace this with 'ls "$1"', as "ls" with BSD-lineage
# enables "-A" by default for root and ends up including ".git" and
# such in its output. (Note, though, that running the test suite as
# root is generally not recommended.)
(cd "$1" && printf '%s\n' *)
}
test_expect_success 'setup' '
git init repo &&
(
cd repo &&
echo "initial" >a &&
mkdir folder1 folder2 deep &&
mkdir deep/deeper1 deep/deeper2 &&
mkdir deep/deeper1/deepest &&
cp a folder1 &&
cp a folder2 &&
cp a deep &&
cp a deep/deeper1 &&
cp a deep/deeper2 &&
cp a deep/deeper1/deepest &&
git add . &&
git commit -m "initial commit"
)
'
test_expect_success 'git sparse-checkout list (empty)' '
git -C repo sparse-checkout list >list 2>err &&
test_must_be_empty list &&
test_i18ngrep "this worktree is not sparse (sparse-checkout file may not exist)" err
'
test_expect_success 'git sparse-checkout list (populated)' '
test_when_finished rm -f repo/.git/info/sparse-checkout &&
cat >repo/.git/info/sparse-checkout <<-EOF &&
/folder1/*
/deep/
**/a
!*bin*
EOF
cp repo/.git/info/sparse-checkout expect &&
git -C repo sparse-checkout list >list &&
test_cmp expect list
'
test_expect_success 'git sparse-checkout init' '
git -C repo sparse-checkout init &&
cat >expect <<-EOF &&
/*
!/*/
EOF
test_cmp expect repo/.git/info/sparse-checkout &&
test_cmp_config -C repo true core.sparsecheckout &&
list_files repo >dir &&
echo a >expect &&
test_cmp expect dir
'
test_expect_success 'git sparse-checkout list after init' '
git -C repo sparse-checkout list >actual &&
cat >expect <<-EOF &&
/*
!/*/
EOF
test_cmp expect actual
'
test_expect_success 'init with existing sparse-checkout' '
echo "*folder*" >> repo/.git/info/sparse-checkout &&
git -C repo sparse-checkout init &&
cat >expect <<-EOF &&
/*
!/*/
*folder*
EOF
test_cmp expect repo/.git/info/sparse-checkout &&
list_files repo >dir &&
cat >expect <<-EOF &&
a
folder1
folder2
EOF
test_cmp expect dir
'
test_expect_success 'clone --sparse' '
git clone --sparse repo clone &&
git -C clone sparse-checkout list >actual &&
cat >expect <<-EOF &&
/*
!/*/
EOF
test_cmp expect actual &&
list_files clone >dir &&
echo a >expect &&
test_cmp expect dir
'
test_expect_success 'set enables config' '
git init empty-config &&
(
cd empty-config &&
test_commit test file &&
test_path_is_missing .git/config.worktree &&
test_must_fail git sparse-checkout set nothing &&
test_path_is_file .git/config.worktree &&
test_must_fail git config core.sparseCheckout &&
git sparse-checkout set "/*" &&
test_cmp_config true core.sparseCheckout
)
'
test_expect_success 'set sparse-checkout using builtin' '
git -C repo sparse-checkout set "/*" "!/*/" "*folder*" &&
cat >expect <<-EOF &&
/*
!/*/
*folder*
EOF
git -C repo sparse-checkout list >actual &&
test_cmp expect actual &&
test_cmp expect repo/.git/info/sparse-checkout &&
list_files repo >dir &&
cat >expect <<-EOF &&
a
folder1
folder2
EOF
test_cmp expect dir
'
test_expect_success 'set sparse-checkout using --stdin' '
cat >expect <<-EOF &&
/*
!/*/
/folder1/
/folder2/
EOF
git -C repo sparse-checkout set --stdin <expect &&
git -C repo sparse-checkout list >actual &&
test_cmp expect actual &&
test_cmp expect repo/.git/info/sparse-checkout &&
list_files repo >dir &&
cat >expect <<-EOF &&
a
folder1
folder2
EOF
test_cmp expect dir
'
test_expect_success 'cone mode: match patterns' '
git -C repo config --worktree core.sparseCheckoutCone true &&
rm -rf repo/a repo/folder1 repo/folder2 &&
git -C repo read-tree -mu HEAD 2>err &&
test_i18ngrep ! "disabling cone patterns" err &&
git -C repo reset --hard &&
list_files repo >dir &&
cat >expect <<-EOF &&
a
folder1
folder2
EOF
test_cmp expect dir
'
test_expect_success 'cone mode: warn on bad pattern' '
test_when_finished mv sparse-checkout repo/.git/info/ &&
cp repo/.git/info/sparse-checkout . &&
echo "!/deep/deeper/*" >>repo/.git/info/sparse-checkout &&
git -C repo read-tree -mu HEAD 2>err &&
test_i18ngrep "unrecognized negative pattern" err
'
test_expect_success 'sparse-checkout disable' '
test_when_finished rm -rf repo/.git/info/sparse-checkout &&
git -C repo sparse-checkout disable &&
test_path_is_file repo/.git/info/sparse-checkout &&
git -C repo config --list >config &&
test_must_fail git config core.sparseCheckout &&
list_files repo >dir &&
cat >expect <<-EOF &&
a
deep
folder1
folder2
EOF
test_cmp expect dir
'
test_expect_success 'cone mode: init and set' '
git -C repo sparse-checkout init --cone &&
git -C repo config --list >config &&
test_i18ngrep "core.sparsecheckoutcone=true" config &&
list_files repo >dir &&
echo a >expect &&
test_cmp expect dir &&
git -C repo sparse-checkout set deep/deeper1/deepest/ 2>err &&
test_must_be_empty err &&
list_files repo >dir &&
cat >expect <<-EOF &&
a
deep
EOF
test_cmp expect dir &&
list_files repo/deep >dir &&
cat >expect <<-EOF &&
a
deeper1
EOF
test_cmp expect dir &&
list_files repo/deep/deeper1 >dir &&
cat >expect <<-EOF &&
a
deepest
EOF
test_cmp expect dir &&
cat >expect <<-EOF &&
/*
!/*/
/deep/
!/deep/*/
/deep/deeper1/
!/deep/deeper1/*/
/deep/deeper1/deepest/
EOF
test_cmp expect repo/.git/info/sparse-checkout &&
git -C repo sparse-checkout set --stdin 2>err <<-EOF &&
folder1
folder2
EOF
test_must_be_empty err &&
cat >expect <<-EOF &&
a
folder1
folder2
EOF
list_files repo >dir &&
test_cmp expect dir
'
test_expect_success 'cone mode: set with nested folders' '
git -C repo sparse-checkout set deep deep/deeper1/deepest 2>err &&
test_line_count = 0 err &&
cat >expect <<-EOF &&
/*
!/*/
/deep/
EOF
test_cmp repo/.git/info/sparse-checkout expect
'
test_expect_success 'revert to old sparse-checkout on bad update' '
test_when_finished git -C repo reset --hard &&
echo update >repo/deep/deeper2/a &&
cp repo/.git/info/sparse-checkout expect &&
test_must_fail git -C repo sparse-checkout set deep/deeper1 2>err &&
test_i18ngrep "cannot set sparse-checkout patterns" err &&
test_cmp repo/.git/info/sparse-checkout expect &&
list_files repo/deep >dir &&
cat >expect <<-EOF &&
a
deeper1
deeper2
EOF
test_cmp dir expect
'
test_expect_success 'revert to old sparse-checkout on empty update' '
git init empty-test &&
(
echo >file &&
git add file &&
git commit -m "test" &&
test_must_fail git sparse-checkout set nothing 2>err &&
test_i18ngrep "Sparse checkout leaves no entry on working directory" err &&
test_i18ngrep ! ".git/index.lock" err &&
git sparse-checkout set file
)
'
test_expect_success 'fail when lock is taken' '
test_when_finished rm -rf repo/.git/info/sparse-checkout.lock &&
touch repo/.git/info/sparse-checkout.lock &&
test_must_fail git -C repo sparse-checkout set deep 2>err &&
test_i18ngrep "File exists" err
'
test_expect_success '.gitignore should not warn about cone mode' '
git -C repo config --worktree core.sparseCheckoutCone true &&
echo "**/bin/*" >repo/.gitignore &&
git -C repo reset --hard 2>err &&
test_i18ngrep ! "disabling cone patterns" err
'
test_expect_success 'sparse-checkout (init|set|disable) fails with dirty status' '
git clone repo dirty &&
echo dirty >dirty/folder1/a &&
test_must_fail git -C dirty sparse-checkout init &&
test_must_fail git -C dirty sparse-checkout set /folder2/* /deep/deeper1/* &&
test_must_fail git -C dirty sparse-checkout disable &&
git -C dirty reset --hard &&
git -C dirty sparse-checkout init &&
git -C dirty sparse-checkout set /folder2/* /deep/deeper1/* &&
git -C dirty sparse-checkout disable
'
test_expect_success 'cone mode: set with core.ignoreCase=true' '
git -C repo sparse-checkout init --cone &&
git -C repo -c core.ignoreCase=true sparse-checkout set folder1 &&
cat >expect <<-EOF &&
/*
!/*/
/folder1/
EOF
test_cmp expect repo/.git/info/sparse-checkout &&
list_files repo >dir &&
cat >expect <<-EOF &&
a
folder1
EOF
test_cmp expect dir
'
test_done

View File

@ -1269,7 +1269,8 @@ static int clear_ce_flags_1(struct index_state *istate,
struct strbuf *prefix,
int select_mask, int clear_mask,
struct pattern_list *pl,
enum pattern_match_result default_match);
enum pattern_match_result default_match,
int progress_nr);
/* Whole directory matching */
static int clear_ce_flags_dir(struct index_state *istate,
@ -1278,20 +1279,23 @@ static int clear_ce_flags_dir(struct index_state *istate,
char *basename,
int select_mask, int clear_mask,
struct pattern_list *pl,
enum pattern_match_result default_match)
enum pattern_match_result default_match,
int progress_nr)
{
struct cache_entry **cache_end;
int dtype = DT_DIR;
int rc;
enum pattern_match_result ret;
ret = path_matches_pattern_list(prefix->buf, prefix->len,
basename, &dtype, pl, istate);
enum pattern_match_result ret, orig_ret;
orig_ret = path_matches_pattern_list(prefix->buf, prefix->len,
basename, &dtype, pl, istate);
strbuf_addch(prefix, '/');
/* If undecided, use matching result of parent dir in defval */
if (ret == UNDECIDED)
if (orig_ret == UNDECIDED)
ret = default_match;
else
ret = orig_ret;
for (cache_end = cache; cache_end != cache + nr; cache_end++) {
struct cache_entry *ce = *cache_end;
@ -1299,17 +1303,24 @@ static int clear_ce_flags_dir(struct index_state *istate,
break;
}
/*
* TODO: check pl, if there are no patterns that may conflict
* with ret (iow, we know in advance the incl/excl
* decision for the entire directory), clear flag here without
* calling clear_ce_flags_1(). That function will call
* the expensive path_matches_pattern_list() on every entry.
*/
rc = clear_ce_flags_1(istate, cache, cache_end - cache,
prefix,
select_mask, clear_mask,
pl, ret);
if (pl->use_cone_patterns && orig_ret == MATCHED_RECURSIVE) {
struct cache_entry **ce = cache;
rc = (cache_end - cache) / sizeof(struct cache_entry *);
while (ce < cache_end) {
(*ce)->ce_flags &= ~clear_mask;
ce++;
}
} else if (pl->use_cone_patterns && orig_ret == NOT_MATCHED) {
rc = (cache_end - cache) / sizeof(struct cache_entry *);
} else {
rc = clear_ce_flags_1(istate, cache, cache_end - cache,
prefix,
select_mask, clear_mask,
pl, ret,
progress_nr);
}
strbuf_setlen(prefix, prefix->len - 1);
return rc;
}
@ -1334,7 +1345,8 @@ static int clear_ce_flags_1(struct index_state *istate,
struct strbuf *prefix,
int select_mask, int clear_mask,
struct pattern_list *pl,
enum pattern_match_result default_match)
enum pattern_match_result default_match,
int progress_nr)
{
struct cache_entry **cache_end = cache + nr;
@ -1348,8 +1360,11 @@ static int clear_ce_flags_1(struct index_state *istate,
int len, dtype;
enum pattern_match_result ret;
display_progress(istate->progress, progress_nr);
if (select_mask && !(ce->ce_flags & select_mask)) {
cache++;
progress_nr++;
continue;
}
@ -1370,20 +1385,26 @@ static int clear_ce_flags_1(struct index_state *istate,
prefix,
prefix->buf + prefix->len - len,
select_mask, clear_mask,
pl, default_match);
pl, default_match,
progress_nr);
/* clear_c_f_dir eats a whole dir already? */
if (processed) {
cache += processed;
progress_nr += processed;
strbuf_setlen(prefix, prefix->len - len);
continue;
}
strbuf_addch(prefix, '/');
cache += clear_ce_flags_1(istate, cache, cache_end - cache,
prefix,
select_mask, clear_mask, pl,
default_match);
processed = clear_ce_flags_1(istate, cache, cache_end - cache,
prefix,
select_mask, clear_mask, pl,
default_match, progress_nr);
cache += processed;
progress_nr += processed;
strbuf_setlen(prefix, prefix->len - len - 1);
continue;
}
@ -1398,24 +1419,41 @@ static int clear_ce_flags_1(struct index_state *istate,
if (ret == MATCHED)
ce->ce_flags &= ~clear_mask;
cache++;
progress_nr++;
}
display_progress(istate->progress, progress_nr);
return nr - (cache_end - cache);
}
static int clear_ce_flags(struct index_state *istate,
int select_mask, int clear_mask,
struct pattern_list *pl)
struct pattern_list *pl,
int show_progress)
{
static struct strbuf prefix = STRBUF_INIT;
char label[100];
int rval;
strbuf_reset(&prefix);
if (show_progress)
istate->progress = start_delayed_progress(
_("Updating index flags"),
istate->cache_nr);
return clear_ce_flags_1(istate,
xsnprintf(label, sizeof(label), "clear_ce_flags(0x%08lx,0x%08lx)",
(unsigned long)select_mask, (unsigned long)clear_mask);
trace2_region_enter("unpack_trees", label, the_repository);
rval = clear_ce_flags_1(istate,
istate->cache,
istate->cache_nr,
&prefix,
select_mask, clear_mask,
pl, 0);
pl, 0, 0);
trace2_region_leave("unpack_trees", label, the_repository);
stop_progress(&istate->progress);
return rval;
}
/*
@ -1423,7 +1461,8 @@ static int clear_ce_flags(struct index_state *istate,
*/
static void mark_new_skip_worktree(struct pattern_list *pl,
struct index_state *istate,
int select_flag, int skip_wt_flag)
int select_flag, int skip_wt_flag,
int show_progress)
{
int i;
@ -1447,7 +1486,7 @@ static void mark_new_skip_worktree(struct pattern_list *pl,
* 2. Widen worktree according to sparse-checkout file.
* Matched entries will have skip_wt_flag cleared (i.e. "in")
*/
clear_ce_flags(istate, select_flag, skip_wt_flag, pl);
clear_ce_flags(istate, select_flag, skip_wt_flag, pl, show_progress);
}
static int verify_absent(const struct cache_entry *,
@ -1472,8 +1511,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
memset(&pl, 0, sizeof(pl));
if (!core_apply_sparse_checkout || !o->update)
o->skip_sparse_checkout = 1;
if (!o->skip_sparse_checkout) {
if (!o->skip_sparse_checkout && !o->pl) {
char *sparse = git_pathdup("info/sparse-checkout");
pl.use_cone_patterns = core_sparse_checkout_cone;
if (add_patterns_from_file_to_list(sparse, "", 0, &pl, NULL) < 0)
o->skip_sparse_checkout = 1;
else
@ -1511,7 +1551,8 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
* Sparse checkout loop #1: set NEW_SKIP_WORKTREE on existing entries
*/
if (!o->skip_sparse_checkout)
mark_new_skip_worktree(o->pl, o->src_index, 0, CE_NEW_SKIP_WORKTREE);
mark_new_skip_worktree(o->pl, o->src_index, 0,
CE_NEW_SKIP_WORKTREE, o->verbose_update);
if (!dfc)
dfc = xcalloc(1, cache_entry_size(0));
@ -1576,7 +1617,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
* If the will have NEW_SKIP_WORKTREE, also set CE_SKIP_WORKTREE
* so apply_sparse_checkout() won't attempt to remove it from worktree
*/
mark_new_skip_worktree(o->pl, &o->result, CE_ADDED, CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE);
mark_new_skip_worktree(o->pl, &o->result,
CE_ADDED, CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE,
o->verbose_update);
ret = 0;
for (i = 0; i < o->result.cache_nr; i++) {
@ -1644,7 +1687,8 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
done:
trace_performance_leave("unpack_trees");
clear_pattern_list(&pl);
if (!o->keep_pattern_list)
clear_pattern_list(&pl);
return ret;
return_failed:

View File

@ -59,7 +59,8 @@ struct unpack_trees_options {
quiet,
exiting_early,
show_all_errors,
dry_run;
dry_run,
keep_pattern_list;
const char *prefix;
int cache_bottom;
struct dir_struct *dir;