Merge branch 'nd/fix-untracked-cache-invalidation' into next
authorJunio C Hamano <gitster@pobox.com>
Thu, 8 Feb 2018 23:08:31 +0000 (15:08 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 8 Feb 2018 23:08:31 +0000 (15:08 -0800)
Some bugs around "untracked cache" feature have been fixed.

* nd/fix-untracked-cache-invalidation:
dir.c: ignore paths containing .git when invalidating untracked cache
dir.c: stop ignoring opendir() error in open_cached_dir()
dir.c: fix missing dir invalidation in untracked code
dir.c: avoid stat() in valid_cached_dir()
status: add a failing test showing a core.untrackedCache bug

1  2 
dir.c
dir.h
unpack-trees.c
diff --combined dir.c
index 536416ff2df5f7c475deba4739d4edaf340ae898,7afc6756ecbecadc87f587f1032844a4999f4ffd..6dd91be8185d4012e11495a9238636913fedeeef
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -221,55 -221,6 +221,55 @@@ int within_depth(const char *name, int 
        return 1;
  }
  
 +/*
 + * Read the contents of the blob with the given OID into a buffer.
 + * Append a trailing LF to the end if the last line doesn't have one.
 + *
 + * Returns:
 + *    -1 when the OID is invalid or unknown or does not refer to a blob.
 + *     0 when the blob is empty.
 + *     1 along with { data, size } of the (possibly augmented) buffer
 + *       when successful.
 + *
 + * Optionally updates the given oid_stat with the given OID (when valid).
 + */
 +static int do_read_blob(const struct object_id *oid, struct oid_stat *oid_stat,
 +                      size_t *size_out, char **data_out)
 +{
 +      enum object_type type;
 +      unsigned long sz;
 +      char *data;
 +
 +      *size_out = 0;
 +      *data_out = NULL;
 +
 +      data = read_sha1_file(oid->hash, &type, &sz);
 +      if (!data || type != OBJ_BLOB) {
 +              free(data);
 +              return -1;
 +      }
 +
 +      if (oid_stat) {
 +              memset(&oid_stat->stat, 0, sizeof(oid_stat->stat));
 +              oidcpy(&oid_stat->oid, oid);
 +      }
 +
 +      if (sz == 0) {
 +              free(data);
 +              return 0;
 +      }
 +
 +      if (data[sz - 1] != '\n') {
 +              data = xrealloc(data, st_add(sz, 1));
 +              data[sz++] = '\n';
 +      }
 +
 +      *size_out = xsize_t(sz);
 +      *data_out = data;
 +
 +      return 1;
 +}
 +
  #define DO_MATCH_EXCLUDE   (1<<0)
  #define DO_MATCH_DIRECTORY (1<<1)
  #define DO_MATCH_SUBMODULE (1<<2)
@@@ -650,21 -601,32 +650,21 @@@ void add_exclude(const char *string, co
        x->el = el;
  }
  
 -static void *read_skip_worktree_file_from_index(const struct index_state *istate,
 -                                              const char *path, size_t *size,
 -                                              struct sha1_stat *sha1_stat)
 +static int read_skip_worktree_file_from_index(const struct index_state *istate,
 +                                            const char *path,
 +                                            size_t *size_out, char **data_out,
 +                                            struct oid_stat *oid_stat)
  {
        int pos, len;
 -      unsigned long sz;
 -      enum object_type type;
 -      void *data;
  
        len = strlen(path);
        pos = index_name_pos(istate, path, len);
        if (pos < 0)
 -              return NULL;
 +              return -1;
        if (!ce_skip_worktree(istate->cache[pos]))
 -              return NULL;
 -      data = read_sha1_file(istate->cache[pos]->oid.hash, &type, &sz);
 -      if (!data || type != OBJ_BLOB) {
 -              free(data);
 -              return NULL;
 -      }
 -      *size = xsize_t(sz);
 -      if (sha1_stat) {
 -              memset(&sha1_stat->stat, 0, sizeof(sha1_stat->stat));
 -              hashcpy(sha1_stat->sha1, istate->cache[pos]->oid.hash);
 -      }
 -      return data;
 +              return -1;
 +
 +      return do_read_blob(&istate->cache[pos]->oid, oid_stat, size_out, data_out);
  }
  
  /*
@@@ -744,8 -706,8 +744,8 @@@ static struct untracked_cache_dir *look
        FLEX_ALLOC_MEM(d, name, name, len);
  
        ALLOC_GROW(dir->dirs, dir->dirs_nr + 1, dir->dirs_alloc);
 -      memmove(dir->dirs + first + 1, dir->dirs + first,
 -              (dir->dirs_nr - first) * sizeof(*dir->dirs));
 +      MOVE_ARRAY(dir->dirs + first + 1, dir->dirs + first,
 +                 dir->dirs_nr - first);
        dir->dirs_nr++;
        dir->dirs[first] = d;
        return d;
@@@ -771,17 -733,22 +771,26 @@@ static void invalidate_directory(struc
                                 struct untracked_cache_dir *dir)
  {
        int i;
-       uc->dir_invalidated++;
+       /*
+        * Invalidation increment here is just roughly correct. If
+        * untracked_nr or any of dirs[].recurse is non-zero, we
+        * should increment dir_invalidated too. But that's more
+        * expensive to do.
+        */
+       if (dir->valid)
+               uc->dir_invalidated++;
        dir->valid = 0;
        dir->untracked_nr = 0;
        for (i = 0; i < dir->dirs_nr; i++)
                dir->dirs[i]->recurse = 0;
  }
  
 +static int add_excludes_from_buffer(char *buf, size_t size,
 +                                  const char *base, int baselen,
 +                                  struct exclude_list *el);
 +
  /*
   * Given a file with name "fname", read it (either from disk, or from
   * an index if 'istate' is non-null), parse it and store the
   * ss_valid is non-zero, "ss" must contain good value as input.
   */
  static int add_excludes(const char *fname, const char *base, int baselen,
 -                      struct exclude_list *el,
 -                      struct index_state *istate,
 -                      struct sha1_stat *sha1_stat)
 +                      struct exclude_list *el, struct index_state *istate,
 +                      struct oid_stat *oid_stat)
  {
        struct stat st;
 -      int fd, i, lineno = 1;
 +      int r;
 +      int fd;
        size_t size = 0;
 -      char *buf, *entry;
 +      char *buf;
  
        fd = open(fname, O_RDONLY);
        if (fd < 0 || fstat(fd, &st) < 0) {
                        warn_on_fopen_errors(fname);
                else
                        close(fd);
 -              if (!istate ||
 -                  (buf = read_skip_worktree_file_from_index(istate, fname, &size, sha1_stat)) == NULL)
 +              if (!istate)
                        return -1;
 -              if (size == 0) {
 -                      free(buf);
 -                      return 0;
 -              }
 -              if (buf[size-1] != '\n') {
 -                      buf = xrealloc(buf, st_add(size, 1));
 -                      buf[size++] = '\n';
 -              }
 +              r = read_skip_worktree_file_from_index(istate, fname,
 +                                                     &size, &buf,
 +                                                     oid_stat);
 +              if (r != 1)
 +                      return r;
        } else {
                size = xsize_t(st.st_size);
                if (size == 0) {
 -                      if (sha1_stat) {
 -                              fill_stat_data(&sha1_stat->stat, &st);
 -                              hashcpy(sha1_stat->sha1, EMPTY_BLOB_SHA1_BIN);
 -                              sha1_stat->valid = 1;
 +                      if (oid_stat) {
 +                              fill_stat_data(&oid_stat->stat, &st);
 +                              oidcpy(&oid_stat->oid, &empty_blob_oid);
 +                              oid_stat->valid = 1;
                        }
                        close(fd);
                        return 0;
                }
                buf[size++] = '\n';
                close(fd);
 -              if (sha1_stat) {
 +              if (oid_stat) {
                        int pos;
 -                      if (sha1_stat->valid &&
 -                          !match_stat_data_racy(istate, &sha1_stat->stat, &st))
 +                      if (oid_stat->valid &&
 +                          !match_stat_data_racy(istate, &oid_stat->stat, &st))
                                ; /* no content change, ss->sha1 still good */
                        else if (istate &&
                                 (pos = index_name_pos(istate, fname, strlen(fname))) >= 0 &&
                                 !ce_stage(istate->cache[pos]) &&
                                 ce_uptodate(istate->cache[pos]) &&
                                 !would_convert_to_git(istate, fname))
 -                              hashcpy(sha1_stat->sha1,
 -                                      istate->cache[pos]->oid.hash);
 +                              oidcpy(&oid_stat->oid,
 +                                     &istate->cache[pos]->oid);
                        else
 -                              hash_sha1_file(buf, size, "blob", sha1_stat->sha1);
 -                      fill_stat_data(&sha1_stat->stat, &st);
 -                      sha1_stat->valid = 1;
 +                              hash_object_file(buf, size, "blob",
 +                                               &oid_stat->oid);
 +                      fill_stat_data(&oid_stat->stat, &st);
 +                      oid_stat->valid = 1;
                }
        }
  
 +      add_excludes_from_buffer(buf, size, base, baselen, el);
 +      return 0;
 +}
 +
 +static int add_excludes_from_buffer(char *buf, size_t size,
 +                                  const char *base, int baselen,
 +                                  struct exclude_list *el)
 +{
 +      int i, lineno = 1;
 +      char *entry;
 +
        el->filebuf = buf;
  
        if (skip_utf8_bom(&buf, size))
@@@ -892,23 -851,6 +901,23 @@@ int add_excludes_from_file_to_list(cons
        return add_excludes(fname, base, baselen, el, istate, NULL);
  }
  
 +int add_excludes_from_blob_to_list(
 +      struct object_id *oid,
 +      const char *base, int baselen,
 +      struct exclude_list *el)
 +{
 +      char *buf;
 +      size_t size;
 +      int r;
 +
 +      r = do_read_blob(oid, NULL, &size, &buf);
 +      if (r != 1)
 +              return r;
 +
 +      add_excludes_from_buffer(buf, size, base, baselen, el);
 +      return 0;
 +}
 +
  struct exclude_list *add_exclude_list(struct dir_struct *dir,
                                      int group_type, const char *src)
  {
   * Used to set up core.excludesfile and .git/info/exclude lists.
   */
  static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,
 -                                   struct sha1_stat *sha1_stat)
 +                                   struct oid_stat *oid_stat)
  {
        struct exclude_list *el;
        /*
        if (!dir->untracked)
                dir->unmanaged_exclude_files++;
        el = add_exclude_list(dir, EXC_FILE, fname);
 -      if (add_excludes(fname, "", 0, el, NULL, sha1_stat) < 0)
 +      if (add_excludes(fname, "", 0, el, NULL, oid_stat) < 0)
                die("cannot use %s as an exclude file", fname);
  }
  
@@@ -1177,7 -1119,7 +1186,7 @@@ static void prep_exclude(struct dir_str
  
        while (current < baselen) {
                const char *cp;
 -              struct sha1_stat sha1_stat;
 +              struct oid_stat oid_stat;
  
                stk = xcalloc(1, sizeof(*stk));
                if (current < 0) {
                }
  
                /* Try to read per-directory file */
 -              hashclr(sha1_stat.sha1);
 -              sha1_stat.valid = 0;
 +              oidclr(&oid_stat.oid);
 +              oid_stat.valid = 0;
                if (dir->exclude_per_dir &&
                    /*
                     * If we know that no files have been added in
                        strbuf_addstr(&sb, dir->exclude_per_dir);
                        el->src = strbuf_detach(&sb, NULL);
                        add_excludes(el->src, el->src, stk->baselen, el, istate,
 -                                   untracked ? &sha1_stat : NULL);
 +                                   untracked ? &oid_stat : NULL);
                }
                /*
                 * NEEDSWORK: when untracked cache is enabled, prep_exclude()
                 * order, though, if you do that.
                 */
                if (untracked &&
 -                  hashcmp(sha1_stat.sha1, untracked->exclude_sha1)) {
 +                  hashcmp(oid_stat.oid.hash, untracked->exclude_sha1)) {
                        invalidate_gitignore(dir->untracked, untracked);
 -                      hashcpy(untracked->exclude_sha1, sha1_stat.sha1);
 +                      hashcpy(untracked->exclude_sha1, oid_stat.oid.hash);
                }
                dir->exclude_stack = stk;
                current = stk->baselen;
@@@ -1770,7 -1712,7 +1779,7 @@@ static enum path_treatment treat_path(s
        if (!de)
                return treat_path_fast(dir, untracked, cdir, istate, path,
                                       baselen, pathspec);
-       if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
+       if (is_dot_or_dotdot(de->d_name) || !fspathcmp(de->d_name, ".git"))
                return path_none;
        strbuf_setlen(path, baselen);
        strbuf_addstr(path, de->d_name);
@@@ -1806,24 -1748,19 +1815,19 @@@ static int valid_cached_dir(struct dir_
         */
        refresh_fsmonitor(istate);
        if (!(dir->untracked->use_fsmonitor && untracked->valid)) {
-               if (stat(path->len ? path->buf : ".", &st)) {
-                       invalidate_directory(dir->untracked, untracked);
+               if (lstat(path->len ? path->buf : ".", &st)) {
                        memset(&untracked->stat_data, 0, sizeof(untracked->stat_data));
                        return 0;
                }
                if (!untracked->valid ||
                        match_stat_data_racy(istate, &untracked->stat_data, &st)) {
-                       if (untracked->valid)
-                               invalidate_directory(dir->untracked, untracked);
                        fill_stat_data(&untracked->stat_data, &st);
                        return 0;
                }
        }
  
-       if (untracked->check_only != !!check_only) {
-               invalidate_directory(dir->untracked, untracked);
+       if (untracked->check_only != !!check_only)
                return 0;
-       }
  
        /*
         * prep_exclude will be called eventually on this directory,
@@@ -1850,13 -1787,20 +1854,20 @@@ static int open_cached_dir(struct cache
                           struct strbuf *path,
                           int check_only)
  {
+       const char *c_path;
        memset(cdir, 0, sizeof(*cdir));
        cdir->untracked = untracked;
        if (valid_cached_dir(dir, untracked, istate, path, check_only))
                return 0;
-       cdir->fdir = opendir(path->len ? path->buf : ".");
-       if (dir->untracked)
+       c_path = path->len ? path->buf : ".";
+       cdir->fdir = opendir(c_path);
+       if (!cdir->fdir)
+               warning_errno(_("could not open directory '%s'"), c_path);
+       if (dir->untracked) {
+               invalidate_directory(dir->untracked, untracked);
                dir->untracked->dir_opened++;
+       }
        if (!cdir->fdir)
                return -1;
        return 0;
@@@ -2225,13 -2169,13 +2236,13 @@@ static struct untracked_cache_dir *vali
  
        /* Validate $GIT_DIR/info/exclude and core.excludesfile */
        root = dir->untracked->root;
 -      if (hashcmp(dir->ss_info_exclude.sha1,
 -                  dir->untracked->ss_info_exclude.sha1)) {
 +      if (oidcmp(&dir->ss_info_exclude.oid,
 +                 &dir->untracked->ss_info_exclude.oid)) {
                invalidate_gitignore(dir->untracked, root);
                dir->untracked->ss_info_exclude = dir->ss_info_exclude;
        }
 -      if (hashcmp(dir->ss_excludes_file.sha1,
 -                  dir->untracked->ss_excludes_file.sha1)) {
 +      if (oidcmp(&dir->ss_excludes_file.oid,
 +                 &dir->untracked->ss_excludes_file.oid)) {
                invalidate_gitignore(dir->untracked, root);
                dir->untracked->ss_excludes_file = dir->ss_excludes_file;
        }
@@@ -2245,7 -2189,6 +2256,7 @@@ int read_directory(struct dir_struct *d
                   const char *path, int len, const struct pathspec *pathspec)
  {
        struct untracked_cache_dir *untracked;
 +      uint64_t start = getnanotime();
  
        if (has_symlink_leading_path(path, len))
                return dir->nr;
                dir->nr = i;
        }
  
 +      trace_performance_since(start, "read directory %.*s", len, path);
        if (dir->untracked) {
                static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS);
                trace_printf_key(&trace_untracked_stats,
@@@ -2637,8 -2579,8 +2648,8 @@@ void write_untracked_extension(struct s
        FLEX_ALLOC_MEM(ouc, exclude_per_dir, untracked->exclude_per_dir, len);
        stat_data_to_disk(&ouc->info_exclude_stat, &untracked->ss_info_exclude.stat);
        stat_data_to_disk(&ouc->excludes_file_stat, &untracked->ss_excludes_file.stat);
 -      hashcpy(ouc->info_exclude_sha1, untracked->ss_info_exclude.sha1);
 -      hashcpy(ouc->excludes_file_sha1, untracked->ss_excludes_file.sha1);
 +      hashcpy(ouc->info_exclude_sha1, untracked->ss_info_exclude.oid.hash);
 +      hashcpy(ouc->excludes_file_sha1, untracked->ss_excludes_file.oid.hash);
        ouc->dir_flags = htonl(untracked->dir_flags);
  
        varint_len = encode_varint(untracked->ident.len, varbuf);
@@@ -2815,12 -2757,13 +2826,12 @@@ static void read_sha1(size_t pos, void 
        rd->data += 20;
  }
  
 -static void load_sha1_stat(struct sha1_stat *sha1_stat,
 -                         const unsigned char *data,
 -                         const unsigned char *sha1)
 +static void load_oid_stat(struct oid_stat *oid_stat, const unsigned char *data,
 +                        const unsigned char *sha1)
  {
 -      stat_data_from_disk(&sha1_stat->stat, data);
 -      hashcpy(sha1_stat->sha1, sha1);
 -      sha1_stat->valid = 1;
 +      stat_data_from_disk(&oid_stat->stat, data);
 +      hashcpy(oid_stat->oid.hash, sha1);
 +      oid_stat->valid = 1;
  }
  
  struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz)
        uc = xcalloc(1, sizeof(*uc));
        strbuf_init(&uc->ident, ident_len);
        strbuf_add(&uc->ident, ident, ident_len);
 -      load_sha1_stat(&uc->ss_info_exclude,
 -                     next + ouc_offset(info_exclude_stat),
 -                     next + ouc_offset(info_exclude_sha1));
 -      load_sha1_stat(&uc->ss_excludes_file,
 -                     next + ouc_offset(excludes_file_stat),
 -                     next + ouc_offset(excludes_file_sha1));
 +      load_oid_stat(&uc->ss_info_exclude,
 +                    next + ouc_offset(info_exclude_stat),
 +                    next + ouc_offset(info_exclude_sha1));
 +      load_oid_stat(&uc->ss_excludes_file,
 +                    next + ouc_offset(excludes_file_stat),
 +                    next + ouc_offset(excludes_file_sha1));
        uc->dir_flags = get_be32(next + ouc_offset(dir_flags));
        exclude_per_dir = (const char *)next + ouc_offset(exclude_per_dir);
        uc->exclude_per_dir = xstrdup(exclude_per_dir);
@@@ -2966,10 -2909,12 +2977,12 @@@ static int invalidate_one_component(str
  }
  
  void untracked_cache_invalidate_path(struct index_state *istate,
-                                    const char *path)
+                                    const char *path, int safe_path)
  {
        if (!istate->untracked || !istate->untracked->root)
                return;
+       if (!safe_path && !verify_path(path))
+               return;
        invalidate_one_component(istate->untracked, istate->untracked->root,
                                 path, strlen(path));
  }
  void untracked_cache_remove_from_index(struct index_state *istate,
                                       const char *path)
  {
-       untracked_cache_invalidate_path(istate, path);
+       untracked_cache_invalidate_path(istate, path, 1);
  }
  
  void untracked_cache_add_to_index(struct index_state *istate,
                                  const char *path)
  {
-       untracked_cache_invalidate_path(istate, path);
+       untracked_cache_invalidate_path(istate, path, 1);
  }
  
  /* Update gitfile and core.worktree setting to connect work tree and git dir */
diff --combined dir.h
index e7bb786a33967360750ad21446ebe65b3bea8b80,efea4b8803f2d87b6b05c0fb965eab2303a28c11..b0758b82a20017dd3ce29c54454678f026718078
--- 1/dir.h
--- 2/dir.h
+++ b/dir.h
@@@ -74,9 -74,9 +74,9 @@@ struct exclude_list_group 
        struct exclude_list *el;
  };
  
 -struct sha1_stat {
 +struct oid_stat {
        struct stat_data stat;
 -      unsigned char sha1[20];
 +      struct object_id oid;
        int valid;
  };
  
@@@ -124,8 -124,8 +124,8 @@@ struct untracked_cache_dir 
  };
  
  struct untracked_cache {
 -      struct sha1_stat ss_info_exclude;
 -      struct sha1_stat ss_excludes_file;
 +      struct oid_stat ss_info_exclude;
 +      struct oid_stat ss_excludes_file;
        const char *exclude_per_dir;
        struct strbuf ident;
        /*
@@@ -195,8 -195,8 +195,8 @@@ struct dir_struct 
  
        /* Enable untracked file cache if set */
        struct untracked_cache *untracked;
 -      struct sha1_stat ss_info_exclude;
 -      struct sha1_stat ss_excludes_file;
 +      struct oid_stat ss_info_exclude;
 +      struct oid_stat ss_excludes_file;
        unsigned unmanaged_exclude_files;
  };
  
@@@ -259,9 -259,6 +259,9 @@@ extern struct exclude_list *add_exclude
  extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
                                          struct exclude_list *el, struct  index_state *istate);
  extern void add_excludes_from_file(struct dir_struct *, const char *fname);
 +extern int add_excludes_from_blob_to_list(struct object_id *oid,
 +                                        const char *base, int baselen,
 +                                        struct exclude_list *el);
  extern void parse_exclude_pattern(const char **string, int *patternlen, unsigned *flags, int *nowildcardlen);
  extern void add_exclude(const char *string, const char *base,
                        int baselen, struct exclude_list *el, int srcpos);
@@@ -350,7 -347,7 +350,7 @@@ static inline int dir_path_match(const 
  int cmp_dir_entry(const void *p1, const void *p2);
  int check_dir_entry_contains(const struct dir_entry *out, const struct dir_entry *in);
  
- void untracked_cache_invalidate_path(struct index_state *, const char *);
+ void untracked_cache_invalidate_path(struct index_state *, const char *, int safe_path);
  void untracked_cache_remove_from_index(struct index_state *, const char *);
  void untracked_cache_add_to_index(struct index_state *, const char *);
  
diff --combined unpack-trees.c
index e6a15bbe44f24555a8edb9461c2af553de70cba0,3a2158bcf03ac349feeaf514031a61792642f9e8..c9f6e314d5cd830d63236ea88c5ff9c7baf418bb
@@@ -15,7 -15,6 +15,7 @@@
  #include "submodule.h"
  #include "submodule-config.h"
  #include "fsmonitor.h"
 +#include "fetch-object.h"
  
  /*
   * Error messages expected by scripts out of plumbing commands such as
@@@ -371,27 -370,6 +371,27 @@@ static int check_updates(struct unpack_
                load_gitmodules_file(index, &state);
  
        enable_delayed_checkout(&state);
 +      if (repository_format_partial_clone && o->update && !o->dry_run) {
 +              /*
 +               * Prefetch the objects that are to be checked out in the loop
 +               * below.
 +               */
 +              struct oid_array to_fetch = OID_ARRAY_INIT;
 +              int fetch_if_missing_store = fetch_if_missing;
 +              fetch_if_missing = 0;
 +              for (i = 0; i < index->cache_nr; i++) {
 +                      struct cache_entry *ce = index->cache[i];
 +                      if ((ce->ce_flags & CE_UPDATE) &&
 +                          !S_ISGITLINK(ce->ce_mode)) {
 +                              if (!has_object_file(&ce->oid))
 +                                      oid_array_append(&to_fetch, &ce->oid);
 +                      }
 +              }
 +              if (to_fetch.nr)
 +                      fetch_objects(repository_format_partial_clone,
 +                                    &to_fetch);
 +              fetch_if_missing = fetch_if_missing_store;
 +      }
        for (i = 0; i < index->cache_nr; i++) {
                struct cache_entry *ce = index->cache[i];
  
@@@ -1528,7 -1506,7 +1528,7 @@@ static void invalidate_ce_path(const st
        if (!ce)
                return;
        cache_tree_invalidate_path(o->src_index, ce->name);
-       untracked_cache_invalidate_path(o->src_index, ce->name);
+       untracked_cache_invalidate_path(o->src_index, ce->name, 1);
  }
  
  /*
@@@ -2161,9 -2139,6 +2161,9 @@@ int oneway_merge(const struct cache_ent
                            ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE))
                                update |= CE_UPDATE;
                }
 +              if (o->update && S_ISGITLINK(old->ce_mode) &&
 +                  should_update_submodules() && !verify_uptodate(old, o))
 +                      update |= CE_UPDATE;
                add_entry(o, old, update, 0);
                return 0;
        }