Merge branch 'lt/core-optim'
authorJunio C Hamano <gitster@pobox.com>
Sun, 11 May 2008 19:08:20 +0000 (12:08 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 11 May 2008 19:08:20 +0000 (12:08 -0700)
* lt/core-optim:
  Optimize symlink/directory detection
  Avoid some unnecessary lstat() calls
  is_racy_timestamp(): do not check timestamp for gitlinks
  diff-lib.c: rename check_work_tree_entity()
  diff: a submodule not checked out is not modified
  Add t7506 to test submodule related functions for git-status
  t4027: test diff for submodule with empty directory
  Make git-add behave more sensibly in a case-insensitive environment
  When adding files to the index, add support for case-independent matches
  Make unpack-tree update removed files before any updated files
  Make branch merging aware of underlying case-insensitive filsystems
  Add 'core.ignorecase' option
  Make hash_name_lookup able to do case-independent lookups
  Make "index_name_exists()" return the cache_entry it found
  Move name hashing functions into a file of its own
  Make unpack_trees_options bit flags actual bitfields

16 files changed:
Makefile
builtin-apply.c
builtin-commit.c
builtin-read-tree.c
cache.h
config.c
diff-lib.c
dir.c
environment.c
name-hash.c [new file with mode: 0644]
read-cache.c
symlinks.c
t/t4027-diff-submodule.sh
t/t7506-status-submodule.sh [new file with mode: 0755]
unpack-trees.c
unpack-trees.h

index 9d84c8d..649ee56 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -423,6 +423,7 @@ LIB_OBJS += log-tree.o
 LIB_OBJS += mailmap.o
 LIB_OBJS += match-trees.o
 LIB_OBJS += merge-file.o
+LIB_OBJS += name-hash.o
 LIB_OBJS += object.o
 LIB_OBJS += pack-check.o
 LIB_OBJS += pack-revindex.o
index caa3f2a..1103625 100644 (file)
@@ -2247,7 +2247,7 @@ static int check_to_create_blob(const char *new_name, int ok_if_exists)
                 * In such a case, path "new_name" does not exist as
                 * far as git is concerned.
                 */
-               if (has_symlink_leading_path(new_name, NULL))
+               if (has_symlink_leading_path(strlen(new_name), new_name))
                        return 0;
 
                return error("%s: already exists in working directory", new_name);
index 256181a..6433f86 100644 (file)
@@ -175,9 +175,11 @@ static void add_remove_files(struct path_list *list)
 {
        int i;
        for (i = 0; i < list->nr; i++) {
+               struct stat st;
                struct path_list_item *p = &(list->items[i]);
-               if (file_exists(p->path))
-                       add_file_to_cache(p->path, 0);
+
+               if (!lstat(p->path, &st))
+                       add_to_cache(p->path, &st, 0);
                else
                        remove_file_from_cache(p->path);
        }
index e9cfd2b..7ac3088 100644 (file)
@@ -40,7 +40,7 @@ static int read_cache_unmerged(void)
        for (i = 0; i < active_nr; i++) {
                struct cache_entry *ce = active_cache[i];
                if (ce_stage(ce)) {
-                       remove_index_entry(ce);
+                       remove_name_hash(ce);
                        if (last && !strcmp(ce->name, last->name))
                                continue;
                        cache_tree_invalidate_path(active_cache_tree, ce->name);
diff --git a/cache.h b/cache.h
index 3442130..9cee9a5 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -133,6 +133,7 @@ struct cache_entry {
 #define CE_UPDATE    (0x10000)
 #define CE_REMOVE    (0x20000)
 #define CE_UPTODATE  (0x40000)
+#define CE_ADDED     (0x80000)
 
 #define CE_HASHED    (0x100000)
 #define CE_UNHASHED  (0x200000)
@@ -153,20 +154,6 @@ static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry
        dst->ce_flags = (dst->ce_flags & ~CE_STATE_MASK) | state;
 }
 
-/*
- * We don't actually *remove* it, we can just mark it invalid so that
- * we won't find it in lookups.
- *
- * Not only would we have to search the lists (simple enough), but
- * we'd also have to rehash other hash buckets in case this makes the
- * hash bucket empty (common). So it's much better to just mark
- * it.
- */
-static inline void remove_index_entry(struct cache_entry *ce)
-{
-       ce->ce_flags |= CE_UNHASHED;
-}
-
 static inline unsigned create_ce_flags(size_t len, unsigned stage)
 {
        if (len >= CE_NAMEMASK)
@@ -241,6 +228,23 @@ struct index_state {
 
 extern struct index_state the_index;
 
+/* Name hashing */
+extern void add_name_hash(struct index_state *istate, struct cache_entry *ce);
+/*
+ * We don't actually *remove* it, we can just mark it invalid so that
+ * we won't find it in lookups.
+ *
+ * Not only would we have to search the lists (simple enough), but
+ * we'd also have to rehash other hash buckets in case this makes the
+ * hash bucket empty (common). So it's much better to just mark
+ * it.
+ */
+static inline void remove_name_hash(struct cache_entry *ce)
+{
+       ce->ce_flags |= CE_UNHASHED;
+}
+
+
 #ifndef NO_THE_INDEX_COMPATIBILITY_MACROS
 #define active_cache (the_index.cache)
 #define active_nr (the_index.cache_nr)
@@ -257,11 +261,12 @@ extern struct index_state the_index;
 #define add_cache_entry(ce, option) add_index_entry(&the_index, (ce), (option))
 #define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos))
 #define remove_file_from_cache(path) remove_file_from_index(&the_index, (path))
+#define add_to_cache(path, st, verbose) add_to_index(&the_index, (path), (st), (verbose))
 #define add_file_to_cache(path, verbose) add_file_to_index(&the_index, (path), (verbose))
 #define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL)
 #define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options))
 #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
-#define cache_name_exists(name, namelen) index_name_exists(&the_index, (name), (namelen))
+#define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase))
 #endif
 
 enum object_type {
@@ -351,7 +356,7 @@ extern int write_index(const struct index_state *, int newfd);
 extern int discard_index(struct index_state *);
 extern int unmerged_index(const struct index_state *);
 extern int verify_path(const char *path);
-extern int index_name_exists(struct index_state *istate, const char *name, int namelen);
+extern struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int igncase);
 extern int index_name_pos(const struct index_state *, const char *name, int namelen);
 #define ADD_CACHE_OK_TO_ADD 1          /* Ok to add */
 #define ADD_CACHE_OK_TO_REPLACE 2      /* Ok to replace file/directory */
@@ -361,6 +366,7 @@ extern int add_index_entry(struct index_state *, struct cache_entry *ce, int opt
 extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
 extern int remove_index_entry_at(struct index_state *, int pos);
 extern int remove_file_from_index(struct index_state *, const char *path);
+extern int add_to_index(struct index_state *, const char *path, struct stat *, int verbose);
 extern int add_file_to_index(struct index_state *, const char *path, int verbose);
 extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
 extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
@@ -405,6 +411,7 @@ extern int delete_ref(const char *, const unsigned char *sha1);
 extern int trust_executable_bit;
 extern int quote_path_fully;
 extern int has_symlinks;
+extern int ignore_case;
 extern int assume_unchanged;
 extern int prefer_symlink_refs;
 extern int log_all_ref_updates;
@@ -599,7 +606,7 @@ struct checkout {
 };
 
 extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
-extern int has_symlink_leading_path(const char *name, char *last_symlink);
+extern int has_symlink_leading_path(int len, const char *name);
 
 extern struct alternate_object_database {
        struct alternate_object_database *next;
index cf2bfd3..4d00ebc 100644 (file)
--- a/config.c
+++ b/config.c
@@ -350,6 +350,11 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.ignorecase")) {
+               ignore_case = git_config_bool(var, value);
+               return 0;
+       }
+
        if (!strcmp(var, "core.bare")) {
                is_bare_repository_cfg = git_config_bool(var, value);
                return 0;
index 9139e45..fe2ccec 100644 (file)
@@ -337,22 +337,41 @@ int run_diff_files_cmd(struct rev_info *revs, int argc, const char **argv)
        }
        return run_diff_files(revs, options);
 }
+
 /*
- * See if work tree has an entity that can be staged.  Return 0 if so,
- * return 1 if not and return -1 if error.
+ * Has the work tree entity been removed?
+ *
+ * Return 1 if it was removed from the work tree, 0 if an entity to be
+ * compared with the cache entry ce still exists (the latter includes
+ * the case where a directory that is not a submodule repository
+ * exists for ce that is a submodule -- it is a submodule that is not
+ * checked out).  Return negative for an error.
  */
-static int check_work_tree_entity(const struct cache_entry *ce, struct stat *st, char *symcache)
+static int check_removed(const struct cache_entry *ce, struct stat *st)
 {
        if (lstat(ce->name, st) < 0) {
                if (errno != ENOENT && errno != ENOTDIR)
                        return -1;
                return 1;
        }
-       if (has_symlink_leading_path(ce->name, symcache))
+       if (has_symlink_leading_path(ce_namelen(ce), ce->name))
                return 1;
        if (S_ISDIR(st->st_mode)) {
                unsigned char sub[20];
-               if (resolve_gitlink_ref(ce->name, "HEAD", sub))
+
+               /*
+                * If ce is already a gitlink, we can have a plain
+                * directory (i.e. the submodule is not checked out),
+                * or a checked out submodule.  Either case this is not
+                * a case where something was removed from the work tree,
+                * so we will return 0.
+                *
+                * Otherwise, if the directory is not a submodule
+                * repository, that means ce which was a blob turned into
+                * a directory --- the blob was removed!
+                */
+               if (!S_ISGITLINK(ce->ce_mode) &&
+                   resolve_gitlink_ref(ce->name, "HEAD", sub))
                        return 1;
        }
        return 0;
@@ -402,7 +421,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                        memset(&(dpath->parent[0]), 0,
                               sizeof(struct combine_diff_parent)*5);
 
-                       changed = check_work_tree_entity(ce, &st, symcache);
+                       changed = check_removed(ce, &st);
                        if (!changed)
                                dpath->mode = ce_mode_from_stat(ce, st.st_mode);
                        else {
@@ -466,7 +485,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                if (ce_uptodate(ce))
                        continue;
 
-               changed = check_work_tree_entity(ce, &st, symcache);
+               changed = check_removed(ce, &st);
                if (changed) {
                        if (changed < 0) {
                                perror(ce->name);
@@ -527,7 +546,7 @@ static int get_stat_data(struct cache_entry *ce,
        if (!cached) {
                int changed;
                struct stat st;
-               changed = check_work_tree_entity(ce, &st, cbdata->symcache);
+               changed = check_removed(ce, &st);
                if (changed < 0)
                        return -1;
                else if (changed) {
diff --git a/dir.c b/dir.c
index 9501476..29d1d5b 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -389,7 +389,7 @@ static struct dir_entry *dir_entry_new(const char *pathname, int len)
 
 struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
 {
-       if (cache_name_exists(pathname, len))
+       if (cache_name_exists(pathname, len, ignore_case))
                return NULL;
 
        ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc);
index 4892a30..55c2527 100644 (file)
@@ -14,6 +14,7 @@ char git_default_name[MAX_GITNAME];
 int trust_executable_bit = 1;
 int quote_path_fully = 1;
 int has_symlinks = 1;
+int ignore_case;
 int assume_unchanged;
 int prefer_symlink_refs;
 int is_bare_repository_cfg = -1; /* unspecified */
diff --git a/name-hash.c b/name-hash.c
new file mode 100644 (file)
index 0000000..0031d78
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * name-hash.c
+ *
+ * Hashing names in the index state
+ *
+ * Copyright (C) 2008 Linus Torvalds
+ */
+#define NO_THE_INDEX_COMPATIBILITY_MACROS
+#include "cache.h"
+
+/*
+ * This removes bit 5 if bit 6 is set.
+ *
+ * That will make US-ASCII characters hash to their upper-case
+ * equivalent. We could easily do this one whole word at a time,
+ * but that's for future worries.
+ */
+static inline unsigned char icase_hash(unsigned char c)
+{
+       return c & ~((c & 0x40) >> 1);
+}
+
+static unsigned int hash_name(const char *name, int namelen)
+{
+       unsigned int hash = 0x123;
+
+       do {
+               unsigned char c = *name++;
+               c = icase_hash(c);
+               hash = hash*101 + c;
+       } while (--namelen);
+       return hash;
+}
+
+static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
+{
+       void **pos;
+       unsigned int hash;
+
+       if (ce->ce_flags & CE_HASHED)
+               return;
+       ce->ce_flags |= CE_HASHED;
+       ce->next = NULL;
+       hash = hash_name(ce->name, ce_namelen(ce));
+       pos = insert_hash(hash, ce, &istate->name_hash);
+       if (pos) {
+               ce->next = *pos;
+               *pos = ce;
+       }
+}
+
+static void lazy_init_name_hash(struct index_state *istate)
+{
+       int nr;
+
+       if (istate->name_hash_initialized)
+               return;
+       for (nr = 0; nr < istate->cache_nr; nr++)
+               hash_index_entry(istate, istate->cache[nr]);
+       istate->name_hash_initialized = 1;
+}
+
+void add_name_hash(struct index_state *istate, struct cache_entry *ce)
+{
+       ce->ce_flags &= ~CE_UNHASHED;
+       if (istate->name_hash_initialized)
+               hash_index_entry(istate, ce);
+}
+
+static int slow_same_name(const char *name1, int len1, const char *name2, int len2)
+{
+       if (len1 != len2)
+               return 0;
+
+       while (len1) {
+               unsigned char c1 = *name1++;
+               unsigned char c2 = *name2++;
+               len1--;
+               if (c1 != c2) {
+                       c1 = toupper(c1);
+                       c2 = toupper(c2);
+                       if (c1 != c2)
+                               return 0;
+               }
+       }
+       return 1;
+}
+
+static int same_name(const struct cache_entry *ce, const char *name, int namelen, int icase)
+{
+       int len = ce_namelen(ce);
+
+       /*
+        * Always do exact compare, even if we want a case-ignoring comparison;
+        * we do the quick exact one first, because it will be the common case.
+        */
+       if (len == namelen && !cache_name_compare(name, namelen, ce->name, len))
+               return 1;
+
+       return icase && slow_same_name(name, namelen, ce->name, len);
+}
+
+struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int icase)
+{
+       unsigned int hash = hash_name(name, namelen);
+       struct cache_entry *ce;
+
+       lazy_init_name_hash(istate);
+       ce = lookup_hash(hash, &istate->name_hash);
+
+       while (ce) {
+               if (!(ce->ce_flags & CE_UNHASHED)) {
+                       if (same_name(ce, name, namelen, icase))
+                               return ce;
+               }
+               ce = ce->next;
+       }
+       return NULL;
+}
index c3692f4..0382804 100644 (file)
 
 struct index_state the_index;
 
-static unsigned int hash_name(const char *name, int namelen)
-{
-       unsigned int hash = 0x123;
-
-       do {
-               unsigned char c = *name++;
-               hash = hash*101 + c;
-       } while (--namelen);
-       return hash;
-}
-
-static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
-{
-       void **pos;
-       unsigned int hash;
-
-       if (ce->ce_flags & CE_HASHED)
-               return;
-       ce->ce_flags |= CE_HASHED;
-       ce->next = NULL;
-       hash = hash_name(ce->name, ce_namelen(ce));
-       pos = insert_hash(hash, ce, &istate->name_hash);
-       if (pos) {
-               ce->next = *pos;
-               *pos = ce;
-       }
-}
-
-static void lazy_init_name_hash(struct index_state *istate)
-{
-       int nr;
-
-       if (istate->name_hash_initialized)
-               return;
-       for (nr = 0; nr < istate->cache_nr; nr++)
-               hash_index_entry(istate, istate->cache[nr]);
-       istate->name_hash_initialized = 1;
-}
-
 static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
 {
-       ce->ce_flags &= ~CE_UNHASHED;
        istate->cache[nr] = ce;
-       if (istate->name_hash_initialized)
-               hash_index_entry(istate, ce);
+       add_name_hash(istate, ce);
 }
 
 static void replace_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
 {
        struct cache_entry *old = istate->cache[nr];
 
-       remove_index_entry(old);
+       remove_name_hash(old);
        set_index_entry(istate, nr, ce);
        istate->cache_changed = 1;
 }
 
-int index_name_exists(struct index_state *istate, const char *name, int namelen)
-{
-       unsigned int hash = hash_name(name, namelen);
-       struct cache_entry *ce;
-
-       lazy_init_name_hash(istate);
-       ce = lookup_hash(hash, &istate->name_hash);
-
-       while (ce) {
-               if (!(ce->ce_flags & CE_UNHASHED)) {
-                       if (!cache_name_compare(name, namelen, ce->name, ce->ce_flags))
-                               return 1;
-               }
-               ce = ce->next;
-       }
-       return 0;
-}
-
 /*
  * This only updates the "non-critical" parts of the directory
  * cache, ie the parts that aren't tracked by GIT, and only used
@@ -257,7 +198,8 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
 
 static int is_racy_timestamp(const struct index_state *istate, struct cache_entry *ce)
 {
-       return (istate->timestamp &&
+       return (!S_ISGITLINK(ce->ce_mode) &&
+               istate->timestamp &&
                ((unsigned int)istate->timestamp) <= ce->ce_mtime);
 }
 
@@ -438,7 +380,7 @@ int remove_index_entry_at(struct index_state *istate, int pos)
 {
        struct cache_entry *ce = istate->cache[pos];
 
-       remove_index_entry(ce);
+       remove_name_hash(ce);
        istate->cache_changed = 1;
        istate->cache_nr--;
        if (pos >= istate->cache_nr)
@@ -488,21 +430,50 @@ static int index_name_pos_also_unmerged(struct index_state *istate,
        return pos;
 }
 
-int add_file_to_index(struct index_state *istate, const char *path, int verbose)
+static int different_name(struct cache_entry *ce, struct cache_entry *alias)
 {
-       int size, namelen, pos;
-       struct stat st;
-       struct cache_entry *ce;
-       unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_RACY_IS_DIRTY;
+       int len = ce_namelen(ce);
+       return ce_namelen(alias) != len || memcmp(ce->name, alias->name, len);
+}
 
-       if (lstat(path, &st))
-               die("%s: unable to stat (%s)", path, strerror(errno));
+/*
+ * If we add a filename that aliases in the cache, we will use the
+ * name that we already have - but we don't want to update the same
+ * alias twice, because that implies that there were actually two
+ * different files with aliasing names!
+ *
+ * So we use the CE_ADDED flag to verify that the alias was an old
+ * one before we accept it as
+ */
+static struct cache_entry *create_alias_ce(struct cache_entry *ce, struct cache_entry *alias)
+{
+       int len;
+       struct cache_entry *new;
+
+       if (alias->ce_flags & CE_ADDED)
+               die("Will not add file alias '%s' ('%s' already exists in index)", ce->name, alias->name);
+
+       /* Ok, create the new entry using the name of the existing alias */
+       len = ce_namelen(alias);
+       new = xcalloc(1, cache_entry_size(len));
+       memcpy(new->name, alias->name, len);
+       copy_cache_entry(new, ce);
+       free(ce);
+       return new;
+}
+
+int add_to_index(struct index_state *istate, const char *path, struct stat *st, int verbose)
+{
+       int size, namelen;
+       mode_t st_mode = st->st_mode;
+       struct cache_entry *ce, *alias;
+       unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_RACY_IS_DIRTY;
 
-       if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode) && !S_ISDIR(st.st_mode))
+       if (!S_ISREG(st_mode) && !S_ISLNK(st_mode) && !S_ISDIR(st_mode))
                die("%s: can only add regular files, symbolic links or git-directories", path);
 
        namelen = strlen(path);
-       if (S_ISDIR(st.st_mode)) {
+       if (S_ISDIR(st_mode)) {
                while (namelen && path[namelen-1] == '/')
                        namelen--;
        }
@@ -510,10 +481,10 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose)
        ce = xcalloc(1, size);
        memcpy(ce->name, path, namelen);
        ce->ce_flags = namelen;
-       fill_stat_cache_info(ce, &st);
+       fill_stat_cache_info(ce, st);
 
        if (trust_executable_bit && has_symlinks)
-               ce->ce_mode = create_ce_mode(st.st_mode);
+               ce->ce_mode = create_ce_mode(st_mode);
        else {
                /* If there is an existing entry, pick the mode bits and type
                 * from it, otherwise assume unexecutable regular file.
@@ -522,21 +493,22 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose)
                int pos = index_name_pos_also_unmerged(istate, path, namelen);
 
                ent = (0 <= pos) ? istate->cache[pos] : NULL;
-               ce->ce_mode = ce_mode_from_stat(ent, st.st_mode);
+               ce->ce_mode = ce_mode_from_stat(ent, st_mode);
        }
 
-       pos = index_name_pos(istate, ce->name, namelen);
-       if (0 <= pos &&
-           !ce_stage(istate->cache[pos]) &&
-           !ie_match_stat(istate, istate->cache[pos], &st, ce_option)) {
+       alias = index_name_exists(istate, ce->name, ce_namelen(ce), ignore_case);
+       if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, ce_option)) {
                /* Nothing changed, really */
                free(ce);
-               ce_mark_uptodate(istate->cache[pos]);
+               ce_mark_uptodate(alias);
+               alias->ce_flags |= CE_ADDED;
                return 0;
        }
-
-       if (index_path(ce->sha1, path, &st, 1))
+       if (index_path(ce->sha1, path, st, 1))
                die("unable to index file %s", path);
+       if (ignore_case && alias && different_name(ce, alias))
+               ce = create_alias_ce(ce, alias);
+       ce->ce_flags |= CE_ADDED;
        if (add_index_entry(istate, ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE))
                die("unable to add %s to index",path);
        if (verbose)
@@ -544,6 +516,14 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose)
        return 0;
 }
 
+int add_file_to_index(struct index_state *istate, const char *path, int verbose)
+{
+       struct stat st;
+       if (lstat(path, &st))
+               die("%s: unable to stat (%s)", path, strerror(errno));
+       return add_to_index(istate, path, &st, verbose);
+}
+
 struct cache_entry *make_cache_entry(unsigned int mode,
                const unsigned char *sha1, const char *path, int stage,
                int refresh)
index be9ace6..5a5e781 100644 (file)
@@ -1,48 +1,64 @@
 #include "cache.h"
 
-int has_symlink_leading_path(const char *name, char *last_symlink)
-{
+struct pathname {
+       int len;
        char path[PATH_MAX];
-       const char *sp, *ep;
-       char *dp;
-
-       sp = name;
-       dp = path;
-
-       if (last_symlink && *last_symlink) {
-               size_t last_len = strlen(last_symlink);
-               size_t len = strlen(name);
-               if (last_len < len &&
-                   !strncmp(name, last_symlink, last_len) &&
-                   name[last_len] == '/')
-                       return 1;
-               *last_symlink = '\0';
+};
+
+/* Return matching pathname prefix length, or zero if not matching */
+static inline int match_pathname(int len, const char *name, struct pathname *match)
+{
+       int match_len = match->len;
+       return (len > match_len &&
+               name[match_len] == '/' &&
+               !memcmp(name, match->path, match_len)) ? match_len : 0;
+}
+
+static inline void set_pathname(int len, const char *name, struct pathname *match)
+{
+       if (len < PATH_MAX) {
+               match->len = len;
+               memcpy(match->path, name, len);
+               match->path[len] = 0;
        }
+}
+
+int has_symlink_leading_path(int len, const char *name)
+{
+       static struct pathname link, nonlink;
+       char path[PATH_MAX];
+       struct stat st;
+       char *sp;
+       int known_dir;
 
-       while (1) {
-               size_t len;
-               struct stat st;
+       /*
+        * See if the last known symlink cache matches.
+        */
+       if (match_pathname(len, name, &link))
+               return 1;
 
-               ep = strchr(sp, '/');
-               if (!ep)
-                       break;
-               len = ep - sp;
-               if (PATH_MAX <= dp + len - path + 2)
-                       return 0; /* new name is longer than that??? */
-               memcpy(dp, sp, len);
-               dp[len] = 0;
+       /*
+        * Get rid of the last known directory part
+        */
+       known_dir = match_pathname(len, name, &nonlink);
+
+       while ((sp = strchr(name + known_dir + 1, '/')) != NULL) {
+               int thislen = sp - name ;
+               memcpy(path, name, thislen);
+               path[thislen] = 0;
 
                if (lstat(path, &st))
                        return 0;
+               if (S_ISDIR(st.st_mode)) {
+                       set_pathname(thislen, path, &nonlink);
+                       known_dir = thislen;
+                       continue;
+               }
                if (S_ISLNK(st.st_mode)) {
-                       if (last_symlink)
-                               strcpy(last_symlink, path);
+                       set_pathname(thislen, path, &link);
                        return 1;
                }
-
-               dp[len++] = '/';
-               dp = dp + len;
-               sp = ep + 1;
+               break;
        }
        return 0;
 }
index 1fd3fb7..ba6679c 100755 (executable)
@@ -50,4 +50,11 @@ test_expect_success 'git diff-files --raw' '
        test_cmp expect actual.files
 '
 
+test_expect_success 'git diff (empty submodule dir)' '
+       : >empty &&
+       rm -rf sub/* sub/.git &&
+       git diff > actual.empty &&
+       test_cmp empty actual.empty
+'
+
 test_done
diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh
new file mode 100755 (executable)
index 0000000..a75130c
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+test_description='git-status for submodule'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_create_repo sub
+       cd sub &&
+       : >bar &&
+       git add bar &&
+       git commit -m " Add bar" &&
+       cd .. &&
+       git add sub &&
+       git commit -m "Add submodule sub"
+'
+
+test_expect_success 'status clean' '
+       git status |
+       grep "nothing to commit"
+'
+test_expect_success 'status -a clean' '
+       git status -a |
+       grep "nothing to commit"
+'
+test_expect_success 'rm submodule contents' '
+       rm -rf sub/* sub/.git
+'
+test_expect_success 'status clean (empty submodule dir)' '
+       git status |
+       grep "nothing to commit"
+'
+test_expect_success 'status -a clean (empty submodule dir)' '
+       git status -a |
+       grep "nothing to commit"
+'
+
+test_done
index a59f475..1ab28fd 100644 (file)
@@ -26,11 +26,12 @@ static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
  * directories, in case this unlink is the removal of the
  * last entry in the directory -- empty directories are removed.
  */
-static void unlink_entry(char *name, char *last_symlink)
+static void unlink_entry(struct cache_entry *ce)
 {
        char *cp, *prev;
+       char *name = ce->name;
 
-       if (has_symlink_leading_path(name, last_symlink))
+       if (has_symlink_leading_path(ce_namelen(ce), ce->name))
                return;
        if (unlink(name))
                return;
@@ -58,7 +59,6 @@ static int check_updates(struct unpack_trees_options *o)
 {
        unsigned cnt = 0, total = 0;
        struct progress *progress = NULL;
-       char last_symlink[PATH_MAX];
        struct index_state *index = &o->result;
        int i;
        int errs = 0;
@@ -75,24 +75,27 @@ static int check_updates(struct unpack_trees_options *o)
                cnt = 0;
        }
 
-       *last_symlink = '\0';
        for (i = 0; i < index->cache_nr; i++) {
                struct cache_entry *ce = index->cache[i];
 
-               if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
-                       display_progress(progress, ++cnt);
                if (ce->ce_flags & CE_REMOVE) {
+                       display_progress(progress, ++cnt);
                        if (o->update)
-                               unlink_entry(ce->name, last_symlink);
+                               unlink_entry(ce);
                        remove_index_entry_at(&o->result, i);
                        i--;
                        continue;
                }
+       }
+
+       for (i = 0; i < index->cache_nr; i++) {
+               struct cache_entry *ce = index->cache[i];
+
                if (ce->ce_flags & CE_UPDATE) {
+                       display_progress(progress, ++cnt);
                        ce->ce_flags &= ~CE_UPDATE;
                        if (o->update) {
                                errs |= checkout_entry(ce, &state, NULL);
-                               *last_symlink = '\0';
                        }
                }
        }
@@ -520,6 +523,22 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
        return cnt;
 }
 
+/*
+ * This gets called when there was no index entry for the tree entry 'dst',
+ * but we found a file in the working tree that 'lstat()' said was fine,
+ * and we're on a case-insensitive filesystem.
+ *
+ * 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)
+{
+       struct cache_entry *src;
+
+       src = index_name_exists(o->src_index, dst->name, ce_namelen(dst), 1);
+       return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID);
+}
+
 /*
  * We do not want to remove or overwrite a working tree file that
  * is not tracked, unless it is ignored.
@@ -532,12 +551,23 @@ static int verify_absent(struct cache_entry *ce, const char *action,
        if (o->index_only || o->reset || !o->update)
                return 0;
 
-       if (has_symlink_leading_path(ce->name, NULL))
+       if (has_symlink_leading_path(ce_namelen(ce), ce->name))
                return 0;
 
        if (!lstat(ce->name, &st)) {
                int cnt;
                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))
                        /*
@@ -581,10 +611,9 @@ static int verify_absent(struct cache_entry *ce, const char *action,
                 * delete this path, which is in a subdirectory that
                 * is being replaced with a blob.
                 */
-               cnt = index_name_pos(&o->result, ce->name, strlen(ce->name));
-               if (0 <= cnt) {
-                       struct cache_entry *ce = o->result.cache[cnt];
-                       if (ce->ce_flags & CE_REMOVE)
+               result = index_name_exists(&o->result, ce->name, ce_namelen(ce), 0);
+               if (result) {
+                       if (result->ce_flags & CE_REMOVE)
                                return 0;
                }
 
index 50453ed..d436d6c 100644 (file)
@@ -9,16 +9,16 @@ typedef int (*merge_fn_t)(struct cache_entry **src,
                struct unpack_trees_options *options);
 
 struct unpack_trees_options {
-       int reset;
-       int merge;
-       int update;
-       int index_only;
-       int nontrivial_merge;
-       int trivial_merges_only;
-       int verbose_update;
-       int aggressive;
-       int skip_unmerged;
-       int gently;
+       unsigned int reset:1,
+                    merge:1,
+                    update:1,
+                    index_only:1,
+                    nontrivial_merge:1,
+                    trivial_merges_only:1,
+                    verbose_update:1,
+                    aggressive:1,
+                    skip_unmerged:1,
+                    gently:1;
        const char *prefix;
        int pos;
        struct dir_struct *dir;
@@ -31,7 +31,7 @@ struct unpack_trees_options {
        void *unpack_data;
 
        struct index_state *dst_index;
-       const struct index_state *src_index;
+       struct index_state *src_index;
        struct index_state result;
 };