Merge branch 'nd/maint-fix-add-typo-detection'
authorJunio C Hamano <gitster@pobox.com>
Wed, 22 Dec 2010 22:40:26 +0000 (14:40 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 22 Dec 2010 22:40:26 +0000 (14:40 -0800)
* nd/maint-fix-add-typo-detection:
Revert "excluded_1(): support exclude files in index"
unpack-trees: fix sparse checkout's "unable to match directories"
unpack-trees: move all skip-worktree checks back to unpack_trees()
dir.c: add free_excludes()
cache.h: realign and use (1 << x) form for CE_* constants

1  2 
Documentation/git-read-tree.txt
cache.h
dir.c
dir.h
t/t1011-read-tree-sparse-checkout.sh
unpack-trees.c
index e88e9c2d55d19844ca56bc2d5b7b683e329b6fb8,f6037c4f6a7c6988a9a497455e555979d44e2600..634423a69ee6a4f2e9c7dd3941fd5160fbf6c99f
@@@ -11,7 -11,7 +11,7 @@@ SYNOPSI
  'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>]
                [-u [--exclude-per-directory=<gitignore>] | -i]]
                [--index-output=<file>] [--no-sparse-checkout]
 -              <tree-ish1> [<tree-ish2> [<tree-ish3>]]
 +              (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])
  
  
  DESCRIPTION
@@@ -114,10 -114,6 +114,10 @@@ OPTION
        Disable sparse checkout support even if `core.sparseCheckout`
        is true.
  
 +--empty::
 +      Instead of reading tree object(s) into the index, just empty
 +      it.
 +
  <tree-ish#>::
        The id of the tree object(s) to be read/merged.
  
@@@ -416,13 -412,6 +416,6 @@@ turn `core.sparseCheckout` on in order 
  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 --combined cache.h
index b45525846d04088ad3900fe4986e62259879523d,cb6931836a6485ae86e8af58a71570ee44e47c0f..32170c293d8f2e303b246cd5892f4d0b5d848861
+++ b/cache.h
@@@ -170,26 -170,26 +170,26 @@@ struct cache_entry 
   *
   * In-memory only flags
   */
- #define CE_UPDATE    (0x10000)
- #define CE_REMOVE    (0x20000)
- #define CE_UPTODATE  (0x40000)
- #define CE_ADDED     (0x80000)
+ #define CE_UPDATE            (1 << 16)
+ #define CE_REMOVE            (1 << 17)
+ #define CE_UPTODATE          (1 << 18)
+ #define CE_ADDED             (1 << 19)
  
- #define CE_HASHED    (0x100000)
- #define CE_UNHASHED  (0x200000)
- #define CE_CONFLICTED (0x800000)
+ #define CE_HASHED            (1 << 20)
+ #define CE_UNHASHED          (1 << 21)
+ #define CE_WT_REMOVE         (1 << 22) /* remove in work directory */
+ #define CE_CONFLICTED        (1 << 23)
  
- #define CE_WT_REMOVE (0x400000) /* remove in work directory */
- #define CE_UNPACKED  (0x1000000)
+ #define CE_UNPACKED          (1 << 24)
+ #define CE_NEW_SKIP_WORKTREE (1 << 25)
  
  /*
   * Extended on-disk flags
   */
- #define CE_INTENT_TO_ADD 0x20000000
- #define CE_SKIP_WORKTREE 0x40000000
+ #define CE_INTENT_TO_ADD     (1 << 29)
+ #define CE_SKIP_WORKTREE     (1 << 30)
  /* CE_EXTENDED2 is for future extension */
- #define CE_EXTENDED2 0x80000000
+ #define CE_EXTENDED2         (1 << 31)
  
  #define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD | CE_SKIP_WORKTREE)
  
@@@ -428,7 -428,7 +428,7 @@@ extern const char **get_pathspec(const 
  extern void setup_work_tree(void);
  extern const char *setup_git_directory_gently(int *);
  extern const char *setup_git_directory(void);
 -extern const char *prefix_path(const char *prefix, int len, const char *path);
 +extern char *prefix_path(const char *prefix, int len, const char *path);
  extern const char *prefix_filename(const char *prefix, int len, const char *path);
  extern int check_filename(const char *prefix, const char *name);
  extern void verify_filename(const char *prefix, const char *name);
@@@ -545,7 -545,6 +545,7 @@@ extern int assume_unchanged
  extern int prefer_symlink_refs;
  extern int log_all_ref_updates;
  extern int warn_ambiguous_refs;
 +extern int unique_abbrev_extra_length;
  extern int shared_repository;
  extern const char *apply_default_whitespace;
  extern const char *apply_default_ignorewhitespace;
@@@ -860,7 -859,7 +860,7 @@@ struct cache_def 
  
  extern int has_symlink_leading_path(const char *name, int len);
  extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int);
 -extern int has_symlink_or_noent_leading_path(const char *name, int len);
 +extern int check_leading_path(const char *name, int len);
  extern int has_dirs_only_path(const char *name, int len, int prefix_len);
  extern void schedule_dir_for_removal(const char *name, int len);
  extern void remove_scheduled_dirs(void);
@@@ -1004,9 -1003,6 +1004,9 @@@ extern int git_env_bool(const char *, i
  extern int git_config_system(void);
  extern int git_config_global(void);
  extern int config_error_nonbool(const char *);
 +extern const char *get_log_output_encoding(void);
 +extern const char *get_commit_output_encoding(void);
 +
  extern const char *config_exclusive_filename;
  
  #define MAX_GITNAME (1000)
@@@ -1091,17 -1087,15 +1091,17 @@@ void shift_tree_by(const unsigned char 
  /*
   * whitespace rules.
   * used by both diff and apply
 + * last two digits are tab width
   */
 -#define WS_BLANK_AT_EOL         01
 -#define WS_SPACE_BEFORE_TAB   02
 -#define WS_INDENT_WITH_NON_TAB        04
 -#define WS_CR_AT_EOL           010
 -#define WS_BLANK_AT_EOF        020
 -#define WS_TAB_IN_INDENT       040
 +#define WS_BLANK_AT_EOL         0100
 +#define WS_SPACE_BEFORE_TAB     0200
 +#define WS_INDENT_WITH_NON_TAB  0400
 +#define WS_CR_AT_EOL           01000
 +#define WS_BLANK_AT_EOF        02000
 +#define WS_TAB_IN_INDENT       04000
  #define WS_TRAILING_SPACE      (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF)
 -#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB)
 +#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|8)
 +#define WS_TAB_WIDTH_MASK        077
  extern unsigned whitespace_rule_cfg;
  extern unsigned whitespace_rule(const char *);
  extern unsigned parse_whitespace_rule(const char *);
@@@ -1110,7 -1104,6 +1110,7 @@@ extern void ws_check_emit(const char *l
  extern char *whitespace_error_string(unsigned ws);
  extern void ws_fix_copy(struct strbuf *, const char *, int, unsigned, int *);
  extern int ws_blank_line(const char *line, int len, unsigned ws_rule);
 +#define ws_tab_width(rule)     ((rule) & WS_TAB_WIDTH_MASK)
  
  /* ls-files */
  int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);
@@@ -1124,7 -1117,6 +1124,7 @@@ const char *split_cmdline_strerror(int 
  /* git.c */
  struct startup_info {
        int have_repository;
 +      const char *prefix;
  };
  extern struct startup_info *startup_info;
  
diff --combined dir.c
index 38f3e3eb9770a9f3d85f175881bd2dd4ced6548b,d059aa1e6e70f48f3069759c949aac6512300de5..570b651a17520cbb0273b9247ab0fcffc0129477
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -18,22 -18,6 +18,22 @@@ static int read_directory_recursive(str
        int check_only, const struct path_simplify *simplify);
  static int get_dtype(struct dirent *de, const char *path, int len);
  
 +/* helper string functions with support for the ignore_case flag */
 +int strcmp_icase(const char *a, const char *b)
 +{
 +      return ignore_case ? strcasecmp(a, b) : strcmp(a, b);
 +}
 +
 +int strncmp_icase(const char *a, const char *b, size_t count)
 +{
 +      return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
 +}
 +
 +int fnmatch_icase(const char *pattern, const char *string, int flags)
 +{
 +      return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0));
 +}
 +
  static int common_prefix(const char **pathspec)
  {
        const char *path, *slash, *next;
@@@ -107,30 -91,16 +107,30 @@@ static int match_one(const char *match
        if (!*match)
                return MATCHED_RECURSIVELY;
  
 -      for (;;) {
 -              unsigned char c1 = *match;
 -              unsigned char c2 = *name;
 -              if (c1 == '\0' || is_glob_special(c1))
 -                      break;
 -              if (c1 != c2)
 -                      return 0;
 -              match++;
 -              name++;
 -              namelen--;
 +      if (ignore_case) {
 +              for (;;) {
 +                      unsigned char c1 = tolower(*match);
 +                      unsigned char c2 = tolower(*name);
 +                      if (c1 == '\0' || is_glob_special(c1))
 +                              break;
 +                      if (c1 != c2)
 +                              return 0;
 +                      match++;
 +                      name++;
 +                      namelen--;
 +              }
 +      } else {
 +              for (;;) {
 +                      unsigned char c1 = *match;
 +                      unsigned char c2 = *name;
 +                      if (c1 == '\0' || is_glob_special(c1))
 +                              break;
 +                      if (c1 != c2)
 +                              return 0;
 +                      match++;
 +                      name++;
 +                      namelen--;
 +              }
        }
  
  
         * we need to match by fnmatch
         */
        matchlen = strlen(match);
 -      if (strncmp(match, name, matchlen))
 -              return !fnmatch(match, name, 0) ? MATCHED_FNMATCH : 0;
 +      if (strncmp_icase(match, name, matchlen))
 +              return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0;
  
        if (namelen == matchlen)
                return MATCHED_EXACTLY;
@@@ -253,6 -223,18 +253,18 @@@ static void *read_skip_worktree_file_fr
        return data;
  }
  
+ void free_excludes(struct exclude_list *el)
+ {
+       int i;
+       for (i = 0; i < el->nr; i++)
+               free(el->excludes[i]);
+       free(el->excludes);
+       el->nr = 0;
+       el->excludes = NULL;
+ }
  int add_excludes_from_file_to_list(const char *fname,
                                   const char *base,
                                   int baselen,
@@@ -389,13 -371,6 +401,6 @@@ int excluded_from_list(const char *path
                        int to_exclude = x->to_exclude;
  
                        if (x->flags & EXC_FLAG_MUSTBEDIR) {
-                               if (!dtype) {
-                                       if (!prefixcmp(pathname, exclude) &&
-                                           pathname[x->patternlen] == '/')
-                                               return to_exclude;
-                                       else
-                                               continue;
-                               }
                                if (*dtype == DT_UNKNOWN)
                                        *dtype = get_dtype(NULL, pathname, pathlen);
                                if (*dtype != DT_DIR)
                        if (x->flags & EXC_FLAG_NODIR) {
                                /* match basename */
                                if (x->flags & EXC_FLAG_NOWILDCARD) {
 -                                      if (!strcmp(exclude, basename))
 +                                      if (!strcmp_icase(exclude, basename))
                                                return to_exclude;
                                } else if (x->flags & EXC_FLAG_ENDSWITH) {
                                        if (x->patternlen - 1 <= pathlen &&
 -                                          !strcmp(exclude + 1, pathname + pathlen - x->patternlen + 1))
 +                                          !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1))
                                                return to_exclude;
                                } else {
 -                                      if (fnmatch(exclude, basename, 0) == 0)
 +                                      if (fnmatch_icase(exclude, basename, 0) == 0)
                                                return to_exclude;
                                }
                        }
  
                                if (pathlen < baselen ||
                                    (baselen && pathname[baselen-1] != '/') ||
 -                                  strncmp(pathname, x->base, baselen))
 +                                  strncmp_icase(pathname, x->base, baselen))
                                    continue;
  
                                if (x->flags & EXC_FLAG_NOWILDCARD) {
 -                                      if (!strcmp(exclude, pathname + baselen))
 +                                      if (!strcmp_icase(exclude, pathname + baselen))
                                                return to_exclude;
                                } else {
 -                                      if (fnmatch(exclude, pathname+baselen,
 +                                      if (fnmatch_icase(exclude, pathname+baselen,
                                                    FNM_PATHNAME) == 0)
                                            return to_exclude;
                                }
@@@ -499,39 -474,6 +504,39 @@@ enum exist_status 
        index_gitdir
  };
  
 +/*
 + * Do not use the alphabetically stored index to look up
 + * the directory name; instead, use the case insensitive
 + * name hash.
 + */
 +static enum exist_status directory_exists_in_index_icase(const char *dirname, int len)
 +{
 +      struct cache_entry *ce = index_name_exists(&the_index, dirname, len + 1, ignore_case);
 +      unsigned char endchar;
 +
 +      if (!ce)
 +              return index_nonexistent;
 +      endchar = ce->name[len];
 +
 +      /*
 +       * The cache_entry structure returned will contain this dirname
 +       * and possibly additional path components.
 +       */
 +      if (endchar == '/')
 +              return index_directory;
 +
 +      /*
 +       * If there are no additional path components, then this cache_entry
 +       * represents a submodule.  Submodules, despite being directories,
 +       * are stored in the cache without a closing slash.
 +       */
 +      if (!endchar && S_ISGITLINK(ce->ce_mode))
 +              return index_gitdir;
 +
 +      /* This should never be hit, but it exists just in case. */
 +      return index_nonexistent;
 +}
 +
  /*
   * The index sorts alphabetically by entry name, which
   * means that a gitlink sorts as '\0' at the end, while
   */
  static enum exist_status directory_exists_in_index(const char *dirname, int len)
  {
 -      int pos = cache_name_pos(dirname, len);
 +      int pos;
 +
 +      if (ignore_case)
 +              return directory_exists_in_index_icase(dirname, len);
 +
 +      pos = cache_name_pos(dirname, len);
        if (pos < 0)
                pos = -pos-1;
        while (pos < active_nr) {
@@@ -1033,12 -970,6 +1038,12 @@@ char *get_relative_cwd(char *buffer, in
        case '/':
                return cwd + 1;
        default:
 +              /*
 +               * dir can end with a path separator when it's root
 +               * directory. Return proper prefix in that case.
 +               */
 +              if (dir[-1] == '/')
 +                      return cwd;
                return NULL;
        }
  }
diff --combined dir.h
index b3e2104b9f231fbed88f98df12ad48d0d9992130,ce55008298bc8fc0e3bef1f8e076787bda90d5a9..72a764ed84828e4a6336d2880f2167fbc9d3143a
--- 1/dir.h
--- 2/dir.h
+++ b/dir.h
@@@ -78,6 -78,7 +78,7 @@@ extern int add_excludes_from_file_to_li
  extern void add_excludes_from_file(struct dir_struct *, const char *fname);
  extern void add_exclude(const char *string, const char *base,
                        int baselen, struct exclude_list *which);
+ extern void free_excludes(struct exclude_list *el);
  extern int file_exists(const char *);
  
  extern char *get_relative_cwd(char *buffer, int size, const char *dir);
@@@ -101,8 -102,4 +102,8 @@@ extern int remove_dir_recursively(struc
  /* tries to remove the path with empty directories along it, ignores ENOENT */
  extern int remove_path(const char *path);
  
 +extern int strcmp_icase(const char *a, const char *b);
 +extern int strncmp_icase(const char *a, const char *b, size_t count);
 +extern int fnmatch_icase(const char *pattern, const char *string, int flags);
 +
  #endif
index 0ef11bccb4a84b508b217d5d2bf4ef13c2d13282,67d9217bf45dbd40b269584a1ab8a5fe39b8b8ab..de84e35c4357c7329b3fae749228df28f31a9d3f
@@@ -49,7 -49,7 +49,7 @@@ test_expect_success 'read-tree without 
  '
  
  test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' '
 -      echo >.git/info/sparse-checkout
 +      echo >.git/info/sparse-checkout &&
        git read-tree -m -u HEAD &&
        git ls-files -t >result &&
        test_cmp expected.swt result &&
@@@ -94,12 -94,20 +94,20 @@@ test_expect_success 'match directories 
        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 --combined unpack-trees.c
index d5a453079a02ff8b7a63a3453d07935e0807c005,93ffc89cf369339fc242ad9bf6abccdebe46da83..1ca41b1a6986559a7c5ddc3983d751d75a0d23ff
@@@ -53,7 -53,6 +53,7 @@@ const char *unpack_plumbing_errors[NB_U
  void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
                                  const char *cmd)
  {
 +      int i;
        const char **msgs = opts->msgs;
        const char *msg;
        char *tmp;
@@@ -97,9 -96,6 +97,9 @@@
                "The following Working tree files would be removed by sparse checkout update:\n%s";
  
        opts->show_all_errors = 1;
 +      /* rejected paths may not have a static buffer */
 +      for (i = 0; i < ARRAY_SIZE(opts->unpack_rejects); i++)
 +              opts->unpack_rejects[i].strdup_strings = 1;
  }
  
  static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
@@@ -128,6 -124,7 +128,6 @@@ static int add_rejected_path(struct unp
                             enum unpack_trees_error_types e,
                             const char *path)
  {
 -      struct rejected_paths_list *newentry;
        if (!o->show_all_errors)
                return error(ERRORMSG(o, e), path);
  
         * Otherwise, insert in a list for future display by
         * display_error_msgs()
         */
 -      newentry = xmalloc(sizeof(struct rejected_paths_list));
 -      newentry->path = (char *)path;
 -      newentry->next = o->unpack_rejects[e];
 -      o->unpack_rejects[e] = newentry;
 +      string_list_append(&o->unpack_rejects[e], path);
        return -1;
  }
  
 -/*
 - * free all the structures allocated for the error <e>
 - */
 -static void free_rejected_paths(struct unpack_trees_options *o,
 -                              enum unpack_trees_error_types e)
 -{
 -      while (o->unpack_rejects[e]) {
 -              struct rejected_paths_list *del = o->unpack_rejects[e];
 -              o->unpack_rejects[e] = o->unpack_rejects[e]->next;
 -              free(del);
 -      }
 -      free(o->unpack_rejects[e]);
 -}
 -
  /*
   * display all the error messages stored in a nice way
   */
  static void display_error_msgs(struct unpack_trees_options *o)
  {
 -      int e;
 +      int e, i;
        int something_displayed = 0;
        for (e = 0; e < NB_UNPACK_TREES_ERROR_TYPES; e++) {
 -              if (o->unpack_rejects[e]) {
 -                      struct rejected_paths_list *rp;
 +              struct string_list *rejects = &o->unpack_rejects[e];
 +              if (rejects->nr > 0) {
                        struct strbuf path = STRBUF_INIT;
                        something_displayed = 1;
 -                      for (rp = o->unpack_rejects[e]; rp; rp = rp->next)
 -                              strbuf_addf(&path, "\t%s\n", rp->path);
 +                      for (i = 0; i < rejects->nr; i++)
 +                              strbuf_addf(&path, "\t%s\n", rejects->items[i].string);
                        error(ERRORMSG(o, e), path.buf);
                        strbuf_release(&path);
 -                      free_rejected_paths(o, e);
                }
 +              string_list_clear(rejects, 0);
        }
        if (something_displayed)
                printf("Aborting\n");
   */
  static void unlink_entry(struct cache_entry *ce)
  {
 -      if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce)))
 +      if (!check_leading_path(ce->name, ce_namelen(ce)))
                return;
        if (remove_or_warn(ce->ce_mode, ce->name))
                return;
@@@ -231,20 -245,11 +231,11 @@@ static int check_updates(struct unpack_
  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);
  
-       if (!ce_stage(ce) && will_have_skip_worktree(ce, o))
+       if (ce->ce_flags & CE_NEW_SKIP_WORKTREE)
                ce->ce_flags |= CE_SKIP_WORKTREE;
        else
                ce->ce_flags &= ~CE_SKIP_WORKTREE;
@@@ -319,7 -324,7 +310,7 @@@ static void mark_all_ce_unused(struct i
  {
        int i;
        for (i = 0; i < index->cache_nr; i++)
-               index->cache[i]->ce_flags &= ~CE_UNPACKED;
+               index->cache[i]->ce_flags &= ~(CE_UNPACKED | CE_ADDED | CE_NEW_SKIP_WORKTREE);
  }
  
  static int locate_in_src_index(struct cache_entry *ce,
@@@ -820,9 -825,177 +811,177 @@@ static int unpack_callback(int n, unsig
        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
+  */
+ static void mark_new_skip_worktree(struct exclude_list *el,
+                                  struct index_state *the_index,
+                                  int select_flag, int skip_wt_flag)
+ {
+       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))
+                       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 *);
  /*
   * N-way merge "len" trees.  Returns 0 on success, -1 on failure to manipulate the
   * resulting index, -2 on failure to reflect the changes to the work tree.
+  *
+  * CE_ADDED, CE_UNPACKED and CE_NEW_SKIP_WORKTREE are used internally
   */
  int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)
  {
        o->merge_size = len;
        mark_all_ce_unused(o->src_index);
  
+       /*
+        * Sparse checkout loop #1: set NEW_SKIP_WORKTREE on existing entries
+        */
+       if (!o->skip_sparse_checkout)
+               mark_new_skip_worktree(o->el, o->src_index, 0, CE_NEW_SKIP_WORKTREE);
        if (!dfc)
                dfc = xcalloc(1, cache_entry_size(0));
        o->df_conflict_entry = dfc;
  
        if (!o->skip_sparse_checkout) {
                int empty_worktree = 1;
-               for (i = 0;i < o->result.cache_nr;i++) {
+               /*
+                * Sparse checkout loop #2: set NEW_SKIP_WORKTREE on entries not in loop #1
+                * 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->el, &o->result, CE_ADDED, CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE);
+               for (i = 0; i < o->result.cache_nr; i++) {
                        struct cache_entry *ce = o->result.cache[i];
  
+                       /*
+                        * Entries marked with CE_ADDED in merged_entry() do not have
+                        * verify_absent() check (the check is effectively disabled
+                        * because CE_NEW_SKIP_WORKTREE is set unconditionally).
+                        *
+                        * Do the real check now because we have had
+                        * correct CE_NEW_SKIP_WORKTREE
+                        */
+                       if (ce->ce_flags & CE_ADDED &&
+                           verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
+                                       return -1;
                        if (apply_sparse_checkout(ce, o)) {
                                ret = -1;
                                goto done;
                *o->dst_index = o->result;
  
  done:
-       for (i = 0;i < el.nr;i++)
-               free(el.excludes[i]);
-       if (el.excludes)
-               free(el.excludes);
+       free_excludes(&el);
        return ret;
  
  return_failed:
@@@ -1003,7 -1198,7 +1184,7 @@@ static int verify_uptodate_1(struct cac
  static int verify_uptodate(struct cache_entry *ce,
                           struct unpack_trees_options *o)
  {
-       if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o))
+       if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
                return 0;
        return verify_uptodate_1(ce, o, ERROR_NOT_UPTODATE_FILE);
  }
@@@ -1113,65 -1308,14 +1294,65 @@@ static int verify_clean_subdirectory(st
   * See if we can find a case-insensitive match in the index that also
   * matches the stat information, and assume it's that other file!
   */
 -static int icase_exists(struct unpack_trees_options *o, struct cache_entry *dst, struct stat *st)
 +static int icase_exists(struct unpack_trees_options *o, const char *name, int len, struct stat *st)
  {
        struct cache_entry *src;
  
 -      src = index_name_exists(o->src_index, dst->name, ce_namelen(dst), 1);
 +      src = index_name_exists(o->src_index, name, len, 1);
        return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
  }
  
 +static int check_ok_to_remove(const char *name, int len, int dtype,
 +                            struct cache_entry *ce, struct stat *st,
 +                            enum unpack_trees_error_types error_type,
 +                            struct unpack_trees_options *o)
 +{
 +      struct cache_entry *result;
 +
 +      /*
 +       * It may be that the 'lstat()' succeeded even though
 +       * target 'ce' was absent, because there is an old
 +       * entry that is different only in case..
 +       *
 +       * Ignore that lstat() if it matches.
 +       */
 +      if (ignore_case && icase_exists(o, name, len, st))
 +              return 0;
 +
 +      if (o->dir && excluded(o->dir, name, &dtype))
 +              /*
 +               * ce->name is explicitly excluded, so it is Ok to
 +               * overwrite it.
 +               */
 +              return 0;
 +      if (S_ISDIR(st->st_mode)) {
 +              /*
 +               * We are checking out path "foo" and
 +               * found "foo/." in the working tree.
 +               * This is tricky -- if we have modified
 +               * files that are in "foo/" we would lose
 +               * them.
 +               */
 +              if (verify_clean_subdirectory(ce, error_type, o) < 0)
 +                      return -1;
 +              return 0;
 +      }
 +
 +      /*
 +       * The previous round may already have decided to
 +       * delete this path, which is in a subdirectory that
 +       * is being replaced with a blob.
 +       */
 +      result = index_name_exists(&o->result, name, len, 0);
 +      if (result) {
 +              if (result->ce_flags & CE_REMOVE)
 +                      return 0;
 +      }
 +
 +      return o->gently ? -1 :
 +              add_rejected_path(o, error_type, name);
 +}
 +
  /*
   * We do not want to remove or overwrite a working tree file that
   * is not tracked, unless it is ignored.
@@@ -1180,36 -1324,68 +1361,36 @@@ static int verify_absent_1(struct cache
                                 enum unpack_trees_error_types error_type,
                                 struct unpack_trees_options *o)
  {
 +      int len;
        struct stat st;
  
        if (o->index_only || o->reset || !o->update)
                return 0;
  
 -      if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce)))
 +      len = check_leading_path(ce->name, ce_namelen(ce));
 +      if (!len)
                return 0;
 +      else if (len > 0) {
 +              char path[PATH_MAX + 1];
 +              memcpy(path, ce->name, len);
 +              path[len] = 0;
 +              lstat(path, &st);
 +
 +              return check_ok_to_remove(path, len, DT_UNKNOWN, NULL, &st,
 +                              error_type, o);
 +      } else if (!lstat(ce->name, &st))
 +              return check_ok_to_remove(ce->name, ce_namelen(ce),
 +                              ce_to_dtype(ce), ce, &st,
 +                              error_type, o);
  
 -      if (!lstat(ce->name, &st)) {
 -              int dtype = ce_to_dtype(ce);
 -              struct cache_entry *result;
 -
 -              /*
 -               * It may be that the 'lstat()' succeeded even though
 -               * target 'ce' was absent, because there is an old
 -               * entry that is different only in case..
 -               *
 -               * Ignore that lstat() if it matches.
 -               */
 -              if (ignore_case && icase_exists(o, ce, &st))
 -                      return 0;
 -
 -              if (o->dir && excluded(o->dir, ce->name, &dtype))
 -                      /*
 -                       * ce->name is explicitly excluded, so it is Ok to
 -                       * overwrite it.
 -                       */
 -                      return 0;
 -              if (S_ISDIR(st.st_mode)) {
 -                      /*
 -                       * We are checking out path "foo" and
 -                       * found "foo/." in the working tree.
 -                       * This is tricky -- if we have modified
 -                       * files that are in "foo/" we would lose
 -                       * them.
 -                       */
 -                      if (verify_clean_subdirectory(ce, error_type, o) < 0)
 -                              return -1;
 -                      return 0;
 -              }
 -
 -              /*
 -               * The previous round may already have decided to
 -               * delete this path, which is in a subdirectory that
 -               * is being replaced with a blob.
 -               */
 -              result = index_name_exists(&o->result, ce->name, ce_namelen(ce), 0);
 -              if (result) {
 -                      if (result->ce_flags & CE_REMOVE)
 -                              return 0;
 -              }
 -
 -              return o->gently ? -1 :
 -                      add_rejected_path(o, error_type, ce->name);
 -      }
        return 0;
  }
 +
  static int verify_absent(struct cache_entry *ce,
                         enum unpack_trees_error_types error_type,
                         struct unpack_trees_options *o)
  {
-       if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o))
+       if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
                return 0;
        return verify_absent_1(ce, error_type, o);
  }
@@@ -1231,10 -1407,23 +1412,23 @@@ static int merged_entry(struct cache_en
        int update = CE_UPDATE;
  
        if (!old) {
+               /*
+                * New index entries. In sparse checkout, the following
+                * verify_absent() will be delayed until after
+                * traverse_trees() finishes in unpack_trees(), then:
+                *
+                *  - CE_NEW_SKIP_WORKTREE will be computed correctly
+                *  - verify_absent() be called again, this time with
+                *    correct CE_NEW_SKIP_WORKTREE
+                *
+                * verify_absent() call here does nothing in sparse
+                * checkout (i.e. o->skip_sparse_checkout == 0)
+                */
+               update |= CE_ADDED;
+               merge->ce_flags |= CE_NEW_SKIP_WORKTREE;
                if (verify_absent(merge, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
                        return -1;
-               if (!o->skip_sparse_checkout && will_have_skip_worktree(merge, o))
-                       update |= CE_SKIP_WORKTREE;
                invalidate_ce_path(merge, o);
        } else if (!(old->ce_flags & CE_CONFLICTED)) {
                /*
                } else {
                        if (verify_uptodate(old, o))
                                return -1;
-                       if (ce_skip_worktree(old))
-                               update |= CE_SKIP_WORKTREE;
+                       /* Migrate old flags over */
+                       update |= old->ce_flags & (CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE);
                        invalidate_ce_path(old, o);
                }
        } else {