Merge branch 'kb/checkout-optim'

* kb/checkout-optim:
  Revert "lstat_cache(): print a warning if doing ping-pong between cache types"
  checkout bugfix: use stat.mtime instead of stat.ctime in two places
  Makefile: Set compiler switch for USE_NSEC
  Create USE_ST_TIMESPEC and turn it on for Darwin
  Not all systems use st_[cm]tim field for ns resolution file timestamp
  Record ns-timestamps if possible, but do not use it without USE_NSEC
  write_index(): update index_state->timestamp after flushing to disk
  verify_uptodate(): add ce_uptodate(ce) test
  make USE_NSEC work as expected
  fix compile error when USE_NSEC is defined
  check_updates(): effective removal of cache entries marked CE_REMOVE
  lstat_cache(): print a warning if doing ping-pong between cache types
  show_patch_diff(): remove a call to fstat()
  write_entry(): use fstat() instead of lstat() when file is open
  write_entry(): cleanup of some duplicated code
  create_directories(): remove some memcpy() and strchr() calls
  unlink_entry(): introduce schedule_dir_for_removal()
  lstat_cache(): swap func(length, string) into func(string, length)
  lstat_cache(): generalise longest_match_lstat_cache()
  lstat_cache(): small cleanup and optimisation
maint
Junio C Hamano 2009-03-17 18:54:31 -07:00
commit a9bfe81309
15 changed files with 304 additions and 178 deletions

View File

@ -129,3 +129,6 @@ For C programs:
used in the git core command set (unless your command is clearly used in the git core command set (unless your command is clearly
separate from it, such as an importer to convert random-scm-X separate from it, such as an importer to convert random-scm-X
repositories to git). repositories to git).

- When we pass <string, length> pair to functions, we should try to
pass them in that order.

View File

@ -126,6 +126,12 @@ all::
# randomly break unless your underlying filesystem supports those sub-second # randomly break unless your underlying filesystem supports those sub-second
# times (my ext3 doesn't). # times (my ext3 doesn't).
# #
# Define USE_ST_TIMESPEC if your "struct stat" uses "st_ctimespec" instead of
# "st_ctim"
#
# Define NO_NSEC if your "struct stat" does not have "st_ctim.tv_nsec"
# available. This automatically turns USE_NSEC off.
#
# Define USE_STDEV below if you want git to care about the underlying device # Define USE_STDEV below if you want git to care about the underlying device
# change being considered an inode change from the update-index perspective. # change being considered an inode change from the update-index perspective.
# #
@ -657,6 +663,7 @@ ifeq ($(uname_S),Darwin)
endif endif
NO_MEMMEM = YesPlease NO_MEMMEM = YesPlease
THREADED_DELTA_SEARCH = YesPlease THREADED_DELTA_SEARCH = YesPlease
USE_ST_TIMESPEC = YesPlease
endif endif
ifeq ($(uname_S),SunOS) ifeq ($(uname_S),SunOS)
NEEDS_SOCKET = YesPlease NEEDS_SOCKET = YesPlease
@ -734,6 +741,7 @@ ifeq ($(uname_S),AIX)
NO_MEMMEM = YesPlease NO_MEMMEM = YesPlease
NO_MKDTEMP = YesPlease NO_MKDTEMP = YesPlease
NO_STRLCPY = YesPlease NO_STRLCPY = YesPlease
NO_NSEC = YesPlease
FREAD_READS_DIRECTORIES = UnfortunatelyYes FREAD_READS_DIRECTORIES = UnfortunatelyYes
INTERNAL_QSORT = UnfortunatelyYes INTERNAL_QSORT = UnfortunatelyYes
NEEDS_LIBICONV=YesPlease NEEDS_LIBICONV=YesPlease
@ -799,6 +807,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
RUNTIME_PREFIX = YesPlease RUNTIME_PREFIX = YesPlease
NO_POSIX_ONLY_PROGRAMS = YesPlease NO_POSIX_ONLY_PROGRAMS = YesPlease
NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
NO_NSEC = YesPlease
COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/regex -Icompat/fnmatch COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/regex -Icompat/fnmatch
COMPAT_CFLAGS += -DSNPRINTF_SIZE_CORR=1 COMPAT_CFLAGS += -DSNPRINTF_SIZE_CORR=1
COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
@ -920,6 +929,15 @@ endif
ifdef NO_ST_BLOCKS_IN_STRUCT_STAT ifdef NO_ST_BLOCKS_IN_STRUCT_STAT
BASIC_CFLAGS += -DNO_ST_BLOCKS_IN_STRUCT_STAT BASIC_CFLAGS += -DNO_ST_BLOCKS_IN_STRUCT_STAT
endif endif
ifdef USE_NSEC
BASIC_CFLAGS += -DUSE_NSEC
endif
ifdef USE_ST_TIMESPEC
BASIC_CFLAGS += -DUSE_ST_TIMESPEC
endif
ifdef NO_NSEC
BASIC_CFLAGS += -DNO_NSEC
endif
ifdef NO_C99_FORMAT ifdef NO_C99_FORMAT
BASIC_CFLAGS += -DNO_C99_FORMAT BASIC_CFLAGS += -DNO_C99_FORMAT
endif endif

View File

@ -148,7 +148,7 @@ static const char **validate_pathspec(int argc, const char **argv, const char *p
if (pathspec) { if (pathspec) {
const char **p; const char **p;
for (p = pathspec; *p; p++) { for (p = pathspec; *p; p++) {
if (has_symlink_leading_path(strlen(*p), *p)) { if (has_symlink_leading_path(*p, strlen(*p))) {
int len = prefix ? strlen(prefix) : 0; int len = prefix ? strlen(prefix) : 0;
die("'%s' is beyond a symbolic link", *p + len); die("'%s' is beyond a symbolic link", *p + len);
} }

View File

@ -2360,7 +2360,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 * In such a case, path "new_name" does not exist as
* far as git is concerned. * far as git is concerned.
*/ */
if (has_symlink_leading_path(strlen(new_name), new_name)) if (has_symlink_leading_path(new_name, strlen(new_name)))
return 0; return 0;


return error("%s: already exists in working directory", new_name); return error("%s: already exists in working directory", new_name);

View File

@ -800,15 +800,13 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args,
int fd; int fd;


mtime.sec = st.st_mtime; mtime.sec = st.st_mtime;
#ifdef USE_NSEC mtime.nsec = ST_MTIME_NSEC(st);
mtime.usec = st.st_mtim.usec;
#endif
if (stat(shallow, &st)) { if (stat(shallow, &st)) {
if (mtime.sec) if (mtime.sec)
die("shallow file was removed during fetch"); die("shallow file was removed during fetch");
} else if (st.st_mtime != mtime.sec } else if (st.st_mtime != mtime.sec
#ifdef USE_NSEC #ifdef USE_NSEC
|| st.st_mtim.usec != mtime.usec || ST_MTIME_NSEC(st) != mtime.nsec
#endif #endif
) )
die("shallow file was changed during fetch"); die("shallow file was changed during fetch");

View File

@ -195,7 +195,7 @@ static int process_path(const char *path)
struct stat st; struct stat st;


len = strlen(path); len = strlen(path);
if (has_symlink_leading_path(len, path)) if (has_symlink_leading_path(path, len))
return error("'%s' is beyond a symbolic link", path); return error("'%s' is beyond a symbolic link", path);


/* /*

19
cache.h
View File

@ -140,8 +140,8 @@ struct ondisk_cache_entry_extended {
}; };


struct cache_entry { struct cache_entry {
unsigned int ce_ctime; struct cache_time ce_ctime;
unsigned int ce_mtime; struct cache_time ce_mtime;
unsigned int ce_dev; unsigned int ce_dev;
unsigned int ce_ino; unsigned int ce_ino;
unsigned int ce_mode; unsigned int ce_mode;
@ -282,7 +282,7 @@ struct index_state {
struct cache_entry **cache; struct cache_entry **cache;
unsigned int cache_nr, cache_alloc, cache_changed; unsigned int cache_nr, cache_alloc, cache_changed;
struct cache_tree *cache_tree; struct cache_tree *cache_tree;
time_t timestamp; struct cache_time timestamp;
void *alloc; void *alloc;
unsigned name_hash_initialized : 1, unsigned name_hash_initialized : 1,
initialized : 1; initialized : 1;
@ -428,7 +428,7 @@ extern int read_index_preload(struct index_state *, const char **pathspec);
extern int read_index_from(struct index_state *, const char *path); extern int read_index_from(struct index_state *, const char *path);
extern int is_index_unborn(struct index_state *); extern int is_index_unborn(struct index_state *);
extern int read_index_unmerged(struct index_state *); extern int read_index_unmerged(struct index_state *);
extern int write_index(const struct index_state *, int newfd); extern int write_index(struct index_state *, int newfd);
extern int discard_index(struct index_state *); extern int discard_index(struct index_state *);
extern int unmerged_index(const struct index_state *); extern int unmerged_index(const struct index_state *);
extern int verify_path(const char *path); extern int verify_path(const char *path);
@ -443,6 +443,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 struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name); extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);
extern int remove_index_entry_at(struct index_state *, int pos); extern int remove_index_entry_at(struct index_state *, int pos);
extern void remove_marked_cache_entries(struct index_state *istate);
extern int remove_file_from_index(struct index_state *, const char *path); extern int remove_file_from_index(struct index_state *, const char *path);
#define ADD_CACHE_VERBOSE 1 #define ADD_CACHE_VERBOSE 1
#define ADD_CACHE_PRETEND 2 #define ADD_CACHE_PRETEND 2
@ -725,11 +726,13 @@ struct checkout {
}; };


extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath); extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
extern int has_symlink_leading_path(int len, const char *name); extern int has_symlink_leading_path(const char *name, int len);
extern int has_symlink_or_noent_leading_path(int len, const char *name); extern int has_symlink_or_noent_leading_path(const char *name, int len);
extern int has_dirs_only_path(int len, const char *name, int prefix_len); extern int has_dirs_only_path(const char *name, int len, int prefix_len);
extern void invalidate_lstat_cache(int len, const char *name); extern void invalidate_lstat_cache(const char *name, int len);
extern void clear_lstat_cache(void); extern void clear_lstat_cache(void);
extern void schedule_dir_for_removal(const char *name, int len);
extern void remove_scheduled_dirs(void);


extern struct alternate_object_database { extern struct alternate_object_database {
struct alternate_object_database *next; struct alternate_object_database *next;

View File

@ -712,9 +712,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
result_size = buf.len; result_size = buf.len;
result = strbuf_detach(&buf, NULL); result = strbuf_detach(&buf, NULL);
elem->mode = canon_mode(st.st_mode); elem->mode = canon_mode(st.st_mode);
} } else if (0 <= (fd = open(elem->path, O_RDONLY))) {
else if (0 <= (fd = open(elem->path, O_RDONLY)) &&
!fstat(fd, &st)) {
size_t len = xsize_t(st.st_size); size_t len = xsize_t(st.st_size);
ssize_t done; ssize_t done;
int is_file, i; int is_file, i;

View File

@ -31,7 +31,7 @@ static int check_removed(const struct cache_entry *ce, struct stat *st)
return -1; return -1;
return 1; return 1;
} }
if (has_symlink_leading_path(ce_namelen(ce), ce->name)) if (has_symlink_leading_path(ce->name, ce_namelen(ce)))
return 1; return 1;
if (S_ISDIR(st->st_mode)) { if (S_ISDIR(st->st_mode)) {
unsigned char sub[20]; unsigned char sub[20];

2
dir.c
View File

@ -720,7 +720,7 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i
{ {
struct path_simplify *simplify; struct path_simplify *simplify;


if (has_symlink_leading_path(strlen(path), path)) if (has_symlink_leading_path(path, strlen(path)))
return dir->nr; return dir->nr;


simplify = create_simplify(pathspec); simplify = create_simplify(pathspec);

106
entry.c
View File

@ -2,15 +2,19 @@
#include "blob.h" #include "blob.h"
#include "dir.h" #include "dir.h"


static void create_directories(const char *path, const struct checkout *state) static void create_directories(const char *path, int path_len,
const struct checkout *state)
{ {
int len = strlen(path); char *buf = xmalloc(path_len + 1);
char *buf = xmalloc(len + 1); int len = 0;
const char *slash = path;


while ((slash = strchr(slash+1, '/')) != NULL) { while (len < path_len) {
len = slash - path; do {
memcpy(buf, path, len); buf[len] = path[len];
len++;
} while (len < path_len && path[len] != '/');
if (len >= path_len)
break;
buf[len] = 0; buf[len] = 0;


/* /*
@ -20,7 +24,7 @@ static void create_directories(const char *path, const struct checkout *state)
* we test the path components of the prefix with the * we test the path components of the prefix with the
* stat() function instead of the lstat() function. * stat() function instead of the lstat() function.
*/ */
if (has_dirs_only_path(len, buf, state->base_dir_len)) if (has_dirs_only_path(buf, len, state->base_dir_len))
continue; /* ok, it is already a directory. */ continue; /* ok, it is already a directory. */


/* /*
@ -74,7 +78,7 @@ static int create_file(const char *path, unsigned int mode)
return open(path, O_WRONLY | O_CREAT | O_EXCL, mode); return open(path, O_WRONLY | O_CREAT | O_EXCL, mode);
} }


static void *read_blob_entry(struct cache_entry *ce, const char *path, unsigned long *size) static void *read_blob_entry(struct cache_entry *ce, unsigned long *size)
{ {
enum object_type type; enum object_type type;
void *new = read_sha1_file(ce->sha1, &type, size); void *new = read_sha1_file(ce->sha1, &type, size);
@ -89,36 +93,52 @@ static void *read_blob_entry(struct cache_entry *ce, const char *path, unsigned


static int write_entry(struct cache_entry *ce, char *path, const struct checkout *state, int to_tempfile) static int write_entry(struct cache_entry *ce, char *path, const struct checkout *state, int to_tempfile)
{ {
int fd; unsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;
long wrote; int fd, ret, fstat_done = 0;

char *new;
switch (ce->ce_mode & S_IFMT) { struct strbuf buf = STRBUF_INIT;
char *new; unsigned long size;
struct strbuf buf; size_t wrote, newsize = 0;
unsigned long size; struct stat st;


switch (ce_mode_s_ifmt) {
case S_IFREG: case S_IFREG:
new = read_blob_entry(ce, path, &size); case S_IFLNK:
new = read_blob_entry(ce, &size);
if (!new) if (!new)
return error("git checkout-index: unable to read sha1 file of %s (%s)", return error("git checkout-index: unable to read sha1 file of %s (%s)",
path, sha1_to_hex(ce->sha1)); path, sha1_to_hex(ce->sha1));


if (ce_mode_s_ifmt == S_IFLNK && has_symlinks && !to_tempfile) {
ret = symlink(new, path);
free(new);
if (ret)
return error("git checkout-index: unable to create symlink %s (%s)",
path, strerror(errno));
break;
}

/* /*
* Convert from git internal format to working tree format * Convert from git internal format to working tree format
*/ */
strbuf_init(&buf, 0); if (ce_mode_s_ifmt == S_IFREG &&
if (convert_to_working_tree(ce->name, new, size, &buf)) { convert_to_working_tree(ce->name, new, size, &buf)) {
size_t newsize = 0;
free(new); free(new);
new = strbuf_detach(&buf, &newsize); new = strbuf_detach(&buf, &newsize);
size = newsize; size = newsize;
} }


if (to_tempfile) { if (to_tempfile) {
strcpy(path, ".merge_file_XXXXXX"); if (ce_mode_s_ifmt == S_IFREG)
strcpy(path, ".merge_file_XXXXXX");
else
strcpy(path, ".merge_link_XXXXXX");
fd = mkstemp(path); fd = mkstemp(path);
} else } else if (ce_mode_s_ifmt == S_IFREG) {
fd = create_file(path, ce->ce_mode); fd = create_file(path, ce->ce_mode);
} else {
fd = create_file(path, 0666);
}
if (fd < 0) { if (fd < 0) {
free(new); free(new);
return error("git checkout-index: unable to create file %s (%s)", return error("git checkout-index: unable to create file %s (%s)",
@ -126,41 +146,16 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
} }


wrote = write_in_full(fd, new, size); wrote = write_in_full(fd, new, size);
/* use fstat() only when path == ce->name */
if (state->refresh_cache && !to_tempfile && !state->base_dir_len) {
fstat(fd, &st);
fstat_done = 1;
}
close(fd); close(fd);
free(new); free(new);
if (wrote != size) if (wrote != size)
return error("git checkout-index: unable to write file %s", path); return error("git checkout-index: unable to write file %s", path);
break; break;
case S_IFLNK:
new = read_blob_entry(ce, path, &size);
if (!new)
return error("git checkout-index: unable to read sha1 file of %s (%s)",
path, sha1_to_hex(ce->sha1));
if (to_tempfile || !has_symlinks) {
if (to_tempfile) {
strcpy(path, ".merge_link_XXXXXX");
fd = mkstemp(path);
} else
fd = create_file(path, 0666);
if (fd < 0) {
free(new);
return error("git checkout-index: unable to create "
"file %s (%s)", path, strerror(errno));
}
wrote = write_in_full(fd, new, size);
close(fd);
free(new);
if (wrote != size)
return error("git checkout-index: unable to write file %s",
path);
} else {
wrote = symlink(new, path);
free(new);
if (wrote)
return error("git checkout-index: unable to create "
"symlink %s (%s)", path, strerror(errno));
}
break;
case S_IFGITLINK: case S_IFGITLINK:
if (to_tempfile) if (to_tempfile)
return error("git checkout-index: cannot create temporary subproject %s", path); return error("git checkout-index: cannot create temporary subproject %s", path);
@ -172,8 +167,8 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
} }


if (state->refresh_cache) { if (state->refresh_cache) {
struct stat st; if (!fstat_done)
lstat(ce->name, &st); lstat(ce->name, &st);
fill_stat_cache_info(ce, &st); fill_stat_cache_info(ce, &st);
} }
return 0; return 0;
@ -190,6 +185,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t


memcpy(path, state->base_dir, len); memcpy(path, state->base_dir, len);
strcpy(path + len, ce->name); strcpy(path + len, ce->name);
len += ce_namelen(ce);


if (!lstat(path, &st)) { if (!lstat(path, &st)) {
unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID); unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
@ -218,6 +214,6 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
return error("unable to unlink old '%s' (%s)", path, strerror(errno)); return error("unable to unlink old '%s' (%s)", path, strerror(errno));
} else if (state->not_new) } else if (state->not_new)
return 0; return 0;
create_directories(path, state); create_directories(path, len, state);
return write_entry(ce, path, state, 0); return write_entry(ce, path, state, 0);
} }

View File

@ -388,4 +388,18 @@ void git_qsort(void *base, size_t nmemb, size_t size,
# define FORCE_DIR_SET_GID 0 # define FORCE_DIR_SET_GID 0
#endif #endif


#ifdef NO_NSEC
#undef USE_NSEC
#define ST_CTIME_NSEC(st) 0
#define ST_MTIME_NSEC(st) 0
#else
#ifdef USE_ST_TIMESPEC
#define ST_CTIME_NSEC(st) ((unsigned int)((st).st_ctimespec.tv_nsec))
#define ST_MTIME_NSEC(st) ((unsigned int)((st).st_mtimespec.tv_nsec))
#else
#define ST_CTIME_NSEC(st) ((unsigned int)((st).st_ctim.tv_nsec))
#define ST_MTIME_NSEC(st) ((unsigned int)((st).st_mtim.tv_nsec))
#endif
#endif

#endif #endif

View File

@ -67,8 +67,10 @@ void rename_index_entry_at(struct index_state *istate, int nr, const char *new_n
*/ */
void fill_stat_cache_info(struct cache_entry *ce, struct stat *st) void fill_stat_cache_info(struct cache_entry *ce, struct stat *st)
{ {
ce->ce_ctime = st->st_ctime; ce->ce_ctime.sec = (unsigned int)st->st_ctime;
ce->ce_mtime = st->st_mtime; ce->ce_mtime.sec = (unsigned int)st->st_mtime;
ce->ce_ctime.nsec = ST_CTIME_NSEC(*st);
ce->ce_mtime.nsec = ST_MTIME_NSEC(*st);
ce->ce_dev = st->st_dev; ce->ce_dev = st->st_dev;
ce->ce_ino = st->st_ino; ce->ce_ino = st->st_ino;
ce->ce_uid = st->st_uid; ce->ce_uid = st->st_uid;
@ -196,11 +198,18 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
default: default:
die("internal error: ce_mode is %o", ce->ce_mode); die("internal error: ce_mode is %o", ce->ce_mode);
} }
if (ce->ce_mtime != (unsigned int) st->st_mtime) if (ce->ce_mtime.sec != (unsigned int)st->st_mtime)
changed |= MTIME_CHANGED; changed |= MTIME_CHANGED;
if (trust_ctime && ce->ce_ctime != (unsigned int) st->st_ctime) if (trust_ctime && ce->ce_ctime.sec != (unsigned int)st->st_ctime)
changed |= CTIME_CHANGED; changed |= CTIME_CHANGED;


#ifdef USE_NSEC
if (ce->ce_mtime.nsec != ST_MTIME_NSEC(*st))
changed |= MTIME_CHANGED;
if (trust_ctime && ce->ce_ctime.nsec != ST_CTIME_NSEC(*st))
changed |= CTIME_CHANGED;
#endif

if (ce->ce_uid != (unsigned int) st->st_uid || if (ce->ce_uid != (unsigned int) st->st_uid ||
ce->ce_gid != (unsigned int) st->st_gid) ce->ce_gid != (unsigned int) st->st_gid)
changed |= OWNER_CHANGED; changed |= OWNER_CHANGED;
@ -232,8 +241,16 @@ 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) static int is_racy_timestamp(const struct index_state *istate, struct cache_entry *ce)
{ {
return (!S_ISGITLINK(ce->ce_mode) && return (!S_ISGITLINK(ce->ce_mode) &&
istate->timestamp && istate->timestamp.sec &&
((unsigned int)istate->timestamp) <= ce->ce_mtime); #ifdef USE_NSEC
/* nanosecond timestamped files can also be racy! */
(istate->timestamp.sec < ce->ce_mtime.sec ||
(istate->timestamp.sec == ce->ce_mtime.sec &&
istate->timestamp.nsec <= ce->ce_mtime.nsec))
#else
istate->timestamp.sec <= ce->ce_mtime.sec
#endif
);
} }


int ie_match_stat(const struct index_state *istate, int ie_match_stat(const struct index_state *istate,
@ -443,6 +460,26 @@ int remove_index_entry_at(struct index_state *istate, int pos)
return 1; return 1;
} }


/*
* Remove all cache ententries marked for removal, that is where
* CE_REMOVE is set in ce_flags. This is much more effective than
* calling remove_index_entry_at() for each entry to be removed.
*/
void remove_marked_cache_entries(struct index_state *istate)
{
struct cache_entry **ce_array = istate->cache;
unsigned int i, j;

for (i = j = 0; i < istate->cache_nr; i++) {
if (ce_array[i]->ce_flags & CE_REMOVE)
remove_name_hash(ce_array[i]);
else
ce_array[j++] = ce_array[i];
}
istate->cache_changed = 1;
istate->cache_nr = j;
}

int remove_file_from_index(struct index_state *istate, const char *path) int remove_file_from_index(struct index_state *istate, const char *path)
{ {
int pos = index_name_pos(istate, path, strlen(path)); int pos = index_name_pos(istate, path, strlen(path));
@ -1139,8 +1176,10 @@ static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_en
size_t len; size_t len;
const char *name; const char *name;


ce->ce_ctime = ntohl(ondisk->ctime.sec); ce->ce_ctime.sec = ntohl(ondisk->ctime.sec);
ce->ce_mtime = ntohl(ondisk->mtime.sec); ce->ce_mtime.sec = ntohl(ondisk->mtime.sec);
ce->ce_ctime.nsec = ntohl(ondisk->ctime.nsec);
ce->ce_mtime.nsec = ntohl(ondisk->mtime.nsec);
ce->ce_dev = ntohl(ondisk->dev); ce->ce_dev = ntohl(ondisk->dev);
ce->ce_ino = ntohl(ondisk->ino); ce->ce_ino = ntohl(ondisk->ino);
ce->ce_mode = ntohl(ondisk->mode); ce->ce_mode = ntohl(ondisk->mode);
@ -1206,7 +1245,8 @@ int read_index_from(struct index_state *istate, const char *path)
return istate->cache_nr; return istate->cache_nr;


errno = ENOENT; errno = ENOENT;
istate->timestamp = 0; istate->timestamp.sec = 0;
istate->timestamp.nsec = 0;
fd = open(path, O_RDONLY); fd = open(path, O_RDONLY);
if (fd < 0) { if (fd < 0) {
if (errno == ENOENT) if (errno == ENOENT)
@ -1258,7 +1298,9 @@ int read_index_from(struct index_state *istate, const char *path)
src_offset += ondisk_ce_size(ce); src_offset += ondisk_ce_size(ce);
dst_offset += ce_size(ce); dst_offset += ce_size(ce);
} }
istate->timestamp = st.st_mtime; istate->timestamp.sec = st.st_mtime;
istate->timestamp.nsec = ST_MTIME_NSEC(st);

while (src_offset <= mmap_size - 20 - 8) { while (src_offset <= mmap_size - 20 - 8) {
/* After an array of active_nr index entries, /* After an array of active_nr index entries,
* there can be arbitrary number of extended * there can be arbitrary number of extended
@ -1288,14 +1330,15 @@ unmap:


int is_index_unborn(struct index_state *istate) int is_index_unborn(struct index_state *istate)
{ {
return (!istate->cache_nr && !istate->alloc && !istate->timestamp); return (!istate->cache_nr && !istate->alloc && !istate->timestamp.sec);
} }


int discard_index(struct index_state *istate) int discard_index(struct index_state *istate)
{ {
istate->cache_nr = 0; istate->cache_nr = 0;
istate->cache_changed = 0; istate->cache_changed = 0;
istate->timestamp = 0; istate->timestamp.sec = 0;
istate->timestamp.nsec = 0;
istate->name_hash_initialized = 0; istate->name_hash_initialized = 0;
free_hash(&istate->name_hash); free_hash(&istate->name_hash);
cache_tree_free(&(istate->cache_tree)); cache_tree_free(&(istate->cache_tree));
@ -1441,10 +1484,10 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce)
struct ondisk_cache_entry *ondisk = xcalloc(1, size); struct ondisk_cache_entry *ondisk = xcalloc(1, size);
char *name; char *name;


ondisk->ctime.sec = htonl(ce->ce_ctime); ondisk->ctime.sec = htonl(ce->ce_ctime.sec);
ondisk->ctime.nsec = 0; ondisk->mtime.sec = htonl(ce->ce_mtime.sec);
ondisk->mtime.sec = htonl(ce->ce_mtime); ondisk->ctime.nsec = htonl(ce->ce_ctime.nsec);
ondisk->mtime.nsec = 0; ondisk->mtime.nsec = htonl(ce->ce_mtime.nsec);
ondisk->dev = htonl(ce->ce_dev); ondisk->dev = htonl(ce->ce_dev);
ondisk->ino = htonl(ce->ce_ino); ondisk->ino = htonl(ce->ce_ino);
ondisk->mode = htonl(ce->ce_mode); ondisk->mode = htonl(ce->ce_mode);
@ -1466,13 +1509,14 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce)
return ce_write(c, fd, ondisk, size); return ce_write(c, fd, ondisk, size);
} }


int write_index(const struct index_state *istate, int newfd) int write_index(struct index_state *istate, int newfd)
{ {
git_SHA_CTX c; git_SHA_CTX c;
struct cache_header hdr; struct cache_header hdr;
int i, err, removed, extended; int i, err, removed, extended;
struct cache_entry **cache = istate->cache; struct cache_entry **cache = istate->cache;
int entries = istate->cache_nr; int entries = istate->cache_nr;
struct stat st;


for (i = removed = extended = 0; i < entries; i++) { for (i = removed = extended = 0; i < entries; i++) {
if (cache[i]->ce_flags & CE_REMOVE) if (cache[i]->ce_flags & CE_REMOVE)
@ -1516,7 +1560,12 @@ int write_index(const struct index_state *istate, int newfd)
if (err) if (err)
return -1; return -1;
} }
return ce_flush(&c, newfd);
if (ce_flush(&c, newfd) || fstat(newfd, &st))
return -1;
istate->timestamp.sec = (unsigned int)st.st_mtime;
istate->timestamp.nsec = ST_MTIME_NSEC(st);
return 0;
} }


/* /*

View File

@ -1,5 +1,37 @@
#include "cache.h" #include "cache.h"


/*
* Returns the length (on a path component basis) of the longest
* common prefix match of 'name_a' and 'name_b'.
*/
static int longest_path_match(const char *name_a, int len_a,
const char *name_b, int len_b,
int *previous_slash)
{
int max_len, match_len = 0, match_len_prev = 0, i = 0;

max_len = len_a < len_b ? len_a : len_b;
while (i < max_len && name_a[i] == name_b[i]) {
if (name_a[i] == '/') {
match_len_prev = match_len;
match_len = i;
}
i++;
}
/*
* Is 'name_b' a substring of 'name_a', the other way around,
* or is 'name_a' and 'name_b' the exact same string?
*/
if (i >= max_len && ((len_a > len_b && name_a[len_b] == '/') ||
(len_a < len_b && name_b[len_a] == '/') ||
(len_a == len_b))) {
match_len_prev = match_len;
match_len = i;
}
*previous_slash = match_len_prev;
return match_len;
}

static struct cache_def { static struct cache_def {
char path[PATH_MAX + 1]; char path[PATH_MAX + 1];
int len; int len;
@ -8,44 +40,15 @@ static struct cache_def {
int prefix_len_stat_func; int prefix_len_stat_func;
} cache; } cache;


/* static inline void reset_lstat_cache(void)
* Returns the length (on a path component basis) of the longest
* common prefix match of 'name' and the cached path string.
*/
static inline int longest_match_lstat_cache(int len, const char *name,
int *previous_slash)
{
int max_len, match_len = 0, match_len_prev = 0, i = 0;

max_len = len < cache.len ? len : cache.len;
while (i < max_len && name[i] == cache.path[i]) {
if (name[i] == '/') {
match_len_prev = match_len;
match_len = i;
}
i++;
}
/* Is the cached path string a substring of 'name'? */
if (i == cache.len && cache.len < len && name[cache.len] == '/') {
match_len_prev = match_len;
match_len = cache.len;
/* Is 'name' a substring of the cached path string? */
} else if ((i == len && len < cache.len && cache.path[len] == '/') ||
(i == len && len == cache.len)) {
match_len_prev = match_len;
match_len = len;
}
*previous_slash = match_len_prev;
return match_len;
}

static inline void reset_lstat_cache(int track_flags, int prefix_len_stat_func)
{ {
cache.path[0] = '\0'; cache.path[0] = '\0';
cache.len = 0; cache.len = 0;
cache.flags = 0; cache.flags = 0;
cache.track_flags = track_flags; /*
cache.prefix_len_stat_func = prefix_len_stat_func; * The track_flags and prefix_len_stat_func members is only
* set by the safeguard rule inside lstat_cache()
*/
} }


#define FL_DIR (1 << 0) #define FL_DIR (1 << 0)
@ -67,7 +70,7 @@ static inline void reset_lstat_cache(int track_flags, int prefix_len_stat_func)
* of the prefix, where the cache should use the stat() function * of the prefix, where the cache should use the stat() function
* instead of the lstat() function to test each path component. * instead of the lstat() function to test each path component.
*/ */
static int lstat_cache(int len, const char *name, static int lstat_cache(const char *name, int len,
int track_flags, int prefix_len_stat_func) int track_flags, int prefix_len_stat_func)
{ {
int match_len, last_slash, last_slash_dir, previous_slash; int match_len, last_slash, last_slash_dir, previous_slash;
@ -77,11 +80,13 @@ static int lstat_cache(int len, const char *name,
if (cache.track_flags != track_flags || if (cache.track_flags != track_flags ||
cache.prefix_len_stat_func != prefix_len_stat_func) { cache.prefix_len_stat_func != prefix_len_stat_func) {
/* /*
* As a safeguard we clear the cache if the values of * As a safeguard rule we clear the cache if the
* track_flags and/or prefix_len_stat_func does not * values of track_flags and/or prefix_len_stat_func
* match with the last supplied values. * does not match with the last supplied values.
*/ */
reset_lstat_cache(track_flags, prefix_len_stat_func); reset_lstat_cache();
cache.track_flags = track_flags;
cache.prefix_len_stat_func = prefix_len_stat_func;
match_len = last_slash = 0; match_len = last_slash = 0;
} else { } else {
/* /*
@ -89,7 +94,8 @@ static int lstat_cache(int len, const char *name,
* the 2 "excluding" path types. * the 2 "excluding" path types.
*/ */
match_len = last_slash = match_len = last_slash =
longest_match_lstat_cache(len, name, &previous_slash); longest_path_match(name, len, cache.path, cache.len,
&previous_slash);
match_flags = cache.flags & track_flags & (FL_NOENT|FL_SYMLINK); match_flags = cache.flags & track_flags & (FL_NOENT|FL_SYMLINK);
if (match_flags && match_len == cache.len) if (match_flags && match_len == cache.len)
return match_flags; return match_flags;
@ -153,7 +159,7 @@ static int lstat_cache(int len, const char *name,
cache.path[last_slash] = '\0'; cache.path[last_slash] = '\0';
cache.len = last_slash; cache.len = last_slash;
cache.flags = save_flags; cache.flags = save_flags;
} else if (track_flags & FL_DIR && } else if ((track_flags & FL_DIR) &&
last_slash_dir > 0 && last_slash_dir <= PATH_MAX) { last_slash_dir > 0 && last_slash_dir <= PATH_MAX) {
/* /*
* We have a separate test for the directory case, * We have a separate test for the directory case,
@ -170,7 +176,7 @@ static int lstat_cache(int len, const char *name,
cache.len = last_slash_dir; cache.len = last_slash_dir;
cache.flags = FL_DIR; cache.flags = FL_DIR;
} else { } else {
reset_lstat_cache(track_flags, prefix_len_stat_func); reset_lstat_cache();
} }
return ret_flags; return ret_flags;
} }
@ -179,19 +185,19 @@ static int lstat_cache(int len, const char *name,
* Invalidate the given 'name' from the cache, if 'name' matches * Invalidate the given 'name' from the cache, if 'name' matches
* completely with the cache. * completely with the cache.
*/ */
void invalidate_lstat_cache(int len, const char *name) void invalidate_lstat_cache(const char *name, int len)
{ {
int match_len, previous_slash; int match_len, previous_slash;


match_len = longest_match_lstat_cache(len, name, &previous_slash); match_len = longest_path_match(name, len, cache.path, cache.len,
&previous_slash);
if (len == match_len) { if (len == match_len) {
if ((cache.track_flags & FL_DIR) && previous_slash > 0) { if ((cache.track_flags & FL_DIR) && previous_slash > 0) {
cache.path[previous_slash] = '\0'; cache.path[previous_slash] = '\0';
cache.len = previous_slash; cache.len = previous_slash;
cache.flags = FL_DIR; cache.flags = FL_DIR;
} else } else
reset_lstat_cache(cache.track_flags, reset_lstat_cache();
cache.prefix_len_stat_func);
} }
} }


@ -200,7 +206,7 @@ void invalidate_lstat_cache(int len, const char *name)
*/ */
void clear_lstat_cache(void) void clear_lstat_cache(void)
{ {
reset_lstat_cache(0, 0); reset_lstat_cache();
} }


#define USE_ONLY_LSTAT 0 #define USE_ONLY_LSTAT 0
@ -208,9 +214,9 @@ void clear_lstat_cache(void)
/* /*
* Return non-zero if path 'name' has a leading symlink component * Return non-zero if path 'name' has a leading symlink component
*/ */
int has_symlink_leading_path(int len, const char *name) int has_symlink_leading_path(const char *name, int len)
{ {
return lstat_cache(len, name, return lstat_cache(name, len,
FL_SYMLINK|FL_DIR, USE_ONLY_LSTAT) & FL_SYMLINK|FL_DIR, USE_ONLY_LSTAT) &
FL_SYMLINK; FL_SYMLINK;
} }
@ -219,9 +225,9 @@ int has_symlink_leading_path(int len, const char *name)
* Return non-zero if path 'name' has a leading symlink component or * Return non-zero if path 'name' has a leading symlink component or
* if some leading path component does not exists. * if some leading path component does not exists.
*/ */
int has_symlink_or_noent_leading_path(int len, const char *name) int has_symlink_or_noent_leading_path(const char *name, int len)
{ {
return lstat_cache(len, name, return lstat_cache(name, len,
FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT) & FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT) &
(FL_SYMLINK|FL_NOENT); (FL_SYMLINK|FL_NOENT);
} }
@ -233,9 +239,68 @@ int has_symlink_or_noent_leading_path(int len, const char *name)
* 'prefix_len', thus we then allow for symlinks in the prefix part as * 'prefix_len', thus we then allow for symlinks in the prefix part as
* long as those points to real existing directories. * long as those points to real existing directories.
*/ */
int has_dirs_only_path(int len, const char *name, int prefix_len) int has_dirs_only_path(const char *name, int len, int prefix_len)
{ {
return lstat_cache(len, name, return lstat_cache(name, len,
FL_DIR|FL_FULLPATH, prefix_len) & FL_DIR|FL_FULLPATH, prefix_len) &
FL_DIR; FL_DIR;
} }

static struct removal_def {
char path[PATH_MAX];
int len;
} removal;

static void do_remove_scheduled_dirs(int new_len)
{
while (removal.len > new_len) {
removal.path[removal.len] = '\0';
if (rmdir(removal.path))
break;
do {
removal.len--;
} while (removal.len > new_len &&
removal.path[removal.len] != '/');
}
removal.len = new_len;
return;
}

void schedule_dir_for_removal(const char *name, int len)
{
int match_len, last_slash, i, previous_slash;

match_len = last_slash = i =
longest_path_match(name, len, removal.path, removal.len,
&previous_slash);
/* Find last slash inside 'name' */
while (i < len) {
if (name[i] == '/')
last_slash = i;
i++;
}

/*
* If we are about to go down the directory tree, we check if
* we must first go upwards the tree, such that we then can
* remove possible empty directories as we go upwards.
*/
if (match_len < last_slash && match_len < removal.len)
do_remove_scheduled_dirs(match_len);
/*
* If we go deeper down the directory tree, we only need to
* save the new path components as we go down.
*/
if (match_len < last_slash) {
memcpy(&removal.path[match_len], &name[match_len],
last_slash - match_len);
removal.len = last_slash;
}
return;
}

void remove_scheduled_dirs(void)
{
do_remove_scheduled_dirs(0);
return;
}

View File

@ -52,36 +52,17 @@ static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
add_index_entry(&o->result, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE|ADD_CACHE_SKIP_DFCHECK); add_index_entry(&o->result, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE|ADD_CACHE_SKIP_DFCHECK);
} }


/* Unlink the last component and attempt to remove leading /*
* directories, in case this unlink is the removal of the * Unlink the last component and schedule the leading directories for
* last entry in the directory -- empty directories are removed. * removal, such that empty directories get removed.
*/ */
static void unlink_entry(struct cache_entry *ce) static void unlink_entry(struct cache_entry *ce)
{ {
char *cp, *prev; if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce)))
char *name = ce->name;

if (has_symlink_or_noent_leading_path(ce_namelen(ce), ce->name))
return; return;
if (unlink(name)) if (unlink(ce->name))
return; return;
prev = NULL; schedule_dir_for_removal(ce->name, ce_namelen(ce));
while (1) {
int status;
cp = strrchr(name, '/');
if (prev)
*prev = '/';
if (!cp)
break;

*cp = 0;
status = rmdir(name);
if (status) {
*cp = '/';
break;
}
prev = cp;
}
} }


static struct checkout state; static struct checkout state;
@ -112,11 +93,10 @@ static int check_updates(struct unpack_trees_options *o)
display_progress(progress, ++cnt); display_progress(progress, ++cnt);
if (o->update) if (o->update)
unlink_entry(ce); unlink_entry(ce);
remove_index_entry_at(&o->result, i);
i--;
continue;
} }
} }
remove_marked_cache_entries(&o->result);
remove_scheduled_dirs();


for (i = 0; i < index->cache_nr; i++) { for (i = 0; i < index->cache_nr; i++) {
struct cache_entry *ce = index->cache[i]; struct cache_entry *ce = index->cache[i];
@ -380,8 +360,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options


memset(&o->result, 0, sizeof(o->result)); memset(&o->result, 0, sizeof(o->result));
o->result.initialized = 1; o->result.initialized = 1;
if (o->src_index) if (o->src_index) {
o->result.timestamp = o->src_index->timestamp; o->result.timestamp.sec = o->src_index->timestamp.sec;
o->result.timestamp.nsec = o->src_index->timestamp.nsec;
}
o->merge_size = len; o->merge_size = len;


if (!dfc) if (!dfc)
@ -446,7 +428,7 @@ static int verify_uptodate(struct cache_entry *ce,
{ {
struct stat st; struct stat st;


if (o->index_only || o->reset) if (o->index_only || o->reset || ce_uptodate(ce))
return 0; return 0;


if (!lstat(ce->name, &st)) { if (!lstat(ce->name, &st)) {
@ -583,7 +565,7 @@ static int verify_absent(struct cache_entry *ce, const char *action,
if (o->index_only || o->reset || !o->update) if (o->index_only || o->reset || !o->update)
return 0; return 0;


if (has_symlink_or_noent_leading_path(ce_namelen(ce), ce->name)) if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce)))
return 0; return 0;


if (!lstat(ce->name, &st)) { if (!lstat(ce->name, &st)) {