Skip to content

Commit 0959525

Browse files
torvaldsJunio C Hamano
authored andcommitted
Teach directory traversal about subprojects
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>
1 parent 1833a92 commit 0959525

File tree

3 files changed

+121
-19
lines changed

3 files changed

+121
-19
lines changed

dir.c

Lines changed: 115 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@
77
*/
88
#include "cache.h"
99
#include "dir.h"
10+
#include "refs.h"
1011

1112
struct path_simplify {
1213
int len;
1314
const char *path;
1415
};
1516

17+
static int read_directory_recursive(struct dir_struct *dir,
18+
const char *path, const char *base, int baselen,
19+
int check_only, const struct path_simplify *simplify);
20+
1621
int common_prefix(const char **pathspec)
1722
{
1823
const char *path, *slash, *next;
@@ -286,15 +291,109 @@ struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int
286291
return ent;
287292
}
288293

289-
static int dir_exists(const char *dirname, int len)
294+
enum exist_status {
295+
index_nonexistent = 0,
296+
index_directory,
297+
index_gitdir,
298+
};
299+
300+
/*
301+
* The index sorts alphabetically by entry name, which
302+
* means that a gitlink sorts as '\0' at the end, while
303+
* a directory (which is defined not as an entry, but as
304+
* the files it contains) will sort with the '/' at the
305+
* end.
306+
*/
307+
static enum exist_status directory_exists_in_index(const char *dirname, int len)
290308
{
291309
int pos = cache_name_pos(dirname, len);
292-
if (pos >= 0)
293-
return 1;
294-
pos = -pos-1;
295-
if (pos >= active_nr) /* can't */
296-
return 0;
297-
return !strncmp(active_cache[pos]->name, dirname, len);
310+
if (pos < 0)
311+
pos = -pos-1;
312+
while (pos < active_nr) {
313+
struct cache_entry *ce = active_cache[pos++];
314+
unsigned char endchar;
315+
316+
if (strncmp(ce->name, dirname, len))
317+
break;
318+
endchar = ce->name[len];
319+
if (endchar > '/')
320+
break;
321+
if (endchar == '/')
322+
return index_directory;
323+
if (!endchar && S_ISDIRLNK(ntohl(ce->ce_mode)))
324+
return index_gitdir;
325+
}
326+
return index_nonexistent;
327+
}
328+
329+
/*
330+
* When we find a directory when traversing the filesystem, we
331+
* have three distinct cases:
332+
*
333+
* - ignore it
334+
* - see it as a directory
335+
* - recurse into it
336+
*
337+
* and which one we choose depends on a combination of existing
338+
* git index contents and the flags passed into the directory
339+
* traversal routine.
340+
*
341+
* Case 1: If we *already* have entries in the index under that
342+
* directory name, we always recurse into the directory to see
343+
* all the files.
344+
*
345+
* Case 2: If we *already* have that directory name as a gitlink,
346+
* we always continue to see it as a gitlink, regardless of whether
347+
* there is an actual git directory there or not (it might not
348+
* be checked out as a subproject!)
349+
*
350+
* Case 3: if we didn't have it in the index previously, we
351+
* have a few sub-cases:
352+
*
353+
* (a) if "show_other_directories" is true, we show it as
354+
* just a directory, unless "hide_empty_directories" is
355+
* also true and the directory is empty, in which case
356+
* we just ignore it entirely.
357+
* (b) if it looks like a git directory, and we don't have
358+
* 'no_dirlinks' set we treat it as a gitlink, and show it
359+
* as a directory.
360+
* (c) otherwise, we recurse into it.
361+
*/
362+
enum directory_treatment {
363+
show_directory,
364+
ignore_directory,
365+
recurse_into_directory,
366+
};
367+
368+
static enum directory_treatment treat_directory(struct dir_struct *dir,
369+
const char *dirname, int len,
370+
const struct path_simplify *simplify)
371+
{
372+
/* The "len-1" is to strip the final '/' */
373+
switch (directory_exists_in_index(dirname, len-1)) {
374+
case index_directory:
375+
return recurse_into_directory;
376+
377+
case index_gitdir:
378+
return show_directory;
379+
380+
case index_nonexistent:
381+
if (dir->show_other_directories)
382+
break;
383+
if (!dir->no_dirlinks) {
384+
unsigned char sha1[20];
385+
if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
386+
return show_directory;
387+
}
388+
return recurse_into_directory;
389+
}
390+
391+
/* This is the "show_other_directories" case */
392+
if (!dir->hide_empty_directories)
393+
return show_directory;
394+
if (!read_directory_recursive(dir, dirname, dirname, len, 1, simplify))
395+
return ignore_directory;
396+
return show_directory;
298397
}
299398

300399
/*
@@ -380,19 +479,17 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
380479
case DT_DIR:
381480
memcpy(fullname + baselen + len, "/", 2);
382481
len++;
383-
if (dir->show_other_directories &&
384-
!dir_exists(fullname, baselen + len)) {
385-
if (dir->hide_empty_directories &&
386-
!read_directory_recursive(dir,
387-
fullname, fullname,
388-
baselen + len, 1, simplify))
389-
continue;
482+
switch (treat_directory(dir, fullname, baselen + len, simplify)) {
483+
case show_directory:
390484
break;
485+
case recurse_into_directory:
486+
contents += read_directory_recursive(dir,
487+
fullname, fullname, baselen + len, 0, simplify);
488+
continue;
489+
case ignore_directory:
490+
continue;
391491
}
392-
393-
contents += read_directory_recursive(dir,
394-
fullname, fullname, baselen + len, 0, simplify);
395-
continue;
492+
break;
396493
case DT_REG:
397494
case DT_LNK:
398495
break;

dir.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ struct dir_struct {
3333
int nr, alloc;
3434
unsigned int show_ignored:1,
3535
show_other_directories:1,
36-
hide_empty_directories:1;
36+
hide_empty_directories:1,
37+
no_dirlinks:1;
3738
struct dir_entry **entries;
3839

3940
/* Exclude info */

read-cache.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,10 @@ int add_file_to_cache(const char *path, int verbose)
365365
die("%s: can only add regular files, symbolic links or git-directories", path);
366366

367367
namelen = strlen(path);
368+
if (S_ISDIR(st.st_mode)) {
369+
while (namelen && path[namelen-1] == '/')
370+
namelen--;
371+
}
368372
size = cache_entry_size(namelen);
369373
ce = xcalloc(1, size);
370374
memcpy(ce->name, path, namelen);

0 commit comments

Comments
 (0)