Merge branch 'jc/ls-files-i-dir'
authorJunio C Hamano <gitster@pobox.com>
Thu, 21 Jun 2012 21:42:06 +0000 (14:42 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 21 Jun 2012 21:42:07 +0000 (14:42 -0700)
"git ls-files --exclude=t -i" did not consider anything under t/
as excluded, as it did not pay attention to exclusion of leading
paths while walking the index. Other two users of excluded() are
also updated.

* jc/ls-files-i-dir:
dir.c: make excluded() file scope static
unpack-trees.c: use path_excluded() in check_ok_to_remove()
builtin/add.c: use path_excluded()
path_excluded(): update API to less cache-entry centric
ls-files -i: micro-optimize path_excluded()
ls-files -i: pay attention to exclusion of leading paths

1  2 
builtin/add.c
dir.c
dir.h
unpack-trees.c
diff --combined builtin/add.c
index b79336d712b4c71cc2f026b0e9f42ea0bcebfc6f,e5b40d98739a590e5b69ba9c793b901b14ee1ead..87446cf92a686ed69717c85a6b38302f59351460
@@@ -13,7 -13,6 +13,7 @@@
  #include "diff.h"
  #include "diffcore.h"
  #include "revision.h"
 +#include "bulk-checkin.h"
  
  static const char * const builtin_add_usage[] = {
        "git add [options] [--] <filepattern>...",
@@@ -280,7 -279,6 +280,7 @@@ static int edit_patch(int argc, const c
  
        argc = setup_revisions(argc, argv, &rev, NULL);
        rev.diffopt.output_format = DIFF_FORMAT_PATCH;
 +      DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES);
        out = open(file, O_CREAT | O_WRONLY, 0644);
        if (out < 0)
                die (_("Could not open '%s' for writing."), file);
@@@ -443,6 -441,9 +443,9 @@@ int cmd_add(int argc, const char **argv
  
        if (pathspec) {
                int i;
+               struct path_exclude_check check;
+               path_exclude_check_init(&check, &dir);
                if (!seen)
                        seen = find_used_pathspec(pathspec);
                for (i = 0; pathspec[i]; i++) {
                            && !file_exists(pathspec[i])) {
                                if (ignore_missing) {
                                        int dtype = DT_UNKNOWN;
-                                       if (excluded(&dir, pathspec[i], &dtype))
+                                       if (path_excluded(&check, pathspec[i], -1, &dtype))
                                                dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
                                } else
                                        die(_("pathspec '%s' did not match any files"),
                        }
                }
                free(seen);
+               path_exclude_check_clear(&check);
        }
  
 +      plug_bulk_checkin();
 +
        exit_status |= add_files_to_cache(prefix, pathspec, flags);
  
        if (add_new_files)
                exit_status |= add_files(&dir, flags);
  
 +      unplug_bulk_checkin();
 +
   finish:
        if (active_cache_changed) {
                if (write_cache(newfd, active_cache, active_nr) ||
diff --combined dir.c
index ed1510fbc808f4c9799eb84a3c38030b7565f3bc,79e43377ad6a1abcfa21e740522d15e4a0b3c566..2c02b312b77f3363c234bdc66faaeafd5899586d
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -74,6 -74,7 +74,6 @@@ char *common_prefix(const char **pathsp
  
  int fill_directory(struct dir_struct *dir, const char **pathspec)
  {
 -      const char *path;
        size_t len;
  
        /*
         * use that to optimize the directory walk
         */
        len = common_prefix_len(pathspec);
 -      path = "";
 -
 -      if (len)
 -              path = xmemdupz(*pathspec, len);
  
        /* Read the directory and prune it */
 -      read_directory(dir, path, len, pathspec);
 -      if (*path)
 -              free((char *)path);
 +      read_directory(dir, pathspec ? *pathspec : "", len, pathspec);
        return len;
  }
  
@@@ -553,7 -560,7 +553,7 @@@ int excluded_from_list(const char *path
        return -1; /* undecided */
  }
  
- int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
static int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
  {
        int pathlen = strlen(pathname);
        int st;
        return 0;
  }
  
+ void path_exclude_check_init(struct path_exclude_check *check,
+                            struct dir_struct *dir)
+ {
+       check->dir = dir;
+       strbuf_init(&check->path, 256);
+ }
+ void path_exclude_check_clear(struct path_exclude_check *check)
+ {
+       strbuf_release(&check->path);
+ }
+ /*
+  * Is this name excluded?  This is for a caller like show_files() that
+  * do not honor directory hierarchy and iterate through paths that are
+  * possibly in an ignored directory.
+  *
+  * A path to a directory known to be excluded is left in check->path to
+  * optimize for repeated checks for files in the same excluded directory.
+  */
+ int path_excluded(struct path_exclude_check *check,
+                 const char *name, int namelen, int *dtype)
+ {
+       int i;
+       struct strbuf *path = &check->path;
+       /*
+        * we allow the caller to pass namelen as an optimization; it
+        * must match the length of the name, as we eventually call
+        * excluded() on the whole name string.
+        */
+       if (namelen < 0)
+               namelen = strlen(name);
+       if (path->len &&
+           path->len <= namelen &&
+           !memcmp(name, path->buf, path->len) &&
+           (!name[path->len] || name[path->len] == '/'))
+               return 1;
+       strbuf_setlen(path, 0);
+       for (i = 0; name[i]; i++) {
+               int ch = name[i];
+               if (ch == '/') {
+                       int dt = DT_DIR;
+                       if (excluded(check->dir, path->buf, &dt))
+                               return 1;
+               }
+               strbuf_addch(path, ch);
+       }
+       /* An entry in the index; cannot be a directory with subentries */
+       strbuf_setlen(path, 0);
+       return excluded(check->dir, name, dtype);
+ }
  static struct dir_entry *dir_entry_new(const char *pathname, int len)
  {
        struct dir_entry *ent;
@@@ -866,14 -931,14 +924,14 @@@ enum path_treatment 
  };
  
  static enum path_treatment treat_one_path(struct dir_struct *dir,
 -                                        char *path, int *len,
 +                                        struct strbuf *path,
                                          const struct path_simplify *simplify,
                                          int dtype, struct dirent *de)
  {
 -      int exclude = excluded(dir, path, &dtype);
 +      int exclude = excluded(dir, path->buf, &dtype);
        if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
 -          && exclude_matches_pathspec(path, *len, simplify))
 -              dir_add_ignored(dir, path, *len);
 +          && exclude_matches_pathspec(path->buf, path->len, simplify))
 +              dir_add_ignored(dir, path->buf, path->len);
  
        /*
         * Excluded? If we don't explicitly want to show
                return path_ignored;
  
        if (dtype == DT_UNKNOWN)
 -              dtype = get_dtype(de, path, *len);
 +              dtype = get_dtype(de, path->buf, path->len);
  
        /*
         * Do we want to see just the ignored files?
        default:
                return path_ignored;
        case DT_DIR:
 -              memcpy(path + *len, "/", 2);
 -              (*len)++;
 -              switch (treat_directory(dir, path, *len, simplify)) {
 +              strbuf_addch(path, '/');
 +              switch (treat_directory(dir, path->buf, path->len, simplify)) {
                case show_directory:
                        if (exclude != !!(dir->flags
                                          & DIR_SHOW_IGNORED))
  
  static enum path_treatment treat_path(struct dir_struct *dir,
                                      struct dirent *de,
 -                                    char *path, int path_max,
 +                                    struct strbuf *path,
                                      int baselen,
 -                                    const struct path_simplify *simplify,
 -                                    int *len)
 +                                    const struct path_simplify *simplify)
  {
        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))
 +      strbuf_setlen(path, baselen);
 +      strbuf_addstr(path, de->d_name);
 +      if (simplify_away(path->buf, path->len, simplify))
                return path_ignored;
  
        dtype = DTYPE(de);
 -      return treat_one_path(dir, path, len, simplify, dtype, de);
 +      return treat_one_path(dir, path, simplify, dtype, de);
  }
  
  /*
@@@ -953,23 -1024,22 +1011,23 @@@ static int read_directory_recursive(str
                                    int check_only,
                                    const struct path_simplify *simplify)
  {
 -      DIR *fdir = opendir(*base ? base : ".");
 +      DIR *fdir;
        int contents = 0;
        struct dirent *de;
 -      char path[PATH_MAX + 1];
 +      struct strbuf path = STRBUF_INIT;
  
 -      if (!fdir)
 -              return 0;
 +      strbuf_add(&path, base, baselen);
  
 -      memcpy(path, base, baselen);
 +      fdir = opendir(path.len ? path.buf : ".");
 +      if (!fdir)
 +              goto out;
  
        while ((de = readdir(fdir)) != NULL) {
 -              int len;
 -              switch (treat_path(dir, de, path, sizeof(path),
 -                                 baselen, simplify, &len)) {
 +              switch (treat_path(dir, de, &path, baselen, simplify)) {
                case path_recurse:
 -                      contents += read_directory_recursive(dir, path, len, 0, simplify);
 +                      contents += read_directory_recursive(dir, path.buf,
 +                                                           path.len, 0,
 +                                                           simplify);
                        continue;
                case path_ignored:
                        continue;
                }
                contents++;
                if (check_only)
 -                      goto exit_early;
 -              else
 -                      dir_add_name(dir, path, len);
 +                      break;
 +              dir_add_name(dir, path.buf, path.len);
        }
 -exit_early:
        closedir(fdir);
 + out:
 +      strbuf_release(&path);
  
        return contents;
  }
@@@ -1046,8 -1116,8 +1104,8 @@@ static int treat_leading_path(struct di
                              const char *path, int len,
                              const struct path_simplify *simplify)
  {
 -      char pathbuf[PATH_MAX];
 -      int baselen, blen;
 +      struct strbuf sb = STRBUF_INIT;
 +      int baselen, rc = 0;
        const char *cp;
  
        while (len && path[len - 1] == '/')
                        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,
 +              strbuf_setlen(&sb, 0);
 +              strbuf_add(&sb, path, baselen);
 +              if (!is_directory(sb.buf))
 +                      break;
 +              if (simplify_away(sb.buf, sb.len, simplify))
 +                      break;
 +              if (treat_one_path(dir, &sb, simplify,
                                   DT_DIR, NULL) == path_ignored)
 -                      return 0; /* do not recurse into it */
 -              if (len <= baselen)
 -                      return 1; /* finished checking */
 +                      break; /* do not recurse into it */
 +              if (len <= baselen) {
 +                      rc = 1;
 +                      break; /* finished checking */
 +              }
        }
 +      strbuf_release(&sb);
 +      return rc;
  }
  
  int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec)
@@@ -1163,32 -1230,22 +1221,32 @@@ int is_empty_dir(const char *path
        return ret;
  }
  
 -int remove_dir_recursively(struct strbuf *path, int flag)
 +static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
  {
        DIR *dir;
        struct dirent *e;
 -      int ret = 0, original_len = path->len, len;
 +      int ret = 0, original_len = path->len, len, kept_down = 0;
        int only_empty = (flag & REMOVE_DIR_EMPTY_ONLY);
 +      int keep_toplevel = (flag & REMOVE_DIR_KEEP_TOPLEVEL);
        unsigned char submodule_head[20];
  
        if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
 -          !resolve_gitlink_ref(path->buf, "HEAD", submodule_head))
 +          !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
                /* Do not descend and nuke a nested git work tree. */
 +              if (kept_up)
 +                      *kept_up = 1;
                return 0;
 +      }
  
 +      flag &= ~REMOVE_DIR_KEEP_TOPLEVEL;
        dir = opendir(path->buf);
 -      if (!dir)
 -              return rmdir(path->buf);
 +      if (!dir) {
 +              /* an empty dir could be removed even if it is unreadble */
 +              if (!keep_toplevel)
 +                      return rmdir(path->buf);
 +              else
 +                      return -1;
 +      }
        if (path->buf[original_len - 1] != '/')
                strbuf_addch(path, '/');
  
                if (lstat(path->buf, &st))
                        ; /* fall thru */
                else if (S_ISDIR(st.st_mode)) {
 -                      if (!remove_dir_recursively(path, only_empty))
 +                      if (!remove_dir_recurse(path, flag, &kept_down))
                                continue; /* happy */
                } else if (!only_empty && !unlink(path->buf))
                        continue; /* happy, too */
        closedir(dir);
  
        strbuf_setlen(path, original_len);
 -      if (!ret)
 +      if (!ret && !keep_toplevel && !kept_down)
                ret = rmdir(path->buf);
 +      else if (kept_up)
 +              /*
 +               * report the uplevel that it is not an error that we
 +               * did not rmdir() our directory.
 +               */
 +              *kept_up = !ret;
        return ret;
  }
  
 +int remove_dir_recursively(struct strbuf *path, int flag)
 +{
 +      return remove_dir_recurse(path, flag, NULL);
 +}
 +
  void setup_standard_excludes(struct dir_struct *dir)
  {
        const char *path;
diff --combined dir.h
index 58b6fc7c86df1bd5ac6f072672027a1474732ac6,1a88a7564da94af478d859c099664e61a3a9ca74..6c73e4151de8374d35427358cde50b506a2d7de9
--- 1/dir.h
--- 2/dir.h
+++ b/dir.h
@@@ -1,6 -1,8 +1,8 @@@
  #ifndef DIR_H
  #define DIR_H
  
+ #include "strbuf.h"
  struct dir_entry {
        unsigned int len;
        char name[FLEX_ARRAY]; /* more */
@@@ -76,8 -78,22 +78,22 @@@ extern int read_directory(struct dir_st
  
  extern int excluded_from_list(const char *pathname, int pathlen, const char *basename,
                              int *dtype, struct exclude_list *el);
- extern int excluded(struct dir_struct *, const char *, int *);
  struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len);
+ /*
+  * The excluded() API is meant for callers that check each level of leading
+  * directory hierarchies with excluded() to avoid recursing into excluded
+  * directories.  Callers that do not do so should use this API instead.
+  */
+ struct path_exclude_check {
+       struct dir_struct *dir;
+       struct strbuf path;
+ };
+ extern void path_exclude_check_init(struct path_exclude_check *, struct dir_struct *);
+ extern void path_exclude_check_clear(struct path_exclude_check *);
+ extern int path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype);
  extern 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);
  extern void add_excludes_from_file(struct dir_struct *, const char *fname);
@@@ -102,7 -118,6 +118,7 @@@ extern void setup_standard_excludes(str
  
  #define REMOVE_DIR_EMPTY_ONLY 01
  #define REMOVE_DIR_KEEP_NESTED_GIT 02
 +#define REMOVE_DIR_KEEP_TOPLEVEL 04
  extern int remove_dir_recursively(struct strbuf *path, int flag);
  
  /* tries to remove the path with empty directories along it, ignores ENOENT */
diff --combined unpack-trees.c
index ad40109432971b8b26f107a84b02aeb405e8daf9,133f2c9d2fecb8c9e1b2cc79ed93a497f4cedbc2..33a581924e11167dc546bdf97c8d49460b43674e
@@@ -102,28 -102,21 +102,28 @@@ void setup_unpack_trees_porcelain(struc
                opts->unpack_rejects[i].strdup_strings = 1;
  }
  
 -static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
 -      unsigned int set, unsigned int clear)
 +static void do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
 +                       unsigned int set, unsigned int clear)
  {
 -      unsigned int size = ce_size(ce);
 -      struct cache_entry *new = xmalloc(size);
 -
        clear |= CE_HASHED | CE_UNHASHED;
  
        if (set & CE_REMOVE)
                set |= CE_WT_REMOVE;
  
 +      ce->next = NULL;
 +      ce->ce_flags = (ce->ce_flags & ~clear) | set;
 +      add_index_entry(&o->result, ce,
 +                      ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
 +}
 +
 +static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
 +      unsigned int set, unsigned int clear)
 +{
 +      unsigned int size = ce_size(ce);
 +      struct cache_entry *new = xmalloc(size);
 +
        memcpy(new, ce, size);
 -      new->next = NULL;
 -      new->ce_flags = (new->ce_flags & ~clear) | set;
 -      add_index_entry(&o->result, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
 +      do_add_entry(o, new, set, clear);
  }
  
  /*
@@@ -594,7 -587,7 +594,7 @@@ static int unpack_nondirectories(int n
  
        for (i = 0; i < n; i++)
                if (src[i] && src[i] != o->df_conflict_entry)
 -                      add_entry(o, src[i], 0, 0);
 +                      do_add_entry(o, src[i], 0, 0);
        return 0;
  }
  
@@@ -779,7 -772,7 +779,7 @@@ static int unpack_callback(int n, unsig
        if (unpack_nondirectories(n, mask, dirmask, src, names, info) < 0)
                return -1;
  
 -      if (src[0]) {
 +      if (o->merge && src[0]) {
                if (ce_stage(src[0]))
                        mark_ce_used_same_name(src[0], o);
                else
@@@ -1023,11 -1016,14 +1023,15 @@@ int unpack_trees(unsigned len, struct t
                        o->el = &el;
        }
  
+       if (o->dir) {
+               o->path_exclude_check = xmalloc(sizeof(struct path_exclude_check));
+               path_exclude_check_init(o->path_exclude_check, o->dir);
+       }
        memset(&o->result, 0, sizeof(o->result));
        o->result.initialized = 1;
        o->result.timestamp.sec = o->src_index->timestamp.sec;
        o->result.timestamp.nsec = o->src_index->timestamp.nsec;
 +      o->result.version = o->src_index->version;
        o->merge_size = len;
        mark_all_ce_unused(o->src_index);
  
  
  done:
        free_excludes(&el);
+       if (o->path_exclude_check) {
+               path_exclude_check_clear(o->path_exclude_check);
+               free(o->path_exclude_check);
+       }
        return ret;
  
  return_failed:
@@@ -1210,7 -1210,7 +1218,7 @@@ static int verify_uptodate_1(struct cac
                        return 0;
                /*
                 * NEEDSWORK: the current default policy is to allow
 -               * submodule to be out of sync wrt the supermodule
 +               * submodule to be out of sync wrt the superproject
                 * index.  This needs to be tightened later for
                 * submodules that are marked to be automatically
                 * checked out.
@@@ -1363,7 -1363,8 +1371,8 @@@ static int check_ok_to_remove(const cha
        if (ignore_case && icase_exists(o, name, len, st))
                return 0;
  
-       if (o->dir && excluded(o->dir, name, &dtype))
+       if (o->dir &&
+           path_excluded(o->path_exclude_check, name, -1, &dtype))
                /*
                 * ce->name is explicitly excluded, so it is Ok to
                 * overwrite it.
@@@ -1793,7 -1794,7 +1802,7 @@@ int bind_merge(struct cache_entry **src
        struct cache_entry *a = src[1];
  
        if (o->merge_size != 1)
 -              return error("Cannot do a bind merge of %d trees\n",
 +              return error("Cannot do a bind merge of %d trees",
                             o->merge_size);
        if (a && old)
                return o->gently ? -1 :