{"thread":{"id":"7615","subject":"[PATCH 8/6] Teach directory traversal about subprojects","startedAt":"2007-04-11T21:49:44Z","lastAt":"2007-04-12T02:03:02Z","messageCount":4,"participants":["Linus Torvalds","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":6},"messages":[{"id":"39140","messageId":"Pine.LNX.4.64.0704111439210.6730@woody.linux-foundation.org","threadId":"7615","inReplyTo":null,"subject":"[PATCH 8/6] Teach directory traversal about subprojects","fromName":"Linus Torvalds","fromEmail":"torvalds@linux-foundation.org","sentAt":"2007-04-11T21:49:44Z","receivedAt":"2007-04-11T21:49:44Z","isPatch":true,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\nThis is the promised cleaned-up version of teaching directory traversal \n(ie the \"read_directory()\" logic) about subprojects. That makes \"git add\" \nunderstand to add/update subprojects.\n\nIt now knows to look at the index file to see if a directory is marked as \na subproject, and use that as information as whether it should be recursed \ninto or not. \n\nIt also generally cleans up the handling of directory entries when \ntraversing the working tree, by splitting up the decision-making process \ninto small functions of their own, and adding a fair number of comments.\n\nFinally, it teaches \"add_file_to_cache()\" that directory names can have \nslashes at the end, since the directory traversal adds them to make the \ndifference between a file and a directory clear (it always did that, but \nmy previous too-ugly-to-apply subproject patch had a totally different \npath for subproject directories and avoided the slash for that case).\n\nSigned-off-by: Linus Torvalds <torvalds@linux-foundation.org>\n---\n\nYes, this adds many more lines than it removes, but 40 of the added lines \nreally are just added comments about what we're doing. And the end result \nreally is more readable too even if ignoring the comments, I think.\n\nPS. This does *not* obviate the need to fix git-update-index too, so that \n\"git commit -a\" works right. I have not yet had time to check out Alex' \npatch for that, so I don't have any comments on that yet. This is \nindependent, and really just replaces the really ugly patch I sent out \nearlier. Unlike that ugly patch, I think this one can/should be applied.\n\n dir.c        |  133 ++++++++++++++++++++++++++++++++++++++++++++++++++--------\n dir.h        |    3 +-\n read-cache.c |    4 ++\n 3 files changed, 121 insertions(+), 19 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 4f5a224..7b91501 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -7,12 +7,17 @@\n  */\n #include \"cache.h\"\n #include \"dir.h\"\n+#include \"refs.h\"\n \n struct path_simplify {\n \tint len;\n \tconst char *path;\n };\n \n+static int read_directory_recursive(struct dir_struct *dir,\n+\tconst char *path, const char *base, int baselen,\n+\tint check_only, const struct path_simplify *simplify);\n+\n int common_prefix(const char **pathspec)\n {\n \tconst char *path, *slash, *next;\n@@ -286,15 +291,109 @@ struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int\n \treturn ent;\n }\n \n-static int dir_exists(const char *dirname, int len)\n+enum exist_status {\n+\tindex_nonexistent = 0,\n+\tindex_directory,\n+\tindex_gitdir,\n+};\n+\n+/*\n+ * The index sorts alphabetically by entry name, which\n+ * means that a gitlink sorts as '\\0' at the end, while\n+ * a directory (which is defined not as an entry, but as\n+ * the files it contains) will sort with the '/' at the\n+ * end.\n+ */\n+static enum exist_status directory_exists_in_index(const char *dirname, int len)\n {\n \tint pos = cache_name_pos(dirname, len);\n-\tif (pos >= 0)\n-\t\treturn 1;\n-\tpos = -pos-1;\n-\tif (pos >= active_nr) /* can't */\n-\t\treturn 0;\n-\treturn !strncmp(active_cache[pos]->name, dirname, len);\n+\tif (pos < 0)\n+\t\tpos = -pos-1;\n+\twhile (pos < active_nr) {\n+\t\tstruct cache_entry *ce = active_cache[pos++];\n+\t\tunsigned char endchar;\n+\n+\t\tif (strncmp(ce->name, dirname, len))\n+\t\t\tbreak;\n+\t\tendchar = ce->name[len];\n+\t\tif (endchar > '/')\n+\t\t\tbreak;\n+\t\tif (endchar == '/')\n+\t\t\treturn index_directory;\n+\t\tif (!endchar && S_ISDIRLNK(ntohl(ce->ce_mode)))\n+\t\t\treturn index_gitdir;\n+\t}\n+\treturn index_nonexistent;\n+}\n+\n+/*\n+ * When we find a directory when traversing the filesystem, we\n+ * have three distinct cases:\n+ *\n+ *  - ignore it\n+ *  - see it as a directory\n+ *  - recurse into it\n+ *\n+ * and which one we choose depends on a combination of existing\n+ * git index contents and the flags passed into the directory\n+ * traversal routine.\n+ *\n+ * Case 1: If we *already* have entries in the index under that\n+ * directory name, we always recurse into the directory to see\n+ * all the files.\n+ *\n+ * Case 2: If we *already* have that directory name as a gitlink,\n+ * we always continue to see it as a gitlink, regardless of whether\n+ * there is an actual git directory there or not (it might not\n+ * be checked out as a subproject!)\n+ *\n+ * Case 3: if we didn't have it in the index previously, we\n+ * have a few sub-cases:\n+ *\n+ *  (a) if \"show_other_directories\" is true, we show it as\n+ *      just a directory, unless \"hide_empty_directories\" is\n+ *      also true and the directory is empty, in which case\n+ *      we just ignore it entirely.\n+ *  (b) if it looks like a git directory, and we don't have\n+ *      'no_dirlinks' set we treat it as a gitlink, and show it\n+ *      as a directory.\n+ *  (c) otherwise, we recurse into it.\n+ */\n+enum directory_treatment {\n+\tshow_directory,\n+\tignore_directory,\n+\trecurse_into_directory,\n+};\n+\n+static enum directory_treatment treat_directory(struct dir_struct *dir,\n+\tconst char *dirname, int len,\n+\tconst struct path_simplify *simplify)\n+{\n+\t/* The \"len-1\" is to strip the final '/' */\n+\tswitch (directory_exists_in_index(dirname, len-1)) {\n+\tcase index_directory:\n+\t\treturn recurse_into_directory;\n+\n+\tcase index_gitdir:\n+\t\treturn show_directory;\n+\n+\tcase index_nonexistent:\n+\t\tif (dir->show_other_directories)\n+\t\t\tbreak;\n+\t\tif (!dir->no_dirlinks) {\n+\t\t\tunsigned char sha1[20];\n+\t\t\tif (resolve_gitlink_ref(dirname, \"HEAD\", sha1) == 0)\n+\t\t\t\treturn show_directory;\n+\t\t}\n+\t\treturn recurse_into_directory;\n+\t}\n+\n+\t/* This is the \"show_other_directories\" case */\n+\tif (!dir->hide_empty_directories)\n+\t\treturn show_directory;\n+\tif (!read_directory_recursive(dir, dirname, dirname, len, 1, simplify))\n+\t\treturn ignore_directory;\n+\treturn show_directory;\n }\n \n /*\n@@ -380,19 +479,17 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co\n \t\t\tcase DT_DIR:\n \t\t\t\tmemcpy(fullname + baselen + len, \"/\", 2);\n \t\t\t\tlen++;\n-\t\t\t\tif (dir->show_other_directories &&\n-\t\t\t\t    !dir_exists(fullname, baselen + len)) {\n-\t\t\t\t\tif (dir->hide_empty_directories &&\n-\t\t\t\t\t    !read_directory_recursive(dir,\n-\t\t\t\t\t\t    fullname, fullname,\n-\t\t\t\t\t\t    baselen + len, 1, simplify))\n-\t\t\t\t\t\tcontinue;\n+\t\t\t\tswitch (treat_directory(dir, fullname, baselen + len, simplify)) {\n+\t\t\t\tcase show_directory:\n \t\t\t\t\tbreak;\n+\t\t\t\tcase recurse_into_directory:\n+\t\t\t\t\tcontents += read_directory_recursive(dir,\n+\t\t\t\t\t\tfullname, fullname, baselen + len, 0, simplify);\n+\t\t\t\t\tcontinue;\n+\t\t\t\tcase ignore_directory:\n+\t\t\t\t\tcontinue;\n \t\t\t\t}\n-\n-\t\t\t\tcontents += read_directory_recursive(dir,\n-\t\t\t\t\tfullname, fullname, baselen + len, 0, simplify);\n-\t\t\t\tcontinue;\n+\t\t\t\tbreak;\n \t\t\tcase DT_REG:\n \t\t\tcase DT_LNK:\n \t\t\t\tbreak;\ndiff --git a/dir.h b/dir.h\nindex 33c31f2..1931609 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -33,7 +33,8 @@ struct dir_struct {\n \tint nr, alloc;\n \tunsigned int show_ignored:1,\n \t\t     show_other_directories:1,\n-\t\t     hide_empty_directories:1;\n+\t\t     hide_empty_directories:1,\n+\t\t     no_dirlinks;\n \tstruct dir_entry **entries;\n \n \t/* Exclude info */\ndiff --git a/read-cache.c b/read-cache.c\nindex 795fc5d..4040fac 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -368,6 +368,10 @@ int add_file_to_cache(const char *path, int verbose)\n \t\tdie(\"%s: can only add regular files, symbolic links or git-directories\", path);\n \n \tnamelen = strlen(path);\n+\tif (S_ISDIR(st.st_mode)) {\n+\t\twhile (namelen && path[namelen-1] == '/')\n+\t\t\tnamelen--;\n+\t}\n \tsize = cache_entry_size(namelen);\n \tce = xcalloc(1, size);\n \tmemcpy(ce->name, path, namelen);\n"},{"id":"39175","messageId":"7vfy76mn4d.fsf@assigned-by-dhcp.cox.net","threadId":"7615","inReplyTo":"Pine.LNX.4.64.0704111439210.6730@woody.linux-foundation.org","subject":"Re: [PATCH 8/6] Teach directory traversal about subprojects","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-04-12T00:50:58Z","receivedAt":"2007-04-12T00:50:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Torvalds <torvalds@linux-foundation.org> writes:\n\n> Yes, this adds many more lines than it removes, but 40 of the added lines \n> really are just added comments about what we're doing. And the end result \n> really is more readable too even if ignoring the comments, I think.\n\nMuch nicer to read.\n\n> diff --git a/dir.h b/dir.h\n> index 33c31f2..1931609 100644\n> --- a/dir.h\n> +++ b/dir.h\n> @@ -33,7 +33,8 @@ struct dir_struct {\n>  \tint nr, alloc;\n>  \tunsigned int show_ignored:1,\n>  \t\t     show_other_directories:1,\n> -\t\t     hide_empty_directories:1;\n> +\t\t     hide_empty_directories:1,\n> +\t\t     no_dirlinks;\n>  \tstruct dir_entry **entries;\n>  \n>  \t/* Exclude info */\n\nDid you mean a bitfield here?\n"},{"id":"39183","messageId":"Pine.LNX.4.64.0704111853050.4061@woody.linux-foundation.org","threadId":"7615","inReplyTo":"7vfy76mn4d.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH 8/6] Teach directory traversal about subprojects","fromName":"Linus Torvalds","fromEmail":"torvalds@linux-foundation.org","sentAt":"2007-04-12T01:53:28Z","receivedAt":"2007-04-12T01:53:28Z","isPatch":true,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Wed, 11 Apr 2007, Junio C Hamano wrote:\n> > diff --git a/dir.h b/dir.h\n> > index 33c31f2..1931609 100644\n> > --- a/dir.h\n> > +++ b/dir.h\n> > @@ -33,7 +33,8 @@ struct dir_struct {\n> >  \tint nr, alloc;\n> >  \tunsigned int show_ignored:1,\n> >  \t\t     show_other_directories:1,\n> > -\t\t     hide_empty_directories:1;\n> > +\t\t     hide_empty_directories:1,\n> > +\t\t     no_dirlinks;\n> >  \tstruct dir_entry **entries;\n> >  \n> >  \t/* Exclude info */\n> \n> Did you mean a bitfield here?\n\nI did indeed. Please fix.\n\n\t\tLinus\n"},{"id":"39187","messageId":"7vtzvml57t.fsf@assigned-by-dhcp.cox.net","threadId":"7615","inReplyTo":"Pine.LNX.4.64.0704111853050.4061@woody.linux-foundation.org","subject":"Re: [PATCH 8/6] Teach directory traversal about subprojects","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-04-12T02:03:02Z","receivedAt":"2007-04-12T02:03:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Torvalds <torvalds@linux-foundation.org> writes:\n\n> On Wed, 11 Apr 2007, Junio C Hamano wrote:\n>> > diff --git a/dir.h b/dir.h\n>> > index 33c31f2..1931609 100644\n>> > --- a/dir.h\n>> > +++ b/dir.h\n>> > @@ -33,7 +33,8 @@ struct dir_struct {\n>> >  \tint nr, alloc;\n>> >  \tunsigned int show_ignored:1,\n>> >  \t\t     show_other_directories:1,\n>> > -\t\t     hide_empty_directories:1;\n>> > +\t\t     hide_empty_directories:1,\n>> > +\t\t     no_dirlinks;\n>> >  \tstruct dir_entry **entries;\n>> >  \n>> >  \t/* Exclude info */\n>> \n>> Did you mean a bitfield here?\n>\n> I did indeed. Please fix.\n\nWill try to remember when I get around to it.  I'm mucking with\n'maint' right now to push 1.5.1.1 out, and then merge it in to\n'master'.  It will be a while before I can get to any of the\ntopics.\n"}]}