Merge branch 'lt/core-optim'
* 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
This commit is contained in:
commit
dccb3a6acb
1
Makefile
1
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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
41
cache.h
41
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;
|
||||
|
5
config.c
5
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;
|
||||
|
35
diff-lib.c
35
diff-lib.c
@ -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) {
|
||||
|
2
dir.c
2
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);
|
||||
|
@ -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 */
|
||||
|
119
name-hash.c
Normal file
119
name-hash.c
Normal file
@ -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;
|
||||
}
|
144
read-cache.c
144
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
|
||||
@ -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;
|
||||
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_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 (lstat(path, &st))
|
||||
die("%s: unable to stat (%s)", path, strerror(errno));
|
||||
|
||||
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)
|
||||
|
78
symlinks.c
78
symlinks.c
@ -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;
|
||||
/* 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;
|
||||
}
|
||||
|
||||
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';
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
while (1) {
|
||||
size_t len;
|
||||
struct stat st;
|
||||
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;
|
||||
|
||||
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;
|
||||
/*
|
||||
* See if the last known symlink cache matches.
|
||||
*/
|
||||
if (match_pathname(len, name, &link))
|
||||
return 1;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
@ -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
|
||||
|
38
t/t7506-status-submodule.sh
Executable file
38
t/t7506-status-submodule.sh
Executable file
@ -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
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user