[PATCH/RFC v2 7/7] unpack-trees.c: introduce schedule_dir_for_removal()

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Currently inside unlink_entry() if we get a successful removal of one
file with unlink(), we try to remove the leading directories each and
every time.  So if one directory containing 200 files is moved to an
other location we get 199 failed calls to rmdir() and 1 successful
call.

To fix this and to avoid some unnecessary calls to rmdir(), we only
schedule each directory for removal and we wait much longer before we
do the actually call to rmdir().

Since the unlink_entry() function is called with alphabetically sorted
names, this new function end up being very effective to avoid
unnecessary calls to rmdir().  In some cases over 95% of all calls to
rmdir() is removed with this patch.

Signed-off-by: Kjetil Barvik <barvik@xxxxxxxxxxxx>
---

  I noticed that I had to copy-and-paste almost all lines from the
  longest_match_lstat_cache() in symlinks.c into this new function, so
  maybe I should do the following:

  Move the 3 new functions below inside the same file (dir.c?) as the
  lstat_cache() functions, rename longest_match_lstat_cache() to
  longest_dir_match(), do some cleanup/fix'es, and let it be called
  from the 3 functions which currently need it.

  Good idea?


 unpack-trees.c |   97 ++++++++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 73 insertions(+), 24 deletions(-)

diff --git a/unpack-trees.c b/unpack-trees.c
index e547282..5c12eca 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -52,36 +52,84 @@ 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.
+static char path_buf[PATH_MAX];
+static unsigned int path_len;
+
+static void do_remove_scheduled_dirs(unsigned int new_len)
+{
+	while (path_len > new_len) {
+		path_buf[path_len] = '\0';
+		if (rmdir(path_buf))
+			break;
+		do {
+			path_len--;
+		} while (path_len > new_len && path_buf[path_len] != '/');
+	}
+	path_len = new_len;
+	return;
+}
+
+static void schedule_dir_for_removal(const char *name, unsigned int len)
+{
+	unsigned int i = 0, match_len = 0, max_len, last_slash;
+
+	/* Find longest dir-match between 'name' and 'path' */
+	max_len = len < path_len ? len : path_len;
+	while (i < max_len && name[i] == path_buf[i]) {
+		if (name[i] == '/')
+			match_len = i;
+		i++;
+	}
+	if (i >= max_len && ((len > path_len && name[path_len] == '/') ||
+			     (len < path_len && path_buf[len] == '/') ||
+			     (len == path_len)))
+		match_len = i;
+
+	/* Find the last slash inside 'name' */
+	last_slash = match_len;
+	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 < path_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) {
+		memmove(&path_buf[match_len], &name[match_len],
+			last_slash - match_len);
+		path_len = last_slash;
+	}
+	return;
+}
+
+#define ALL_DIRS 0
+static void remove_scheduled_dirs(void)
+{
+	do_remove_scheduled_dirs(ALL_DIRS);
+	return;
+}
+
+/*
+ * 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_namelen(ce), ce->name))
 		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 +165,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];
-- 
1.6.1.349.g99fa5

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux