Merge branch 'jn/add-2.0-u-A-sans-pathspec'
authorJunio C Hamano <gitster@pobox.com>
Fri, 7 Mar 2014 23:14:01 +0000 (15:14 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 7 Mar 2014 23:14:02 +0000 (15:14 -0800)
"git add -u" and "git add -A" without any pathspec is a tree-wide
operation now, even when they are run in a subdirectory of the
working tree.

1  2 
builtin/add.c
cache.h
t/t2200-add-update.sh
diff --combined builtin/add.c
index 672adc01ffc07fa97c305f9110cfa3995658b922,61f3cb49ffd512e70181843ae2684b4e8578db0c..de79f35efcb7fff541557f6d0d02ed768c602fbf
@@@ -26,55 -26,10 +26,10 @@@ static int take_worktree_changes
  struct update_callback_data {
        int flags;
        int add_errors;
-       const char *implicit_dot;
-       size_t implicit_dot_len;
        /* only needed for 2.0 transition preparation */
        int warn_add_would_remove;
  };
  
- static const char *option_with_implicit_dot;
- static const char *short_option_with_implicit_dot;
- static void warn_pathless_add(void)
- {
-       static int shown;
-       assert(option_with_implicit_dot && short_option_with_implicit_dot);
-       if (shown)
-               return;
-       shown = 1;
-       /*
-        * To be consistent with "git add -p" and most Git
-        * commands, we should default to being tree-wide, but
-        * this is not the original behavior and can't be
-        * changed until users trained themselves not to type
-        * "git add -u" or "git add -A". For now, we warn and
-        * keep the old behavior. Later, the behavior can be changed
-        * to tree-wide, keeping the warning for a while, and
-        * eventually we can drop the warning.
-        */
-       warning(_("The behavior of 'git add %s (or %s)' with no path argument from a\n"
-                 "subdirectory of the tree will change in Git 2.0 and should not be used anymore.\n"
-                 "To add content for the whole tree, run:\n"
-                 "\n"
-                 "  git add %s :/\n"
-                 "  (or git add %s :/)\n"
-                 "\n"
-                 "To restrict the command to the current directory, run:\n"
-                 "\n"
-                 "  git add %s .\n"
-                 "  (or git add %s .)\n"
-                 "\n"
-                 "With the current Git version, the command is restricted to "
-                 "the current directory.\n"
-                 ""),
-               option_with_implicit_dot, short_option_with_implicit_dot,
-               option_with_implicit_dot, short_option_with_implicit_dot,
-               option_with_implicit_dot, short_option_with_implicit_dot);
- }
  static int fix_unmerged_status(struct diff_filepair *p,
                               struct update_callback_data *data)
  {
@@@ -119,26 -74,10 +74,10 @@@ static void update_callback(struct diff
  {
        int i;
        struct update_callback_data *data = cbdata;
-       const char *implicit_dot = data->implicit_dot;
-       size_t implicit_dot_len = data->implicit_dot_len;
  
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
                const char *path = p->one->path;
-               /*
-                * Check if "git add -A" or "git add -u" was run from a
-                * subdirectory with a modified file outside that directory,
-                * and warn if so.
-                *
-                * "git add -u" will behave like "git add -u :/" instead of
-                * "git add -u ." in the future.  This warning prepares for
-                * that change.
-                */
-               if (implicit_dot &&
-                   strncmp_icase(path, implicit_dot, implicit_dot_len)) {
-                       warn_pathless_add();
-                       continue;
-               }
                switch (fix_unmerged_status(p, data)) {
                default:
                        die(_("unexpected diff status %c"), p->status);
        }
  }
  
 -static void update_files_in_cache(const char *prefix, const char **pathspec,
 +static void update_files_in_cache(const char *prefix,
 +                                const struct pathspec *pathspec,
                                  struct update_callback_data *data)
  {
        struct rev_info rev;
  
        init_revisions(&rev, prefix);
        setup_revisions(0, NULL, &rev, NULL);
 -      init_pathspec(&rev.prune_data, pathspec);
 +      if (pathspec)
 +              copy_pathspec(&rev.prune_data, pathspec);
        rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = update_callback;
        rev.diffopt.format_callback_data = data;
        run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
  }
  
 -int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
 +int add_files_to_cache(const char *prefix,
 +                     const struct pathspec *pathspec, int flags)
  {
        struct update_callback_data data;
  
        return !!data.add_errors;
  }
  
- #define WARN_IMPLICIT_DOT (1u << 0)
- static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec,
-                            int prefix, unsigned flag)
 -static char *prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
++static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec, int prefix)
  {
        char *seen;
 -      int i, specs;
 +      int i;
        struct dir_entry **src, **dst;
  
 -      for (specs = 0; pathspec[specs];  specs++)
 -              /* nothing */;
 -      seen = xcalloc(specs, 1);
 +      seen = xcalloc(pathspec->nr, 1);
  
        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))
 +              if (dir_path_match(entry, pathspec, prefix, seen))
                        *dst++ = entry;
-               else if (flag & WARN_IMPLICIT_DOT)
-                       /*
-                        * "git add -A" was run from a subdirectory with a
-                        * new file outside that directory.
-                        *
-                        * "git add -A" will behave like "git add -A :/"
-                        * instead of "git add -A ." in the future.
-                        * Warn about the coming behavior change.
-                        */
-                       warn_pathless_add();
        }
        dir->nr = dst - dir->entries;
 -      add_pathspec_matches_against_index(pathspec, seen, specs);
 +      add_pathspec_matches_against_index(pathspec, seen);
        return seen;
  }
  
 -/*
 - * Checks the index to see whether any path in pathspec refers to
 - * something inside a submodule.  If so, dies with an error message.
 - */
 -static void treat_gitlinks(const char **pathspec)
 -{
 -      int i;
 -
 -      if (!pathspec || !*pathspec)
 -              return;
 -
 -      for (i = 0; pathspec[i]; i++)
 -              pathspec[i] = check_path_for_gitlink(pathspec[i]);
 -}
 -
 -static void refresh(int verbose, const char **pathspec)
 +static void refresh(int verbose, const struct pathspec *pathspec)
  {
        char *seen;
 -      int i, specs;
 +      int i;
  
 -      for (specs = 0; pathspec[specs];  specs++)
 -              /* nothing */;
 -      seen = xcalloc(specs, 1);
 +      seen = xcalloc(pathspec->nr, 1);
        refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET,
                      pathspec, seen, _("Unstaged changes after refreshing the index:"));
 -      for (i = 0; i < specs; i++) {
 +      for (i = 0; i < pathspec->nr; i++) {
                if (!seen[i])
 -                      die(_("pathspec '%s' did not match any files"), pathspec[i]);
 +                      die(_("pathspec '%s' did not match any files"),
 +                          pathspec->items[i].match);
        }
          free(seen);
  }
  
 -/*
 - * Normalizes argv relative to prefix, via get_pathspec(), and then
 - * runs die_if_path_beyond_symlink() on each path in the normalized
 - * list.
 - */
 -static const char **validate_pathspec(const char **argv, const char *prefix)
 -{
 -      const char **pathspec = get_pathspec(prefix, argv);
 -
 -      if (pathspec) {
 -              const char **p;
 -              for (p = pathspec; *p; p++) {
 -                      die_if_path_beyond_symlink(*p, prefix);
 -              }
 -      }
 -
 -      return pathspec;
 -}
 -
  int run_add_interactive(const char *revision, const char *patch_mode,
 -                      const char **pathspec)
 +                      const struct pathspec *pathspec)
  {
 -      int status, ac, pc = 0;
 +      int status, ac, i;
        const char **args;
  
 -      if (pathspec)
 -              while (pathspec[pc])
 -                      pc++;
 -
 -      args = xcalloc(sizeof(const char *), (pc + 5));
 +      args = xcalloc(sizeof(const char *), (pathspec->nr + 6));
        ac = 0;
        args[ac++] = "add--interactive";
        if (patch_mode)
        if (revision)
                args[ac++] = revision;
        args[ac++] = "--";
 -      if (pc) {
 -              memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc);
 -              ac += pc;
 -      }
 -      args[ac] = NULL;
 +      for (i = 0; i < pathspec->nr; i++)
 +              /* pass original pathspec, to be re-parsed */
 +              args[ac++] = pathspec->items[i].original;
  
        status = run_command_v_opt(args, RUN_GIT_CMD);
        free(args);
  
  int interactive_add(int argc, const char **argv, const char *prefix, int patch)
  {
 -      const char **pathspec = NULL;
 +      struct pathspec pathspec;
  
 -      if (argc) {
 -              pathspec = validate_pathspec(argv, prefix);
 -              if (!pathspec)
 -                      return -1;
 -      }
 +      parse_pathspec(&pathspec, 0,
 +                     PATHSPEC_PREFER_FULL |
 +                     PATHSPEC_SYMLINK_LEADING_PATH |
 +                     PATHSPEC_PREFIX_ORIGIN,
 +                     prefix, argv);
  
        return run_add_interactive(NULL,
                                   patch ? "--patch" : NULL,
 -                                 pathspec);
 +                                 &pathspec);
  }
  
  static int edit_patch(int argc, const char **argv, const char *prefix)
        git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
  
        if (read_cache() < 0)
 -              die (_("Could not read the index"));
 +              die(_("Could not read the index"));
  
        init_revisions(&rev, prefix);
        rev.diffopt.context = 7;
  
        argc = setup_revisions(argc, argv, &rev, NULL);
        rev.diffopt.output_format = DIFF_FORMAT_PATCH;
 +      rev.diffopt.use_color = 0;
        DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES);
        out = open(file, O_CREAT | O_WRONLY, 0666);
        if (out < 0)
 -              die (_("Could not open '%s' for writing."), file);
 +              die(_("Could not open '%s' for writing."), file);
        rev.diffopt.file = xfdopen(out, "w");
        rev.diffopt.close_file = 1;
        if (run_diff_files(&rev, 0))
 -              die (_("Could not write patch"));
 +              die(_("Could not write patch"));
  
        launch_editor(file, NULL, NULL);
  
        child.git_cmd = 1;
        child.argv = apply_argv;
        if (run_command(&child))
 -              die (_("Could not apply '%s'"), file);
 +              die(_("Could not apply '%s'"), file);
  
        unlink(file);
        free(file);
@@@ -405,13 -372,12 +332,12 @@@ int cmd_add(int argc, const char **argv
  {
        int exit_status = 0;
        int newfd;
 -      const char **pathspec;
 +      struct pathspec pathspec;
        struct dir_struct dir;
        int flags;
        int add_new_files;
        int require_pathspec;
        char *seen = NULL;
-       int implicit_dot = 0;
        struct update_callback_data update_data;
  
        git_config(add_config, NULL);
  
        if (!show_only && ignore_missing)
                die(_("Option --ignore-missing can only be used together with --dry-run"));
-       if (addremove) {
-               option_with_implicit_dot = "--all";
-               short_option_with_implicit_dot = "-A";
-       }
-       if (take_worktree_changes) {
-               option_with_implicit_dot = "--update";
-               short_option_with_implicit_dot = "-u";
-       }
-       if (option_with_implicit_dot && !argc) {
-               static const char *here[2] = { ".", NULL };
+       if ((addremove || take_worktree_changes) && !argc) {
+               static const char *whole[2] = { ":/", NULL };
                argc = 1;
-               argv = here;
-               implicit_dot = 1;
+               argv = whole;
        }
  
        add_new_files = !take_worktree_changes && !refresh_only;
                 (intent_to_add ? ADD_CACHE_INTENT : 0) |
                 (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |
                 (!(addremove || take_worktree_changes)
-                 ? ADD_CACHE_IGNORE_REMOVAL : 0)) |
-                (implicit_dot ? ADD_CACHE_IMPLICIT_DOT : 0);
+                 ? ADD_CACHE_IGNORE_REMOVAL : 0));
  
        if (require_pathspec && argc == 0) {
                fprintf(stderr, _("Nothing specified, nothing added.\n"));
                fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
                return 0;
        }
 -      pathspec = validate_pathspec(argv, prefix);
  
        if (read_cache() < 0)
                die(_("index file corrupt"));
 -      treat_gitlinks(pathspec);
 +
 +      /*
 +       * Check the "pathspec '%s' did not match any files" block
 +       * below before enabling new magic.
 +       */
 +      parse_pathspec(&pathspec, 0,
 +                     PATHSPEC_PREFER_FULL |
 +                     PATHSPEC_SYMLINK_LEADING_PATH |
 +                     PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE,
 +                     prefix, argv);
  
        if (add_new_files) {
                int baselen;
 +              struct pathspec empty_pathspec;
  
                /* Set up the default git porcelain excludes */
                memset(&dir, 0, sizeof(dir));
                        setup_standard_excludes(&dir);
                }
  
 +              memset(&empty_pathspec, 0, sizeof(empty_pathspec));
                /* This picks up the paths that are not tracked */
-               baselen = fill_directory(&dir, implicit_dot ? &empty_pathspec : &pathspec);
 -              baselen = fill_directory(&dir, pathspec);
 -              if (pathspec)
 -                      seen = prune_directory(&dir, pathspec, baselen);
++              baselen = fill_directory(&dir, &pathspec);
 +              if (pathspec.nr)
-                       seen = prune_directory(&dir, &pathspec, baselen,
-                                       implicit_dot ? WARN_IMPLICIT_DOT : 0);
++                      seen = prune_directory(&dir, &pathspec, baselen);
        }
  
        if (refresh_only) {
 -              refresh(verbose, pathspec);
 +              refresh(verbose, &pathspec);
                goto finish;
        }
-       if (implicit_dot && prefix)
-               refresh_cache(REFRESH_QUIET);
  
 -      if (pathspec) {
 +      if (pathspec.nr) {
                int i;
  
                if (!seen)
 -                      seen = find_pathspecs_matching_against_index(pathspec);
 -              for (i = 0; pathspec[i]; i++) {
 -                      if (!seen[i] && pathspec[i][0]
 -                          && !file_exists(pathspec[i])) {
 +                      seen = find_pathspecs_matching_against_index(&pathspec);
 +
 +              /*
 +               * file_exists() assumes exact match
 +               */
 +              GUARD_PATHSPEC(&pathspec,
 +                             PATHSPEC_FROMTOP |
 +                             PATHSPEC_LITERAL |
 +                             PATHSPEC_GLOB |
 +                             PATHSPEC_ICASE |
 +                             PATHSPEC_EXCLUDE);
 +
 +              for (i = 0; i < pathspec.nr; i++) {
 +                      const char *path = pathspec.items[i].match;
 +                      if (pathspec.items[i].magic & PATHSPEC_EXCLUDE)
 +                              continue;
 +                      if (!seen[i] && path[0] &&
 +                          ((pathspec.items[i].magic &
 +                            (PATHSPEC_GLOB | PATHSPEC_ICASE)) ||
 +                           !file_exists(path))) {
                                if (ignore_missing) {
                                        int dtype = DT_UNKNOWN;
 -                                      if (is_excluded(&dir, pathspec[i], &dtype))
 -                                              dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
 +                                      if (is_excluded(&dir, path, &dtype))
 +                                              dir_add_ignored(&dir, path, pathspec.items[i].len);
                                } else
                                        die(_("pathspec '%s' did not match any files"),
 -                                          pathspec[i]);
 +                                          pathspec.items[i].original);
                        }
                }
                free(seen);
  
        plug_bulk_checkin();
  
-       if ((flags & ADD_CACHE_IMPLICIT_DOT) && prefix) {
-               /*
-                * Check for modified files throughout the worktree so
-                * update_callback has a chance to warn about changes
-                * outside the cwd.
-                */
-               update_data.implicit_dot = prefix;
-               update_data.implicit_dot_len = strlen(prefix);
-               free_pathspec(&pathspec);
-               memset(&pathspec, 0, sizeof(pathspec));
-       }
-       update_data.flags = flags & ~ADD_CACHE_IMPLICIT_DOT;
+       update_data.flags = flags;
 -      update_files_in_cache(prefix, pathspec, &update_data);
 +      update_files_in_cache(prefix, &pathspec, &update_data);
  
        exit_status |= !!update_data.add_errors;
        if (add_new_files)
  
        unplug_bulk_checkin();
  
 - finish:
 +finish:
        if (active_cache_changed) {
                if (write_cache(newfd, active_cache, active_nr) ||
                    commit_locked_index(&lock_file))
diff --combined cache.h
index b7d82e5d52bb09aed15e43d34e89871e3735df5c,99acf80d2fbdc38396c3c97c61e2c3967c24c864..4c0043113cf1dd8d5d0aa1ae37835a432f3e438d
+++ b/cache.h
@@@ -3,7 -3,7 +3,7 @@@
  
  #include "git-compat-util.h"
  #include "strbuf.h"
 -#include "hash.h"
 +#include "hashmap.h"
  #include "advice.h"
  #include "gettext.h"
  #include "convert.h"
@@@ -101,9 -101,9 +101,9 @@@ unsigned long git_deflate_bound(git_zst
  
  #define CACHE_SIGNATURE 0x44495243    /* "DIRC" */
  struct cache_header {
 -      unsigned int hdr_signature;
 -      unsigned int hdr_version;
 -      unsigned int hdr_entries;
 +      uint32_t hdr_signature;
 +      uint32_t hdr_version;
 +      uint32_t hdr_entries;
  };
  
  #define INDEX_FORMAT_LB 2
   * check it for equality in the 32 bits we save.
   */
  struct cache_time {
 -      unsigned int sec;
 -      unsigned int nsec;
 +      uint32_t sec;
 +      uint32_t nsec;
 +};
 +
 +struct stat_data {
 +      struct cache_time sd_ctime;
 +      struct cache_time sd_mtime;
 +      unsigned int sd_dev;
 +      unsigned int sd_ino;
 +      unsigned int sd_uid;
 +      unsigned int sd_gid;
 +      unsigned int sd_size;
  };
  
  struct cache_entry {
 -      struct cache_time ce_ctime;
 -      struct cache_time ce_mtime;
 -      unsigned int ce_dev;
 -      unsigned int ce_ino;
 +      struct hashmap_entry ent;
 +      struct stat_data ce_stat_data;
        unsigned int ce_mode;
 -      unsigned int ce_uid;
 -      unsigned int ce_gid;
 -      unsigned int ce_size;
        unsigned int ce_flags;
        unsigned int ce_namelen;
        unsigned char sha1[20];
 -      struct cache_entry *next;
        char name[FLEX_ARRAY]; /* more */
  };
  
  #define CE_ADDED             (1 << 19)
  
  #define CE_HASHED            (1 << 20)
 -#define CE_UNHASHED          (1 << 21)
  #define CE_WT_REMOVE         (1 << 22) /* remove in work directory */
  #define CE_CONFLICTED        (1 << 23)
  
  #error "CE_EXTENDED_FLAGS out of range"
  #endif
  
 +struct pathspec;
 +
  /*
   * Copy the sha1 and stat state of a cache entry from one to
   * another. But we never change the name, or the hash state!
   */
 -#define CE_STATE_MASK (CE_HASHED | CE_UNHASHED)
 -static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry *src)
 +static inline void copy_cache_entry(struct cache_entry *dst,
 +                                  const struct cache_entry *src)
  {
 -      unsigned int state = dst->ce_flags & CE_STATE_MASK;
 +      unsigned int state = dst->ce_flags & CE_HASHED;
  
        /* Don't copy hash chain and name */
 -      memcpy(dst, src, offsetof(struct cache_entry, next));
 +      memcpy(&dst->ce_stat_data, &src->ce_stat_data,
 +                      offsetof(struct cache_entry, name) -
 +                      offsetof(struct cache_entry, ce_stat_data));
  
        /* Restore the hash state */
 -      dst->ce_flags = (dst->ce_flags & ~CE_STATE_MASK) | state;
 +      dst->ce_flags = (dst->ce_flags & ~CE_HASHED) | state;
  }
  
  static inline unsigned create_ce_flags(unsigned stage)
@@@ -229,8 -222,7 +229,8 @@@ static inline unsigned int create_ce_mo
                return S_IFGITLINK;
        return S_IFREG | ce_permissions(mode);
  }
 -static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)
 +static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,
 +                                           unsigned int mode)
  {
        extern int trust_executable_bit, has_symlinks;
        if (!has_symlinks && S_ISREG(mode) &&
@@@ -277,8 -269,8 +277,8 @@@ struct index_state 
        struct cache_time timestamp;
        unsigned name_hash_initialized : 1,
                 initialized : 1;
 -      struct hash_table name_hash;
 -      struct hash_table dir_hash;
 +      struct hashmap name_hash;
 +      struct hashmap dir_hash;
  };
  
  extern struct index_state the_index;
@@@ -314,8 -306,7 +314,8 @@@ extern void free_name_hash(struct index
  #define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL, NULL)
  #define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options))
  #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
 -#define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase))
 +#define cache_dir_exists(name, namelen) index_dir_exists(&the_index, (name), (namelen))
 +#define cache_file_exists(name, namelen, igncase) index_file_exists(&the_index, (name), (namelen), (igncase))
  #define cache_name_is_other(name, namelen) index_name_is_other(&the_index, (name), (namelen))
  #define resolve_undo_clear() resolve_undo_clear_index(&the_index)
  #define unmerge_cache_entry_at(at) unmerge_index_entry_at(&the_index, at)
@@@ -353,7 -344,6 +353,7 @@@ static inline enum object_type object_t
  #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
  #define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
  #define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE"
 +#define GIT_SHALLOW_FILE_ENVIRONMENT "GIT_SHALLOW_FILE"
  #define TEMPLATE_DIR_ENVIRONMENT "GIT_TEMPLATE_DIR"
  #define CONFIG_ENVIRONMENT "GIT_CONFIG"
  #define CONFIG_DATA_ENVIRONMENT "GIT_CONFIG_PARAMETERS"
  #define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
  #define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
  #define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS"
 +#define GIT_GLOB_PATHSPECS_ENVIRONMENT "GIT_GLOB_PATHSPECS"
 +#define GIT_NOGLOB_PATHSPECS_ENVIRONMENT "GIT_NOGLOB_PATHSPECS"
 +#define GIT_ICASE_PATHSPECS_ENVIRONMENT "GIT_ICASE_PATHSPECS"
  
  /*
   * This environment variable is expected to contain a boolean indicating
@@@ -398,6 -385,7 +398,6 @@@ extern int is_bare_repository(void)
  extern int is_inside_git_dir(void);
  extern char *git_work_tree_cfg;
  extern int is_inside_work_tree(void);
 -extern int have_git_dir(void);
  extern const char *get_git_dir(void);
  extern int is_git_directory(const char *path);
  extern char *get_object_directory(void);
@@@ -418,7 -406,6 +418,7 @@@ extern void setup_work_tree(void)
  extern const char *setup_git_directory_gently(int *);
  extern const char *setup_git_directory(void);
  extern char *prefix_path(const char *prefix, int len, const char *path);
 +extern char *prefix_path_gently(const char *prefix, int len, int *remaining, const char *path);
  extern const char *prefix_filename(const char *prefix, int len, const char *path);
  extern int check_filename(const char *prefix, const char *name);
  extern void verify_filename(const char *prefix,
@@@ -432,9 -419,6 +432,9 @@@ extern int path_inside_repo(const char 
  extern int set_git_dir_init(const char *git_dir, const char *real_git_dir, int);
  extern int init_db(const char *template_dir, unsigned int flags);
  
 +extern void sanitize_stdfds(void);
 +extern int daemonize(void);
 +
  #define alloc_nr(x) (((x)+16)*3/2)
  
  /*
  
  /* Initialize and use the cache information */
  extern int read_index(struct index_state *);
 -extern int read_index_preload(struct index_state *, const char **pathspec);
 +extern int read_index_preload(struct index_state *, const struct pathspec *pathspec);
  extern int read_index_from(struct index_state *, const char *path);
  extern int is_index_unborn(struct index_state *);
  extern int read_index_unmerged(struct index_state *);
@@@ -465,8 -449,7 +465,8 @@@ extern int write_index(struct index_sta
  extern int discard_index(struct index_state *);
  extern int unmerged_index(const struct index_state *);
  extern int verify_path(const char *path);
 -extern struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int igncase);
 +extern struct cache_entry *index_dir_exists(struct index_state *istate, const char *name, int namelen);
 +extern struct cache_entry *index_file_exists(struct index_state *istate, const char *name, int namelen, int igncase);
  extern int index_name_pos(const struct index_state *, 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 */
@@@ -483,12 -466,10 +483,11 @@@ extern int remove_file_from_index(struc
  #define ADD_CACHE_IGNORE_ERRORS       4
  #define ADD_CACHE_IGNORE_REMOVAL 8
  #define ADD_CACHE_INTENT 16
- #define ADD_CACHE_IMPLICIT_DOT 32     /* internal to "git add -u/-A" */
  extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags);
  extern int add_file_to_index(struct index_state *, const char *path, int flags);
 -extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
 -extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
 +extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, unsigned int refresh_options);
 +extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
 +extern void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);
  extern int index_name_is_other(const struct index_state *, const char *, int);
  extern void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *);
  
  #define CE_MATCH_RACY_IS_DIRTY                02
  /* do stat comparison even if CE_SKIP_WORKTREE is true */
  #define CE_MATCH_IGNORE_SKIP_WORKTREE 04
 -extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 -extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 -
 -#define PATHSPEC_ONESTAR 1    /* the pathspec pattern sastisfies GFNM_ONESTAR */
 -
 -struct pathspec {
 -      const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
 -      int nr;
 -      unsigned int has_wildcard:1;
 -      unsigned int recursive:1;
 -      int max_depth;
 -      struct pathspec_item {
 -              const char *match;
 -              int len;
 -              int nowildcard_len;
 -              int flags;
 -      } *items;
 -};
 -
 -extern int init_pathspec(struct pathspec *, const char **);
 -extern void free_pathspec(struct pathspec *);
 -extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
 -
 -extern int limit_pathspec_to_literal(void);
 +/* ignore non-existent files during stat update  */
 +#define CE_MATCH_IGNORE_MISSING               0x08
 +/* enable stat refresh */
 +#define CE_MATCH_REFRESH              0x10
 +extern int ie_match_stat(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
 +extern int ie_modified(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
  
  #define HASH_WRITE_OBJECT 1
  #define HASH_FORMAT_CHECK 2
  extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
  extern int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags);
 +
 +/*
 + * Record to sd the data from st that we use to check whether a file
 + * might have changed.
 + */
 +extern void fill_stat_data(struct stat_data *sd, struct stat *st);
 +
 +/*
 + * Return 0 if st is consistent with a file not having been changed
 + * since sd was filled.  If there are differences, return a
 + * combination of MTIME_CHANGED, CTIME_CHANGED, OWNER_CHANGED,
 + * INODE_CHANGED, and DATA_CHANGED.
 + */
 +extern int match_stat_data(const struct stat_data *sd, struct stat *st);
 +
  extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
  
  #define REFRESH_REALLY                0x0001  /* ignore_valid */
  #define REFRESH_IGNORE_MISSING        0x0008  /* ignore non-existent */
  #define REFRESH_IGNORE_SUBMODULES     0x0010  /* ignore submodules */
  #define REFRESH_IN_PORCELAIN  0x0020  /* user friendly output, not "needs update" */
 -extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, const char *header_msg);
 +extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg);
  
  struct lock_file {
        struct lock_file *next;
@@@ -569,7 -553,6 +568,7 @@@ extern int assume_unchanged
  extern int prefer_symlink_refs;
  extern int log_all_ref_updates;
  extern int warn_ambiguous_refs;
 +extern int warn_on_object_refname_ambiguity;
  extern int shared_repository;
  extern const char *apply_default_whitespace;
  extern const char *apply_default_ignorewhitespace;
@@@ -739,29 -722,8 +738,29 @@@ enum sharedrepo 
  };
  int git_config_perm(const char *var, const char *value);
  int adjust_shared_perm(const char *path);
 -int safe_create_leading_directories(char *path);
 -int safe_create_leading_directories_const(const char *path);
 +
 +/*
 + * Create the directory containing the named path, using care to be
 + * somewhat safe against races.  Return one of the scld_error values
 + * to indicate success/failure.
 + *
 + * SCLD_VANISHED indicates that one of the ancestor directories of the
 + * path existed at one point during the function call and then
 + * suddenly vanished, probably because another process pruned the
 + * directory while we were working.  To be robust against this kind of
 + * race, callers might want to try invoking the function again when it
 + * returns SCLD_VANISHED.
 + */
 +enum scld_error {
 +      SCLD_OK = 0,
 +      SCLD_FAILED = -1,
 +      SCLD_PERMS = -2,
 +      SCLD_EXISTS = -3,
 +      SCLD_VANISHED = -4
 +};
 +enum scld_error safe_create_leading_directories(char *path);
 +enum scld_error safe_create_leading_directories_const(const char *path);
 +
  int mkdir_in_gitdir(const char *path);
  extern void home_config_paths(char **global, char **xdg, char *file);
  extern char *expand_user_path(const char *path);
@@@ -774,9 -736,7 +773,9 @@@ int is_directory(const char *)
  const char *real_path(const char *path);
  const char *real_path_if_valid(const char *path);
  const char *absolute_path(const char *path);
 -const char *relative_path(const char *abs, const char *base);
 +const char *remove_leading_path(const char *in, const char *prefix);
 +const char *relative_path(const char *in, const char *prefix, struct strbuf *sb);
 +int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
  int normalize_path_copy(char *dst, const char *src);
  int longest_ancestor_length(const char *path, struct string_list *prefixes);
  char *strip_path_suffix(const char *path, const char *suffix);
@@@ -784,11 -744,11 +783,11 @@@ int daemon_avoid_alias(const char *path
  int offset_1st_component(const char *path);
  
  /* object replacement */
 -#define READ_SHA1_FILE_REPLACE 1
 +#define LOOKUP_REPLACE_OBJECT 1
  extern void *read_sha1_file_extended(const unsigned char *sha1, enum object_type *type, unsigned long *size, unsigned flag);
  static inline void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
  {
 -      return read_sha1_file_extended(sha1, type, size, READ_SHA1_FILE_REPLACE);
 +      return read_sha1_file_extended(sha1, type, size, LOOKUP_REPLACE_OBJECT);
  }
  extern const unsigned char *do_lookup_replace_object(const unsigned char *sha1);
  static inline const unsigned char *lookup_replace_object(const unsigned char *sha1)
                return sha1;
        return do_lookup_replace_object(sha1);
  }
 +static inline const unsigned char *lookup_replace_object_extended(const unsigned char *sha1, unsigned flag)
 +{
 +      if (!(flag & LOOKUP_REPLACE_OBJECT))
 +              return sha1;
 +      return lookup_replace_object(sha1);
 +}
  
  /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
  extern int sha1_object_info(const unsigned char *, unsigned long *);
@@@ -810,7 -764,6 +809,7 @@@ extern int hash_sha1_file(const void *b
  extern int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
  extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
  extern int force_object_loose(const unsigned char *sha1, time_t mtime);
 +extern int git_open_noatime(const char *name);
  extern void *map_sha1_file(const unsigned char *sha1, unsigned long *size);
  extern int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz);
  extern int parse_sha1_header(const char *hdr, unsigned long *sizep);
  /* global flag to enable extra checks when accessing packed objects */
  extern int do_check_packed_object_crc;
  
 -/* for development: log offset of pack access */
 -extern const char *log_pack_access;
 -
  extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
  
  extern int move_temp_to_file(const char *tmpfile, const char *filename);
@@@ -915,15 -871,12 +914,15 @@@ extern char *resolve_refdup(const char 
  
  extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
  extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
 -extern int interpret_branch_name(const char *str, struct strbuf *);
 +extern int interpret_branch_name(const char *str, int len, struct strbuf *);
  extern int get_sha1_mb(const char *str, unsigned char *sha1);
  
 -extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
 -extern const char *ref_rev_parse_rules[];
 -#define ref_fetch_rules ref_rev_parse_rules
 +/*
 + * Return true iff abbrev_name is a possible abbreviation for
 + * full_name according to the rules defined by ref_rev_parse_rules in
 + * refs.c.
 + */
 +extern int refname_match(const char *abbrev_name, const char *full_name);
  
  extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);
  extern int validate_headref(const char *ref);
@@@ -956,7 -909,6 +955,7 @@@ void show_date_relative(unsigned long t
                        struct strbuf *timebuf);
  int parse_date(const char *date, char *buf, int bufsize);
  int parse_date_basic(const char *date, unsigned long *timestamp, int *offset);
 +int parse_expiry_date(const char *date, unsigned long *timestamp);
  void datestamp(char *buf, int bufsize);
  #define approxidate(s) approxidate_careful((s), NULL)
  unsigned long approxidate_careful(const char *, int *);
@@@ -991,15 -943,6 +990,15 @@@ struct ident_split 
   */
  extern int split_ident_line(struct ident_split *, const char *, int);
  
 +/*
 + * Compare split idents for equality or strict ordering. Note that we
 + * compare only the ident part of the line, ignoring any timestamp.
 + *
 + * Because there are two fields, we must choose one as the primary key; we
 + * currently arbitrarily pick the email.
 + */
 +extern int ident_cmp(const struct ident_split *, const struct ident_split *);
 +
  struct checkout {
        const char *base_dir;
        int base_dir_len;
                 refresh_cache:1;
  };
  
 +#define TEMPORARY_FILENAME_LENGTH 25
  extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
  
  struct cache_def {
@@@ -1073,6 -1015,56 +1072,6 @@@ struct pack_entry 
        struct packed_git *p;
  };
  
 -struct ref {
 -      struct ref *next;
 -      unsigned char old_sha1[20];
 -      unsigned char new_sha1[20];
 -      char *symref;
 -      unsigned int
 -              force:1,
 -              forced_update:1,
 -              merge:1,
 -              deletion:1,
 -              matched:1;
 -      enum {
 -              REF_STATUS_NONE = 0,
 -              REF_STATUS_OK,
 -              REF_STATUS_REJECT_NONFASTFORWARD,
 -              REF_STATUS_REJECT_ALREADY_EXISTS,
 -              REF_STATUS_REJECT_NODELETE,
 -              REF_STATUS_REJECT_FETCH_FIRST,
 -              REF_STATUS_REJECT_NEEDS_FORCE,
 -              REF_STATUS_UPTODATE,
 -              REF_STATUS_REMOTE_REJECT,
 -              REF_STATUS_EXPECTING_REPORT
 -      } status;
 -      char *remote_status;
 -      struct ref *peer_ref; /* when renaming */
 -      char name[FLEX_ARRAY]; /* more */
 -};
 -
 -#define REF_NORMAL    (1u << 0)
 -#define REF_HEADS     (1u << 1)
 -#define REF_TAGS      (1u << 2)
 -
 -extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
 -
 -#define CONNECT_VERBOSE       (1u << 0)
 -extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
 -extern int finish_connect(struct child_process *conn);
 -extern int git_connection_is_socket(struct child_process *conn);
 -struct extra_have_objects {
 -      int nr, alloc;
 -      unsigned char (*array)[20];
 -};
 -extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
 -                                   struct ref **list, unsigned int flags,
 -                                   struct extra_have_objects *);
 -extern int server_supports(const char *feature);
 -extern int parse_feature_request(const char *features, const char *feature);
 -extern const char *server_feature_value(const char *feature, int *len_ret);
 -extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);
 -
  extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
  
  /* A hook for count-objects to report invalid files in pack directory */
@@@ -1105,10 -1097,7 +1104,10 @@@ extern int unpack_object_header(struct 
  
  struct object_info {
        /* Request */
 +      enum object_type *typep;
        unsigned long *sizep;
 +      unsigned long *disk_sizep;
 +      unsigned char *delta_base_sha1;
  
        /* Response */
        enum {
                } packed;
        } u;
  };
 -extern int sha1_object_info_extended(const unsigned char *, struct object_info *);
 +extern int sha1_object_info_extended(const unsigned char *, struct object_info *, unsigned flags);
  
  /* Dumb servers support */
  extern int update_server_info(int);
  typedef int (*config_fn_t)(const char *, const char *, void *);
  extern int git_default_config(const char *, const char *, void *);
  extern int git_config_from_file(config_fn_t fn, const char *, void *);
 +extern int git_config_from_buf(config_fn_t fn, const char *name,
 +                             const char *buf, size_t len, void *data);
  extern void git_config_push_parameter(const char *text);
  extern int git_config_from_parameters(config_fn_t fn, void *data);
  extern int git_config(config_fn_t fn, void *);
  extern int git_config_with_options(config_fn_t fn, void *,
 -                                 const char *filename, int respect_includes);
 +                                 const char *filename,
 +                                 const char *blob_ref,
 +                                 int respect_includes);
  extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
  extern int git_parse_ulong(const char *, unsigned long *);
  extern int git_config_int(const char *, const char *);
 +extern int64_t git_config_int64(const char *, const char *);
  extern unsigned long git_config_ulong(const char *, const char *);
  extern int git_config_bool_or_int(const char *, const char *, int *);
  extern int git_config_bool(const char *, const char *);
@@@ -1271,8 -1255,6 +1270,8 @@@ __attribute__((format (printf, 2, 3))
  extern void trace_argv_printf(const char **argv, const char *format, ...);
  extern void trace_repo_setup(const char *prefix);
  extern int trace_want(const char *key);
 +__attribute__((format (printf, 2, 3)))
 +extern void trace_printf_key(const char *key, const char *fmt, ...);
  extern void trace_strbuf(const char *key, const struct strbuf *buf);
  
  void packet_trace_identity(const char *prog);
   * return 0 if success, 1 - if addition of a file failed and
   * ADD_FILES_IGNORE_ERRORS was specified in flags
   */
 -int add_files_to_cache(const char *prefix, const char **pathspec, int flags);
 +int add_files_to_cache(const char *prefix, const struct pathspec *pathspec, int flags);
  
  /* diff.c */
  extern int diff_auto_refresh_index;
@@@ -1316,7 -1298,7 +1315,7 @@@ extern int ws_blank_line(const char *li
  #define ws_tab_width(rule)     ((rule) & WS_TAB_WIDTH_MASK)
  
  /* ls-files */
 -int report_path_error(const char *ps_matched, const char **pathspec, const char *prefix);
 +int report_path_error(const char *ps_matched, const struct pathspec *pathspec, const char *prefix);
  void overlay_tree_on_cache(const char *tree_name, const char *prefix);
  
  char *alias_lookup(const char *alias);
@@@ -1343,31 -1325,4 +1342,31 @@@ int checkout_fast_forward(const unsigne
  
  int sane_execvp(const char *file, char *const argv[]);
  
 +/*
 + * A struct to encapsulate the concept of whether a file has changed
 + * since we last checked it. This uses criteria similar to those used
 + * for the index.
 + */
 +struct stat_validity {
 +      struct stat_data *sd;
 +};
 +
 +void stat_validity_clear(struct stat_validity *sv);
 +
 +/*
 + * Returns 1 if the path is a regular file (or a symlink to a regular
 + * file) and matches the saved stat_validity, 0 otherwise.  A missing
 + * or inaccessible file is considered a match if the struct was just
 + * initialized, or if the previous update found an inaccessible file.
 + */
 +int stat_validity_check(struct stat_validity *sv, const char *path);
 +
 +/*
 + * Update the stat_validity from a file opened at descriptor fd. If
 + * the file is missing, inaccessible, or not a regular file, then
 + * future calls to stat_validity_check will match iff one of those
 + * conditions continues to be true.
 + */
 +void stat_validity_update(struct stat_validity *sv, int fd);
 +
  #endif /* CACHE_H */
diff --combined t/t2200-add-update.sh
index 9bf2bdffd24b773a2eec636efdd534269f45e74b,9c7419f4508bed0f64e77bb9784a72cda3963f4c..e16b15d3e5784eb3c6423318142cb91fe3a58601
@@@ -80,26 -80,22 +80,21 @@@ test_expect_success 'change gets notice
  
  '
  
- # Note that this is scheduled to change in Git 2.0, when
- # "git add -u" will become full-tree by default.
- test_expect_success 'non-limited update in subdir leaves root alone' '
+ test_expect_success 'non-qualified update in subdir updates from the root' '
        (
                cd dir1 &&
                echo even more >>sub2 &&
                git add -u
        ) &&
-       cat >expect <<-\EOF &&
-       check
-       top
-       EOF
+       : >expect &&
        git diff-files --name-only >actual &&
        test_cmp expect actual
  '
  
 -test_expect_success SYMLINKS 'replace a file with a symlink' '
 +test_expect_success 'replace a file with a symlink' '
  
        rm foo &&
 -      ln -s top foo &&
 -      git add -u -- foo
 +      test_ln_s_add top foo
  
  '