Teach directory traversal about subprojects
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 11 Apr 2007 21:49:44 +0000 (14:49 -0700)
committerJunio C Hamano <junkio@cox.net>
Thu, 12 Apr 2007 02:09:55 +0000 (19:09 -0700)
This is the promised cleaned-up version of teaching directory traversal
(ie the "read_directory()" logic) about subprojects. That makes "git add"
understand to add/update subprojects.

It now knows to look at the index file to see if a directory is marked as
a subproject, and use that as information as whether it should be recursed
into or not.

It also generally cleans up the handling of directory entries when
traversing the working tree, by splitting up the decision-making process
into small functions of their own, and adding a fair number of comments.

Finally, it teaches "add_file_to_cache()" that directory names can have
slashes at the end, since the directory traversal adds them to make the
difference between a file and a directory clear (it always did that, but
my previous too-ugly-to-apply subproject patch had a totally different
path for subproject directories and avoided the slash for that case).

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
dir.c
dir.h
read-cache.c

diff --git a/dir.c b/dir.c
index 4f5a2241e6db2c973ea14a58fd25582fcbddee75..7b91501255e69925629870e575f3a15286cb3f07 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -7,12 +7,17 @@
  */
 #include "cache.h"
 #include "dir.h"
+#include "refs.h"
 
 struct path_simplify {
        int len;
        const char *path;
 };
 
+static int read_directory_recursive(struct dir_struct *dir,
+       const char *path, const char *base, int baselen,
+       int check_only, const struct path_simplify *simplify);
+
 int common_prefix(const char **pathspec)
 {
        const char *path, *slash, *next;
@@ -286,15 +291,109 @@ struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int
        return ent;
 }
 
-static int dir_exists(const char *dirname, int len)
+enum exist_status {
+       index_nonexistent = 0,
+       index_directory,
+       index_gitdir,
+};
+
+/*
+ * The index sorts alphabetically by entry name, which
+ * means that a gitlink sorts as '\0' at the end, while
+ * a directory (which is defined not as an entry, but as
+ * the files it contains) will sort with the '/' at the
+ * end.
+ */
+static enum exist_status directory_exists_in_index(const char *dirname, int len)
 {
        int pos = cache_name_pos(dirname, len);
-       if (pos >= 0)
-               return 1;
-       pos = -pos-1;
-       if (pos >= active_nr) /* can't */
-               return 0;
-       return !strncmp(active_cache[pos]->name, dirname, len);
+       if (pos < 0)
+               pos = -pos-1;
+       while (pos < active_nr) {
+               struct cache_entry *ce = active_cache[pos++];
+               unsigned char endchar;
+
+               if (strncmp(ce->name, dirname, len))
+                       break;
+               endchar = ce->name[len];
+               if (endchar > '/')
+                       break;
+               if (endchar == '/')
+                       return index_directory;
+               if (!endchar && S_ISDIRLNK(ntohl(ce->ce_mode)))
+                       return index_gitdir;
+       }
+       return index_nonexistent;
+}
+
+/*
+ * When we find a directory when traversing the filesystem, we
+ * have three distinct cases:
+ *
+ *  - ignore it
+ *  - see it as a directory
+ *  - recurse into it
+ *
+ * and which one we choose depends on a combination of existing
+ * git index contents and the flags passed into the directory
+ * traversal routine.
+ *
+ * Case 1: If we *already* have entries in the index under that
+ * directory name, we always recurse into the directory to see
+ * all the files.
+ *
+ * Case 2: If we *already* have that directory name as a gitlink,
+ * we always continue to see it as a gitlink, regardless of whether
+ * there is an actual git directory there or not (it might not
+ * be checked out as a subproject!)
+ *
+ * Case 3: if we didn't have it in the index previously, we
+ * have a few sub-cases:
+ *
+ *  (a) if "show_other_directories" is true, we show it as
+ *      just a directory, unless "hide_empty_directories" is
+ *      also true and the directory is empty, in which case
+ *      we just ignore it entirely.
+ *  (b) if it looks like a git directory, and we don't have
+ *      'no_dirlinks' set we treat it as a gitlink, and show it
+ *      as a directory.
+ *  (c) otherwise, we recurse into it.
+ */
+enum directory_treatment {
+       show_directory,
+       ignore_directory,
+       recurse_into_directory,
+};
+
+static enum directory_treatment treat_directory(struct dir_struct *dir,
+       const char *dirname, int len,
+       const struct path_simplify *simplify)
+{
+       /* The "len-1" is to strip the final '/' */
+       switch (directory_exists_in_index(dirname, len-1)) {
+       case index_directory:
+               return recurse_into_directory;
+
+       case index_gitdir:
+               return show_directory;
+
+       case index_nonexistent:
+               if (dir->show_other_directories)
+                       break;
+               if (!dir->no_dirlinks) {
+                       unsigned char sha1[20];
+                       if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
+                               return show_directory;
+               }
+               return recurse_into_directory;
+       }
+
+       /* This is the "show_other_directories" case */
+       if (!dir->hide_empty_directories)
+               return show_directory;
+       if (!read_directory_recursive(dir, dirname, dirname, len, 1, simplify))
+               return ignore_directory;
+       return show_directory;
 }
 
 /*
@@ -380,19 +479,17 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                        case DT_DIR:
                                memcpy(fullname + baselen + len, "/", 2);
                                len++;
-                               if (dir->show_other_directories &&
-                                   !dir_exists(fullname, baselen + len)) {
-                                       if (dir->hide_empty_directories &&
-                                           !read_directory_recursive(dir,
-                                                   fullname, fullname,
-                                                   baselen + len, 1, simplify))
-                                               continue;
+                               switch (treat_directory(dir, fullname, baselen + len, simplify)) {
+                               case show_directory:
                                        break;
+                               case recurse_into_directory:
+                                       contents += read_directory_recursive(dir,
+                                               fullname, fullname, baselen + len, 0, simplify);
+                                       continue;
+                               case ignore_directory:
+                                       continue;
                                }
-
-                               contents += read_directory_recursive(dir,
-                                       fullname, fullname, baselen + len, 0, simplify);
-                               continue;
+                               break;
                        case DT_REG:
                        case DT_LNK:
                                break;
diff --git a/dir.h b/dir.h
index 33c31f25fbabc36db26e6fdf9f33381f166d2d7f..817c674da1e017cffea9dddae672f9125aca8475 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -33,7 +33,8 @@ struct dir_struct {
        int nr, alloc;
        unsigned int show_ignored:1,
                     show_other_directories:1,
-                    hide_empty_directories:1;
+                    hide_empty_directories:1,
+                    no_dirlinks:1;
        struct dir_entry **entries;
 
        /* Exclude info */
index b8b6d11dba3c08b6746fe7e141d9ad53fa3dee4b..e4c628f9275abe5b87d5c2000bec323397191586 100644 (file)
@@ -365,6 +365,10 @@ int add_file_to_cache(const char *path, int verbose)
                die("%s: can only add regular files, symbolic links or git-directories", path);
 
        namelen = strlen(path);
+       if (S_ISDIR(st.st_mode)) {
+               while (namelen && path[namelen-1] == '/')
+                       namelen--;
+       }
        size = cache_entry_size(namelen);
        ce = xcalloc(1, size);
        memcpy(ce->name, path, namelen);