From 75dc6c7cb879d0018893baf6ba6b49d1f16e1b92 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Fri, 21 Mar 2008 13:14:47 -0700 Subject: [PATCH 1/9] Make unpack_trees_options bit flags actual bitfields Instead of wasting space with whole integers for a single bit. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- unpack-trees.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/unpack-trees.h b/unpack-trees.h index 50453ed20f..ad8cc65d68 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -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; From 96872bc200c41407607019c1f0fb005840f576a2 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Fri, 21 Mar 2008 13:16:24 -0700 Subject: [PATCH 2/9] Move name hashing functions into a file of its own It's really totally separate functionality, and if we want to start doing case-insensitive hash lookups, I'd rather do it when it's separated out. It also renames "remove_index_entry()" to "remove_name_hash()", because that really describes the thing better. It doesn't actually remove the index entry, that's done by "remove_index_entry_at()", which is something very different, despite the similarity in names. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- Makefile | 1 + builtin-read-tree.c | 2 +- cache.h | 31 ++++++++++--------- name-hash.c | 73 +++++++++++++++++++++++++++++++++++++++++++++ read-cache.c | 65 ++-------------------------------------- 5 files changed, 95 insertions(+), 77 deletions(-) create mode 100644 name-hash.c diff --git a/Makefile b/Makefile index 78b7738621..390b37b941 100644 --- a/Makefile +++ b/Makefile @@ -422,6 +422,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 diff --git a/builtin-read-tree.c b/builtin-read-tree.c index e9cfd2bbc5..7ac30883bc 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -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 2a1e7ec6b2..2afc788f74 100644 --- a/cache.h +++ b/cache.h @@ -153,20 +153,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 +227,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) diff --git a/name-hash.c b/name-hash.c new file mode 100644 index 0000000000..e56eb16c28 --- /dev/null +++ b/name-hash.c @@ -0,0 +1,73 @@ +/* + * name-hash.c + * + * Hashing names in the index state + * + * Copyright (C) 2008 Linus Torvalds + */ +#define NO_THE_INDEX_COMPATIBILITY_MACROS +#include "cache.h" + +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; +} + +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); +} + +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; +} diff --git a/read-cache.c b/read-cache.c index a92b25b59b..5dc998d21e 100644 --- a/read-cache.c +++ b/read-cache.c @@ -23,80 +23,21 @@ 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 @@ 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) From df292c791ab790340cc9e3577a073bcb9d1900ea Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Fri, 21 Mar 2008 15:53:00 -0700 Subject: [PATCH 3/9] Make "index_name_exists()" return the cache_entry it found This allows verify_absent() in unpack_trees() to use the hash chains rather than looking it up using the binary search. Perhaps more importantly, it's also going to be useful for the next phase, where we actually start looking at the cache entry when we do case-insensitive lookups and checking the result. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- cache.h | 2 +- name-hash.c | 6 +++--- unpack-trees.c | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cache.h b/cache.h index 2afc788f74..76d95d2c81 100644 --- a/cache.h +++ b/cache.h @@ -353,7 +353,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); 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 */ diff --git a/name-hash.c b/name-hash.c index e56eb16c28..2678148937 100644 --- a/name-hash.c +++ b/name-hash.c @@ -54,7 +54,7 @@ void add_name_hash(struct index_state *istate, struct cache_entry *ce) hash_index_entry(istate, ce); } -int index_name_exists(struct index_state *istate, const char *name, int namelen) +struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen) { unsigned int hash = hash_name(name, namelen); struct cache_entry *ce; @@ -65,9 +65,9 @@ int index_name_exists(struct index_state *istate, const char *name, int namelen) while (ce) { if (!(ce->ce_flags & CE_UNHASHED)) { if (!cache_name_compare(name, namelen, ce->name, ce->ce_flags)) - return 1; + return ce; } ce = ce->next; } - return 0; + return NULL; } diff --git a/unpack-trees.c b/unpack-trees.c index a59f47557a..ca4c845beb 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -538,6 +538,7 @@ static int verify_absent(struct cache_entry *ce, const char *action, if (!lstat(ce->name, &st)) { int cnt; int dtype = ce_to_dtype(ce); + struct cache_entry *result; if (o->dir && excluded(o->dir, ce->name, &dtype)) /* @@ -581,10 +582,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)); + if (result) { + if (result->ce_flags & CE_REMOVE) return 0; } From cd2fef59edf72a1d9792d9cb72aae1e6f6c7b1d4 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Fri, 21 Mar 2008 15:55:19 -0700 Subject: [PATCH 4/9] Make hash_name_lookup able to do case-independent lookups Right now nobody uses it, but "index_name_exists()" gets a flag so you can enable it on a case-by-case basis. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- cache.h | 4 ++-- dir.c | 2 +- name-hash.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- unpack-trees.c | 2 +- 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/cache.h b/cache.h index 76d95d2c81..a9ddaa1240 100644 --- a/cache.h +++ b/cache.h @@ -264,7 +264,7 @@ static inline void remove_name_hash(struct cache_entry *ce) #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 { @@ -353,7 +353,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 struct cache_entry *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 */ diff --git a/dir.c b/dir.c index edc458e020..7362e83282 100644 --- a/dir.c +++ b/dir.c @@ -371,7 +371,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, 0)) return NULL; ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc); diff --git a/name-hash.c b/name-hash.c index 2678148937..0031d78e8c 100644 --- a/name-hash.c +++ b/name-hash.c @@ -8,12 +8,25 @@ #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; @@ -54,7 +67,40 @@ void add_name_hash(struct index_state *istate, struct cache_entry *ce) hash_index_entry(istate, ce); } -struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen) +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; @@ -64,7 +110,7 @@ struct cache_entry *index_name_exists(struct index_state *istate, const char *na while (ce) { if (!(ce->ce_flags & CE_UNHASHED)) { - if (!cache_name_compare(name, namelen, ce->name, ce->ce_flags)) + if (same_name(ce, name, namelen, icase)) return ce; } ce = ce->next; diff --git a/unpack-trees.c b/unpack-trees.c index ca4c845beb..bf7d8f6c5c 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -582,7 +582,7 @@ 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. */ - result = index_name_exists(&o->result, ce->name, ce_namelen(ce)); + result = index_name_exists(&o->result, ce->name, ce_namelen(ce), 0); if (result) { if (result->ce_flags & CE_REMOVE) return 0; From 0a9b88b7dee70bd36d35b7857640a18ee3adeef1 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Fri, 21 Mar 2008 16:52:46 -0700 Subject: [PATCH 5/9] Add 'core.ignorecase' option ..and start using it for directory entry traversal (ie "git status" will not consider entries that match an existing entry case-insensitively to be a new file) Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- cache.h | 1 + config.c | 5 +++++ dir.c | 2 +- environment.c | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/cache.h b/cache.h index a9ddaa1240..9bce723bb8 100644 --- a/cache.h +++ b/cache.h @@ -407,6 +407,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; diff --git a/config.c b/config.c index 062449459e..3d51868f2b 100644 --- a/config.c +++ b/config.c @@ -342,6 +342,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; diff --git a/dir.c b/dir.c index 7362e83282..b5bfbcaac7 100644 --- a/dir.c +++ b/dir.c @@ -371,7 +371,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, 0)) + if (cache_name_exists(pathname, len, ignore_case)) return NULL; ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc); diff --git a/environment.c b/environment.c index 6739a3f417..3c81682429 100644 --- a/environment.c +++ b/environment.c @@ -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 */ From 32260ad5dbc3100ebb5e05432198888bfbe600f8 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 22 Mar 2008 09:35:59 -0700 Subject: [PATCH 6/9] Make branch merging aware of underlying case-insensitive filsystems If we find an unexpected file, see if that filename perhaps exists in a case-insensitive way in the index, and whether the file matches that. If so, ignore it as a known pre-existing file of a different name. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- unpack-trees.c | 26 ++++++++++++++++++++++++++ unpack-trees.h | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/unpack-trees.c b/unpack-trees.c index bf7d8f6c5c..95d3413ae5 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -520,6 +520,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. @@ -540,6 +556,16 @@ static int verify_absent(struct cache_entry *ce, const char *action, 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 diff --git a/unpack-trees.h b/unpack-trees.h index ad8cc65d68..d436d6ced9 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -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; }; From 1fa6ead492c81bffdbe336373e5b162d3b5ac6d3 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 22 Mar 2008 09:48:41 -0700 Subject: [PATCH 7/9] Make unpack-tree update removed files before any updated files This is immaterial on sane filesystems, but if you have a broken (aka case-insensitive) filesystem, and the objective is to remove the file 'abc' and replace it with the file 'Abc', then we must make sure to do the removal first. Otherwise, you'd first update the file 'Abc' - which would just overwrite the file 'abc' due to the broken case-insensitive filesystem - and then remove file 'abc' - which would now brokenly remove the just updated file 'Abc' on that broken filesystem. By doing removals first, this won't happen. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- unpack-trees.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/unpack-trees.c b/unpack-trees.c index 95d3413ae5..feae846226 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -79,16 +79,21 @@ static int check_updates(struct unpack_trees_options *o) 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); 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); From 6835550def046bfd52f3e65f248024956a6df62c Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 22 Mar 2008 13:19:49 -0700 Subject: [PATCH 8/9] When adding files to the index, add support for case-independent matches This simplifies the matching case of "I already have this file and it is up-to-date" and makes it do the right thing in the face of case-insensitive aliases. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- read-cache.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/read-cache.c b/read-cache.c index 5dc998d21e..8c57adfa16 100644 --- a/read-cache.c +++ b/read-cache.c @@ -431,9 +431,9 @@ static int index_name_pos_also_unmerged(struct index_state *istate, 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)) @@ -466,13 +466,11 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose) 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); return 0; } From 1102952b45dde09d73445aa2284bcb592362fa23 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 22 Mar 2008 14:22:44 -0700 Subject: [PATCH 9/9] Make git-add behave more sensibly in a case-insensitive environment This expands on the previous patch, and allows "git add" to sanely handle a filename that has changed case, keeping the case in the index constant, and avoiding aliases. In particular, if you have an index entry called "File", but the checked-out tree is case-corrupted and has an entry called "file" instead, doing a git add . (or naming "file" explicitly) will automatically notice that we have an alias, and will replace the name "file" with the existing index capitalization (ie "File"). However, if we actually have *both* a file called "File" and one called "file", and they don't have the same lstat() information (ie we're on a case-sensitive filesystem but have the "core.ignorecase" flag set), we will error out if we try to add them both. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- cache.h | 1 + read-cache.c | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/cache.h b/cache.h index 9bce723bb8..81727e4afe 100644 --- 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) diff --git a/read-cache.c b/read-cache.c index 8c57adfa16..6b7d16c554 100644 --- a/read-cache.c +++ b/read-cache.c @@ -429,6 +429,38 @@ static int index_name_pos_also_unmerged(struct index_state *istate, 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; @@ -471,11 +503,14 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose) /* Nothing changed, really */ free(ce); 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)