diff options
-rw-r--r-- | Documentation/git-read-tree.txt | 7 | ||||
-rwxr-xr-x | t/t1011-read-tree-sparse-checkout.sh | 14 | ||||
-rw-r--r-- | unpack-trees.c | 154 |
3 files changed, 155 insertions, 20 deletions
diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 2e78da448f..f6037c4f6a 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -412,13 +412,6 @@ turn `core.sparseCheckout` on in order to have sparse checkout support. -BUGS ----- -In order to match a directory with $GIT_DIR/info/sparse-checkout, -trailing slash must be used. The form without trailing slash, while -works with .gitignore, does not work with sparse checkout. - - SEE ALSO -------- linkgit:git-write-tree[1]; linkgit:git-ls-files[1]; diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh index 8008fa2d89..67d9217bf4 100755 --- a/t/t1011-read-tree-sparse-checkout.sh +++ b/t/t1011-read-tree-sparse-checkout.sh @@ -94,12 +94,20 @@ test_expect_success 'match directories with trailing slash' ' test -f sub/added ' -test_expect_failure 'match directories without trailing slash' ' - echo init.t >.git/info/sparse-checkout && +test_expect_success 'match directories without trailing slash' ' echo sub >>.git/info/sparse-checkout && git read-tree -m -u HEAD && git ls-files -t >result && - test_cmp expected.swt result && + test_cmp expected.swt-noinit result && + test ! -f init.t && + test -f sub/added +' + +test_expect_success 'match directory pattern' ' + echo "s?b" >>.git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files -t >result && + test_cmp expected.swt-noinit result && test ! -f init.t && test -f sub/added ' diff --git a/unpack-trees.c b/unpack-trees.c index a6518db60d..93ffc89cf3 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -245,15 +245,6 @@ static int check_updates(struct unpack_trees_options *o) static int verify_uptodate_sparse(struct cache_entry *ce, struct unpack_trees_options *o); static int verify_absent_sparse(struct cache_entry *ce, enum unpack_trees_error_types, struct unpack_trees_options *o); -static int will_have_skip_worktree(const struct cache_entry *ce, struct unpack_trees_options *o) -{ - const char *basename; - - basename = strrchr(ce->name, '/'); - basename = basename ? basename+1 : ce->name; - return excluded_from_list(ce->name, ce_namelen(ce), basename, NULL, o->el) <= 0; -} - static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_options *o) { int was_skip_worktree = ce_skip_worktree(ce); @@ -834,6 +825,138 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str return mask; } +/* Whole directory matching */ +static int clear_ce_flags_dir(struct cache_entry **cache, int nr, + char *prefix, int prefix_len, + char *basename, + int select_mask, int clear_mask, + struct exclude_list *el) +{ + struct cache_entry **cache_end = cache + nr; + int dtype = DT_DIR; + int ret = excluded_from_list(prefix, prefix_len, basename, &dtype, el); + + prefix[prefix_len++] = '/'; + + /* included, no clearing for any entries under this directory */ + if (!ret) { + for (; cache != cache_end; cache++) { + struct cache_entry *ce = *cache; + if (strncmp(ce->name, prefix, prefix_len)) + break; + } + return nr - (cache_end - cache); + } + + /* excluded, clear all selected entries under this directory. */ + if (ret == 1) { + for (; cache != cache_end; cache++) { + struct cache_entry *ce = *cache; + if (select_mask && !(ce->ce_flags & select_mask)) + continue; + if (strncmp(ce->name, prefix, prefix_len)) + break; + ce->ce_flags &= ~clear_mask; + } + return nr - (cache_end - cache); + } + + return 0; +} + +/* + * Traverse the index, find every entry that matches according to + * o->el. Do "ce_flags &= ~clear_mask" on those entries. Return the + * number of traversed entries. + * + * If select_mask is non-zero, only entries whose ce_flags has on of + * those bits enabled are traversed. + * + * cache : pointer to an index entry + * prefix_len : an offset to its path + * + * The current path ("prefix") including the trailing '/' is + * cache[0]->name[0..(prefix_len-1)] + * Top level path has prefix_len zero. + */ +static int clear_ce_flags_1(struct cache_entry **cache, int nr, + char *prefix, int prefix_len, + int select_mask, int clear_mask, + struct exclude_list *el) +{ + struct cache_entry **cache_end = cache + nr; + + /* + * Process all entries that have the given prefix and meet + * select_mask condition + */ + while(cache != cache_end) { + struct cache_entry *ce = *cache; + const char *name, *slash; + int len, dtype; + + if (select_mask && !(ce->ce_flags & select_mask)) { + cache++; + continue; + } + + if (prefix_len && strncmp(ce->name, prefix, prefix_len)) + break; + + name = ce->name + prefix_len; + slash = strchr(name, '/'); + + /* If it's a directory, try whole directory match first */ + if (slash) { + int processed; + + len = slash - name; + memcpy(prefix + prefix_len, name, len); + + /* + * terminate the string (no trailing slash), + * clear_c_f_dir needs it + */ + prefix[prefix_len + len] = '\0'; + processed = clear_ce_flags_dir(cache, cache_end - cache, + prefix, prefix_len + len, + prefix + prefix_len, + select_mask, clear_mask, + el); + + /* clear_c_f_dir eats a whole dir already? */ + if (processed) { + cache += processed; + continue; + } + + prefix[prefix_len + len++] = '/'; + cache += clear_ce_flags_1(cache, cache_end - cache, + prefix, prefix_len + len, + select_mask, clear_mask, el); + continue; + } + + /* Non-directory */ + dtype = ce_to_dtype(ce); + if (excluded_from_list(ce->name, ce_namelen(ce), name, &dtype, el) > 0) + ce->ce_flags &= ~clear_mask; + cache++; + } + return nr - (cache_end - cache); +} + +static int clear_ce_flags(struct cache_entry **cache, int nr, + int select_mask, int clear_mask, + struct exclude_list *el) +{ + char prefix[PATH_MAX]; + return clear_ce_flags_1(cache, nr, + prefix, 0, + select_mask, clear_mask, + el); +} + /* * Set/Clear CE_NEW_SKIP_WORKTREE according to $GIT_DIR/info/sparse-checkout */ @@ -843,17 +966,28 @@ static void mark_new_skip_worktree(struct exclude_list *el, { int i; + /* + * 1. Pretend the narrowest worktree: only unmerged entries + * are checked out + */ for (i = 0; i < the_index->cache_nr; i++) { struct cache_entry *ce = the_index->cache[i]; if (select_flag && !(ce->ce_flags & select_flag)) continue; - if (!ce_stage(ce) && will_have_skip_worktree(ce, o)) + if (!ce_stage(ce)) ce->ce_flags |= skip_wt_flag; else ce->ce_flags &= ~skip_wt_flag; } + + /* + * 2. Widen worktree according to sparse-checkout file. + * Matched entries will have skip_wt_flag cleared (i.e. "in") + */ + clear_ce_flags(the_index->cache, the_index->cache_nr, + select_flag, skip_wt_flag, el); } static int verify_absent(struct cache_entry *, enum unpack_trees_error_types, struct unpack_trees_options *); |