Skip to content

Commit 3af59e6

Browse files
committed
Merge branch 'jc/ls-files-ignored-pathspec'
* jc/ls-files-ignored-pathspec: ls-files: fix overeager pathspec optimization read_directory(): further split treat_path() read_directory_recursive(): refactor handling of a single path into a separate function t3001: test ls-files -o ignored/dir
2 parents 34349be + 48ffef9 commit 3af59e6

File tree

2 files changed

+174
-64
lines changed

2 files changed

+174
-64
lines changed

dir.c

Lines changed: 135 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,92 @@ static int get_dtype(struct dirent *de, const char *path, int len)
655655
return dtype;
656656
}
657657

658+
enum path_treatment {
659+
path_ignored,
660+
path_handled,
661+
path_recurse,
662+
};
663+
664+
static enum path_treatment treat_one_path(struct dir_struct *dir,
665+
char *path, int *len,
666+
const struct path_simplify *simplify,
667+
int dtype, struct dirent *de)
668+
{
669+
int exclude = excluded(dir, path, &dtype);
670+
if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
671+
&& in_pathspec(path, *len, simplify))
672+
dir_add_ignored(dir, path, *len);
673+
674+
/*
675+
* Excluded? If we don't explicitly want to show
676+
* ignored files, ignore it
677+
*/
678+
if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
679+
return path_ignored;
680+
681+
if (dtype == DT_UNKNOWN)
682+
dtype = get_dtype(de, path, *len);
683+
684+
/*
685+
* Do we want to see just the ignored files?
686+
* We still need to recurse into directories,
687+
* even if we don't ignore them, since the
688+
* directory may contain files that we do..
689+
*/
690+
if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) {
691+
if (dtype != DT_DIR)
692+
return path_ignored;
693+
}
694+
695+
switch (dtype) {
696+
default:
697+
return path_ignored;
698+
case DT_DIR:
699+
memcpy(path + *len, "/", 2);
700+
(*len)++;
701+
switch (treat_directory(dir, path, *len, simplify)) {
702+
case show_directory:
703+
if (exclude != !!(dir->flags
704+
& DIR_SHOW_IGNORED))
705+
return path_ignored;
706+
break;
707+
case recurse_into_directory:
708+
return path_recurse;
709+
case ignore_directory:
710+
return path_ignored;
711+
}
712+
break;
713+
case DT_REG:
714+
case DT_LNK:
715+
break;
716+
}
717+
return path_handled;
718+
}
719+
720+
static enum path_treatment treat_path(struct dir_struct *dir,
721+
struct dirent *de,
722+
char *path, int path_max,
723+
int baselen,
724+
const struct path_simplify *simplify,
725+
int *len)
726+
{
727+
int dtype;
728+
729+
if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
730+
return path_ignored;
731+
*len = strlen(de->d_name);
732+
/* Ignore overly long pathnames! */
733+
if (*len + baselen + 8 > path_max)
734+
return path_ignored;
735+
memcpy(path + baselen, de->d_name, *len + 1);
736+
*len += baselen;
737+
if (simplify_away(path, *len, simplify))
738+
return path_ignored;
739+
740+
dtype = DTYPE(de);
741+
return treat_one_path(dir, path, len, simplify, dtype, de);
742+
}
743+
658744
/*
659745
* Read a directory tree. We currently ignore anything but
660746
* directories, regular files and symlinks. That's because git
@@ -664,7 +750,10 @@ static int get_dtype(struct dirent *de, const char *path, int len)
664750
* Also, we ignore the name ".git" (even if it is not a directory).
665751
* That likely will not change.
666752
*/
667-
static int read_directory_recursive(struct dir_struct *dir, const char *base, int baselen, int check_only, const struct path_simplify *simplify)
753+
static int read_directory_recursive(struct dir_struct *dir,
754+
const char *base, int baselen,
755+
int check_only,
756+
const struct path_simplify *simplify)
668757
{
669758
DIR *fdir = opendir(*base ? base : ".");
670759
int contents = 0;
@@ -675,70 +764,16 @@ static int read_directory_recursive(struct dir_struct *dir, const char *base, in
675764
memcpy(path, base, baselen);
676765

677766
while ((de = readdir(fdir)) != NULL) {
678-
int len, dtype;
679-
int exclude;
680-
681-
if (is_dot_or_dotdot(de->d_name) ||
682-
!strcmp(de->d_name, ".git"))
683-
continue;
684-
len = strlen(de->d_name);
685-
/* Ignore overly long pathnames! */
686-
if (len + baselen + 8 > sizeof(path))
687-
continue;
688-
memcpy(path + baselen, de->d_name, len+1);
689-
len = baselen + len;
690-
if (simplify_away(path, len, simplify))
767+
int len;
768+
switch (treat_path(dir, de, path, sizeof(path),
769+
baselen, simplify, &len)) {
770+
case path_recurse:
771+
contents += read_directory_recursive
772+
(dir, path, len, 0, simplify);
691773
continue;
692-
693-
dtype = DTYPE(de);
694-
exclude = excluded(dir, path, &dtype);
695-
if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
696-
&& in_pathspec(path, len, simplify))
697-
dir_add_ignored(dir, path,len);
698-
699-
/*
700-
* Excluded? If we don't explicitly want to show
701-
* ignored files, ignore it
702-
*/
703-
if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
774+
case path_ignored:
704775
continue;
705-
706-
if (dtype == DT_UNKNOWN)
707-
dtype = get_dtype(de, path, len);
708-
709-
/*
710-
* Do we want to see just the ignored files?
711-
* We still need to recurse into directories,
712-
* even if we don't ignore them, since the
713-
* directory may contain files that we do..
714-
*/
715-
if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) {
716-
if (dtype != DT_DIR)
717-
continue;
718-
}
719-
720-
switch (dtype) {
721-
default:
722-
continue;
723-
case DT_DIR:
724-
memcpy(path + len, "/", 2);
725-
len++;
726-
switch (treat_directory(dir, path, len, simplify)) {
727-
case show_directory:
728-
if (exclude != !!(dir->flags
729-
& DIR_SHOW_IGNORED))
730-
continue;
731-
break;
732-
case recurse_into_directory:
733-
contents += read_directory_recursive(dir,
734-
path, len, 0, simplify);
735-
continue;
736-
case ignore_directory:
737-
continue;
738-
}
739-
break;
740-
case DT_REG:
741-
case DT_LNK:
776+
case path_handled:
742777
break;
743778
}
744779
contents++;
@@ -808,6 +843,41 @@ static void free_simplify(struct path_simplify *simplify)
808843
free(simplify);
809844
}
810845

846+
static int treat_leading_path(struct dir_struct *dir,
847+
const char *path, int len,
848+
const struct path_simplify *simplify)
849+
{
850+
char pathbuf[PATH_MAX];
851+
int baselen, blen;
852+
const char *cp;
853+
854+
while (len && path[len - 1] == '/')
855+
len--;
856+
if (!len)
857+
return 1;
858+
baselen = 0;
859+
while (1) {
860+
cp = path + baselen + !!baselen;
861+
cp = memchr(cp, '/', path + len - cp);
862+
if (!cp)
863+
baselen = len;
864+
else
865+
baselen = cp - path;
866+
memcpy(pathbuf, path, baselen);
867+
pathbuf[baselen] = '\0';
868+
if (!is_directory(pathbuf))
869+
return 0;
870+
if (simplify_away(pathbuf, baselen, simplify))
871+
return 0;
872+
blen = baselen;
873+
if (treat_one_path(dir, pathbuf, &blen, simplify,
874+
DT_DIR, NULL) == path_ignored)
875+
return 0; /* do not recurse into it */
876+
if (len <= baselen)
877+
return 1; /* finished checking */
878+
}
879+
}
880+
811881
int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec)
812882
{
813883
struct path_simplify *simplify;
@@ -816,7 +886,8 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const char
816886
return dir->nr;
817887

818888
simplify = create_simplify(pathspec);
819-
read_directory_recursive(dir, path, len, 0, simplify);
889+
if (!len || treat_leading_path(dir, path, len, simplify))
890+
read_directory_recursive(dir, path, len, 0, simplify);
820891
free_simplify(simplify);
821892
qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
822893
qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);

t/t3001-ls-files-others-exclude.sh

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,43 @@ test_expect_success 'negated exclude matches can override previous ones' '
175175
grep "^a.1" output
176176
'
177177

178+
test_expect_success 'subdirectory ignore (setup)' '
179+
mkdir -p top/l1/l2 &&
180+
(
181+
cd top &&
182+
git init &&
183+
echo /.gitignore >.gitignore &&
184+
echo l1 >>.gitignore &&
185+
echo l2 >l1/.gitignore &&
186+
>l1/l2/l1
187+
)
188+
'
189+
190+
test_expect_success 'subdirectory ignore (toplevel)' '
191+
(
192+
cd top &&
193+
git ls-files -o --exclude-standard
194+
) >actual &&
195+
>expect &&
196+
test_cmp expect actual
197+
'
198+
199+
test_expect_success 'subdirectory ignore (l1/l2)' '
200+
(
201+
cd top/l1/l2 &&
202+
git ls-files -o --exclude-standard
203+
) >actual &&
204+
>expect &&
205+
test_cmp expect actual
206+
'
207+
208+
test_expect_success 'subdirectory ignore (l1)' '
209+
(
210+
cd top/l1 &&
211+
git ls-files -o --exclude-standard
212+
) >actual &&
213+
>expect &&
214+
test_cmp expect actual
215+
'
216+
178217
test_done

0 commit comments

Comments
 (0)