diff --git a/cache.h b/cache.h index 80eeeb7db8d363..1bf2d4bde6b799 100644 --- a/cache.h +++ b/cache.h @@ -729,6 +729,8 @@ extern int has_symlink_or_noent_leading_path(const char *name, int len); extern int has_dirs_only_path(const char *name, int len, int prefix_len); extern void invalidate_lstat_cache(const char *name, int len); 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 { struct alternate_object_database *next; diff --git a/symlinks.c b/symlinks.c index 51672868d15e9a..1d6b35b8580203 100644 --- a/symlinks.c +++ b/symlinks.c @@ -245,3 +245,62 @@ int has_dirs_only_path(const char *name, int len, int prefix_len) FL_DIR|FL_FULLPATH, prefix_len) & 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; +} diff --git a/unpack-trees.c b/unpack-trees.c index 22931588503f28..e3c3fa12aaa803 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -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); } -/* Unlink the last component and attempt to remove leading - * directories, in case this unlink is the removal of the - * last entry in the directory -- empty directories are removed. +/* + * Unlink the last component and schedule the leading directories for + * removal, such that empty directories get removed. */ static void unlink_entry(struct cache_entry *ce) { - char *cp, *prev; - char *name = ce->name; - if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce))) return; - if (unlink(name)) + if (unlink(ce->name)) return; - prev = NULL; - while (1) { - int status; - cp = strrchr(name, '/'); - if (prev) - *prev = '/'; - if (!cp) - break; - - *cp = 0; - status = rmdir(name); - if (status) { - *cp = '/'; - break; - } - prev = cp; - } + schedule_dir_for_removal(ce->name, ce_namelen(ce)); } static struct checkout state; @@ -117,6 +98,7 @@ static int check_updates(struct unpack_trees_options *o) continue; } } + remove_scheduled_dirs(); for (i = 0; i < index->cache_nr; i++) { struct cache_entry *ce = index->cache[i];