Merge branch 'lt/dirwalk' into jc/dirwalk-n-cache-tree
authorJunio C Hamano <junkio@cox.net>
Sat, 20 May 2006 08:28:49 +0000 (01:28 -0700)
committerJunio C Hamano <junkio@cox.net>
Sat, 20 May 2006 08:52:19 +0000 (01:52 -0700)
This commit is what this branch is all about.  It records the
evil merge needed to adjust built-in git-add and git-rm for
the cache-tree extension.

* lt/dirwalk:
  Add builtin "git rm" command
  Move pathspec matching from builtin-add.c into dir.c
  Prevent bogus paths from being added to the index.
  builtin-add: fix unmatched pathspec warnings.
  Remove old "git-add.sh" remnants
  builtin-add: warn on unmatched pathspecs
  Do "git add" as a builtin
  Clean up git-ls-file directory walking library interface
  libify git-ls-files directory traversal

Conflicts:

Makefile
builtin.h
git.c
update-index.c

1  2 
Makefile
builtin-add.c
builtin-rm.c
builtin.h
cache.h
git.c
read-cache.c
update-index.c

diff --combined Makefile
+++ b/Makefile
@@@ -113,19 -113,19 +113,19 @@@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__pow
  ### --- END CONFIGURATION SECTION ---
  
  SCRIPT_SH = \
-       git-add.sh git-bisect.sh git-branch.sh git-checkout.sh \
+       git-bisect.sh git-branch.sh git-checkout.sh \
        git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \
        git-fetch.sh \
        git-format-patch.sh git-ls-remote.sh \
        git-merge-one-file.sh git-parse-remote.sh \
        git-prune.sh git-pull.sh git-rebase.sh \
        git-repack.sh git-request-pull.sh git-reset.sh \
-       git-resolve.sh git-revert.sh git-rm.sh git-sh-setup.sh \
+       git-resolve.sh git-revert.sh git-sh-setup.sh \
        git-tag.sh git-verify-tag.sh \
        git-applymbox.sh git-applypatch.sh git-am.sh \
        git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
        git-merge-resolve.sh git-merge-ours.sh \
 -      git-lost-found.sh
 +      git-lost-found.sh git-quiltimport.sh
  
  SCRIPT_PERL = \
        git-archimport.perl git-cvsimport.perl git-relink.perl \
@@@ -154,24 -154,24 +154,25 @@@ PROGRAMS = 
        git-convert-objects$X git-diff-files$X \
        git-diff-index$X git-diff-stages$X \
        git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \
 -      git-hash-object$X git-index-pack$X git-init-db$X git-local-fetch$X \
 +      git-hash-object$X git-index-pack$X git-local-fetch$X \
        git-ls-files$X git-ls-tree$X git-mailinfo$X git-merge-base$X \
        git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \
        git-peek-remote$X git-prune-packed$X git-read-tree$X \
 -      git-receive-pack$X git-rev-list$X git-rev-parse$X \
 +      git-receive-pack$X git-rev-parse$X \
        git-send-pack$X git-show-branch$X git-shell$X \
        git-show-index$X git-ssh-fetch$X \
        git-ssh-upload$X git-tar-tree$X git-unpack-file$X \
        git-unpack-objects$X git-update-index$X git-update-server-info$X \
        git-upload-pack$X git-verify-pack$X git-write-tree$X \
 -      git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \
 +      git-update-ref$X git-symbolic-ref$X \
        git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \
        git-describe$X git-merge-tree$X git-blame$X git-imap-send$X
  
  BUILT_INS = git-log$X git-whatchanged$X git-show$X \
        git-count-objects$X git-diff$X git-push$X \
-       git-grep$X git-rev-list$X git-check-ref-format$X \
+       git-grep$X git-add$X git-rm$X git-rev-list$X \
 -      git-check-ref-format$X
++      git-check-ref-format$X \
 +      git-init-db$X
  
  # what 'all' will build and 'install' will install, in gitexecdir
  ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@@ -200,7 -200,7 +201,7 @@@ LIB_H = 
        blob.h cache.h commit.h csum-file.h delta.h \
        diff.h object.h pack.h pkt-line.h quote.h refs.h \
        run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
-       tree-walk.h log-tree.h
+       tree-walk.h log-tree.h dir.h
  
  DIFF_OBJS = \
        diff.o diff-lib.o diffcore-break.o diffcore-order.o \
        diffcore-delta.o log-tree.o
  
  LIB_OBJS = \
 -      blob.o commit.o connect.o csum-file.o base85.o \
 +      blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \
        date.o diff-delta.o entry.o exec_cmd.o ident.o index.o \
        object.o pack-check.o patch-delta.o path.o pkt-line.o \
-       quote.o read-cache.o refs.o run-command.o \
+       quote.o read-cache.o refs.o run-command.o dir.o \
        server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
        tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
        fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
  
  BUILTIN_OBJS = \
        builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
-       builtin-grep.o builtin-rev-list.o builtin-check-ref-format.o \
-       builtin-init-db.o
+       builtin-grep.o builtin-add.o builtin-rev-list.o builtin-check-ref-format.o \
 -      builtin-rm.o
++      builtin-rm.o builtin-init-db.o
  
  GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
  LIBS = $(GITLIBS) -lz
@@@ -461,7 -461,6 +462,7 @@@ PYTHON_PATH_SQ = $(subst ','\'',$(PYTHO
  GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR))
  
  ALL_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS)
 +ALL_CFLAGS += -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"'
  LIB_OBJS += $(COMPAT_OBJS)
  export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
  ### Build rules
@@@ -568,6 -567,10 +569,6 @@@ git-http-push$X: revision.o http.o http
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
  
 -init-db.o: init-db.c
 -      $(CC) -c $(ALL_CFLAGS) \
 -              -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $*.c
 -
  $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)
  $(patsubst git-%$X,%.o,$(PROGRAMS)): $(GITLIBS)
  $(DIFF_OBJS): diffcore.h
@@@ -609,9 -612,6 +610,9 @@@ test-date$X: test-date.c date.o ctype.
  test-delta$X: test-delta.c diff-delta.o patch-delta.o
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^
  
 +test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS)
 +      $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 +
  check:
        for i in *.c; do sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; done
  
@@@ -654,25 -654,6 +655,25 @@@ dist: git.spec git-tar-tre
  rpm: dist
        $(RPMBUILD) -ta $(GIT_TARNAME).tar.gz
  
 +htmldocs = git-htmldocs-$(GIT_VERSION)
 +manpages = git-manpages-$(GIT_VERSION)
 +dist-doc:
 +      rm -fr .doc-tmp-dir
 +      mkdir .doc-tmp-dir
 +      $(MAKE) -C Documentation WEBDOC_DEST=../.doc-tmp-dir install-webdoc
 +      cd .doc-tmp-dir && $(TAR) cf ../$(htmldocs).tar .
 +      gzip -n -9 -f $(htmldocs).tar
 +      :
 +      rm -fr .doc-tmp-dir
 +      mkdir .doc-tmp-dir .doc-tmp-dir/man1 .doc-tmp-dir/man7
 +      $(MAKE) -C Documentation DESTDIR=. \
 +              man1=../.doc-tmp-dir/man1 \
 +              man7=../.doc-tmp-dir/man7 \
 +              install
 +      cd .doc-tmp-dir && $(TAR) cf ../$(manpages).tar .
 +      gzip -n -9 -f $(manpages).tar
 +      rm -fr .doc-tmp-dir
 +
  ### Cleaning rules
  
  clean:
                $(LIB_FILE) $(XDIFF_LIB)
        rm -f $(ALL_PROGRAMS) $(BUILT_INS) git$X
        rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags
 -      rm -rf $(GIT_TARNAME)
 +      rm -rf $(GIT_TARNAME) .doc-tmp-dir
        rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
 +      rm -f $(htmldocs).tar $(manpages).tar
        $(MAKE) -C Documentation/ clean
        $(MAKE) -C templates clean
        $(MAKE) -C t/ clean
diff --combined builtin-add.c
index 0000000,6166f66..02fe38b
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,187 +1,189 @@@
+ /*
+  * "git add" builtin command
+  *
+  * Copyright (C) 2006 Linus Torvalds
+  */
+ #include <fnmatch.h>
+ #include "cache.h"
+ #include "builtin.h"
+ #include "dir.h"
++#include "cache-tree.h"
+ static const char builtin_add_usage[] =
+ "git-add [-n] [-v] <filepattern>...";
+ static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
+ {
+       char *seen;
+       int i, specs;
+       struct dir_entry **src, **dst;
+       for (specs = 0; pathspec[specs];  specs++)
+               /* nothing */;
+       seen = xmalloc(specs);
+       memset(seen, 0, specs);
+       src = dst = dir->entries;
+       i = dir->nr;
+       while (--i >= 0) {
+               struct dir_entry *entry = *src++;
+               if (!match_pathspec(pathspec, entry->name, entry->len, prefix, seen)) {
+                       free(entry);
+                       continue;
+               }
+               *dst++ = entry;
+       }
+       dir->nr = dst - dir->entries;
+       for (i = 0; i < specs; i++) {
+               struct stat st;
+               const char *match;
+               if (seen[i])
+                       continue;
+               /* Existing file? We must have ignored it */
+               match = pathspec[i];
+               if (!match[0] || !lstat(match, &st))
+                       continue;
+               die("pathspec '%s' did not match any files", match);
+       }
+ }
+ static void fill_directory(struct dir_struct *dir, const char **pathspec)
+ {
+       const char *path, *base;
+       int baselen;
+       /* Set up the default git porcelain excludes */
+       memset(dir, 0, sizeof(*dir));
+       dir->exclude_per_dir = ".gitignore";
+       path = git_path("info/exclude");
+       if (!access(path, R_OK))
+               add_excludes_from_file(dir, path);
+       /*
+        * Calculate common prefix for the pathspec, and
+        * use that to optimize the directory walk
+        */
+       baselen = common_prefix(pathspec);
+       path = ".";
+       base = "";
+       if (baselen) {
+               char *common = xmalloc(baselen + 1);
+               common = xmalloc(baselen + 1);
+               memcpy(common, *pathspec, baselen);
+               common[baselen] = 0;
+               path = base = common;
+       }
+       /* Read the directory and prune it */
+       read_directory(dir, path, base, baselen);
+       if (pathspec)
+               prune_directory(dir, pathspec, baselen);
+ }
+ static int add_file_to_index(const char *path, int verbose)
+ {
+       int size, namelen;
+       struct stat st;
+       struct cache_entry *ce;
+       if (lstat(path, &st))
+               die("%s: unable to stat (%s)", path, strerror(errno));
+       if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode))
+               die("%s: can only add regular files or symbolic links", path);
+       namelen = strlen(path);
+       size = cache_entry_size(namelen);
+       ce = xcalloc(1, size);
+       memcpy(ce->name, path, namelen);
+       ce->ce_flags = htons(namelen);
+       fill_stat_cache_info(ce, &st);
+       ce->ce_mode = create_ce_mode(st.st_mode);
+       if (!trust_executable_bit) {
+               /* If there is an existing entry, pick the mode bits
+                * from it.
+                */
+               int pos = cache_name_pos(path, namelen);
+               if (pos >= 0)
+                       ce->ce_mode = active_cache[pos]->ce_mode;
+       }
+       if (index_path(ce->sha1, path, &st, 1))
+               die("unable to index file %s", path);
+       if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD))
+               die("unable to add %s to index",path);
+       if (verbose)
+               printf("add '%s'\n", path);
++      cache_tree_invalidate_path(active_cache_tree, path);
+       return 0;
+ }
+ static struct cache_file cache_file;
+ int cmd_add(int argc, const char **argv, char **envp)
+ {
+       int i, newfd;
+       int verbose = 0, show_only = 0;
+       const char *prefix = setup_git_directory();
+       const char **pathspec;
+       struct dir_struct dir;
+       git_config(git_default_config);
+       newfd = hold_index_file_for_update(&cache_file, get_index_file());
+       if (newfd < 0)
+               die("unable to create new cachefile");
+       if (read_cache() < 0)
+               die("index file corrupt");
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "-n")) {
+                       show_only = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-v")) {
+                       verbose = 1;
+                       continue;
+               }
+               die(builtin_add_usage);
+       }
+       git_config(git_default_config);
+       pathspec = get_pathspec(prefix, argv + i);
+       fill_directory(&dir, pathspec);
+       if (show_only) {
+               const char *sep = "", *eof = "";
+               for (i = 0; i < dir.nr; i++) {
+                       printf("%s%s", sep, dir.entries[i]->name);
+                       sep = " ";
+                       eof = "\n";
+               }
+               fputs(eof, stdout);
+               return 0;
+       }
+       for (i = 0; i < dir.nr; i++)
+               add_file_to_index(dir.entries[i]->name, verbose);
+       if (active_cache_changed) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_index_file(&cache_file))
+                       die("Unable to write new index file");
+       }
+       return 0;
+ }
diff --combined builtin-rm.c
index 0000000,9014c61..0beb86d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,150 +1,151 @@@
+ /*
+  * "git rm" builtin command
+  *
+  * Copyright (C) Linus Torvalds 2006
+  */
+ #include "cache.h"
+ #include "builtin.h"
+ #include "dir.h"
+ static const char builtin_rm_usage[] =
+ "git-rm [-n] [-v] [-f] <filepattern>...";
+ static struct {
+       int nr, alloc;
+       const char **name;
+ } list;
+ static void add_list(const char *name)
+ {
+       if (list.nr >= list.alloc) {
+               list.alloc = alloc_nr(list.alloc);
+               list.name = xrealloc(list.name, list.alloc * sizeof(const char *));
+       }
+       list.name[list.nr++] = name;
+ }
+ static int remove_file(const char *name)
+ {
+       int ret;
+       char *slash;
+       ret = unlink(name);
+       if (!ret && (slash = strrchr(name, '/'))) {
+               char *n = strdup(name);
+               do {
+                       n[slash - name] = 0;
+                       name = n;
+               } while (!rmdir(name) && (slash = strrchr(name, '/')));
+       }
+       return ret;
+ }
+ static struct cache_file cache_file;
+ int cmd_rm(int argc, const char **argv, char **envp)
+ {
+       int i, newfd;
+       int verbose = 0, show_only = 0, force = 0;
+       const char *prefix = setup_git_directory();
+       const char **pathspec;
+       char *seen;
+       git_config(git_default_config);
+       newfd = hold_index_file_for_update(&cache_file, get_index_file());
+       if (newfd < 0)
+               die("unable to create new index file");
+       if (read_cache() < 0)
+               die("index file corrupt");
+       for (i = 1 ; i < argc ; i++) {
+               const char *arg = argv[i];
+               if (*arg != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "-n")) {
+                       show_only = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-v")) {
+                       verbose = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-f")) {
+                       force = 1;
+                       continue;
+               }
+               die(builtin_rm_usage);
+       }
+       pathspec = get_pathspec(prefix, argv + i);
+       seen = NULL;
+       if (pathspec) {
+               for (i = 0; pathspec[i] ; i++)
+                       /* nothing */;
+               seen = xmalloc(i);
+               memset(seen, 0, i);
+       }
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
+                       continue;
+               add_list(ce->name);
+       }
+       if (pathspec) {
+               const char *match;
+               for (i = 0; (match = pathspec[i]) != NULL ; i++) {
+                       if (*match && !seen[i])
+                               die("pathspec '%s' did not match any files", match);
+               }
+       }
+       /*
+        * First remove the names from the index: we won't commit
+        * the index unless all of them succeed
+        */
+       for (i = 0; i < list.nr; i++) {
+               const char *path = list.name[i];
+               printf("rm '%s'\n", path);
+               if (remove_file_from_cache(path))
+                       die("git rm: unable to remove %s", path);
++              cache_tree_invalidate_path(active_cache_tree, path);
+       }
+       /*
+        * Then, if we used "-f", remove the filenames from the
+        * workspace. If we fail to remove the first one, we
+        * abort the "git rm" (but once we've successfully removed
+        * any file at all, we'll go ahead and commit to it all:
+        * by then we've already committed ourself and can't fail
+        * in the middle)
+        */
+       if (force) {
+               int removed = 0;
+               for (i = 0; i < list.nr; i++) {
+                       const char *path = list.name[i];
+                       if (!remove_file(path)) {
+                               removed = 1;
+                               continue;
+                       }
+                       if (!removed)
+                               die("git rm: %s: %s", path, strerror(errno));
+               }
+       }
+       if (active_cache_changed) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_index_file(&cache_file))
+                       die("Unable to write new index file");
+       }
+       return 0;
+ }
diff --combined builtin.h
+++ b/builtin.h
@@@ -24,8 -24,7 +24,10 @@@ extern int cmd_count_objects(int argc, 
  
  extern int cmd_push(int argc, const char **argv, char **envp);
  extern int cmd_grep(int argc, const char **argv, char **envp);
+ extern int cmd_rm(int argc, const char **argv, char **envp);
+ extern int cmd_add(int argc, const char **argv, char **envp);
 +extern int cmd_rev_list(int argc, const char **argv, char **envp);
 +extern int cmd_check_ref_format(int argc, const char **argv, char **envp);
 +extern int cmd_init_db(int argc, const char **argv, char **envp);
  
  #endif
diff --combined cache.h
+++ b/cache.h
@@@ -114,7 -114,6 +114,7 @@@ static inline unsigned int create_ce_mo
  
  extern struct cache_entry **active_cache;
  extern unsigned int active_nr, active_alloc, active_cache_changed;
 +extern struct cache_tree *active_cache_tree;
  
  #define GIT_DIR_ENVIRONMENT "GIT_DIR"
  #define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
@@@ -143,6 -142,7 +143,7 @@@ extern void verify_non_filename(const c
  /* Initialize and use the cache information */
  extern int read_cache(void);
  extern int write_cache(int newfd, struct cache_entry **cache, int entries);
+ extern int verify_path(const char *path);
  extern int cache_name_pos(const char *name, int namelen);
  #define ADD_CACHE_OK_TO_ADD 1         /* Ok to add */
  #define ADD_CACHE_OK_TO_REPLACE 2     /* Ok to replace file/directory */
@@@ -159,12 -159,6 +160,12 @@@ extern int index_pipe(unsigned char *sh
  extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
  extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
  
 +#define REFRESH_REALLY                0x0001  /* ignore_valid */
 +#define REFRESH_UNMERGED      0x0002  /* allow unmerged */
 +#define REFRESH_QUIET         0x0004  /* be quiet about it */
 +#define REFRESH_IGNORE_MISSING        0x0008  /* ignore non-existent */
 +extern int refresh_cache(unsigned int flags);
 +
  struct cache_file {
        struct cache_file *next;
        char lockfile[PATH_MAX];
diff --combined git.c
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -50,9 -50,8 +50,11 @@@ static void handle_internal_command(in
                { "count-objects", cmd_count_objects },
                { "diff", cmd_diff },
                { "grep", cmd_grep },
+               { "rm", cmd_rm },
+               { "add", cmd_add },
 +              { "rev-list", cmd_rev_list },
 +              { "init-db", cmd_init_db },
 +              { "check-ref-format", cmd_check_ref_format }
        };
        int i;
  
diff --combined read-cache.c
@@@ -4,26 -4,11 +4,26 @@@
   * Copyright (C) Linus Torvalds, 2005
   */
  #include "cache.h"
 +#include "cache-tree.h"
 +
 +/* Index extensions.
 + *
 + * The first letter should be 'A'..'Z' for extensions that are not
 + * necessary for a correct operation (i.e. optimization data).
 + * When new extensions are added that _needs_ to be understood in
 + * order to correctly interpret the index file, pick character that
 + * is outside the range, to cause the reader to abort.
 + */
 +
 +#define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) )
 +#define CACHE_EXT_TREE 0x54524545     /* "TREE" */
  
  struct cache_entry **active_cache = NULL;
  static time_t index_file_timestamp;
  unsigned int active_nr = 0, active_alloc = 0, active_cache_changed = 0;
  
 +struct cache_tree *active_cache_tree = NULL;
 +
  /*
   * This only updates the "non-critical" parts of the directory
   * cache, ie the parts that aren't tracked by GIT, and only used
@@@ -347,6 -332,70 +347,70 @@@ int ce_path_match(const struct cache_en
  }
  
  /*
+  * We fundamentally don't like some paths: we don't want
+  * dot or dot-dot anywhere, and for obvious reasons don't
+  * want to recurse into ".git" either.
+  *
+  * Also, we don't want double slashes or slashes at the
+  * end that can make pathnames ambiguous.
+  */
+ static int verify_dotfile(const char *rest)
+ {
+       /*
+        * The first character was '.', but that
+        * has already been discarded, we now test
+        * the rest.
+        */
+       switch (*rest) {
+       /* "." is not allowed */
+       case '\0': case '/':
+               return 0;
+       /*
+        * ".git" followed by  NUL or slash is bad. This
+        * shares the path end test with the ".." case.
+        */
+       case 'g':
+               if (rest[1] != 'i')
+                       break;
+               if (rest[2] != 't')
+                       break;
+               rest += 2;
+       /* fallthrough */
+       case '.':
+               if (rest[1] == '\0' || rest[1] == '/')
+                       return 0;
+       }
+       return 1;
+ }
+ int verify_path(const char *path)
+ {
+       char c;
+       goto inside;
+       for (;;) {
+               if (!c)
+                       return 1;
+               if (c == '/') {
+ inside:
+                       c = *path++;
+                       switch (c) {
+                       default:
+                               continue;
+                       case '/': case '\0':
+                               break;
+                       case '.':
+                               if (verify_dotfile(path))
+                                       continue;
+                       }
+                       return 0;
+               }
+               c = *path++;
+       }
+ }
+ /*
   * Do we have another file that has the beginning components being a
   * proper superset of the name we're trying to add?
   */
@@@ -487,6 -536,8 +551,8 @@@ int add_cache_entry(struct cache_entry 
  
        if (!ok_to_add)
                return -1;
+       if (!verify_path(ce->name))
+               return -1;
  
        if (!skip_df_check &&
            check_file_directory_conflict(ce, pos, ok_to_replace)) {
        return 0;
  }
  
 +/* Three functions to allow overloaded pointer return; see linux/err.h */
 +static inline void *ERR_PTR(long error)
 +{
 +      return (void *) error;
 +}
 +
 +static inline long PTR_ERR(const void *ptr)
 +{
 +      return (long) ptr;
 +}
 +
 +static inline long IS_ERR(const void *ptr)
 +{
 +      return (unsigned long)ptr > (unsigned long)-1000L;
 +}
 +
 +/*
 + * "refresh" does not calculate a new sha1 file or bring the
 + * cache up-to-date for mode/content changes. But what it
 + * _does_ do is to "re-match" the stat information of a file
 + * with the cache, so that you can refresh the cache for a
 + * file that hasn't been changed but where the stat entry is
 + * out of date.
 + *
 + * For example, you'd want to do this after doing a "git-read-tree",
 + * to link up the stat cache details with the proper files.
 + */
 +static struct cache_entry *refresh_entry(struct cache_entry *ce, int really)
 +{
 +      struct stat st;
 +      struct cache_entry *updated;
 +      int changed, size;
 +
 +      if (lstat(ce->name, &st) < 0)
 +              return ERR_PTR(-errno);
 +
 +      changed = ce_match_stat(ce, &st, really);
 +      if (!changed) {
 +              if (really && assume_unchanged &&
 +                  !(ce->ce_flags & htons(CE_VALID)))
 +                      ; /* mark this one VALID again */
 +              else
 +                      return NULL;
 +      }
 +
 +      if (ce_modified(ce, &st, really))
 +              return ERR_PTR(-EINVAL);
 +
 +      size = ce_size(ce);
 +      updated = xmalloc(size);
 +      memcpy(updated, ce, size);
 +      fill_stat_cache_info(updated, &st);
 +
 +      /* In this case, if really is not set, we should leave
 +       * CE_VALID bit alone.  Otherwise, paths marked with
 +       * --no-assume-unchanged (i.e. things to be edited) will
 +       * reacquire CE_VALID bit automatically, which is not
 +       * really what we want.
 +       */
 +      if (!really && assume_unchanged && !(ce->ce_flags & htons(CE_VALID)))
 +              updated->ce_flags &= ~htons(CE_VALID);
 +
 +      return updated;
 +}
 +
 +int refresh_cache(unsigned int flags)
 +{
 +      int i;
 +      int has_errors = 0;
 +      int really = (flags & REFRESH_REALLY) != 0;
 +      int allow_unmerged = (flags & REFRESH_UNMERGED) != 0;
 +      int quiet = (flags & REFRESH_QUIET) != 0;
 +      int not_new = (flags & REFRESH_IGNORE_MISSING) != 0;
 +
 +      for (i = 0; i < active_nr; i++) {
 +              struct cache_entry *ce, *new;
 +              ce = active_cache[i];
 +              if (ce_stage(ce)) {
 +                      while ((i < active_nr) &&
 +                             ! strcmp(active_cache[i]->name, ce->name))
 +                              i++;
 +                      i--;
 +                      if (allow_unmerged)
 +                              continue;
 +                      printf("%s: needs merge\n", ce->name);
 +                      has_errors = 1;
 +                      continue;
 +              }
 +
 +              new = refresh_entry(ce, really);
 +              if (!new)
 +                      continue;
 +              if (IS_ERR(new)) {
 +                      if (not_new && PTR_ERR(new) == -ENOENT)
 +                              continue;
 +                      if (really && PTR_ERR(new) == -EINVAL) {
 +                              /* If we are doing --really-refresh that
 +                               * means the index is not valid anymore.
 +                               */
 +                              ce->ce_flags &= ~htons(CE_VALID);
 +                              active_cache_changed = 1;
 +                      }
 +                      if (quiet)
 +                              continue;
 +                      printf("%s: needs update\n", ce->name);
 +                      has_errors = 1;
 +                      continue;
 +              }
 +              active_cache_changed = 1;
 +              /* You can NOT just free active_cache[i] here, since it
 +               * might not be necessarily malloc()ed but can also come
 +               * from mmap(). */
 +              active_cache[i] = new;
 +      }
 +      return has_errors;
 +}
 +
  static int verify_hdr(struct cache_header *hdr, unsigned long size)
  {
        SHA_CTX c;
        return 0;
  }
  
 +static int read_index_extension(const char *ext, void *data, unsigned long sz)
 +{
 +      switch (CACHE_EXT(ext)) {
 +      case CACHE_EXT_TREE:
 +              active_cache_tree = cache_tree_read(data, sz);
 +              break;
 +      default:
 +              if (*ext < 'A' || 'Z' < *ext)
 +                      return error("index uses %.4s extension, which we do not understand",
 +                                   ext);
 +              fprintf(stderr, "ignoring %.4s extension\n", ext);
 +              break;
 +      }
 +      return 0;
 +}
 +
  int read_cache(void)
  {
        int fd, i;
                active_cache[i] = ce;
        }
        index_file_timestamp = st.st_mtime;
 +      while (offset <= size - 20 - 8) {
 +              /* After an array of active_nr index entries,
 +               * there can be arbitrary number of extended
 +               * sections, each of which is prefixed with
 +               * extension name (4-byte) and section length
 +               * in 4-byte network byte order.
 +               */
 +              unsigned long extsize;
 +              memcpy(&extsize, map + offset + 4, 4);
 +              extsize = ntohl(extsize);
 +              if (read_index_extension(map + offset,
 +                                       map + offset + 8, extsize) < 0)
 +                      goto unmap;
 +              offset += 8;
 +              offset += extsize;
 +      }
        return active_nr;
  
  unmap:
@@@ -759,17 -661,6 +825,17 @@@ static int ce_write(SHA_CTX *context, i
        return 0;
  }
  
 +static int write_index_ext_header(SHA_CTX *context, int fd,
 +                                unsigned long ext, unsigned long sz)
 +{
 +      ext = htonl(ext);
 +      sz = htonl(sz);
 +      if ((ce_write(context, fd, &ext, 4) < 0) ||
 +          (ce_write(context, fd, &sz, 4) < 0))
 +              return -1;
 +      return 0;
 +}
 +
  static int ce_flush(SHA_CTX *context, int fd)
  {
        unsigned int left = write_buffer_len;
@@@ -866,19 -757,5 +932,19 @@@ int write_cache(int newfd, struct cache
                if (ce_write(&c, newfd, ce, ce_size(ce)) < 0)
                        return -1;
        }
 +
 +      /* Write extension data here */
 +      if (active_cache_tree) {
 +              unsigned long sz;
 +              void *data = cache_tree_write(active_cache_tree, &sz);
 +              if (data &&
 +                  !write_index_ext_header(&c, newfd, CACHE_EXT_TREE, sz) &&
 +                  !ce_write(&c, newfd, data, sz))
 +                      ;
 +              else {
 +                      free(data);
 +                      return -1;
 +              }
 +      }
        return ce_flush(&c, newfd);
  }
diff --combined update-index.c
@@@ -6,7 -6,6 +6,7 @@@
  #include "cache.h"
  #include "strbuf.h"
  #include "quote.h"
 +#include "cache-tree.h"
  #include "tree-walk.h"
  
  /*
@@@ -19,6 -18,9 +19,6 @@@
  static int allow_add;
  static int allow_remove;
  static int allow_replace;
 -static int allow_unmerged; /* --refresh needing merge is not error */
 -static int not_new; /* --refresh not having working tree files is not error */
 -static int quiet; /* --refresh needing update is not error */
  static int info_only;
  static int force_remove;
  static int verbose;
@@@ -26,6 -28,23 +26,6 @@@ static int mark_valid_only = 0
  #define MARK_VALID 1
  #define UNMARK_VALID 2
  
 -
 -/* Three functions to allow overloaded pointer return; see linux/err.h */
 -static inline void *ERR_PTR(long error)
 -{
 -      return (void *) error;
 -}
 -
 -static inline long PTR_ERR(const void *ptr)
 -{
 -      return (long) ptr;
 -}
 -
 -static inline long IS_ERR(const void *ptr)
 -{
 -      return (unsigned long)ptr > (unsigned long)-1000L;
 -}
 -
  static void report(const char *fmt, ...)
  {
        va_list vp;
@@@ -52,7 -71,6 +52,7 @@@ static int mark_valid(const char *path
                        active_cache[pos]->ce_flags &= ~htons(CE_VALID);
                        break;
                }
 +              cache_tree_invalidate_path(active_cache_tree, path);
                active_cache_changed = 1;
                return 0;
        }
@@@ -66,12 -84,6 +66,12 @@@ static int add_file_to_cache(const cha
        struct stat st;
  
        status = lstat(path, &st);
 +
 +      /* We probably want to do this in remove_file_from_cache() and
 +       * add_cache_entry() instead...
 +       */
 +      cache_tree_invalidate_path(active_cache_tree, path);
 +
        if (status < 0 || S_ISDIR(st.st_mode)) {
                /* When we used to have "path" and now we want to add
                 * "path/file", we need a way to remove "path" before
        return 0;
  }
  
--/*
-  * We fundamentally don't like some paths: we don't want
-  * dot or dot-dot anywhere, and for obvious reasons don't
-  * want to recurse into ".git" either.
 - * "refresh" does not calculate a new sha1 file or bring the
 - * cache up-to-date for mode/content changes. But what it
 - * _does_ do is to "re-match" the stat information of a file
 - * with the cache, so that you can refresh the cache for a
 - * file that hasn't been changed but where the stat entry is
 - * out of date.
-- *
-  * Also, we don't want double slashes or slashes at the
-  * end that can make pathnames ambiguous.
 - * For example, you'd want to do this after doing a "git-read-tree",
 - * to link up the stat cache details with the proper files.
-- */
- static int verify_dotfile(const char *rest)
 -static struct cache_entry *refresh_entry(struct cache_entry *ce, int really)
--{
-       /*
-        * The first character was '.', but that
-        * has already been discarded, we now test
-        * the rest.
-        */
-       switch (*rest) {
-       /* "." is not allowed */
-       case '\0': case '/':
-               return 0;
 -      struct stat st;
 -      struct cache_entry *updated;
 -      int changed, size;
--
-       /*
-        * ".git" followed by  NUL or slash is bad. This
-        * shares the path end test with the ".." case.
-        */
-       case 'g':
-               if (rest[1] != 'i')
-                       break;
-               if (rest[2] != 't')
-                       break;
-               rest += 2;
-       /* fallthrough */
-       case '.':
-               if (rest[1] == '\0' || rest[1] == '/')
-                       return 0;
 -      if (lstat(ce->name, &st) < 0)
 -              return ERR_PTR(-errno);
 -
 -      changed = ce_match_stat(ce, &st, really);
 -      if (!changed) {
 -              if (really && assume_unchanged &&
 -                  !(ce->ce_flags & htons(CE_VALID)))
 -                      ; /* mark this one VALID again */
 -              else
 -                      return NULL;
--      }
-       return 1;
 -
 -      if (ce_modified(ce, &st, really))
 -              return ERR_PTR(-EINVAL);
 -
 -      size = ce_size(ce);
 -      updated = xmalloc(size);
 -      memcpy(updated, ce, size);
 -      fill_stat_cache_info(updated, &st);
 -
 -      /* In this case, if really is not set, we should leave
 -       * CE_VALID bit alone.  Otherwise, paths marked with
 -       * --no-assume-unchanged (i.e. things to be edited) will
 -       * reacquire CE_VALID bit automatically, which is not
 -       * really what we want.
 -       */
 -      if (!really && assume_unchanged && !(ce->ce_flags & htons(CE_VALID)))
 -              updated->ce_flags &= ~htons(CE_VALID);
 -
 -      return updated;
--}
--
- static int verify_path(const char *path)
 -static int refresh_cache(int really)
--{
-       char c;
 -      int i;
 -      int has_errors = 0;
--
-       goto inside;
-       for (;;) {
-               if (!c)
-                       return 1;
-               if (c == '/') {
- inside:
-                       c = *path++;
-                       switch (c) {
-                       default:
 -      for (i = 0; i < active_nr; i++) {
 -              struct cache_entry *ce, *new;
 -              ce = active_cache[i];
 -              if (ce_stage(ce)) {
 -                      while ((i < active_nr) &&
 -                             ! strcmp(active_cache[i]->name, ce->name))
 -                              i++;
 -                      i--;
 -                      if (allow_unmerged)
--                              continue;
-                       case '/': case '\0':
-                               break;
-                       case '.':
-                               if (verify_dotfile(path))
-                                       continue;
 -                      printf("%s: needs merge\n", ce->name);
 -                      has_errors = 1;
 -                      continue;
 -              }
 -
 -              new = refresh_entry(ce, really);
 -              if (!new)
 -                      continue;
 -              if (IS_ERR(new)) {
 -                      if (not_new && PTR_ERR(new) == -ENOENT)
 -                              continue;
 -                      if (really && PTR_ERR(new) == -EINVAL) {
 -                              /* If we are doing --really-refresh that
 -                               * means the index is not valid anymore.
 -                               */
 -                              ce->ce_flags &= ~htons(CE_VALID);
 -                              active_cache_changed = 1;
--                      }
-                       return 0;
 -                      if (quiet)
 -                              continue;
 -                      printf("%s: needs update\n", ce->name);
 -                      has_errors = 1;
 -                      continue;
--              }
-               c = *path++;
 -              active_cache_changed = 1;
 -              /* You can NOT just free active_cache[i] here, since it
 -               * might not be necessarily malloc()ed but can also come
 -               * from mmap(). */
 -              active_cache[i] = new;
--      }
 -      return has_errors;
--}
--
  static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
                         const char *path, int stage)
  {
                return error("%s: cannot add to the index - missing --add option?",
                             path);
        report("add '%s'", path);
 +      cache_tree_invalidate_path(active_cache_tree, path);
        return 0;
  }
  
@@@ -242,7 -286,6 +178,7 @@@ static void chmod_path(int flip, const 
        default:
                goto fail;
        }
 +      cache_tree_invalidate_path(active_cache_tree, path);
        active_cache_changed = 1;
        report("chmod %cx '%s'", flip, path);
        return;
@@@ -264,7 -307,6 +200,7 @@@ static void update_one(const char *path
                        die("Unable to mark file %s", path);
                goto free_return;
        }
 +      cache_tree_invalidate_path(active_cache_tree, path);
  
        if (force_remove) {
                if (remove_file_from_cache(p))
@@@ -343,7 -385,6 +279,7 @@@ static void read_index_info(int line_te
                                free(path_name);
                        continue;
                }
 +              cache_tree_invalidate_path(active_cache_tree, path_name);
  
                if (!mode) {
                        /* mode == 0 means there is no such path -- remove */
@@@ -450,7 -491,6 +386,7 @@@ static int unresolve_one(const char *pa
                goto free_return;
        }
  
 +      cache_tree_invalidate_path(active_cache_tree, path);
        remove_file_from_cache(path);
        if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) {
                error("%s: cannot add our version to the index.", path);
@@@ -549,7 -589,6 +485,7 @@@ int main(int argc, const char **argv
        const char *prefix = setup_git_directory();
        int prefix_length = prefix ? strlen(prefix) : 0;
        char set_executable_bit = 0;
 +      unsigned int refresh_flags = 0;
  
        git_config(git_default_config);
  
                                continue;
                        }
                        if (!strcmp(path, "-q")) {
 -                              quiet = 1;
 +                              refresh_flags |= REFRESH_QUIET;
                                continue;
                        }
                        if (!strcmp(path, "--add")) {
                                continue;
                        }
                        if (!strcmp(path, "--unmerged")) {
 -                              allow_unmerged = 1;
 +                              refresh_flags |= REFRESH_UNMERGED;
                                continue;
                        }
                        if (!strcmp(path, "--refresh")) {
 -                              has_errors |= refresh_cache(0);
 +                              has_errors |= refresh_cache(refresh_flags);
                                continue;
                        }
                        if (!strcmp(path, "--really-refresh")) {
 -                              has_errors |= refresh_cache(1);
 +                              has_errors |= refresh_cache(REFRESH_REALLY | refresh_flags);
                                continue;
                        }
                        if (!strcmp(path, "--cacheinfo")) {
                                goto finish;
                        }
                        if (!strcmp(path, "--ignore-missing")) {
 -                              not_new = 1;
 +                              refresh_flags |= REFRESH_IGNORE_MISSING;
                                continue;
                        }
                        if (!strcmp(path, "--verbose")) {