Merge branch 'jc/ls-files-ignored-pathspec'
authorJunio C Hamano <gitster@pobox.com>
Wed, 20 Jan 2010 22:43:54 +0000 (14:43 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 20 Jan 2010 22:43:54 +0000 (14:43 -0800)
* 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

1  2 
dir.c
t/t3001-ls-files-others-exclude.sh
diff --combined dir.c
index 3a8d3e67a529c03659dc950d64f4b0d6226f9925,00d698d79f8133549a0269b2c0477b2792917aaa..1538ad5da30e08c03e4297ceb369b7a09a301af4
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -200,35 -200,11 +200,35 @@@ void add_exclude(const char *string, co
        which->excludes[which->nr++] = x;
  }
  
 -static int add_excludes_from_file_1(const char *fname,
 -                                  const char *base,
 -                                  int baselen,
 -                                  char **buf_p,
 -                                  struct exclude_list *which)
 +static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
 +{
 +      int pos, len;
 +      unsigned long sz;
 +      enum object_type type;
 +      void *data;
 +      struct index_state *istate = &the_index;
 +
 +      len = strlen(path);
 +      pos = index_name_pos(istate, path, len);
 +      if (pos < 0)
 +              return NULL;
 +      if (!ce_skip_worktree(istate->cache[pos]))
 +              return NULL;
 +      data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz);
 +      if (!data || type != OBJ_BLOB) {
 +              free(data);
 +              return NULL;
 +      }
 +      *size = xsize_t(sz);
 +      return data;
 +}
 +
 +int add_excludes_from_file_to_list(const char *fname,
 +                                 const char *base,
 +                                 int baselen,
 +                                 char **buf_p,
 +                                 struct exclude_list *which,
 +                                 int check_index)
  {
        struct stat st;
        int fd, i;
        char *buf, *entry;
  
        fd = open(fname, O_RDONLY);
 -      if (fd < 0 || fstat(fd, &st) < 0)
 -              goto err;
 -      size = xsize_t(st.st_size);
 -      if (size == 0) {
 -              close(fd);
 -              return 0;
 +      if (fd < 0 || fstat(fd, &st) < 0) {
 +              if (0 <= fd)
 +                      close(fd);
 +              if (!check_index ||
 +                  (buf = read_skip_worktree_file_from_index(fname, &size)) == NULL)
 +                      return -1;
        }
 -      buf = xmalloc(size+1);
 -      if (read_in_full(fd, buf, size) != size)
 -      {
 -              free(buf);
 -              goto err;
 +      else {
 +              size = xsize_t(st.st_size);
 +              if (size == 0) {
 +                      close(fd);
 +                      return 0;
 +              }
 +              buf = xmalloc(size);
 +              if (read_in_full(fd, buf, size) != size) {
 +                      close(fd);
 +                      return -1;
 +              }
 +              close(fd);
        }
 -      close(fd);
  
        if (buf_p)
                *buf_p = buf;
 -      buf[size++] = '\n';
        entry = buf;
 -      for (i = 0; i < size; i++) {
 -              if (buf[i] == '\n') {
 +      for (i = 0; i <= size; i++) {
 +              if (i == size || buf[i] == '\n') {
                        if (entry != buf + i && entry[0] != '#') {
                                buf[i - (i && buf[i-1] == '\r')] = 0;
                                add_exclude(entry, base, baselen, which);
                }
        }
        return 0;
 -
 - err:
 -      if (0 <= fd)
 -              close(fd);
 -      return -1;
  }
  
  void add_excludes_from_file(struct dir_struct *dir, const char *fname)
  {
 -      if (add_excludes_from_file_1(fname, "", 0, NULL,
 -                                   &dir->exclude_list[EXC_FILE]) < 0)
 +      if (add_excludes_from_file_to_list(fname, "", 0, NULL,
 +                                         &dir->exclude_list[EXC_FILE], 0) < 0)
                die("cannot use %s as an exclude file", fname);
  }
  
@@@ -324,9 -300,9 +324,9 @@@ static void prep_exclude(struct dir_str
                memcpy(dir->basebuf + current, base + current,
                       stk->baselen - current);
                strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
 -              add_excludes_from_file_1(dir->basebuf,
 -                                       dir->basebuf, stk->baselen,
 -                                       &stk->filebuf, el);
 +              add_excludes_from_file_to_list(dir->basebuf,
 +                                             dir->basebuf, stk->baselen,
 +                                             &stk->filebuf, el, 1);
                dir->exclude_stack = stk;
                current = stk->baselen;
        }
  /* Scan the list and let the last match determine the fate.
   * Return 1 for exclude, 0 for include and -1 for undecided.
   */
 -static int excluded_1(const char *pathname,
 -                    int pathlen, const char *basename, int *dtype,
 -                    struct exclude_list *el)
 +int excluded_from_list(const char *pathname,
 +                     int pathlen, const char *basename, int *dtype,
 +                     struct exclude_list *el)
  {
        int i;
  
                        int to_exclude = x->to_exclude;
  
                        if (x->flags & EXC_FLAG_MUSTBEDIR) {
 +                              if (!dtype) {
 +                                      if (!prefixcmp(pathname, exclude))
 +                                              return to_exclude;
 +                                      else
 +                                              continue;
 +                              }
                                if (*dtype == DT_UNKNOWN)
                                        *dtype = get_dtype(NULL, pathname, pathlen);
                                if (*dtype != DT_DIR)
@@@ -412,8 -382,8 +412,8 @@@ int excluded(struct dir_struct *dir, co
  
        prep_exclude(dir, pathname, basename-pathname);
        for (st = EXC_CMDL; st <= EXC_FILE; st++) {
 -              switch (excluded_1(pathname, pathlen, basename,
 -                                 dtype_p, &dir->exclude_list[st])) {
 +              switch (excluded_from_list(pathname, pathlen, basename,
 +                                         dtype_p, &dir->exclude_list[st])) {
                case 0:
                        return 0;
                case 1:
@@@ -655,6 -625,92 +655,92 @@@ static int get_dtype(struct dirent *de
        return dtype;
  }
  
+ enum path_treatment {
+       path_ignored,
+       path_handled,
+       path_recurse,
+ };
+ static enum path_treatment treat_one_path(struct dir_struct *dir,
+                                         char *path, int *len,
+                                         const struct path_simplify *simplify,
+                                         int dtype, struct dirent *de)
+ {
+       int exclude = excluded(dir, path, &dtype);
+       if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
+           && in_pathspec(path, *len, simplify))
+               dir_add_ignored(dir, path, *len);
+       /*
+        * Excluded? If we don't explicitly want to show
+        * ignored files, ignore it
+        */
+       if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
+               return path_ignored;
+       if (dtype == DT_UNKNOWN)
+               dtype = get_dtype(de, path, *len);
+       /*
+        * Do we want to see just the ignored files?
+        * We still need to recurse into directories,
+        * even if we don't ignore them, since the
+        * directory may contain files that we do..
+        */
+       if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) {
+               if (dtype != DT_DIR)
+                       return path_ignored;
+       }
+       switch (dtype) {
+       default:
+               return path_ignored;
+       case DT_DIR:
+               memcpy(path + *len, "/", 2);
+               (*len)++;
+               switch (treat_directory(dir, path, *len, simplify)) {
+               case show_directory:
+                       if (exclude != !!(dir->flags
+                                         & DIR_SHOW_IGNORED))
+                               return path_ignored;
+                       break;
+               case recurse_into_directory:
+                       return path_recurse;
+               case ignore_directory:
+                       return path_ignored;
+               }
+               break;
+       case DT_REG:
+       case DT_LNK:
+               break;
+       }
+       return path_handled;
+ }
+ static enum path_treatment treat_path(struct dir_struct *dir,
+                                     struct dirent *de,
+                                     char *path, int path_max,
+                                     int baselen,
+                                     const struct path_simplify *simplify,
+                                     int *len)
+ {
+       int dtype;
+       if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
+               return path_ignored;
+       *len = strlen(de->d_name);
+       /* Ignore overly long pathnames! */
+       if (*len + baselen + 8 > path_max)
+               return path_ignored;
+       memcpy(path + baselen, de->d_name, *len + 1);
+       *len += baselen;
+       if (simplify_away(path, *len, simplify))
+               return path_ignored;
+       dtype = DTYPE(de);
+       return treat_one_path(dir, path, len, simplify, dtype, de);
+ }
  /*
   * Read a directory tree. We currently ignore anything but
   * directories, regular files and symlinks. That's because git
   * Also, we ignore the name ".git" (even if it is not a directory).
   * That likely will not change.
   */
- static int read_directory_recursive(struct dir_struct *dir, const char *base, int baselen, int check_only, const struct path_simplify *simplify)
+ static int read_directory_recursive(struct dir_struct *dir,
+                                   const char *base, int baselen,
+                                   int check_only,
+                                   const struct path_simplify *simplify)
  {
        DIR *fdir = opendir(*base ? base : ".");
        int contents = 0;
                memcpy(path, base, baselen);
  
                while ((de = readdir(fdir)) != NULL) {
-                       int len, dtype;
-                       int exclude;
-                       if (is_dot_or_dotdot(de->d_name) ||
-                            !strcmp(de->d_name, ".git"))
+                       int len;
+                       switch (treat_path(dir, de, path, sizeof(path),
+                                          baselen, simplify, &len)) {
+                       case path_recurse:
+                               contents += read_directory_recursive
+                                       (dir, path, len, 0, simplify);
                                continue;
-                       len = strlen(de->d_name);
-                       /* Ignore overly long pathnames! */
-                       if (len + baselen + 8 > sizeof(path))
+                       case path_ignored:
                                continue;
-                       memcpy(path + baselen, de->d_name, len+1);
-                       len = baselen + len;
-                       if (simplify_away(path, len, simplify))
-                               continue;
-                       dtype = DTYPE(de);
-                       exclude = excluded(dir, path, &dtype);
-                       if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
-                           && in_pathspec(path, len, simplify))
-                               dir_add_ignored(dir, path,len);
-                       /*
-                        * Excluded? If we don't explicitly want to show
-                        * ignored files, ignore it
-                        */
-                       if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
-                               continue;
-                       if (dtype == DT_UNKNOWN)
-                               dtype = get_dtype(de, path, len);
-                       /*
-                        * Do we want to see just the ignored files?
-                        * We still need to recurse into directories,
-                        * even if we don't ignore them, since the
-                        * directory may contain files that we do..
-                        */
-                       if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) {
-                               if (dtype != DT_DIR)
-                                       continue;
-                       }
-                       switch (dtype) {
-                       default:
-                               continue;
-                       case DT_DIR:
-                               memcpy(path + len, "/", 2);
-                               len++;
-                               switch (treat_directory(dir, path, len, simplify)) {
-                               case show_directory:
-                                       if (exclude != !!(dir->flags
-                                                       & DIR_SHOW_IGNORED))
-                                               continue;
-                                       break;
-                               case recurse_into_directory:
-                                       contents += read_directory_recursive(dir,
-                                               path, len, 0, simplify);
-                                       continue;
-                               case ignore_directory:
-                                       continue;
-                               }
-                               break;
-                       case DT_REG:
-                       case DT_LNK:
+                       case path_handled:
                                break;
                        }
                        contents++;
@@@ -808,6 -813,41 +843,41 @@@ static void free_simplify(struct path_s
        free(simplify);
  }
  
+ static int treat_leading_path(struct dir_struct *dir,
+                             const char *path, int len,
+                             const struct path_simplify *simplify)
+ {
+       char pathbuf[PATH_MAX];
+       int baselen, blen;
+       const char *cp;
+       while (len && path[len - 1] == '/')
+               len--;
+       if (!len)
+               return 1;
+       baselen = 0;
+       while (1) {
+               cp = path + baselen + !!baselen;
+               cp = memchr(cp, '/', path + len - cp);
+               if (!cp)
+                       baselen = len;
+               else
+                       baselen = cp - path;
+               memcpy(pathbuf, path, baselen);
+               pathbuf[baselen] = '\0';
+               if (!is_directory(pathbuf))
+                       return 0;
+               if (simplify_away(pathbuf, baselen, simplify))
+                       return 0;
+               blen = baselen;
+               if (treat_one_path(dir, pathbuf, &blen, simplify,
+                                  DT_DIR, NULL) == path_ignored)
+                       return 0; /* do not recurse into it */
+               if (len <= baselen)
+                       return 1; /* finished checking */
+       }
+ }
  int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec)
  {
        struct path_simplify *simplify;
                return dir->nr;
  
        simplify = create_simplify(pathspec);
-       read_directory_recursive(dir, path, len, 0, simplify);
+       if (!len || treat_leading_path(dir, path, len, simplify))
+               read_directory_recursive(dir, path, len, 0, simplify);
        free_simplify(simplify);
        qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
        qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
index 132c4765cbe8ffc8b92deb5d3e6ca05c012d7c37,9e71260ad04f6527f9030c14b6159fb37cbeaaad..6d2f2b67ee8d03e1f1dc4874da100cb2e179b6d1
@@@ -64,8 -64,6 +64,8 @@@ two/*.
  echo '!*.2
  !*.8' >one/two/.gitignore
  
 +allignores='.gitignore one/.gitignore one/two/.gitignore'
 +
  test_expect_success \
      'git ls-files --others with various exclude options.' \
      'git ls-files --others \
@@@ -87,26 -85,6 +87,26 @@@ test_expect_success 
         >output &&
       test_cmp expect output'
  
 +test_expect_success 'setup skip-worktree gitignore' '
 +      git add $allignores &&
 +      git update-index --skip-worktree $allignores &&
 +      rm $allignores
 +'
 +
 +test_expect_success \
 +    'git ls-files --others with various exclude options.' \
 +    'git ls-files --others \
 +       --exclude=\*.6 \
 +       --exclude-per-directory=.gitignore \
 +       --exclude-from=.git/ignore \
 +       >output &&
 +     test_cmp expect output'
 +
 +test_expect_success 'restore gitignore' '
 +      git checkout $allignores &&
 +      rm .git/index
 +'
 +
  cat > excludes-file <<\EOF
  *.[1-8]
  e*
@@@ -175,4 -153,43 +175,43 @@@ test_expect_success 'negated exclude ma
        grep "^a.1" output
  '
  
+ test_expect_success 'subdirectory ignore (setup)' '
+       mkdir -p top/l1/l2 &&
+       (
+               cd top &&
+               git init &&
+               echo /.gitignore >.gitignore &&
+               echo l1 >>.gitignore &&
+               echo l2 >l1/.gitignore &&
+               >l1/l2/l1
+       )
+ '
+ test_expect_success 'subdirectory ignore (toplevel)' '
+       (
+               cd top &&
+               git ls-files -o --exclude-standard
+       ) >actual &&
+       >expect &&
+       test_cmp expect actual
+ '
+ test_expect_success 'subdirectory ignore (l1/l2)' '
+       (
+               cd top/l1/l2 &&
+               git ls-files -o --exclude-standard
+       ) >actual &&
+       >expect &&
+       test_cmp expect actual
+ '
+ test_expect_success 'subdirectory ignore (l1)' '
+       (
+               cd top/l1 &&
+               git ls-files -o --exclude-standard
+       ) >actual &&
+       >expect &&
+       test_cmp expect actual
+ '
  test_done