Merge branch 'lt/case-insensitive'
authorJunio C Hamano <gitster@pobox.com>
Sun, 11 May 2008 01:14:28 +0000 (18:14 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 11 May 2008 01:14:28 +0000 (18:14 -0700)
* lt/case-insensitive:
  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

1  2 
Makefile
cache.h
config.c
dir.c
environment.c
read-cache.c

diff --combined Makefile
+++ b/Makefile
@@@ -366,7 -366,6 +366,7 @@@ LIB_H += refs.
  LIB_H += remote.h
  LIB_H += revision.h
  LIB_H += run-command.h
 +LIB_H += sha1-lookup.h
  LIB_H += sideband.h
  LIB_H += strbuf.h
  LIB_H += tag.h
@@@ -423,6 -422,7 +423,7 @@@ LIB_OBJS += log-tree.
  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
@@@ -447,7 -447,6 +448,7 @@@ LIB_OBJS += run-command.
  LIB_OBJS += server-info.o
  LIB_OBJS += setup.o
  LIB_OBJS += sha1_file.o
 +LIB_OBJS += sha1-lookup.o
  LIB_OBJS += sha1_name.o
  LIB_OBJS += shallow.o
  LIB_OBJS += sideband.o
diff --combined cache.h
+++ b/cache.h
@@@ -133,6 -133,7 +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 +154,6 @@@ static inline void copy_cache_entry(str
        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 +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)
  #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 {
@@@ -311,7 -315,6 +315,7 @@@ extern char *get_index_file(void)
  extern char *get_graft_file(void);
  extern int set_git_dir(const char *path);
  extern const char *get_git_work_tree(void);
 +extern const char *read_gitfile_gently(const char *path);
  
  #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
  
@@@ -351,7 -354,7 +355,7 @@@ extern int write_index(const struct ind
  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 */
@@@ -405,6 -408,7 +409,7 @@@ extern int delete_ref(const char *, con
  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;
@@@ -475,20 -479,10 +480,20 @@@ static inline void hashclr(unsigned cha
  
  int git_mkstemp(char *path, size_t n, const char *template);
  
 +/*
 + * NOTE NOTE NOTE!!
 + *
 + * PERM_UMASK, OLD_PERM_GROUP and OLD_PERM_EVERYBODY enumerations must
 + * not be changed. Old repositories have core.sharedrepository written in
 + * numeric format, and therefore these values are preserved for compatibility
 + * reasons.
 + */
  enum sharedrepo {
 -      PERM_UMASK = 0,
 -      PERM_GROUP,
 -      PERM_EVERYBODY
 +      PERM_UMASK          = 0,
 +      OLD_PERM_GROUP      = 1,
 +      OLD_PERM_EVERYBODY  = 2,
 +      PERM_GROUP          = 0660,
 +      PERM_EVERYBODY      = 0664,
  };
  int git_config_perm(const char *var, const char *value);
  int adjust_shared_perm(const char *path);
@@@ -635,7 -629,6 +640,7 @@@ struct ref 
        struct ref *next;
        unsigned char old_sha1[20];
        unsigned char new_sha1[20];
 +      char *symref;
        unsigned int force:1,
                merge:1,
                nonfastforward:1,
@@@ -704,7 -697,6 +709,7 @@@ extern int git_parse_long(const char *
  extern int git_parse_ulong(const char *, unsigned long *);
  extern int git_config_int(const char *, const char *);
  extern unsigned long git_config_ulong(const char *, const char *);
 +extern int git_config_bool_or_int(const char *, const char *, int *);
  extern int git_config_bool(const char *, const char *);
  extern int git_config_string(const char **, const char *, const char *);
  extern int git_config_set(const char *, const char *);
@@@ -728,8 -720,8 +733,8 @@@ extern const char *git_log_output_encod
  extern void maybe_flush_or_die(FILE *, const char *);
  extern int copy_fd(int ifd, int ofd);
  extern int copy_file(const char *dst, const char *src, int mode);
 -extern int read_in_full(int fd, void *buf, size_t count);
 -extern int write_in_full(int fd, const void *buf, size_t count);
 +extern ssize_t read_in_full(int fd, void *buf, size_t count);
 +extern ssize_t write_in_full(int fd, const void *buf, size_t count);
  extern void write_or_die(int fd, const void *buf, size_t count);
  extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg);
  extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg);
diff --combined config.c
+++ b/config.c
@@@ -303,9 -303,8 +303,9 @@@ unsigned long git_config_ulong(const ch
        return ret;
  }
  
 -int git_config_bool(const char *name, const char *value)
 +int git_config_bool_or_int(const char *name, const char *value, int *is_bool)
  {
 +      *is_bool = 1;
        if (!value)
                return 1;
        if (!*value)
                return 1;
        if (!strcasecmp(value, "false") || !strcasecmp(value, "no"))
                return 0;
 -      return git_config_int(name, value) != 0;
 +      *is_bool = 0;
 +      return git_config_int(name, value);
 +}
 +
 +int git_config_bool(const char *name, const char *value)
 +{
 +      int discard;
 +      return !!git_config_bool_or_int(name, value, &discard);
  }
  
  int git_config_string(const char **dest, const char *var, const char *value)
@@@ -350,6 -342,11 +350,11 @@@ int git_default_config(const char *var
                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;
diff --combined dir.c
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -52,11 -52,6 +52,11 @@@ int common_prefix(const char **pathspec
        return prefix;
  }
  
 +static inline int special_char(unsigned char c1)
 +{
 +      return !c1 || c1 == '*' || c1 == '[' || c1 == '?';
 +}
 +
  /*
   * Does 'match' matches the given name?
   * A match is found if
@@@ -74,31 -69,18 +74,31 @@@ static int match_one(const char *match
        int matchlen;
  
        /* If the match was just the prefix, we matched */
 -      matchlen = strlen(match);
 -      if (!matchlen)
 +      if (!*match)
                return MATCHED_RECURSIVELY;
  
 +      for (;;) {
 +              unsigned char c1 = *match;
 +              unsigned char c2 = *name;
 +              if (special_char(c1))
 +                      break;
 +              if (c1 != c2)
 +                      return 0;
 +              match++;
 +              name++;
 +              namelen--;
 +      }
 +
 +
        /*
         * If we don't match the matchstring exactly,
         * we need to match by fnmatch
         */
 +      matchlen = strlen(match);
        if (strncmp(match, name, matchlen))
                return !fnmatch(match, name, 0) ? MATCHED_FNMATCH : 0;
  
 -      if (!name[matchlen])
 +      if (namelen == matchlen)
                return MATCHED_EXACTLY;
        if (match[matchlen-1] == '/' || name[matchlen] == '/')
                return MATCHED_RECURSIVELY;
@@@ -389,7 -371,7 +389,7 @@@ static struct dir_entry *dir_entry_new(
  
  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);
diff --combined environment.c
@@@ -14,6 -14,7 +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 */
@@@ -49,8 -50,6 +50,8 @@@ static char *git_object_dir, *git_index
  static void setup_git_env(void)
  {
        git_dir = getenv(GIT_DIR_ENVIRONMENT);
 +      if (!git_dir)
 +              git_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
        if (!git_dir)
                git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
        git_object_dir = getenv(DB_ENVIRONMENT);
diff --combined read-cache.c
  
  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
@@@ -438,7 -379,7 +379,7 @@@ int remove_index_entry_at(struct index_
  {
        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,11 -429,43 +429,43 @@@ static int index_name_pos_also_unmerged
        return pos;
  }
  
+ static int different_name(struct cache_entry *ce, struct cache_entry *alias)
+ {
+       int len = ce_namelen(ce);
+       return ce_namelen(alias) != len || memcmp(ce->name, alias->name, len);
+ }
+ /*
+  * 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_file_to_index(struct index_state *istate, const char *path, int verbose)
  {
-       int size, namelen, pos;
+       int size, namelen;
        struct stat st;
-       struct cache_entry *ce;
+       struct cache_entry *ce, *alias;
        unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_RACY_IS_DIRTY;
  
        if (lstat(path, &st))
                ce->ce_mode = ce_mode_from_stat(ent, st.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))
                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)
@@@ -1370,7 -1344,7 +1344,7 @@@ int write_index(const struct index_stat
                struct cache_entry *ce = cache[i];
                if (ce->ce_flags & CE_REMOVE)
                        continue;
 -              if (is_racy_timestamp(istate, ce))
 +              if (!ce_uptodate(ce) && is_racy_timestamp(istate, ce))
                        ce_smudge_racily_clean_entry(ce);
                if (ce_write_entry(&c, newfd, ce) < 0)
                        return -1;