Merge branch 'cc/untracked'
authorJunio C Hamano <gitster@pobox.com>
Wed, 10 Feb 2016 22:20:06 +0000 (14:20 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 10 Feb 2016 22:20:06 +0000 (14:20 -0800)
Update the untracked cache subsystem and change its primary UI from
"git update-index" to "git config".

* cc/untracked:
t7063: add tests for core.untrackedCache
test-dump-untracked-cache: don't modify the untracked cache
config: add core.untrackedCache
dir: simplify untracked cache "ident" field
dir: add remove_untracked_cache()
dir: add {new,add}_untracked_cache()
update-index: move 'uc' var declaration
update-index: add untracked cache notifications
update-index: add --test-untracked-cache
update-index: use enum for untracked cache options
dir: free untracked cache when removing it

1  2 
Documentation/config.txt
builtin/update-index.c
cache.h
contrib/completion/git-completion.bash
dir.c
environment.c
read-cache.c
diff --combined Documentation/config.txt
index 02bcde6bb596ee5a82fa5630396fa78925abc2b0,e3c8cfe93c830eeb871fb30cf7b7abc56d8d9510..27f02be35ed2aebedab93281814b1f4e0fea4f35
@@@ -308,6 -308,15 +308,15 @@@ core.trustctime:
        crawlers and some backup systems).
        See linkgit:git-update-index[1]. True by default.
  
+ core.untrackedCache::
+       Determines what to do about the untracked cache feature of the
+       index. It will be kept, if this variable is unset or set to
+       `keep`. It will automatically be added if set to `true`. And
+       it will automatically be removed, if set to `false`. Before
+       setting it to `true`, you should check that mtime is working
+       properly on your system.
+       See linkgit:git-update-index[1]. `keep` by default.
  core.checkStat::
        Determines which stat fields to match between the index
        and work tree. The user can set this to 'default' or
@@@ -870,8 -879,6 +879,8 @@@ When preserve, also pass `--preserve-me
  so that locally committed merge commits will not be flattened
  by running 'git pull'.
  +
 +When the value is `interactive`, the rebase is run in interactive mode.
 ++
  *NOTE*: this is a possibly dangerous operation; do *not* use
  it unless you understand the implications (see linkgit:git-rebase[1]
  for details).
@@@ -1245,10 -1252,6 +1254,10 @@@ format.coverLetter:
        format-patch is invoked, but in addition can be set to "auto", to
        generate a cover-letter only when there's more than one patch.
  
 +format.outputDirectory::
 +      Set a custom directory to store the resulting files instead of the
 +      current working directory.
 +
  filter.<driver>.clean::
        The command which is used to convert the content of a worktree
        file to a blob upon checkin.  See linkgit:gitattributes[5] for
@@@ -1456,14 -1459,6 +1465,14 @@@ grep.extendedRegexp:
        option is ignored when the 'grep.patternType' option is set to a value
        other than 'default'.
  
 +grep.threads::
 +      Number of grep worker threads to use.
 +      See `grep.threads` in linkgit:git-grep[1] for more information.
 +
 +grep.fallbackToNoIndex::
 +      If set to true, fall back to git grep --no-index if git grep
 +      is executed outside of a git repository.  Defaults to false.
 +
  gpg.program::
        Use this custom program instead of "gpg" found on $PATH when
        making or verifying a PGP signature. The program must support the
@@@ -1610,34 -1605,9 +1619,34 @@@ help.htmlPath:
  
  http.proxy::
        Override the HTTP proxy, normally configured using the 'http_proxy',
 -      'https_proxy', and 'all_proxy' environment variables (see
 -      `curl(1)`).  This can be overridden on a per-remote basis; see
 -      remote.<name>.proxy
 +      'https_proxy', and 'all_proxy' environment variables (see `curl(1)`). In
 +      addition to the syntax understood by curl, it is possible to specify a
 +      proxy string with a user name but no password, in which case git will
 +      attempt to acquire one in the same way it does for other credentials. See
 +      linkgit:gitcredentials[7] for more information. The syntax thus is
 +      '[protocol://][user[:password]@]proxyhost[:port]'. This can be overridden
 +      on a per-remote basis; see remote.<name>.proxy
 +
 +http.proxyAuthMethod::
 +      Set the method with which to authenticate against the HTTP proxy. This
 +      only takes effect if the configured proxy string contains a user name part
 +      (i.e. is of the form 'user@host' or 'user@host:port'). This can be
 +      overridden on a per-remote basis; see `remote.<name>.proxyAuthMethod`.
 +      Both can be overridden by the 'GIT_HTTP_PROXY_AUTHMETHOD' environment
 +      variable.  Possible values are:
 ++
 +--
 +* `anyauth` - Automatically pick a suitable authentication method. It is
 +  assumed that the proxy answers an unauthenticated request with a 407
 +  status code and one or more Proxy-authenticate headers with supported
 +  authentication methods. This is the default.
 +* `basic` - HTTP Basic authentication
 +* `digest` - HTTP Digest authentication; this prevents the password from being
 +  transmitted to the proxy in clear text
 +* `negotiate` - GSS-Negotiate authentication (compare the --negotiate option
 +  of `curl(1)`)
 +* `ntlm` - NTLM authentication (compare the --ntlm option of `curl(1)`)
 +--
  
  http.cookieFile::
        File containing previously stored cookie lines which should be used
@@@ -2188,8 -2158,6 +2197,8 @@@ When preserve, also pass `--preserve-me
  so that locally committed merge commits will not be flattened
  by running 'git pull'.
  +
 +When the value is `interactive`, the rebase is run in interactive mode.
 ++
  *NOTE*: this is a possibly dangerous operation; do *not* use
  it unless you understand the implications (see linkgit:git-rebase[1]
  for details).
@@@ -2270,20 -2238,6 +2279,20 @@@ push.gpgSign:
        override a value from a lower-priority config file. An explicit
        command-line flag always overrides this config option.
  
 +push.recurseSubmodules::
 +      Make sure all submodule commits used by the revisions to be pushed
 +      are available on a remote-tracking branch. If the value is 'check'
 +      then Git will verify that all submodule commits that changed in the
 +      revisions to be pushed are available on at least one remote of the
 +      submodule. If any commits are missing, the push will be aborted and
 +      exit with non-zero status. If the value is 'on-demand' then all
 +      submodules that changed in the revisions to be pushed will be
 +      pushed. If on-demand was not able to push all necessary revisions
 +      it will also be aborted and exit with non-zero status. If the value
 +      is 'no' then default behavior of ignoring submodules when pushing
 +      is retained. You may override this configuration at time of push by
 +      specifying '--recurse-submodules=check|on-demand|no'.
 +
  rebase.stat::
        Whether to show a diffstat of what changed upstream since the last
        rebase. False by default.
@@@ -2448,11 -2402,6 +2457,11 @@@ remote.<name>.proxy:
        the proxy to use for that remote.  Set to the empty string to
        disable proxying for that remote.
  
 +remote.<name>.proxyAuthMethod::
 +      For remotes that require curl (http, https and ftp), the method to use for
 +      authenticating against the proxy in use (probably set in
 +      `remote.<name>.proxy`). See `http.proxyAuthMethod`.
 +
  remote.<name>.fetch::
        The default set of "refspec" for linkgit:git-fetch[1]. See
        linkgit:git-fetch[1].
diff --combined builtin/update-index.c
index 7c5c143de5dc74dda2b17832037268fc4e4b9d60,7a5533491eb19ca48556d1fa20bf7cdaf5b50903..dbc23a46b13bee7ff532232c0e1d821c565fd2fb
@@@ -35,6 -35,15 +35,15 @@@ static int mark_skip_worktree_only
  #define UNMARK_FLAG 2
  static struct strbuf mtime_dir = STRBUF_INIT;
  
+ /* Untracked cache mode */
+ enum uc_mode {
+       UC_UNSPECIFIED = -1,
+       UC_DISABLE = 0,
+       UC_ENABLE,
+       UC_TEST,
+       UC_FORCE
+ };
  __attribute__((format (printf, 1, 2)))
  static void report(const char *fmt, ...)
  {
@@@ -121,7 -130,7 +130,7 @@@ static int test_if_untracked_cache_is_s
        if (!mkdtemp(mtime_dir.buf))
                die_errno("Could not make temporary directory");
  
-       fprintf(stderr, _("Testing "));
+       fprintf(stderr, _("Testing mtime in '%s' "), xgetcwd());
        atexit(remove_test_directory);
        xstat_mtime_dir(&st);
        fill_stat_data(&base, &st);
@@@ -468,14 -477,12 +477,14 @@@ static void update_one(const char *path
        report("add '%s'", path);
  }
  
 -static void read_index_info(int line_termination)
 +static void read_index_info(int nul_term_line)
  {
        struct strbuf buf = STRBUF_INIT;
        struct strbuf uq = STRBUF_INIT;
 +      strbuf_getline_fn getline_fn;
  
 -      while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
 +      getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf;
 +      while (getline_fn(&buf, stdin) != EOF) {
                char *ptr, *tab;
                char *path_name;
                unsigned char sha1[20];
                        goto bad_line;
  
                path_name = ptr;
 -              if (line_termination && path_name[0] == '"') {
 +              if (!nul_term_line && path_name[0] == '"') {
                        strbuf_reset(&uq);
                        if (unquote_c_style(&uq, path_name, NULL)) {
                                die("git update-index: bad quoting of path name");
@@@ -846,12 -853,12 +855,12 @@@ static int cacheinfo_callback(struct pa
  static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx,
                              const struct option *opt, int unset)
  {
 -      int *line_termination = opt->value;
 +      int *nul_term_line = opt->value;
  
        if (ctx->argc != 1)
                return error("option '%s' must be the last argument", opt->long_name);
        allow_add = allow_replace = allow_remove = 1;
 -      read_index_info(*line_termination);
 +      read_index_info(*nul_term_line);
        return 0;
  }
  
@@@ -903,8 -910,8 +912,8 @@@ static int reupdate_callback(struct par
  
  int cmd_update_index(int argc, const char **argv, const char *prefix)
  {
 -      int newfd, entries, has_errors = 0, line_termination = '\n';
 +      int newfd, entries, has_errors = 0, nul_term_line = 0;
-       int untracked_cache = -1;
+       enum uc_mode untracked_cache = UC_UNSPECIFIED;
        int read_from_stdin = 0;
        int prefix_length = prefix ? strlen(prefix) : 0;
        int preferred_index_format = 0;
        int split_index = -1;
        struct lock_file *lock_file;
        struct parse_opt_ctx_t ctx;
 +      strbuf_getline_fn getline_fn;
        int parseopt_state = PARSE_OPT_UNKNOWN;
        struct option options[] = {
                OPT_BIT('q', NULL, &refresh_args.flags,
                        N_("add to index only; do not add content to object database"), 1),
                OPT_SET_INT(0, "force-remove", &force_remove,
                        N_("remove named paths even if present in worktree"), 1),
 -              OPT_SET_INT('z', NULL, &line_termination,
 -                      N_("with --stdin: input lines are terminated by null bytes"), '\0'),
 +              OPT_BOOL('z', NULL, &nul_term_line,
 +                       N_("with --stdin: input lines are terminated by null bytes")),
                {OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL,
                        N_("read list of paths to be updated from standard input"),
                        PARSE_OPT_NONEG | PARSE_OPT_NOARG,
                        (parse_opt_cb *) stdin_callback},
 -              {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &line_termination, NULL,
 +              {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &nul_term_line, NULL,
                        N_("add entries from standard input to the index"),
                        PARSE_OPT_NONEG | PARSE_OPT_NOARG,
                        (parse_opt_cb *) stdin_cacheinfo_callback},
                        N_("enable or disable split index")),
                OPT_BOOL(0, "untracked-cache", &untracked_cache,
                        N_("enable/disable untracked cache")),
+               OPT_SET_INT(0, "test-untracked-cache", &untracked_cache,
+                           N_("test if the filesystem supports untracked cache"), UC_TEST),
                OPT_SET_INT(0, "force-untracked-cache", &untracked_cache,
-                           N_("enable untracked cache without testing the filesystem"), 2),
+                           N_("enable untracked cache without testing the filesystem"), UC_FORCE),
                OPT_END()
        };
  
                }
        }
        argc = parse_options_end(&ctx);
 +
 +      getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf;
        if (preferred_index_format) {
                if (preferred_index_format < INDEX_FORMAT_LB ||
                    INDEX_FORMAT_UB < preferred_index_format)
                struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
  
                setup_work_tree();
 -              while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
 +              while (getline_fn(&buf, stdin) != EOF) {
                        char *p;
 -                      if (line_termination && buf.buf[0] == '"') {
 +                      if (!nul_term_line && buf.buf[0] == '"') {
                                strbuf_reset(&nbuf);
                                if (unquote_c_style(&nbuf, buf.buf, NULL))
                                        die("line is badly quoted");
                the_index.split_index = NULL;
                the_index.cache_changed |= SOMETHING_CHANGED;
        }
-       if (untracked_cache > 0) {
-               struct untracked_cache *uc;
  
-               if (untracked_cache < 2) {
-                       setup_work_tree();
-                       if (!test_if_untracked_cache_is_supported())
-                               return 1;
-               }
-               if (!the_index.untracked) {
-                       uc = xcalloc(1, sizeof(*uc));
-                       strbuf_init(&uc->ident, 100);
-                       uc->exclude_per_dir = ".gitignore";
-                       /* should be the same flags used by git-status */
-                       uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
-                       the_index.untracked = uc;
-               }
-               add_untracked_ident(the_index.untracked);
-               the_index.cache_changed |= UNTRACKED_CHANGED;
-       } else if (!untracked_cache && the_index.untracked) {
-               the_index.untracked = NULL;
-               the_index.cache_changed |= UNTRACKED_CHANGED;
+       switch (untracked_cache) {
+       case UC_UNSPECIFIED:
+               break;
+       case UC_DISABLE:
+               if (git_config_get_untracked_cache() == 1)
+                       warning("core.untrackedCache is set to true; "
+                               "remove or change it, if you really want to "
+                               "disable the untracked cache");
+               remove_untracked_cache(&the_index);
+               report(_("Untracked cache disabled"));
+               break;
+       case UC_TEST:
+               setup_work_tree();
+               return !test_if_untracked_cache_is_supported();
+       case UC_ENABLE:
+       case UC_FORCE:
+               if (git_config_get_untracked_cache() == 0)
+                       warning("core.untrackedCache is set to false; "
+                               "remove or change it, if you really want to "
+                               "enable the untracked cache");
+               add_untracked_cache(&the_index);
+               report(_("Untracked cache enabled for '%s'"), get_git_work_tree());
+               break;
+       default:
+               die("Bug: bad untracked_cache value: %d", untracked_cache);
        }
  
        if (active_cache_changed) {
diff --combined cache.h
index 553b04bfb8f899a4106a800f29225683ecd2e922,255b273d82a8ec4b51bfc582e93a55a09c6d45f9..26640b421d938a215236935d35c2d6b4f37fee6d
+++ b/cache.h
@@@ -9,7 -9,6 +9,7 @@@
  #include "convert.h"
  #include "trace.h"
  #include "string-list.h"
 +#include "pack-revindex.h"
  
  #include SHA1_HEADER
  #ifndef platform_SHA_CTX
@@@ -215,7 -214,7 +215,7 @@@ struct cache_entry 
  #define CE_INTENT_TO_ADD     (1 << 29)
  #define CE_SKIP_WORKTREE     (1 << 30)
  /* CE_EXTENDED2 is for future extension */
 -#define CE_EXTENDED2         (1 << 31)
 +#define CE_EXTENDED2         (1U << 31)
  
  #define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD | CE_SKIP_WORKTREE)
  
@@@ -260,7 -259,6 +260,7 @@@ static inline unsigned create_ce_flags(
  #define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE)
  #define ce_skip_worktree(ce) ((ce)->ce_flags & CE_SKIP_WORKTREE)
  #define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE)
 +#define ce_intent_to_add(ce) ((ce)->ce_flags & CE_INTENT_TO_ADD)
  
  #define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644)
  static inline unsigned int create_ce_mode(unsigned int mode)
@@@ -458,6 -456,7 +458,6 @@@ extern char *git_work_tree_cfg
  extern int is_inside_work_tree(void);
  extern const char *get_git_dir(void);
  extern const char *get_git_common_dir(void);
 -extern int is_git_directory(const char *path);
  extern char *get_object_directory(void);
  extern char *get_index_file(void);
  extern char *get_graft_file(void);
@@@ -468,25 -467,6 +468,25 @@@ extern const char *get_git_namespace(vo
  extern const char *strip_namespace(const char *namespaced_ref);
  extern const char *get_git_work_tree(void);
  
 +/*
 + * Return true if the given path is a git directory; note that this _just_
 + * looks at the directory itself. If you want to know whether "foo/.git"
 + * is a repository, you must feed that path, not just "foo".
 + */
 +extern int is_git_directory(const char *path);
 +
 +/*
 + * Return 1 if the given path is the root of a git repository or
 + * submodule, else 0. Will not return 1 for bare repositories with the
 + * exception of creating a bare repository in "foo/.git" and calling
 + * is_git_repository("foo").
 + *
 + * If we run into read errors, we err on the side of saying "yes, it is",
 + * as we usually consider sub-repos precious, and would prefer to err on the
 + * side of not disrupting or deleting them.
 + */
 +extern int is_nonbare_repository_dir(struct strbuf *path);
 +
  #define READ_GITFILE_ERR_STAT_FAILED 1
  #define READ_GITFILE_ERR_NOT_A_FILE 2
  #define READ_GITFILE_ERR_OPEN_FAILED 3
@@@ -851,7 -831,6 +851,7 @@@ extern const char *find_unique_abbrev(c
  extern int find_unique_abbrev_r(char *hex, const unsigned char *sha1, int len);
  
  extern const unsigned char null_sha1[GIT_SHA1_RAWSZ];
 +extern const struct object_id null_oid;
  
  static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
  {
@@@ -1319,7 -1298,6 +1319,7 @@@ extern struct packed_git 
                 freshened:1,
                 do_not_close:1;
        unsigned char sha1[20];
 +      struct revindex_entry *revindex;
        /* something like ".git/objects/pack/xxxxx.pack" */
        char pack_name[FLEX_ARRAY]; /* more */
  } *packed_git;
@@@ -1624,6 -1602,14 +1624,14 @@@ extern int git_config_get_bool(const ch
  extern int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest);
  extern int git_config_get_maybe_bool(const char *key, int *dest);
  extern int git_config_get_pathname(const char *key, const char **dest);
+ extern int git_config_get_untracked_cache(void);
+ /*
+  * This is a hack for test programs like test-dump-untracked-cache to
+  * ensure that they do not modify the untracked cache when reading it.
+  * Do not use it otherwise!
+  */
+ extern int ignore_untracked_cache_config;
  
  struct key_value_info {
        const char *filename;
index 15ebba51dc11ec28f98f6c759719125f155aa5d3,a429e6ea299582a93e464a633be068928ed2cce8..45ec47f2b1b45e346290e7bbad143519b1ad91a6
@@@ -664,7 -664,6 +664,7 @@@ __git_list_porcelain_commands (
                check-mailmap)    : plumbing;;
                check-ref-format) : plumbing;;
                checkout-index)   : plumbing;;
 +              column)           : internal helper;;
                commit-tree)      : plumbing;;
                count-objects)    : infrequent;;
                credential)       : credentials;;
@@@ -1169,7 -1168,7 +1169,7 @@@ __git_diff_common_options="--stat --num
                        --no-prefix --src-prefix= --dst-prefix=
                        --inter-hunk-context=
                        --patience --histogram --minimal
 -                      --raw --word-diff
 +                      --raw --word-diff --word-diff-regex=
                        --dirstat --dirstat= --dirstat-by-file
                        --dirstat-by-file= --cumulative
                        --diff-algorithm=
@@@ -1312,7 -1311,6 +1312,7 @@@ _git_grep (
                        --full-name --line-number
                        --extended-regexp --basic-regexp --fixed-strings
                        --perl-regexp
 +                      --threads
                        --files-with-matches --name-only
                        --files-without-match
                        --max-depth
@@@ -1688,12 -1686,8 +1688,12 @@@ _git_rebase (
                        --preserve-merges --stat --no-stat
                        --committer-date-is-author-date --ignore-date
                        --ignore-whitespace --whitespace=
 -                      --autosquash --fork-point --no-fork-point
 -                      --autostash
 +                      --autosquash --no-autosquash
 +                      --fork-point --no-fork-point
 +                      --autostash --no-autostash
 +                      --verify --no-verify
 +                      --keep-empty --root --force-rebase --no-ff
 +                      --exec
                        "
  
                return
@@@ -1813,7 -1807,7 +1813,7 @@@ _git_config (
                return
                ;;
        branch.*.rebase)
 -              __gitcomp "false true"
 +              __gitcomp "false true preserve interactive"
                return
                ;;
        remote.pushdefault)
                core.sparseCheckout
                core.symlinks
                core.trustctime
+               core.untrackedCache
                core.warnAmbiguousRefs
                core.whitespace
                core.worktree
@@@ -2373,7 -2368,7 +2374,7 @@@ _git_show_branch (
        case "$cur" in
        --*)
                __gitcomp "
 -                      --all --remotes --topo-order --current --more=
 +                      --all --remotes --topo-order --date-order --current --more=
                        --list --independent --merge-base --no-name
                        --color --no-color
                        --sha1-name --sparse --topics --reflog
  
  _git_stash ()
  {
 -      local save_opts='--keep-index --no-keep-index --quiet --patch'
 +      local save_opts='--all --keep-index --no-keep-index --quiet --patch --include-untracked'
        local subcommands='save list show apply clear drop pop create branch'
        local subcommand="$(__git_find_on_cmdline "$subcommands")"
        if [ -z "$subcommand" ]; then
                apply,--*|pop,--*)
                        __gitcomp "--index --quiet"
                        ;;
 -              show,--*|drop,--*|branch,--*)
 +              drop,--*)
 +                      __gitcomp "--quiet"
 +                      ;;
 +              show,--*|branch,--*)
 +                      ;;
 +              branch,*)
 +                if [ $cword -eq 3 ]; then
 +                      __gitcomp_nl "$(__git_refs)";
 +                      else
 +                              __gitcomp_nl "$(git --git-dir="$(__gitdir)" stash list \
 +                                              | sed -n -e 's/:.*//p')"
 +                      fi
                        ;;
 -              show,*|apply,*|drop,*|pop,*|branch,*)
 +              show,*|apply,*|drop,*|pop,*)
                        __gitcomp_nl "$(git --git-dir="$(__gitdir)" stash list \
                                        | sed -n -e 's/:.*//p')"
                        ;;
diff --combined dir.c
index 29aec124871e44972de70cb05f1c6023e514fcc2,42d3b6b10042b595d7b0e32a9bfe5512d6033f09..f0b6d0a3eab704311ed2c93a3da5db0683b3125c
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -564,7 -564,9 +564,7 @@@ void clear_exclude_list(struct exclude_
        free(el->excludes);
        free(el->filebuf);
  
 -      el->nr = 0;
 -      el->excludes = NULL;
 -      el->filebuf = NULL;
 +      memset(el, 0, sizeof(*el));
  }
  
  static void trim_trailing_spaces(char *buf)
@@@ -880,6 -882,25 +880,6 @@@ int match_pathname(const char *pathname
                 */
                if (!patternlen && !namelen)
                        return 1;
 -              /*
 -               * This can happen when we ignore some exclude rules
 -               * on directories in other to see if negative rules
 -               * may match. E.g.
 -               *
 -               * /abc
 -               * !/abc/def/ghi
 -               *
 -               * The pattern of interest is "/abc". On the first
 -               * try, we should match path "abc" with this pattern
 -               * in the "if" statement right above, but the caller
 -               * ignores it.
 -               *
 -               * On the second try with paths within "abc",
 -               * e.g. "abc/xyz", we come here and try to match it
 -               * with "/abc".
 -               */
 -              if (!patternlen && namelen && *name == '/')
 -                      return 1;
        }
  
        return fnmatch_icase_mem(pattern, patternlen,
                                 WM_PATHNAME) == 0;
  }
  
 -/*
 - * Return non-zero if pathname is a directory and an ancestor of the
 - * literal path in a (negative) pattern. This is used to keep
 - * descending in "foo" and "foo/bar" when the pattern is
 - * "!foo/bar/.gitignore". "foo/notbar" will not be descended however.
 - */
 -static int match_neg_path(const char *pathname, int pathlen, int *dtype,
 -                        const char *base, int baselen,
 -                        const char *pattern, int prefix, int patternlen,
 -                        int flags)
 -{
 -      assert((flags & EXC_FLAG_NEGATIVE) && !(flags & EXC_FLAG_NODIR));
 -
 -      if (*dtype == DT_UNKNOWN)
 -              *dtype = get_dtype(NULL, pathname, pathlen);
 -      if (*dtype != DT_DIR)
 -              return 0;
 -
 -      if (*pattern == '/') {
 -              pattern++;
 -              patternlen--;
 -              prefix--;
 -      }
 -
 -      if (baselen) {
 -              if (((pathlen < baselen && base[pathlen] == '/') ||
 -                   pathlen == baselen) &&
 -                  !strncmp_icase(pathname, base, pathlen))
 -                      return 1;
 -              pathname += baselen + 1;
 -              pathlen  -= baselen + 1;
 -      }
 -
 -
 -      if (prefix &&
 -          ((pathlen < prefix && pattern[pathlen] == '/') &&
 -           !strncmp_icase(pathname, pattern, pathlen)))
 -              return 1;
 -
 -      return 0;
 -}
 -
  /*
   * Scan the given exclude list in reverse to see whether pathname
   * should be ignored.  The first match (i.e. the last on the list), if
@@@ -900,7 -963,7 +900,7 @@@ static struct exclude *last_exclude_mat
                                                       struct exclude_list *el)
  {
        struct exclude *exc = NULL; /* undecided */
 -      int i, matched_negative_path = 0;
 +      int i;
  
        if (!el->nr)
                return NULL;    /* undefined */
                        exc = x;
                        break;
                }
 -
 -              if ((x->flags & EXC_FLAG_NEGATIVE) && !matched_negative_path &&
 -                  match_neg_path(pathname, pathlen, dtype, x->base,
 -                                 x->baselen ? x->baselen - 1 : 0,
 -                                 exclude, prefix, x->patternlen, x->flags))
 -                      matched_negative_path = 1;
        }
 -      if (exc &&
 -          !(exc->flags & EXC_FLAG_NEGATIVE) &&
 -          !(exc->flags & EXC_FLAG_NODIR) &&
 -          matched_negative_path)
 -              exc = NULL;
        return exc;
  }
  
@@@ -1839,31 -1913,67 +1839,67 @@@ static const char *get_ident_string(voi
                return sb.buf;
        if (uname(&uts) < 0)
                die_errno(_("failed to get kernel name and information"));
-       strbuf_addf(&sb, "Location %s, system %s %s %s", get_git_work_tree(),
-                   uts.sysname, uts.release, uts.version);
+       strbuf_addf(&sb, "Location %s, system %s", get_git_work_tree(),
+                   uts.sysname);
        return sb.buf;
  }
  
  static int ident_in_untracked(const struct untracked_cache *uc)
  {
-       const char *end = uc->ident.buf + uc->ident.len;
-       const char *p   = uc->ident.buf;
+       /*
+        * Previous git versions may have saved many NUL separated
+        * strings in the "ident" field, but it is insane to manage
+        * many locations, so just take care of the first one.
+        */
  
-       for (p = uc->ident.buf; p < end; p += strlen(p) + 1)
-               if (!strcmp(p, get_ident_string()))
-                       return 1;
-       return 0;
+       return !strcmp(uc->ident.buf, get_ident_string());
  }
  
void add_untracked_ident(struct untracked_cache *uc)
static void set_untracked_ident(struct untracked_cache *uc)
  {
-       if (ident_in_untracked(uc))
-               return;
+       strbuf_reset(&uc->ident);
        strbuf_addstr(&uc->ident, get_ident_string());
-       /* this strbuf contains a list of strings, save NUL too */
+       /*
+        * This strbuf used to contain a list of NUL separated
+        * strings, so save NUL too for backward compatibility.
+        */
        strbuf_addch(&uc->ident, 0);
  }
  
+ static void new_untracked_cache(struct index_state *istate)
+ {
+       struct untracked_cache *uc = xcalloc(1, sizeof(*uc));
+       strbuf_init(&uc->ident, 100);
+       uc->exclude_per_dir = ".gitignore";
+       /* should be the same flags used by git-status */
+       uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
+       set_untracked_ident(uc);
+       istate->untracked = uc;
+       istate->cache_changed |= UNTRACKED_CHANGED;
+ }
+ void add_untracked_cache(struct index_state *istate)
+ {
+       if (!istate->untracked) {
+               new_untracked_cache(istate);
+       } else {
+               if (!ident_in_untracked(istate->untracked)) {
+                       free_untracked_cache(istate->untracked);
+                       new_untracked_cache(istate);
+               }
+       }
+ }
+ void remove_untracked_cache(struct index_state *istate)
+ {
+       if (istate->untracked) {
+               free_untracked_cache(istate->untracked);
+               istate->untracked = NULL;
+               istate->cache_changed |= UNTRACKED_CHANGED;
+       }
+ }
  static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *dir,
                                                      int base_len,
                                                      const struct pathspec *pathspec)
                return NULL;
  
        if (!ident_in_untracked(dir->untracked)) {
-               warning(_("Untracked cache is disabled on this system."));
+               warning(_("Untracked cache is disabled on this system or location."));
                return NULL;
        }
  
diff --combined environment.c
index 1cc4aab4eacb629f8fe147c2e20b53c4141587aa,b329024f134a9c859689f99e8818d8b58a6b05ce..6dec9d0403f11579a7ab316db87a1dbbc58e71b2
@@@ -87,6 -87,13 +87,13 @@@ int auto_comment_line_char
  /* Parallel index stat data preload? */
  int core_preload_index = 1;
  
+ /*
+  * This is a hack for test programs like test-dump-untracked-cache to
+  * ensure that they do not modify the untracked cache when reading it.
+  * Do not use it otherwise!
+  */
+ int ignore_untracked_cache_config;
  /* This is set by setup_git_dir_gently() and/or git_default_config() */
  char *git_work_tree_cfg;
  static char *work_tree;
@@@ -235,6 -242,8 +242,6 @@@ void set_git_work_tree(const char *new_
        }
        git_work_tree_initialized = 1;
        work_tree = xstrdup(real_path(new_work_tree));
 -      if (setenv(GIT_WORK_TREE_ENVIRONMENT, work_tree, 1))
 -              die("could not set GIT_WORK_TREE to '%s'", work_tree);
  }
  
  const char *get_git_work_tree(void)
diff --combined read-cache.c
index 5be7cd1dbf50c850ee5e02dcfde69d7611c181b5,56614d54c7ef72bc7ceab6d499c010a7fb99e79b..d9fb78bc559ac9de642833df5af63e33481cad52
@@@ -327,7 -327,7 +327,7 @@@ int ie_match_stat(const struct index_st
         * by definition never matches what is in the work tree until it
         * actually gets added.
         */
 -      if (ce->ce_flags & CE_INTENT_TO_ADD)
 +      if (ce_intent_to_add(ce))
                return DATA_CHANGED | TYPE_CHANGED | MODE_CHANGED;
  
        changed = ce_match_stat_basic(ce, st);
@@@ -1237,7 -1237,7 +1237,7 @@@ int refresh_index(struct index_state *i
  
                        if (cache_errno == ENOENT)
                                fmt = deleted_fmt;
 -                      else if (ce->ce_flags & CE_INTENT_TO_ADD)
 +                      else if (ce_intent_to_add(ce))
                                fmt = added_fmt; /* must be before other checks */
                        else if (changed & TYPE_CHANGED)
                                fmt = typechange_fmt;
@@@ -1519,6 -1519,28 +1519,28 @@@ static void check_ce_order(struct index
        }
  }
  
+ static void tweak_untracked_cache(struct index_state *istate)
+ {
+       switch (git_config_get_untracked_cache()) {
+       case -1: /* keep: do nothing */
+               break;
+       case 0: /* false */
+               remove_untracked_cache(istate);
+               break;
+       case 1: /* true */
+               add_untracked_cache(istate);
+               break;
+       default: /* unknown value: do nothing */
+               break;
+       }
+ }
+ static void post_read_index_from(struct index_state *istate)
+ {
+       check_ce_order(istate);
+       tweak_untracked_cache(istate);
+ }
  /* remember to discard_cache() before reading a different cache! */
  int do_read_index(struct index_state *istate, const char *path, int must_exist)
  {
@@@ -1622,9 -1644,10 +1644,10 @@@ int read_index_from(struct index_state 
                return istate->cache_nr;
  
        ret = do_read_index(istate, path, 0);
        split_index = istate->split_index;
        if (!split_index || is_null_sha1(split_index->base_sha1)) {
-               check_ce_order(istate);
+               post_read_index_from(istate);
                return ret;
        }
  
                             sha1_to_hex(split_index->base_sha1)),
                    sha1_to_hex(split_index->base->sha1));
        merge_base_index(istate);
-       check_ce_order(istate);
+       post_read_index_from(istate);
        return ret;
  }