Merge branch 'bw/recurse-submodules-relative-fix'
authorJunio C Hamano <gitster@pobox.com>
Thu, 30 Mar 2017 21:07:15 +0000 (14:07 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 30 Mar 2017 21:07:15 +0000 (14:07 -0700)
A few commands that recently learned the "--recurse-submodule"
option misbehaved when started from a subdirectory of the
superproject.

* bw/recurse-submodules-relative-fix:
ls-files: fix bug when recursing with relative pathspec
ls-files: fix typo in variable name
grep: fix bug when recursing with relative pathspec
setup: allow for prefix to be passed to git commands
grep: fix help text typo

1  2 
builtin/grep.c
cache.h
git.c
setup.c
diff --combined builtin/grep.c
index 3f3efa95de2570bcd4f51a29fc7936817b9cee23,4a279ca4bfb38898e7cc87b5947a36abe1bce64c..65070c52fc2da84bc48804817f1e714f755c9ab5
@@@ -294,26 -294,23 +294,23 @@@ static int grep_cmd_config(const char *
        return st;
  }
  
 -static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
 +static void *lock_and_read_oid_file(const struct object_id *oid, enum object_type *type, unsigned long *size)
  {
        void *data;
  
        grep_read_lock();
 -      data = read_sha1_file(sha1, type, size);
 +      data = read_sha1_file(oid->hash, type, size);
        grep_read_unlock();
        return data;
  }
  
 -static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
 +static int grep_oid(struct grep_opt *opt, const struct object_id *oid,
                     const char *filename, int tree_name_len,
                     const char *path)
  {
        struct strbuf pathbuf = STRBUF_INIT;
  
-       if (opt->relative && opt->prefix_length) {
-               quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf);
-               strbuf_insert(&pathbuf, 0, filename, tree_name_len);
-       } else if (super_prefix) {
+       if (super_prefix) {
                strbuf_add(&pathbuf, filename, tree_name_len);
                strbuf_addstr(&pathbuf, super_prefix);
                strbuf_addstr(&pathbuf, filename + tree_name_len);
                strbuf_addstr(&pathbuf, filename);
        }
  
+       if (opt->relative && opt->prefix_length) {
+               char *name = strbuf_detach(&pathbuf, NULL);
+               quote_path_relative(name + tree_name_len, opt->prefix, &pathbuf);
+               strbuf_insert(&pathbuf, 0, name, tree_name_len);
+               free(name);
+       }
  #ifndef NO_PTHREADS
        if (num_threads) {
 -              add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1);
 +              add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, oid);
                strbuf_release(&pathbuf);
                return 0;
        } else
                struct grep_source gs;
                int hit;
  
 -              grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1);
 +              grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, path, oid);
                strbuf_release(&pathbuf);
                hit = grep_source(opt, &gs);
  
@@@ -345,12 -349,14 +349,14 @@@ static int grep_file(struct grep_opt *o
  {
        struct strbuf buf = STRBUF_INIT;
  
+       if (super_prefix)
+               strbuf_addstr(&buf, super_prefix);
+       strbuf_addstr(&buf, filename);
        if (opt->relative && opt->prefix_length) {
-               quote_path_relative(filename, opt->prefix, &buf);
-       } else {
-               if (super_prefix)
-                       strbuf_addstr(&buf, super_prefix);
-               strbuf_addstr(&buf, filename);
+               char *name = strbuf_detach(&buf, NULL);
+               quote_path_relative(name, opt->prefix, &buf);
+               free(name);
        }
  
  #ifndef NO_PTHREADS
@@@ -399,13 -405,12 +405,12 @@@ static void run_pager(struct grep_opt *
  }
  
  static void compile_submodule_options(const struct grep_opt *opt,
-                                     const struct pathspec *pathspec,
+                                     const char **argv,
                                      int cached, int untracked,
                                      int opt_exclude, int use_index,
                                      int pattern_type_arg)
  {
        struct grep_pat *pattern;
-       int i;
  
        if (recurse_submodules)
                argv_array_push(&submodule_options, "--recurse-submodules");
  
        /* Add Pathspecs */
        argv_array_push(&submodule_options, "--");
-       for (i = 0; i < pathspec->nr; i++)
-               argv_array_push(&submodule_options,
-                               pathspec->items[i].original);
+       for (; *argv; argv++)
+               argv_array_push(&submodule_options, *argv);
  }
  
  /*
@@@ -538,7 -542,7 +542,7 @@@ static int grep_submodule_launch(struc
        int status, i;
        const char *end_of_base;
        const char *name;
 -      struct work_item *w = opt->output_priv;
 +      struct strbuf child_output = STRBUF_INIT;
  
        end_of_base = strchr(gs->name, ':');
        if (gs->identifier && end_of_base)
        prepare_submodule_repo_env(&cp.env_array);
        argv_array_push(&cp.env_array, GIT_DIR_ENVIRONMENT);
  
+       if (opt->relative && opt->prefix_length)
+               argv_array_pushf(&cp.env_array, "%s=%s",
+                                GIT_TOPLEVEL_PREFIX_ENVIRONMENT,
+                                opt->prefix);
        /* Add super prefix */
        argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
                         super_prefix ? super_prefix : "",
         * child process.  A '0' indicates a hit, a '1' indicates no hit and
         * anything else is an error.
         */
 -      status = capture_command(&cp, &w->out, 0);
 +      status = capture_command(&cp, &child_output, 0);
        if (status && (status != 1)) {
                /* flush the buffer */
 -              write_or_die(1, w->out.buf, w->out.len);
 +              write_or_die(1, child_output.buf, child_output.len);
                die("process for submodule '%s' failed with exit code: %d",
                    gs->name, status);
        }
  
 +      opt->output(opt, child_output.buf, child_output.len);
 +      strbuf_release(&child_output);
        /* invert the return code to make a hit equal to 1 */
        return !status;
  }
@@@ -618,7 -625,7 +627,7 @@@ static int grep_submodule(struct grep_o
  {
        if (!is_submodule_initialized(path))
                return 0;
 -      if (!is_submodule_populated(path)) {
 +      if (!is_submodule_populated_gently(path, NULL)) {
                /*
                 * If searching history, check for the presense of the
                 * submodule's gitdir before skipping the submodule.
        } else
  #endif
        {
 -              struct work_item w;
 +              struct grep_source gs;
                int hit;
  
 -              grep_source_init(&w.source, GREP_SOURCE_SUBMODULE,
 +              grep_source_init(&gs, GREP_SOURCE_SUBMODULE,
                                 filename, path, sha1);
 -              strbuf_init(&w.out, 0);
 -              opt->output_priv = &w;
 -              hit = grep_submodule_launch(opt, &w.source);
 +              hit = grep_submodule_launch(opt, &gs);
  
 -              write_or_die(1, w.out.buf, w.out.len);
 -
 -              grep_source_clear(&w.source);
 -              strbuf_release(&w.out);
 +              grep_source_clear(&gs);
                return hit;
        }
  }
@@@ -687,7 -699,7 +696,7 @@@ static int grep_cache(struct grep_opt *
                            ce_skip_worktree(ce)) {
                                if (ce_stage(ce) || ce_intent_to_add(ce))
                                        continue;
 -                              hit |= grep_sha1(opt, ce->oid.hash, ce->name,
 +                              hit |= grep_oid(opt, &ce->oid, ce->name,
                                                 0, ce->name);
                        } else {
                                hit |= grep_file(opt, ce->name);
@@@ -747,7 -759,7 +756,7 @@@ static int grep_tree(struct grep_opt *o
                strbuf_add(base, entry.path, te_len);
  
                if (S_ISREG(entry.mode)) {
 -                      hit |= grep_sha1(opt, entry.oid->hash, base->buf, tn_len,
 +                      hit |= grep_oid(opt, entry.oid, base->buf, tn_len,
                                         check_attr ? base->buf + tn_len : NULL);
                } else if (S_ISDIR(entry.mode)) {
                        enum object_type type;
                        void *data;
                        unsigned long size;
  
 -                      data = lock_and_read_sha1_file(entry.oid->hash, &type, &size);
 +                      data = lock_and_read_oid_file(entry.oid, &type, &size);
                        if (!data)
                                die(_("unable to read tree (%s)"),
                                    oid_to_hex(entry.oid));
@@@ -784,7 -796,7 +793,7 @@@ static int grep_object(struct grep_opt 
                       struct object *obj, const char *name, const char *path)
  {
        if (obj->type == OBJ_BLOB)
 -              return grep_sha1(opt, obj->oid.hash, name, 0, path);
 +              return grep_oid(opt, &obj->oid, name, 0, path);
        if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) {
                struct tree_desc tree;
                void *data;
@@@ -964,7 -976,6 +973,7 @@@ int cmd_grep(int argc, const char **arg
        int dummy;
        int use_index = 1;
        int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED;
 +      int allow_revs;
  
        struct option options[] = {
                OPT_BOOL(0, "cached", &cached,
                OPT_SET_INT(0, "exclude-standard", &opt_exclude,
                            N_("ignore files specified via '.gitignore'"), 1),
                OPT_BOOL(0, "recurse-submodules", &recurse_submodules,
-                        N_("recursivley search in each submodule")),
+                        N_("recursively search in each submodule")),
                OPT_STRING(0, "parent-basename", &parent_basename,
                           N_("basename"),
                           N_("prepend parent project's basename to output")),
  
        compile_grep_patterns(&opt);
  
 -      /* Check revs and then paths */
 +      /*
 +       * We have to find "--" in a separate pass, because its presence
 +       * influences how we will parse arguments that come before it.
 +       */
 +      for (i = 0; i < argc; i++) {
 +              if (!strcmp(argv[i], "--")) {
 +                      seen_dashdash = 1;
 +                      break;
 +              }
 +      }
 +
 +      /*
 +       * Resolve any rev arguments. If we have a dashdash, then everything up
 +       * to it must resolve as a rev. If not, then we stop at the first
 +       * non-rev and assume everything else is a path.
 +       */
 +      allow_revs = use_index && !untracked;
        for (i = 0; i < argc; i++) {
                const char *arg = argv[i];
 -              unsigned char sha1[20];
 +              struct object_id oid;
                struct object_context oc;
 -              /* Is it a rev? */
 -              if (!get_sha1_with_context(arg, 0, sha1, &oc)) {
 -                      struct object *object = parse_object_or_die(sha1, arg);
 -                      if (!seen_dashdash)
 -                              verify_non_filename(prefix, arg);
 -                      add_object_array_with_path(object, arg, &list, oc.mode, oc.path);
 -                      continue;
 -              }
 +              struct object *object;
 +
                if (!strcmp(arg, "--")) {
                        i++;
 -                      seen_dashdash = 1;
 +                      break;
                }
 -              break;
 +
 +              if (!allow_revs) {
 +                      if (seen_dashdash)
 +                              die(_("--no-index or --untracked cannot be used with revs"));
 +                      break;
 +              }
 +
 +              if (get_sha1_with_context(arg, 0, oid.hash, &oc)) {
 +                      if (seen_dashdash)
 +                              die(_("unable to resolve revision: %s"), arg);
 +                      break;
 +              }
 +
 +              object = parse_object_or_die(oid.hash, arg);
 +              if (!seen_dashdash)
 +                      verify_non_filename(prefix, arg);
 +              add_object_array_with_path(object, arg, &list, oc.mode, oc.path);
        }
  
 +      /*
 +       * Anything left over is presumed to be a path. But in the non-dashdash
 +       * "do what I mean" case, we verify and complain when that isn't true.
 +       */
 +      if (!seen_dashdash) {
 +              int j;
 +              for (j = i; j < argc; j++)
 +                      verify_filename(prefix, argv[j], j == i && allow_revs);
 +      }
 +
 +      parse_pathspec(&pathspec, 0,
 +                     PATHSPEC_PREFER_CWD |
 +                     (opt.max_depth != -1 ? PATHSPEC_MAXDEPTH_VALID : 0),
 +                     prefix, argv + i);
 +      pathspec.max_depth = opt.max_depth;
 +      pathspec.recursive = 1;
 +
  #ifndef NO_PTHREADS
        if (list.nr || cached || show_in_pager)
                num_threads = 0;
        }
  #endif
  
 -      /* The rest are paths */
 -      if (!seen_dashdash) {
 -              int j;
 -              for (j = i; j < argc; j++)
 -                      verify_filename(prefix, argv[j], j == i);
 -      }
 -
 -      parse_pathspec(&pathspec, 0,
 -                     PATHSPEC_PREFER_CWD |
 -                     (opt.max_depth != -1 ? PATHSPEC_MAXDEPTH_VALID : 0),
 -                     prefix, argv + i);
 -      pathspec.max_depth = opt.max_depth;
 -      pathspec.recursive = 1;
 -
        if (recurse_submodules) {
                gitmodules_config();
-               compile_submodule_options(&opt, &pathspec, cached, untracked,
+               compile_submodule_options(&opt, argv + i, cached, untracked,
                                          opt_exclude, use_index,
                                          pattern_type_arg);
        }
  
        if (!use_index || untracked) {
                int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude;
 -              if (list.nr)
 -                      die(_("--no-index or --untracked cannot be used with revs."));
                hit = grep_directory(&opt, &pathspec, use_exclude, use_index);
        } else if (0 <= opt_exclude) {
                die(_("--[no-]exclude-standard cannot be used for tracked contents."));
diff --combined cache.h
index fbdf7a815a1f6b56df82877d0a5b2febe5a7d095,c7aa6f4bfe4518ee14249e2436238edd4443afcc..5c8078291c478f579d6b8bcc69417ac6adb1ced0
+++ b/cache.h
@@@ -10,8 -10,8 +10,8 @@@
  #include "trace.h"
  #include "string-list.h"
  #include "pack-revindex.h"
 +#include "hash.h"
  
 -#include SHA1_HEADER
  #ifndef platform_SHA_CTX
  /*
   * platform's underlying implementation of SHA-1; could be OpenSSL,
@@@ -343,7 -343,6 +343,7 @@@ struct index_state 
  extern struct index_state the_index;
  
  /* Name hashing */
 +extern int test_lazy_init_name_hash(struct index_state *istate, int try_threaded);
  extern void add_name_hash(struct index_state *istate, struct cache_entry *ce);
  extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce);
  extern void free_name_hash(struct index_state *istate);
@@@ -411,6 -410,7 +411,7 @@@ static inline enum object_type object_t
  #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
  #define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
  #define GIT_SUPER_PREFIX_ENVIRONMENT "GIT_INTERNAL_SUPER_PREFIX"
+ #define GIT_TOPLEVEL_PREFIX_ENVIRONMENT "GIT_INTERNAL_TOPLEVEL_PREFIX"
  #define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
  #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
  #define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
@@@ -519,30 -519,11 +520,30 @@@ extern void set_git_work_tree(const cha
  #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
  
  extern void setup_work_tree(void);
 +/*
 + * Find GIT_DIR of the repository that contains the current working directory,
 + * without changing the working directory or other global state. The result is
 + * appended to gitdir. The return value is either NULL if no repository was
 + * found, or pointing to the path inside gitdir's buffer.
 + */
 +extern const char *discover_git_directory(struct strbuf *gitdir);
  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);
 +
 +/*
 + * Concatenate "prefix" (if len is non-zero) and "path", with no
 + * connecting characters (so "prefix" should end with a "/").
 + * Unlike prefix_path, this should be used if the named file does
 + * not have to interact with index entry; i.e. name of a random file
 + * on the filesystem.
 + *
 + * The return value is always a newly allocated string (even if the
 + * prefix was empty).
 + */
 +extern char *prefix_filename(const char *prefix, const char *path);
 +
  extern int check_filename(const char *prefix, const char *name);
  extern void verify_filename(const char *prefix,
                            const char *name,
@@@ -1065,6 -1046,9 +1066,6 @@@ static inline int is_empty_tree_oid(con
        return !hashcmp(oid->hash, EMPTY_TREE_SHA1_BIN);
  }
  
 -
 -int git_mkstemp(char *path, size_t n, const char *template);
 -
  /* set default permissions by passing mode arguments to open(2) */
  int git_mkstemps_mode(char *pattern, int suffix_len, int mode);
  int git_mkstemp_mode(char *pattern, int mode);
@@@ -1089,9 -1073,8 +1090,9 @@@ int adjust_shared_perm(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.
 + * somewhat safe against races. Return one of the scld_error values to
 + * indicate success/failure. On error, set errno to describe the
 + * problem.
   *
   * SCLD_VANISHED indicates that one of the ancestor directories of the
   * path existed at one point during the function call and then
@@@ -1115,49 -1098,6 +1116,49 @@@ enum scld_error 
  enum scld_error safe_create_leading_directories(char *path);
  enum scld_error safe_create_leading_directories_const(const char *path);
  
 +/*
 + * Callback function for raceproof_create_file(). This function is
 + * expected to do something that makes dirname(path) permanent despite
 + * the fact that other processes might be cleaning up empty
 + * directories at the same time. Usually it will create a file named
 + * path, but alternatively it could create another file in that
 + * directory, or even chdir() into that directory. The function should
 + * return 0 if the action was completed successfully. On error, it
 + * should return a nonzero result and set errno.
 + * raceproof_create_file() treats two errno values specially:
 + *
 + * - ENOENT -- dirname(path) does not exist. In this case,
 + *             raceproof_create_file() tries creating dirname(path)
 + *             (and any parent directories, if necessary) and calls
 + *             the function again.
 + *
 + * - EISDIR -- the file already exists and is a directory. In this
 + *             case, raceproof_create_file() removes the directory if
 + *             it is empty (and recursively any empty directories that
 + *             it contains) and calls the function again.
 + *
 + * Any other errno causes raceproof_create_file() to fail with the
 + * callback's return value and errno.
 + *
 + * Obviously, this function should be OK with being called again if it
 + * fails with ENOENT or EISDIR. In other scenarios it will not be
 + * called again.
 + */
 +typedef int create_file_fn(const char *path, void *cb);
 +
 +/*
 + * Create a file in dirname(path) by calling fn, creating leading
 + * directories if necessary. Retry a few times in case we are racing
 + * with another process that is trying to clean up the directory that
 + * contains path. See the documentation for create_file_fn for more
 + * details.
 + *
 + * Return the value and set the errno that resulted from the most
 + * recent call of fn. fn is always called at least once, and will be
 + * called more than once if it returns ENOENT or EISDIR.
 + */
 +int raceproof_create_file(const char *path, create_file_fn fn, void *cb);
 +
  int mkdir_in_gitdir(const char *path);
  extern char *expand_user_path(const char *path);
  const char *enter_repo(const char *path, int strict);
@@@ -1170,7 -1110,7 +1171,7 @@@ char *strbuf_realpath(struct strbuf *re
                      int die_on_error);
  const char *real_path(const char *path);
  const char *real_path_if_valid(const char *path);
 -char *real_pathdup(const char *path);
 +char *real_pathdup(const char *path, int die_on_error);
  const char *absolute_path(const char *path);
  char *absolute_pathdup(const char *path);
  const char *remove_leading_path(const char *in, const char *prefix);
@@@ -1189,13 -1129,6 +1190,13 @@@ extern int is_ntfs_dotgit(const char *n
   */
  extern char *xdg_config_home(const char *filename);
  
 +/**
 + * Return a newly allocated string with the evaluation of
 + * "$XDG_CACHE_HOME/git/$filename" if $XDG_CACHE_HOME is non-empty, otherwise
 + * "$HOME/.cache/git/$filename". Return NULL upon error.
 + */
 +extern char *xdg_cache_home(const char *filename);
 +
  /* object replacement */
  #define LOOKUP_REPLACE_OBJECT 1
  #define LOOKUP_UNKNOWN_OBJECT 2
@@@ -1297,9 -1230,6 +1298,9 @@@ extern int has_pack_index(const unsigne
  
  extern void assert_sha1_type(const unsigned char *sha1, enum object_type expect);
  
 +/* Helper to check and "touch" a file */
 +extern int check_and_freshen_file(const char *fn, int freshen);
 +
  extern const signed char hexval_table[256];
  static inline unsigned int hexval(unsigned char c)
  {
@@@ -1390,46 -1320,7 +1391,46 @@@ extern char *oid_to_hex_r(char *out, co
  extern char *sha1_to_hex(const unsigned char *sha1);  /* static buffer result! */
  extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */
  
 -extern int interpret_branch_name(const char *str, int len, struct strbuf *);
 +/*
 + * Parse a 40-character hexadecimal object ID starting from hex, updating the
 + * pointer specified by end when parsing stops.  The resulting object ID is
 + * stored in oid.  Returns 0 on success.  Parsing will stop on the first NUL or
 + * other invalid character.  end is only updated on success; otherwise, it is
 + * unmodified.
 + */
 +extern int parse_oid_hex(const char *hex, struct object_id *oid, const char **end);
 +
 +/*
 + * This reads short-hand syntax that not only evaluates to a commit
 + * object name, but also can act as if the end user spelled the name
 + * of the branch from the command line.
 + *
 + * - "@{-N}" finds the name of the Nth previous branch we were on, and
 + *   places the name of the branch in the given buf and returns the
 + *   number of characters parsed if successful.
 + *
 + * - "<branch>@{upstream}" finds the name of the other ref that
 + *   <branch> is configured to merge with (missing <branch> defaults
 + *   to the current branch), and places the name of the branch in the
 + *   given buf and returns the number of characters parsed if
 + *   successful.
 + *
 + * If the input is not of the accepted format, it returns a negative
 + * number to signal an error.
 + *
 + * If the input was ok but there are not N branch switches in the
 + * reflog, it returns 0.
 + *
 + * If "allowed" is non-zero, it is a treated as a bitfield of allowable
 + * expansions: local branches ("refs/heads/"), remote branches
 + * ("refs/remotes/"), or "HEAD". If no "allowed" bits are set, any expansion is
 + * allowed, even ones to refs outside of those namespaces.
 + */
 +#define INTERPRET_BRANCH_LOCAL (1<<0)
 +#define INTERPRET_BRANCH_REMOTE (1<<1)
 +#define INTERPRET_BRANCH_HEAD (1<<2)
 +extern int interpret_branch_name(const char *str, int len, struct strbuf *,
 +                               unsigned allowed);
  extern int get_oid_mb(const char *str, struct object_id *oid);
  
  extern int validate_headref(const char *ref);
@@@ -1673,27 -1564,6 +1674,27 @@@ extern struct packed_git *find_sha1_pac
  
  extern void pack_report(void);
  
 +/*
 + * Create a temporary file rooted in the object database directory.
 + */
 +extern int odb_mkstemp(char *template, size_t limit, const char *pattern);
 +
 +/*
 + * Generate the filename to be used for a pack file with checksum "sha1" and
 + * extension "ext". The result is written into the strbuf "buf", overwriting
 + * any existing contents. A pointer to buf->buf is returned as a convenience.
 + *
 + * Example: odb_pack_name(out, sha1, "idx") => ".git/objects/pack/pack-1234..idx"
 + */
 +extern char *odb_pack_name(struct strbuf *buf, const unsigned char *sha1, const char *ext);
 +
 +/*
 + * Create a pack .keep file named "name" (which should generally be the output
 + * of odb_pack_name). Returns a file descriptor opened for writing, or -1 on
 + * error.
 + */
 +extern int odb_pack_keep(const char *name);
 +
  /*
   * mmap the index file for the specified packfile (if it is not
   * already mmapped).  Return 0 on success.
@@@ -1730,12 -1600,6 +1731,12 @@@ extern void check_pack_index_ptr(const 
   * error.
   */
  extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t n);
 +/*
 + * Like nth_packed_object_sha1, but write the data into the object specified by
 + * the the first argument.  Returns the first argument on success, and NULL on
 + * error.
 + */
 +extern const struct object_id *nth_packed_object_oid(struct object_id *, struct packed_git *, uint32_t n);
  
  /*
   * Return the offset of the nth object within the specified packfile.
@@@ -1777,7 -1641,7 +1778,7 @@@ extern int unpack_object_header(struct 
   * scratch buffer, but restored to its original contents before
   * the function returns.
   */
 -typedef int each_loose_object_fn(const unsigned char *sha1,
 +typedef int each_loose_object_fn(const struct object_id *oid,
                                 const char *path,
                                 void *data);
  typedef int each_loose_cruft_fn(const char *basename,
@@@ -1803,7 -1667,7 +1804,7 @@@ int for_each_loose_file_in_objdir_buf(s
   * LOCAL_ONLY flag is set).
   */
  #define FOR_EACH_OBJECT_LOCAL_ONLY 0x1
 -typedef int each_packed_object_fn(const unsigned char *sha1,
 +typedef int each_packed_object_fn(const struct object_id *oid,
                                  struct packed_git *pack,
                                  uint32_t pos,
                                  void *data);
@@@ -1890,7 -1754,6 +1891,7 @@@ extern int git_config_from_blob_sha1(co
                                     const unsigned char *sha1, void *data);
  extern void git_config_push_parameter(const char *text);
  extern int git_config_from_parameters(config_fn_t fn, void *data);
 +extern void read_early_config(config_fn_t cb, void *data);
  extern void git_config(config_fn_t fn, void *);
  extern int git_config_with_options(config_fn_t fn, void *,
                                   struct git_config_source *config_source,
@@@ -1957,11 -1820,8 +1958,11 @@@ extern int git_config_include(const cha
   *
   * (i.e., what gets handed to a config_fn_t). The caller provides the section;
   * we return -1 if it does not match, 0 otherwise. The subsection and key
 - * out-parameters are filled by the function (and subsection is NULL if it is
 + * out-parameters are filled by the function (and *subsection is NULL if it is
   * missing).
 + *
 + * If the subsection pointer-to-pointer passed in is NULL, returns 0 only if
 + * there is no subsection at all.
   */
  extern int parse_config_key(const char *var,
                            const char *section,
@@@ -2023,11 -1883,6 +2024,11 @@@ extern int git_config_get_bool_or_int(c
  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);
 +extern int git_config_get_split_index(void);
 +extern int git_config_get_max_percent_split_change(void);
 +
 +/* This dies if the configured or default date is in the future */
 +extern int git_config_get_expiry(const char *key, const char **output);
  
  /*
   * This is a hack for test programs like test-dump-untracked-cache to
diff --combined git.c
index 33f52acbcc8647e10e5d675806ca03d33fed899d,51bad4127412d5a7631afe0d03fbf6c9feeb9ba7..8ff44f081d43176474b267de5451f2c2e88089d0
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -361,8 -361,6 +361,6 @@@ static int run_builtin(struct cmd_struc
        if (!help && get_super_prefix()) {
                if (!(p->option & SUPPORT_SUPER_PREFIX))
                        die("%s doesn't support --super-prefix", p->cmd);
-               if (prefix)
-                       die("can't use --super-prefix from a subdirectory");
        }
  
        if (!help && p->option & NEED_WORK_TREE)
@@@ -473,7 -471,6 +471,7 @@@ static struct cmd_struct commands[] = 
        { "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE },
        { "push", cmd_push, RUN_SETUP },
        { "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX},
 +      { "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE },
        { "receive-pack", cmd_receive_pack },
        { "reflog", cmd_reflog, RUN_SETUP },
        { "remote", cmd_remote, RUN_SETUP },
diff --combined setup.c
index 5c7946d2b45f067049e11347cca1cedc9790e228,56cd68ba93c47a64529bb811e21ae7bbbdabb38c..0309c278218f96cb6b11e6a9d60011efce54cf62
+++ b/setup.c
@@@ -135,7 -135,6 +135,7 @@@ int path_inside_repo(const char *prefix
  int check_filename(const char *prefix, const char *arg)
  {
        const char *name;
 +      char *to_free = NULL;
        struct stat st;
  
        if (starts_with(arg, ":/")) {
                        return 1;
                name = arg + 2;
        } else if (prefix)
 -              name = prefix_filename(prefix, strlen(prefix), arg);
 +              name = to_free = prefix_filename(prefix, arg);
        else
                name = arg;
 -      if (!lstat(name, &st))
 +      if (!lstat(name, &st)) {
 +              free(to_free);
                return 1; /* file exists */
 -      if (errno == ENOENT || errno == ENOTDIR)
 +      }
 +      if (errno == ENOENT || errno == ENOTDIR) {
 +              free(to_free);
                return 0; /* file does not exist */
 +      }
        die_errno("failed to stat '%s'", arg);
  }
  
@@@ -259,7 -254,7 +259,7 @@@ int get_common_dir_noenv(struct strbuf 
                if (!is_absolute_path(data.buf))
                        strbuf_addf(&path, "%s/", gitdir);
                strbuf_addbuf(&path, &data);
 -              strbuf_addstr(sb, real_path(path.buf));
 +              strbuf_add_real_path(sb, path.buf);
                ret = 1;
        } else {
                strbuf_addstr(sb, gitdir);
@@@ -536,7 -531,6 +536,7 @@@ const char *read_gitfile_gently(const c
        ssize_t len;
  
        if (stat(path, &st)) {
 +              /* NEEDSWORK: discern between ENOENT vs other errors */
                error_code = READ_GITFILE_ERR_STAT_FAILED;
                goto cleanup_return;
        }
@@@ -704,7 -698,7 +704,7 @@@ static const char *setup_discovered_git
        /* --work-tree is set without --git-dir; use discovered one */
        if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
                if (offset != cwd->len && !is_absolute_path(gitdir))
 -                      gitdir = real_pathdup(gitdir);
 +                      gitdir = real_pathdup(gitdir, 1);
                if (chdir(cwd->buf))
                        die_errno("Could not come back to cwd");
                return setup_explicit_git_dir(gitdir, cwd, nongit_ok);
        if (offset == cwd->len)
                return NULL;
  
 -      /* Make "offset" point to past the '/', and add a '/' at the end */
 -      offset++;
 +      /* Make "offset" point past the '/' (already the case for root dirs) */
 +      if (offset != offset_1st_component(cwd->buf))
 +              offset++;
 +      /* Add a '/' at the end */
        strbuf_addch(cwd, '/');
        return cwd->buf + offset;
  }
@@@ -814,7 -806,7 +814,7 @@@ static int canonicalize_ceiling_entry(s
                /* Keep entry but do not canonicalize it */
                return 1;
        } else {
 -              char *real_path = real_pathdup(ceil);
 +              char *real_path = real_pathdup(ceil, 0);
                if (!real_path) {
                        return 0;
                }
        }
  }
  
 +enum discovery_result {
 +      GIT_DIR_NONE = 0,
 +      GIT_DIR_EXPLICIT,
 +      GIT_DIR_DISCOVERED,
 +      GIT_DIR_BARE,
 +      /* these are errors */
 +      GIT_DIR_HIT_CEILING = -1,
 +      GIT_DIR_HIT_MOUNT_POINT = -2,
 +      GIT_DIR_INVALID_GITFILE = -3
 +};
 +
  /*
   * We cannot decide in this function whether we are in the work tree or
   * not, since the config can only be read _after_ this function was called.
 + *
 + * Also, we avoid changing any global state (such as the current working
 + * directory) to allow early callers.
 + *
 + * The directory where the search should start needs to be passed in via the
 + * `dir` parameter; upon return, the `dir` buffer will contain the path of
 + * the directory where the search ended, and `gitdir` will contain the path of
 + * the discovered .git/ directory, if any. If `gitdir` is not absolute, it
 + * is relative to `dir` (i.e. *not* necessarily the cwd).
   */
 -static const char *setup_git_directory_gently_1(int *nongit_ok)
 +static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
 +                                                        struct strbuf *gitdir,
 +                                                        int die_on_error)
  {
        const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
        struct string_list ceiling_dirs = STRING_LIST_INIT_DUP;
 -      static struct strbuf cwd = STRBUF_INIT;
 -      const char *gitdirenv, *ret;
 -      char *gitfile;
 -      int offset, offset_parent, ceil_offset = -1;
 +      const char *gitdirenv;
 +      int ceil_offset = -1, min_offset = has_dos_drive_prefix(dir->buf) ? 3 : 1;
        dev_t current_device = 0;
        int one_filesystem = 1;
  
 -      /*
 -       * We may have read an incomplete configuration before
 -       * setting-up the git directory. If so, clear the cache so
 -       * that the next queries to the configuration reload complete
 -       * configuration (including the per-repo config file that we
 -       * ignored previously).
 -       */
 -      git_config_clear();
 -
 -      /*
 -       * Let's assume that we are in a git repository.
 -       * If it turns out later that we are somewhere else, the value will be
 -       * updated accordingly.
 -       */
 -      if (nongit_ok)
 -              *nongit_ok = 0;
 -
 -      if (strbuf_getcwd(&cwd))
 -              die_errno(_("Unable to read current working directory"));
 -      offset = cwd.len;
 -
        /*
         * If GIT_DIR is set explicitly, we're not going
         * to do any discovery, but we still do repository
         * validation.
         */
        gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
 -      if (gitdirenv)
 -              return setup_explicit_git_dir(gitdirenv, &cwd, nongit_ok);
 +      if (gitdirenv) {
 +              strbuf_addstr(gitdir, gitdirenv);
 +              return GIT_DIR_EXPLICIT;
 +      }
  
        if (env_ceiling_dirs) {
                int empty_entry_found = 0;
                string_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);
                filter_string_list(&ceiling_dirs, 0,
                                   canonicalize_ceiling_entry, &empty_entry_found);
 -              ceil_offset = longest_ancestor_length(cwd.buf, &ceiling_dirs);
 +              ceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs);
                string_list_clear(&ceiling_dirs, 0);
        }
  
 -      if (ceil_offset < 0 && has_dos_drive_prefix(cwd.buf))
 -              ceil_offset = 1;
 +      if (ceil_offset < 0)
 +              ceil_offset = min_offset - 2;
  
        /*
 -       * Test in the following order (relative to the cwd):
 +       * Test in the following order (relative to the dir):
         * - .git (file containing "gitdir: <path>")
         * - .git/
         * - ./ (bare)
         */
        one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0);
        if (one_filesystem)
 -              current_device = get_device_or_die(".", NULL, 0);
 +              current_device = get_device_or_die(dir->buf, NULL, 0);
        for (;;) {
 -              gitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);
 -              if (gitfile)
 -                      gitdirenv = gitfile = xstrdup(gitfile);
 -              else {
 -                      if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))
 -                              gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;
 +              int offset = dir->len, error_code = 0;
 +
 +              if (offset > min_offset)
 +                      strbuf_addch(dir, '/');
 +              strbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);
 +              gitdirenv = read_gitfile_gently(dir->buf, die_on_error ?
 +                                              NULL : &error_code);
 +              if (!gitdirenv) {
 +                      if (die_on_error ||
 +                          error_code == READ_GITFILE_ERR_NOT_A_FILE) {
 +                              /* NEEDSWORK: fail if .git is not file nor dir */
 +                              if (is_git_directory(dir->buf))
 +                                      gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;
 +                      } else if (error_code != READ_GITFILE_ERR_STAT_FAILED)
 +                              return GIT_DIR_INVALID_GITFILE;
                }
 -
 +              strbuf_setlen(dir, offset);
                if (gitdirenv) {
 -                      ret = setup_discovered_git_dir(gitdirenv,
 -                                                     &cwd, offset,
 -                                                     nongit_ok);
 -                      free(gitfile);
 -                      return ret;
 +                      strbuf_addstr(gitdir, gitdirenv);
 +                      return GIT_DIR_DISCOVERED;
                }
 -              free(gitfile);
  
 -              if (is_git_directory("."))
 -                      return setup_bare_git_dir(&cwd, offset, nongit_ok);
 -
 -              offset_parent = offset;
 -              while (--offset_parent > ceil_offset && cwd.buf[offset_parent] != '/');
 -              if (offset_parent <= ceil_offset)
 -                      return setup_nongit(cwd.buf, nongit_ok);
 -              if (one_filesystem) {
 -                      dev_t parent_device = get_device_or_die("..", cwd.buf,
 -                                                              offset);
 -                      if (parent_device != current_device) {
 -                              if (nongit_ok) {
 -                                      if (chdir(cwd.buf))
 -                                              die_errno(_("Cannot come back to cwd"));
 -                                      *nongit_ok = 1;
 -                                      return NULL;
 -                              }
 -                              strbuf_setlen(&cwd, offset);
 -                              die(_("Not a git repository (or any parent up to mount point %s)\n"
 -                              "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)."),
 -                                  cwd.buf);
 -                      }
 -              }
 -              if (chdir("..")) {
 -                      strbuf_setlen(&cwd, offset);
 -                      die_errno(_("Cannot change to '%s/..'"), cwd.buf);
 +              if (is_git_directory(dir->buf)) {
 +                      strbuf_addstr(gitdir, ".");
 +                      return GIT_DIR_BARE;
                }
 -              offset = offset_parent;
 +
 +              if (offset <= min_offset)
 +                      return GIT_DIR_HIT_CEILING;
 +
 +              while (--offset > ceil_offset && !is_dir_sep(dir->buf[offset]))
 +                      ; /* continue */
 +              if (offset <= ceil_offset)
 +                      return GIT_DIR_HIT_CEILING;
 +
 +              strbuf_setlen(dir, offset > min_offset ?  offset : min_offset);
 +              if (one_filesystem &&
 +                  current_device != get_device_or_die(dir->buf, NULL, offset))
 +                      return GIT_DIR_HIT_MOUNT_POINT;
 +      }
 +}
 +
 +const char *discover_git_directory(struct strbuf *gitdir)
 +{
 +      struct strbuf dir = STRBUF_INIT, err = STRBUF_INIT;
 +      size_t gitdir_offset = gitdir->len, cwd_len;
 +      struct repository_format candidate;
 +
 +      if (strbuf_getcwd(&dir))
 +              return NULL;
 +
 +      cwd_len = dir.len;
 +      if (setup_git_directory_gently_1(&dir, gitdir, 0) <= 0) {
 +              strbuf_release(&dir);
 +              return NULL;
        }
 +
 +      /*
 +       * The returned gitdir is relative to dir, and if dir does not reflect
 +       * the current working directory, we simply make the gitdir absolute.
 +       */
 +      if (dir.len < cwd_len && !is_absolute_path(gitdir->buf + gitdir_offset)) {
 +              /* Avoid a trailing "/." */
 +              if (!strcmp(".", gitdir->buf + gitdir_offset))
 +                      strbuf_setlen(gitdir, gitdir_offset);
 +              else
 +                      strbuf_addch(&dir, '/');
 +              strbuf_insert(gitdir, gitdir_offset, dir.buf, dir.len);
 +      }
 +
 +      strbuf_reset(&dir);
 +      strbuf_addf(&dir, "%s/config", gitdir->buf + gitdir_offset);
 +      read_repository_format(&candidate, dir.buf);
 +      strbuf_release(&dir);
 +
 +      if (verify_repository_format(&candidate, &err) < 0) {
 +              warning("ignoring git dir '%s': %s",
 +                      gitdir->buf + gitdir_offset, err.buf);
 +              strbuf_release(&err);
 +              return NULL;
 +      }
 +
 +      return gitdir->buf + gitdir_offset;
  }
  
  const char *setup_git_directory_gently(int *nongit_ok)
  {
-       const char *prefix;
 +      static struct strbuf cwd = STRBUF_INIT;
 +      struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT;
+       const char *prefix, *env_prefix;
  
 -      prefix = setup_git_directory_gently_1(nongit_ok);
 -      env_prefix = getenv(GIT_TOPLEVEL_PREFIX_ENVIRONMENT);
 +      /*
 +       * We may have read an incomplete configuration before
 +       * setting-up the git directory. If so, clear the cache so
 +       * that the next queries to the configuration reload complete
 +       * configuration (including the per-repo config file that we
 +       * ignored previously).
 +       */
 +      git_config_clear();
 +
 +      /*
 +       * Let's assume that we are in a git repository.
 +       * If it turns out later that we are somewhere else, the value will be
 +       * updated accordingly.
 +       */
 +      if (nongit_ok)
 +              *nongit_ok = 0;
  
 +      if (strbuf_getcwd(&cwd))
 +              die_errno(_("Unable to read current working directory"));
 +      strbuf_addbuf(&dir, &cwd);
 +
 +      switch (setup_git_directory_gently_1(&dir, &gitdir, 1)) {
 +      case GIT_DIR_NONE:
 +              prefix = NULL;
 +              break;
 +      case GIT_DIR_EXPLICIT:
 +              prefix = setup_explicit_git_dir(gitdir.buf, &cwd, nongit_ok);
 +              break;
 +      case GIT_DIR_DISCOVERED:
 +              if (dir.len < cwd.len && chdir(dir.buf))
 +                      die(_("Cannot change to '%s'"), dir.buf);
 +              prefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len,
 +                                                nongit_ok);
 +              break;
 +      case GIT_DIR_BARE:
 +              if (dir.len < cwd.len && chdir(dir.buf))
 +                      die(_("Cannot change to '%s'"), dir.buf);
 +              prefix = setup_bare_git_dir(&cwd, dir.len, nongit_ok);
 +              break;
 +      case GIT_DIR_HIT_CEILING:
 +              prefix = setup_nongit(cwd.buf, nongit_ok);
 +              break;
 +      case GIT_DIR_HIT_MOUNT_POINT:
 +              if (nongit_ok) {
 +                      *nongit_ok = 1;
 +                      strbuf_release(&cwd);
 +                      strbuf_release(&dir);
 +                      return NULL;
 +              }
 +              die(_("Not a git repository (or any parent up to mount point %s)\n"
 +                    "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)."),
 +                  dir.buf);
 +      default:
 +              die("BUG: unhandled setup_git_directory_1() result");
 +      }
 +
++      env_prefix = getenv(GIT_TOPLEVEL_PREFIX_ENVIRONMENT);
+       if (env_prefix)
+               prefix = env_prefix;
        if (prefix)
                setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);
        else
        startup_info->have_repository = !nongit_ok || !*nongit_ok;
        startup_info->prefix = prefix;
  
 +      strbuf_release(&dir);
 +      strbuf_release(&gitdir);
 +
        return prefix;
  }