@matheusbsbs / matheus-bagliano / commits / 9037026d34
Fork 0

unpack-trees: fix sparse checkout's "unable to match directories"

Matching index entries against an excludes file currently has two problems. First, there's no function to do it. Code paths (like sparse checkout) that wanted to try it would iterate over index entries and for each index entry pass that path to excluded_from_list(). But that is not how excluded_from_list() works; one is supposed to feed in each ancester of a path before a given path to find out if it was excluded because of some parent or grandparent matching a bigsubdirectory/ pattern despite the path not matching any .gitignore pattern directly. Second, it's inefficient. The excludes mechanism is supposed to let us block off vast swaths of the filesystem as uninteresting; separately checking every index entry doesn't fit that model. Introduce a new function to take care of both these problems. This traverses the index in depth-first order (well, that's what order the index is in) to mark un-excluded entries. Maybe some day the in-core index format will be restructured to make this sort of operation easier. Or maybe we will want to try some binary search based thing. The interface is simple enough to allow all those things. Example: clear_ce_flags(the_index.cache, the_index.cache_nr, CE_CANDIDATE, CE_CLEARME, exclude_list); would clear the CE_CLEARME flag on all index entries with CE_CANDIDATE flag and not matched by exclude_list. Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Nguyễn Thái Ngọc Duy committed Nov 27, 2010 at 01:17 UTC 9037026d34907c0c94e3ab46f96068fa57da6ebc
3 files changed +155 -20
Documentation/git-read-tree.txt
-7
@@ -412,13 +412,6 @@ turn `core.sparseCheckout` on in order to have sparse checkout
412 support.
413
414
415 -BUGS
416 -----
417 -In order to match a directory with $GIT_DIR/info/sparse-checkout,
418 -trailing slash must be used. The form without trailing slash, while
419 -works with .gitignore, does not work with sparse checkout.
420 -
421 -
415 SEE ALSO
416 --------
417 linkgit:git-write-tree[1]; linkgit:git-ls-files[1];
t/t1011-read-tree-sparse-checkout.sh
+11 -3
@@ -94,12 +94,20 @@ test_expect_success 'match directories with trailing slash' '
94 test -f sub/added
95 '
96
97 -test_expect_failure 'match directories without trailing slash' '
98 - echo init.t >.git/info/sparse-checkout &&
97 +test_expect_success 'match directories without trailing slash' '
98 echo sub >>.git/info/sparse-checkout &&
99 git read-tree -m -u HEAD &&
100 git ls-files -t >result &&
102 - test_cmp expected.swt result &&
101 + test_cmp expected.swt-noinit result &&
102 + test ! -f init.t &&
103 + test -f sub/added
104 +'
105 +
106 +test_expect_success 'match directory pattern' '
107 + echo "s?b" >>.git/info/sparse-checkout &&
108 + git read-tree -m -u HEAD &&
109 + git ls-files -t >result &&
110 + test_cmp expected.swt-noinit result &&
111 test ! -f init.t &&
112 test -f sub/added
113 '
unpack-trees.c
+144 -10
@@ -245,15 +245,6 @@ static int check_updates(struct unpack_trees_options *o)
245 static int verify_uptodate_sparse(struct cache_entry *ce, struct unpack_trees_options *o);
246 static int verify_absent_sparse(struct cache_entry *ce, enum unpack_trees_error_types, struct unpack_trees_options *o);
247
248 -static int will_have_skip_worktree(const struct cache_entry *ce, struct unpack_trees_options *o)
249 -{
250 - const char *basename;
251 -
252 - basename = strrchr(ce->name, '/');
253 - basename = basename ? basename+1 : ce->name;
254 - return excluded_from_list(ce->name, ce_namelen(ce), basename, NULL, o->el) <= 0;
255 -}
256 -
248 static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_options *o)
249 {
250 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
825 return mask;
826 }
827
828 +/* Whole directory matching */
829 +static int clear_ce_flags_dir(struct cache_entry **cache, int nr,
830 + char *prefix, int prefix_len,
831 + char *basename,
832 + int select_mask, int clear_mask,
833 + struct exclude_list *el)
834 +{
835 + struct cache_entry **cache_end = cache + nr;
836 + int dtype = DT_DIR;
837 + int ret = excluded_from_list(prefix, prefix_len, basename, &dtype, el);
838 +
839 + prefix[prefix_len++] = '/';
840 +
841 + /* included, no clearing for any entries under this directory */
842 + if (!ret) {
843 + for (; cache != cache_end; cache++) {
844 + struct cache_entry *ce = *cache;
845 + if (strncmp(ce->name, prefix, prefix_len))
846 + break;
847 + }
848 + return nr - (cache_end - cache);
849 + }
850 +
851 + /* excluded, clear all selected entries under this directory. */
852 + if (ret == 1) {
853 + for (; cache != cache_end; cache++) {
854 + struct cache_entry *ce = *cache;
855 + if (select_mask && !(ce->ce_flags & select_mask))
856 + continue;
857 + if (strncmp(ce->name, prefix, prefix_len))
858 + break;
859 + ce->ce_flags &= ~clear_mask;
860 + }
861 + return nr - (cache_end - cache);
862 + }
863 +
864 + return 0;
865 +}
866 +
867 +/*
868 + * Traverse the index, find every entry that matches according to
869 + * o->el. Do "ce_flags &= ~clear_mask" on those entries. Return the
870 + * number of traversed entries.
871 + *
872 + * If select_mask is non-zero, only entries whose ce_flags has on of
873 + * those bits enabled are traversed.
874 + *
875 + * cache : pointer to an index entry
876 + * prefix_len : an offset to its path
877 + *
878 + * The current path ("prefix") including the trailing '/' is
879 + * cache[0]->name[0..(prefix_len-1)]
880 + * Top level path has prefix_len zero.
881 + */
882 +static int clear_ce_flags_1(struct cache_entry **cache, int nr,
883 + char *prefix, int prefix_len,
884 + int select_mask, int clear_mask,
885 + struct exclude_list *el)
886 +{
887 + struct cache_entry **cache_end = cache + nr;
888 +
889 + /*
890 + * Process all entries that have the given prefix and meet
891 + * select_mask condition
892 + */
893 + while(cache != cache_end) {
894 + struct cache_entry *ce = *cache;
895 + const char *name, *slash;
896 + int len, dtype;
897 +
898 + if (select_mask && !(ce->ce_flags & select_mask)) {
899 + cache++;
900 + continue;
901 + }
902 +
903 + if (prefix_len && strncmp(ce->name, prefix, prefix_len))
904 + break;
905 +
906 + name = ce->name + prefix_len;
907 + slash = strchr(name, '/');
908 +
909 + /* If it's a directory, try whole directory match first */
910 + if (slash) {
911 + int processed;
912 +
913 + len = slash - name;
914 + memcpy(prefix + prefix_len, name, len);
915 +
916 + /*
917 + * terminate the string (no trailing slash),
918 + * clear_c_f_dir needs it
919 + */
920 + prefix[prefix_len + len] = '\0';
921 + processed = clear_ce_flags_dir(cache, cache_end - cache,
922 + prefix, prefix_len + len,
923 + prefix + prefix_len,
924 + select_mask, clear_mask,
925 + el);
926 +
927 + /* clear_c_f_dir eats a whole dir already? */
928 + if (processed) {
929 + cache += processed;
930 + continue;
931 + }
932 +
933 + prefix[prefix_len + len++] = '/';
934 + cache += clear_ce_flags_1(cache, cache_end - cache,
935 + prefix, prefix_len + len,
936 + select_mask, clear_mask, el);
937 + continue;
938 + }
939 +
940 + /* Non-directory */
941 + dtype = ce_to_dtype(ce);
942 + if (excluded_from_list(ce->name, ce_namelen(ce), name, &dtype, el) > 0)
943 + ce->ce_flags &= ~clear_mask;
944 + cache++;
945 + }
946 + return nr - (cache_end - cache);
947 +}
948 +
949 +static int clear_ce_flags(struct cache_entry **cache, int nr,
950 + int select_mask, int clear_mask,
951 + struct exclude_list *el)
952 +{
953 + char prefix[PATH_MAX];
954 + return clear_ce_flags_1(cache, nr,
955 + prefix, 0,
956 + select_mask, clear_mask,
957 + el);
958 +}
959 +
960 /*
961 * Set/Clear CE_NEW_SKIP_WORKTREE according to $GIT_DIR/info/sparse-checkout
962 */
@@ -843,17 +966,28 @@ static void mark_new_skip_worktree(struct exclude_list *el,
966 {
967 int i;
968
969 + /*
970 + * 1. Pretend the narrowest worktree: only unmerged entries
971 + * are checked out
972 + */
973 for (i = 0; i < the_index->cache_nr; i++) {
974 struct cache_entry *ce = the_index->cache[i];
975
976 if (select_flag && !(ce->ce_flags & select_flag))
977 continue;
978
852 - if (!ce_stage(ce) && will_have_skip_worktree(ce, o))
979 + if (!ce_stage(ce))
980 ce->ce_flags |= skip_wt_flag;
981 else
982 ce->ce_flags &= ~skip_wt_flag;
983 }
984 +
985 + /*
986 + * 2. Widen worktree according to sparse-checkout file.
987 + * Matched entries will have skip_wt_flag cleared (i.e. "in")
988 + */
989 + clear_ce_flags(the_index->cache, the_index->cache_nr,
990 + select_flag, skip_wt_flag, el);
991 }
992
993 static int verify_absent(struct cache_entry *, enum unpack_trees_error_types, struct unpack_trees_options *);