These patches are an attempt to make Git's startup sequence a bit less
surprising.

The idea here is to discover the .git/ directory gently (i.e. without
changing the current working directory, or global variables), and to use
it to read the .git/config file early, before we actually called
setup_git_directory() (if we ever do that).

This also allows us to fix the early config e.g. to determine the pager
or to resolve aliases in a non-surprising manner.

The first iteration of this patch series still tried to be clever and to
avoid having to disentangle the side effects from the
setup_git_directory_gently_1() function simply by duplicating the logic.

However, Peff suggested in a very short sentence that this would not fly
well.

Little did I know that I would spend the better part of an entire week
on trying to address that innocuous comment! There are simply *so many*
side effects in that code. Who would have thought that a function
called check_repository_format() would set global variables?

But after all that work, I am actually quite a bit satisfied with the
way things turned out.

My dirty little secret is that I actually need this for something else
entirely. I need to patch an internal version of Git to gather
statistics, and to that end I need to read the config before and after
running every Git command. Hence the need for a gentle, and correct
early config.

Notes:

- I do not handle dashed invocations of `init` and `clone` correctly.
  That is, even if `git-init` and `git-clone` clearly do not want to
  read the local config, they do. It does not matter all that much
  because they do not use a pager, but still. It is a wart.

- The read_early_config() function is still called multiple times,
  re-reading all the config files and re-discovering the .git/ directory
  multiple times, which is quite wasteful. I was tempted to take care of
  that but I must not run the danger to spread myself even thinner these
  days. If a patch adding that caching were to fly my way, I'd gladly
  integrate it, of course... ;-)

Changes since v1:

- the discover_git_directory() function is no longer completely separate
  from setup_git_directory(), but a callee of the latter.

- t7006 succeeds now (I removed the incorrect test case in favor of one
  that verifies that setup_git_directory() was not run via the tell-tale
  that the current working directory has not changed when the pager
  runs).


Johannes Schindelin (9):
  t7006: replace dubious test
  setup_git_directory(): use is_dir_sep() helper
  setup_git_directory(): avoid changing global state during discovery
  Export the discover_git_directory() function
  Make read_early_config() reusable
  read_early_config(): special-case builtins that create a repository
  read_early_config(): avoid .git/config hack when unneeded
  read_early_config(): really discover .git/
  Test read_early_config()

 cache.h                 |   4 +-
 config.c                |  36 +++++++++
 git.c                   |   3 +
 pager.c                 |  31 -------
 setup.c                 | 211 +++++++++++++++++++++++++++++++-----------------
 t/helper/test-config.c  |  15 ++++
 t/t1309-early-config.sh |  50 ++++++++++++
 t/t7006-pager.sh        |  18 ++++-
 8 files changed, 257 insertions(+), 111 deletions(-)
 create mode 100755 t/t1309-early-config.sh


base-commit: 3bc53220cb2dcf709f7a027a3f526befd021d858
Published-As: https://github.com/dscho/git/releases/tag/early-config-v2
Fetch-It-Via: git fetch https://github.com/dscho/git early-config-v2

Interdiff vs v1:

 diff --git a/builtin/am.c b/builtin/am.c
 index 2b81dabddd9..f7a7a971fbe 100644
 --- a/builtin/am.c
 +++ b/builtin/am.c
 @@ -1791,7 +1791,7 @@ static int do_interactive(struct am_state *state)
                        }
                        strbuf_release(&msg);
                } else if (*reply == 'v' || *reply == 'V') {
 -                      const char *pager = git_pager(1, 1);
 +                      const char *pager = git_pager(1);
                        struct child_process cp = CHILD_PROCESS_INIT;
  
                        if (!pager)
 diff --git a/builtin/blame.c b/builtin/blame.c
 index 628ca237da6..cffc6265408 100644
 --- a/builtin/blame.c
 +++ b/builtin/blame.c
 @@ -2919,7 +2919,7 @@ int cmd_blame(int argc, const char **argv, const char 
*prefix)
        assign_blame(&sb, opt);
  
        if (!incremental)
 -              setup_pager(1);
 +              setup_pager();
  
        free(final_commit_name);
  
 diff --git a/builtin/grep.c b/builtin/grep.c
 index f820e4b1c4d..9304c33e750 100644
 --- a/builtin/grep.c
 +++ b/builtin/grep.c
 @@ -1133,7 +1133,7 @@ int cmd_grep(int argc, const char **argv, const char 
*prefix)
        }
  
        if (show_in_pager == default_pager)
 -              show_in_pager = git_pager(1, 1);
 +              show_in_pager = git_pager(1);
        if (show_in_pager) {
                opt.color = 0;
                opt.name_only = 1;
 @@ -1268,7 +1268,7 @@ int cmd_grep(int argc, const char **argv, const char 
*prefix)
                die(_("option not supported with --recurse-submodules."));
  
        if (!show_in_pager && !opt.status_only)
 -              setup_pager(1);
 +              setup_pager();
  
        if (!use_index && (untracked || cached))
                die(_("--cached or --untracked cannot be used with 
--no-index."));
 diff --git a/builtin/log.c b/builtin/log.c
 index 96618d38cbf..55d20cc2d88 100644
 --- a/builtin/log.c
 +++ b/builtin/log.c
 @@ -203,7 +203,7 @@ static void cmd_log_init_finish(int argc, const char 
**argv, const char *prefix,
        if (rev->line_level_traverse)
                line_log_init(rev, line_cb.prefix, &line_cb.args);
  
 -      setup_pager(1);
 +      setup_pager();
  }
  
  static void cmd_log_init(int argc, const char **argv, const char *prefix,
 @@ -1600,7 +1600,7 @@ int cmd_format_patch(int argc, const char **argv, const 
char *prefix)
        if (!use_stdout)
                output_directory = set_outdir(prefix, output_directory);
        else
 -              setup_pager(1);
 +              setup_pager();
  
        if (output_directory) {
                if (rev.diffopt.use_color != GIT_COLOR_ALWAYS)
 diff --git a/builtin/var.c b/builtin/var.c
 index 879867b8427..aedbb53a2da 100644
 --- a/builtin/var.c
 +++ b/builtin/var.c
 @@ -19,7 +19,7 @@ static const char *editor(int flag)
  
  static const char *pager(int flag)
  {
 -      const char *pgm = git_pager(1, 1);
 +      const char *pgm = git_pager(1);
  
        if (!pgm)
                pgm = "cat";
 diff --git a/cache.h b/cache.h
 index 4d89966d711..0af7141242f 100644
 --- a/cache.h
 +++ b/cache.h
 @@ -518,6 +518,7 @@ extern void set_git_work_tree(const char *tree);
  #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
  
  extern void setup_work_tree(void);
 +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);
 @@ -1428,7 +1429,7 @@ extern const char *fmt_name(const char *name, const char 
*email);
  extern const char *ident_default_name(void);
  extern const char *ident_default_email(void);
  extern const char *git_editor(void);
 -extern const char *git_pager(int stdout_is_tty, int discover_git_dir);
 +extern const char *git_pager(int stdout_is_tty);
  extern int git_ident_config(const char *, const char *, void *);
  extern void reset_ident_date(void);
  
 @@ -1797,8 +1798,7 @@ extern int git_config_from_blob_sha1(config_fn_t fn, 
const char *name,
                                     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,
 -                            int discover_git_dir);
 +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,
 @@ -1994,12 +1994,12 @@ __attribute__((format (printf, 2, 3)))
  extern void write_file(const char *path, const char *fmt, ...);
  
  /* pager.c */
 -extern void setup_pager(int discover_git_dir);
 +extern void setup_pager(void);
  extern int pager_in_use(void);
  extern int pager_use_color;
  extern int term_columns(void);
  extern int decimal_width(uintmax_t);
 -extern int check_pager_config(const char *cmd, int discover_git_dir);
 +extern int check_pager_config(const char *cmd);
  extern void prepare_pager_args(struct child_process *, const char *pager);
  
  extern const char *editor_program;
 @@ -2070,7 +2070,7 @@ const char *split_cmdline_strerror(int cmdline_errno);
  
  /* setup.c */
  struct startup_info {
 -      int have_repository;
 +      int have_repository, creating_repository;
        const char *prefix;
  };
  extern struct startup_info *startup_info;
 diff --git a/config.c b/config.c
 index c9f191e1fe3..bcda397d42e 100644
 --- a/config.c
 +++ b/config.c
 @@ -1412,106 +1412,32 @@ static void configset_iter(struct config_set *cs, 
config_fn_t fn, void *data)
        }
  }
  
 -/*
 - * A "string_list_each_func_t" function that canonicalizes an entry
 - * from GIT_CEILING_DIRECTORIES using real_path_if_valid(), or
 - * discards it if unusable.  The presence of an empty entry in
 - * GIT_CEILING_DIRECTORIES turns off canonicalization for all
 - * subsequent entries.
 - */
 -static int canonicalize_ceiling_entry(struct string_list_item *item,
 -                                    void *cb_data)
 -{
 -      int *empty_entry_found = cb_data;
 -      char *ceil = item->string;
 -
 -      if (!*ceil) {
 -              *empty_entry_found = 1;
 -              return 0;
 -      } else if (!is_absolute_path(ceil)) {
 -              return 0;
 -      } else if (*empty_entry_found) {
 -              /* Keep entry but do not canonicalize it */
 -              return 1;
 -      } else {
 -              const char *real_path = real_path_if_valid(ceil);
 -              if (!real_path)
 -                      return 0;
 -              free(item->string);
 -              item->string = xstrdup(real_path);
 -              return 1;
 -      }
 -}
 -
 -/*
 - * Note that this is a really dirty hack that replicates what the
 - * setup_git_directory() function does, without changing the current
 - * working directory. The crux of the problem is that we cannot run
 - * setup_git_directory() early on in git's setup, so we have to
 - * duplicate the work that setup_git_directory() would otherwise do.
 - */
 -static int discover_git_directory_gently(struct strbuf *result)
 -{
 -      const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
 -      int ceiling_offset = -1;
 -      const char *p;
 -
 -      if (strbuf_getcwd(result) < 0)
 -              return -1;
 -      p = real_path_if_valid(result->buf);
 -      if (!p)
 -              return -1;
 -      strbuf_reset(result);
 -      strbuf_addstr(result, p);
 -
 -      if (env_ceiling_dirs) {
 -              struct string_list ceiling_dirs = STRING_LIST_INIT_DUP;
 -              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);
 -              ceiling_offset = longest_ancestor_length(result->buf,
 -                                                       &ceiling_dirs);
 -              string_list_clear(&ceiling_dirs, 0);
 -      }
 -
 -      if (ceiling_offset < 0 && has_dos_drive_prefix(result->buf))
 -              ceiling_offset = 1;
 -
 -      for (;;) {
 -              int len = result->len, i;
 -
 -              strbuf_addstr(result, "/" DEFAULT_GIT_DIR_ENVIRONMENT);
 -              p = read_gitfile_gently(result->buf, &i);
 -              if (p) {
 -                      strbuf_reset(result);
 -                      strbuf_addstr(result, p);
 -                      return 0;
 -              }
 -              if (is_git_directory(result->buf))
 -                      return 0;
 -              strbuf_setlen(result, len);
 -              if (is_git_directory(result->buf))
 -                      return 0;
 -              for (i = len; --i > ceiling_offset; )
 -                      if (is_dir_sep(result->buf[i]))
 -                              break;
 -              if (i <= ceiling_offset)
 -                      return -1;
 -              strbuf_setlen(result, i);
 -      }
 -}
 -
 -void read_early_config(config_fn_t cb, void *data, int discover_git_dir)
 +void read_early_config(config_fn_t cb, void *data)
  {
        struct strbuf buf = STRBUF_INIT;
  
        git_config_with_options(cb, data, NULL, 1);
  
 -      if (discover_git_dir && !have_git_dir() &&
 -          !discover_git_directory_gently(&buf)) {
 +      /*
 +       * Note that this is a really dirty hack that does the wrong thing in
 +       * many cases. The crux of the problem is that we cannot run
 +       * setup_git_directory() early on in git's setup, so we have no idea if
 +       * we are in a repository or not, and therefore are not sure whether
 +       * and how to read repository-local config.
 +       *
 +       * So if we _aren't_ in a repository (or we are but we would reject its
 +       * core.repositoryformatversion), we'll read whatever is in .git/config
 +       * blindly. Similarly, if we _are_ in a repository, but not at the
 +       * root, we'll fail to find .git/config (because it's really
 +       * ../.git/config, etc). See t7006 for a complete set of failures.
 +       *
 +       * However, we have historically provided this hack because it does
 +       * work some of the time (namely when you are at the top-level of a
 +       * valid repository), and would rarely make things worse (i.e., you do
 +       * not generally have a .git/config file sitting around).
 +       */
 +      if (!startup_info->creating_repository && !have_git_dir() &&
 +          discover_git_directory(&buf)) {
                struct git_config_source repo_config;
  
                memset(&repo_config, 0, sizeof(repo_config));
 diff --git a/diff.c b/diff.c
 index 1e1d3b85c2a..051761be405 100644
 --- a/diff.c
 +++ b/diff.c
 @@ -5259,6 +5259,6 @@ void setup_diff_pager(struct diff_options *opt)
         * --exit-code" in hooks and other scripts, we do not do so.
         */
        if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
 -          check_pager_config("diff", 1) != 0)
 -              setup_pager(1);
 +          check_pager_config("diff") != 0)
 +              setup_pager();
  }
 diff --git a/git.c b/git.c
 index d4712b25fee..9fb9bb90a21 100644
 --- a/git.c
 +++ b/git.c
 @@ -61,13 +61,13 @@ static void restore_env(int external_alias)
        }
  }
  
 -static void commit_pager_choice(int discover_git_dir) {
 +static void commit_pager_choice(void) {
        switch (use_pager) {
        case 0:
                setenv("GIT_PAGER", "cat", 1);
                break;
        case 1:
 -              setup_pager(discover_git_dir);
 +              setup_pager();
                break;
        default:
                break;
 @@ -261,7 +261,7 @@ static int handle_alias(int *argcp, const char ***argv)
                if (alias_string[0] == '!') {
                        struct child_process child = CHILD_PROCESS_INIT;
  
 -                      commit_pager_choice(1);
 +                      commit_pager_choice();
                        restore_env(1);
  
                        child.use_shell = 1;
 @@ -318,13 +318,12 @@ static int handle_alias(int *argcp, const char ***argv)
  #define RUN_SETUP             (1<<0)
  #define RUN_SETUP_GENTLY      (1<<1)
  #define USE_PAGER             (1<<2)
 -#define CREATES_GIT_DIR         (1<<3)
  /*
   * require working tree to be present -- anything uses this needs
   * RUN_SETUP for reading from the configuration file.
   */
 -#define NEED_WORK_TREE                (1<<4)
 -#define SUPPORT_SUPER_PREFIX  (1<<5)
 +#define NEED_WORK_TREE                (1<<3)
 +#define SUPPORT_SUPER_PREFIX  (1<<4)
  
  struct cmd_struct {
        const char *cmd;
 @@ -338,6 +337,9 @@ static int run_builtin(struct cmd_struct *p, int argc, 
const char **argv)
        struct stat st;
        const char *prefix;
  
 +      if (p->fn == cmd_init_db || p->fn == cmd_clone)
 +              startup_info->creating_repository = 1;
 +
        prefix = NULL;
        help = argc == 2 && !strcmp(argv[1], "-h");
        if (!help) {
 @@ -349,7 +351,7 @@ static int run_builtin(struct cmd_struct *p, int argc, 
const char **argv)
                }
  
                if (use_pager == -1 && p->option & (RUN_SETUP | 
RUN_SETUP_GENTLY))
 -                      use_pager = check_pager_config(p->cmd, !(p->option & 
CREATES_GIT_DIR));
 +                      use_pager = check_pager_config(p->cmd);
                if (use_pager == -1 && p->option & USE_PAGER)
                        use_pager = 1;
  
 @@ -357,7 +359,7 @@ static int run_builtin(struct cmd_struct *p, int argc, 
const char **argv)
                    startup_info->have_repository) /* get_git_dir() may set up 
repo, avoid that */
                        trace_repo_setup(prefix);
        }
 -      commit_pager_choice(!(p->option & CREATES_GIT_DIR));
 +      commit_pager_choice();
  
        if (!help && get_super_prefix()) {
                if (!(p->option & SUPPORT_SUPER_PREFIX))
 @@ -413,7 +415,7 @@ static struct cmd_struct commands[] = {
        { "cherry", cmd_cherry, RUN_SETUP },
        { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
        { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
 -      { "clone", cmd_clone, CREATES_GIT_DIR },
 +      { "clone", cmd_clone },
        { "column", cmd_column, RUN_SETUP_GENTLY },
        { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
        { "commit-tree", cmd_commit_tree, RUN_SETUP },
 @@ -440,7 +442,7 @@ static struct cmd_struct commands[] = {
        { "hash-object", cmd_hash_object },
        { "help", cmd_help },
        { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY },
 -      { "init", cmd_init_db, CREATES_GIT_DIR },
 +      { "init", cmd_init_db },
        { "init-db", cmd_init_db },
        { "interpret-trailers", cmd_interpret_trailers, RUN_SETUP_GENTLY },
        { "log", cmd_log, RUN_SETUP },
 @@ -585,8 +587,8 @@ static void execv_dashed_external(const char **argv)
                die("%s doesn't support --super-prefix", argv[0]);
  
        if (use_pager == -1)
 -              use_pager = check_pager_config(argv[0], 1);
 -      commit_pager_choice(1);
 +              use_pager = check_pager_config(argv[0]);
 +      commit_pager_choice();
  
        argv_array_pushf(&cmd.args, "git-%s", argv[0]);
        argv_array_pushv(&cmd.args, argv + 1);
 @@ -684,7 +686,7 @@ int cmd_main(int argc, const char **argv)
                skip_prefix(argv[0], "--", &argv[0]);
        } else {
                /* The user didn't specify a command; give them help */
 -              commit_pager_choice(1);
 +              commit_pager_choice();
                printf("usage: %s\n\n", git_usage_string);
                list_common_cmds_help();
                printf("\n%s\n", _(git_more_info_string));
 diff --git a/pager.c b/pager.c
 index 16b3cbe2320..73ca8bc3b17 100644
 --- a/pager.c
 +++ b/pager.c
 @@ -43,7 +43,7 @@ static int core_pager_config(const char *var, const char 
*value, void *data)
        return 0;
  }
  
 -const char *git_pager(int stdout_is_tty, int discover_git_dir)
 +const char *git_pager(int stdout_is_tty)
  {
        const char *pager;
  
 @@ -53,8 +53,7 @@ const char *git_pager(int stdout_is_tty, int 
discover_git_dir)
        pager = getenv("GIT_PAGER");
        if (!pager) {
                if (!pager_program)
 -                      read_early_config(core_pager_config, NULL,
 -                                        discover_git_dir);
 +                      read_early_config(core_pager_config, NULL);
                pager = pager_program;
        }
        if (!pager)
 @@ -101,9 +100,9 @@ void prepare_pager_args(struct child_process 
*pager_process, const char *pager)
        setup_pager_env(&pager_process->env_array);
  }
  
 -void setup_pager(int discover_git_dir)
 +void setup_pager(void)
  {
 -      const char *pager = git_pager(isatty(1), discover_git_dir);
 +      const char *pager = git_pager(isatty(1));
  
        if (!pager)
                return;
 @@ -209,7 +208,7 @@ static int pager_command_config(const char *var, const 
char *value, void *vdata)
  }
  
  /* returns 0 for "no pager", 1 for "use pager", and -1 for "not specified" */
 -int check_pager_config(const char *cmd, int discover_git_dir)
 +int check_pager_config(const char *cmd)
  {
        struct pager_command_config_data data;
  
 @@ -217,7 +216,7 @@ int check_pager_config(const char *cmd, int 
discover_git_dir)
        data.want = -1;
        data.value = NULL;
  
 -      read_early_config(pager_command_config, &data, discover_git_dir);
 +      read_early_config(pager_command_config, &data);
  
        if (data.value)
                pager_program = data.value;
 diff --git a/setup.c b/setup.c
 index 967f289f1ef..7ceca6cc6ef 100644
 --- a/setup.c
 +++ b/setup.c
 @@ -816,50 +816,49 @@ static int canonicalize_ceiling_entry(struct 
string_list_item *item,
        }
  }
  
 +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
 +};
 +
  /*
   * 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. This path may be relative against
 + * `dir` (i.e. *not* necessarily the cwd).
   */
 -static const char *setup_git_directory_gently_1(int *nongit_ok)
 +static enum discovery_result discover_git_directory_1(struct strbuf *dir,
 +                                                    struct strbuf *gitdir)
  {
        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;
 @@ -867,15 +866,15 @@ static const char *setup_git_directory_gently_1(int 
*nongit_ok)
                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)
 @@ -887,61 +886,123 @@ static const char *setup_git_directory_gently_1(int 
*nongit_ok)
         */
        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;
 +
 +              if (offset > min_offset)
 +                      strbuf_addch(dir, '/');
 +              strbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);
 +              gitdirenv = read_gitfile(dir->buf);
 +              if (!gitdirenv && is_git_directory(dir->buf))
 +                      gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;
 +              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]));
 +              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;
 +      int len;
 +
 +      if (strbuf_getcwd(&dir))
 +              return NULL;
 +
 +      len = dir.len;
 +      if (discover_git_directory_1(&dir, gitdir) < 0) {
 +              strbuf_release(&dir);
 +              return NULL;
 +      }
 +
 +      if (dir.len < len && !is_absolute_path(gitdir->buf)) {
 +              strbuf_addch(&dir, '/');
 +              strbuf_insert(gitdir, 0, dir.buf, dir.len);
        }
 +      strbuf_release(&dir);
 +
 +      return gitdir->buf;
  }
  
  const char *setup_git_directory_gently(int *nongit_ok)
  {
 +      struct strbuf cwd = STRBUF_INIT, dir = STRBUF_INIT, gitdir = 
STRBUF_INIT;
        const char *prefix;
  
 -      prefix = setup_git_directory_gently_1(nongit_ok);
 +      /*
 +       * 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 (discover_git_directory_1(&dir, &gitdir)) {
 +      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;
 +                      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 discover_git_directory() result");
 +      }
 +
        if (prefix)
                setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);
        else
 diff --git a/t/helper/test-config.c b/t/helper/test-config.c
 index 51050695876..8e3ed6a76cb 100644
 --- a/t/helper/test-config.c
 +++ b/t/helper/test-config.c
 @@ -84,7 +84,7 @@ int cmd_main(int argc, const char **argv)
        struct config_set cs;
  
        if (argc == 3 && !strcmp(argv[1], "read_early_config")) {
 -              read_early_config(early_config_cb, (void *)argv[2], 1);
 +              read_early_config(early_config_cb, (void *)argv[2]);
                return 0;
        }
  
 diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh
 index c8dc665f2fd..bf89340988b 100755
 --- a/t/t7006-pager.sh
 +++ b/t/t7006-pager.sh
 @@ -360,27 +360,37 @@ test_pager_choices                       'git aliasedlog'
  test_default_pager        expect_success 'git -p aliasedlog'
  test_PAGER_overrides      expect_success 'git -p aliasedlog'
  test_core_pager_overrides expect_success 'git -p aliasedlog'
 -test_core_pager_subdir    expect_failure 'git -p aliasedlog'
 +test_core_pager_subdir    expect_success 'git -p aliasedlog'
  test_GIT_PAGER_overrides  expect_success 'git -p aliasedlog'
  
  test_default_pager        expect_success 'git -p true'
  test_PAGER_overrides      expect_success 'git -p true'
  test_core_pager_overrides expect_success 'git -p true'
 -test_core_pager_subdir    expect_failure 'git -p true'
 +test_core_pager_subdir    expect_success 'git -p true'
  test_GIT_PAGER_overrides  expect_success 'git -p true'
  
  test_default_pager        expect_success test_must_fail 'git -p request-pull'
  test_PAGER_overrides      expect_success test_must_fail 'git -p request-pull'
  test_core_pager_overrides expect_success test_must_fail 'git -p request-pull'
 -test_core_pager_subdir    expect_failure test_must_fail 'git -p request-pull'
 +test_core_pager_subdir    expect_success test_must_fail 'git -p request-pull'
  test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p request-pull'
  
  test_default_pager        expect_success test_must_fail 'git -p'
  test_PAGER_overrides      expect_success test_must_fail 'git -p'
  test_local_config_ignored expect_failure test_must_fail 'git -p'
 -test_no_local_config_subdir expect_success test_must_fail 'git -p'
  test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p'
  
 +test_expect_success TTY 'core.pager in repo config works and retains cwd' '
 +      sane_unset GIT_PAGER &&
 +      test_config core.pager "cat >cwd-retained" &&
 +      (
 +              cd sub &&
 +              rm -f cwd-retained &&
 +              test_terminal git -p rev-parse HEAD &&
 +              test -e cwd-retained
 +      )
 +'
 +
  test_doesnt_paginate      expect_failure test_must_fail 'git -p nonsense'
  
  test_pager_choices                       'git shortlog'

-- 
2.12.0.windows.1.3.g8a117c48243

Reply via email to