{"thread":{"id":"44653","subject":"[PATCH/RFC 0/7] Pie-in-the-sky attempt to fix the early config","startedAt":"2016-12-08T15:36:25Z","lastAt":"2017-03-14T18:01:08Z","messageCount":123,"participants":["Johannes Schindelin","Jeff King","Duy Nguyen","Junio C Hamano","Brandon Williams"],"isPatch":true,"patchVersion":1,"patchTotal":7},"messages":[{"id":"307254","messageId":"cover.1481211338.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":null,"subject":"[PATCH/RFC 0/7] Pie-in-the-sky attempt to fix the early config","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-12-08T15:35:56Z","receivedAt":"2016-12-08T15:36:25Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hopefully these patches will lead to something that we can integrate,\nand that eventually will make Git's startup sequence much less\nsurprising.\n\nThe idea here is to discover the .git/ directory gently (i.e. without\nchanging the current working directory), and to use it to read the\n.git/config file early, before we actually called setup_git_directory()\n(if we ever do that).\n\nNotes:\n\n- I find the diff pretty ugly: I wish there was a more elegant way to\n  *disable* discovery of .git/ *just* for `init` and `clone`. I\n  considered a function `about_to_create_git_dir()` that is called in a\n  hard-coded manner *only* for `init` and `clone`, but that would\n  introduce another magic side effect, when all I want is to reduce those.\n\n- For the moment, I do not handle dashed invocations of `init` and\n  `clone` correctly. The real problem is the continued existence of\n  the dashed git-init and git-clone, of course.\n\n- There is still duplicated code. For the sake of this RFC, I did not\n  address that yet.\n\n- The read_early_config() function is called multiple times, re-reading\n  all the config files and re-discovering the .git/ directory multiple\n  times, which is quite wasteful. For the sake of this RFC, I did not\n  address that yet.\n\n- t7006 fails and the error message is a bit cryptic (not to mention the\n  involved function trace, which is mind-boggling for what is supposed\n  to be a simply, shell script-based test suite). I did not have time to\n  look into that yet.\n\n- after discover_git_directory_gently() did its work, the code happily\n  uses its result *only* for the current read_early_config() run, and\n  lets setup_git_dir_gently() do the whole work *again*. For the sake of\n  this RFC, I did not address that yet.\n\n\nJohannes Schindelin (7):\n  Make read_early_config() reusable\n  read_early_config(): avoid .git/config hack when unneeded\n  Mark builtins that create .git/ directories\n  read_early_config(): special-case `init` and `clone`\n  read_early_config(): really discover .git/\n  WIP read_config_early(): respect ceiling directories\n  WIP: read_early_config(): add tests\n\n builtin/am.c            |   2 +-\n builtin/blame.c         |   2 +-\n builtin/grep.c          |   4 +-\n builtin/log.c           |   4 +-\n builtin/var.c           |   2 +-\n cache.h                 |   8 ++--\n config.c                | 110 ++++++++++++++++++++++++++++++++++++++++++++++++\n diff.c                  |   4 +-\n git.c                   |  25 +++++------\n pager.c                 |  44 +++----------------\n t/helper/test-config.c  |  15 +++++++\n t/t1309-early-config.sh |  50 ++++++++++++++++++++++\n 12 files changed, 209 insertions(+), 61 deletions(-)\n create mode 100755 t/t1309-early-config.sh\n\n\nbase-commit: 8d7a455ed52e2a96debc080dfc011b6bb00db5d2\nPublished-As: https://github.com/dscho/git/releases/tag/early-config-v1\nFetch-It-Via: git fetch https://github.com/dscho/git early-config-v1\n\n-- \n2.11.0.rc3.windows.1\n\n"},{"id":"307255","messageId":"43a2d85a3bf92c9efacad722fafdbea77b409acd.1481211338.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1481211338.git.johannes.schindelin@gmx.de","subject":"[PATCH/RFC 3/7] Mark builtins that create .git/ directories","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-12-08T15:36:14Z","receivedAt":"2016-12-08T15:36:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"To refactor read_early_config() so that it discovers .git/config\nproperly, we have to make certain that commands such as `git init` (i.e.\ncommands that create their own .git/ and therefore do *not* want to\nbe affected by any other .git/ directory) skip this discovery.\n\nLet's introduce a flag that states for every builtin whether it creates\nits own .git/ directory or not.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n git.c | 9 +++++----\n 1 file changed, 5 insertions(+), 4 deletions(-)\n\ndiff --git a/git.c b/git.c\nindex dce529fcbf..61df78afc8 100644\n--- a/git.c\n+++ b/git.c\n@@ -318,12 +318,13 @@ static int handle_alias(int *argcp, const char ***argv)\n #define RUN_SETUP\t\t(1<<0)\n #define RUN_SETUP_GENTLY\t(1<<1)\n #define USE_PAGER\t\t(1<<2)\n+#define CREATES_GIT_DIR         (1<<3)\n /*\n  * require working tree to be present -- anything uses this needs\n  * RUN_SETUP for reading from the configuration file.\n  */\n-#define NEED_WORK_TREE\t\t(1<<3)\n-#define SUPPORT_SUPER_PREFIX\t(1<<4)\n+#define NEED_WORK_TREE\t\t(1<<4)\n+#define SUPPORT_SUPER_PREFIX\t(1<<5)\n \n struct cmd_struct {\n \tconst char *cmd;\n@@ -412,7 +413,7 @@ static struct cmd_struct commands[] = {\n \t{ \"cherry\", cmd_cherry, RUN_SETUP },\n \t{ \"cherry-pick\", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"clean\", cmd_clean, RUN_SETUP | NEED_WORK_TREE },\n-\t{ \"clone\", cmd_clone },\n+\t{ \"clone\", cmd_clone, CREATES_GIT_DIR },\n \t{ \"column\", cmd_column, RUN_SETUP_GENTLY },\n \t{ \"commit\", cmd_commit, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"commit-tree\", cmd_commit_tree, RUN_SETUP },\n@@ -438,7 +439,7 @@ static struct cmd_struct commands[] = {\n \t{ \"hash-object\", cmd_hash_object },\n \t{ \"help\", cmd_help },\n \t{ \"index-pack\", cmd_index_pack, RUN_SETUP_GENTLY },\n-\t{ \"init\", cmd_init_db },\n+\t{ \"init\", cmd_init_db, CREATES_GIT_DIR },\n \t{ \"init-db\", cmd_init_db },\n \t{ \"interpret-trailers\", cmd_interpret_trailers, RUN_SETUP_GENTLY },\n \t{ \"log\", cmd_log, RUN_SETUP },\n-- \n2.11.0.rc3.windows.1\n\n\n"},{"id":"307256","messageId":"fc498e3720358b918977d693eef05132bce00116.1481211338.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1481211338.git.johannes.schindelin@gmx.de","subject":"[PATCH/RFC 1/7] Make read_early_config() reusable","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-12-08T15:36:03Z","receivedAt":"2016-12-08T15:36:41Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The pager configuration needs to be read early, possibly before\ndiscovering any .git/ directory.\n\nLet's not hide this function in pager.c, but make it available to other\ncallers.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n cache.h  |  1 +\n config.c | 31 +++++++++++++++++++++++++++++++\n pager.c  | 31 -------------------------------\n 3 files changed, 32 insertions(+), 31 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex a50a61a197..96e0cb2523 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1692,6 +1692,7 @@ extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type,\n \t\t\t\t\tconst char *name, const char *buf, size_t len, void *data);\n extern void git_config_push_parameter(const char *text);\n extern int git_config_from_parameters(config_fn_t fn, void *data);\n+extern void read_early_config(config_fn_t cb, void *data);\n extern void git_config(config_fn_t fn, void *);\n extern int git_config_with_options(config_fn_t fn, void *,\n \t\t\t\t   struct git_config_source *config_source,\ndiff --git a/config.c b/config.c\nindex 83fdecb1bc..7dcd8d8622 100644\n--- a/config.c\n+++ b/config.c\n@@ -1385,6 +1385,37 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \t}\n }\n \n+void read_early_config(config_fn_t cb, void *data)\n+{\n+\tgit_config_with_options(cb, data, NULL, 1);\n+\n+\t/*\n+\t * Note that this is a really dirty hack that does the wrong thing in\n+\t * many cases. The crux of the problem is that we cannot run\n+\t * setup_git_directory() early on in git's setup, so we have no idea if\n+\t * we are in a repository or not, and therefore are not sure whether\n+\t * and how to read repository-local config.\n+\t *\n+\t * So if we _aren't_ in a repository (or we are but we would reject its\n+\t * core.repositoryformatversion), we'll read whatever is in .git/config\n+\t * blindly. Similarly, if we _are_ in a repository, but not at the\n+\t * root, we'll fail to find .git/config (because it's really\n+\t * ../.git/config, etc). See t7006 for a complete set of failures.\n+\t *\n+\t * However, we have historically provided this hack because it does\n+\t * work some of the time (namely when you are at the top-level of a\n+\t * valid repository), and would rarely make things worse (i.e., you do\n+\t * not generally have a .git/config file sitting around).\n+\t */\n+\tif (!startup_info->have_repository) {\n+\t\tstruct git_config_source repo_config;\n+\n+\t\tmemset(&repo_config, 0, sizeof(repo_config));\n+\t\trepo_config.file = \".git/config\";\n+\t\tgit_config_with_options(cb, data, &repo_config, 1);\n+\t}\n+}\n+\n static void git_config_check_init(void);\n \n void git_config(config_fn_t fn, void *data)\ndiff --git a/pager.c b/pager.c\nindex ae79643363..73ca8bc3b1 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -43,37 +43,6 @@ static int core_pager_config(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n-static void read_early_config(config_fn_t cb, void *data)\n-{\n-\tgit_config_with_options(cb, data, NULL, 1);\n-\n-\t/*\n-\t * Note that this is a really dirty hack that does the wrong thing in\n-\t * many cases. The crux of the problem is that we cannot run\n-\t * setup_git_directory() early on in git's setup, so we have no idea if\n-\t * we are in a repository or not, and therefore are not sure whether\n-\t * and how to read repository-local config.\n-\t *\n-\t * So if we _aren't_ in a repository (or we are but we would reject its\n-\t * core.repositoryformatversion), we'll read whatever is in .git/config\n-\t * blindly. Similarly, if we _are_ in a repository, but not at the\n-\t * root, we'll fail to find .git/config (because it's really\n-\t * ../.git/config, etc). See t7006 for a complete set of failures.\n-\t *\n-\t * However, we have historically provided this hack because it does\n-\t * work some of the time (namely when you are at the top-level of a\n-\t * valid repository), and would rarely make things worse (i.e., you do\n-\t * not generally have a .git/config file sitting around).\n-\t */\n-\tif (!startup_info->have_repository) {\n-\t\tstruct git_config_source repo_config;\n-\n-\t\tmemset(&repo_config, 0, sizeof(repo_config));\n-\t\trepo_config.file = \".git/config\";\n-\t\tgit_config_with_options(cb, data, &repo_config, 1);\n-\t}\n-}\n-\n const char *git_pager(int stdout_is_tty)\n {\n \tconst char *pager;\n-- \n2.11.0.rc3.windows.1\n\n\n"},{"id":"307257","messageId":"ac26b262fa2429897064118bcbe6b80847556b1b.1481211338.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1481211338.git.johannes.schindelin@gmx.de","subject":"[PATCH/RFC 2/7] read_early_config(): avoid .git/config hack when unneeded","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-12-08T15:36:09Z","receivedAt":"2016-12-08T15:36:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"So far, we only look whether the startup_info claims to have seen a\ngit_dir.\n\nHowever, do_git_config_sequence() (and consequently the\ngit_config_with_options() call used by read_early_config() asks the\nhave_git_dir() function whether we have a .git/ directory, which in turn\nalso looks at git_dir and at the environment variable GIT_DIR.\n\nLet's just use the same function, have_git_dir(), to determine whether we\nhave to look for .git/config specifically.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n config.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/config.c b/config.c\nindex 7dcd8d8622..104c3c3dcd 100644\n--- a/config.c\n+++ b/config.c\n@@ -1407,7 +1407,7 @@ void read_early_config(config_fn_t cb, void *data)\n \t * valid repository), and would rarely make things worse (i.e., you do\n \t * not generally have a .git/config file sitting around).\n \t */\n-\tif (!startup_info->have_repository) {\n+\tif (!have_git_dir()) {\n \t\tstruct git_config_source repo_config;\n \n \t\tmemset(&repo_config, 0, sizeof(repo_config));\n-- \n2.11.0.rc3.windows.1\n\n\n"},{"id":"307258","messageId":"fcc3262f1245b6fe846f434ae9d500aa953e39f7.1481211338.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1481211338.git.johannes.schindelin@gmx.de","subject":"[PATCH/RFC 4/7] read_early_config(): special-case `init` and `clone`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-12-08T15:36:19Z","receivedAt":"2016-12-08T15:36:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The `init` and `clone` commands create their own .git/ directory,\ntherefore we must be careful not to read any repository config when\ndetermining the pager settings.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/am.c    |  2 +-\n builtin/blame.c |  2 +-\n builtin/grep.c  |  4 ++--\n builtin/log.c   |  4 ++--\n builtin/var.c   |  2 +-\n cache.h         |  9 +++++----\n config.c        |  4 ++--\n diff.c          |  4 ++--\n git.c           | 16 ++++++++--------\n pager.c         | 13 +++++++------\n 10 files changed, 31 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 6981f42ce9..e6c2ee01bc 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1791,7 +1791,7 @@ static int do_interactive(struct am_state *state)\n \t\t\t}\n \t\t\tstrbuf_release(&msg);\n \t\t} else if (*reply == 'v' || *reply == 'V') {\n-\t\t\tconst char *pager = git_pager(1);\n+\t\t\tconst char *pager = git_pager(1, 1);\n \t\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n \n \t\t\tif (!pager)\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 4ddfadb71f..5b7daa3686 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -2913,7 +2913,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \tassign_blame(&sb, opt);\n \n \tif (!incremental)\n-\t\tsetup_pager();\n+\t\tsetup_pager(1);\n \n \tfree(final_commit_name);\n \ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 8887b6addb..363a753369 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -800,7 +800,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (show_in_pager == default_pager)\n-\t\tshow_in_pager = git_pager(1);\n+\t\tshow_in_pager = git_pager(1, 1);\n \tif (show_in_pager) {\n \t\topt.color = 0;\n \t\topt.name_only = 1;\n@@ -896,7 +896,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (!show_in_pager && !opt.status_only)\n-\t\tsetup_pager();\n+\t\tsetup_pager(1);\n \n \tif (!use_index && (untracked || cached))\n \t\tdie(_(\"--cached or --untracked cannot be used with --no-index.\"));\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 55d20cc2d8..96618d38cb 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -203,7 +203,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,\n \tif (rev->line_level_traverse)\n \t\tline_log_init(rev, line_cb.prefix, &line_cb.args);\n \n-\tsetup_pager();\n+\tsetup_pager(1);\n }\n \n static void cmd_log_init(int argc, const char **argv, const char *prefix,\n@@ -1600,7 +1600,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \tif (!use_stdout)\n \t\toutput_directory = set_outdir(prefix, output_directory);\n \telse\n-\t\tsetup_pager();\n+\t\tsetup_pager(1);\n \n \tif (output_directory) {\n \t\tif (rev.diffopt.use_color != GIT_COLOR_ALWAYS)\ndiff --git a/builtin/var.c b/builtin/var.c\nindex aedbb53a2d..879867b842 100644\n--- a/builtin/var.c\n+++ b/builtin/var.c\n@@ -19,7 +19,7 @@ static const char *editor(int flag)\n \n static const char *pager(int flag)\n {\n-\tconst char *pgm = git_pager(1);\n+\tconst char *pgm = git_pager(1, 1);\n \n \tif (!pgm)\n \t\tpgm = \"cat\";\ndiff --git a/cache.h b/cache.h\nindex 96e0cb2523..6627239de8 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1325,7 +1325,7 @@ extern const char *fmt_name(const char *name, const char *email);\n extern const char *ident_default_name(void);\n extern const char *ident_default_email(void);\n extern const char *git_editor(void);\n-extern const char *git_pager(int stdout_is_tty);\n+extern const char *git_pager(int stdout_is_tty, int discover_git_dir);\n extern int git_ident_config(const char *, const char *, void *);\n extern void reset_ident_date(void);\n \n@@ -1692,7 +1692,8 @@ extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type,\n \t\t\t\t\tconst char *name, const char *buf, size_t len, void *data);\n extern void git_config_push_parameter(const char *text);\n extern int git_config_from_parameters(config_fn_t fn, void *data);\n-extern void read_early_config(config_fn_t cb, void *data);\n+extern void read_early_config(config_fn_t cb, void *data,\n+\t\t\t      int discover_git_dir);\n extern void git_config(config_fn_t fn, void *);\n extern int git_config_with_options(config_fn_t fn, void *,\n \t\t\t\t   struct git_config_source *config_source,\n@@ -1888,12 +1889,12 @@ __attribute__((format (printf, 2, 3)))\n extern void write_file(const char *path, const char *fmt, ...);\n \n /* pager.c */\n-extern void setup_pager(void);\n+extern void setup_pager(int discover_git_dir);\n extern int pager_in_use(void);\n extern int pager_use_color;\n extern int term_columns(void);\n extern int decimal_width(uintmax_t);\n-extern int check_pager_config(const char *cmd);\n+extern int check_pager_config(const char *cmd, int discover_git_dir);\n extern void prepare_pager_args(struct child_process *, const char *pager);\n \n extern const char *editor_program;\ndiff --git a/config.c b/config.c\nindex 104c3c3dcd..4c756edca1 100644\n--- a/config.c\n+++ b/config.c\n@@ -1385,7 +1385,7 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \t}\n }\n \n-void read_early_config(config_fn_t cb, void *data)\n+void read_early_config(config_fn_t cb, void *data, int discover_git_dir)\n {\n \tgit_config_with_options(cb, data, NULL, 1);\n \n@@ -1407,7 +1407,7 @@ void read_early_config(config_fn_t cb, void *data)\n \t * valid repository), and would rarely make things worse (i.e., you do\n \t * not generally have a .git/config file sitting around).\n \t */\n-\tif (!have_git_dir()) {\n+\tif (discover_git_dir && !have_git_dir()) {\n \t\tstruct git_config_source repo_config;\n \n \t\tmemset(&repo_config, 0, sizeof(repo_config));\ndiff --git a/diff.c b/diff.c\nindex ec8728362d..0769c0590a 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -5267,6 +5267,6 @@ void setup_diff_pager(struct diff_options *opt)\n \t * --exit-code\" in hooks and other scripts, we do not do so.\n \t */\n \tif (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&\n-\t    check_pager_config(\"diff\") != 0)\n-\t\tsetup_pager();\n+\t    check_pager_config(\"diff\", 1) != 0)\n+\t\tsetup_pager(1);\n }\ndiff --git a/git.c b/git.c\nindex 61df78afc8..2b007b83ec 100644\n--- a/git.c\n+++ b/git.c\n@@ -61,13 +61,13 @@ static void restore_env(int external_alias)\n \t}\n }\n \n-static void commit_pager_choice(void) {\n+static void commit_pager_choice(int discover_git_dir) {\n \tswitch (use_pager) {\n \tcase 0:\n \t\tsetenv(\"GIT_PAGER\", \"cat\", 1);\n \t\tbreak;\n \tcase 1:\n-\t\tsetup_pager();\n+\t\tsetup_pager(discover_git_dir);\n \t\tbreak;\n \tdefault:\n \t\tbreak;\n@@ -261,7 +261,7 @@ static int handle_alias(int *argcp, const char ***argv)\n \t\tif (alias_string[0] == '!') {\n \t\t\tstruct child_process child = CHILD_PROCESS_INIT;\n \n-\t\t\tcommit_pager_choice();\n+\t\t\tcommit_pager_choice(1);\n \t\t\trestore_env(1);\n \n \t\t\tchild.use_shell = 1;\n@@ -349,7 +349,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n \t\t}\n \n \t\tif (use_pager == -1 && p->option & (RUN_SETUP | RUN_SETUP_GENTLY))\n-\t\t\tuse_pager = check_pager_config(p->cmd);\n+\t\t\tuse_pager = check_pager_config(p->cmd, !(p->option & CREATES_GIT_DIR));\n \t\tif (use_pager == -1 && p->option & USE_PAGER)\n \t\t\tuse_pager = 1;\n \n@@ -357,7 +357,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n \t\t    startup_info->have_repository) /* get_git_dir() may set up repo, avoid that */\n \t\t\ttrace_repo_setup(prefix);\n \t}\n-\tcommit_pager_choice();\n+\tcommit_pager_choice(!(p->option & CREATES_GIT_DIR));\n \n \tif (!help && get_super_prefix()) {\n \t\tif (!(p->option & SUPPORT_SUPER_PREFIX))\n@@ -584,8 +584,8 @@ static void execv_dashed_external(const char **argv)\n \t\tdie(\"%s doesn't support --super-prefix\", argv[0]);\n \n \tif (use_pager == -1)\n-\t\tuse_pager = check_pager_config(argv[0]);\n-\tcommit_pager_choice();\n+\t\tuse_pager = check_pager_config(argv[0], 1);\n+\tcommit_pager_choice(1);\n \n \tstrbuf_addf(&cmd, \"git-%s\", argv[0]);\n \n@@ -688,7 +688,7 @@ int cmd_main(int argc, const char **argv)\n \t\tskip_prefix(argv[0], \"--\", &argv[0]);\n \t} else {\n \t\t/* The user didn't specify a command; give them help */\n-\t\tcommit_pager_choice();\n+\t\tcommit_pager_choice(1);\n \t\tprintf(\"usage: %s\\n\\n\", git_usage_string);\n \t\tlist_common_cmds_help();\n \t\tprintf(\"\\n%s\\n\", _(git_more_info_string));\ndiff --git a/pager.c b/pager.c\nindex 73ca8bc3b1..16b3cbe232 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -43,7 +43,7 @@ static int core_pager_config(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n-const char *git_pager(int stdout_is_tty)\n+const char *git_pager(int stdout_is_tty, int discover_git_dir)\n {\n \tconst char *pager;\n \n@@ -53,7 +53,8 @@ const char *git_pager(int stdout_is_tty)\n \tpager = getenv(\"GIT_PAGER\");\n \tif (!pager) {\n \t\tif (!pager_program)\n-\t\t\tread_early_config(core_pager_config, NULL);\n+\t\t\tread_early_config(core_pager_config, NULL,\n+\t\t\t\t\t  discover_git_dir);\n \t\tpager = pager_program;\n \t}\n \tif (!pager)\n@@ -100,9 +101,9 @@ void prepare_pager_args(struct child_process *pager_process, const char *pager)\n \tsetup_pager_env(&pager_process->env_array);\n }\n \n-void setup_pager(void)\n+void setup_pager(int discover_git_dir)\n {\n-\tconst char *pager = git_pager(isatty(1));\n+\tconst char *pager = git_pager(isatty(1), discover_git_dir);\n \n \tif (!pager)\n \t\treturn;\n@@ -208,7 +209,7 @@ static int pager_command_config(const char *var, const char *value, void *vdata)\n }\n \n /* returns 0 for \"no pager\", 1 for \"use pager\", and -1 for \"not specified\" */\n-int check_pager_config(const char *cmd)\n+int check_pager_config(const char *cmd, int discover_git_dir)\n {\n \tstruct pager_command_config_data data;\n \n@@ -216,7 +217,7 @@ int check_pager_config(const char *cmd)\n \tdata.want = -1;\n \tdata.value = NULL;\n \n-\tread_early_config(pager_command_config, &data);\n+\tread_early_config(pager_command_config, &data, discover_git_dir);\n \n \tif (data.value)\n \t\tpager_program = data.value;\n-- \n2.11.0.rc3.windows.1\n\n\n"},{"id":"307259","messageId":"15fa873f3d6960c14fb3195ed2d9dc4ec58ba402.1481211338.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1481211338.git.johannes.schindelin@gmx.de","subject":"[PATCH/RFC 5/7] read_early_config(): really discover .git/","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-12-08T15:36:23Z","receivedAt":"2016-12-08T15:36:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Earlier, we punted and simply assumed that we are in the top-level\ndirectory of the project, and that there is no .git file but a .git/\ndirectory so that we can read directly from .git/config.\n\nLet's discover the .git/ directory correctly.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n config.c | 69 +++++++++++++++++++++++++++++++++++++++++++++-------------------\n 1 file changed, 49 insertions(+), 20 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 4c756edca1..286d3cad66 100644\n--- a/config.c\n+++ b/config.c\n@@ -1385,35 +1385,64 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \t}\n }\n \n+/*\n+ * Note that this is a really dirty hack that replicates what the\n+ * setup_git_directory() function does, without changing the current\n+ * working directory. The crux of the problem is that we cannot run\n+ * setup_git_directory() early on in git's setup, so we have to\n+ * duplicate the work that setup_git_directory() would otherwise do.\n+ */\n+static int discover_git_directory_gently(struct strbuf *result)\n+{\n+\tconst char *p;\n+\n+\tif (strbuf_getcwd(result) < 0)\n+\t\treturn -1;\n+\tp = real_path_if_valid(result->buf);\n+\tif (!p)\n+\t\treturn -1;\n+\tstrbuf_reset(result);\n+\tstrbuf_addstr(result, p);\n+\tfor (;;) {\n+\t\tint len = result->len, i;\n+\n+\t\tstrbuf_addstr(result, \"/\" DEFAULT_GIT_DIR_ENVIRONMENT);\n+\t\tp = read_gitfile_gently(result->buf, &i);\n+\t\tif (p) {\n+\t\t\tstrbuf_reset(result);\n+\t\t\tstrbuf_addstr(result, p);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tif (is_git_directory(result->buf))\n+\t\t\treturn 0;\n+\t\tstrbuf_setlen(result, len);\n+\t\tif (is_git_directory(result->buf))\n+\t\t\treturn 0;\n+\t\tfor (i = len; i > 0; )\n+\t\t\tif (is_dir_sep(result->buf[--i]))\n+\t\t\t\tbreak;\n+\t\tif (!i)\n+\t\t\treturn -1;\n+\t\tstrbuf_setlen(result, i);\n+\t}\n+}\n+\n void read_early_config(config_fn_t cb, void *data, int discover_git_dir)\n {\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n \tgit_config_with_options(cb, data, NULL, 1);\n \n-\t/*\n-\t * Note that this is a really dirty hack that does the wrong thing in\n-\t * many cases. The crux of the problem is that we cannot run\n-\t * setup_git_directory() early on in git's setup, so we have no idea if\n-\t * we are in a repository or not, and therefore are not sure whether\n-\t * and how to read repository-local config.\n-\t *\n-\t * So if we _aren't_ in a repository (or we are but we would reject its\n-\t * core.repositoryformatversion), we'll read whatever is in .git/config\n-\t * blindly. Similarly, if we _are_ in a repository, but not at the\n-\t * root, we'll fail to find .git/config (because it's really\n-\t * ../.git/config, etc). See t7006 for a complete set of failures.\n-\t *\n-\t * However, we have historically provided this hack because it does\n-\t * work some of the time (namely when you are at the top-level of a\n-\t * valid repository), and would rarely make things worse (i.e., you do\n-\t * not generally have a .git/config file sitting around).\n-\t */\n-\tif (discover_git_dir && !have_git_dir()) {\n+\tif (discover_git_dir && !have_git_dir() &&\n+\t    !discover_git_directory_gently(&buf)) {\n \t\tstruct git_config_source repo_config;\n \n \t\tmemset(&repo_config, 0, sizeof(repo_config));\n-\t\trepo_config.file = \".git/config\";\n+\t\tstrbuf_addstr(&buf, \"/config\");\n+\t\trepo_config.file = buf.buf;\n \t\tgit_config_with_options(cb, data, &repo_config, 1);\n \t}\n+\tstrbuf_release(&buf);\n }\n \n static void git_config_check_init(void);\n-- \n2.11.0.rc3.windows.1\n\n\n"},{"id":"307261","messageId":"62f04326163842ccfcffce5e5d6ffb48f8d3035e.1481211338.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1481211338.git.johannes.schindelin@gmx.de","subject":"[PATCH/RFC 7/7] WIP: read_early_config(): add tests","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-12-08T15:36:33Z","receivedAt":"2016-12-08T15:37:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/helper/test-config.c  | 15 +++++++++++++++\n t/t1309-early-config.sh | 50 +++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 65 insertions(+)\n create mode 100755 t/t1309-early-config.sh\n\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 83a4f2ab86..5105069587 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -66,6 +66,16 @@ static int iterate_cb(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n+static int early_config_cb(const char *var, const char *value, void *vdata)\n+{\n+\tconst char *key = vdata;\n+\n+\tif (!strcmp(key, var))\n+\t\tprintf(\"%s\\n\", value);\n+\n+\treturn 0;\n+}\n+\n int cmd_main(int argc, const char **argv)\n {\n \tint i, val;\n@@ -73,6 +83,11 @@ int cmd_main(int argc, const char **argv)\n \tconst struct string_list *strptr;\n \tstruct config_set cs;\n \n+\tif (argc == 3 && !strcmp(argv[1], \"read_early_config\")) {\n+\t\tread_early_config(early_config_cb, (void *)argv[2], 1);\n+\t\treturn 0;\n+\t}\n+\n \tsetup_git_directory();\n \n \tgit_configset_init(&cs);\ndiff --git a/t/t1309-early-config.sh b/t/t1309-early-config.sh\nnew file mode 100755\nindex 0000000000..0c55dee514\n--- /dev/null\n+++ b/t/t1309-early-config.sh\n@@ -0,0 +1,50 @@\n+#!/bin/sh\n+\n+test_description='Test read_early_config()'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'read early config' '\n+\ttest_config early.config correct &&\n+\ttest-config read_early_config early.config >output &&\n+\ttest correct = \"$(cat output)\"\n+'\n+\n+test_expect_success 'in a sub-directory' '\n+\ttest_config early.config sub &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest sub = \"$(cat output)\"\n+'\n+\n+test_expect_success 'ceiling' '\n+\ttest_config early.config ceiling &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tGIT_CEILING_DIRECTORIES=\"$PWD\" &&\n+\t\texport GIT_CEILING_DIRECTORIES &&\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest -z \"$(cat output)\"\n+'\n+\n+test_expect_success 'ceiling #2' '\n+\tmkdir -p xdg/git &&\n+\tgit config -f xdg/git/config early.config xdg &&\n+\ttest_config early.config ceiling &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tXDG_CONFIG_HOME=\"$PWD\"/xdg &&\n+\t\tGIT_CEILING_DIRECTORIES=\"$PWD\" &&\n+\t\texport GIT_CEILING_DIRECTORIES XDG_CONFIG_HOME &&\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest xdg = \"$(cat output)\"\n+'\n+\n+test_done\n-- \n2.11.0.rc3.windows.1\n"},{"id":"307262","messageId":"07c329e9d4875c2137d7b9ceedcb9241fc372b31.1481211338.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1481211338.git.johannes.schindelin@gmx.de","subject":"[PATCH/RFC 6/7] WIP read_config_early(): respect ceiling directories","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-12-08T15:36:28Z","receivedAt":"2016-12-08T15:37:19Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This ports the part from setup_git_directory_gently_1() where the\nGIT_CEILING_DIRECTORIES environment variable is handled.\n\nTODO: DRY up code again (exporting canonicalize_ceiling_directories()?)\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n config.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++---\n 1 file changed, 53 insertions(+), 3 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 286d3cad66..eda3546491 100644\n--- a/config.c\n+++ b/config.c\n@@ -1386,6 +1386,37 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n }\n \n /*\n+ * A \"string_list_each_func_t\" function that canonicalizes an entry\n+ * from GIT_CEILING_DIRECTORIES using real_path_if_valid(), or\n+ * discards it if unusable.  The presence of an empty entry in\n+ * GIT_CEILING_DIRECTORIES turns off canonicalization for all\n+ * subsequent entries.\n+ */\n+static int canonicalize_ceiling_entry(struct string_list_item *item,\n+\t\t\t\t      void *cb_data)\n+{\n+\tint *empty_entry_found = cb_data;\n+\tchar *ceil = item->string;\n+\n+\tif (!*ceil) {\n+\t\t*empty_entry_found = 1;\n+\t\treturn 0;\n+\t} else if (!is_absolute_path(ceil)) {\n+\t\treturn 0;\n+\t} else if (*empty_entry_found) {\n+\t\t/* Keep entry but do not canonicalize it */\n+\t\treturn 1;\n+\t} else {\n+\t\tconst char *real_path = real_path_if_valid(ceil);\n+\t\tif (!real_path)\n+\t\t\treturn 0;\n+\t\tfree(item->string);\n+\t\titem->string = xstrdup(real_path);\n+\t\treturn 1;\n+\t}\n+}\n+\n+/*\n  * Note that this is a really dirty hack that replicates what the\n  * setup_git_directory() function does, without changing the current\n  * working directory. The crux of the problem is that we cannot run\n@@ -1394,6 +1425,8 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n  */\n static int discover_git_directory_gently(struct strbuf *result)\n {\n+\tconst char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);\n+\tint ceiling_offset = -1;\n \tconst char *p;\n \n \tif (strbuf_getcwd(result) < 0)\n@@ -1403,6 +1436,23 @@ static int discover_git_directory_gently(struct strbuf *result)\n \t\treturn -1;\n \tstrbuf_reset(result);\n \tstrbuf_addstr(result, p);\n+\n+\tif (env_ceiling_dirs) {\n+\t\tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n+\t\tint empty_entry_found = 0;\n+\n+\t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP,\n+\t\t\t\t  -1);\n+\t\tfilter_string_list(&ceiling_dirs, 0, canonicalize_ceiling_entry,\n+\t\t\t\t   &empty_entry_found);\n+\t\tceiling_offset = longest_ancestor_length(result->buf,\n+\t\t\t\t\t\t\t &ceiling_dirs);\n+\t\tstring_list_clear(&ceiling_dirs, 0);\n+\t}\n+\n+\tif (ceiling_offset < 0 && has_dos_drive_prefix(result->buf))\n+\t\tceiling_offset = 1;\n+\n \tfor (;;) {\n \t\tint len = result->len, i;\n \n@@ -1418,10 +1468,10 @@ static int discover_git_directory_gently(struct strbuf *result)\n \t\tstrbuf_setlen(result, len);\n \t\tif (is_git_directory(result->buf))\n \t\t\treturn 0;\n-\t\tfor (i = len; i > 0; )\n-\t\t\tif (is_dir_sep(result->buf[--i]))\n+\t\tfor (i = len; --i > ceiling_offset; )\n+\t\t\tif (is_dir_sep(result->buf[i]))\n \t\t\t\tbreak;\n-\t\tif (!i)\n+\t\tif (i <= ceiling_offset)\n \t\t\treturn -1;\n \t\tstrbuf_setlen(result, i);\n \t}\n-- \n2.11.0.rc3.windows.1\n\n\n"},{"id":"307265","messageId":"20161208172626.3ee2nmgsxsh2vovf@sigill.intra.peff.net","threadId":"44653","inReplyTo":"cover.1481211338.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH/RFC 0/7] Pie-in-the-sky attempt to fix the early config","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-12-08T17:26:26Z","receivedAt":"2016-12-08T17:27:15Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Dec 08, 2016 at 04:35:56PM +0100, Johannes Schindelin wrote:\n\n> The idea here is to discover the .git/ directory gently (i.e. without\n> changing the current working directory), and to use it to read the\n> .git/config file early, before we actually called setup_git_directory()\n> (if we ever do that).\n\nGreat. Thanks for taking a stab at this.\n\n> Notes:\n> \n> - I find the diff pretty ugly: I wish there was a more elegant way to\n>   *disable* discovery of .git/ *just* for `init` and `clone`. I\n>   considered a function `about_to_create_git_dir()` that is called in a\n>   hard-coded manner *only* for `init` and `clone`, but that would\n>   introduce another magic side effect, when all I want is to reduce those.\n\nIt looks like a lot of that ugliness comes from passing around the \"are\nwe OK to peek at git-dir config\" flag through the various pager-related\ncalls.  I don't think it would be bad to use a global for \"we do not\nwant a repo\".  After all, it's just modifying the _existing_ global for\n\"are we in a repo\". And I do not see that global going away anytime\nsoon. Sometimes it's good to make incremental steps towards an end goal,\nbut in this case it seems like we just pay the cost of the step without\nany real plan for reaping the benefit in the long term.\n\nAs an alternative, I also think it would be OK to just always have the\npager config read from local repo, even for init/clone. In other words,\nto loosen the idea that git-init can _never_ look in the current\ngit-dir, and declare that there is a stage before the command is\ninitiated, and during which git may read local-repo config. Aliases\nwould fall into this, too, so:\n\n  git config --local alias.foo init\n  git foo /some/other/dir\n\nwould work (as it must, because we cannot know that \"foo\" is \"init\"\nuntil we read the config!).\n\nI have a feeling you may declare that too magical, but I think it's\nconsistent and practical.\n\n> - For the moment, I do not handle dashed invocations of `init` and\n>   `clone` correctly. The real problem is the continued existence of\n>   the dashed git-init and git-clone, of course.\n\nI assume you mean setting the CREATES_GIT_DIR flag here? I think it\nwould apply to the dashed invocations, too. They strip off the \"git-\"\nand end up in handle_builtin() just like \"git clone\" does. I didn't test\nit, though.\n\n> - There is still duplicated code. For the sake of this RFC, I did not\n>   address that yet.\n\nYeah, I did not read your discover function very carefully. Because I\nthink the one thing we really don't want to do here is introduce a\nseparate lookup process that is not shared by setup_git_directory(). The\nonly sane path, I think, is to refactor setup_git_directory() to build\non top of discover_git_directory(), which implies that the latter\nhandles all of the cases.\n\n> - The read_early_config() function is called multiple times, re-reading\n>   all the config files and re-discovering the .git/ directory multiple\n>   times, which is quite wasteful. For the sake of this RFC, I did not\n>   address that yet.\n\nWe already have a config-caching system. If we went with a global\n\"config_discover_refs\", then I think the sequence for something like\ngit-init would become:\n\n  1. When git.c starts, config_discover_refs is set to \"true\". Pager and\n     alias lookup may look in .git/config if it's available, even if\n     they go through the configset cache.\n\n  2. As soon as git-init starts, it disables config_discover_refs, and\n     it flushes the config cache. Any configset lookups will now examine\n     the reduced config.\n\n  3. When git-init has set up the real repo it is operating on, it can\n     reenable config_discover_refs (though it may not even need to; that\n     flag probably wouldn't have any effect once we've entered the\n     repository and have_git_dir() returns true).\n\n> - t7006 fails and the error message is a bit cryptic (not to mention the\n>   involved function trace, which is mind-boggling for what is supposed\n>   to be a simply, shell script-based test suite). I did not have time to\n>   look into that yet.\n\nRunning t7006 I see a lot of old failures turned into successes, which\nis good (because running from a subdirectory now actually respects local\npager config). The one failure looks like it is testing the wrong thing.\n\nIt is checking that we _don't_ respect a local core.pager in some cases,\nas a proxy for whether or not we are breaking things by doing setup too\nearly. But the whole point of your series is to fix that, and respect\nthe config without causing the setup breakage. After your patches, the\nproxy behavior and the failure case are disconnected. The test should be\nflipped, and ideally another one added that confirms we didn't actually\nrun setup_git_directory(), but I'm not sure how to test that directly.\n\n> - after discover_git_directory_gently() did its work, the code happily\n>   uses its result *only* for the current read_early_config() run, and\n>   lets setup_git_dir_gently() do the whole work *again*. For the sake of\n>   this RFC, I did not address that yet.\n\nIf caching happens at the config layer, then we'd probably only call\nthis once anyway (or if we did call it again after a config flush, it\nwould be a good sign that we should compute its value again).\n\n-Peff\n"},{"id":"307348","messageId":"CACsJy8DDwKhOBOxD_yOf2MfhxP4_TDDj3AwCf25ct6YH3TGp6g@mail.gmail.com","threadId":"44653","inReplyTo":"cover.1481211338.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH/RFC 0/7] Pie-in-the-sky attempt to fix the early config","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-12-09T12:42:40Z","receivedAt":"2016-12-09T12:43:17Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Dec 8, 2016 at 10:35 PM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> Hopefully these patches will lead to something that we can integrate,\n> and that eventually will make Git's startup sequence much less\n> surprising.\n\nWhat did it surprise you with? Just curious. I can see that I\ndisrespect the ceiling directory setting, perhaps that's that.\n-- \nDuy\n"},{"id":"307367","messageId":"alpine.DEB.2.20.1612091722290.23160@virtualbox","threadId":"44653","inReplyTo":"CACsJy8DDwKhOBOxD_yOf2MfhxP4_TDDj3AwCf25ct6YH3TGp6g@mail.gmail.com","subject":"Re: [PATCH/RFC 0/7] Pie-in-the-sky attempt to fix the early config","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-12-09T16:52:56Z","receivedAt":"2016-12-09T16:53:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Duy,\n\nOn Fri, 9 Dec 2016, Duy Nguyen wrote:\n\n> On Thu, Dec 8, 2016 at 10:35 PM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > Hopefully these patches will lead to something that we can integrate,\n> > and that eventually will make Git's startup sequence much less\n> > surprising.\n> \n> What did it surprise you with?\n\nI mentioned a couple of WTFs elsewhere:\n\n- that it changes the working directory (personally, I am not surprised,\n  just because I was exposed to the Git source code for 10+ years,\n  however, I know developers who do not share that experience and find that\n  feature... interesting)\n\n- that there are multiple code paths to do essentially the same thing, but\n  in an incompatible manner: setup_git_directory() and setup_git_env()\n  (and possibly more)\n\n- that some environment variables are set, and need to be unset (or even\n  possibly reset to no-longer-known values) to allow subprocesses to\n  access different repositories\n\n- that git_path() can be used before setup_git_directory() without\n  failure, but wreaks havoc with subsequent git_config() calls.\n\n- that the setup_git_directory() cascade is completely oblivious of\n  whatever config has already been read (possibly requiring clearing)\n\n- that setup_git_directory() *returns* the prefix, even stores it in\n  startup_info, but a subsequent call to setup_git_directory() returns\n  NULL\n\n- that as a consequence, builtins that require the prefix to work if there\n  is any, but do not necessarily require a repository to begin with, think\n  `git diff`, *must not* be marked with RUN_SETUP_GENTLY\n\n- that check_repository_format() sets have_repository=1\n\n- that setup_git_directory() is a oneliner, calling\n  setup_git_directory_gently(NULL), when it would be more logical to\n  simply make setup_git_directory() accept the \"nongit_ok\" parameter\n\n- that setup_git_directory_gently() is not at all gentle when the\n  parameter is NULL\n\n- that resolve_gitdir() does not, in fact, resolve any git directory, but\n  only tests a specified path whether it refers to a .git/ directory or to\n  a .git file\n\n- that resolve_gitdir() actually tests for a .git *file*\n\n- that resolve_gitdir() is not used in setup_git_directory_gently_1()\n\n- that resolve_gitdir() tries the order directory,file and\n  setup_git_directory_gently_1() tries the opposite order\n\n- that the handling of the ceiling directories is hardcoded into the\n  setup_git_directory_gently_1() function\n\n- that a ceiling directory of /home/hello/world does not prevent Git from\n  looking at /home/hello/world/.git/\n\n- that canonicalize_ceiling_entry()' relationship to ceiling directories\n  is rather coincidental when the name suggests that it is very specific\n\n- that canonicalize_ceiling_entry() does not, in fact, canonicalize\n  non-absolute entries\n\nNeed I go on? I could, you know...\n\n> I can see that I disrespect the ceiling directory setting, perhaps\n> that's that.\n\nNo, I actually see a lot of good reasons for the ceiling directories.\n\nCiao,\nDscho\n"},{"id":"307369","messageId":"alpine.DEB.2.20.1612091810500.23160@virtualbox","threadId":"44653","inReplyTo":"20161208172626.3ee2nmgsxsh2vovf@sigill.intra.peff.net","subject":"Re: [PATCH/RFC 0/7] Pie-in-the-sky attempt to fix the early config","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-12-09T17:28:10Z","receivedAt":"2016-12-09T17:28:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Thu, 8 Dec 2016, Jeff King wrote:\n\n> On Thu, Dec 08, 2016 at 04:35:56PM +0100, Johannes Schindelin wrote:\n> \n> > The idea here is to discover the .git/ directory gently (i.e. without\n> > changing the current working directory), and to use it to read the\n> > .git/config file early, before we actually called setup_git_directory()\n> > (if we ever do that).\n> \n> Great. Thanks for taking a stab at this.\n\nWell, I figured that I can go through you to get this integrated into\ngit.git.\n\n> > Notes:\n> > \n> > - I find the diff pretty ugly: I wish there was a more elegant way to\n> >   *disable* discovery of .git/ *just* for `init` and `clone`. I\n> >   considered a function `about_to_create_git_dir()` that is called in a\n> >   hard-coded manner *only* for `init` and `clone`, but that would\n> >   introduce another magic side effect, when all I want is to reduce those.\n> \n> It looks like a lot of that ugliness comes from passing around the \"are\n> we OK to peek at git-dir config\" flag through the various pager-related\n> calls.\n\nCorrect.\n\n> I don't think it would be bad to use a global for \"we do not want a\n> repo\".\n\nI would think it would be bad, as the entire reason for this patch series\nis that we have global state that gets messed up too early (I am speaking\nfrom the point of view of somebody who patched Git locally so that it does\nread config variables *before* launching builtins).\n\n> After all, it's just modifying the _existing_ global for \"are we in a\n> repo\".\n\nNo it does not.\n\nThe read_early_config() function specifically does not leave any traces in\nthe global namespace. It calls the git_config_with_options() function\nwithout touching the_config_set.\n\nOf course we could introduce a new config set, just for early use. It\nwould share all the downsides with the current config set.\n\nI would look more favorably on this idea if we were to teach\nthe_config_set to record a little bit more about the state from which it\nwas constructed, and to auto-flush-and-re-read when it detects that, say,\ngit_dir was changed in the meantime.\n\n> And I do not see that global going away anytime soon.\n\nAnd that is really, really sad.\n\n> Sometimes it's good to make incremental steps towards an end goal, but\n> in this case it seems like we just pay the cost of the step without any\n> real plan for reaping the benefit in the long term.\n\nFine. Let's roll with this for now, then.\n\nI just hope that we do not repeat the \"slam the door shut to a better\nsolution\" mistake that led to this situation, as the chdir() way did.\n\n> As an alternative, I also think it would be OK to just always have the\n> pager config read from local repo, even for init/clone.\n\nFor the purpose of this current discussion, I am utterly uninterested in\nthe pager config. What I want to use the early config for is substantially\ndifferent and more relevant: I need to configure some command to run\nbefore, and after, every single Git command here. And this configuration\nneeds to be per-repository. And no, I do not want to hardcode anything.\n\nSo now the cat is out of the bag, I have ulterior motives.\n\nThese motives serve a greater good, though: designing read_early_config()\naround a very specific use case will always fall short of a well-designed\nread_early_config() that could then be used for any use case that\nrequires, well, reading the config early.\n\n> In other words, to loosen the idea that git-init can _never_ look in the\n> current git-dir, and declare that there is a stage before the command is\n> initiated, and during which git may read local-repo config. Aliases\n> would fall into this, too, so:\n> \n>   git config --local alias.foo init\n>   git foo /some/other/dir\n> \n> would work (as it must, because we cannot know that \"foo\" is \"init\"\n> until we read the config!).\n\nTrue.\n\nBut is this a good excuse to just shrug our shoulders and let git-init\n(which we do know very well) fall into the same trap?\n\n> > - For the moment, I do not handle dashed invocations of `init` and\n> >   `clone` correctly. The real problem is the continued existence of\n> >   the dashed git-init and git-clone, of course.\n> \n> I assume you mean setting the CREATES_GIT_DIR flag here? I think it\n> would apply to the dashed invocations, too. They strip off the \"git-\"\n> and end up in handle_builtin() just like \"git clone\" does. I didn't test\n> it, though.\n\nYou are correct. I misread the code to expect it to spawn another instance\nof git.exe.\n\n> > - There is still duplicated code. For the sake of this RFC, I did not\n> >   address that yet.\n> \n> Yeah, I did not read your discover function very carefully. Because I\n> think the one thing we really don't want to do here is introduce a\n> separate lookup process that is not shared by setup_git_directory(). The\n> only sane path, I think, is to refactor setup_git_directory() to build\n> on top of discover_git_directory(), which implies that the latter\n> handles all of the cases.\n\nThis could maybe happen later, but for now I do not concern myself with\nbuilding the prefix, as the config machinery does not require that\nknowledge.\n\nIf we go that route, we should of course cache the cwd, git_dir and prefix\nof earlier runs, and avoid the entire discovery if we start from the same\ncwd.\n\n> > - The read_early_config() function is called multiple times, re-reading\n> >   all the config files and re-discovering the .git/ directory multiple\n> >   times, which is quite wasteful. For the sake of this RFC, I did not\n> >   address that yet.\n> \n> We already have a config-caching system. If we went with a global\n> \"config_discover_refs\",\n\nWhy \"_refs\"?\n\n> then I think the sequence for something like git-init would become:\n> \n>   1. When git.c starts, config_discover_refs is set to \"true\". Pager and\n>      alias lookup may look in .git/config if it's available, even if\n>      they go through the configset cache.\n> \n>   2. As soon as git-init starts, it disables config_discover_refs, and\n>      it flushes the config cache. Any configset lookups will now examine\n>      the reduced config.\n> \n>   3. When git-init has set up the real repo it is operating on, it can\n>      reenable config_discover_refs (though it may not even need to; that\n>      flag probably wouldn't have any effect once we've entered the\n>      repository and have_git_dir() returns true).\n\nThat is a bit fiddly, don't you think? The callers have to have very\nintimate knowledge of the config reading to remember to set, and re-set,\nthat global. And to flush when appropriate.\n\nHow much nicer would the code be if the call to git_config() would realize\nwhat needs to be done, don't you agree?\n\nLess chance for bugs to be introduced by contributors with flakey\nunderstanding of the config/git_dir machinery, myself included.\n\n> > - t7006 fails and the error message is a bit cryptic (not to mention the\n> >   involved function trace, which is mind-boggling for what is supposed\n> >   to be a simply, shell script-based test suite). I did not have time to\n> >   look into that yet.\n> \n> Running t7006 I see a lot of old failures turned into successes, which\n> is good (because running from a subdirectory now actually respects local\n> pager config). The one failure looks like it is testing the wrong thing.\n\nYeah, but due to the redirections I was not able to figure out what to\nchange to \"fix\" this.\n\n> It is checking that we _don't_ respect a local core.pager in some cases,\n> as a proxy for whether or not we are breaking things by doing setup too\n> early. But the whole point of your series is to fix that, and respect\n> the config without causing the setup breakage. After your patches, the\n> proxy behavior and the failure case are disconnected. The test should be\n> flipped, and ideally another one added that confirms we didn't actually\n> run setup_git_directory(), but I'm not sure how to test that directly.\n\nSome dirty tricks come to mind, but I am not even sure that I want to test\nfor this. Why exactly do we need to avoid calling setup_git_directory() in\nthat case?\n\n> > - after discover_git_directory_gently() did its work, the code happily\n> >   uses its result *only* for the current read_early_config() run, and\n> >   lets setup_git_dir_gently() do the whole work *again*. For the sake of\n> >   this RFC, I did not address that yet.\n> \n> If caching happens at the config layer, then we'd probably only call\n> this once anyway (or if we did call it again after a config flush, it\n> would be a good sign that we should compute its value again).\n\nI meant the git_dir, not the config... The git_dir is discovered, but a\nsubsequent setup_git_dir_gently() discovers it *again*, in a different\nway.\n\nCiao,\nDscho\n"},{"id":"307371","messageId":"20161209175507.3ap73ew4fkflkhy6@sigill.intra.peff.net","threadId":"44653","inReplyTo":"alpine.DEB.2.20.1612091810500.23160@virtualbox","subject":"Re: [PATCH/RFC 0/7] Pie-in-the-sky attempt to fix the early config","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-12-09T17:55:08Z","receivedAt":"2016-12-09T17:55:16Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Dec 09, 2016 at 06:28:10PM +0100, Johannes Schindelin wrote:\n\n> > Great. Thanks for taking a stab at this.\n> \n> Well, I figured that I can go through you to get this integrated into\n> git.git.\n\nI am not sure what you mean here, but it _sounds_ like you are\ncontinuing to be negative about the chances of fixes going into git.git\nhere. I really don't think that negativity is merited, but even if it\nis, making snide comments does not help and mostly just makes the\nconversation less pleasant.\n\n> > I don't think it would be bad to use a global for \"we do not want a\n> > repo\".\n> \n> I would think it would be bad, as the entire reason for this patch series\n> is that we have global state that gets messed up too early (I am speaking\n> from the point of view of somebody who patched Git locally so that it does\n> read config variables *before* launching builtins).\n\nI think those are two different things. One is global state that is\nmunged as a side effect of setup_git_directory(). The other is global\nstate that the process sets to say \"this is an invariant\" so it does not\nhave to deal with passing it through a huge call chain.\n\n> > After all, it's just modifying the _existing_ global for \"are we in a\n> > repo\".\n> \n> No it does not.\n\nPerhaps I wasn't clear on my \"it\" here. I mean that we will continue to\nhave startup_info->have_repository as a global (and if not that, then\ncertainly the environment variable GIT_DIR and the cwd are process\nglobals). I'm proposing a global flag to modify how to interpret those\nglobals. I don't think that really makes anything worse.\n\n> The read_early_config() function specifically does not leave any traces in\n> the global namespace. It calls the git_config_with_options() function\n> without touching the_config_set.\n\nI'm not talking about read_early_config() modifying the global state.\nI'm talking about main() modifying it so that lower-level functions like\nread_early_config() can make use of it.\n\n> I would look more favorably on this idea if we were to teach\n> the_config_set to record a little bit more about the state from which it\n> was constructed, and to auto-flush-and-re-read when it detects that, say,\n> git_dir was changed in the meantime.\n\nI considered that when fixing some bugs in git-init a few months ago,\nbut I think the cure becomes worse than the problem. Automatic cache\ninvalidation can have some tricky corner cases, and there really\n_aren't_ that many places where we need to do it. Just dropping\ngit_config_clear() in those places is ugly, but practical.\n\n> > And I do not see that global going away anytime soon.\n> \n> And that is really, really sad.\n\nI think we differ on that.\n\n> > As an alternative, I also think it would be OK to just always have the\n> > pager config read from local repo, even for init/clone.\n> \n> For the purpose of this current discussion, I am utterly uninterested in\n> the pager config. What I want to use the early config for is substantially\n> different and more relevant: I need to configure some command to run\n> before, and after, every single Git command here. And this configuration\n> needs to be per-repository. And no, I do not want to hardcode anything.\n\nI do not see how that changes things. If there is the concept of a\n\"before the command is run\" phase during which we would read from the\ncurrent-directory config even for git-init, it seems to me that applies\nlogically to pagers, aliases, _and_ whatever pre-command magic you are\ninterested in adding.\n\n> > In other words, to loosen the idea that git-init can _never_ look in the\n> > current git-dir, and declare that there is a stage before the command is\n> > initiated, and during which git may read local-repo config. Aliases\n> > would fall into this, too, so:\n> > \n> >   git config --local alias.foo init\n> >   git foo /some/other/dir\n> > \n> > would work (as it must, because we cannot know that \"foo\" is \"init\"\n> > until we read the config!).\n> \n> True.\n> \n> But is this a good excuse to just shrug our shoulders and let git-init\n> (which we do know very well) fall into the same trap?\n\nI'm not sure I agree it is a trap. There is a consistent and reasonable\nmental model where it is the natural thing. I understand that's not the\none you prefer, but it seems like a practical one to me.\n\n> > We already have a config-caching system. If we went with a global\n> > \"config_discover_refs\",\n> \n> Why \"_refs\"?\n\nEr, sorry, slip of the tongue. I think I meant config_discover_git, and\nfor some reason managed to repeat it over and over. Hopefully you\nfigured out what I meant.\n\n> > then I think the sequence for something like git-init would become:\n> > \n> >   1. When git.c starts, config_discover_refs is set to \"true\". Pager and\n> >      alias lookup may look in .git/config if it's available, even if\n> >      they go through the configset cache.\n> > \n> >   2. As soon as git-init starts, it disables config_discover_refs, and\n> >      it flushes the config cache. Any configset lookups will now examine\n> >      the reduced config.\n> > \n> >   3. When git-init has set up the real repo it is operating on, it can\n> >      reenable config_discover_refs (though it may not even need to; that\n> >      flag probably wouldn't have any effect once we've entered the\n> >      repository and have_git_dir() returns true).\n> \n> That is a bit fiddly, don't you think? The callers have to have very\n> intimate knowledge of the config reading to remember to set, and re-set,\n> that global. And to flush when appropriate.\n\nSure, but I think there are literally 2 callers who care about this.\n\n> How much nicer would the code be if the call to git_config() would realize\n> what needs to be done, don't you agree?\n\nNo, I wouldn't agree until I had seen a very elegant patch that\nimplements this and handles any corner cases. I looked into making a\npatch before and found that it got a bit ugly (sorry, I don't really\nremember the details). I am happy to have you show me that patch, but\npardon me if I remain skeptical until I see it. ;)\n\n> > Running t7006 I see a lot of old failures turned into successes, which\n> > is good (because running from a subdirectory now actually respects local\n> > pager config). The one failure looks like it is testing the wrong thing.\n> \n> Yeah, but due to the redirections I was not able to figure out what to\n> change to \"fix\" this.\n\nI think test_local_config_ignored would go away, and its callers become\ntest_core_pager_overrides. More or less a revert of 73e25e7cc (git\n--paginate: do not commit pager choice too early, 2010-06-26).\n\n> > proxy behavior and the failure case are disconnected. The test should be\n> > flipped, and ideally another one added that confirms we didn't actually\n> > run setup_git_directory(), but I'm not sure how to test that directly.\n> \n> Some dirty tricks come to mind, but I am not even sure that I want to test\n> for this. Why exactly do we need to avoid calling setup_git_directory() in\n> that case?\n\nI imagine that \"git -p init /path/to/new/repo\" would start from the\nwrong directory, for instance (or any other builtin which does not have\nRUN_SETUP would probably get confused).\n\n> > > - after discover_git_directory_gently() did its work, the code happily\n> > >   uses its result *only* for the current read_early_config() run, and\n> > >   lets setup_git_dir_gently() do the whole work *again*. For the sake of\n> > >   this RFC, I did not address that yet.\n> > \n> > If caching happens at the config layer, then we'd probably only call\n> > this once anyway (or if we did call it again after a config flush, it\n> > would be a good sign that we should compute its value again).\n> \n> I meant the git_dir, not the config... The git_dir is discovered, but a\n> subsequent setup_git_dir_gently() discovers it *again*, in a different\n> way.\n\nI don't think it's such a huge amount of work that it really matters\nfrom an optimization standpoint. The really ugly thing to me is if it\nreturns a different answer. And I think the best way to deal with that\nis to literally use the same code (and then if you want to cache the\nresult and not do the work again, it's pretty easy).\n\n-Peff\n"},{"id":"313114","messageId":"49f66f2b723af38a58c17bc8ae413bcee69e2a2f.1488506615.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488506615.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 3/9] setup_git_directory(): avoid changing global state during discovery","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T02:04:11Z","receivedAt":"2017-03-03T02:04:27Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"For historical reasons, Git searches for the .git/ directory (or the\n.git file) by changing the working directory successively to the parent\ndirectory of the current directory, until either anything was found or\nuntil a ceiling or a mount point is hit.\n\nFurther global state may be changed, depending on the actual type of\ndiscovery, e.g. the global variable `repository_format_precious_objects`\nis set in the `check_repository_format_gently()` function (which is a\nbit surprising, given the function name).\n\nWe do have a use case where we would like to find the .git/ directory\nwithout having any global state touched, though: when we read the early\nconfig e.g. for the pager or for alias expansion.\n\nLet's just rename the function `setup_git_directory_gently_1()` to\n`discover_git_directory()` and move all code that changes any global\nstate back into `setup_git_directory_gently()`.\n\nIn subsequent patches, we will export the `discover_git_directory()`\nfunction and make use of it.\n\nNote: the new loop is a *little* tricky, as we have to handle the root\ndirectory specially: we cannot simply strip away the last component\nincluding the slash, as the root directory only has that slash. To remedy\nthat, we introduce the `min_offset` variable that holds the minimal length\nof an absolute path, and using that to special-case the root directory,\nincluding an early exit before trying to find the parent of the root\ndirectory.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 187 ++++++++++++++++++++++++++++++++++++++--------------------------\n 1 file changed, 112 insertions(+), 75 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 89a0cef9231..edac3c27dc1 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -816,50 +816,49 @@ static int canonicalize_ceiling_entry(struct string_list_item *item,\n \t}\n }\n \n+enum discovery_result {\n+\tGIT_DIR_NONE = 0,\n+\tGIT_DIR_EXPLICIT,\n+\tGIT_DIR_DISCOVERED,\n+\tGIT_DIR_BARE,\n+\t/* these are errors */\n+\tGIT_DIR_HIT_CEILING = -1,\n+\tGIT_DIR_HIT_MOUNT_POINT = -2\n+};\n+\n /*\n  * We cannot decide in this function whether we are in the work tree or\n  * not, since the config can only be read _after_ this function was called.\n+ *\n+ * Also, we avoid changing any global state (such as the current working\n+ * directory) to allow early callers.\n+ *\n+ * The directory where the search should start needs to be passed in via the\n+ * `dir` parameter; upon return, the `dir` buffer will contain the path of\n+ * the directory where the search ended, and `gitdir` will contain the path of\n+ * the discovered .git/ directory, if any. This path may be relative against\n+ * `dir` (i.e. *not* necessarily the cwd).\n  */\n-static const char *setup_git_directory_gently_1(int *nongit_ok)\n+static enum discovery_result discover_git_directory(struct strbuf *dir,\n+\t\t\t\t\t\t    struct strbuf *gitdir)\n {\n \tconst char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);\n \tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n-\tstatic struct strbuf cwd = STRBUF_INIT;\n-\tconst char *gitdirenv, *ret;\n-\tchar *gitfile;\n-\tint offset, offset_parent, ceil_offset = -1;\n+\tconst char *gitdirenv;\n+\tint ceil_offset = -1, min_offset = has_dos_drive_prefix(dir->buf) ? 3 : 1;\n \tdev_t current_device = 0;\n \tint one_filesystem = 1;\n \n \t/*\n-\t * We may have read an incomplete configuration before\n-\t * setting-up the git directory. If so, clear the cache so\n-\t * that the next queries to the configuration reload complete\n-\t * configuration (including the per-repo config file that we\n-\t * ignored previously).\n-\t */\n-\tgit_config_clear();\n-\n-\t/*\n-\t * Let's assume that we are in a git repository.\n-\t * If it turns out later that we are somewhere else, the value will be\n-\t * updated accordingly.\n-\t */\n-\tif (nongit_ok)\n-\t\t*nongit_ok = 0;\n-\n-\tif (strbuf_getcwd(&cwd))\n-\t\tdie_errno(_(\"Unable to read current working directory\"));\n-\toffset = cwd.len;\n-\n-\t/*\n \t * If GIT_DIR is set explicitly, we're not going\n \t * to do any discovery, but we still do repository\n \t * validation.\n \t */\n \tgitdirenv = getenv(GIT_DIR_ENVIRONMENT);\n-\tif (gitdirenv)\n-\t\treturn setup_explicit_git_dir(gitdirenv, &cwd, nongit_ok);\n+\tif (gitdirenv) {\n+\t\tstrbuf_addstr(gitdir, gitdirenv);\n+\t\treturn GIT_DIR_EXPLICIT;\n+\t}\n \n \tif (env_ceiling_dirs) {\n \t\tint empty_entry_found = 0;\n@@ -867,15 +866,15 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);\n \t\tfilter_string_list(&ceiling_dirs, 0,\n \t\t\t\t   canonicalize_ceiling_entry, &empty_entry_found);\n-\t\tceil_offset = longest_ancestor_length(cwd.buf, &ceiling_dirs);\n+\t\tceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs);\n \t\tstring_list_clear(&ceiling_dirs, 0);\n \t}\n \n-\tif (ceil_offset < 0 && has_dos_drive_prefix(cwd.buf))\n-\t\tceil_offset = 1;\n+\tif (ceil_offset < 0)\n+\t\tceil_offset = min_offset - 2;\n \n \t/*\n-\t * Test in the following order (relative to the cwd):\n+\t * Test in the following order (relative to the dir):\n \t * - .git (file containing \"gitdir: <path>\")\n \t * - .git/\n \t * - ./ (bare)\n@@ -887,62 +886,100 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t */\n \tone_filesystem = !git_env_bool(\"GIT_DISCOVERY_ACROSS_FILESYSTEM\", 0);\n \tif (one_filesystem)\n-\t\tcurrent_device = get_device_or_die(\".\", NULL, 0);\n+\t\tcurrent_device = get_device_or_die(dir->buf, NULL, 0);\n \tfor (;;) {\n-\t\tgitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);\n-\t\tif (gitfile)\n-\t\t\tgitdirenv = gitfile = xstrdup(gitfile);\n-\t\telse {\n-\t\t\tif (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))\n-\t\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n-\t\t}\n+\t\tint offset = dir->len;\n \n+\t\tif (offset > min_offset)\n+\t\t\tstrbuf_addch(dir, '/');\n+\t\tstrbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);\n+\t\tgitdirenv = read_gitfile(dir->buf);\n+\t\tif (!gitdirenv && is_git_directory(dir->buf))\n+\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n+\t\tstrbuf_setlen(dir, offset);\n \t\tif (gitdirenv) {\n-\t\t\tret = setup_discovered_git_dir(gitdirenv,\n-\t\t\t\t\t\t       &cwd, offset,\n-\t\t\t\t\t\t       nongit_ok);\n-\t\t\tfree(gitfile);\n-\t\t\treturn ret;\n+\t\t\tstrbuf_addstr(gitdir, gitdirenv);\n+\t\t\treturn GIT_DIR_DISCOVERED;\n \t\t}\n-\t\tfree(gitfile);\n \n-\t\tif (is_git_directory(\".\"))\n-\t\t\treturn setup_bare_git_dir(&cwd, offset, nongit_ok);\n-\n-\t\toffset_parent = offset;\n-\t\twhile (--offset_parent > ceil_offset &&\n-\t\t       !is_dir_sep(dir->buf[offset_parent]));\n-\t\tif (offset_parent <= ceil_offset)\n-\t\t\treturn setup_nongit(cwd.buf, nongit_ok);\n-\t\tif (one_filesystem) {\n-\t\t\tdev_t parent_device = get_device_or_die(\"..\", cwd.buf,\n-\t\t\t\t\t\t\t\toffset);\n-\t\t\tif (parent_device != current_device) {\n-\t\t\t\tif (nongit_ok) {\n-\t\t\t\t\tif (chdir(cwd.buf))\n-\t\t\t\t\t\tdie_errno(_(\"Cannot come back to cwd\"));\n-\t\t\t\t\t*nongit_ok = 1;\n-\t\t\t\t\treturn NULL;\n-\t\t\t\t}\n-\t\t\t\tstrbuf_setlen(&cwd, offset);\n-\t\t\t\tdie(_(\"Not a git repository (or any parent up to mount point %s)\\n\"\n-\t\t\t\t\"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\"),\n-\t\t\t\t    cwd.buf);\n-\t\t\t}\n-\t\t}\n-\t\tif (chdir(\"..\")) {\n-\t\t\tstrbuf_setlen(&cwd, offset);\n-\t\t\tdie_errno(_(\"Cannot change to '%s/..'\"), cwd.buf);\n+\t\tif (is_git_directory(dir->buf)) {\n+\t\t\tstrbuf_addstr(gitdir, \".\");\n+\t\t\treturn GIT_DIR_BARE;\n \t\t}\n-\t\toffset = offset_parent;\n+\n+\t\tif (offset <= min_offset)\n+\t\t\treturn GIT_DIR_HIT_CEILING;\n+\n+\t\twhile (--offset > ceil_offset && !is_dir_sep(dir->buf[offset]));\n+\t\tif (offset <= ceil_offset)\n+\t\t\treturn GIT_DIR_HIT_CEILING;\n+\n+\t\tstrbuf_setlen(dir, offset > min_offset ?  offset : min_offset);\n+\t\tif (one_filesystem &&\n+\t\t    current_device != get_device_or_die(dir->buf, NULL, offset))\n+\t\t\treturn GIT_DIR_HIT_MOUNT_POINT;\n \t}\n }\n \n const char *setup_git_directory_gently(int *nongit_ok)\n {\n+\tstruct strbuf cwd = STRBUF_INIT, dir = STRBUF_INIT, gitdir = STRBUF_INIT;\n \tconst char *prefix;\n \n-\tprefix = setup_git_directory_gently_1(nongit_ok);\n+\t/*\n+\t * We may have read an incomplete configuration before\n+\t * setting-up the git directory. If so, clear the cache so\n+\t * that the next queries to the configuration reload complete\n+\t * configuration (including the per-repo config file that we\n+\t * ignored previously).\n+\t */\n+\tgit_config_clear();\n+\n+\t/*\n+\t * Let's assume that we are in a git repository.\n+\t * If it turns out later that we are somewhere else, the value will be\n+\t * updated accordingly.\n+\t */\n+\tif (nongit_ok)\n+\t\t*nongit_ok = 0;\n+\n+\tif (strbuf_getcwd(&cwd))\n+\t\tdie_errno(_(\"Unable to read current working directory\"));\n+\tstrbuf_addbuf(&dir, &cwd);\n+\n+\tswitch (discover_git_directory(&dir, &gitdir)) {\n+\tcase GIT_DIR_NONE:\n+\t\tprefix = NULL;\n+\t\tbreak;\n+\tcase GIT_DIR_EXPLICIT:\n+\t\tprefix = setup_explicit_git_dir(gitdir.buf, &cwd, nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_DISCOVERED:\n+\t\tif (dir.len < cwd.len && chdir(dir.buf))\n+\t\t\tdie(_(\"Cannot change to '%s'\"), dir.buf);\n+\t\tprefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len,\n+\t\t\t\t\t\t  nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_BARE:\n+\t\tif (dir.len < cwd.len && chdir(dir.buf))\n+\t\t\tdie(_(\"Cannot change to '%s'\"), dir.buf);\n+\t\tprefix = setup_bare_git_dir(&cwd, dir.len, nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_HIT_CEILING:\n+\t\tprefix = setup_nongit(cwd.buf, nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_HIT_MOUNT_POINT:\n+\t\tif (nongit_ok) {\n+\t\t\t*nongit_ok = 1;\n+\t\t\treturn NULL;\n+\t\t}\n+\t\tdie(_(\"Not a git repository (or any parent up to mount point %s)\\n\"\n+\t\t      \"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\"),\n+\t\t    dir.buf);\n+\tdefault:\n+\t\tdie(\"BUG: unhandled discover_git_directory() result\");\n+\t}\n+\n \tif (prefix)\n \t\tsetenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);\n \telse\n-- \n2.12.0.windows.1.3.g8a117c48243\n\n\n"},{"id":"313115","messageId":"0ca4cce042cc0b0f13f87363b70a3689057ae9b0.1488506615.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488506615.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 4/9] Export the discover_git_directory() function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T02:04:15Z","receivedAt":"2017-03-03T02:04:34Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The function we introduced earlier needs to return both the path to the\n.git/ directory as well as the \"cd-up\" path to allow\nsetup_git_directory() to retain its previous behavior as if it changed\nthe current working directory on its quest for the .git/ directory.\n\nLet's rename it and export a function with an easier signature that\n*just* discovers the .git/ directory.\n\nWe will use it in a subsequent patch to support early config reading\nbetter.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n cache.h |  1 +\n setup.c | 29 ++++++++++++++++++++++++++---\n 2 files changed, 27 insertions(+), 3 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 80b6372cf76..a104b76c02e 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -518,6 +518,7 @@ extern void set_git_work_tree(const char *tree);\n #define ALTERNATE_DB_ENVIRONMENT \"GIT_ALTERNATE_OBJECT_DIRECTORIES\"\n \n extern void setup_work_tree(void);\n+extern const char *discover_git_directory(struct strbuf *gitdir);\n extern const char *setup_git_directory_gently(int *);\n extern const char *setup_git_directory(void);\n extern char *prefix_path(const char *prefix, int len, const char *path);\ndiff --git a/setup.c b/setup.c\nindex edac3c27dc1..7ceca6cc6ef 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -839,8 +839,8 @@ enum discovery_result {\n  * the discovered .git/ directory, if any. This path may be relative against\n  * `dir` (i.e. *not* necessarily the cwd).\n  */\n-static enum discovery_result discover_git_directory(struct strbuf *dir,\n-\t\t\t\t\t\t    struct strbuf *gitdir)\n+static enum discovery_result discover_git_directory_1(struct strbuf *dir,\n+\t\t\t\t\t\t      struct strbuf *gitdir)\n {\n \tconst char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);\n \tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n@@ -921,6 +921,29 @@ static enum discovery_result discover_git_directory(struct strbuf *dir,\n \t}\n }\n \n+const char *discover_git_directory(struct strbuf *gitdir)\n+{\n+\tstruct strbuf dir = STRBUF_INIT;\n+\tint len;\n+\n+\tif (strbuf_getcwd(&dir))\n+\t\treturn NULL;\n+\n+\tlen = dir.len;\n+\tif (discover_git_directory_1(&dir, gitdir) < 0) {\n+\t\tstrbuf_release(&dir);\n+\t\treturn NULL;\n+\t}\n+\n+\tif (dir.len < len && !is_absolute_path(gitdir->buf)) {\n+\t\tstrbuf_addch(&dir, '/');\n+\t\tstrbuf_insert(gitdir, 0, dir.buf, dir.len);\n+\t}\n+\tstrbuf_release(&dir);\n+\n+\treturn gitdir->buf;\n+}\n+\n const char *setup_git_directory_gently(int *nongit_ok)\n {\n \tstruct strbuf cwd = STRBUF_INIT, dir = STRBUF_INIT, gitdir = STRBUF_INIT;\n@@ -947,7 +970,7 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \t\tdie_errno(_(\"Unable to read current working directory\"));\n \tstrbuf_addbuf(&dir, &cwd);\n \n-\tswitch (discover_git_directory(&dir, &gitdir)) {\n+\tswitch (discover_git_directory_1(&dir, &gitdir)) {\n \tcase GIT_DIR_NONE:\n \t\tprefix = NULL;\n \t\tbreak;\n-- \n2.12.0.windows.1.3.g8a117c48243\n\n\n"},{"id":"313116","messageId":"d1342d28fd402615f1f94d9190559070ed04b0d7.1488506615.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488506615.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 2/9] setup_git_directory(): use is_dir_sep() helper","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T02:04:07Z","receivedAt":"2017-03-03T02:04:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It is okay in practice to test for forward slashes in the output of\ngetcwd(), because we go out of our way to convert backslashes to forward\nslashes in getcwd()'s output on Windows.\n\nStill, the correct way to test for a dir separator is by using the\nhelper function we introduced for that very purpose. It also serves as a\ngood documentation what the code tries to do (not \"how\").\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/setup.c b/setup.c\nindex 967f289f1ef..89a0cef9231 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -910,7 +910,8 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t\t\treturn setup_bare_git_dir(&cwd, offset, nongit_ok);\n \n \t\toffset_parent = offset;\n-\t\twhile (--offset_parent > ceil_offset && cwd.buf[offset_parent] != '/');\n+\t\twhile (--offset_parent > ceil_offset &&\n+\t\t       !is_dir_sep(dir->buf[offset_parent]));\n \t\tif (offset_parent <= ceil_offset)\n \t\t\treturn setup_nongit(cwd.buf, nongit_ok);\n \t\tif (one_filesystem) {\n-- \n2.12.0.windows.1.3.g8a117c48243\n\n\n"},{"id":"313117","messageId":"8f79df34e63a37c052437dd022269b4a6c495108.1488506615.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488506615.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 1/9] t7006: replace dubious test","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T02:04:02Z","receivedAt":"2017-03-03T02:04:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The idea of the test case \"git -p - core.pager is not used from\nsubdirectory\" was to verify that the setup_git_directory() function had\nnot been called just to obtain the core.pager setting.\n\nHowever, we are about to fix the early config machinery so that it\n*does* work, without messing up the global state.\n\nOnce that is done, the core.pager setting *will* be used, even when\nrunning from a subdirectory, and that is a Good Thing.\n\nThe intention of that test case, however, was to verify that the\nsetup_git_directory() function has not run, because it changes global\nstate such as the current working directory.\n\nTo keep that spirit, but fix the incorrect assumption, this patch\nreplaces that test case by a new one that verifies that the pager is\nrun in the subdirectory, i.e. that the current working directory has\nnot been changed at the time the pager is configured and launched, even\nif the `rev-parse` command requires a .git/ directory and *will* change\nthe working directory.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t7006-pager.sh | 12 +++++++++++-\n 1 file changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t7006-pager.sh b/t/t7006-pager.sh\nindex c8dc665f2fd..427bfc605ad 100755\n--- a/t/t7006-pager.sh\n+++ b/t/t7006-pager.sh\n@@ -378,9 +378,19 @@ test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p request-pull'\n test_default_pager        expect_success test_must_fail 'git -p'\n test_PAGER_overrides      expect_success test_must_fail 'git -p'\n test_local_config_ignored expect_failure test_must_fail 'git -p'\n-test_no_local_config_subdir expect_success test_must_fail 'git -p'\n test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p'\n \n+test_expect_failure TTY 'core.pager in repo config works and retains cwd' '\n+\tsane_unset GIT_PAGER &&\n+\ttest_config core.pager \"cat >cwd-retained\" &&\n+\t(\n+\t\tcd sub &&\n+\t\trm -f cwd-retained &&\n+\t\ttest_terminal git -p rev-parse HEAD &&\n+\t\ttest -e cwd-retained\n+\t)\n+'\n+\n test_doesnt_paginate      expect_failure test_must_fail 'git -p nonsense'\n \n test_pager_choices                       'git shortlog'\n-- \n2.12.0.windows.1.3.g8a117c48243\n\n\n"},{"id":"313118","messageId":"64135c6df520c1ca6a6e667855ac03c14d2dd153.1488506615.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488506615.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 5/9] Make read_early_config() reusable","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T02:04:20Z","receivedAt":"2017-03-03T02:04:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The pager configuration needs to be read early, possibly before\ndiscovering any .git/ directory.\n\nLet's not hide this function in pager.c, but make it available to other\ncallers.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n cache.h  |  1 +\n config.c | 31 +++++++++++++++++++++++++++++++\n pager.c  | 31 -------------------------------\n 3 files changed, 32 insertions(+), 31 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex a104b76c02e..6b6780064f0 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1798,6 +1798,7 @@ extern int git_config_from_blob_sha1(config_fn_t fn, const char *name,\n \t\t\t\t     const unsigned char *sha1, void *data);\n extern void git_config_push_parameter(const char *text);\n extern int git_config_from_parameters(config_fn_t fn, void *data);\n+extern void read_early_config(config_fn_t cb, void *data);\n extern void git_config(config_fn_t fn, void *);\n extern int git_config_with_options(config_fn_t fn, void *,\n \t\t\t\t   struct git_config_source *config_source,\ndiff --git a/config.c b/config.c\nindex c6b874a7bf7..9cfbeafd04c 100644\n--- a/config.c\n+++ b/config.c\n@@ -1412,6 +1412,37 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \t}\n }\n \n+void read_early_config(config_fn_t cb, void *data)\n+{\n+\tgit_config_with_options(cb, data, NULL, 1);\n+\n+\t/*\n+\t * Note that this is a really dirty hack that does the wrong thing in\n+\t * many cases. The crux of the problem is that we cannot run\n+\t * setup_git_directory() early on in git's setup, so we have no idea if\n+\t * we are in a repository or not, and therefore are not sure whether\n+\t * and how to read repository-local config.\n+\t *\n+\t * So if we _aren't_ in a repository (or we are but we would reject its\n+\t * core.repositoryformatversion), we'll read whatever is in .git/config\n+\t * blindly. Similarly, if we _are_ in a repository, but not at the\n+\t * root, we'll fail to find .git/config (because it's really\n+\t * ../.git/config, etc). See t7006 for a complete set of failures.\n+\t *\n+\t * However, we have historically provided this hack because it does\n+\t * work some of the time (namely when you are at the top-level of a\n+\t * valid repository), and would rarely make things worse (i.e., you do\n+\t * not generally have a .git/config file sitting around).\n+\t */\n+\tif (!startup_info->have_repository) {\n+\t\tstruct git_config_source repo_config;\n+\n+\t\tmemset(&repo_config, 0, sizeof(repo_config));\n+\t\trepo_config.file = \".git/config\";\n+\t\tgit_config_with_options(cb, data, &repo_config, 1);\n+\t}\n+}\n+\n static void git_config_check_init(void);\n \n void git_config(config_fn_t fn, void *data)\ndiff --git a/pager.c b/pager.c\nindex ae796433630..73ca8bc3b17 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -43,37 +43,6 @@ static int core_pager_config(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n-static void read_early_config(config_fn_t cb, void *data)\n-{\n-\tgit_config_with_options(cb, data, NULL, 1);\n-\n-\t/*\n-\t * Note that this is a really dirty hack that does the wrong thing in\n-\t * many cases. The crux of the problem is that we cannot run\n-\t * setup_git_directory() early on in git's setup, so we have no idea if\n-\t * we are in a repository or not, and therefore are not sure whether\n-\t * and how to read repository-local config.\n-\t *\n-\t * So if we _aren't_ in a repository (or we are but we would reject its\n-\t * core.repositoryformatversion), we'll read whatever is in .git/config\n-\t * blindly. Similarly, if we _are_ in a repository, but not at the\n-\t * root, we'll fail to find .git/config (because it's really\n-\t * ../.git/config, etc). See t7006 for a complete set of failures.\n-\t *\n-\t * However, we have historically provided this hack because it does\n-\t * work some of the time (namely when you are at the top-level of a\n-\t * valid repository), and would rarely make things worse (i.e., you do\n-\t * not generally have a .git/config file sitting around).\n-\t */\n-\tif (!startup_info->have_repository) {\n-\t\tstruct git_config_source repo_config;\n-\n-\t\tmemset(&repo_config, 0, sizeof(repo_config));\n-\t\trepo_config.file = \".git/config\";\n-\t\tgit_config_with_options(cb, data, &repo_config, 1);\n-\t}\n-}\n-\n const char *git_pager(int stdout_is_tty)\n {\n \tconst char *pager;\n-- \n2.12.0.windows.1.3.g8a117c48243\n\n\n"},{"id":"313119","messageId":"33e692918c8d41507de5ec2b2e2d55982678408e.1488506615.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488506615.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 6/9] read_early_config(): special-case builtins that create a repository","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T02:04:24Z","receivedAt":"2017-03-03T02:04:45Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"When the command we are about to execute wants to create a repository\n(i.e. the command is `init` or `clone`), we *must not* look for a\nrepository config.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n cache.h  | 2 +-\n config.c | 3 ++-\n git.c    | 3 +++\n 3 files changed, 6 insertions(+), 2 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 6b6780064f0..0af7141242f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -2070,7 +2070,7 @@ const char *split_cmdline_strerror(int cmdline_errno);\n \n /* setup.c */\n struct startup_info {\n-\tint have_repository;\n+\tint have_repository, creating_repository;\n \tconst char *prefix;\n };\n extern struct startup_info *startup_info;\ndiff --git a/config.c b/config.c\nindex 9cfbeafd04c..980fcc6ff2e 100644\n--- a/config.c\n+++ b/config.c\n@@ -1434,7 +1434,8 @@ void read_early_config(config_fn_t cb, void *data)\n \t * valid repository), and would rarely make things worse (i.e., you do\n \t * not generally have a .git/config file sitting around).\n \t */\n-\tif (!startup_info->have_repository) {\n+\tif (!startup_info->creating_repository &&\n+\t    !startup_info->have_repository) {\n \t\tstruct git_config_source repo_config;\n \n \t\tmemset(&repo_config, 0, sizeof(repo_config));\ndiff --git a/git.c b/git.c\nindex 33f52acbcc8..9fb9bb90a21 100644\n--- a/git.c\n+++ b/git.c\n@@ -337,6 +337,9 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n \tstruct stat st;\n \tconst char *prefix;\n \n+\tif (p->fn == cmd_init_db || p->fn == cmd_clone)\n+\t\tstartup_info->creating_repository = 1;\n+\n \tprefix = NULL;\n \thelp = argc == 2 && !strcmp(argv[1], \"-h\");\n \tif (!help) {\n-- \n2.12.0.windows.1.3.g8a117c48243\n\n\n"},{"id":"313120","messageId":"cover.1488506615.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1481211338.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 0/9] Fix the early config","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T02:03:56Z","receivedAt":"2017-03-03T02:04:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"These patches are an attempt to make Git's startup sequence a bit less\nsurprising.\n\nThe idea here is to discover the .git/ directory gently (i.e. without\nchanging the current working directory, or global variables), and to use\nit to read the .git/config file early, before we actually called\nsetup_git_directory() (if we ever do that).\n\nThis also allows us to fix the early config e.g. to determine the pager\nor to resolve aliases in a non-surprising manner.\n\nThe first iteration of this patch series still tried to be clever and to\navoid having to disentangle the side effects from the\nsetup_git_directory_gently_1() function simply by duplicating the logic.\n\nHowever, Peff suggested in a very short sentence that this would not fly\nwell.\n\nLittle did I know that I would spend the better part of an entire week\non trying to address that innocuous comment! There are simply *so many*\nside effects in that code. Who would have thought that a function\ncalled check_repository_format() would set global variables?\n\nBut after all that work, I am actually quite a bit satisfied with the\nway things turned out.\n\nMy dirty little secret is that I actually need this for something else\nentirely. I need to patch an internal version of Git to gather\nstatistics, and to that end I need to read the config before and after\nrunning every Git command. Hence the need for a gentle, and correct\nearly config.\n\nNotes:\n\n- I do not handle dashed invocations of `init` and `clone` correctly.\n  That is, even if `git-init` and `git-clone` clearly do not want to\n  read the local config, they do. It does not matter all that much\n  because they do not use a pager, but still. It is a wart.\n\n- The read_early_config() function is still called multiple times,\n  re-reading all the config files and re-discovering the .git/ directory\n  multiple times, which is quite wasteful. I was tempted to take care of\n  that but I must not run the danger to spread myself even thinner these\n  days. If a patch adding that caching were to fly my way, I'd gladly\n  integrate it, of course... ;-)\n\nChanges since v1:\n\n- the discover_git_directory() function is no longer completely separate\n  from setup_git_directory(), but a callee of the latter.\n\n- t7006 succeeds now (I removed the incorrect test case in favor of one\n  that verifies that setup_git_directory() was not run via the tell-tale\n  that the current working directory has not changed when the pager\n  runs).\n\n\nJohannes Schindelin (9):\n  t7006: replace dubious test\n  setup_git_directory(): use is_dir_sep() helper\n  setup_git_directory(): avoid changing global state during discovery\n  Export the discover_git_directory() function\n  Make read_early_config() reusable\n  read_early_config(): special-case builtins that create a repository\n  read_early_config(): avoid .git/config hack when unneeded\n  read_early_config(): really discover .git/\n  Test read_early_config()\n\n cache.h                 |   4 +-\n config.c                |  36 +++++++++\n git.c                   |   3 +\n pager.c                 |  31 -------\n setup.c                 | 211 +++++++++++++++++++++++++++++++-----------------\n t/helper/test-config.c  |  15 ++++\n t/t1309-early-config.sh |  50 ++++++++++++\n t/t7006-pager.sh        |  18 ++++-\n 8 files changed, 257 insertions(+), 111 deletions(-)\n create mode 100755 t/t1309-early-config.sh\n\n\nbase-commit: 3bc53220cb2dcf709f7a027a3f526befd021d858\nPublished-As: https://github.com/dscho/git/releases/tag/early-config-v2\nFetch-It-Via: git fetch https://github.com/dscho/git early-config-v2\n\nInterdiff vs v1:\n\n diff --git a/builtin/am.c b/builtin/am.c\n index 2b81dabddd9..f7a7a971fbe 100644\n --- a/builtin/am.c\n +++ b/builtin/am.c\n @@ -1791,7 +1791,7 @@ static int do_interactive(struct am_state *state)\n  \t\t\t}\n  \t\t\tstrbuf_release(&msg);\n  \t\t} else if (*reply == 'v' || *reply == 'V') {\n -\t\t\tconst char *pager = git_pager(1, 1);\n +\t\t\tconst char *pager = git_pager(1);\n  \t\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n  \n  \t\t\tif (!pager)\n diff --git a/builtin/blame.c b/builtin/blame.c\n index 628ca237da6..cffc6265408 100644\n --- a/builtin/blame.c\n +++ b/builtin/blame.c\n @@ -2919,7 +2919,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n  \tassign_blame(&sb, opt);\n  \n  \tif (!incremental)\n -\t\tsetup_pager(1);\n +\t\tsetup_pager();\n  \n  \tfree(final_commit_name);\n  \n diff --git a/builtin/grep.c b/builtin/grep.c\n index f820e4b1c4d..9304c33e750 100644\n --- a/builtin/grep.c\n +++ b/builtin/grep.c\n @@ -1133,7 +1133,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n  \t}\n  \n  \tif (show_in_pager == default_pager)\n -\t\tshow_in_pager = git_pager(1, 1);\n +\t\tshow_in_pager = git_pager(1);\n  \tif (show_in_pager) {\n  \t\topt.color = 0;\n  \t\topt.name_only = 1;\n @@ -1268,7 +1268,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n  \t\tdie(_(\"option not supported with --recurse-submodules.\"));\n  \n  \tif (!show_in_pager && !opt.status_only)\n -\t\tsetup_pager(1);\n +\t\tsetup_pager();\n  \n  \tif (!use_index && (untracked || cached))\n  \t\tdie(_(\"--cached or --untracked cannot be used with --no-index.\"));\n diff --git a/builtin/log.c b/builtin/log.c\n index 96618d38cbf..55d20cc2d88 100644\n --- a/builtin/log.c\n +++ b/builtin/log.c\n @@ -203,7 +203,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,\n  \tif (rev->line_level_traverse)\n  \t\tline_log_init(rev, line_cb.prefix, &line_cb.args);\n  \n -\tsetup_pager(1);\n +\tsetup_pager();\n  }\n  \n  static void cmd_log_init(int argc, const char **argv, const char *prefix,\n @@ -1600,7 +1600,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n  \tif (!use_stdout)\n  \t\toutput_directory = set_outdir(prefix, output_directory);\n  \telse\n -\t\tsetup_pager(1);\n +\t\tsetup_pager();\n  \n  \tif (output_directory) {\n  \t\tif (rev.diffopt.use_color != GIT_COLOR_ALWAYS)\n diff --git a/builtin/var.c b/builtin/var.c\n index 879867b8427..aedbb53a2da 100644\n --- a/builtin/var.c\n +++ b/builtin/var.c\n @@ -19,7 +19,7 @@ static const char *editor(int flag)\n  \n  static const char *pager(int flag)\n  {\n -\tconst char *pgm = git_pager(1, 1);\n +\tconst char *pgm = git_pager(1);\n  \n  \tif (!pgm)\n  \t\tpgm = \"cat\";\n diff --git a/cache.h b/cache.h\n index 4d89966d711..0af7141242f 100644\n --- a/cache.h\n +++ b/cache.h\n @@ -518,6 +518,7 @@ extern void set_git_work_tree(const char *tree);\n  #define ALTERNATE_DB_ENVIRONMENT \"GIT_ALTERNATE_OBJECT_DIRECTORIES\"\n  \n  extern void setup_work_tree(void);\n +extern const char *discover_git_directory(struct strbuf *gitdir);\n  extern const char *setup_git_directory_gently(int *);\n  extern const char *setup_git_directory(void);\n  extern char *prefix_path(const char *prefix, int len, const char *path);\n @@ -1428,7 +1429,7 @@ extern const char *fmt_name(const char *name, const char *email);\n  extern const char *ident_default_name(void);\n  extern const char *ident_default_email(void);\n  extern const char *git_editor(void);\n -extern const char *git_pager(int stdout_is_tty, int discover_git_dir);\n +extern const char *git_pager(int stdout_is_tty);\n  extern int git_ident_config(const char *, const char *, void *);\n  extern void reset_ident_date(void);\n  \n @@ -1797,8 +1798,7 @@ extern int git_config_from_blob_sha1(config_fn_t fn, const char *name,\n  \t\t\t\t     const unsigned char *sha1, void *data);\n  extern void git_config_push_parameter(const char *text);\n  extern int git_config_from_parameters(config_fn_t fn, void *data);\n -extern void read_early_config(config_fn_t cb, void *data,\n -\t\t\t      int discover_git_dir);\n +extern void read_early_config(config_fn_t cb, void *data);\n  extern void git_config(config_fn_t fn, void *);\n  extern int git_config_with_options(config_fn_t fn, void *,\n  \t\t\t\t   struct git_config_source *config_source,\n @@ -1994,12 +1994,12 @@ __attribute__((format (printf, 2, 3)))\n  extern void write_file(const char *path, const char *fmt, ...);\n  \n  /* pager.c */\n -extern void setup_pager(int discover_git_dir);\n +extern void setup_pager(void);\n  extern int pager_in_use(void);\n  extern int pager_use_color;\n  extern int term_columns(void);\n  extern int decimal_width(uintmax_t);\n -extern int check_pager_config(const char *cmd, int discover_git_dir);\n +extern int check_pager_config(const char *cmd);\n  extern void prepare_pager_args(struct child_process *, const char *pager);\n  \n  extern const char *editor_program;\n @@ -2070,7 +2070,7 @@ const char *split_cmdline_strerror(int cmdline_errno);\n  \n  /* setup.c */\n  struct startup_info {\n -\tint have_repository;\n +\tint have_repository, creating_repository;\n  \tconst char *prefix;\n  };\n  extern struct startup_info *startup_info;\n diff --git a/config.c b/config.c\n index c9f191e1fe3..bcda397d42e 100644\n --- a/config.c\n +++ b/config.c\n @@ -1412,106 +1412,32 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n  \t}\n  }\n  \n -/*\n - * A \"string_list_each_func_t\" function that canonicalizes an entry\n - * from GIT_CEILING_DIRECTORIES using real_path_if_valid(), or\n - * discards it if unusable.  The presence of an empty entry in\n - * GIT_CEILING_DIRECTORIES turns off canonicalization for all\n - * subsequent entries.\n - */\n -static int canonicalize_ceiling_entry(struct string_list_item *item,\n -\t\t\t\t      void *cb_data)\n -{\n -\tint *empty_entry_found = cb_data;\n -\tchar *ceil = item->string;\n -\n -\tif (!*ceil) {\n -\t\t*empty_entry_found = 1;\n -\t\treturn 0;\n -\t} else if (!is_absolute_path(ceil)) {\n -\t\treturn 0;\n -\t} else if (*empty_entry_found) {\n -\t\t/* Keep entry but do not canonicalize it */\n -\t\treturn 1;\n -\t} else {\n -\t\tconst char *real_path = real_path_if_valid(ceil);\n -\t\tif (!real_path)\n -\t\t\treturn 0;\n -\t\tfree(item->string);\n -\t\titem->string = xstrdup(real_path);\n -\t\treturn 1;\n -\t}\n -}\n -\n -/*\n - * Note that this is a really dirty hack that replicates what the\n - * setup_git_directory() function does, without changing the current\n - * working directory. The crux of the problem is that we cannot run\n - * setup_git_directory() early on in git's setup, so we have to\n - * duplicate the work that setup_git_directory() would otherwise do.\n - */\n -static int discover_git_directory_gently(struct strbuf *result)\n -{\n -\tconst char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);\n -\tint ceiling_offset = -1;\n -\tconst char *p;\n -\n -\tif (strbuf_getcwd(result) < 0)\n -\t\treturn -1;\n -\tp = real_path_if_valid(result->buf);\n -\tif (!p)\n -\t\treturn -1;\n -\tstrbuf_reset(result);\n -\tstrbuf_addstr(result, p);\n -\n -\tif (env_ceiling_dirs) {\n -\t\tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n -\t\tint empty_entry_found = 0;\n -\n -\t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP,\n -\t\t\t\t  -1);\n -\t\tfilter_string_list(&ceiling_dirs, 0, canonicalize_ceiling_entry,\n -\t\t\t\t   &empty_entry_found);\n -\t\tceiling_offset = longest_ancestor_length(result->buf,\n -\t\t\t\t\t\t\t &ceiling_dirs);\n -\t\tstring_list_clear(&ceiling_dirs, 0);\n -\t}\n -\n -\tif (ceiling_offset < 0 && has_dos_drive_prefix(result->buf))\n -\t\tceiling_offset = 1;\n -\n -\tfor (;;) {\n -\t\tint len = result->len, i;\n -\n -\t\tstrbuf_addstr(result, \"/\" DEFAULT_GIT_DIR_ENVIRONMENT);\n -\t\tp = read_gitfile_gently(result->buf, &i);\n -\t\tif (p) {\n -\t\t\tstrbuf_reset(result);\n -\t\t\tstrbuf_addstr(result, p);\n -\t\t\treturn 0;\n -\t\t}\n -\t\tif (is_git_directory(result->buf))\n -\t\t\treturn 0;\n -\t\tstrbuf_setlen(result, len);\n -\t\tif (is_git_directory(result->buf))\n -\t\t\treturn 0;\n -\t\tfor (i = len; --i > ceiling_offset; )\n -\t\t\tif (is_dir_sep(result->buf[i]))\n -\t\t\t\tbreak;\n -\t\tif (i <= ceiling_offset)\n -\t\t\treturn -1;\n -\t\tstrbuf_setlen(result, i);\n -\t}\n -}\n -\n -void read_early_config(config_fn_t cb, void *data, int discover_git_dir)\n +void read_early_config(config_fn_t cb, void *data)\n  {\n  \tstruct strbuf buf = STRBUF_INIT;\n  \n  \tgit_config_with_options(cb, data, NULL, 1);\n  \n -\tif (discover_git_dir && !have_git_dir() &&\n -\t    !discover_git_directory_gently(&buf)) {\n +\t/*\n +\t * Note that this is a really dirty hack that does the wrong thing in\n +\t * many cases. The crux of the problem is that we cannot run\n +\t * setup_git_directory() early on in git's setup, so we have no idea if\n +\t * we are in a repository or not, and therefore are not sure whether\n +\t * and how to read repository-local config.\n +\t *\n +\t * So if we _aren't_ in a repository (or we are but we would reject its\n +\t * core.repositoryformatversion), we'll read whatever is in .git/config\n +\t * blindly. Similarly, if we _are_ in a repository, but not at the\n +\t * root, we'll fail to find .git/config (because it's really\n +\t * ../.git/config, etc). See t7006 for a complete set of failures.\n +\t *\n +\t * However, we have historically provided this hack because it does\n +\t * work some of the time (namely when you are at the top-level of a\n +\t * valid repository), and would rarely make things worse (i.e., you do\n +\t * not generally have a .git/config file sitting around).\n +\t */\n +\tif (!startup_info->creating_repository && !have_git_dir() &&\n +\t    discover_git_directory(&buf)) {\n  \t\tstruct git_config_source repo_config;\n  \n  \t\tmemset(&repo_config, 0, sizeof(repo_config));\n diff --git a/diff.c b/diff.c\n index 1e1d3b85c2a..051761be405 100644\n --- a/diff.c\n +++ b/diff.c\n @@ -5259,6 +5259,6 @@ void setup_diff_pager(struct diff_options *opt)\n  \t * --exit-code\" in hooks and other scripts, we do not do so.\n  \t */\n  \tif (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&\n -\t    check_pager_config(\"diff\", 1) != 0)\n -\t\tsetup_pager(1);\n +\t    check_pager_config(\"diff\") != 0)\n +\t\tsetup_pager();\n  }\n diff --git a/git.c b/git.c\n index d4712b25fee..9fb9bb90a21 100644\n --- a/git.c\n +++ b/git.c\n @@ -61,13 +61,13 @@ static void restore_env(int external_alias)\n  \t}\n  }\n  \n -static void commit_pager_choice(int discover_git_dir) {\n +static void commit_pager_choice(void) {\n  \tswitch (use_pager) {\n  \tcase 0:\n  \t\tsetenv(\"GIT_PAGER\", \"cat\", 1);\n  \t\tbreak;\n  \tcase 1:\n -\t\tsetup_pager(discover_git_dir);\n +\t\tsetup_pager();\n  \t\tbreak;\n  \tdefault:\n  \t\tbreak;\n @@ -261,7 +261,7 @@ static int handle_alias(int *argcp, const char ***argv)\n  \t\tif (alias_string[0] == '!') {\n  \t\t\tstruct child_process child = CHILD_PROCESS_INIT;\n  \n -\t\t\tcommit_pager_choice(1);\n +\t\t\tcommit_pager_choice();\n  \t\t\trestore_env(1);\n  \n  \t\t\tchild.use_shell = 1;\n @@ -318,13 +318,12 @@ static int handle_alias(int *argcp, const char ***argv)\n  #define RUN_SETUP\t\t(1<<0)\n  #define RUN_SETUP_GENTLY\t(1<<1)\n  #define USE_PAGER\t\t(1<<2)\n -#define CREATES_GIT_DIR         (1<<3)\n  /*\n   * require working tree to be present -- anything uses this needs\n   * RUN_SETUP for reading from the configuration file.\n   */\n -#define NEED_WORK_TREE\t\t(1<<4)\n -#define SUPPORT_SUPER_PREFIX\t(1<<5)\n +#define NEED_WORK_TREE\t\t(1<<3)\n +#define SUPPORT_SUPER_PREFIX\t(1<<4)\n  \n  struct cmd_struct {\n  \tconst char *cmd;\n @@ -338,6 +337,9 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n  \tstruct stat st;\n  \tconst char *prefix;\n  \n +\tif (p->fn == cmd_init_db || p->fn == cmd_clone)\n +\t\tstartup_info->creating_repository = 1;\n +\n  \tprefix = NULL;\n  \thelp = argc == 2 && !strcmp(argv[1], \"-h\");\n  \tif (!help) {\n @@ -349,7 +351,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n  \t\t}\n  \n  \t\tif (use_pager == -1 && p->option & (RUN_SETUP | RUN_SETUP_GENTLY))\n -\t\t\tuse_pager = check_pager_config(p->cmd, !(p->option & CREATES_GIT_DIR));\n +\t\t\tuse_pager = check_pager_config(p->cmd);\n  \t\tif (use_pager == -1 && p->option & USE_PAGER)\n  \t\t\tuse_pager = 1;\n  \n @@ -357,7 +359,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n  \t\t    startup_info->have_repository) /* get_git_dir() may set up repo, avoid that */\n  \t\t\ttrace_repo_setup(prefix);\n  \t}\n -\tcommit_pager_choice(!(p->option & CREATES_GIT_DIR));\n +\tcommit_pager_choice();\n  \n  \tif (!help && get_super_prefix()) {\n  \t\tif (!(p->option & SUPPORT_SUPER_PREFIX))\n @@ -413,7 +415,7 @@ static struct cmd_struct commands[] = {\n  \t{ \"cherry\", cmd_cherry, RUN_SETUP },\n  \t{ \"cherry-pick\", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },\n  \t{ \"clean\", cmd_clean, RUN_SETUP | NEED_WORK_TREE },\n -\t{ \"clone\", cmd_clone, CREATES_GIT_DIR },\n +\t{ \"clone\", cmd_clone },\n  \t{ \"column\", cmd_column, RUN_SETUP_GENTLY },\n  \t{ \"commit\", cmd_commit, RUN_SETUP | NEED_WORK_TREE },\n  \t{ \"commit-tree\", cmd_commit_tree, RUN_SETUP },\n @@ -440,7 +442,7 @@ static struct cmd_struct commands[] = {\n  \t{ \"hash-object\", cmd_hash_object },\n  \t{ \"help\", cmd_help },\n  \t{ \"index-pack\", cmd_index_pack, RUN_SETUP_GENTLY },\n -\t{ \"init\", cmd_init_db, CREATES_GIT_DIR },\n +\t{ \"init\", cmd_init_db },\n  \t{ \"init-db\", cmd_init_db },\n  \t{ \"interpret-trailers\", cmd_interpret_trailers, RUN_SETUP_GENTLY },\n  \t{ \"log\", cmd_log, RUN_SETUP },\n @@ -585,8 +587,8 @@ static void execv_dashed_external(const char **argv)\n  \t\tdie(\"%s doesn't support --super-prefix\", argv[0]);\n  \n  \tif (use_pager == -1)\n -\t\tuse_pager = check_pager_config(argv[0], 1);\n -\tcommit_pager_choice(1);\n +\t\tuse_pager = check_pager_config(argv[0]);\n +\tcommit_pager_choice();\n  \n  \targv_array_pushf(&cmd.args, \"git-%s\", argv[0]);\n  \targv_array_pushv(&cmd.args, argv + 1);\n @@ -684,7 +686,7 @@ int cmd_main(int argc, const char **argv)\n  \t\tskip_prefix(argv[0], \"--\", &argv[0]);\n  \t} else {\n  \t\t/* The user didn't specify a command; give them help */\n -\t\tcommit_pager_choice(1);\n +\t\tcommit_pager_choice();\n  \t\tprintf(\"usage: %s\\n\\n\", git_usage_string);\n  \t\tlist_common_cmds_help();\n  \t\tprintf(\"\\n%s\\n\", _(git_more_info_string));\n diff --git a/pager.c b/pager.c\n index 16b3cbe2320..73ca8bc3b17 100644\n --- a/pager.c\n +++ b/pager.c\n @@ -43,7 +43,7 @@ static int core_pager_config(const char *var, const char *value, void *data)\n  \treturn 0;\n  }\n  \n -const char *git_pager(int stdout_is_tty, int discover_git_dir)\n +const char *git_pager(int stdout_is_tty)\n  {\n  \tconst char *pager;\n  \n @@ -53,8 +53,7 @@ const char *git_pager(int stdout_is_tty, int discover_git_dir)\n  \tpager = getenv(\"GIT_PAGER\");\n  \tif (!pager) {\n  \t\tif (!pager_program)\n -\t\t\tread_early_config(core_pager_config, NULL,\n -\t\t\t\t\t  discover_git_dir);\n +\t\t\tread_early_config(core_pager_config, NULL);\n  \t\tpager = pager_program;\n  \t}\n  \tif (!pager)\n @@ -101,9 +100,9 @@ void prepare_pager_args(struct child_process *pager_process, const char *pager)\n  \tsetup_pager_env(&pager_process->env_array);\n  }\n  \n -void setup_pager(int discover_git_dir)\n +void setup_pager(void)\n  {\n -\tconst char *pager = git_pager(isatty(1), discover_git_dir);\n +\tconst char *pager = git_pager(isatty(1));\n  \n  \tif (!pager)\n  \t\treturn;\n @@ -209,7 +208,7 @@ static int pager_command_config(const char *var, const char *value, void *vdata)\n  }\n  \n  /* returns 0 for \"no pager\", 1 for \"use pager\", and -1 for \"not specified\" */\n -int check_pager_config(const char *cmd, int discover_git_dir)\n +int check_pager_config(const char *cmd)\n  {\n  \tstruct pager_command_config_data data;\n  \n @@ -217,7 +216,7 @@ int check_pager_config(const char *cmd, int discover_git_dir)\n  \tdata.want = -1;\n  \tdata.value = NULL;\n  \n -\tread_early_config(pager_command_config, &data, discover_git_dir);\n +\tread_early_config(pager_command_config, &data);\n  \n  \tif (data.value)\n  \t\tpager_program = data.value;\n diff --git a/setup.c b/setup.c\n index 967f289f1ef..7ceca6cc6ef 100644\n --- a/setup.c\n +++ b/setup.c\n @@ -816,50 +816,49 @@ static int canonicalize_ceiling_entry(struct string_list_item *item,\n  \t}\n  }\n  \n +enum discovery_result {\n +\tGIT_DIR_NONE = 0,\n +\tGIT_DIR_EXPLICIT,\n +\tGIT_DIR_DISCOVERED,\n +\tGIT_DIR_BARE,\n +\t/* these are errors */\n +\tGIT_DIR_HIT_CEILING = -1,\n +\tGIT_DIR_HIT_MOUNT_POINT = -2\n +};\n +\n  /*\n   * We cannot decide in this function whether we are in the work tree or\n   * not, since the config can only be read _after_ this function was called.\n + *\n + * Also, we avoid changing any global state (such as the current working\n + * directory) to allow early callers.\n + *\n + * The directory where the search should start needs to be passed in via the\n + * `dir` parameter; upon return, the `dir` buffer will contain the path of\n + * the directory where the search ended, and `gitdir` will contain the path of\n + * the discovered .git/ directory, if any. This path may be relative against\n + * `dir` (i.e. *not* necessarily the cwd).\n   */\n -static const char *setup_git_directory_gently_1(int *nongit_ok)\n +static enum discovery_result discover_git_directory_1(struct strbuf *dir,\n +\t\t\t\t\t\t      struct strbuf *gitdir)\n  {\n  \tconst char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);\n  \tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n -\tstatic struct strbuf cwd = STRBUF_INIT;\n -\tconst char *gitdirenv, *ret;\n -\tchar *gitfile;\n -\tint offset, offset_parent, ceil_offset = -1;\n +\tconst char *gitdirenv;\n +\tint ceil_offset = -1, min_offset = has_dos_drive_prefix(dir->buf) ? 3 : 1;\n  \tdev_t current_device = 0;\n  \tint one_filesystem = 1;\n  \n  \t/*\n -\t * We may have read an incomplete configuration before\n -\t * setting-up the git directory. If so, clear the cache so\n -\t * that the next queries to the configuration reload complete\n -\t * configuration (including the per-repo config file that we\n -\t * ignored previously).\n -\t */\n -\tgit_config_clear();\n -\n -\t/*\n -\t * Let's assume that we are in a git repository.\n -\t * If it turns out later that we are somewhere else, the value will be\n -\t * updated accordingly.\n -\t */\n -\tif (nongit_ok)\n -\t\t*nongit_ok = 0;\n -\n -\tif (strbuf_getcwd(&cwd))\n -\t\tdie_errno(_(\"Unable to read current working directory\"));\n -\toffset = cwd.len;\n -\n -\t/*\n  \t * If GIT_DIR is set explicitly, we're not going\n  \t * to do any discovery, but we still do repository\n  \t * validation.\n  \t */\n  \tgitdirenv = getenv(GIT_DIR_ENVIRONMENT);\n -\tif (gitdirenv)\n -\t\treturn setup_explicit_git_dir(gitdirenv, &cwd, nongit_ok);\n +\tif (gitdirenv) {\n +\t\tstrbuf_addstr(gitdir, gitdirenv);\n +\t\treturn GIT_DIR_EXPLICIT;\n +\t}\n  \n  \tif (env_ceiling_dirs) {\n  \t\tint empty_entry_found = 0;\n @@ -867,15 +866,15 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n  \t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);\n  \t\tfilter_string_list(&ceiling_dirs, 0,\n  \t\t\t\t   canonicalize_ceiling_entry, &empty_entry_found);\n -\t\tceil_offset = longest_ancestor_length(cwd.buf, &ceiling_dirs);\n +\t\tceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs);\n  \t\tstring_list_clear(&ceiling_dirs, 0);\n  \t}\n  \n -\tif (ceil_offset < 0 && has_dos_drive_prefix(cwd.buf))\n -\t\tceil_offset = 1;\n +\tif (ceil_offset < 0)\n +\t\tceil_offset = min_offset - 2;\n  \n  \t/*\n -\t * Test in the following order (relative to the cwd):\n +\t * Test in the following order (relative to the dir):\n  \t * - .git (file containing \"gitdir: <path>\")\n  \t * - .git/\n  \t * - ./ (bare)\n @@ -887,61 +886,123 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n  \t */\n  \tone_filesystem = !git_env_bool(\"GIT_DISCOVERY_ACROSS_FILESYSTEM\", 0);\n  \tif (one_filesystem)\n -\t\tcurrent_device = get_device_or_die(\".\", NULL, 0);\n +\t\tcurrent_device = get_device_or_die(dir->buf, NULL, 0);\n  \tfor (;;) {\n -\t\tgitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);\n -\t\tif (gitfile)\n -\t\t\tgitdirenv = gitfile = xstrdup(gitfile);\n -\t\telse {\n -\t\t\tif (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))\n -\t\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n -\t\t}\n -\n +\t\tint offset = dir->len;\n +\n +\t\tif (offset > min_offset)\n +\t\t\tstrbuf_addch(dir, '/');\n +\t\tstrbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);\n +\t\tgitdirenv = read_gitfile(dir->buf);\n +\t\tif (!gitdirenv && is_git_directory(dir->buf))\n +\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n +\t\tstrbuf_setlen(dir, offset);\n  \t\tif (gitdirenv) {\n -\t\t\tret = setup_discovered_git_dir(gitdirenv,\n -\t\t\t\t\t\t       &cwd, offset,\n -\t\t\t\t\t\t       nongit_ok);\n -\t\t\tfree(gitfile);\n -\t\t\treturn ret;\n +\t\t\tstrbuf_addstr(gitdir, gitdirenv);\n +\t\t\treturn GIT_DIR_DISCOVERED;\n  \t\t}\n -\t\tfree(gitfile);\n  \n -\t\tif (is_git_directory(\".\"))\n -\t\t\treturn setup_bare_git_dir(&cwd, offset, nongit_ok);\n -\n -\t\toffset_parent = offset;\n -\t\twhile (--offset_parent > ceil_offset && cwd.buf[offset_parent] != '/');\n -\t\tif (offset_parent <= ceil_offset)\n -\t\t\treturn setup_nongit(cwd.buf, nongit_ok);\n -\t\tif (one_filesystem) {\n -\t\t\tdev_t parent_device = get_device_or_die(\"..\", cwd.buf,\n -\t\t\t\t\t\t\t\toffset);\n -\t\t\tif (parent_device != current_device) {\n -\t\t\t\tif (nongit_ok) {\n -\t\t\t\t\tif (chdir(cwd.buf))\n -\t\t\t\t\t\tdie_errno(_(\"Cannot come back to cwd\"));\n -\t\t\t\t\t*nongit_ok = 1;\n -\t\t\t\t\treturn NULL;\n -\t\t\t\t}\n -\t\t\t\tstrbuf_setlen(&cwd, offset);\n -\t\t\t\tdie(_(\"Not a git repository (or any parent up to mount point %s)\\n\"\n -\t\t\t\t\"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\"),\n -\t\t\t\t    cwd.buf);\n -\t\t\t}\n -\t\t}\n -\t\tif (chdir(\"..\")) {\n -\t\t\tstrbuf_setlen(&cwd, offset);\n -\t\t\tdie_errno(_(\"Cannot change to '%s/..'\"), cwd.buf);\n +\t\tif (is_git_directory(dir->buf)) {\n +\t\t\tstrbuf_addstr(gitdir, \".\");\n +\t\t\treturn GIT_DIR_BARE;\n  \t\t}\n -\t\toffset = offset_parent;\n +\n +\t\tif (offset <= min_offset)\n +\t\t\treturn GIT_DIR_HIT_CEILING;\n +\n +\t\twhile (--offset > ceil_offset && !is_dir_sep(dir->buf[offset]));\n +\t\tif (offset <= ceil_offset)\n +\t\t\treturn GIT_DIR_HIT_CEILING;\n +\n +\t\tstrbuf_setlen(dir, offset > min_offset ?  offset : min_offset);\n +\t\tif (one_filesystem &&\n +\t\t    current_device != get_device_or_die(dir->buf, NULL, offset))\n +\t\t\treturn GIT_DIR_HIT_MOUNT_POINT;\n +\t}\n +}\n +\n +const char *discover_git_directory(struct strbuf *gitdir)\n +{\n +\tstruct strbuf dir = STRBUF_INIT;\n +\tint len;\n +\n +\tif (strbuf_getcwd(&dir))\n +\t\treturn NULL;\n +\n +\tlen = dir.len;\n +\tif (discover_git_directory_1(&dir, gitdir) < 0) {\n +\t\tstrbuf_release(&dir);\n +\t\treturn NULL;\n +\t}\n +\n +\tif (dir.len < len && !is_absolute_path(gitdir->buf)) {\n +\t\tstrbuf_addch(&dir, '/');\n +\t\tstrbuf_insert(gitdir, 0, dir.buf, dir.len);\n  \t}\n +\tstrbuf_release(&dir);\n +\n +\treturn gitdir->buf;\n  }\n  \n  const char *setup_git_directory_gently(int *nongit_ok)\n  {\n +\tstruct strbuf cwd = STRBUF_INIT, dir = STRBUF_INIT, gitdir = STRBUF_INIT;\n  \tconst char *prefix;\n  \n -\tprefix = setup_git_directory_gently_1(nongit_ok);\n +\t/*\n +\t * We may have read an incomplete configuration before\n +\t * setting-up the git directory. If so, clear the cache so\n +\t * that the next queries to the configuration reload complete\n +\t * configuration (including the per-repo config file that we\n +\t * ignored previously).\n +\t */\n +\tgit_config_clear();\n +\n +\t/*\n +\t * Let's assume that we are in a git repository.\n +\t * If it turns out later that we are somewhere else, the value will be\n +\t * updated accordingly.\n +\t */\n +\tif (nongit_ok)\n +\t\t*nongit_ok = 0;\n +\n +\tif (strbuf_getcwd(&cwd))\n +\t\tdie_errno(_(\"Unable to read current working directory\"));\n +\tstrbuf_addbuf(&dir, &cwd);\n +\n +\tswitch (discover_git_directory_1(&dir, &gitdir)) {\n +\tcase GIT_DIR_NONE:\n +\t\tprefix = NULL;\n +\t\tbreak;\n +\tcase GIT_DIR_EXPLICIT:\n +\t\tprefix = setup_explicit_git_dir(gitdir.buf, &cwd, nongit_ok);\n +\t\tbreak;\n +\tcase GIT_DIR_DISCOVERED:\n +\t\tif (dir.len < cwd.len && chdir(dir.buf))\n +\t\t\tdie(_(\"Cannot change to '%s'\"), dir.buf);\n +\t\tprefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len,\n +\t\t\t\t\t\t  nongit_ok);\n +\t\tbreak;\n +\tcase GIT_DIR_BARE:\n +\t\tif (dir.len < cwd.len && chdir(dir.buf))\n +\t\t\tdie(_(\"Cannot change to '%s'\"), dir.buf);\n +\t\tprefix = setup_bare_git_dir(&cwd, dir.len, nongit_ok);\n +\t\tbreak;\n +\tcase GIT_DIR_HIT_CEILING:\n +\t\tprefix = setup_nongit(cwd.buf, nongit_ok);\n +\t\tbreak;\n +\tcase GIT_DIR_HIT_MOUNT_POINT:\n +\t\tif (nongit_ok) {\n +\t\t\t*nongit_ok = 1;\n +\t\t\treturn NULL;\n +\t\t}\n +\t\tdie(_(\"Not a git repository (or any parent up to mount point %s)\\n\"\n +\t\t      \"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\"),\n +\t\t    dir.buf);\n +\tdefault:\n +\t\tdie(\"BUG: unhandled discover_git_directory() result\");\n +\t}\n +\n  \tif (prefix)\n  \t\tsetenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);\n  \telse\n diff --git a/t/helper/test-config.c b/t/helper/test-config.c\n index 51050695876..8e3ed6a76cb 100644\n --- a/t/helper/test-config.c\n +++ b/t/helper/test-config.c\n @@ -84,7 +84,7 @@ int cmd_main(int argc, const char **argv)\n  \tstruct config_set cs;\n  \n  \tif (argc == 3 && !strcmp(argv[1], \"read_early_config\")) {\n -\t\tread_early_config(early_config_cb, (void *)argv[2], 1);\n +\t\tread_early_config(early_config_cb, (void *)argv[2]);\n  \t\treturn 0;\n  \t}\n  \n diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh\n index c8dc665f2fd..bf89340988b 100755\n --- a/t/t7006-pager.sh\n +++ b/t/t7006-pager.sh\n @@ -360,27 +360,37 @@ test_pager_choices                       'git aliasedlog'\n  test_default_pager        expect_success 'git -p aliasedlog'\n  test_PAGER_overrides      expect_success 'git -p aliasedlog'\n  test_core_pager_overrides expect_success 'git -p aliasedlog'\n -test_core_pager_subdir    expect_failure 'git -p aliasedlog'\n +test_core_pager_subdir    expect_success 'git -p aliasedlog'\n  test_GIT_PAGER_overrides  expect_success 'git -p aliasedlog'\n  \n  test_default_pager        expect_success 'git -p true'\n  test_PAGER_overrides      expect_success 'git -p true'\n  test_core_pager_overrides expect_success 'git -p true'\n -test_core_pager_subdir    expect_failure 'git -p true'\n +test_core_pager_subdir    expect_success 'git -p true'\n  test_GIT_PAGER_overrides  expect_success 'git -p true'\n  \n  test_default_pager        expect_success test_must_fail 'git -p request-pull'\n  test_PAGER_overrides      expect_success test_must_fail 'git -p request-pull'\n  test_core_pager_overrides expect_success test_must_fail 'git -p request-pull'\n -test_core_pager_subdir    expect_failure test_must_fail 'git -p request-pull'\n +test_core_pager_subdir    expect_success test_must_fail 'git -p request-pull'\n  test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p request-pull'\n  \n  test_default_pager        expect_success test_must_fail 'git -p'\n  test_PAGER_overrides      expect_success test_must_fail 'git -p'\n  test_local_config_ignored expect_failure test_must_fail 'git -p'\n -test_no_local_config_subdir expect_success test_must_fail 'git -p'\n  test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p'\n  \n +test_expect_success TTY 'core.pager in repo config works and retains cwd' '\n +\tsane_unset GIT_PAGER &&\n +\ttest_config core.pager \"cat >cwd-retained\" &&\n +\t(\n +\t\tcd sub &&\n +\t\trm -f cwd-retained &&\n +\t\ttest_terminal git -p rev-parse HEAD &&\n +\t\ttest -e cwd-retained\n +\t)\n +'\n +\n  test_doesnt_paginate      expect_failure test_must_fail 'git -p nonsense'\n  \n  test_pager_choices                       'git shortlog'\n\n-- \n2.12.0.windows.1.3.g8a117c48243\n\n"},{"id":"313121","messageId":"db619060d18df21f3259653a52cd79f704b34566.1488506615.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488506615.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 7/9] read_early_config(): avoid .git/config hack when unneeded","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T02:04:28Z","receivedAt":"2017-03-03T02:04:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"So far, we only look whether the startup_info claims to have seen a\ngit_dir.\n\nHowever, do_git_config_sequence() (and consequently the\ngit_config_with_options() call used by read_early_config() asks the\nhave_git_dir() function whether we have a .git/ directory, which in turn\nalso looks at git_dir and at the environment variable GIT_DIR. And when\nthis is the case, the repository config is handled already, so we do not\nhave to do that again explicitly.\n\nLet's just use the same function, have_git_dir(), to determine whether we\nhave to handle .git/config explicitly.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n config.c | 3 +--\n 1 file changed, 1 insertion(+), 2 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 980fcc6ff2e..3ee5e5a15d6 100644\n--- a/config.c\n+++ b/config.c\n@@ -1434,8 +1434,7 @@ void read_early_config(config_fn_t cb, void *data)\n \t * valid repository), and would rarely make things worse (i.e., you do\n \t * not generally have a .git/config file sitting around).\n \t */\n-\tif (!startup_info->creating_repository &&\n-\t    !startup_info->have_repository) {\n+\tif (!startup_info->creating_repository && !have_git_dir()) {\n \t\tstruct git_config_source repo_config;\n \n \t\tmemset(&repo_config, 0, sizeof(repo_config));\n-- \n2.12.0.windows.1.3.g8a117c48243\n\n\n"},{"id":"313122","messageId":"f27a753830b8fb61a5276ce1d8eeba04ae4dbbfd.1488506615.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488506615.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 9/9] Test read_early_config()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T02:04:36Z","receivedAt":"2017-03-03T02:07:14Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"So far, we had no explicit tests of that function.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/helper/test-config.c  | 15 +++++++++++++++\n t/t1309-early-config.sh | 50 +++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 65 insertions(+)\n create mode 100755 t/t1309-early-config.sh\n\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 83a4f2ab869..8e3ed6a76cb 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -66,6 +66,16 @@ static int iterate_cb(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n+static int early_config_cb(const char *var, const char *value, void *vdata)\n+{\n+\tconst char *key = vdata;\n+\n+\tif (!strcmp(key, var))\n+\t\tprintf(\"%s\\n\", value);\n+\n+\treturn 0;\n+}\n+\n int cmd_main(int argc, const char **argv)\n {\n \tint i, val;\n@@ -73,6 +83,11 @@ int cmd_main(int argc, const char **argv)\n \tconst struct string_list *strptr;\n \tstruct config_set cs;\n \n+\tif (argc == 3 && !strcmp(argv[1], \"read_early_config\")) {\n+\t\tread_early_config(early_config_cb, (void *)argv[2]);\n+\t\treturn 0;\n+\t}\n+\n \tsetup_git_directory();\n \n \tgit_configset_init(&cs);\ndiff --git a/t/t1309-early-config.sh b/t/t1309-early-config.sh\nnew file mode 100755\nindex 00000000000..0c55dee514c\n--- /dev/null\n+++ b/t/t1309-early-config.sh\n@@ -0,0 +1,50 @@\n+#!/bin/sh\n+\n+test_description='Test read_early_config()'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'read early config' '\n+\ttest_config early.config correct &&\n+\ttest-config read_early_config early.config >output &&\n+\ttest correct = \"$(cat output)\"\n+'\n+\n+test_expect_success 'in a sub-directory' '\n+\ttest_config early.config sub &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest sub = \"$(cat output)\"\n+'\n+\n+test_expect_success 'ceiling' '\n+\ttest_config early.config ceiling &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tGIT_CEILING_DIRECTORIES=\"$PWD\" &&\n+\t\texport GIT_CEILING_DIRECTORIES &&\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest -z \"$(cat output)\"\n+'\n+\n+test_expect_success 'ceiling #2' '\n+\tmkdir -p xdg/git &&\n+\tgit config -f xdg/git/config early.config xdg &&\n+\ttest_config early.config ceiling &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tXDG_CONFIG_HOME=\"$PWD\"/xdg &&\n+\t\tGIT_CEILING_DIRECTORIES=\"$PWD\" &&\n+\t\texport GIT_CEILING_DIRECTORIES XDG_CONFIG_HOME &&\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest xdg = \"$(cat output)\"\n+'\n+\n+test_done\n-- \n2.12.0.windows.1.3.g8a117c48243\n"},{"id":"313123","messageId":"921faef822901715fa877d6969255ce00d80b925.1488506615.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488506615.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 8/9] read_early_config(): really discover .git/","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T02:04:32Z","receivedAt":"2017-03-03T02:07:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Earlier, we punted and simply assumed that we are in the top-level\ndirectory of the project, and that there is no .git file but a .git/\ndirectory so that we can read directly from .git/config.\n\nHowever, that is not necessarily true. We may be in a subdirectory. Or\n.git may be a gitfile. Or the environment variable GIT_DIR may be set.\n\nTo remedy this situation, we just refactored the way\nsetup_git_directory() discovers the .git/ directory, to make it\nreusable, and more importantly, to leave all global variables and the\ncurrent working directory alone.\n\nLet's discover the .git/ directory correctly in read_early_config() by\nusing that new function.\n\nThis fixes 4 known breakages in t7006.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n config.c         | 9 +++++++--\n t/t7006-pager.sh | 8 ++++----\n 2 files changed, 11 insertions(+), 6 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 3ee5e5a15d6..bcda397d42e 100644\n--- a/config.c\n+++ b/config.c\n@@ -1414,6 +1414,8 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \n void read_early_config(config_fn_t cb, void *data)\n {\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n \tgit_config_with_options(cb, data, NULL, 1);\n \n \t/*\n@@ -1434,13 +1436,16 @@ void read_early_config(config_fn_t cb, void *data)\n \t * valid repository), and would rarely make things worse (i.e., you do\n \t * not generally have a .git/config file sitting around).\n \t */\n-\tif (!startup_info->creating_repository && !have_git_dir()) {\n+\tif (!startup_info->creating_repository && !have_git_dir() &&\n+\t    discover_git_directory(&buf)) {\n \t\tstruct git_config_source repo_config;\n \n \t\tmemset(&repo_config, 0, sizeof(repo_config));\n-\t\trepo_config.file = \".git/config\";\n+\t\tstrbuf_addstr(&buf, \"/config\");\n+\t\trepo_config.file = buf.buf;\n \t\tgit_config_with_options(cb, data, &repo_config, 1);\n \t}\n+\tstrbuf_release(&buf);\n }\n \n static void git_config_check_init(void);\ndiff --git a/t/t7006-pager.sh b/t/t7006-pager.sh\nindex 427bfc605ad..bf89340988b 100755\n--- a/t/t7006-pager.sh\n+++ b/t/t7006-pager.sh\n@@ -360,19 +360,19 @@ test_pager_choices                       'git aliasedlog'\n test_default_pager        expect_success 'git -p aliasedlog'\n test_PAGER_overrides      expect_success 'git -p aliasedlog'\n test_core_pager_overrides expect_success 'git -p aliasedlog'\n-test_core_pager_subdir    expect_failure 'git -p aliasedlog'\n+test_core_pager_subdir    expect_success 'git -p aliasedlog'\n test_GIT_PAGER_overrides  expect_success 'git -p aliasedlog'\n \n test_default_pager        expect_success 'git -p true'\n test_PAGER_overrides      expect_success 'git -p true'\n test_core_pager_overrides expect_success 'git -p true'\n-test_core_pager_subdir    expect_failure 'git -p true'\n+test_core_pager_subdir    expect_success 'git -p true'\n test_GIT_PAGER_overrides  expect_success 'git -p true'\n \n test_default_pager        expect_success test_must_fail 'git -p request-pull'\n test_PAGER_overrides      expect_success test_must_fail 'git -p request-pull'\n test_core_pager_overrides expect_success test_must_fail 'git -p request-pull'\n-test_core_pager_subdir    expect_failure test_must_fail 'git -p request-pull'\n+test_core_pager_subdir    expect_success test_must_fail 'git -p request-pull'\n test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p request-pull'\n \n test_default_pager        expect_success test_must_fail 'git -p'\n@@ -380,7 +380,7 @@ test_PAGER_overrides      expect_success test_must_fail 'git -p'\n test_local_config_ignored expect_failure test_must_fail 'git -p'\n test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p'\n \n-test_expect_failure TTY 'core.pager in repo config works and retains cwd' '\n+test_expect_success TTY 'core.pager in repo config works and retains cwd' '\n \tsane_unset GIT_PAGER &&\n \ttest_config core.pager \"cat >cwd-retained\" &&\n \t(\n-- \n2.12.0.windows.1.3.g8a117c48243\n\n\n"},{"id":"313127","messageId":"20170303033623.lfi7jhul6psbdde6@sigill.intra.peff.net","threadId":"44653","inReplyTo":"8f79df34e63a37c052437dd022269b4a6c495108.1488506615.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 1/9] t7006: replace dubious test","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-03T03:36:23Z","receivedAt":"2017-03-03T03:45:38Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Mar 03, 2017 at 03:04:02AM +0100, Johannes Schindelin wrote:\n\n> The intention of that test case, however, was to verify that the\n> setup_git_directory() function has not run, because it changes global\n> state such as the current working directory.\n> \n> To keep that spirit, but fix the incorrect assumption, this patch\n> replaces that test case by a new one that verifies that the pager is\n> run in the subdirectory, i.e. that the current working directory has\n> not been changed at the time the pager is configured and launched, even\n> if the `rev-parse` command requires a .git/ directory and *will* change\n> the working directory.\n\nThis is a great solution to the question I had in v1 (\"how do we know\nwhen setup_git_directory() is run?\"). It not only checks the\ninternal-code case that we care about, but it also shows how real users\nwould get bit if we do the wrong thing.\n\n> +test_expect_failure TTY 'core.pager in repo config works and retains cwd' '\n> +\tsane_unset GIT_PAGER &&\n> +\ttest_config core.pager \"cat >cwd-retained\" &&\n> +\t(\n> +\t\tcd sub &&\n> +\t\trm -f cwd-retained &&\n> +\t\ttest_terminal git -p rev-parse HEAD &&\n> +\t\ttest -e cwd-retained\n> +\t)\n> +'\n\nDoes this actually need TTY and test_terminal? You replace the pager\nwith something that doesn't care about the terminal, and \"-p\" should\nunconditionally turn it on.\n\n(Also a minor nit: we usually prefer test_path_is_file to \"test -e\"\nthese days).\n\n-Peff\n"},{"id":"313128","messageId":"20170303042422.s3dnbgbjszhygwqz@sigill.intra.peff.net","threadId":"44653","inReplyTo":"49f66f2b723af38a58c17bc8ae413bcee69e2a2f.1488506615.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 3/9] setup_git_directory(): avoid changing global state during discovery","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-03T04:24:22Z","receivedAt":"2017-03-03T04:25:12Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Mar 03, 2017 at 03:04:11AM +0100, Johannes Schindelin wrote:\n\n> For historical reasons, Git searches for the .git/ directory (or the\n> .git file) by changing the working directory successively to the parent\n> directory of the current directory, until either anything was found or\n> until a ceiling or a mount point is hit.\n\nThis is starting to get into the meat of changes we've been putting off\nwriting for ages. I'll read with my fingers crossed. :)\n\n> Further global state may be changed, depending on the actual type of\n> discovery, e.g. the global variable `repository_format_precious_objects`\n> is set in the `check_repository_format_gently()` function (which is a\n> bit surprising, given the function name).\n\nIt's gentle in the sense that if it does not find a valid repo, it\ntouches no state. I do suspect the functions you want are the ones it\nbuilds on: read_repository_format() and verify_repository_format(),\nwhich I added not too long ago for the exact purpose of checking repo\nconfig without touching anything global.\n\n> We do have a use case where we would like to find the .git/ directory\n> without having any global state touched, though: when we read the early\n> config e.g. for the pager or for alias expansion.\n> \n> Let's just rename the function `setup_git_directory_gently_1()` to\n> `discover_git_directory()` and move all code that changes any global\n> state back into `setup_git_directory_gently()`.\n\nGiven the earlier paragraph, it sounds like you want to move the\nglobal-state-changing parts out of check_repository_format_gently(). But\nthat wouldn't be right; it's triggered separate from the discovery\nprocess by things like enter_repo().\n\nHowever, I don't see that happening in the patch, which is good. I just\nwonder if the earlier paragraph should really be complaining about how\nsetup_git_directory() (and its callees) touches a lot of global state,\nnot check_repository_format_gently(), whose use is but one of multiple\nglobal-state modifications.\n\n> Note: the new loop is a *little* tricky, as we have to handle the root\n> directory specially: we cannot simply strip away the last component\n> including the slash, as the root directory only has that slash. To remedy\n> that, we introduce the `min_offset` variable that holds the minimal length\n> of an absolute path, and using that to special-case the root directory,\n> including an early exit before trying to find the parent of the root\n> directory.\n\nI wondered how t1509 fared with this, as it is the only test of\nrepositories at the root directory (and it is not run by default because\nit involves a bunch of flaky and expensive chroot setup). Unfortunately\nit seems to fail with your patch:\n\n  expecting success: \n  \t\ttest_cmp_val \"foo/\" \"$(git rev-parse --show-prefix)\"\n  \t\n  --- expected\n  +++ result\n  @@ -1 +1 @@\n  -foo/\n  +oo/\n  not ok 66 - auto gitdir, foo: prefix\n\nCould the problem be this:\n\n> +\tint ceil_offset = -1, min_offset = has_dos_drive_prefix(dir->buf) ? 3 : 1;\n> [...]\n> -\tif (ceil_offset < 0 && has_dos_drive_prefix(cwd.buf))\n> -\t\tceil_offset = 1;\n> +\tif (ceil_offset < 0)\n> +\t\tceil_offset = min_offset - 2;\n\nIt works the same as before in the dos-drive case, but we'd get\nceil_offset = -1 otherwise.  Which seems weird, but maybe I don't\nunderstand how ceil_offset is supposed to work.\n\nInterestingly, I don't think this is the bug, though. We still correctly\nfind /.git as a repository. The problem seems to happen later, in\nsetup_discovered_git_dir(), which does this:\n\n  /* Make \"offset\" point to past the '/', and add a '/' at the end */\n  offset++;\n  strbuf_addch(cwd, '/');\n  return cwd->buf + offset;\n\nHere, \"offset\" is the length of the working tree path. The root\ndirectory case differs from normal in that \"offset\" already accounts for\nthe trailing slash.\n\nSo I think the bug comes from:\n\n> -\t\t\tret = setup_discovered_git_dir(gitdirenv,\n> -\t\t\t\t\t\t       &cwd, offset,\n> -\t\t\t\t\t\t       nongit_ok);\n> [...]\n> +\t\tprefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len,\n> +\t\t\t\t\t\t  nongit_ok);\n\nThe original knew that \"offset\" took into account the off-by-one for the\nroot, but that's lost when we use dir.len. I haven't studied it enough\nto know the best solution, but I suspect you will.\n\nOther than this bug, I very much like the direction that this patch is\ntaking us.\n\n-Peff\n"},{"id":"313129","messageId":"20170303033714.5h7qrq57hdqwoi3y@sigill.intra.peff.net","threadId":"44653","inReplyTo":"d1342d28fd402615f1f94d9190559070ed04b0d7.1488506615.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 2/9] setup_git_directory(): use is_dir_sep() helper","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-03T03:37:14Z","receivedAt":"2017-03-03T04:38:01Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Mar 03, 2017 at 03:04:07AM +0100, Johannes Schindelin wrote:\n\n> It is okay in practice to test for forward slashes in the output of\n> getcwd(), because we go out of our way to convert backslashes to forward\n> slashes in getcwd()'s output on Windows.\n> \n> Still, the correct way to test for a dir separator is by using the\n> helper function we introduced for that very purpose. It also serves as a\n> good documentation what the code tries to do (not \"how\").\n\nMakes sense, but...\n\n> @@ -910,7 +910,8 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n>  \t\t\treturn setup_bare_git_dir(&cwd, offset, nongit_ok);\n>  \n>  \t\toffset_parent = offset;\n> -\t\twhile (--offset_parent > ceil_offset && cwd.buf[offset_parent] != '/');\n> +\t\twhile (--offset_parent > ceil_offset &&\n> +\t\t       !is_dir_sep(dir->buf[offset_parent]));\n\nWhat is \"dir\"? I'm guessing this patch got reordered and it should stay\nas cwd.buf?\n\n-Peff\n"},{"id":"313130","messageId":"20170303050745.wv4zsb24ykr34xr7@sigill.intra.peff.net","threadId":"44653","inReplyTo":"f27a753830b8fb61a5276ce1d8eeba04ae4dbbfd.1488506615.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 9/9] Test read_early_config()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-03T05:07:45Z","receivedAt":"2017-03-03T05:14:51Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Mar 03, 2017 at 03:04:36AM +0100, Johannes Schindelin wrote:\n\n> So far, we had no explicit tests of that function.\n\nMakes sense. The pager tests fixed in an earlier commit were effectively\nchecking those, but I don't mind making it explicit.\n\n-Peff\n"},{"id":"313131","messageId":"20170303051451.elzz2kzumft2iugg@sigill.intra.peff.net","threadId":"44653","inReplyTo":"cover.1488506615.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 0/9] Fix the early config","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-03T05:14:52Z","receivedAt":"2017-03-03T05:15:09Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Mar 03, 2017 at 03:03:56AM +0100, Johannes Schindelin wrote:\n\n> These patches are an attempt to make Git's startup sequence a bit less\n> surprising.\n> \n> The idea here is to discover the .git/ directory gently (i.e. without\n> changing the current working directory, or global variables), and to use\n> it to read the .git/config file early, before we actually called\n> setup_git_directory() (if we ever do that).\n\nThanks for working on this. I think it's a huge improvement over the\nstatus quo, and over the earlier attempt. I don't see anything hugely\nwrong with this series, though I did note one bug, along with some minor\nrefinements.\n\n> My dirty little secret is that I actually need this for something else\n> entirely. I need to patch an internal version of Git to gather\n> statistics, and to that end I need to read the config before and after\n> running every Git command. Hence the need for a gentle, and correct\n> early config.\n\nWe do something similar at GitHub, but it falls into two categories:\n\n  - stat-gathering that's on all the time, so doesn't need to look at\n    config (I'm not sure in your case if you want to trigger the\n    gathering with config, or if config is just one of the things you\n    are gathering).\n\n  - logging that is turned on selectively for some repos, but which\n    doesn't have to be looked up until we know we are in a repo\n\nI looked into making something upstream-able, and my approach was to\nallow GIT_TRACE_* variables to be specified in the config. But of course\nthat ran into the early-config problem (and I really wanted repo-level\nconfig there, because I'd like to be able to turn on tracing for just a\nsingle problematic repo).\n\nSo not really something you need to work on, but maybe food for thought\nas you work on your internal project.\n\n-Peff\n"},{"id":"313133","messageId":"20170303045148.uzltelasbodvgys4@sigill.intra.peff.net","threadId":"44653","inReplyTo":"db619060d18df21f3259653a52cd79f704b34566.1488506615.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 7/9] read_early_config(): avoid .git/config hack when unneeded","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-03T04:51:48Z","receivedAt":"2017-03-03T05:51:05Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Mar 03, 2017 at 03:04:28AM +0100, Johannes Schindelin wrote:\n\n> So far, we only look whether the startup_info claims to have seen a\n> git_dir.\n> \n> However, do_git_config_sequence() (and consequently the\n> git_config_with_options() call used by read_early_config() asks the\n> have_git_dir() function whether we have a .git/ directory, which in turn\n> also looks at git_dir and at the environment variable GIT_DIR. And when\n> this is the case, the repository config is handled already, so we do not\n> have to do that again explicitly.\n> \n> Let's just use the same function, have_git_dir(), to determine whether we\n> have to handle .git/config explicitly.\n\nGood, this makes sense.\n\n-Peff\n"},{"id":"313134","messageId":"20170303045101.om7qtbcmtuw6kzvr@sigill.intra.peff.net","threadId":"44653","inReplyTo":"33e692918c8d41507de5ec2b2e2d55982678408e.1488506615.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 6/9] read_early_config(): special-case builtins that create a repository","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-03T04:51:01Z","receivedAt":"2017-03-03T05:51:19Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Mar 03, 2017 at 03:04:24AM +0100, Johannes Schindelin wrote:\n\n> When the command we are about to execute wants to create a repository\n> (i.e. the command is `init` or `clone`), we *must not* look for a\n> repository config.\n\nHmm. I'm not sure if this one is worth the hackery here.\n\nYes, it would be wrong for init or clone to read something like\ncore.sharedrepository from a repo it happens to be in. But I wonder if\nit would be cleaner to consider calls to read_early_config their own\n\"pre-command\" stage that may respect global config, or config in a repo\ndirectory you happen to be sitting in.\n\nBecause I think for aliases, we're going to end up having to do that\nanyway (you won't know that your alias is \"clone\" until you've resolved\nit!). And I think the pager fits into this \"pre-command\" concept, too\n(we already have \"-p\" as a pre-command option on the command-line).\n\nI dunno. It probably doesn't matter _too much_ either way. But it's one\nless hack to maintain going forward, and it also makes your \"git-init\nrespects the pager\" into the normal, consistent thing.\n\n-Peff\n"},{"id":"313139","messageId":"20170303050630.mw6asla65prku3vq@sigill.intra.peff.net","threadId":"44653","inReplyTo":"921faef822901715fa877d6969255ce00d80b925.1488506615.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 8/9] read_early_config(): really discover .git/","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-03T05:06:30Z","receivedAt":"2017-03-03T07:11:11Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Mar 03, 2017 at 03:04:32AM +0100, Johannes Schindelin wrote:\n\n> Earlier, we punted and simply assumed that we are in the top-level\n> directory of the project, and that there is no .git file but a .git/\n> directory so that we can read directly from .git/config.\n> \n> However, that is not necessarily true. We may be in a subdirectory. Or\n> .git may be a gitfile. Or the environment variable GIT_DIR may be set.\n> \n> To remedy this situation, we just refactored the way\n> setup_git_directory() discovers the .git/ directory, to make it\n> reusable, and more importantly, to leave all global variables and the\n> current working directory alone.\n> \n> Let's discover the .git/ directory correctly in read_early_config() by\n> using that new function.\n> \n> This fixes 4 known breakages in t7006.\n\nYay, this is much nicer.\n\nI think the \"dirty hack...\" comment in read_early_config() can be\ncondensed (or go away) now.\n\nI think we _could_ do away with read_early_config() entirely, and just\nhave the regular config code do this lookup when we're not already in a\nrepo. But then we'd really need to depend on the \"creating_repository\"\nflag being managed correctly.\n\nI think I prefer the idea that a few \"early\" spots like pager and alias\nconfig need to use this special function to access the config. That's\nless likely to cause surprises when some config option is picked up\nbefore we have run setup_git_directory().\n\nThere is one surprising case that I think we need to deal with even now,\nthough. If I do:\n\n  git init repo\n  git -C repo config pager.upload-pack 'echo whoops'\n  git upload-pack repo\n  cd repo && git upload-pack .\n\nthe first one is OK, but the second reads and executes the pager config\nfrom the repo, even though we usually consider upload-pack to be OK to\nrun in an untrusted repo. This _isn't_ a new thing in your patch, but\njust something I noticed while we are here.\n\nAnd maybe it is a non-issue. The early-config bits all happen via the\ngit wrapper, and normally we use the direct dashed \"git-upload-pack\" to\naccess the other repositories. Certainly I have been known to use \"git\n-c ... upload-pack\" while debugging various things.\n\nSo I dunno. You really have to jump through some hoops for it to matter,\nbut I just wonder if the git wrapper should be special-casing\nupload-pack, too.\n\n-Peff\n"},{"id":"313143","messageId":"20170303044518.rdtyvgs7kqe7fkpf@sigill.intra.peff.net","threadId":"44653","inReplyTo":"0ca4cce042cc0b0f13f87363b70a3689057ae9b0.1488506615.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 4/9] Export the discover_git_directory() function","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-03T04:45:19Z","receivedAt":"2017-03-03T09:13:29Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Mar 03, 2017 at 03:04:15AM +0100, Johannes Schindelin wrote:\n\n> The function we introduced earlier needs to return both the path to the\n> .git/ directory as well as the \"cd-up\" path to allow\n> setup_git_directory() to retain its previous behavior as if it changed\n> the current working directory on its quest for the .git/ directory.\n> \n> Let's rename it and export a function with an easier signature that\n> *just* discovers the .git/ directory.\n> \n> We will use it in a subsequent patch to support early config reading\n> better.\n> \n\nSeems like an obviously good next step.\n\n> diff --git a/cache.h b/cache.h\n> index 80b6372cf76..a104b76c02e 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -518,6 +518,7 @@ extern void set_git_work_tree(const char *tree);\n>  #define ALTERNATE_DB_ENVIRONMENT \"GIT_ALTERNATE_OBJECT_DIRECTORIES\"\n>  \n>  extern void setup_work_tree(void);\n> +extern const char *discover_git_directory(struct strbuf *gitdir);\n\nPerhaps it's worth adding a short docstring describing the function. I\nknow that would make it unlike all of its neighbors, but it is not\nimmediately obvious to me what the return value is (or whether gitdir is\nan input or output parameter).\n\n> +const char *discover_git_directory(struct strbuf *gitdir)\n> +{\n> +\tstruct strbuf dir = STRBUF_INIT;\n> +\tint len;\n\nNit: please use size_t for storing strbuf lengths.\n\n> +\tif (strbuf_getcwd(&dir))\n> +\t\treturn NULL;\n> +\n> +\tlen = dir.len;\n> +\tif (discover_git_directory_1(&dir, gitdir) < 0) {\n> +\t\tstrbuf_release(&dir);\n> +\t\treturn NULL;\n> +\t}\n> +\n> +\tif (dir.len < len && !is_absolute_path(gitdir->buf)) {\n> +\t\tstrbuf_addch(&dir, '/');\n> +\t\tstrbuf_insert(gitdir, 0, dir.buf, dir.len);\n> +\t}\n> +\tstrbuf_release(&dir);\n\nI was confused by two things here.\n\nOne is that because I was wondering whether \"gitdir\" was supposed to be\npassed empty or not, it wasn't clear that this insert is doing the right\nthing.  If there _is_ something in it, then discover_git_directory_1()\nwould just append to it, which makes sense. But then this insert blindly\nsticks the absolute-path bit at the front of the string, leaving\nwhatever was originally there spliced into the middle of the path.\nDoing:\n\n  size_t base = gitdir->len;\n  ...\n  strbuf_insert(gitdir, base, dir.buf, dir.len);\n\nwould solve that. It's probably not that likely for somebody to do:\n\n  strbuf_addstr(&buf, \"my git dir is \");\n  discover_git_directory(&buf);\n\nbut since it's not much effort, it might be worth making it work.\n\nThe second is that I don't quite understand why we only make the result\nabsolute when we walked upwards. In git.git, the result of the function\nin various directories is:\n\n  - \".git\", when we're at the root of the worktree\n  - \"/home/peff/git/.git, when we're in \"t/\"\n  - \".\" when we're already in \".git\"\n  - \"/home/peff/git/.git/.\" when we're in \".git/objects\"\n\nI'm not sure if some of those cases are not behaving as intended, or\nthere's some reason for the differences that I don't quite understand.\n\n-Peff\n"},{"id":"313145","messageId":"20170303044630.tfogzhzbokuoiwh6@sigill.intra.peff.net","threadId":"44653","inReplyTo":"64135c6df520c1ca6a6e667855ac03c14d2dd153.1488506615.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 5/9] Make read_early_config() reusable","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-03T04:46:30Z","receivedAt":"2017-03-03T09:18:02Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Mar 03, 2017 at 03:04:20AM +0100, Johannes Schindelin wrote:\n\n> The pager configuration needs to be read early, possibly before\n> discovering any .git/ directory.\n> \n> Let's not hide this function in pager.c, but make it available to other\n> callers.\n> [...]\n> +\t * Note that this is a really dirty hack that does the wrong thing in\n> +\t * many cases. The crux of the problem is that we cannot run\n\nMakes sense. I'll assume the words \"dirty hack\" disappear from this\nnow-public function as you fix it up in a future patch. :)\n\n-Peff\n"},{"id":"313158","messageId":"alpine.DEB.2.20.1703031212280.3767@virtualbox","threadId":"44653","inReplyTo":"20170303033714.5h7qrq57hdqwoi3y@sigill.intra.peff.net","subject":"Re: [PATCH v2 2/9] setup_git_directory(): use is_dir_sep() helper","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T11:16:31Z","receivedAt":"2017-03-03T11:25:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Thu, 2 Mar 2017, Jeff King wrote:\n\n> On Fri, Mar 03, 2017 at 03:04:07AM +0100, Johannes Schindelin wrote:\n> \n> > It is okay in practice to test for forward slashes in the output of\n> > getcwd(), because we go out of our way to convert backslashes to\n> > forward slashes in getcwd()'s output on Windows.\n> > \n> > Still, the correct way to test for a dir separator is by using the\n> > helper function we introduced for that very purpose. It also serves as\n> > a good documentation what the code tries to do (not \"how\").\n> \n> Makes sense, but...\n> \n> > @@ -910,7 +910,8 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n> >  \t\t\treturn setup_bare_git_dir(&cwd, offset, nongit_ok);\n> >  \n> >  \t\toffset_parent = offset;\n> > -\t\twhile (--offset_parent > ceil_offset && cwd.buf[offset_parent] != '/');\n> > +\t\twhile (--offset_parent > ceil_offset &&\n> > +\t\t       !is_dir_sep(dir->buf[offset_parent]));\n> \n> What is \"dir\"? I'm guessing this patch got reordered and it should stay\n> as cwd.buf?\n\nOh drats. Usually I do a final `git rebase -x \"make test\" upstream/master`\nrun before submitting, but I was really, really tired by the end of that\nstretch.\n\nThanks for being thorough (and I fixed it, of course),\nDscho\n"},{"id":"313163","messageId":"20170303112610.x6mdh42nd4hmxz2h@sigill.intra.peff.net","threadId":"44653","inReplyTo":"alpine.DEB.2.20.1703031212280.3767@virtualbox","subject":"Re: [PATCH v2 2/9] setup_git_directory(): use is_dir_sep() helper","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-03T11:26:10Z","receivedAt":"2017-03-03T11:55:14Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Mar 03, 2017 at 12:16:31PM +0100, Johannes Schindelin wrote:\n\n> > What is \"dir\"? I'm guessing this patch got reordered and it should stay\n> > as cwd.buf?\n> \n> Oh drats. Usually I do a final `git rebase -x \"make test\" upstream/master`\n> run before submitting, but I was really, really tired by the end of that\n> stretch.\n\nI usually do the same, and have done the \"too tired\" thing, too, only to\nhave it bite me. That's why I so readily recognized the problem. :)\n\nI've recently switched to using Michael's \"git test\" program[1], which\ncaches the test results for each tree in a git-note. That makes the\nfinal \"rebase -x\" a lot less painful if you've left the early commits\nalone.\n\nThe python dependency might be a blocker for you, but I suspect the\ncaching parts would be easy to hack together with shell.\n\n-Peff\n\n[1] https://github.com/mhagger/git-test\n"},{"id":"313167","messageId":"alpine.DEB.2.20.1703031208430.3767@virtualbox","threadId":"44653","inReplyTo":"20170303033623.lfi7jhul6psbdde6@sigill.intra.peff.net","subject":"Re: [PATCH v2 1/9] t7006: replace dubious test","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T11:10:51Z","receivedAt":"2017-03-03T12:08:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Thu, 2 Mar 2017, Jeff King wrote:\n\n> On Fri, Mar 03, 2017 at 03:04:02AM +0100, Johannes Schindelin wrote:\n> \n> > The intention of that test case, however, was to verify that the\n> > setup_git_directory() function has not run, because it changes global\n> > state such as the current working directory.\n> > \n> > To keep that spirit, but fix the incorrect assumption, this patch\n> > replaces that test case by a new one that verifies that the pager is\n> > run in the subdirectory, i.e. that the current working directory has\n> > not been changed at the time the pager is configured and launched,\n> > even if the `rev-parse` command requires a .git/ directory and *will*\n> > change the working directory.\n> \n> This is a great solution to the question I had in v1 (\"how do we know\n> when setup_git_directory() is run?\"). It not only checks the\n> internal-code case that we care about, but it also shows how real users\n> would get bit if we do the wrong thing.\n\nThanks!\n\n> > +test_expect_failure TTY 'core.pager in repo config works and retains cwd' '\n> > +\tsane_unset GIT_PAGER &&\n> > +\ttest_config core.pager \"cat >cwd-retained\" &&\n> > +\t(\n> > +\t\tcd sub &&\n> > +\t\trm -f cwd-retained &&\n> > +\t\ttest_terminal git -p rev-parse HEAD &&\n> > +\t\ttest -e cwd-retained\n> > +\t)\n> > +'\n> \n> Does this actually need TTY and test_terminal?\n\nSadly, yes. If git.c sees a --paginate or -p, it sets use_pager to 1 which\nis later used as indicator that setup_pager() should be run. And the first\nline of that function reads:\n\n\tconst char *pager = git_pager(isatty(1));\n\ni.e. it does *not* turn on the pager unconditionally.\n\n> (Also a minor nit: we usually prefer test_path_is_file to \"test -e\"\n> these days).\n\nThanks, fixed.\n\nCiao,\nDscho\n"},{"id":"313174","messageId":"alpine.DEB.2.20.1703031220170.3767@virtualbox","threadId":"44653","inReplyTo":"20170303042422.s3dnbgbjszhygwqz@sigill.intra.peff.net","subject":"Re: [PATCH v2 3/9] setup_git_directory(): avoid changing global state during discovery","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T13:54:34Z","receivedAt":"2017-03-03T14:11:57Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Thu, 2 Mar 2017, Jeff King wrote:\n\n> On Fri, Mar 03, 2017 at 03:04:11AM +0100, Johannes Schindelin wrote:\n> \n> > For historical reasons, Git searches for the .git/ directory (or the\n> > .git file) by changing the working directory successively to the\n> > parent directory of the current directory, until either anything was\n> > found or until a ceiling or a mount point is hit.\n> \n> This is starting to get into the meat of changes we've been putting off\n> writing for ages. I'll read with my fingers crossed. :)\n\nHeh.\n\n> > Further global state may be changed, depending on the actual type of\n> > discovery, e.g. the global variable\n> > `repository_format_precious_objects` is set in the\n> > `check_repository_format_gently()` function (which is a bit\n> > surprising, given the function name).\n> \n> It's gentle in the sense that if it does not find a valid repo, it\n> touches no state. I do suspect the functions you want are the ones it\n> builds on: read_repository_format() and verify_repository_format(),\n> which I added not too long ago for the exact purpose of checking repo\n> config without touching anything global.\n\nOkay. But I think that my interpretation of the word \"gently\" is as valid\nas Git's source code's.\n\n> > We do have a use case where we would like to find the .git/ directory\n> > without having any global state touched, though: when we read the early\n> > config e.g. for the pager or for alias expansion.\n> > \n> > Let's just rename the function `setup_git_directory_gently_1()` to\n> > `discover_git_directory()` and move all code that changes any global\n> > state back into `setup_git_directory_gently()`.\n> \n> Given the earlier paragraph, it sounds like you want to move the\n> global-state-changing parts out of check_repository_format_gently(). But\n> that wouldn't be right; it's triggered separate from the discovery\n> process by things like enter_repo().\n\nOh, right. I really only meant to move the global-state-changing parts out\nof the discover_git_directory().\n\n> However, I don't see that happening in the patch, which is good. I just\n> wonder if the earlier paragraph should really be complaining about how\n> setup_git_directory() (and its callees) touches a lot of global state,\n> not check_repository_format_gently(), whose use is but one of multiple\n> global-state modifications.\n\nOkay, I'll try my best to rephrase the commit message.\n\nFor good measure, I will also keep the name setup_git_directory_gently_1()\nbecause it won't get exported directly (I made up my mind about wrapping\nthat function to allow for an easier interface that does *not* return the\n\"cdup\").\n\n> > Note: the new loop is a *little* tricky, as we have to handle the root\n> > directory specially: we cannot simply strip away the last component\n> > including the slash, as the root directory only has that slash. To\n> > remedy that, we introduce the `min_offset` variable that holds the\n> > minimal length of an absolute path, and using that to special-case the\n> > root directory, including an early exit before trying to find the\n> > parent of the root directory.\n> \n> I wondered how t1509 fared with this, as it is the only test of\n> repositories at the root directory (and it is not run by default because\n> it involves a bunch of flaky and expensive chroot setup).\n\nOh, thanks. I allowed myself to forget about that test script (and did a\nlot of testing by hand, but of course *during* the development of v2, not\nwhen I had finished...).\n\n> Unfortunately it seems to fail with your patch:\n> \n>   expecting success: \n>   \t\ttest_cmp_val \"foo/\" \"$(git rev-parse --show-prefix)\"\n>   \t\n>   --- expected\n>   +++ result\n>   @@ -1 +1 @@\n>   -foo/\n>   +oo/\n>   not ok 66 - auto gitdir, foo: prefix\n\nI can reproduce this failure here.\n\nSide note: it took a while until I realized that the prepare-chroot.sh\nscript has to be run *every time* I change *anything* in either Git's\nsource code or in the test script.\n\n> Could the problem be this:\n> \n> > +\tint ceil_offset = -1, min_offset = has_dos_drive_prefix(dir->buf) ? 3 : 1;\n> > [...]\n> > -\tif (ceil_offset < 0 && has_dos_drive_prefix(cwd.buf))\n> > -\t\tceil_offset = 1;\n> > +\tif (ceil_offset < 0)\n> > +\t\tceil_offset = min_offset - 2;\n\nYes. The previous code did not need cwd.buf[0..offset] to be a valid path,\nbut it needed the offset to point to the trailing slash, if any.\n\n> Interestingly, I don't think this is the bug, though. We still correctly\n> find /.git as a repository. The problem seems to happen later, in\n> setup_discovered_git_dir(), which does this:\n> \n>   /* Make \"offset\" point to past the '/', and add a '/' at the end */\n>   offset++;\n>   strbuf_addch(cwd, '/');\n>   return cwd->buf + offset;\n\nI fixed this by ensuring that we only increment the offset if it is not\nalready pointing at the end of the first offset (which handles Windows\npaths correctly, too).\n\n> Other than this bug, I very much like the direction that this patch is\n> taking us.\n\nAwesome. I was anxious to hear something like that.\n\nCiao,\nDscho\n"},{"id":"313175","messageId":"alpine.DEB.2.20.1703031511200.3767@virtualbox","threadId":"44653","inReplyTo":"20170303044630.tfogzhzbokuoiwh6@sigill.intra.peff.net","subject":"Re: [PATCH v2 5/9] Make read_early_config() reusable","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T14:11:38Z","receivedAt":"2017-03-03T14:12:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Thu, 2 Mar 2017, Jeff King wrote:\n\n> On Fri, Mar 03, 2017 at 03:04:20AM +0100, Johannes Schindelin wrote:\n> \n> > The pager configuration needs to be read early, possibly before\n> > discovering any .git/ directory.\n> > \n> > Let's not hide this function in pager.c, but make it available to other\n> > callers.\n> > [...]\n> > +\t * Note that this is a really dirty hack that does the wrong thing in\n> > +\t * many cases. The crux of the problem is that we cannot run\n> \n> Makes sense. I'll assume the words \"dirty hack\" disappear from this\n> now-public function as you fix it up in a future patch. :)\n\nOops. I did not even think about that.\n\nFixed,\nDscho\n"},{"id":"313178","messageId":"alpine.DEB.2.20.1703031511490.3767@virtualbox","threadId":"44653","inReplyTo":"20170303044518.rdtyvgs7kqe7fkpf@sigill.intra.peff.net","subject":"Re: [PATCH v2 4/9] Export the discover_git_directory() function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T14:49:24Z","receivedAt":"2017-03-03T15:10:04Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Thu, 2 Mar 2017, Jeff King wrote:\n\n> On Fri, Mar 03, 2017 at 03:04:15AM +0100, Johannes Schindelin wrote:\n> \n> > diff --git a/cache.h b/cache.h\n> > index 80b6372cf76..a104b76c02e 100644\n> > --- a/cache.h\n> > +++ b/cache.h\n> > @@ -518,6 +518,7 @@ extern void set_git_work_tree(const char *tree);\n> >  #define ALTERNATE_DB_ENVIRONMENT \"GIT_ALTERNATE_OBJECT_DIRECTORIES\"\n> >  \n> >  extern void setup_work_tree(void);\n> > +extern const char *discover_git_directory(struct strbuf *gitdir);\n> \n> Perhaps it's worth adding a short docstring describing the function.\n\nOkay.\n\n> > +const char *discover_git_directory(struct strbuf *gitdir)\n> > +{\n> > +\tstruct strbuf dir = STRBUF_INIT;\n> > +\tint len;\n> \n> Nit: please use size_t for storing strbuf lengths.\n\nOkay.\n\n> > +\tif (strbuf_getcwd(&dir))\n> > +\t\treturn NULL;\n> > +\n> > +\tlen = dir.len;\n> > +\tif (discover_git_directory_1(&dir, gitdir) < 0) {\n> > +\t\tstrbuf_release(&dir);\n> > +\t\treturn NULL;\n> > +\t}\n> > +\n> > +\tif (dir.len < len && !is_absolute_path(gitdir->buf)) {\n> > +\t\tstrbuf_addch(&dir, '/');\n> > +\t\tstrbuf_insert(gitdir, 0, dir.buf, dir.len);\n> > +\t}\n> > +\tstrbuf_release(&dir);\n> \n> I was confused by two things here.\n> \n> One is that because I was wondering whether \"gitdir\" was supposed to be\n> passed empty or not, it wasn't clear that this insert is doing the right\n> thing.  If there _is_ something in it, then discover_git_directory_1()\n> would just append to it, which makes sense. But then this insert blindly\n> sticks the absolute-path bit at the front of the string, leaving\n> whatever was originally there spliced into the middle of the path.\n> Doing:\n> \n>   size_t base = gitdir->len;\n>   ...\n>   strbuf_insert(gitdir, base, dir.buf, dir.len);\n> \n> would solve that.\n\nAnd of course the is_absolute_path() call also needs to offset `gitdir->buf\n+ base`.\n\n> It's probably not that likely for somebody to do:\n> \n>   strbuf_addstr(&buf, \"my git dir is \");\n>   discover_git_directory(&buf);\n> \n> but since it's not much effort, it might be worth making it work.\n\nPlus, I have no assert()s in place to ensure any expectation to the\ncontrary. So I fixed it as you suggested.\n\n> The second is that I don't quite understand why we only make the result\n> absolute when we walked upwards. In git.git, the result of the function\n> in various directories is:\n> \n>   - \".git\", when we're at the root of the worktree\n>   - \"/home/peff/git/.git, when we're in \"t/\"\n>   - \".\" when we're already in \".git\"\n>   - \"/home/peff/git/.git/.\" when we're in \".git/objects\"\n> > I'm not sure if some of those cases are not behaving as intended, or\n> there's some reason for the differences that I don't quite understand.\n\nYes, some of those cases do not behave as intended: it is true that\nsetup_git_directory() sets git_dir to \"/home/peff/git/.git\" when we\n(actually, you, given that my home directory is different) are in \"t/\",\nbut setup_git_directory_gently_1() would set gitdir to \".git\" and dir to\n\"/home/peff/git\". But the current directory is still what cwd says it is:\n\"/home/peff/git/t\".\n\nI added a comment:\n\n        /*\n         * The returned gitdir is relative to dir, and if dir does not reflect\n         * the current working directory, we simply make the gitdir\n         * absolute.\n         */\n\nAnd thanks: you reminded me of another issue I wanted to address but\nforgot: if gitdir is \".\", I do not want the resulting absolute path to\nhave a trailing \"/.\". I fixed that, too.\n\nCiao,\nDscho\n"},{"id":"313180","messageId":"alpine.DEB.2.20.1703031606130.3767@virtualbox","threadId":"44653","inReplyTo":"20170303045101.om7qtbcmtuw6kzvr@sigill.intra.peff.net","subject":"Re: [PATCH v2 6/9] read_early_config(): special-case builtins that create a repository","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T15:11:41Z","receivedAt":"2017-03-03T15:13:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Thu, 2 Mar 2017, Jeff King wrote:\n\n> On Fri, Mar 03, 2017 at 03:04:24AM +0100, Johannes Schindelin wrote:\n> \n> > When the command we are about to execute wants to create a repository\n> > (i.e. the command is `init` or `clone`), we *must not* look for a\n> > repository config.\n> \n> Hmm. I'm not sure if this one is worth the hackery here.\n\nI guess you're right. Let me think about this for a while in the back of\nmy head while I work on other parts of the patch series.\n\nCiao,\nDscho\n"},{"id":"313182","messageId":"alpine.DEB.2.20.1703031553070.3767@virtualbox","threadId":"44653","inReplyTo":"20170303050745.wv4zsb24ykr34xr7@sigill.intra.peff.net","subject":"Re: [PATCH v2 9/9] Test read_early_config()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T15:04:15Z","receivedAt":"2017-03-03T15:38:23Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Fri, 3 Mar 2017, Jeff King wrote:\n\n> On Fri, Mar 03, 2017 at 03:04:36AM +0100, Johannes Schindelin wrote:\n> \n> > So far, we had no explicit tests of that function.\n> \n> Makes sense. The pager tests fixed in an earlier commit were effectively\n> checking those, but I don't mind making it explicit.\n\nWell, TBH I am a bit uncomfortable with t7006 doing those tests. Just\nimagine that something breaks in that script, say, when working on\nexporting the read_early_config() function. You probably see this coming:\ndebugging those breakages is half a nightmare. There are multiple levels\nof shell script functions, a Perl script, and an isatty() call between the\nbug hunter and the bug.\n\nWith the new tests, it all becomes much more straight-forward to debug.\nAnd also less surprising (think about the fun you can have with test cases\nthat fail when running `make t7006-pager.sh` but not when running `bash\nt7006-pager.sh -i -v -x`, just because of a forgotten `test_terminal`...).\n\nAnd yes, you can debug t7006 with the good old \"insert debug print\nstatements here and there, then compile and run, rinse & repeat\" method.\nBut you know, getting to use a real IDE with real debugger/intellisense\nintegration after years of working on C code in the terminal with vim and\ngdb [*1*], I kinda feel a bit pampered and ask myself how I could possibly\nhave put up with the awkwardness. ;-)\n\nCiao,\nDscho\n\nFootnote *1*: for some years I developed Java code in Eclipse and already\nthen did I notice just how much faster development becomes when you have\npowerful tools to help you...\n"},{"id":"313183","messageId":"alpine.DEB.2.20.1703031612210.3767@virtualbox","threadId":"44653","inReplyTo":"20170303051451.elzz2kzumft2iugg@sigill.intra.peff.net","subject":"Re: [PATCH v2 0/9] Fix the early config","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T15:31:37Z","receivedAt":"2017-03-03T15:38:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Fri, 3 Mar 2017, Jeff King wrote:\n\n> On Fri, Mar 03, 2017 at 03:03:56AM +0100, Johannes Schindelin wrote:\n> \n> > These patches are an attempt to make Git's startup sequence a bit less\n> > surprising.\n> > \n> > The idea here is to discover the .git/ directory gently (i.e. without\n> > changing the current working directory, or global variables), and to\n> > use it to read the .git/config file early, before we actually called\n> > setup_git_directory() (if we ever do that).\n> \n> Thanks for working on this. I think it's a huge improvement over the\n> status quo, and over the earlier attempt. I don't see anything hugely\n> wrong with this series, though I did note one bug, along with some minor\n> refinements.\n\nThank you very much for your review. You did point out a couple of things\nI overlooked, and while it was a pain in the back to go from v1 to v2\n(i.e. avoiding code duplication, which immediately put the changed code to\na huge test by having the very central setup_git_directory() use it), I\nreally believe that we are better off because the patch series pays off\nmore technical debt now than it introduces.\n\n> > My dirty little secret is that I actually need this for something else\n> > entirely. I need to patch an internal version of Git to gather\n> > statistics, and to that end I need to read the config before and after\n> > running every Git command. Hence the need for a gentle, and correct\n> > early config.\n> \n> We do something similar at GitHub, but it falls into two categories:\n> \n>   - stat-gathering that's on all the time, so doesn't need to look at\n>     config (I'm not sure in your case if you want to trigger the\n>     gathering with config, or if config is just one of the things you\n>     are gathering).\n> \n>   - logging that is turned on selectively for some repos, but which\n>     doesn't have to be looked up until we know we are in a repo\n\nYou probably guessed what I need to do, anyway: for our GVFS usage, we\nneed some telemetry (i.e. usage statistics), and I basically want to run a\nhook whenever any Git command is called, but only on GVFS-enabled\nrepositories.\n\nSo it is somewhat related, but slightly different from your usage.\n\n> I looked into making something upstream-able, and my approach was to\n> allow GIT_TRACE_* variables to be specified in the config. But of course\n> that ran into the early-config problem (and I really wanted repo-level\n> config there, because I'd like to be able to turn on tracing for just a\n> single problematic repo).\n> \n> So not really something you need to work on, but maybe food for thought\n> as you work on your internal project.\n\nRight, GIT_TRACE_* settings in the config should be doable now.\n\nCiao,\nDscho\n"},{"id":"313184","messageId":"alpine.DEB.2.20.1703031632370.3767@virtualbox","threadId":"44653","inReplyTo":"20170303112610.x6mdh42nd4hmxz2h@sigill.intra.peff.net","subject":"Re: [PATCH v2 2/9] setup_git_directory(): use is_dir_sep() helper","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T15:35:26Z","receivedAt":"2017-03-03T15:39:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Fri, 3 Mar 2017, Jeff King wrote:\n\n> On Fri, Mar 03, 2017 at 12:16:31PM +0100, Johannes Schindelin wrote:\n> \n> > > What is \"dir\"? I'm guessing this patch got reordered and it should\n> > > stay as cwd.buf?\n> > \n> > Oh drats. Usually I do a final `git rebase -x \"make test\"\n> > upstream/master` run before submitting, but I was really, really tired\n> > by the end of that stretch.\n> \n> I usually do the same, and have done the \"too tired\" thing, too, only to\n> have it bite me. That's why I so readily recognized the problem. :)\n\n:-)\n\n> I've recently switched to using Michael's \"git test\" program[1], which\n> caches the test results for each tree in a git-note. That makes the\n> final \"rebase -x\" a lot less painful if you've left the early commits\n> alone.\n\nGood point. I meant to have a look, but got really overwhelmed with other\nthings.\n\n> The python dependency might be a blocker for you, but I suspect the\n> caching parts would be easy to hack together with shell.\n\nNo, personally I have no problem with Python. If you asked me to include\nit in Git for Windows' installer, increasing the size noticably, that\nwould be a totally different matter, of course.\n\nCiao,\nDscho\n"},{"id":"313196","messageId":"alpine.DEB.2.20.1703031620190.3767@virtualbox","threadId":"44653","inReplyTo":"20170303050630.mw6asla65prku3vq@sigill.intra.peff.net","subject":"Re: [PATCH v2 8/9] read_early_config(): really discover .git/","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T15:26:09Z","receivedAt":"2017-03-03T16:59:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Fri, 3 Mar 2017, Jeff King wrote:\n\n> I think the \"dirty hack...\" comment in read_early_config() can be\n> condensed (or go away) now.\n\nYes, I made that change in response to a comment you made about an earlier\npatch in this series.\n\n> I think we _could_ do away with read_early_config() entirely, and just\n> have the regular config code do this lookup when we're not already in a\n> repo. But then we'd really need to depend on the \"creating_repository\"\n> flag being managed correctly.\n\nWell, that would be a major design change. I'm not really all that\ncomfortable with that...\n\n> I think I prefer the idea that a few \"early\" spots like pager and alias\n> config need to use this special function to access the config. That's\n> less likely to cause surprises when some config option is picked up\n> before we have run setup_git_directory().\n\nExactly. There is semantic meaning in calling read_early_config() vs\ngit_config().\n\n> There is one surprising case that I think we need to deal with even now,\n> though. If I do:\n> \n>   git init repo\n>   git -C repo config pager.upload-pack 'echo whoops'\n>   git upload-pack repo\n>   cd repo && git upload-pack .\n> \n> the first one is OK, but the second reads and executes the pager config\n> from the repo, even though we usually consider upload-pack to be OK to\n> run in an untrusted repo. This _isn't_ a new thing in your patch, but\n> just something I noticed while we are here.\n> \n> And maybe it is a non-issue. The early-config bits all happen via the\n> git wrapper, and normally we use the direct dashed \"git-upload-pack\" to\n> access the other repositories. Certainly I have been known to use \"git\n> -c ... upload-pack\" while debugging various things.\n> \n> So I dunno. You really have to jump through some hoops for it to matter,\n> but I just wonder if the git wrapper should be special-casing\n> upload-pack, too.\n\nWhile I agree that it may sound surprising, I would like to point out that\nthere *is* a semantic difference between `git upload-pack repo` and `git\n-C repo upload-pack .`: the working directory is different. And given that\nso much in Git's operations depend on the working directory, it makes\nsense that those two invocations have slightly different semantics, too.\n\nI also agree, obviously, that this is something that would need a separate\npatch series to tackle ;-)\n\nCiao,\nDscho\n"},{"id":"313199","messageId":"cover.1488562287.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488506615.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 0/9] Fix the early config","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T17:31:55Z","receivedAt":"2017-03-03T17:32:41Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"These patches are an attempt to make Git's startup sequence a bit less\nsurprising.\n\nThe idea here is to discover the .git/ directory gently (i.e. without\nchanging the current working directory, or global variables), and to use\nit to read the .git/config file early, before we actually called\nsetup_git_directory() (if we ever do that).\n\nThis also allows us to fix the early config e.g. to determine the pager\nor to resolve aliases in a non-surprising manner.\n\nMy dirty little secret is that I need to discover the Git directory\nearly, without changing global state, for usage statistics gathering in\nthe GVFS Git project, so I actually do not care all that much about the\nearly config, although it is a welcome fallout (and a good reason for\naccepting these patches and thereby releasing me of more maintenance\nburden :-)).\n\nNotable notes:\n\n- In contrast to earlier versions, I no longer special-case init and\n  clone. Peff pointed out that this adds technical debt, and that we can\n  actually argue (for consistency's sake) that early config reads the\n  current repository config (if any) even for init and clone.\n\n- The read_early_config() function does not cache Git directory\n  discovery nor read values. If needed, this can be implemented later,\n  in a separate patch series.\n\n- The alias handling in git.c could possibly benefit from this work, but\n  again, this is a separate topic from the current patch series.\n\nChanges since v2:\n\n- replaced `test -e` by `test_path_is_file`\n\n- fixed premature \"cwd -> dir\" in 2/9\n\n- the setup_git_directory_gently_1() function is no longer renamed\n  because it is not exported directly, anyway\n\n- fixed the way setup_discovered_git_dir() expected the offset parameter\n  to exclude the trailing slash (which is not true for root\n  directories); also verified that setup_bare_git_dir() does not require\n  a corresponding patch\n\n- switched to using size_t instead of int to save the length of the\n  strbuf in discover_git_directory()\n\n- ensured that discover_git_directory() turns a relative gitdir into an\n  absolute one even if there is already some text in the strbuf\n\n- clarified under which circumstances we turn a relative gitdir into an\n  absolute one\n\n- avoided absolute gitdir with trailing \"/.\" to be returned\n\n- the commit that fixes the \"really dirty hack\" now rewords that comment\n  to reflect that it is no longer a really dirty hack\n\n- dropped the special-casing of init and clone\n\n- the discover_git_directory() function now correctly checks the\n  repository version, warning (and returning NULL) in case of a problem\n\n\nJohannes Schindelin (9):\n  t7006: replace dubious test\n  setup_git_directory(): use is_dir_sep() helper\n  Prepare setup_discovered_git_directory() the root directory\n  setup_git_directory_1(): avoid changing global state\n  Export the discover_git_directory() function\n  Make read_early_config() reusable\n  read_early_config(): avoid .git/config hack when unneeded\n  read_early_config(): really discover .git/\n  Test read_early_config()\n\n cache.h                 |   3 +\n config.c                |  27 ++++++\n pager.c                 |  31 -------\n setup.c                 | 237 ++++++++++++++++++++++++++++++++----------------\n t/helper/test-config.c  |  15 +++\n t/t1309-early-config.sh |  50 ++++++++++\n t/t7006-pager.sh        |  18 +++-\n 7 files changed, 269 insertions(+), 112 deletions(-)\n create mode 100755 t/t1309-early-config.sh\n\n\nbase-commit: 3bc53220cb2dcf709f7a027a3f526befd021d858\nPublished-As: https://github.com/dscho/git/releases/tag/early-config-v3\nFetch-It-Via: git fetch https://github.com/dscho/git early-config-v3\n\nInterdiff vs v2:\n\n diff --git a/cache.h b/cache.h\n index 0af7141242f..8a4580f921d 100644\n --- a/cache.h\n +++ b/cache.h\n @@ -518,6 +518,7 @@ extern void set_git_work_tree(const char *tree);\n  #define ALTERNATE_DB_ENVIRONMENT \"GIT_ALTERNATE_OBJECT_DIRECTORIES\"\n  \n  extern void setup_work_tree(void);\n +/* Find GIT_DIR without changing the working directory or other global state */\n  extern const char *discover_git_directory(struct strbuf *gitdir);\n  extern const char *setup_git_directory_gently(int *);\n  extern const char *setup_git_directory(void);\n @@ -2070,7 +2071,7 @@ const char *split_cmdline_strerror(int cmdline_errno);\n  \n  /* setup.c */\n  struct startup_info {\n -\tint have_repository, creating_repository;\n +\tint have_repository;\n  \tconst char *prefix;\n  };\n  extern struct startup_info *startup_info;\n diff --git a/config.c b/config.c\n index bcda397d42e..749623a9649 100644\n --- a/config.c\n +++ b/config.c\n @@ -1419,25 +1419,16 @@ void read_early_config(config_fn_t cb, void *data)\n  \tgit_config_with_options(cb, data, NULL, 1);\n  \n  \t/*\n -\t * Note that this is a really dirty hack that does the wrong thing in\n -\t * many cases. The crux of the problem is that we cannot run\n -\t * setup_git_directory() early on in git's setup, so we have no idea if\n -\t * we are in a repository or not, and therefore are not sure whether\n -\t * and how to read repository-local config.\n -\t *\n -\t * So if we _aren't_ in a repository (or we are but we would reject its\n -\t * core.repositoryformatversion), we'll read whatever is in .git/config\n -\t * blindly. Similarly, if we _are_ in a repository, but not at the\n -\t * root, we'll fail to find .git/config (because it's really\n -\t * ../.git/config, etc). See t7006 for a complete set of failures.\n -\t *\n -\t * However, we have historically provided this hack because it does\n -\t * work some of the time (namely when you are at the top-level of a\n -\t * valid repository), and would rarely make things worse (i.e., you do\n -\t * not generally have a .git/config file sitting around).\n +\t * When we are not about to create a repository ourselves (init or\n +\t * clone) and when no .git/ directory was set up yet (in which case\n +\t * git_config_with_options() would already have picked up the\n +\t * repository config), we ask discover_git_directory() to figure out\n +\t * whether there is any repository config we should use (but unlike\n +\t * setup_git_directory_gently(), no global state is changed, most\n +\t * notably, the current working directory is still the same after\n +\t * the call).\n  \t */\n -\tif (!startup_info->creating_repository && !have_git_dir() &&\n -\t    discover_git_directory(&buf)) {\n +\tif (!have_git_dir() && discover_git_directory(&buf)) {\n  \t\tstruct git_config_source repo_config;\n  \n  \t\tmemset(&repo_config, 0, sizeof(repo_config));\n diff --git a/git.c b/git.c\n index 9fb9bb90a21..33f52acbcc8 100644\n --- a/git.c\n +++ b/git.c\n @@ -337,9 +337,6 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n  \tstruct stat st;\n  \tconst char *prefix;\n  \n -\tif (p->fn == cmd_init_db || p->fn == cmd_clone)\n -\t\tstartup_info->creating_repository = 1;\n -\n  \tprefix = NULL;\n  \thelp = argc == 2 && !strcmp(argv[1], \"-h\");\n  \tif (!help) {\n diff --git a/setup.c b/setup.c\n index 7ceca6cc6ef..5320ae37314 100644\n --- a/setup.c\n +++ b/setup.c\n @@ -721,8 +721,10 @@ static const char *setup_discovered_git_dir(const char *gitdir,\n  \tif (offset == cwd->len)\n  \t\treturn NULL;\n  \n -\t/* Make \"offset\" point to past the '/', and add a '/' at the end */\n -\toffset++;\n +\t/* Make \"offset\" point past the '/' (already the case for root dirs) */\n +\tif (offset != offset_1st_component(cwd->buf))\n +\t\toffset++;\n +\t/* Add a '/' at the end */\n  \tstrbuf_addch(cwd, '/');\n  \treturn cwd->buf + offset;\n  }\n @@ -839,8 +841,8 @@ enum discovery_result {\n   * the discovered .git/ directory, if any. This path may be relative against\n   * `dir` (i.e. *not* necessarily the cwd).\n   */\n -static enum discovery_result discover_git_directory_1(struct strbuf *dir,\n -\t\t\t\t\t\t      struct strbuf *gitdir)\n +static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n +\t\t\t\t\t\t\t  struct strbuf *gitdir)\n  {\n  \tconst char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);\n  \tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n @@ -923,24 +925,44 @@ static enum discovery_result discover_git_directory_1(struct strbuf *dir,\n  \n  const char *discover_git_directory(struct strbuf *gitdir)\n  {\n -\tstruct strbuf dir = STRBUF_INIT;\n -\tint len;\n +\tstruct strbuf dir = STRBUF_INIT, err = STRBUF_INIT;\n +\tsize_t gitdir_offset = gitdir->len, cwd_len;\n +\tstruct repository_format candidate;\n  \n  \tif (strbuf_getcwd(&dir))\n  \t\treturn NULL;\n  \n -\tlen = dir.len;\n -\tif (discover_git_directory_1(&dir, gitdir) < 0) {\n +\tcwd_len = dir.len;\n +\tif (setup_git_directory_gently_1(&dir, gitdir) < 0) {\n  \t\tstrbuf_release(&dir);\n  \t\treturn NULL;\n  \t}\n  \n -\tif (dir.len < len && !is_absolute_path(gitdir->buf)) {\n -\t\tstrbuf_addch(&dir, '/');\n -\t\tstrbuf_insert(gitdir, 0, dir.buf, dir.len);\n +\t/*\n +\t * The returned gitdir is relative to dir, and if dir does not reflect\n +\t * the current working directory, we simply make the gitdir absolute.\n +\t */\n +\tif (dir.len < cwd_len && !is_absolute_path(gitdir->buf + gitdir_offset)) {\n +\t\t/* Avoid a trailing \"/.\" */\n +\t\tif (!strcmp(\".\", gitdir->buf + gitdir_offset))\n +\t\t\tstrbuf_setlen(gitdir, gitdir_offset);\n +\t\telse\n +\t\t\tstrbuf_addch(&dir, '/');\n +\t\tstrbuf_insert(gitdir, gitdir_offset, dir.buf, dir.len);\n  \t}\n +\n +\tstrbuf_reset(&dir);\n +\tstrbuf_addf(&dir, \"%s/config\", gitdir->buf + gitdir_offset);\n +\tread_repository_format(&candidate, dir.buf);\n  \tstrbuf_release(&dir);\n  \n +\tif (verify_repository_format(&candidate, &err) < 0) {\n +\t\twarning(\"ignoring git dir '%s': %s\",\n +\t\t\tgitdir->buf + gitdir_offset, err.buf);\n +\t\tstrbuf_release(&err);\n +\t\treturn NULL;\n +\t}\n +\n  \treturn gitdir->buf;\n  }\n  \n @@ -970,7 +992,7 @@ const char *setup_git_directory_gently(int *nongit_ok)\n  \t\tdie_errno(_(\"Unable to read current working directory\"));\n  \tstrbuf_addbuf(&dir, &cwd);\n  \n -\tswitch (discover_git_directory_1(&dir, &gitdir)) {\n +\tswitch (setup_git_directory_gently_1(&dir, &gitdir)) {\n  \tcase GIT_DIR_NONE:\n  \t\tprefix = NULL;\n  \t\tbreak;\n @@ -1000,7 +1022,7 @@ const char *setup_git_directory_gently(int *nongit_ok)\n  \t\t      \"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\"),\n  \t\t    dir.buf);\n  \tdefault:\n -\t\tdie(\"BUG: unhandled discover_git_directory() result\");\n +\t\tdie(\"BUG: unhandled setup_git_directory_1() result\");\n  \t}\n  \n  \tif (prefix)\n diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh\n index bf89340988b..4f3794d415e 100755\n --- a/t/t7006-pager.sh\n +++ b/t/t7006-pager.sh\n @@ -387,7 +387,7 @@ test_expect_success TTY 'core.pager in repo config works and retains cwd' '\n  \t\tcd sub &&\n  \t\trm -f cwd-retained &&\n  \t\ttest_terminal git -p rev-parse HEAD &&\n -\t\ttest -e cwd-retained\n +\t\ttest_path_is_file cwd-retained\n  \t)\n  '\n  \n\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n"},{"id":"313200","messageId":"96c2b0be087ee212cbe0aa18cefd2965c09e48fd.1488562287.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488562287.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 1/9] t7006: replace dubious test","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T17:32:01Z","receivedAt":"2017-03-03T17:32:51Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The idea of the test case \"git -p - core.pager is not used from\nsubdirectory\" was to verify that the setup_git_directory() function had\nnot been called just to obtain the core.pager setting.\n\nHowever, we are about to fix the early config machinery so that it\n*does* work, without messing up the global state.\n\nOnce that is done, the core.pager setting *will* be used, even when\nrunning from a subdirectory, and that is a Good Thing.\n\nThe intention of that test case, however, was to verify that the\nsetup_git_directory() function has not run, because it changes global\nstate such as the current working directory.\n\nTo keep that spirit, but fix the incorrect assumption, this patch\nreplaces that test case by a new one that verifies that the pager is\nrun in the subdirectory, i.e. that the current working directory has\nnot been changed at the time the pager is configured and launched, even\nif the `rev-parse` command requires a .git/ directory and *will* change\nthe working directory.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t7006-pager.sh | 12 +++++++++++-\n 1 file changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t7006-pager.sh b/t/t7006-pager.sh\nindex c8dc665f2fd..304ae06c600 100755\n--- a/t/t7006-pager.sh\n+++ b/t/t7006-pager.sh\n@@ -378,9 +378,19 @@ test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p request-pull'\n test_default_pager        expect_success test_must_fail 'git -p'\n test_PAGER_overrides      expect_success test_must_fail 'git -p'\n test_local_config_ignored expect_failure test_must_fail 'git -p'\n-test_no_local_config_subdir expect_success test_must_fail 'git -p'\n test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p'\n \n+test_expect_failure TTY 'core.pager in repo config works and retains cwd' '\n+\tsane_unset GIT_PAGER &&\n+\ttest_config core.pager \"cat >cwd-retained\" &&\n+\t(\n+\t\tcd sub &&\n+\t\trm -f cwd-retained &&\n+\t\ttest_terminal git -p rev-parse HEAD &&\n+\t\ttest_path_is_file cwd-retained\n+\t)\n+'\n+\n test_doesnt_paginate      expect_failure test_must_fail 'git -p nonsense'\n \n test_pager_choices                       'git shortlog'\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313201","messageId":"6211def3f876aaed0aabf479ef048e6d52135b97.1488562287.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488562287.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 2/9] setup_git_directory(): use is_dir_sep() helper","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T17:32:42Z","receivedAt":"2017-03-03T17:32:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It is okay in practice to test for forward slashes in the output of\ngetcwd(), because we go out of our way to convert backslashes to forward\nslashes in getcwd()'s output on Windows.\n\nStill, the correct way to test for a dir separator is by using the\nhelper function we introduced for that very purpose. It also serves as a\ngood documentation what the code tries to do (not \"how\").\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/setup.c b/setup.c\nindex 967f289f1ef..c47619605fb 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -910,7 +910,8 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t\t\treturn setup_bare_git_dir(&cwd, offset, nongit_ok);\n \n \t\toffset_parent = offset;\n-\t\twhile (--offset_parent > ceil_offset && cwd.buf[offset_parent] != '/');\n+\t\twhile (--offset_parent > ceil_offset &&\n+\t\t       !is_dir_sep(cwd.buf[offset_parent]));\n \t\tif (offset_parent <= ceil_offset)\n \t\t\treturn setup_nongit(cwd.buf, nongit_ok);\n \t\tif (one_filesystem) {\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313202","messageId":"11d7318604fe3d5d6b30cf51101a57555011880e.1488562287.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488562287.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 3/9] Prepare setup_discovered_git_directory() the root directory","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T17:32:45Z","receivedAt":"2017-03-03T17:33:07Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Currently, the offset parameter (indicating what part of the cwd\nparameter corresponds to the current directory after discovering the\n.git/ directory) is set to 0 when we are running in the root directory.\n\nHowever, in the next patches we will avoid changing the current working\ndirectory while searching for the .git/ directory, meaning that the\noffset corresponding to the root directory will have to be 1 to reflect\nthat this directory is characterized by the path \"/\" (and not \"\").\n\nSo let's make sure that setup_discovered_git_directory() only tries to\nappend the trailing slash to non-root directories.\n\nNote: the setup_bare_git_directory() does not need a corresponding\nchange, as it does not want to return a prefix.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex c47619605fb..91d884b6746 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -721,8 +721,10 @@ static const char *setup_discovered_git_dir(const char *gitdir,\n \tif (offset == cwd->len)\n \t\treturn NULL;\n \n-\t/* Make \"offset\" point to past the '/', and add a '/' at the end */\n-\toffset++;\n+\t/* Make \"offset\" point past the '/' (already the case for root dirs) */\n+\tif (offset != offset_1st_component(cwd->buf))\n+\t\toffset++;\n+\t/* Add a '/' at the end */\n \tstrbuf_addch(cwd, '/');\n \treturn cwd->buf + offset;\n }\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313203","messageId":"cdb128397b8f6eddad56bc720342a6a811930788.1488562287.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488562287.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 4/9] setup_git_directory_1(): avoid changing global state","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T17:32:51Z","receivedAt":"2017-03-03T17:33:18Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"For historical reasons, Git searches for the .git/ directory (or the\n.git file) by changing the working directory successively to the parent\ndirectory of the current directory, until either anything was found or\nuntil a ceiling or a mount point is hit.\n\nFurther global state may be changed in case a .git/ directory was found.\n\nWe do have a use case, though, where we would like to find the .git/\ndirectory without having any global state touched, though: when we read\nthe early config e.g. for the pager or for alias expansion.\n\nLet's just move all of code that changes any global state out of the\nfunction `setup_git_directory_gently_1()` into\n`setup_git_directory_gently()`.\n\nIn subsequent patches, we will use the _1() function in a new\n`discover_git_directory()` function that we will then use for the early\nconfig code.\n\nNote: the new loop is a *little* tricky, as we have to handle the root\ndirectory specially: we cannot simply strip away the last component\nincluding the slash, as the root directory only has that slash. To remedy\nthat, we introduce the `min_offset` variable that holds the minimal length\nof an absolute path, and using that to special-case the root directory,\nincluding an early exit before trying to find the parent of the root\ndirectory.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 189 ++++++++++++++++++++++++++++++++++++++--------------------------\n 1 file changed, 113 insertions(+), 76 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 91d884b6746..9a09bb41ab5 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -818,50 +818,49 @@ static int canonicalize_ceiling_entry(struct string_list_item *item,\n \t}\n }\n \n+enum discovery_result {\n+\tGIT_DIR_NONE = 0,\n+\tGIT_DIR_EXPLICIT,\n+\tGIT_DIR_DISCOVERED,\n+\tGIT_DIR_BARE,\n+\t/* these are errors */\n+\tGIT_DIR_HIT_CEILING = -1,\n+\tGIT_DIR_HIT_MOUNT_POINT = -2\n+};\n+\n /*\n  * We cannot decide in this function whether we are in the work tree or\n  * not, since the config can only be read _after_ this function was called.\n+ *\n+ * Also, we avoid changing any global state (such as the current working\n+ * directory) to allow early callers.\n+ *\n+ * The directory where the search should start needs to be passed in via the\n+ * `dir` parameter; upon return, the `dir` buffer will contain the path of\n+ * the directory where the search ended, and `gitdir` will contain the path of\n+ * the discovered .git/ directory, if any. This path may be relative against\n+ * `dir` (i.e. *not* necessarily the cwd).\n  */\n-static const char *setup_git_directory_gently_1(int *nongit_ok)\n+static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n+\t\t\t\t\t\t\t  struct strbuf *gitdir)\n {\n \tconst char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);\n \tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n-\tstatic struct strbuf cwd = STRBUF_INIT;\n-\tconst char *gitdirenv, *ret;\n-\tchar *gitfile;\n-\tint offset, offset_parent, ceil_offset = -1;\n+\tconst char *gitdirenv;\n+\tint ceil_offset = -1, min_offset = has_dos_drive_prefix(dir->buf) ? 3 : 1;\n \tdev_t current_device = 0;\n \tint one_filesystem = 1;\n \n \t/*\n-\t * We may have read an incomplete configuration before\n-\t * setting-up the git directory. If so, clear the cache so\n-\t * that the next queries to the configuration reload complete\n-\t * configuration (including the per-repo config file that we\n-\t * ignored previously).\n-\t */\n-\tgit_config_clear();\n-\n-\t/*\n-\t * Let's assume that we are in a git repository.\n-\t * If it turns out later that we are somewhere else, the value will be\n-\t * updated accordingly.\n-\t */\n-\tif (nongit_ok)\n-\t\t*nongit_ok = 0;\n-\n-\tif (strbuf_getcwd(&cwd))\n-\t\tdie_errno(_(\"Unable to read current working directory\"));\n-\toffset = cwd.len;\n-\n-\t/*\n \t * If GIT_DIR is set explicitly, we're not going\n \t * to do any discovery, but we still do repository\n \t * validation.\n \t */\n \tgitdirenv = getenv(GIT_DIR_ENVIRONMENT);\n-\tif (gitdirenv)\n-\t\treturn setup_explicit_git_dir(gitdirenv, &cwd, nongit_ok);\n+\tif (gitdirenv) {\n+\t\tstrbuf_addstr(gitdir, gitdirenv);\n+\t\treturn GIT_DIR_EXPLICIT;\n+\t}\n \n \tif (env_ceiling_dirs) {\n \t\tint empty_entry_found = 0;\n@@ -869,15 +868,15 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);\n \t\tfilter_string_list(&ceiling_dirs, 0,\n \t\t\t\t   canonicalize_ceiling_entry, &empty_entry_found);\n-\t\tceil_offset = longest_ancestor_length(cwd.buf, &ceiling_dirs);\n+\t\tceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs);\n \t\tstring_list_clear(&ceiling_dirs, 0);\n \t}\n \n-\tif (ceil_offset < 0 && has_dos_drive_prefix(cwd.buf))\n-\t\tceil_offset = 1;\n+\tif (ceil_offset < 0)\n+\t\tceil_offset = min_offset - 2;\n \n \t/*\n-\t * Test in the following order (relative to the cwd):\n+\t * Test in the following order (relative to the dir):\n \t * - .git (file containing \"gitdir: <path>\")\n \t * - .git/\n \t * - ./ (bare)\n@@ -889,62 +888,100 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t */\n \tone_filesystem = !git_env_bool(\"GIT_DISCOVERY_ACROSS_FILESYSTEM\", 0);\n \tif (one_filesystem)\n-\t\tcurrent_device = get_device_or_die(\".\", NULL, 0);\n+\t\tcurrent_device = get_device_or_die(dir->buf, NULL, 0);\n \tfor (;;) {\n-\t\tgitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);\n-\t\tif (gitfile)\n-\t\t\tgitdirenv = gitfile = xstrdup(gitfile);\n-\t\telse {\n-\t\t\tif (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))\n-\t\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n-\t\t}\n-\n+\t\tint offset = dir->len;\n+\n+\t\tif (offset > min_offset)\n+\t\t\tstrbuf_addch(dir, '/');\n+\t\tstrbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);\n+\t\tgitdirenv = read_gitfile(dir->buf);\n+\t\tif (!gitdirenv && is_git_directory(dir->buf))\n+\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n+\t\tstrbuf_setlen(dir, offset);\n \t\tif (gitdirenv) {\n-\t\t\tret = setup_discovered_git_dir(gitdirenv,\n-\t\t\t\t\t\t       &cwd, offset,\n-\t\t\t\t\t\t       nongit_ok);\n-\t\t\tfree(gitfile);\n-\t\t\treturn ret;\n+\t\t\tstrbuf_addstr(gitdir, gitdirenv);\n+\t\t\treturn GIT_DIR_DISCOVERED;\n \t\t}\n-\t\tfree(gitfile);\n \n-\t\tif (is_git_directory(\".\"))\n-\t\t\treturn setup_bare_git_dir(&cwd, offset, nongit_ok);\n-\n-\t\toffset_parent = offset;\n-\t\twhile (--offset_parent > ceil_offset &&\n-\t\t       !is_dir_sep(cwd.buf[offset_parent]));\n-\t\tif (offset_parent <= ceil_offset)\n-\t\t\treturn setup_nongit(cwd.buf, nongit_ok);\n-\t\tif (one_filesystem) {\n-\t\t\tdev_t parent_device = get_device_or_die(\"..\", cwd.buf,\n-\t\t\t\t\t\t\t\toffset);\n-\t\t\tif (parent_device != current_device) {\n-\t\t\t\tif (nongit_ok) {\n-\t\t\t\t\tif (chdir(cwd.buf))\n-\t\t\t\t\t\tdie_errno(_(\"Cannot come back to cwd\"));\n-\t\t\t\t\t*nongit_ok = 1;\n-\t\t\t\t\treturn NULL;\n-\t\t\t\t}\n-\t\t\t\tstrbuf_setlen(&cwd, offset);\n-\t\t\t\tdie(_(\"Not a git repository (or any parent up to mount point %s)\\n\"\n-\t\t\t\t\"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\"),\n-\t\t\t\t    cwd.buf);\n-\t\t\t}\n-\t\t}\n-\t\tif (chdir(\"..\")) {\n-\t\t\tstrbuf_setlen(&cwd, offset);\n-\t\t\tdie_errno(_(\"Cannot change to '%s/..'\"), cwd.buf);\n+\t\tif (is_git_directory(dir->buf)) {\n+\t\t\tstrbuf_addstr(gitdir, \".\");\n+\t\t\treturn GIT_DIR_BARE;\n \t\t}\n-\t\toffset = offset_parent;\n+\n+\t\tif (offset <= min_offset)\n+\t\t\treturn GIT_DIR_HIT_CEILING;\n+\n+\t\twhile (--offset > ceil_offset && !is_dir_sep(dir->buf[offset]));\n+\t\tif (offset <= ceil_offset)\n+\t\t\treturn GIT_DIR_HIT_CEILING;\n+\n+\t\tstrbuf_setlen(dir, offset > min_offset ?  offset : min_offset);\n+\t\tif (one_filesystem &&\n+\t\t    current_device != get_device_or_die(dir->buf, NULL, offset))\n+\t\t\treturn GIT_DIR_HIT_MOUNT_POINT;\n \t}\n }\n \n const char *setup_git_directory_gently(int *nongit_ok)\n {\n+\tstruct strbuf cwd = STRBUF_INIT, dir = STRBUF_INIT, gitdir = STRBUF_INIT;\n \tconst char *prefix;\n \n-\tprefix = setup_git_directory_gently_1(nongit_ok);\n+\t/*\n+\t * We may have read an incomplete configuration before\n+\t * setting-up the git directory. If so, clear the cache so\n+\t * that the next queries to the configuration reload complete\n+\t * configuration (including the per-repo config file that we\n+\t * ignored previously).\n+\t */\n+\tgit_config_clear();\n+\n+\t/*\n+\t * Let's assume that we are in a git repository.\n+\t * If it turns out later that we are somewhere else, the value will be\n+\t * updated accordingly.\n+\t */\n+\tif (nongit_ok)\n+\t\t*nongit_ok = 0;\n+\n+\tif (strbuf_getcwd(&cwd))\n+\t\tdie_errno(_(\"Unable to read current working directory\"));\n+\tstrbuf_addbuf(&dir, &cwd);\n+\n+\tswitch (setup_git_directory_gently_1(&dir, &gitdir)) {\n+\tcase GIT_DIR_NONE:\n+\t\tprefix = NULL;\n+\t\tbreak;\n+\tcase GIT_DIR_EXPLICIT:\n+\t\tprefix = setup_explicit_git_dir(gitdir.buf, &cwd, nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_DISCOVERED:\n+\t\tif (dir.len < cwd.len && chdir(dir.buf))\n+\t\t\tdie(_(\"Cannot change to '%s'\"), dir.buf);\n+\t\tprefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len,\n+\t\t\t\t\t\t  nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_BARE:\n+\t\tif (dir.len < cwd.len && chdir(dir.buf))\n+\t\t\tdie(_(\"Cannot change to '%s'\"), dir.buf);\n+\t\tprefix = setup_bare_git_dir(&cwd, dir.len, nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_HIT_CEILING:\n+\t\tprefix = setup_nongit(cwd.buf, nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_HIT_MOUNT_POINT:\n+\t\tif (nongit_ok) {\n+\t\t\t*nongit_ok = 1;\n+\t\t\treturn NULL;\n+\t\t}\n+\t\tdie(_(\"Not a git repository (or any parent up to mount point %s)\\n\"\n+\t\t      \"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\"),\n+\t\t    dir.buf);\n+\tdefault:\n+\t\tdie(\"BUG: unhandled setup_git_directory_1() result\");\n+\t}\n+\n \tif (prefix)\n \t\tsetenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);\n \telse\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313204","messageId":"a6cb213746f455adca3ce006f771f4f77b969da2.1488562287.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488562287.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 7/9] read_early_config(): avoid .git/config hack when unneeded","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T17:33:23Z","receivedAt":"2017-03-03T17:33:39Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"So far, we only look whether the startup_info claims to have seen a\ngit_dir.\n\nHowever, do_git_config_sequence() (and consequently the\ngit_config_with_options() call used by read_early_config() asks the\nhave_git_dir() function whether we have a .git/ directory, which in turn\nalso looks at git_dir and at the environment variable GIT_DIR. And when\nthis is the case, the repository config is handled already, so we do not\nhave to do that again explicitly.\n\nLet's just use the same function, have_git_dir(), to determine whether we\nhave to handle .git/config explicitly.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n config.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 9cfbeafd04c..068fa4dcfa6 100644\n--- a/config.c\n+++ b/config.c\n@@ -1427,14 +1427,15 @@ void read_early_config(config_fn_t cb, void *data)\n \t * core.repositoryformatversion), we'll read whatever is in .git/config\n \t * blindly. Similarly, if we _are_ in a repository, but not at the\n \t * root, we'll fail to find .git/config (because it's really\n-\t * ../.git/config, etc). See t7006 for a complete set of failures.\n+\t * ../.git/config, etc), unless setup_git_directory() was already called.\n+\t * See t7006 for a complete set of failures.\n \t *\n \t * However, we have historically provided this hack because it does\n \t * work some of the time (namely when you are at the top-level of a\n \t * valid repository), and would rarely make things worse (i.e., you do\n \t * not generally have a .git/config file sitting around).\n \t */\n-\tif (!startup_info->have_repository) {\n+\tif (!have_git_dir()) {\n \t\tstruct git_config_source repo_config;\n \n \t\tmemset(&repo_config, 0, sizeof(repo_config));\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313205","messageId":"f84bac1f6c3138347360ec9a8045cec2e979a48e.1488562287.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488562287.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 5/9] Export the discover_git_directory() function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T17:33:16Z","receivedAt":"2017-03-03T17:33:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The setup_git_directory_gently_1() function we modified earlier still\nneeds to return both the path to the .git/ directory as well as the\n\"cd-up\" path to allow setup_git_directory() to retain its previous\nbehavior as if it changed the current working directory on its quest for\nthe .git/ directory.\n\nThis is a bit cumbersome to use if you only need to figure out the\n(possibly absolute) path of the .git/ directory. Let's just provide\na convenient wrapper function with an easier signature that *just*\ndiscovers the .git/ directory.\n\nWe will use it in a subsequent patch to support early config reading\nbetter.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n cache.h |  2 ++\n setup.c | 31 +++++++++++++++++++++++++++++++\n 2 files changed, 33 insertions(+)\n\ndiff --git a/cache.h b/cache.h\nindex 80b6372cf76..4d8eb38de74 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -518,6 +518,8 @@ extern void set_git_work_tree(const char *tree);\n #define ALTERNATE_DB_ENVIRONMENT \"GIT_ALTERNATE_OBJECT_DIRECTORIES\"\n \n extern void setup_work_tree(void);\n+/* Find GIT_DIR without changing the working directory or other global state */\n+extern const char *discover_git_directory(struct strbuf *gitdir);\n extern const char *setup_git_directory_gently(int *);\n extern const char *setup_git_directory(void);\n extern char *prefix_path(const char *prefix, int len, const char *path);\ndiff --git a/setup.c b/setup.c\nindex 9a09bb41ab5..32ce023638a 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -923,6 +923,37 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \t}\n }\n \n+const char *discover_git_directory(struct strbuf *gitdir)\n+{\n+\tstruct strbuf dir = STRBUF_INIT;\n+\tsize_t gitdir_offset = gitdir->len, cwd_len;\n+\n+\tif (strbuf_getcwd(&dir))\n+\t\treturn NULL;\n+\n+\tcwd_len = dir.len;\n+\tif (setup_git_directory_gently_1(&dir, gitdir) < 0) {\n+\t\tstrbuf_release(&dir);\n+\t\treturn NULL;\n+\t}\n+\n+\t/*\n+\t * The returned gitdir is relative to dir, and if dir does not reflect\n+\t * the current working directory, we simply make the gitdir absolute.\n+\t */\n+\tif (dir.len < cwd_len && !is_absolute_path(gitdir->buf + gitdir_offset)) {\n+\t\t/* Avoid a trailing \"/.\" */\n+\t\tif (!strcmp(\".\", gitdir->buf + gitdir_offset))\n+\t\t\tstrbuf_setlen(gitdir, gitdir_offset);\n+\t\telse\n+\t\t\tstrbuf_addch(&dir, '/');\n+\t\tstrbuf_insert(gitdir, gitdir_offset, dir.buf, dir.len);\n+\t}\n+\tstrbuf_release(&dir);\n+\n+\treturn gitdir->buf;\n+}\n+\n const char *setup_git_directory_gently(int *nongit_ok)\n {\n \tstruct strbuf cwd = STRBUF_INIT, dir = STRBUF_INIT, gitdir = STRBUF_INIT;\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313206","messageId":"e3819700196bba4916fd39c5354eb5135874b8c0.1488562287.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488562287.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 6/9] Make read_early_config() reusable","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T17:33:20Z","receivedAt":"2017-03-03T17:33:57Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The pager configuration needs to be read early, possibly before\ndiscovering any .git/ directory.\n\nLet's not hide this function in pager.c, but make it available to other\ncallers.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n cache.h  |  1 +\n config.c | 31 +++++++++++++++++++++++++++++++\n pager.c  | 31 -------------------------------\n 3 files changed, 32 insertions(+), 31 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 4d8eb38de74..8a4580f921d 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1799,6 +1799,7 @@ extern int git_config_from_blob_sha1(config_fn_t fn, const char *name,\n \t\t\t\t     const unsigned char *sha1, void *data);\n extern void git_config_push_parameter(const char *text);\n extern int git_config_from_parameters(config_fn_t fn, void *data);\n+extern void read_early_config(config_fn_t cb, void *data);\n extern void git_config(config_fn_t fn, void *);\n extern int git_config_with_options(config_fn_t fn, void *,\n \t\t\t\t   struct git_config_source *config_source,\ndiff --git a/config.c b/config.c\nindex c6b874a7bf7..9cfbeafd04c 100644\n--- a/config.c\n+++ b/config.c\n@@ -1412,6 +1412,37 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \t}\n }\n \n+void read_early_config(config_fn_t cb, void *data)\n+{\n+\tgit_config_with_options(cb, data, NULL, 1);\n+\n+\t/*\n+\t * Note that this is a really dirty hack that does the wrong thing in\n+\t * many cases. The crux of the problem is that we cannot run\n+\t * setup_git_directory() early on in git's setup, so we have no idea if\n+\t * we are in a repository or not, and therefore are not sure whether\n+\t * and how to read repository-local config.\n+\t *\n+\t * So if we _aren't_ in a repository (or we are but we would reject its\n+\t * core.repositoryformatversion), we'll read whatever is in .git/config\n+\t * blindly. Similarly, if we _are_ in a repository, but not at the\n+\t * root, we'll fail to find .git/config (because it's really\n+\t * ../.git/config, etc). See t7006 for a complete set of failures.\n+\t *\n+\t * However, we have historically provided this hack because it does\n+\t * work some of the time (namely when you are at the top-level of a\n+\t * valid repository), and would rarely make things worse (i.e., you do\n+\t * not generally have a .git/config file sitting around).\n+\t */\n+\tif (!startup_info->have_repository) {\n+\t\tstruct git_config_source repo_config;\n+\n+\t\tmemset(&repo_config, 0, sizeof(repo_config));\n+\t\trepo_config.file = \".git/config\";\n+\t\tgit_config_with_options(cb, data, &repo_config, 1);\n+\t}\n+}\n+\n static void git_config_check_init(void);\n \n void git_config(config_fn_t fn, void *data)\ndiff --git a/pager.c b/pager.c\nindex ae796433630..73ca8bc3b17 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -43,37 +43,6 @@ static int core_pager_config(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n-static void read_early_config(config_fn_t cb, void *data)\n-{\n-\tgit_config_with_options(cb, data, NULL, 1);\n-\n-\t/*\n-\t * Note that this is a really dirty hack that does the wrong thing in\n-\t * many cases. The crux of the problem is that we cannot run\n-\t * setup_git_directory() early on in git's setup, so we have no idea if\n-\t * we are in a repository or not, and therefore are not sure whether\n-\t * and how to read repository-local config.\n-\t *\n-\t * So if we _aren't_ in a repository (or we are but we would reject its\n-\t * core.repositoryformatversion), we'll read whatever is in .git/config\n-\t * blindly. Similarly, if we _are_ in a repository, but not at the\n-\t * root, we'll fail to find .git/config (because it's really\n-\t * ../.git/config, etc). See t7006 for a complete set of failures.\n-\t *\n-\t * However, we have historically provided this hack because it does\n-\t * work some of the time (namely when you are at the top-level of a\n-\t * valid repository), and would rarely make things worse (i.e., you do\n-\t * not generally have a .git/config file sitting around).\n-\t */\n-\tif (!startup_info->have_repository) {\n-\t\tstruct git_config_source repo_config;\n-\n-\t\tmemset(&repo_config, 0, sizeof(repo_config));\n-\t\trepo_config.file = \".git/config\";\n-\t\tgit_config_with_options(cb, data, &repo_config, 1);\n-\t}\n-}\n-\n const char *git_pager(int stdout_is_tty)\n {\n \tconst char *pager;\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313207","messageId":"3e0790a6ea61b6f4f59df013e9233affb93e8634.1488562287.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488562287.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 9/9] Test read_early_config()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T17:33:32Z","receivedAt":"2017-03-03T17:33:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"So far, we had no explicit tests of that function.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/helper/test-config.c  | 15 +++++++++++++++\n t/t1309-early-config.sh | 50 +++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 65 insertions(+)\n create mode 100755 t/t1309-early-config.sh\n\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 83a4f2ab869..8e3ed6a76cb 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -66,6 +66,16 @@ static int iterate_cb(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n+static int early_config_cb(const char *var, const char *value, void *vdata)\n+{\n+\tconst char *key = vdata;\n+\n+\tif (!strcmp(key, var))\n+\t\tprintf(\"%s\\n\", value);\n+\n+\treturn 0;\n+}\n+\n int cmd_main(int argc, const char **argv)\n {\n \tint i, val;\n@@ -73,6 +83,11 @@ int cmd_main(int argc, const char **argv)\n \tconst struct string_list *strptr;\n \tstruct config_set cs;\n \n+\tif (argc == 3 && !strcmp(argv[1], \"read_early_config\")) {\n+\t\tread_early_config(early_config_cb, (void *)argv[2]);\n+\t\treturn 0;\n+\t}\n+\n \tsetup_git_directory();\n \n \tgit_configset_init(&cs);\ndiff --git a/t/t1309-early-config.sh b/t/t1309-early-config.sh\nnew file mode 100755\nindex 00000000000..0c55dee514c\n--- /dev/null\n+++ b/t/t1309-early-config.sh\n@@ -0,0 +1,50 @@\n+#!/bin/sh\n+\n+test_description='Test read_early_config()'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'read early config' '\n+\ttest_config early.config correct &&\n+\ttest-config read_early_config early.config >output &&\n+\ttest correct = \"$(cat output)\"\n+'\n+\n+test_expect_success 'in a sub-directory' '\n+\ttest_config early.config sub &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest sub = \"$(cat output)\"\n+'\n+\n+test_expect_success 'ceiling' '\n+\ttest_config early.config ceiling &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tGIT_CEILING_DIRECTORIES=\"$PWD\" &&\n+\t\texport GIT_CEILING_DIRECTORIES &&\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest -z \"$(cat output)\"\n+'\n+\n+test_expect_success 'ceiling #2' '\n+\tmkdir -p xdg/git &&\n+\tgit config -f xdg/git/config early.config xdg &&\n+\ttest_config early.config ceiling &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tXDG_CONFIG_HOME=\"$PWD\"/xdg &&\n+\t\tGIT_CEILING_DIRECTORIES=\"$PWD\" &&\n+\t\texport GIT_CEILING_DIRECTORIES XDG_CONFIG_HOME &&\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest xdg = \"$(cat output)\"\n+'\n+\n+test_done\n-- \n2.12.0.windows.1.7.g94dafc3b124\n"},{"id":"313208","messageId":"3952d78fa82b2cb5394369ae0be2f5992f418ff2.1488562287.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488562287.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 8/9] read_early_config(): really discover .git/","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-03T17:33:27Z","receivedAt":"2017-03-03T17:34:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Earlier, we punted and simply assumed that we are in the top-level\ndirectory of the project, and that there is no .git file but a .git/\ndirectory so that we can read directly from .git/config.\n\nHowever, that is not necessarily true. We may be in a subdirectory. Or\n.git may be a gitfile. Or the environment variable GIT_DIR may be set.\n\nTo remedy this situation, we just refactored the way\nsetup_git_directory() discovers the .git/ directory, to make it\nreusable, and more importantly, to leave all global variables and the\ncurrent working directory alone.\n\nLet's discover the .git/ directory correctly in read_early_config() by\nusing that new function.\n\nThis fixes 4 known breakages in t7006.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n config.c         | 33 ++++++++++++++-------------------\n setup.c          | 14 +++++++++++++-\n t/t7006-pager.sh |  8 ++++----\n 3 files changed, 31 insertions(+), 24 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 068fa4dcfa6..749623a9649 100644\n--- a/config.c\n+++ b/config.c\n@@ -1414,34 +1414,29 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \n void read_early_config(config_fn_t cb, void *data)\n {\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n \tgit_config_with_options(cb, data, NULL, 1);\n \n \t/*\n-\t * Note that this is a really dirty hack that does the wrong thing in\n-\t * many cases. The crux of the problem is that we cannot run\n-\t * setup_git_directory() early on in git's setup, so we have no idea if\n-\t * we are in a repository or not, and therefore are not sure whether\n-\t * and how to read repository-local config.\n-\t *\n-\t * So if we _aren't_ in a repository (or we are but we would reject its\n-\t * core.repositoryformatversion), we'll read whatever is in .git/config\n-\t * blindly. Similarly, if we _are_ in a repository, but not at the\n-\t * root, we'll fail to find .git/config (because it's really\n-\t * ../.git/config, etc), unless setup_git_directory() was already called.\n-\t * See t7006 for a complete set of failures.\n-\t *\n-\t * However, we have historically provided this hack because it does\n-\t * work some of the time (namely when you are at the top-level of a\n-\t * valid repository), and would rarely make things worse (i.e., you do\n-\t * not generally have a .git/config file sitting around).\n+\t * When we are not about to create a repository ourselves (init or\n+\t * clone) and when no .git/ directory was set up yet (in which case\n+\t * git_config_with_options() would already have picked up the\n+\t * repository config), we ask discover_git_directory() to figure out\n+\t * whether there is any repository config we should use (but unlike\n+\t * setup_git_directory_gently(), no global state is changed, most\n+\t * notably, the current working directory is still the same after\n+\t * the call).\n \t */\n-\tif (!have_git_dir()) {\n+\tif (!have_git_dir() && discover_git_directory(&buf)) {\n \t\tstruct git_config_source repo_config;\n \n \t\tmemset(&repo_config, 0, sizeof(repo_config));\n-\t\trepo_config.file = \".git/config\";\n+\t\tstrbuf_addstr(&buf, \"/config\");\n+\t\trepo_config.file = buf.buf;\n \t\tgit_config_with_options(cb, data, &repo_config, 1);\n \t}\n+\tstrbuf_release(&buf);\n }\n \n static void git_config_check_init(void);\ndiff --git a/setup.c b/setup.c\nindex 32ce023638a..5320ae37314 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -925,8 +925,9 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \n const char *discover_git_directory(struct strbuf *gitdir)\n {\n-\tstruct strbuf dir = STRBUF_INIT;\n+\tstruct strbuf dir = STRBUF_INIT, err = STRBUF_INIT;\n \tsize_t gitdir_offset = gitdir->len, cwd_len;\n+\tstruct repository_format candidate;\n \n \tif (strbuf_getcwd(&dir))\n \t\treturn NULL;\n@@ -949,8 +950,19 @@ const char *discover_git_directory(struct strbuf *gitdir)\n \t\t\tstrbuf_addch(&dir, '/');\n \t\tstrbuf_insert(gitdir, gitdir_offset, dir.buf, dir.len);\n \t}\n+\n+\tstrbuf_reset(&dir);\n+\tstrbuf_addf(&dir, \"%s/config\", gitdir->buf + gitdir_offset);\n+\tread_repository_format(&candidate, dir.buf);\n \tstrbuf_release(&dir);\n \n+\tif (verify_repository_format(&candidate, &err) < 0) {\n+\t\twarning(\"ignoring git dir '%s': %s\",\n+\t\t\tgitdir->buf + gitdir_offset, err.buf);\n+\t\tstrbuf_release(&err);\n+\t\treturn NULL;\n+\t}\n+\n \treturn gitdir->buf;\n }\n \ndiff --git a/t/t7006-pager.sh b/t/t7006-pager.sh\nindex 304ae06c600..4f3794d415e 100755\n--- a/t/t7006-pager.sh\n+++ b/t/t7006-pager.sh\n@@ -360,19 +360,19 @@ test_pager_choices                       'git aliasedlog'\n test_default_pager        expect_success 'git -p aliasedlog'\n test_PAGER_overrides      expect_success 'git -p aliasedlog'\n test_core_pager_overrides expect_success 'git -p aliasedlog'\n-test_core_pager_subdir    expect_failure 'git -p aliasedlog'\n+test_core_pager_subdir    expect_success 'git -p aliasedlog'\n test_GIT_PAGER_overrides  expect_success 'git -p aliasedlog'\n \n test_default_pager        expect_success 'git -p true'\n test_PAGER_overrides      expect_success 'git -p true'\n test_core_pager_overrides expect_success 'git -p true'\n-test_core_pager_subdir    expect_failure 'git -p true'\n+test_core_pager_subdir    expect_success 'git -p true'\n test_GIT_PAGER_overrides  expect_success 'git -p true'\n \n test_default_pager        expect_success test_must_fail 'git -p request-pull'\n test_PAGER_overrides      expect_success test_must_fail 'git -p request-pull'\n test_core_pager_overrides expect_success test_must_fail 'git -p request-pull'\n-test_core_pager_subdir    expect_failure test_must_fail 'git -p request-pull'\n+test_core_pager_subdir    expect_success test_must_fail 'git -p request-pull'\n test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p request-pull'\n \n test_default_pager        expect_success test_must_fail 'git -p'\n@@ -380,7 +380,7 @@ test_PAGER_overrides      expect_success test_must_fail 'git -p'\n test_local_config_ignored expect_failure test_must_fail 'git -p'\n test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p'\n \n-test_expect_failure TTY 'core.pager in repo config works and retains cwd' '\n+test_expect_success TTY 'core.pager in repo config works and retains cwd' '\n \tsane_unset GIT_PAGER &&\n \ttest_config core.pager \"cat >cwd-retained\" &&\n \t(\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313239","messageId":"xmqqpohy6o2a.fsf@gitster.mtv.corp.google.com","threadId":"44653","inReplyTo":"cover.1488562287.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v3 0/9] Fix the early config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-03-03T21:35:41Z","receivedAt":"2017-03-03T21:38:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> Notable notes:\n>\n> - In contrast to earlier versions, I no longer special-case init and\n>   clone. Peff pointed out that this adds technical debt, and that we can\n>   actually argue (for consistency's sake) that early config reads the\n>   current repository config (if any) even for init and clone.\n>\n> - The read_early_config() function does not cache Git directory\n>   discovery nor read values. If needed, this can be implemented later,\n>   in a separate patch series.\n>\n> - The alias handling in git.c could possibly benefit from this work, but\n>   again, this is a separate topic from the current patch series.\n\nAs Peff said in his review, I too find the result of this series a\nmore pleasant read than than original.\n\n2/9 and corresponding 4/9 triggers \"ERROR: trailing statements\nshould be on next line\" from ../linux/scripts/checkpatch.pl because\nof a line inherited from the original; I'll queue them with an\nobvious style fix to work it around.\n\nThanks.\n"},{"id":"313263","messageId":"20170304073952.drfgy7jacnlm6tum@sigill.intra.peff.net","threadId":"44653","inReplyTo":"cover.1488562287.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v3 0/9] Fix the early config","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-04T07:39:53Z","receivedAt":"2017-03-04T07:46:47Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Mar 03, 2017 at 06:31:55PM +0100, Johannes Schindelin wrote:\n\n> Interdiff vs v2:\n> [...]\n>  +\t * When we are not about to create a repository ourselves (init or\n>  +\t * clone) and when no .git/ directory was set up yet (in which case\n>  +\t * git_config_with_options() would already have picked up the\n>  +\t * repository config), we ask discover_git_directory() to figure out\n>  +\t * whether there is any repository config we should use (but unlike\n>  +\t * setup_git_directory_gently(), no global state is changed, most\n>  +\t * notably, the current working directory is still the same after\n>  +\t * the call).\n>   \t */\n>  -\tif (!startup_info->creating_repository && !have_git_dir() &&\n>  -\t    discover_git_directory(&buf)) {\n>  +\tif (!have_git_dir() && discover_git_directory(&buf)) {\n\nI think this \"when we are not about to...\" part of the comment is no\nlonger true, given the second part of the hunk.\n\n>  @@ -721,8 +721,10 @@ static const char *setup_discovered_git_dir(const char *gitdir,\n>   \tif (offset == cwd->len)\n>   \t\treturn NULL;\n>   \n>  -\t/* Make \"offset\" point to past the '/', and add a '/' at the end */\n>  -\toffset++;\n>  +\t/* Make \"offset\" point past the '/' (already the case for root dirs) */\n>  +\tif (offset != offset_1st_component(cwd->buf))\n>  +\t\toffset++;\n\nNice. I was worried we would have to have a hacky \"well, sometimes we\ndon't add one here...\" code, but using offset_1st_component says\nexactly what we mean.\n\n> +/* Find GIT_DIR without changing the working directory or other global state */\n>  extern const char *discover_git_directory(struct strbuf *gitdir);\n\nThe parts that actually confused me were the parameters (mostly whether\ngitdir was a directory to start looking in, or an output parameter). So\nmaybe:\n\n  /*\n   * Find GIT_DIR of the repository that contains the current working\n   * directory, without changing the working directory or other global\n   * state. The result is appended to gitdir. The return value is NULL\n   * if no repository was found, or gitdir->buf otherwise.\n   */\n\nThis looks good to me aside from those few comment nits. I'm still not\nsure I understand how ceil_offset works in setup_git_directory_gently_1(),\nbut I don't think your patch actually changed it. I can live with my\nconfusion.\n\n-Peff\n"},{"id":"313288","messageId":"CAPc5daXZ+VUKYtNV4OT+CBsqL3qOGiKQsttFJdby6OrNzMOxbg@mail.gmail.com","threadId":"44653","inReplyTo":"20170304073952.drfgy7jacnlm6tum@sigill.intra.peff.net","subject":"Re: [PATCH v3 0/9] Fix the early config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-03-05T03:36:37Z","receivedAt":"2017-03-05T03:37:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"On Fri, Mar 3, 2017 at 11:39 PM, Jeff King <peff@peff.net> wrote:\n>>  +     * When we are not about to create a repository ourselves (init or\n>>  +     * clone) and when no .git/ directory was set up yet (in which case\n>>  +     * git_config_with_options() would already have picked up the\n>>  +     * repository config), we ask discover_git_directory() to figure out\n>>  +     * whether there is any repository config we should use (but unlike\n>>  +     * setup_git_directory_gently(), no global state is changed, most\n>>  +     * notably, the current working directory is still the same after\n>>  +     * the call).\n>>        */\n>>  -    if (!startup_info->creating_repository && !have_git_dir() &&\n>>  -        discover_git_directory(&buf)) {\n>>  +    if (!have_git_dir() && discover_git_directory(&buf)) {\n>\n> I think this \"when we are not about to...\" part of the comment is no\n> longer true, given the second part of the hunk.\n\nGood point.\n\n> The parts that actually confused me were the parameters (mostly whether\n> gitdir was a directory to start looking in, or an output parameter). So\n> maybe:\n>\n>   /*\n>    * Find GIT_DIR of the repository that contains the current working\n>    * directory, without changing the working directory or other global\n>    * state. The result is appended to gitdir. The return value is NULL\n>    * if no repository was found, or gitdir->buf otherwise.\n>    */\n\nThis, too.\n\n> This looks good to me aside from those few comment nits. I'm still not\n> sure I understand how ceil_offset works in setup_git_directory_gently_1(),\n> but I don't think your patch actually changed it. I can live with my\n> confusion.\n\nI'll wait to see if Dscho wants to clarify the code and comments further.\n\nThanks.\n"},{"id":"313408","messageId":"alpine.DEB.2.20.1703071249220.3767@virtualbox","threadId":"44653","inReplyTo":"xmqqpohy6o2a.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 0/9] Fix the early config","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-07T11:55:35Z","receivedAt":"2017-03-07T11:57:20Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Fri, 3 Mar 2017, Junio C Hamano wrote:\n\n> 2/9 and corresponding 4/9 triggers \"ERROR: trailing statements\n> should be on next line\" from ../linux/scripts/checkpatch.pl because\n> of a line inherited from the original; I'll queue them with an\n> obvious style fix to work it around.\n\nWow, it seems that script requires the entire Linux kernel repository to\nbe cloned, you cannot simply download just the script and run it.\n\nIn any case, as you pointed out, this style was inherited from the\noriginal.\n\nI squashed that style fix in, as you probably would have (your\njs/early-config does not have anything beyond v2.12.0). But I have to\npoint out that it is conflating the purpose of this patch series (its goal\nis *not* to fix any style). I am absolutely not a fan of that.\n\nCiao,\nJohannes\n"},{"id":"313417","messageId":"alpine.DEB.2.20.1703071314180.3767@virtualbox","threadId":"44653","inReplyTo":"20170304073952.drfgy7jacnlm6tum@sigill.intra.peff.net","subject":"Re: [PATCH v3 0/9] Fix the early config","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-07T14:31:43Z","receivedAt":"2017-03-07T14:32:34Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Sat, 4 Mar 2017, Jeff King wrote:\n\n> On Fri, Mar 03, 2017 at 06:31:55PM +0100, Johannes Schindelin wrote:\n> \n> > Interdiff vs v2:\n> > [...]\n> >  +\t * When we are not about to create a repository ourselves (init or\n> >  +\t * clone) and when no .git/ directory was set up yet (in which case\n> >  +\t * git_config_with_options() would already have picked up the\n> >  +\t * repository config), we ask discover_git_directory() to figure out\n> >  +\t * whether there is any repository config we should use (but unlike\n> >  +\t * setup_git_directory_gently(), no global state is changed, most\n> >  +\t * notably, the current working directory is still the same after\n> >  +\t * the call).\n> >   \t */\n> >  -\tif (!startup_info->creating_repository && !have_git_dir() &&\n> >  -\t    discover_git_directory(&buf)) {\n> >  +\tif (!have_git_dir() && discover_git_directory(&buf)) {\n> \n> I think this \"when we are not about to...\" part of the comment is no\n> longer true, given the second part of the hunk.\n\nYep, that was a stale part of that patch. Thanks for noticing!\n\n> >  @@ -721,8 +721,10 @@ static const char *setup_discovered_git_dir(const char *gitdir,\n> >   \tif (offset == cwd->len)\n> >   \t\treturn NULL;\n> >   \n> >  -\t/* Make \"offset\" point to past the '/', and add a '/' at the end */\n> >  -\toffset++;\n> >  +\t/* Make \"offset\" point past the '/' (already the case for root dirs) */\n> >  +\tif (offset != offset_1st_component(cwd->buf))\n> >  +\t\toffset++;\n> \n> Nice. I was worried we would have to have a hacky \"well, sometimes we\n> don't add one here...\" code, but using offset_1st_component says\n> exactly what we mean.\n\nRight. I also wanted to avoid that very, very much. My initial version\nactually tried to detect whether cwd already has a trailing slash, but\nthen I figured that we can be much, much more precise here (and I am\nreally pleased how offset_1st_component() is *semantically* precise, i.e.\nit describes very well what the code is supposed to do here).\n\n> > +/* Find GIT_DIR without changing the working directory or other global state */\n> >  extern const char *discover_git_directory(struct strbuf *gitdir);\n> \n> The parts that actually confused me were the parameters (mostly whether\n> gitdir was a directory to start looking in, or an output parameter). So\n> maybe:\n> \n>   /*\n>    * Find GIT_DIR of the repository that contains the current working\n>    * directory, without changing the working directory or other global\n>    * state. The result is appended to gitdir. The return value is NULL\n>    * if no repository was found, or gitdir->buf otherwise.\n>    */\n\nI changed it a little bit more. In particular, I changed the\ndiscover_git_directory() function to return the pointer to the path\nitself: it provides additional value, and if that is not what the caller\nwants, they can use git_dir->buf just as well.\n\n> This looks good to me aside from those few comment nits.\n\nThanks.\n\nIt is not obvious from the interdiff, but I had an incorrect fixup to 8/9\nthat actually wanted to go to 5/9: the code in\ndiscover_git_repository() tests the repository version should be part of\nthe initial version of this function, of course.\n\nThere is one more thing I included in v4: when I (re-)implemented that\npre-command/post-command hook I was hinting at earlier, the test suite\nidentified a problem where an invalid .git file would prevent even `git\ninit` from working (it was actually much more complicated than that, but\nthe gist is that `git -p init` would fail, no matter how much sense it\nmay make to you to paginate an `init` run, it should still not fail,\nright?). So I added a patch on top to fix that.\n\nAnd another change: the GIT_DIR_NONE value was handled incorrectly in\ndiscover_git_directory().\n\nI am slightly disappointed that the these additional problems were not\nspotted in any review but my own. And I had not even included a Duck.\n\n> I'm still not sure I understand how ceil_offset works in\n> setup_git_directory_gently_1(), but I don't think your patch actually\n> changed it. I can live with my confusion.\n\nYes, that code is very confusing. It also does not help that the naming is\ninconsistent in that it abbreviates \"ceiling\" but not \"offset\". What makes\nit even worse is that the function name `longest_ancestor_length()` is\nhighly misleading: in Git's context, \"ancestor\" of sth pretty much always\nrefers to a commit reachable from sth, but in this context it refers to\nthe path of a directory containing sth.\n\nSo basically, we set ceil_offset to the offset of the last directory\nseparator in our path that corresponds to the most precise match in\nGIT_CEILING_DIRECTORIES.\n\nExample: given GIT_CEILING_DIRECTORIES /foo:/:/bar and a path of /foo/bar,\nceil_offset would be 4, pointing to the slash at the end of /foo/ because\nthat is the most precise match in GIT_CEILING_DIRECTORIES (\"/\" would also\nmatch, but is less precise).\n\nLater, setup_git_directory_gently_1() ensures that it does not go beyond\nceil_offset when looking for the parent directory as the next candidate to\ntest for .git/.\n\nHopefully that clears up the picture?\n\nCiao,\nDscho\n"},{"id":"313418","messageId":"96c2b0be087ee212cbe0aa18cefd2965c09e48fd.1488897111.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488897111.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 01/10] t7006: replace dubious test","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-07T14:32:26Z","receivedAt":"2017-03-07T14:33:13Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The idea of the test case \"git -p - core.pager is not used from\nsubdirectory\" was to verify that the setup_git_directory() function had\nnot been called just to obtain the core.pager setting.\n\nHowever, we are about to fix the early config machinery so that it\n*does* work, without messing up the global state.\n\nOnce that is done, the core.pager setting *will* be used, even when\nrunning from a subdirectory, and that is a Good Thing.\n\nThe intention of that test case, however, was to verify that the\nsetup_git_directory() function has not run, because it changes global\nstate such as the current working directory.\n\nTo keep that spirit, but fix the incorrect assumption, this patch\nreplaces that test case by a new one that verifies that the pager is\nrun in the subdirectory, i.e. that the current working directory has\nnot been changed at the time the pager is configured and launched, even\nif the `rev-parse` command requires a .git/ directory and *will* change\nthe working directory.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t7006-pager.sh | 12 +++++++++++-\n 1 file changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t7006-pager.sh b/t/t7006-pager.sh\nindex c8dc665f2fd..304ae06c600 100755\n--- a/t/t7006-pager.sh\n+++ b/t/t7006-pager.sh\n@@ -378,9 +378,19 @@ test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p request-pull'\n test_default_pager        expect_success test_must_fail 'git -p'\n test_PAGER_overrides      expect_success test_must_fail 'git -p'\n test_local_config_ignored expect_failure test_must_fail 'git -p'\n-test_no_local_config_subdir expect_success test_must_fail 'git -p'\n test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p'\n \n+test_expect_failure TTY 'core.pager in repo config works and retains cwd' '\n+\tsane_unset GIT_PAGER &&\n+\ttest_config core.pager \"cat >cwd-retained\" &&\n+\t(\n+\t\tcd sub &&\n+\t\trm -f cwd-retained &&\n+\t\ttest_terminal git -p rev-parse HEAD &&\n+\t\ttest_path_is_file cwd-retained\n+\t)\n+'\n+\n test_doesnt_paginate      expect_failure test_must_fail 'git -p nonsense'\n \n test_pager_choices                       'git shortlog'\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313419","messageId":"cover.1488897111.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488562287.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 00/10] Fix the early config","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-07T14:32:19Z","receivedAt":"2017-03-07T14:33:27Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"These patches are an attempt to make Git's startup sequence a bit less\nsurprising.\n\nThe idea here is to discover the .git/ directory gently (i.e. without\nchanging the current working directory, nor any global variables), and\nto use it to read the .git/config file early, before we actually called\nsetup_git_directory() (if we ever do that).\n\nThis also allows us to fix the early config e.g. to determine the pager\nor to resolve aliases in a non-surprising manner.\n\nMy own use case: in the GVFS Git fork, we need to execute pre-command\nand post-command hooks before and after *every* Git command. A previous\nversion of the pre-command/post-command hook support was broken, as it\nused run_hook() which implicitly called setup_git_directory() too early.\nThe discover_git_directory() function (and due to core.hooksPath also\nthe read_early_config() function) helped me fix this.\n\nNotable notes:\n\n- Even if it can cause surprising problems, `init` and `clone` are not\n  special-cased. Rationale: it would introduce technical debt and\n  violate the Principle Of Least Astonishment.\n\n- The read_early_config() function does not cache Git directory\n  discovery nor read values. This is left for another patch series, if\n  it ever becomes necessary.\n\n- The alias handling in git.c could possibly benefit from this work, but\n  again, this is a separate topic from the current patch series.\n\nChanges since v3:\n\n- instead of just fixing the `== '/'` comparison, 2/9 now *also* changes\n  the style of the original to something Linux' checkpatch.pl prefers.\n\n- fixed the comment added in 8/9 which was stale from an earlier\n  iteration of this patch series.\n\n- adjusted the commit above discover_git_directory()'s declaration, to\n  make it more understandable.\n\n- touched up the commit message of 5/9 to make the flow more natural.\n\n- moved a fault fixup: by mistake, the repository_format check was added\n  to 8/9 when it really needed to go to 5/9, where the\n  discover_git_directory() function was introduced (I noticed this while\n  rebasing).\n\n- fixed discover_git_directory() when setup_git_directory_gently_1()\n  returned GIT_DIR_NONE (the previous iteration would *not* return NULL\n  in that case).\n\n\nJohannes Schindelin (10):\n  t7006: replace dubious test\n  setup_git_directory(): use is_dir_sep() helper\n  Prepare setup_discovered_git_directory() the root directory\n  setup_git_directory_1(): avoid changing global state\n  Introduce the discover_git_directory() function\n  Make read_early_config() reusable\n  read_early_config(): avoid .git/config hack when unneeded\n  read_early_config(): really discover .git/\n  Test read_early_config()\n  setup_git_directory_gently_1(): avoid die()ing\n\n cache.h                 |   8 ++\n config.c                |  25 +++++\n pager.c                 |  31 ------\n setup.c                 | 246 +++++++++++++++++++++++++++++++++---------------\n t/helper/test-config.c  |  15 +++\n t/t1309-early-config.sh |  50 ++++++++++\n t/t7006-pager.sh        |  18 +++-\n 7 files changed, 282 insertions(+), 111 deletions(-)\n create mode 100755 t/t1309-early-config.sh\n\n\nbase-commit: 3bc53220cb2dcf709f7a027a3f526befd021d858\nPublished-As: https://github.com/dscho/git/releases/tag/early-config-v4\nFetch-It-Via: git fetch https://github.com/dscho/git early-config-v4\n\nInterdiff vs v3:\n\n diff --git a/cache.h b/cache.h\n index 8a4580f921d..e7b57457e73 100644\n --- a/cache.h\n +++ b/cache.h\n @@ -518,7 +518,12 @@ extern void set_git_work_tree(const char *tree);\n  #define ALTERNATE_DB_ENVIRONMENT \"GIT_ALTERNATE_OBJECT_DIRECTORIES\"\n  \n  extern void setup_work_tree(void);\n -/* Find GIT_DIR without changing the working directory or other global state */\n +/*\n + * Find GIT_DIR of the repository that contains the current working directory,\n + * without changing the working directory or other global state. The result is\n + * appended to gitdir. The return value is either NULL if no repository was\n + * found, or pointing to the path inside gitdir's buffer.\n + */\n  extern const char *discover_git_directory(struct strbuf *gitdir);\n  extern const char *setup_git_directory_gently(int *);\n  extern const char *setup_git_directory(void);\n diff --git a/config.c b/config.c\n index 749623a9649..a88df53fdbc 100644\n --- a/config.c\n +++ b/config.c\n @@ -1419,14 +1419,12 @@ void read_early_config(config_fn_t cb, void *data)\n  \tgit_config_with_options(cb, data, NULL, 1);\n  \n  \t/*\n -\t * When we are not about to create a repository ourselves (init or\n -\t * clone) and when no .git/ directory was set up yet (in which case\n -\t * git_config_with_options() would already have picked up the\n -\t * repository config), we ask discover_git_directory() to figure out\n -\t * whether there is any repository config we should use (but unlike\n +\t * When setup_git_directory() was not yet asked to discover the\n +\t * GIT_DIR, we ask discover_git_directory() to figure out whether there\n +\t * is any repository config we should use (but unlike\n  \t * setup_git_directory_gently(), no global state is changed, most\n -\t * notably, the current working directory is still the same after\n -\t * the call).\n +\t * notably, the current working directory is still the same after the\n +\t * call).\n  \t */\n  \tif (!have_git_dir() && discover_git_directory(&buf)) {\n  \t\tstruct git_config_source repo_config;\n diff --git a/setup.c b/setup.c\n index 5320ae37314..9118b48590a 100644\n --- a/setup.c\n +++ b/setup.c\n @@ -825,7 +825,8 @@ enum discovery_result {\n  \tGIT_DIR_BARE,\n  \t/* these are errors */\n  \tGIT_DIR_HIT_CEILING = -1,\n -\tGIT_DIR_HIT_MOUNT_POINT = -2\n +\tGIT_DIR_HIT_MOUNT_POINT = -2,\n +\tGIT_DIR_INVALID_GITFILE = -3\n  };\n  \n  /*\n @@ -842,7 +843,8 @@ enum discovery_result {\n   * `dir` (i.e. *not* necessarily the cwd).\n   */\n  static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n -\t\t\t\t\t\t\t  struct strbuf *gitdir)\n +\t\t\t\t\t\t\t  struct strbuf *gitdir,\n +\t\t\t\t\t\t\t  int die_on_error)\n  {\n  \tconst char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);\n  \tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n @@ -890,14 +892,22 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n  \tif (one_filesystem)\n  \t\tcurrent_device = get_device_or_die(dir->buf, NULL, 0);\n  \tfor (;;) {\n -\t\tint offset = dir->len;\n +\t\tint offset = dir->len, error_code = 0;\n  \n  \t\tif (offset > min_offset)\n  \t\t\tstrbuf_addch(dir, '/');\n  \t\tstrbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);\n -\t\tgitdirenv = read_gitfile(dir->buf);\n -\t\tif (!gitdirenv && is_git_directory(dir->buf))\n -\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n +\t\tgitdirenv = read_gitfile_gently(dir->buf, die_on_error ?\n +\t\t\t\t\t\tNULL : &error_code);\n +\t\tif (!gitdirenv) {\n +\t\t\tif (die_on_error ||\n +\t\t\t    error_code == READ_GITFILE_ERR_NOT_A_FILE) {\n +\t\t\t\tif (is_git_directory(dir->buf))\n +\t\t\t\t    gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n +\t\t\t} else if (error_code &&\n +\t\t\t\t   error_code != READ_GITFILE_ERR_STAT_FAILED)\n +\t\t\t\treturn GIT_DIR_INVALID_GITFILE;\n +\t\t}\n  \t\tstrbuf_setlen(dir, offset);\n  \t\tif (gitdirenv) {\n  \t\t\tstrbuf_addstr(gitdir, gitdirenv);\n @@ -912,7 +922,8 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n  \t\tif (offset <= min_offset)\n  \t\t\treturn GIT_DIR_HIT_CEILING;\n  \n -\t\twhile (--offset > ceil_offset && !is_dir_sep(dir->buf[offset]));\n +\t\twhile (--offset > ceil_offset && !is_dir_sep(dir->buf[offset]))\n +\t\t\t; /* continue */\n  \t\tif (offset <= ceil_offset)\n  \t\t\treturn GIT_DIR_HIT_CEILING;\n  \n @@ -933,7 +944,7 @@ const char *discover_git_directory(struct strbuf *gitdir)\n  \t\treturn NULL;\n  \n  \tcwd_len = dir.len;\n -\tif (setup_git_directory_gently_1(&dir, gitdir) < 0) {\n +\tif (setup_git_directory_gently_1(&dir, gitdir, 0) <= 0) {\n  \t\tstrbuf_release(&dir);\n  \t\treturn NULL;\n  \t}\n @@ -963,7 +974,7 @@ const char *discover_git_directory(struct strbuf *gitdir)\n  \t\treturn NULL;\n  \t}\n  \n -\treturn gitdir->buf;\n +\treturn gitdir->buf + gitdir_offset;\n  }\n  \n  const char *setup_git_directory_gently(int *nongit_ok)\n @@ -992,7 +1003,7 @@ const char *setup_git_directory_gently(int *nongit_ok)\n  \t\tdie_errno(_(\"Unable to read current working directory\"));\n  \tstrbuf_addbuf(&dir, &cwd);\n  \n -\tswitch (setup_git_directory_gently_1(&dir, &gitdir)) {\n +\tswitch (setup_git_directory_gently_1(&dir, &gitdir, 1)) {\n  \tcase GIT_DIR_NONE:\n  \t\tprefix = NULL;\n  \t\tbreak;\n\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n"},{"id":"313420","messageId":"08d8699a735024a4b7e2f859552ae9ae1d3cf0d5.1488897111.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488897111.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 02/10] setup_git_directory(): use is_dir_sep() helper","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-07T14:32:32Z","receivedAt":"2017-03-07T14:33:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It is okay in practice to test for forward slashes in the output of\ngetcwd(), because we go out of our way to convert backslashes to forward\nslashes in getcwd()'s output on Windows.\n\nStill, the correct way to test for a dir separator is by using the\nhelper function we introduced for that very purpose. It also serves as a\ngood documentation what the code tries to do (not \"how\").\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/setup.c b/setup.c\nindex 967f289f1ef..4a15b105676 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -910,7 +910,9 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t\t\treturn setup_bare_git_dir(&cwd, offset, nongit_ok);\n \n \t\toffset_parent = offset;\n-\t\twhile (--offset_parent > ceil_offset && cwd.buf[offset_parent] != '/');\n+\t\twhile (--offset_parent > ceil_offset &&\n+\t\t       !is_dir_sep(cwd.buf[offset_parent]))\n+\t\t\t; /* continue */\n \t\tif (offset_parent <= ceil_offset)\n \t\t\treturn setup_nongit(cwd.buf, nongit_ok);\n \t\tif (one_filesystem) {\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313421","messageId":"44b547dd1e7cc57c8612ca12771cca75d5f6c4a0.1488897111.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488897111.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 03/10] Prepare setup_discovered_git_directory() the root directory","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-07T14:32:40Z","receivedAt":"2017-03-07T14:33:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Currently, the offset parameter (indicating what part of the cwd\nparameter corresponds to the current directory after discovering the\n.git/ directory) is set to 0 when we are running in the root directory.\n\nHowever, in the next patches we will avoid changing the current working\ndirectory while searching for the .git/ directory, meaning that the\noffset corresponding to the root directory will have to be 1 to reflect\nthat this directory is characterized by the path \"/\" (and not \"\").\n\nSo let's make sure that setup_discovered_git_directory() only tries to\nappend the trailing slash to non-root directories.\n\nNote: the setup_bare_git_directory() does not need a corresponding\nchange, as it does not want to return a prefix.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 4a15b105676..20a1f0f870e 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -721,8 +721,10 @@ static const char *setup_discovered_git_dir(const char *gitdir,\n \tif (offset == cwd->len)\n \t\treturn NULL;\n \n-\t/* Make \"offset\" point to past the '/', and add a '/' at the end */\n-\toffset++;\n+\t/* Make \"offset\" point past the '/' (already the case for root dirs) */\n+\tif (offset != offset_1st_component(cwd->buf))\n+\t\toffset++;\n+\t/* Add a '/' at the end */\n \tstrbuf_addch(cwd, '/');\n \treturn cwd->buf + offset;\n }\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313422","messageId":"2c8ab22700fb40c9e4e9b46f4981b45db7f2dcf2.1488897111.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488897111.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 04/10] setup_git_directory_1(): avoid changing global state","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-07T14:32:56Z","receivedAt":"2017-03-07T14:33:32Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"For historical reasons, Git searches for the .git/ directory (or the\n.git file) by changing the working directory successively to the parent\ndirectory of the current directory, until either anything was found or\nuntil a ceiling or a mount point is hit.\n\nFurther global state may be changed in case a .git/ directory was found.\n\nWe do have a use case, though, where we would like to find the .git/\ndirectory without having any global state touched, though: when we read\nthe early config e.g. for the pager or for alias expansion.\n\nLet's just move all of code that changes any global state out of the\nfunction `setup_git_directory_gently_1()` into\n`setup_git_directory_gently()`.\n\nIn subsequent patches, we will use the _1() function in a new\n`discover_git_directory()` function that we will then use for the early\nconfig code.\n\nNote: the new loop is a *little* tricky, as we have to handle the root\ndirectory specially: we cannot simply strip away the last component\nincluding the slash, as the root directory only has that slash. To remedy\nthat, we introduce the `min_offset` variable that holds the minimal length\nof an absolute path, and using that to special-case the root directory,\nincluding an early exit before trying to find the parent of the root\ndirectory.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 187 ++++++++++++++++++++++++++++++++++++++--------------------------\n 1 file changed, 112 insertions(+), 75 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 20a1f0f870e..d7af343d14e 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -818,50 +818,49 @@ static int canonicalize_ceiling_entry(struct string_list_item *item,\n \t}\n }\n \n+enum discovery_result {\n+\tGIT_DIR_NONE = 0,\n+\tGIT_DIR_EXPLICIT,\n+\tGIT_DIR_DISCOVERED,\n+\tGIT_DIR_BARE,\n+\t/* these are errors */\n+\tGIT_DIR_HIT_CEILING = -1,\n+\tGIT_DIR_HIT_MOUNT_POINT = -2\n+};\n+\n /*\n  * We cannot decide in this function whether we are in the work tree or\n  * not, since the config can only be read _after_ this function was called.\n+ *\n+ * Also, we avoid changing any global state (such as the current working\n+ * directory) to allow early callers.\n+ *\n+ * The directory where the search should start needs to be passed in via the\n+ * `dir` parameter; upon return, the `dir` buffer will contain the path of\n+ * the directory where the search ended, and `gitdir` will contain the path of\n+ * the discovered .git/ directory, if any. This path may be relative against\n+ * `dir` (i.e. *not* necessarily the cwd).\n  */\n-static const char *setup_git_directory_gently_1(int *nongit_ok)\n+static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n+\t\t\t\t\t\t\t  struct strbuf *gitdir)\n {\n \tconst char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);\n \tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n-\tstatic struct strbuf cwd = STRBUF_INIT;\n-\tconst char *gitdirenv, *ret;\n-\tchar *gitfile;\n-\tint offset, offset_parent, ceil_offset = -1;\n+\tconst char *gitdirenv;\n+\tint ceil_offset = -1, min_offset = has_dos_drive_prefix(dir->buf) ? 3 : 1;\n \tdev_t current_device = 0;\n \tint one_filesystem = 1;\n \n \t/*\n-\t * We may have read an incomplete configuration before\n-\t * setting-up the git directory. If so, clear the cache so\n-\t * that the next queries to the configuration reload complete\n-\t * configuration (including the per-repo config file that we\n-\t * ignored previously).\n-\t */\n-\tgit_config_clear();\n-\n-\t/*\n-\t * Let's assume that we are in a git repository.\n-\t * If it turns out later that we are somewhere else, the value will be\n-\t * updated accordingly.\n-\t */\n-\tif (nongit_ok)\n-\t\t*nongit_ok = 0;\n-\n-\tif (strbuf_getcwd(&cwd))\n-\t\tdie_errno(_(\"Unable to read current working directory\"));\n-\toffset = cwd.len;\n-\n-\t/*\n \t * If GIT_DIR is set explicitly, we're not going\n \t * to do any discovery, but we still do repository\n \t * validation.\n \t */\n \tgitdirenv = getenv(GIT_DIR_ENVIRONMENT);\n-\tif (gitdirenv)\n-\t\treturn setup_explicit_git_dir(gitdirenv, &cwd, nongit_ok);\n+\tif (gitdirenv) {\n+\t\tstrbuf_addstr(gitdir, gitdirenv);\n+\t\treturn GIT_DIR_EXPLICIT;\n+\t}\n \n \tif (env_ceiling_dirs) {\n \t\tint empty_entry_found = 0;\n@@ -869,15 +868,15 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);\n \t\tfilter_string_list(&ceiling_dirs, 0,\n \t\t\t\t   canonicalize_ceiling_entry, &empty_entry_found);\n-\t\tceil_offset = longest_ancestor_length(cwd.buf, &ceiling_dirs);\n+\t\tceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs);\n \t\tstring_list_clear(&ceiling_dirs, 0);\n \t}\n \n-\tif (ceil_offset < 0 && has_dos_drive_prefix(cwd.buf))\n-\t\tceil_offset = 1;\n+\tif (ceil_offset < 0)\n+\t\tceil_offset = min_offset - 2;\n \n \t/*\n-\t * Test in the following order (relative to the cwd):\n+\t * Test in the following order (relative to the dir):\n \t * - .git (file containing \"gitdir: <path>\")\n \t * - .git/\n \t * - ./ (bare)\n@@ -889,63 +888,101 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t */\n \tone_filesystem = !git_env_bool(\"GIT_DISCOVERY_ACROSS_FILESYSTEM\", 0);\n \tif (one_filesystem)\n-\t\tcurrent_device = get_device_or_die(\".\", NULL, 0);\n+\t\tcurrent_device = get_device_or_die(dir->buf, NULL, 0);\n \tfor (;;) {\n-\t\tgitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);\n-\t\tif (gitfile)\n-\t\t\tgitdirenv = gitfile = xstrdup(gitfile);\n-\t\telse {\n-\t\t\tif (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))\n-\t\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n+\t\tint offset = dir->len;\n+\n+\t\tif (offset > min_offset)\n+\t\t\tstrbuf_addch(dir, '/');\n+\t\tstrbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);\n+\t\tgitdirenv = read_gitfile(dir->buf);\n+\t\tif (!gitdirenv && is_git_directory(dir->buf))\n+\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n+\t\tstrbuf_setlen(dir, offset);\n+\t\tif (gitdirenv) {\n+\t\t\tstrbuf_addstr(gitdir, gitdirenv);\n+\t\t\treturn GIT_DIR_DISCOVERED;\n \t\t}\n \n-\t\tif (gitdirenv) {\n-\t\t\tret = setup_discovered_git_dir(gitdirenv,\n-\t\t\t\t\t\t       &cwd, offset,\n-\t\t\t\t\t\t       nongit_ok);\n-\t\t\tfree(gitfile);\n-\t\t\treturn ret;\n+\t\tif (is_git_directory(dir->buf)) {\n+\t\t\tstrbuf_addstr(gitdir, \".\");\n+\t\t\treturn GIT_DIR_BARE;\n \t\t}\n-\t\tfree(gitfile);\n \n-\t\tif (is_git_directory(\".\"))\n-\t\t\treturn setup_bare_git_dir(&cwd, offset, nongit_ok);\n+\t\tif (offset <= min_offset)\n+\t\t\treturn GIT_DIR_HIT_CEILING;\n \n-\t\toffset_parent = offset;\n-\t\twhile (--offset_parent > ceil_offset &&\n-\t\t       !is_dir_sep(cwd.buf[offset_parent]))\n+\t\twhile (--offset > ceil_offset && !is_dir_sep(dir->buf[offset]))\n \t\t\t; /* continue */\n-\t\tif (offset_parent <= ceil_offset)\n-\t\t\treturn setup_nongit(cwd.buf, nongit_ok);\n-\t\tif (one_filesystem) {\n-\t\t\tdev_t parent_device = get_device_or_die(\"..\", cwd.buf,\n-\t\t\t\t\t\t\t\toffset);\n-\t\t\tif (parent_device != current_device) {\n-\t\t\t\tif (nongit_ok) {\n-\t\t\t\t\tif (chdir(cwd.buf))\n-\t\t\t\t\t\tdie_errno(_(\"Cannot come back to cwd\"));\n-\t\t\t\t\t*nongit_ok = 1;\n-\t\t\t\t\treturn NULL;\n-\t\t\t\t}\n-\t\t\t\tstrbuf_setlen(&cwd, offset);\n-\t\t\t\tdie(_(\"Not a git repository (or any parent up to mount point %s)\\n\"\n-\t\t\t\t\"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\"),\n-\t\t\t\t    cwd.buf);\n-\t\t\t}\n-\t\t}\n-\t\tif (chdir(\"..\")) {\n-\t\t\tstrbuf_setlen(&cwd, offset);\n-\t\t\tdie_errno(_(\"Cannot change to '%s/..'\"), cwd.buf);\n-\t\t}\n-\t\toffset = offset_parent;\n+\t\tif (offset <= ceil_offset)\n+\t\t\treturn GIT_DIR_HIT_CEILING;\n+\n+\t\tstrbuf_setlen(dir, offset > min_offset ?  offset : min_offset);\n+\t\tif (one_filesystem &&\n+\t\t    current_device != get_device_or_die(dir->buf, NULL, offset))\n+\t\t\treturn GIT_DIR_HIT_MOUNT_POINT;\n \t}\n }\n \n const char *setup_git_directory_gently(int *nongit_ok)\n {\n+\tstruct strbuf cwd = STRBUF_INIT, dir = STRBUF_INIT, gitdir = STRBUF_INIT;\n \tconst char *prefix;\n \n-\tprefix = setup_git_directory_gently_1(nongit_ok);\n+\t/*\n+\t * We may have read an incomplete configuration before\n+\t * setting-up the git directory. If so, clear the cache so\n+\t * that the next queries to the configuration reload complete\n+\t * configuration (including the per-repo config file that we\n+\t * ignored previously).\n+\t */\n+\tgit_config_clear();\n+\n+\t/*\n+\t * Let's assume that we are in a git repository.\n+\t * If it turns out later that we are somewhere else, the value will be\n+\t * updated accordingly.\n+\t */\n+\tif (nongit_ok)\n+\t\t*nongit_ok = 0;\n+\n+\tif (strbuf_getcwd(&cwd))\n+\t\tdie_errno(_(\"Unable to read current working directory\"));\n+\tstrbuf_addbuf(&dir, &cwd);\n+\n+\tswitch (setup_git_directory_gently_1(&dir, &gitdir)) {\n+\tcase GIT_DIR_NONE:\n+\t\tprefix = NULL;\n+\t\tbreak;\n+\tcase GIT_DIR_EXPLICIT:\n+\t\tprefix = setup_explicit_git_dir(gitdir.buf, &cwd, nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_DISCOVERED:\n+\t\tif (dir.len < cwd.len && chdir(dir.buf))\n+\t\t\tdie(_(\"Cannot change to '%s'\"), dir.buf);\n+\t\tprefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len,\n+\t\t\t\t\t\t  nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_BARE:\n+\t\tif (dir.len < cwd.len && chdir(dir.buf))\n+\t\t\tdie(_(\"Cannot change to '%s'\"), dir.buf);\n+\t\tprefix = setup_bare_git_dir(&cwd, dir.len, nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_HIT_CEILING:\n+\t\tprefix = setup_nongit(cwd.buf, nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_HIT_MOUNT_POINT:\n+\t\tif (nongit_ok) {\n+\t\t\t*nongit_ok = 1;\n+\t\t\treturn NULL;\n+\t\t}\n+\t\tdie(_(\"Not a git repository (or any parent up to mount point %s)\\n\"\n+\t\t      \"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\"),\n+\t\t    dir.buf);\n+\tdefault:\n+\t\tdie(\"BUG: unhandled setup_git_directory_1() result\");\n+\t}\n+\n \tif (prefix)\n \t\tsetenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);\n \telse\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313423","messageId":"b73714db4555626e6c4ca3f2771683f1fa9d7312.1488897111.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488897111.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 05/10] Introduce the discover_git_directory() function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-07T14:33:02Z","receivedAt":"2017-03-07T14:33:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"We modified the setup_git_directory_gently_1() function earlier to make\nit possible to discover the GIT_DIR without changing global state.\n\nHowever, it is still a bit cumbersome to use if you only need to figure\nout the (possibly absolute) path of the .git/ directory. Let's just\nprovide a convenient wrapper function with an easier signature that\n*just* discovers the .git/ directory.\n\nWe will use it in a subsequent patch to fix the early config.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n cache.h |  7 +++++++\n setup.c | 43 +++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 50 insertions(+)\n\ndiff --git a/cache.h b/cache.h\nindex 80b6372cf76..5218726cf88 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -518,6 +518,13 @@ extern void set_git_work_tree(const char *tree);\n #define ALTERNATE_DB_ENVIRONMENT \"GIT_ALTERNATE_OBJECT_DIRECTORIES\"\n \n extern void setup_work_tree(void);\n+/*\n+ * Find GIT_DIR of the repository that contains the current working directory,\n+ * without changing the working directory or other global state. The result is\n+ * appended to gitdir. The return value is either NULL if no repository was\n+ * found, or pointing to the path inside gitdir's buffer.\n+ */\n+extern const char *discover_git_directory(struct strbuf *gitdir);\n extern const char *setup_git_directory_gently(int *);\n extern const char *setup_git_directory(void);\n extern char *prefix_path(const char *prefix, int len, const char *path);\ndiff --git a/setup.c b/setup.c\nindex d7af343d14e..486acda2054 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -924,6 +924,49 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \t}\n }\n \n+const char *discover_git_directory(struct strbuf *gitdir)\n+{\n+\tstruct strbuf dir = STRBUF_INIT, err = STRBUF_INIT;\n+\tsize_t gitdir_offset = gitdir->len, cwd_len;\n+\tstruct repository_format candidate;\n+\n+\tif (strbuf_getcwd(&dir))\n+\t\treturn NULL;\n+\n+\tcwd_len = dir.len;\n+\tif (setup_git_directory_gently_1(&dir, gitdir) <= 0) {\n+\t\tstrbuf_release(&dir);\n+\t\treturn NULL;\n+\t}\n+\n+\t/*\n+\t * The returned gitdir is relative to dir, and if dir does not reflect\n+\t * the current working directory, we simply make the gitdir absolute.\n+\t */\n+\tif (dir.len < cwd_len && !is_absolute_path(gitdir->buf + gitdir_offset)) {\n+\t\t/* Avoid a trailing \"/.\" */\n+\t\tif (!strcmp(\".\", gitdir->buf + gitdir_offset))\n+\t\t\tstrbuf_setlen(gitdir, gitdir_offset);\n+\t\telse\n+\t\t\tstrbuf_addch(&dir, '/');\n+\t\tstrbuf_insert(gitdir, gitdir_offset, dir.buf, dir.len);\n+\t}\n+\n+\tstrbuf_reset(&dir);\n+\tstrbuf_addf(&dir, \"%s/config\", gitdir->buf + gitdir_offset);\n+\tread_repository_format(&candidate, dir.buf);\n+\tstrbuf_release(&dir);\n+\n+\tif (verify_repository_format(&candidate, &err) < 0) {\n+\t\twarning(\"ignoring git dir '%s': %s\",\n+\t\t\tgitdir->buf + gitdir_offset, err.buf);\n+\t\tstrbuf_release(&err);\n+\t\treturn NULL;\n+\t}\n+\n+\treturn gitdir->buf + gitdir_offset;\n+}\n+\n const char *setup_git_directory_gently(int *nongit_ok)\n {\n \tstruct strbuf cwd = STRBUF_INIT, dir = STRBUF_INIT, gitdir = STRBUF_INIT;\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313424","messageId":"7377f3f8c2c0c008dbff6b08603b91593ac9cc79.1488897111.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488897111.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 06/10] Make read_early_config() reusable","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-07T14:33:07Z","receivedAt":"2017-03-07T14:33:39Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The pager configuration needs to be read early, possibly before\ndiscovering any .git/ directory.\n\nLet's not hide this function in pager.c, but make it available to other\ncallers.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n cache.h  |  1 +\n config.c | 31 +++++++++++++++++++++++++++++++\n pager.c  | 31 -------------------------------\n 3 files changed, 32 insertions(+), 31 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 5218726cf88..e7b57457e73 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1804,6 +1804,7 @@ extern int git_config_from_blob_sha1(config_fn_t fn, const char *name,\n \t\t\t\t     const unsigned char *sha1, void *data);\n extern void git_config_push_parameter(const char *text);\n extern int git_config_from_parameters(config_fn_t fn, void *data);\n+extern void read_early_config(config_fn_t cb, void *data);\n extern void git_config(config_fn_t fn, void *);\n extern int git_config_with_options(config_fn_t fn, void *,\n \t\t\t\t   struct git_config_source *config_source,\ndiff --git a/config.c b/config.c\nindex c6b874a7bf7..9cfbeafd04c 100644\n--- a/config.c\n+++ b/config.c\n@@ -1412,6 +1412,37 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \t}\n }\n \n+void read_early_config(config_fn_t cb, void *data)\n+{\n+\tgit_config_with_options(cb, data, NULL, 1);\n+\n+\t/*\n+\t * Note that this is a really dirty hack that does the wrong thing in\n+\t * many cases. The crux of the problem is that we cannot run\n+\t * setup_git_directory() early on in git's setup, so we have no idea if\n+\t * we are in a repository or not, and therefore are not sure whether\n+\t * and how to read repository-local config.\n+\t *\n+\t * So if we _aren't_ in a repository (or we are but we would reject its\n+\t * core.repositoryformatversion), we'll read whatever is in .git/config\n+\t * blindly. Similarly, if we _are_ in a repository, but not at the\n+\t * root, we'll fail to find .git/config (because it's really\n+\t * ../.git/config, etc). See t7006 for a complete set of failures.\n+\t *\n+\t * However, we have historically provided this hack because it does\n+\t * work some of the time (namely when you are at the top-level of a\n+\t * valid repository), and would rarely make things worse (i.e., you do\n+\t * not generally have a .git/config file sitting around).\n+\t */\n+\tif (!startup_info->have_repository) {\n+\t\tstruct git_config_source repo_config;\n+\n+\t\tmemset(&repo_config, 0, sizeof(repo_config));\n+\t\trepo_config.file = \".git/config\";\n+\t\tgit_config_with_options(cb, data, &repo_config, 1);\n+\t}\n+}\n+\n static void git_config_check_init(void);\n \n void git_config(config_fn_t fn, void *data)\ndiff --git a/pager.c b/pager.c\nindex ae796433630..73ca8bc3b17 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -43,37 +43,6 @@ static int core_pager_config(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n-static void read_early_config(config_fn_t cb, void *data)\n-{\n-\tgit_config_with_options(cb, data, NULL, 1);\n-\n-\t/*\n-\t * Note that this is a really dirty hack that does the wrong thing in\n-\t * many cases. The crux of the problem is that we cannot run\n-\t * setup_git_directory() early on in git's setup, so we have no idea if\n-\t * we are in a repository or not, and therefore are not sure whether\n-\t * and how to read repository-local config.\n-\t *\n-\t * So if we _aren't_ in a repository (or we are but we would reject its\n-\t * core.repositoryformatversion), we'll read whatever is in .git/config\n-\t * blindly. Similarly, if we _are_ in a repository, but not at the\n-\t * root, we'll fail to find .git/config (because it's really\n-\t * ../.git/config, etc). See t7006 for a complete set of failures.\n-\t *\n-\t * However, we have historically provided this hack because it does\n-\t * work some of the time (namely when you are at the top-level of a\n-\t * valid repository), and would rarely make things worse (i.e., you do\n-\t * not generally have a .git/config file sitting around).\n-\t */\n-\tif (!startup_info->have_repository) {\n-\t\tstruct git_config_source repo_config;\n-\n-\t\tmemset(&repo_config, 0, sizeof(repo_config));\n-\t\trepo_config.file = \".git/config\";\n-\t\tgit_config_with_options(cb, data, &repo_config, 1);\n-\t}\n-}\n-\n const char *git_pager(int stdout_is_tty)\n {\n \tconst char *pager;\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313425","messageId":"9b87d8b78e3b94b6e269eb8db2f76a68ea102f6d.1488897111.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488897111.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 08/10] read_early_config(): really discover .git/","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-07T14:33:15Z","receivedAt":"2017-03-07T14:35:14Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Earlier, we punted and simply assumed that we are in the top-level\ndirectory of the project, and that there is no .git file but a .git/\ndirectory so that we can read directly from .git/config.\n\nHowever, that is not necessarily true. We may be in a subdirectory. Or\n.git may be a gitfile. Or the environment variable GIT_DIR may be set.\n\nTo remedy this situation, we just refactored the way\nsetup_git_directory() discovers the .git/ directory, to make it\nreusable, and more importantly, to leave all global variables and the\ncurrent working directory alone.\n\nLet's discover the .git/ directory correctly in read_early_config() by\nusing that new function.\n\nThis fixes 4 known breakages in t7006.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n config.c         | 31 ++++++++++++-------------------\n t/t7006-pager.sh |  8 ++++----\n 2 files changed, 16 insertions(+), 23 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 068fa4dcfa6..a88df53fdbc 100644\n--- a/config.c\n+++ b/config.c\n@@ -1414,34 +1414,27 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \n void read_early_config(config_fn_t cb, void *data)\n {\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n \tgit_config_with_options(cb, data, NULL, 1);\n \n \t/*\n-\t * Note that this is a really dirty hack that does the wrong thing in\n-\t * many cases. The crux of the problem is that we cannot run\n-\t * setup_git_directory() early on in git's setup, so we have no idea if\n-\t * we are in a repository or not, and therefore are not sure whether\n-\t * and how to read repository-local config.\n-\t *\n-\t * So if we _aren't_ in a repository (or we are but we would reject its\n-\t * core.repositoryformatversion), we'll read whatever is in .git/config\n-\t * blindly. Similarly, if we _are_ in a repository, but not at the\n-\t * root, we'll fail to find .git/config (because it's really\n-\t * ../.git/config, etc), unless setup_git_directory() was already called.\n-\t * See t7006 for a complete set of failures.\n-\t *\n-\t * However, we have historically provided this hack because it does\n-\t * work some of the time (namely when you are at the top-level of a\n-\t * valid repository), and would rarely make things worse (i.e., you do\n-\t * not generally have a .git/config file sitting around).\n+\t * When setup_git_directory() was not yet asked to discover the\n+\t * GIT_DIR, we ask discover_git_directory() to figure out whether there\n+\t * is any repository config we should use (but unlike\n+\t * setup_git_directory_gently(), no global state is changed, most\n+\t * notably, the current working directory is still the same after the\n+\t * call).\n \t */\n-\tif (!have_git_dir()) {\n+\tif (!have_git_dir() && discover_git_directory(&buf)) {\n \t\tstruct git_config_source repo_config;\n \n \t\tmemset(&repo_config, 0, sizeof(repo_config));\n-\t\trepo_config.file = \".git/config\";\n+\t\tstrbuf_addstr(&buf, \"/config\");\n+\t\trepo_config.file = buf.buf;\n \t\tgit_config_with_options(cb, data, &repo_config, 1);\n \t}\n+\tstrbuf_release(&buf);\n }\n \n static void git_config_check_init(void);\ndiff --git a/t/t7006-pager.sh b/t/t7006-pager.sh\nindex 304ae06c600..4f3794d415e 100755\n--- a/t/t7006-pager.sh\n+++ b/t/t7006-pager.sh\n@@ -360,19 +360,19 @@ test_pager_choices                       'git aliasedlog'\n test_default_pager        expect_success 'git -p aliasedlog'\n test_PAGER_overrides      expect_success 'git -p aliasedlog'\n test_core_pager_overrides expect_success 'git -p aliasedlog'\n-test_core_pager_subdir    expect_failure 'git -p aliasedlog'\n+test_core_pager_subdir    expect_success 'git -p aliasedlog'\n test_GIT_PAGER_overrides  expect_success 'git -p aliasedlog'\n \n test_default_pager        expect_success 'git -p true'\n test_PAGER_overrides      expect_success 'git -p true'\n test_core_pager_overrides expect_success 'git -p true'\n-test_core_pager_subdir    expect_failure 'git -p true'\n+test_core_pager_subdir    expect_success 'git -p true'\n test_GIT_PAGER_overrides  expect_success 'git -p true'\n \n test_default_pager        expect_success test_must_fail 'git -p request-pull'\n test_PAGER_overrides      expect_success test_must_fail 'git -p request-pull'\n test_core_pager_overrides expect_success test_must_fail 'git -p request-pull'\n-test_core_pager_subdir    expect_failure test_must_fail 'git -p request-pull'\n+test_core_pager_subdir    expect_success test_must_fail 'git -p request-pull'\n test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p request-pull'\n \n test_default_pager        expect_success test_must_fail 'git -p'\n@@ -380,7 +380,7 @@ test_PAGER_overrides      expect_success test_must_fail 'git -p'\n test_local_config_ignored expect_failure test_must_fail 'git -p'\n test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p'\n \n-test_expect_failure TTY 'core.pager in repo config works and retains cwd' '\n+test_expect_success TTY 'core.pager in repo config works and retains cwd' '\n \tsane_unset GIT_PAGER &&\n \ttest_config core.pager \"cat >cwd-retained\" &&\n \t(\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313426","messageId":"642c42bb97c3fc4943aaec0fc77173f415d246bc.1488897111.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488897111.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 10/10] setup_git_directory_gently_1(): avoid die()ing","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-07T14:33:30Z","receivedAt":"2017-03-07T14:40:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This function now has a new caller in addition to setup_git_directory():\nthe newly introduced discover_git_directory(). That function wants to\ndiscover the current .git/ directory, and in case of a corrupted one\nsimply pretend that there is none to be found.\n\nExample: if a stale .git file exists in the parent directory, and the\nuser calls `git -p init`, we want Git to simply *not* read any\nrepository config for the pager (instead of aborting with a message that\nthe .git file is corrupt).\n\nLet's actually pretend that there was no GIT_DIR to be found in that case\nwhen being called from discover_git_directory(), but keep the previous\nbehavior (i.e. to die()) for the setup_git_directory() case.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 26 ++++++++++++++++++--------\n 1 file changed, 18 insertions(+), 8 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 486acda2054..9118b48590a 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -825,7 +825,8 @@ enum discovery_result {\n \tGIT_DIR_BARE,\n \t/* these are errors */\n \tGIT_DIR_HIT_CEILING = -1,\n-\tGIT_DIR_HIT_MOUNT_POINT = -2\n+\tGIT_DIR_HIT_MOUNT_POINT = -2,\n+\tGIT_DIR_INVALID_GITFILE = -3\n };\n \n /*\n@@ -842,7 +843,8 @@ enum discovery_result {\n  * `dir` (i.e. *not* necessarily the cwd).\n  */\n static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n-\t\t\t\t\t\t\t  struct strbuf *gitdir)\n+\t\t\t\t\t\t\t  struct strbuf *gitdir,\n+\t\t\t\t\t\t\t  int die_on_error)\n {\n \tconst char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);\n \tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n@@ -890,14 +892,22 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \tif (one_filesystem)\n \t\tcurrent_device = get_device_or_die(dir->buf, NULL, 0);\n \tfor (;;) {\n-\t\tint offset = dir->len;\n+\t\tint offset = dir->len, error_code = 0;\n \n \t\tif (offset > min_offset)\n \t\t\tstrbuf_addch(dir, '/');\n \t\tstrbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);\n-\t\tgitdirenv = read_gitfile(dir->buf);\n-\t\tif (!gitdirenv && is_git_directory(dir->buf))\n-\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n+\t\tgitdirenv = read_gitfile_gently(dir->buf, die_on_error ?\n+\t\t\t\t\t\tNULL : &error_code);\n+\t\tif (!gitdirenv) {\n+\t\t\tif (die_on_error ||\n+\t\t\t    error_code == READ_GITFILE_ERR_NOT_A_FILE) {\n+\t\t\t\tif (is_git_directory(dir->buf))\n+\t\t\t\t    gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n+\t\t\t} else if (error_code &&\n+\t\t\t\t   error_code != READ_GITFILE_ERR_STAT_FAILED)\n+\t\t\t\treturn GIT_DIR_INVALID_GITFILE;\n+\t\t}\n \t\tstrbuf_setlen(dir, offset);\n \t\tif (gitdirenv) {\n \t\t\tstrbuf_addstr(gitdir, gitdirenv);\n@@ -934,7 +944,7 @@ const char *discover_git_directory(struct strbuf *gitdir)\n \t\treturn NULL;\n \n \tcwd_len = dir.len;\n-\tif (setup_git_directory_gently_1(&dir, gitdir) <= 0) {\n+\tif (setup_git_directory_gently_1(&dir, gitdir, 0) <= 0) {\n \t\tstrbuf_release(&dir);\n \t\treturn NULL;\n \t}\n@@ -993,7 +1003,7 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \t\tdie_errno(_(\"Unable to read current working directory\"));\n \tstrbuf_addbuf(&dir, &cwd);\n \n-\tswitch (setup_git_directory_gently_1(&dir, &gitdir)) {\n+\tswitch (setup_git_directory_gently_1(&dir, &gitdir, 1)) {\n \tcase GIT_DIR_NONE:\n \t\tprefix = NULL;\n \t\tbreak;\n-- \n2.12.0.windows.1.7.g94dafc3b124\n"},{"id":"313428","messageId":"a2d57d9cc9b2ef8c9cd0e1f8313b23dd07a52109.1488897111.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488897111.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 09/10] Test read_early_config()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-07T14:33:25Z","receivedAt":"2017-03-07T15:06:33Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"So far, we had no explicit tests of that function.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/helper/test-config.c  | 15 +++++++++++++++\n t/t1309-early-config.sh | 50 +++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 65 insertions(+)\n create mode 100755 t/t1309-early-config.sh\n\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 83a4f2ab869..8e3ed6a76cb 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -66,6 +66,16 @@ static int iterate_cb(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n+static int early_config_cb(const char *var, const char *value, void *vdata)\n+{\n+\tconst char *key = vdata;\n+\n+\tif (!strcmp(key, var))\n+\t\tprintf(\"%s\\n\", value);\n+\n+\treturn 0;\n+}\n+\n int cmd_main(int argc, const char **argv)\n {\n \tint i, val;\n@@ -73,6 +83,11 @@ int cmd_main(int argc, const char **argv)\n \tconst struct string_list *strptr;\n \tstruct config_set cs;\n \n+\tif (argc == 3 && !strcmp(argv[1], \"read_early_config\")) {\n+\t\tread_early_config(early_config_cb, (void *)argv[2]);\n+\t\treturn 0;\n+\t}\n+\n \tsetup_git_directory();\n \n \tgit_configset_init(&cs);\ndiff --git a/t/t1309-early-config.sh b/t/t1309-early-config.sh\nnew file mode 100755\nindex 00000000000..0c55dee514c\n--- /dev/null\n+++ b/t/t1309-early-config.sh\n@@ -0,0 +1,50 @@\n+#!/bin/sh\n+\n+test_description='Test read_early_config()'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'read early config' '\n+\ttest_config early.config correct &&\n+\ttest-config read_early_config early.config >output &&\n+\ttest correct = \"$(cat output)\"\n+'\n+\n+test_expect_success 'in a sub-directory' '\n+\ttest_config early.config sub &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest sub = \"$(cat output)\"\n+'\n+\n+test_expect_success 'ceiling' '\n+\ttest_config early.config ceiling &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tGIT_CEILING_DIRECTORIES=\"$PWD\" &&\n+\t\texport GIT_CEILING_DIRECTORIES &&\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest -z \"$(cat output)\"\n+'\n+\n+test_expect_success 'ceiling #2' '\n+\tmkdir -p xdg/git &&\n+\tgit config -f xdg/git/config early.config xdg &&\n+\ttest_config early.config ceiling &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tXDG_CONFIG_HOME=\"$PWD\"/xdg &&\n+\t\tGIT_CEILING_DIRECTORIES=\"$PWD\" &&\n+\t\texport GIT_CEILING_DIRECTORIES XDG_CONFIG_HOME &&\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest xdg = \"$(cat output)\"\n+'\n+\n+test_done\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313429","messageId":"84b39810f2c12a4774f566f857c5014ac65adae8.1488897111.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488897111.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 07/10] read_early_config(): avoid .git/config hack when unneeded","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-07T14:33:11Z","receivedAt":"2017-03-07T15:06:47Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"So far, we only look whether the startup_info claims to have seen a\ngit_dir.\n\nHowever, do_git_config_sequence() (and consequently the\ngit_config_with_options() call used by read_early_config() asks the\nhave_git_dir() function whether we have a .git/ directory, which in turn\nalso looks at git_dir and at the environment variable GIT_DIR. And when\nthis is the case, the repository config is handled already, so we do not\nhave to do that again explicitly.\n\nLet's just use the same function, have_git_dir(), to determine whether we\nhave to handle .git/config explicitly.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n config.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 9cfbeafd04c..068fa4dcfa6 100644\n--- a/config.c\n+++ b/config.c\n@@ -1427,14 +1427,15 @@ void read_early_config(config_fn_t cb, void *data)\n \t * core.repositoryformatversion), we'll read whatever is in .git/config\n \t * blindly. Similarly, if we _are_ in a repository, but not at the\n \t * root, we'll fail to find .git/config (because it's really\n-\t * ../.git/config, etc). See t7006 for a complete set of failures.\n+\t * ../.git/config, etc), unless setup_git_directory() was already called.\n+\t * See t7006 for a complete set of failures.\n \t *\n \t * However, we have historically provided this hack because it does\n \t * work some of the time (namely when you are at the top-level of a\n \t * valid repository), and would rarely make things worse (i.e., you do\n \t * not generally have a .git/config file sitting around).\n \t */\n-\tif (!startup_info->have_repository) {\n+\tif (!have_git_dir()) {\n \t\tstruct git_config_source repo_config;\n \n \t\tmemset(&repo_config, 0, sizeof(repo_config));\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313430","messageId":"alpine.DEB.2.20.1703061803230.3767@virtualbox","threadId":"44653","inReplyTo":"xmqqpohy6o2a.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 0/9] Fix the early config","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-07T15:18:59Z","receivedAt":"2017-03-07T15:22:24Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Fri, 3 Mar 2017, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > Notable notes:\n> >\n> > - In contrast to earlier versions, I no longer special-case init and\n> >   clone. Peff pointed out that this adds technical debt, and that we can\n> >   actually argue (for consistency's sake) that early config reads the\n> >   current repository config (if any) even for init and clone.\n> >\n> > - The read_early_config() function does not cache Git directory\n> >   discovery nor read values. If needed, this can be implemented later,\n> >   in a separate patch series.\n> >\n> > - The alias handling in git.c could possibly benefit from this work, but\n> >   again, this is a separate topic from the current patch series.\n> \n> As Peff said in his review, I too find the result of this series a\n> more pleasant read than than original.\n\nAs do I.\n\n> 2/9 and corresponding 4/9 triggers \"ERROR: trailing statements\n> should be on next line\" from ../linux/scripts/checkpatch.pl because\n> of a line inherited from the original; I'll queue them with an\n> obvious style fix to work it around.\n\nThank you. I'll try to pick it up for v3 (which is needed, as I found\nanother issue that needs to be fixed).\n\nCiao,\nJohannes\n"},{"id":"313463","messageId":"xmqqtw744qmm.fsf@gitster.mtv.corp.google.com","threadId":"44653","inReplyTo":"2c8ab22700fb40c9e4e9b46f4981b45db7f2dcf2.1488897111.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v4 04/10] setup_git_directory_1(): avoid changing global state","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-03-07T23:24:33Z","receivedAt":"2017-03-07T23:29:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> +\tswitch (setup_git_directory_gently_1(&dir, &gitdir)) {\n> +\tcase GIT_DIR_NONE:\n> +\t\tprefix = NULL;\n> +\t\tbreak;\n> +\tcase GIT_DIR_EXPLICIT:\n> +\t\tprefix = setup_explicit_git_dir(gitdir.buf, &cwd, nongit_ok);\n> +\t\tbreak;\n> +\tcase GIT_DIR_DISCOVERED:\n> +\t\tif (dir.len < cwd.len && chdir(dir.buf))\n> +\t\t\tdie(_(\"Cannot change to '%s'\"), dir.buf);\n> +\t\tprefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len,\n> +\t\t\t\t\t\t  nongit_ok);\n> +\t\tbreak;\n> +\tcase GIT_DIR_BARE:\n> +\t\tif (dir.len < cwd.len && chdir(dir.buf))\n> +\t\t\tdie(_(\"Cannot change to '%s'\"), dir.buf);\n> +\t\tprefix = setup_bare_git_dir(&cwd, dir.len, nongit_ok);\n> +\t\tbreak;\n> +\tcase GIT_DIR_HIT_CEILING:\n> +\t\tprefix = setup_nongit(cwd.buf, nongit_ok);\n> +\t\tbreak;\n> +\tcase GIT_DIR_HIT_MOUNT_POINT:\n> +\t\tif (nongit_ok) {\n> +\t\t\t*nongit_ok = 1;\n> +\t\t\treturn NULL;\n> +\t\t}\n> +\t\tdie(_(\"Not a git repository (or any parent up to mount point %s)\\n\"\n> +\t\t      \"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\"),\n> +\t\t    dir.buf);\n> +\tdefault:\n> +\t\tdie(\"BUG: unhandled setup_git_directory_1() result\");\n> +\t}\n\n\nI _might_ find niggles in other patches (and other parts of this\npatch) that enables the above clean implementation, but this\nswitch() statement speaks of the value of this entire series ;-)\n\nVery nicely done.\n"},{"id":"313465","messageId":"20170307233549.GA128647@google.com","threadId":"44653","inReplyTo":"2c8ab22700fb40c9e4e9b46f4981b45db7f2dcf2.1488897111.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v4 04/10] setup_git_directory_1(): avoid changing global state","fromName":"Brandon Williams","fromEmail":"bmwill@google.com","sentAt":"2017-03-07T23:35:49Z","receivedAt":"2017-03-07T23:43:35Z","isPatch":true,"sender":{"key":"bwilliams.eng@gmail.com","avatar":null},"body":"On 03/07, Johannes Schindelin wrote:\n>  const char *setup_git_directory_gently(int *nongit_ok)\n>  {\n> +\tstruct strbuf cwd = STRBUF_INIT, dir = STRBUF_INIT, gitdir = STRBUF_INIT;\n\nI couldn't see any strbuf_release() calls for these strbufs so there may\nbe some memory leaking here.\n\n>  \tconst char *prefix;\n>  \n> -\tprefix = setup_git_directory_gently_1(nongit_ok);\n> +\t/*\n> +\t * We may have read an incomplete configuration before\n> +\t * setting-up the git directory. If so, clear the cache so\n> +\t * that the next queries to the configuration reload complete\n> +\t * configuration (including the per-repo config file that we\n> +\t * ignored previously).\n> +\t */\n> +\tgit_config_clear();\n> +\n> +\t/*\n> +\t * Let's assume that we are in a git repository.\n> +\t * If it turns out later that we are somewhere else, the value will be\n> +\t * updated accordingly.\n> +\t */\n> +\tif (nongit_ok)\n> +\t\t*nongit_ok = 0;\n> +\n> +\tif (strbuf_getcwd(&cwd))\n> +\t\tdie_errno(_(\"Unable to read current working directory\"));\n> +\tstrbuf_addbuf(&dir, &cwd);\n> +\n> +\tswitch (setup_git_directory_gently_1(&dir, &gitdir)) {\n> +\tcase GIT_DIR_NONE:\n> +\t\tprefix = NULL;\n> +\t\tbreak;\n> +\tcase GIT_DIR_EXPLICIT:\n> +\t\tprefix = setup_explicit_git_dir(gitdir.buf, &cwd, nongit_ok);\n> +\t\tbreak;\n> +\tcase GIT_DIR_DISCOVERED:\n> +\t\tif (dir.len < cwd.len && chdir(dir.buf))\n> +\t\t\tdie(_(\"Cannot change to '%s'\"), dir.buf);\n> +\t\tprefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len,\n> +\t\t\t\t\t\t  nongit_ok);\n> +\t\tbreak;\n> +\tcase GIT_DIR_BARE:\n> +\t\tif (dir.len < cwd.len && chdir(dir.buf))\n> +\t\t\tdie(_(\"Cannot change to '%s'\"), dir.buf);\n> +\t\tprefix = setup_bare_git_dir(&cwd, dir.len, nongit_ok);\n> +\t\tbreak;\n> +\tcase GIT_DIR_HIT_CEILING:\n> +\t\tprefix = setup_nongit(cwd.buf, nongit_ok);\n> +\t\tbreak;\n> +\tcase GIT_DIR_HIT_MOUNT_POINT:\n> +\t\tif (nongit_ok) {\n> +\t\t\t*nongit_ok = 1;\n> +\t\t\treturn NULL;\n> +\t\t}\n> +\t\tdie(_(\"Not a git repository (or any parent up to mount point %s)\\n\"\n> +\t\t      \"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\"),\n> +\t\t    dir.buf);\n> +\tdefault:\n> +\t\tdie(\"BUG: unhandled setup_git_directory_1() result\");\n> +\t}\n> +\n>  \tif (prefix)\n>  \t\tsetenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);\n>  \telse\n> -- \n> 2.12.0.windows.1.7.g94dafc3b124\n> \n> \n\n-- \nBrandon Williams\n"},{"id":"313471","messageId":"alpine.DEB.2.20.1703080156040.3767@virtualbox","threadId":"44653","inReplyTo":"20170307233549.GA128647@google.com","subject":"Re: [PATCH v4 04/10] setup_git_directory_1(): avoid changing global state","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-08T00:57:05Z","receivedAt":"2017-03-08T00:58:28Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Brandon,\n\nOn Tue, 7 Mar 2017, Brandon Williams wrote:\n\n> On 03/07, Johannes Schindelin wrote:\n> >  const char *setup_git_directory_gently(int *nongit_ok)\n> >  {\n> > +\tstruct strbuf cwd = STRBUF_INIT, dir = STRBUF_INIT, gitdir = STRBUF_INIT;\n> \n> I couldn't see any strbuf_release() calls for these strbufs so there may\n> be some memory leaking here.\n\nYou are correct, of course. Something like this may work:\n\n-- snipsnap --\ndiff --git a/setup.c b/setup.c\nindex 9118b48590a..c822582b96e 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1027,6 +1027,8 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \tcase GIT_DIR_HIT_MOUNT_POINT:\n \t\tif (nongit_ok) {\n \t\t\t*nongit_ok = 1;\n+\t\t\tstrbuf_release(&cwd);\n+\t\t\tstrbuf_release(&dir);\n \t\t\treturn NULL;\n \t\t}\n \t\tdie(_(\"Not a git repository (or any parent up to mount point %s)\\n\"\n@@ -1044,6 +1046,10 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \tstartup_info->have_repository = !nongit_ok || !*nongit_ok;\n \tstartup_info->prefix = prefix;\n \n+\tstrbuf_release(&cwd);\n+\tstrbuf_release(&dir);\n+\tstrbuf_release(&gitdir);\n+\n \treturn prefix;\n }\n \n"},{"id":"313479","messageId":"20170308021044.GB1650@google.com","threadId":"44653","inReplyTo":"alpine.DEB.2.20.1703080156040.3767@virtualbox","subject":"Re: [PATCH v4 04/10] setup_git_directory_1(): avoid changing global state","fromName":"Brandon Williams","fromEmail":"bmwill@google.com","sentAt":"2017-03-08T02:10:44Z","receivedAt":"2017-03-08T02:18:07Z","isPatch":true,"sender":{"key":"bwilliams.eng@gmail.com","avatar":null},"body":"On 03/08, Johannes Schindelin wrote:\n> Hi Brandon,\n> \n> On Tue, 7 Mar 2017, Brandon Williams wrote:\n> \n> > On 03/07, Johannes Schindelin wrote:\n> > >  const char *setup_git_directory_gently(int *nongit_ok)\n> > >  {\n> > > +\tstruct strbuf cwd = STRBUF_INIT, dir = STRBUF_INIT, gitdir = STRBUF_INIT;\n> > \n> > I couldn't see any strbuf_release() calls for these strbufs so there may\n> > be some memory leaking here.\n> \n> You are correct, of course. Something like this may work:\n\nYep that should fix it!\n\n> \n> -- snipsnap --\n> diff --git a/setup.c b/setup.c\n> index 9118b48590a..c822582b96e 100644\n> --- a/setup.c\n> +++ b/setup.c\n> @@ -1027,6 +1027,8 @@ const char *setup_git_directory_gently(int *nongit_ok)\n>  \tcase GIT_DIR_HIT_MOUNT_POINT:\n>  \t\tif (nongit_ok) {\n>  \t\t\t*nongit_ok = 1;\n> +\t\t\tstrbuf_release(&cwd);\n> +\t\t\tstrbuf_release(&dir);\n>  \t\t\treturn NULL;\n>  \t\t}\n>  \t\tdie(_(\"Not a git repository (or any parent up to mount point %s)\\n\"\n> @@ -1044,6 +1046,10 @@ const char *setup_git_directory_gently(int *nongit_ok)\n>  \tstartup_info->have_repository = !nongit_ok || !*nongit_ok;\n>  \tstartup_info->prefix = prefix;\n>  \n> +\tstrbuf_release(&cwd);\n> +\tstrbuf_release(&dir);\n> +\tstrbuf_release(&gitdir);\n> +\n>  \treturn prefix;\n>  }\n>  \n\n-- \nBrandon Williams\n"},{"id":"313493","messageId":"20170308073035.pt5ysp6bzpozoqps@sigill.intra.peff.net","threadId":"44653","inReplyTo":"alpine.DEB.2.20.1703071314180.3767@virtualbox","subject":"Re: [PATCH v3 0/9] Fix the early config","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-08T07:30:35Z","receivedAt":"2017-03-08T12:57:24Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Mar 07, 2017 at 03:31:43PM +0100, Johannes Schindelin wrote:\n\n> >   /*\n> >    * Find GIT_DIR of the repository that contains the current working\n> >    * directory, without changing the working directory or other global\n> >    * state. The result is appended to gitdir. The return value is NULL\n> >    * if no repository was found, or gitdir->buf otherwise.\n> >    */\n> \n> I changed it a little bit more. In particular, I changed the\n> discover_git_directory() function to return the pointer to the path\n> itself: it provides additional value, and if that is not what the caller\n> wants, they can use git_dir->buf just as well.\n\nYes, that makes much more sense.\n\n> There is one more thing I included in v4: when I (re-)implemented that\n> pre-command/post-command hook I was hinting at earlier, the test suite\n> identified a problem where an invalid .git file would prevent even `git\n> init` from working (it was actually much more complicated than that, but\n> the gist is that `git -p init` would fail, no matter how much sense it\n> may make to you to paginate an `init` run, it should still not fail,\n> right?). So I added a patch on top to fix that.\n\nGood catch. Another \"non-gentle\" thing I noticed here while looking at\nanother thread: the repository-format version check uses the config\nparser, which will die() in certain circumstances. So for instance:\n\n  $ git init\n  $ git rev-parse && echo ok\n  ok\n\n  $ echo '[core]repositoryformatversion = 10' >.git/config\n  $ git rev-parse && echo ok\n  fatal: Expected git repo version <= 1, found 10\n\n  $ echo '[core]repositoryformatversion = foobar' >.git/config\n  $ git rev-parse && echo ok\n  fatal: bad numeric config value 'foobar' for 'core.repositoryformatversion' in file .git/config: invalid unit\n\n  $ echo '[co' >.git/config\n  $ git rev-parse && echo ok\n  fatal: bad config line 1 in file .git/config\n\nYour series correctly avoids the first failure by calling the\nread/verify_repository_format functions correctly. But I think it would\nget tripped up by the other two.\n\nFixing the first one is probably not too hard; check_repo_format()\nshould use a more forgiving parser than git_config_int().\n\nThe second one I thought would be tricky, but it looks like we added a\ndie_on_error flag in b2dc09455. That does what we want, but it needs to\nbe plumbed through to git_config_from_file().\n\n> And another change: the GIT_DIR_NONE value was handled incorrectly in\n> discover_git_directory().\n\nThis is the \"if (setup_git_directory_1() <= 0)\" change from the\ninterdiff? That's subtle. The compiler could have noticed if we used a\nswitch statement here. But then any new error conditions would have to\nbe added to that switch statement.\n\n> I am slightly disappointed that the these additional problems were not\n> spotted in any review but my own. And I had not even included a Duck.\n\nGet used to being disappointed, I guess. A non-zero number of bugs will\nslip through when writing code _and_ when reviewing it.\n\n> [ceil_offset]\n> Hopefully that clears up the picture?\n\nYes, it does. Thanks.\n\n-Peff\n"},{"id":"313508","messageId":"alpine.DEB.2.20.1703081701100.3767@virtualbox","threadId":"44653","inReplyTo":"20170308073035.pt5ysp6bzpozoqps@sigill.intra.peff.net","subject":"Re: [PATCH v3 0/9] Fix the early config","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-08T16:18:46Z","receivedAt":"2017-03-08T16:28:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Peff,\n\nOn Wed, 8 Mar 2017, Jeff King wrote:\n\n> Another \"non-gentle\" thing I noticed here while looking at\n> another thread: the repository-format version check uses the config\n> parser, which will die() in certain circumstances. [...]\n\nYes, that is part of the reason why I was not eager to add that check to\ndiscover_git_directory(). The config code is die()-happy.\n\nThis is a much bigger problem, of course, and related to a constant gripe\nof mine: I *always* get the quoting wrong in my aliases. Always. And when\nI want to fix it, `git config -e` simply errors out because of an invalid\nconfig. Yes, Git, I know, that is the exact reason why I want to edit the\nconfig in the first place.\n\nI am certain you will agree that this is a different topic, therefore\nsubject to a separate patch series.\n\nIn any case, I am fairly certain that the examples you showed demonstrate\nthat the config has to be rather broken for this patch series to have a\nnegative impact. And it still would report the broken config so that the\nuser is not blocked (she can fix the config and call the paginating\ncommand again).\n\n> On Tue, Mar 07, 2017 at 03:31:43PM +0100, Johannes Schindelin wrote:\n> \n> > And another change: the GIT_DIR_NONE value was handled incorrectly in\n> > discover_git_directory().\n> \n> This is the \"if (setup_git_directory_1() <= 0)\" change from the\n> interdiff? That's subtle.\n\nYes, it is subtle.\n\n> The compiler could have noticed if we used a switch statement here. But\n> then any new error conditions would have to be added to that switch\n> statement.\n\nWe could still do that.\n\n> > I am slightly disappointed that the these additional problems were not\n> > spotted in any review but my own. And I had not even included a Duck.\n> \n> Get used to being disappointed, I guess. A non-zero number of bugs will\n> slip through when writing code _and_ when reviewing it.\n\nI know that. I know that bugs are prone to come in through code\ncontributions. I don't go for 100%. But I would hope for a better rate\nthan we have right now: we pride ourselves in the OSS community to make bugs\nshallow. I really would like to believe that we catch bugs rather than\ndiscuss formatting (which should be automated) or white-space mangling\n(which should not even be a problem if we were truly open for\ncontributions).\n\nMaybe I am just a grumpy old guy. But I *hate* seeing how much buggy code\nwe get into Git, despite having a review process that does a very good job\nof deterring seasoned developers from contributing.\n\nI really wish it were different. I really wish that we did a better job at\ncatching bugs before they enter `master`. I really wish that I could be\nproud of our code review process.\n\n> > [ceil_offset]\n> > Hopefully that clears up the picture?\n> \n> Yes, it does. Thanks.\n\nPerfect. Then the time I spent trying to figure all of this out was not\nspent in vain.\n\nCiao,\nDscho\n"},{"id":"313510","messageId":"xmqqy3wf3dbo.fsf@gitster.mtv.corp.google.com","threadId":"44653","inReplyTo":"20170308073035.pt5ysp6bzpozoqps@sigill.intra.peff.net","subject":"Re: [PATCH v3 0/9] Fix the early config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-03-08T17:09:31Z","receivedAt":"2017-03-08T17:24:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> Good catch. Another \"non-gentle\" thing I noticed here while looking at\n> another thread: the repository-format version check uses the config\n> parser, which will die() in certain circumstances. So for instance:\n>\n>   $ git init\n>   $ git rev-parse && echo ok\n>   ok\n>\n>   $ echo '[core]repositoryformatversion = 10' >.git/config\n>   $ git rev-parse && echo ok\n>   fatal: Expected git repo version <= 1, found 10\n\nJust to set my expectation straight.  Do you expect/wish this not to\nfail because of this in cmd_rev_parse()?\n\n\t/* No options; just report on whether we're in a git repo or not. */\n\tif (argc == 1) {\n\t\tsetup_git_directory();\n\t\tgit_config(git_default_config, NULL);\n\t\treturn 0;\n\t}\n\nBecause we do not have anything other than yes/no to the question\n\"Are we in Git repository?\", I'd expect that the expected answer to\nthe question would be \"no\" (if we could say \"Yes, we are in a Git\nrepository but its version and layout is unknown to us so we are not\nsupposed to look at or touch it\", that is a different matter).\n\nSo \"fatal:\" may be bad, but I think not seeing \"ok\" is what we\nwant to happen in this case.\n\nHaving said that, I am not sure asking for default-config is what we\nwanted to do in the above code.  Perhaps a more modern way to write\nthe above code would be to do the \"gently\" version of setup, without\ncalling git_config() ourselves, and return the resulting value\nreturned in *nongit_ok?  If we can do so without triggering \"fatal:\"\nand still return \"no, we are not in a Git repository we are supposed\nto touch\", that would be good.\n\nOr are you discussing a more general issue, iow, anything that can\nwork without repository (i.e. those who do _gently version of the\nsetup and act on *nongit_ok) should pretend as if there were no\n(broken) repository and take the \"no we are not in a repository\"\ncodepath?\n"},{"id":"313525","messageId":"20170308174237.cm6e5uvve6hu7lpf@sigill.intra.peff.net","threadId":"44653","inReplyTo":"xmqqy3wf3dbo.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 0/9] Fix the early config","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-08T17:42:37Z","receivedAt":"2017-03-08T17:49:31Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Mar 08, 2017 at 09:09:31AM -0800, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > Good catch. Another \"non-gentle\" thing I noticed here while looking at\n> > another thread: the repository-format version check uses the config\n> > parser, which will die() in certain circumstances. So for instance:\n> >\n> >   $ git init\n> >   $ git rev-parse && echo ok\n> >   ok\n> >\n> >   $ echo '[core]repositoryformatversion = 10' >.git/config\n> >   $ git rev-parse && echo ok\n> >   fatal: Expected git repo version <= 1, found 10\n> \n> Just to set my expectation straight.  Do you expect/wish this not to\n> fail because of this in cmd_rev_parse()?\n\nNo, I was just using \"rev-parse\" as a sample command that tried to do\nrepo setup. I meant the above snippet that you quoted to both be fine\nand expected outputs. The problem is the _other_ two cases where the\nconfig code dies before we even get to the version-number check.\n\n> Or are you discussing a more general issue, iow, anything that can\n> work without repository (i.e. those who do _gently version of the\n> setup and act on *nongit_ok) should pretend as if there were no\n> (broken) repository and take the \"no we are not in a repository\"\n> codepath?\n\nYes, exactly.  It would have been less confusing if I picked something\nthat passed nongit_ok. Like hash-object:\n\n  $ git init\n  $ echo content >file\n  $ git hash-object file\n  d95f3ad14dee633a758d2e331151e950dd13e4ed\n\n  $ echo '[core]repositoryformatversion = 10' >.git/config\n  $ git hash-object file\n  warning: Expected git repo version <= 1, found 10\n  d95f3ad14dee633a758d2e331151e950dd13e4ed\n\nThe warning is fine and reasonable here. But then:\n\n  $ echo '[core]repositoryformatversion = foobar' >.git/config\n  $ git hash-object file\n  fatal: bad numeric config value 'foobar' for 'core.repositoryformatversion' in file .git/config: invalid unit\n\nThat's wrong. We're supposed to be gentle. And ditto:\n\n  $ echo '[co' >.git/config\n  $ git hash-object file\n  fatal: bad config line 1 in file .git/config\n\nThose last two should issue a warning at most, and then let the command\ncontinue.\n\n-Peff\n"},{"id":"313529","messageId":"20170308162926.mcjuterphnctfmrp@sigill.intra.peff.net","threadId":"44653","inReplyTo":"alpine.DEB.2.20.1703081701100.3767@virtualbox","subject":"Re: [PATCH v3 0/9] Fix the early config","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-08T16:29:26Z","receivedAt":"2017-03-08T18:16:48Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Mar 08, 2017 at 05:18:46PM +0100, Johannes Schindelin wrote:\n\n> On Wed, 8 Mar 2017, Jeff King wrote:\n> \n> > Another \"non-gentle\" thing I noticed here while looking at\n> > another thread: the repository-format version check uses the config\n> > parser, which will die() in certain circumstances. [...]\n> \n> Yes, that is part of the reason why I was not eager to add that check to\n> discover_git_directory(). The config code is die()-happy.\n> \n> This is a much bigger problem, of course, and related to a constant gripe\n> of mine: I *always* get the quoting wrong in my aliases. Always. And when\n> I want to fix it, `git config -e` simply errors out because of an invalid\n> config. Yes, Git, I know, that is the exact reason why I want to edit the\n> config in the first place.\n> \n> I am certain you will agree that this is a different topic, therefore\n> subject to a separate patch series.\n\nI agree that in general it is a separate issue. Technically it could\ncause a regression because with your series we are more eager to call\ndiscover_git_directory() even when the command would not otherwise need\nto even look at the current git directory.\n\nBut I don't think that is the case in practice. Your new function is\nonly called when we would try to read the config anyway. So it would\nonly affect cases which were previously so broken they did not properly\nread the config (like running a command without RUN_SETUP from a\nsubdirectory of the working tree, which was _supposed_ to respect the\nconfig but failed to do so).\n\nSo yeah, I'm happy to leave it for now.\n\n-Peff\n"},{"id":"313568","messageId":"xmqqinnj1jb6.fsf@gitster.mtv.corp.google.com","threadId":"44653","inReplyTo":"20170308174237.cm6e5uvve6hu7lpf@sigill.intra.peff.net","subject":"Re: [PATCH v3 0/9] Fix the early config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-03-08T22:43:09Z","receivedAt":"2017-03-08T22:43:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n>> Or are you discussing a more general issue, iow, anything that can\n>> work without repository (i.e. those who do _gently version of the\n>> setup and act on *nongit_ok) should pretend as if there were no\n>> (broken) repository and take the \"no we are not in a repository\"\n>> codepath?\n>\n> Yes, exactly.  It would have been less confusing if I picked something\n> that passed nongit_ok. Like hash-object:\n>\n>   $ git init\n>   $ echo content >file\n>   $ git hash-object file\n>   d95f3ad14dee633a758d2e331151e950dd13e4ed\n>\n>   $ echo '[core]repositoryformatversion = 10' >.git/config\n>   $ git hash-object file\n>   warning: Expected git repo version <= 1, found 10\n>   d95f3ad14dee633a758d2e331151e950dd13e4ed\n>\n> The warning is fine and reasonable here. But then:\n>\n>   $ echo '[core]repositoryformatversion = foobar' >.git/config\n>   $ git hash-object file\n>   fatal: bad numeric config value 'foobar' for 'core.repositoryformatversion' in file .git/config: invalid unit\n>\n> That's wrong. We're supposed to be gentle. And ditto:\n>\n>   $ echo '[co' >.git/config\n>   $ git hash-object file\n>   fatal: bad config line 1 in file .git/config\n>\n> Those last two should issue a warning at most, and then let the command\n> continue.\n\nYeah, I agree with that as one of the worthy goals.  IIUC, we\ndecided to leave that outside of this series and later fix on top,\nwhich is fine by me, too.\n\n"},{"id":"313634","messageId":"alpine.DEB.2.20.1703091243260.3767@virtualbox","threadId":"44653","inReplyTo":"xmqqinnj1jb6.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 0/9] Fix the early config","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-09T11:51:06Z","receivedAt":"2017-03-09T11:52:56Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Wed, 8 Mar 2017, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> >> Or are you discussing a more general issue, iow, anything that can\n> >> work without repository (i.e. those who do _gently version of the\n> >> setup and act on *nongit_ok) should pretend as if there were no\n> >> (broken) repository and take the \"no we are not in a repository\"\n> >> codepath?\n> >\n> > Yes, exactly.  It would have been less confusing if I picked something\n> > that passed nongit_ok. Like hash-object:\n\n... or like testing the early config directly?\n\n> >   $ git init\n> >   $ echo content >file\n> >   $ git hash-object file\n> >   d95f3ad14dee633a758d2e331151e950dd13e4ed\n> >\n> >   $ echo '[core]repositoryformatversion = 10' >.git/config\n> >   $ git hash-object file\n> >   warning: Expected git repo version <= 1, found 10\n> >   d95f3ad14dee633a758d2e331151e950dd13e4ed\n> >\n> > The warning is fine and reasonable here. But then:\n> >\n> >   $ echo '[core]repositoryformatversion = foobar' >.git/config\n> >   $ git hash-object file\n> >   fatal: bad numeric config value 'foobar' for 'core.repositoryformatversion' in file .git/config: invalid unit\n> >\n> > That's wrong. We're supposed to be gentle. And ditto:\n> >\n> >   $ echo '[co' >.git/config\n> >   $ git hash-object file\n> >   fatal: bad config line 1 in file .git/config\n> >\n> > Those last two should issue a warning at most, and then let the command\n> > continue.\n> \n> Yeah, I agree with that as one of the worthy goals.  IIUC, we\n> decided to leave that outside of this series and later fix on top,\n> which is fine by me, too.\n\nHow about this on top, then:\n\n-- snipsnap --\nFrom: Johannes Schindelin <johannes.schindelin@gmx.de>\nSubject: [PATCH] t1309: document cases where we would want early config not to\n die()\n\nJeff King came up with a couple examples that demonstrate how the new\nread_early_config() that looks harder for the current .git/ directory\ncould die() in an undesirable way.\n\nLet's add those cases to the test script, to document what we would like\nto happen when early config encounters problems.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t1309-early-config.sh | 25 +++++++++++++++++++++++++\n 1 file changed, 25 insertions(+)\n\ndiff --git a/t/t1309-early-config.sh b/t/t1309-early-config.sh\nindex 0c55dee514c..027eca63a3c 100755\n--- a/t/t1309-early-config.sh\n+++ b/t/t1309-early-config.sh\n@@ -47,4 +47,29 @@ test_expect_success 'ceiling #2' '\n \ttest xdg = \"$(cat output)\"\n '\n \n+test_with_config ()\n+{\n+\trm -rf throwaway &&\n+\tgit init throwaway &&\n+\t(\n+\t\tcd throwaway &&\n+\t\techo \"$*\" >.git/config &&\n+\t\ttest-config read_early_config early.config\n+\t)\n+}\n+\n+test_expect_success 'ignore .git/ with incompatible repository version' '\n+\ttest_with_config \"[core]repositoryformatversion = 999999\" 2>err &&\n+\tgrep \"warning:.* Expected git repo version <= [1-9]\" err\n+'\n+\n+test_expect_failure 'ignore .git/ with invalid repository version' '\n+\ttest_with_config \"[core]repositoryformatversion = invalid\"\n+'\n+\n+\n+test_expect_failure 'ignore .git/ with invalid config' '\n+\ttest_with_config \"[\"\n+'\n+\n test_done\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n"},{"id":"313636","messageId":"20170309121620.rld6scydqru72nbk@sigill.intra.peff.net","threadId":"44653","inReplyTo":"alpine.DEB.2.20.1703091243260.3767@virtualbox","subject":"Re: [PATCH v3 0/9] Fix the early config","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-09T12:16:20Z","receivedAt":"2017-03-09T12:17:19Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Mar 09, 2017 at 12:51:06PM +0100, Johannes Schindelin wrote:\n\n> On Wed, 8 Mar 2017, Junio C Hamano wrote:\n> \n> > Jeff King <peff@peff.net> writes:\n> > \n> > >> Or are you discussing a more general issue, iow, anything that can\n> > >> work without repository (i.e. those who do _gently version of the\n> > >> setup and act on *nongit_ok) should pretend as if there were no\n> > >> (broken) repository and take the \"no we are not in a repository\"\n> > >> codepath?\n> > >\n> > > Yes, exactly.  It would have been less confusing if I picked something\n> > > that passed nongit_ok. Like hash-object:\n> \n> ... or like testing the early config directly?\n\nI was trying to demonstrate that the problem existed already without\nyour patch series.\n\n> -- snipsnap --\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> Subject: [PATCH] t1309: document cases where we would want early config not to\n>  die()\n> \n> Jeff King came up with a couple examples that demonstrate how the new\n> read_early_config() that looks harder for the current .git/ directory\n> could die() in an undesirable way.\n> \n> Let's add those cases to the test script, to document what we would like\n> to happen when early config encounters problems.\n\nYep, these all look fine.\n\n-Peff\n"},{"id":"313707","messageId":"cover.1489098170.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1488897111.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 00/11] Fix the early config","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-09T22:23:44Z","receivedAt":"2017-03-09T22:24:07Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"These patches are an attempt to make Git's startup sequence a bit less\nsurprising.\n\nThe idea here is to discover the .git/ directory gently (i.e. without\nchanging the current working directory, nor any global variables), and\nto use it to read the .git/config file early, before we actually called\nsetup_git_directory() (if we ever do that).\n\nThis also allows us to fix the early config e.g. to determine the pager\nor to resolve aliases in a non-surprising manner.\n\nMy own use case: in the GVFS Git fork, we need to execute pre-command\nand post-command hooks before and after *every* Git command. A previous\nversion of the pre-command/post-command hook support was broken, as it\nused run_hook() which implicitly called setup_git_directory() too early.\nThe discover_git_directory() function (and due to core.hooksPath also\nthe read_early_config() function) helped me fix this.\n\nNotable notes:\n\n- Even if it can cause surprising problems, `init` and `clone` are not\n  special-cased. Rationale: it would introduce technical debt and\n  violate the Principle Of Least Astonishment.\n\n- The read_early_config() function does not cache Git directory\n  discovery nor read values. This is left for another patch series, if\n  it ever becomes necessary.\n\n- The alias handling in git.c could possibly benefit from this work, but\n  again, this is a separate topic from the current patch series.\n\nChanges since v4:\n\n- plugged memory leaks in setup_git_directory() (thanks, Brandon)\n\n- added some tests that demonstrate how early config currently can fail\n  in undesired ways\n\n- fixed indentation (this change was snuck into gitster/git's\n  js/early-config branch, and I happened to notice the difference by\n  pure random chance)\n\n\nJohannes Schindelin (11):\n  t7006: replace dubious test\n  setup_git_directory(): use is_dir_sep() helper\n  Prepare setup_discovered_git_directory() the root directory\n  setup_git_directory_1(): avoid changing global state\n  Introduce the discover_git_directory() function\n  Make read_early_config() reusable\n  read_early_config(): avoid .git/config hack when unneeded\n  read_early_config(): really discover .git/\n  Test read_early_config()\n  setup_git_directory_gently_1(): avoid die()ing\n  t1309: document cases where we would want early config not to die()\n\n cache.h                 |   8 ++\n config.c                |  25 +++++\n pager.c                 |  31 ------\n setup.c                 | 252 +++++++++++++++++++++++++++++++++---------------\n t/helper/test-config.c  |  15 +++\n t/t1309-early-config.sh |  75 ++++++++++++++\n t/t7006-pager.sh        |  18 +++-\n 7 files changed, 313 insertions(+), 111 deletions(-)\n create mode 100755 t/t1309-early-config.sh\n\n\nbase-commit: e0688e9b28f2c5ff711460ee8b62077be5df2360\nPublished-As: https://github.com/dscho/git/releases/tag/early-config-v5\nFetch-It-Via: git fetch https://github.com/dscho/git early-config-v5\n\nInterdiff vs v4:\n\n diff --git a/setup.c b/setup.c\n index 9118b48590a..b0a28f609e2 100644\n --- a/setup.c\n +++ b/setup.c\n @@ -903,7 +903,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n  \t\t\tif (die_on_error ||\n  \t\t\t    error_code == READ_GITFILE_ERR_NOT_A_FILE) {\n  \t\t\t\tif (is_git_directory(dir->buf))\n -\t\t\t\t    gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n +\t\t\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n  \t\t\t} else if (error_code &&\n  \t\t\t\t   error_code != READ_GITFILE_ERR_STAT_FAILED)\n  \t\t\t\treturn GIT_DIR_INVALID_GITFILE;\n @@ -979,7 +979,8 @@ const char *discover_git_directory(struct strbuf *gitdir)\n  \n  const char *setup_git_directory_gently(int *nongit_ok)\n  {\n -\tstruct strbuf cwd = STRBUF_INIT, dir = STRBUF_INIT, gitdir = STRBUF_INIT;\n +\tstatic struct strbuf cwd = STRBUF_INIT;\n +\tstruct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT;\n  \tconst char *prefix;\n  \n  \t/*\n @@ -1027,6 +1028,8 @@ const char *setup_git_directory_gently(int *nongit_ok)\n  \tcase GIT_DIR_HIT_MOUNT_POINT:\n  \t\tif (nongit_ok) {\n  \t\t\t*nongit_ok = 1;\n +\t\t\tstrbuf_release(&cwd);\n +\t\t\tstrbuf_release(&dir);\n  \t\t\treturn NULL;\n  \t\t}\n  \t\tdie(_(\"Not a git repository (or any parent up to mount point %s)\\n\"\n @@ -1044,6 +1047,9 @@ const char *setup_git_directory_gently(int *nongit_ok)\n  \tstartup_info->have_repository = !nongit_ok || !*nongit_ok;\n  \tstartup_info->prefix = prefix;\n  \n +\tstrbuf_release(&dir);\n +\tstrbuf_release(&gitdir);\n +\n  \treturn prefix;\n  }\n  \n diff --git a/t/t1309-early-config.sh b/t/t1309-early-config.sh\n index 0c55dee514c..027eca63a3c 100755\n --- a/t/t1309-early-config.sh\n +++ b/t/t1309-early-config.sh\n @@ -47,4 +47,29 @@ test_expect_success 'ceiling #2' '\n  \ttest xdg = \"$(cat output)\"\n  '\n  \n +test_with_config ()\n +{\n +\trm -rf throwaway &&\n +\tgit init throwaway &&\n +\t(\n +\t\tcd throwaway &&\n +\t\techo \"$*\" >.git/config &&\n +\t\ttest-config read_early_config early.config\n +\t)\n +}\n +\n +test_expect_success 'ignore .git/ with incompatible repository version' '\n +\ttest_with_config \"[core]repositoryformatversion = 999999\" 2>err &&\n +\tgrep \"warning:.* Expected git repo version <= [1-9]\" err\n +'\n +\n +test_expect_failure 'ignore .git/ with invalid repository version' '\n +\ttest_with_config \"[core]repositoryformatversion = invalid\"\n +'\n +\n +\n +test_expect_failure 'ignore .git/ with invalid config' '\n +\ttest_with_config \"[\"\n +'\n +\n  test_done\n\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n"},{"id":"313708","messageId":"9a8c14d065cc0dd63ef71c18b6047a9a9023dac3.1489098170.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489098170.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 01/11] t7006: replace dubious test","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-09T22:23:50Z","receivedAt":"2017-03-09T22:24:10Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The idea of the test case \"git -p - core.pager is not used from\nsubdirectory\" was to verify that the setup_git_directory() function had\nnot been called just to obtain the core.pager setting.\n\nHowever, we are about to fix the early config machinery so that it\n*does* work, without messing up the global state.\n\nOnce that is done, the core.pager setting *will* be used, even when\nrunning from a subdirectory, and that is a Good Thing.\n\nThe intention of that test case, however, was to verify that the\nsetup_git_directory() function has not run, because it changes global\nstate such as the current working directory.\n\nTo keep that spirit, but fix the incorrect assumption, this patch\nreplaces that test case by a new one that verifies that the pager is\nrun in the subdirectory, i.e. that the current working directory has\nnot been changed at the time the pager is configured and launched, even\nif the `rev-parse` command requires a .git/ directory and *will* change\nthe working directory.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t7006-pager.sh | 12 +++++++++++-\n 1 file changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t7006-pager.sh b/t/t7006-pager.sh\nindex c8dc665f2fd..304ae06c600 100755\n--- a/t/t7006-pager.sh\n+++ b/t/t7006-pager.sh\n@@ -378,9 +378,19 @@ test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p request-pull'\n test_default_pager        expect_success test_must_fail 'git -p'\n test_PAGER_overrides      expect_success test_must_fail 'git -p'\n test_local_config_ignored expect_failure test_must_fail 'git -p'\n-test_no_local_config_subdir expect_success test_must_fail 'git -p'\n test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p'\n \n+test_expect_failure TTY 'core.pager in repo config works and retains cwd' '\n+\tsane_unset GIT_PAGER &&\n+\ttest_config core.pager \"cat >cwd-retained\" &&\n+\t(\n+\t\tcd sub &&\n+\t\trm -f cwd-retained &&\n+\t\ttest_terminal git -p rev-parse HEAD &&\n+\t\ttest_path_is_file cwd-retained\n+\t)\n+'\n+\n test_doesnt_paginate      expect_failure test_must_fail 'git -p nonsense'\n \n test_pager_choices                       'git shortlog'\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313709","messageId":"e1e36dea6dc1ca14cf6d94ecc2e8bbef940d1d7c.1489098170.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489098170.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 02/11] setup_git_directory(): use is_dir_sep() helper","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-09T22:23:55Z","receivedAt":"2017-03-09T22:24:18Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It is okay in practice to test for forward slashes in the output of\ngetcwd(), because we go out of our way to convert backslashes to forward\nslashes in getcwd()'s output on Windows.\n\nStill, the correct way to test for a dir separator is by using the\nhelper function we introduced for that very purpose. It also serves as a\ngood documentation what the code tries to do (not \"how\").\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/setup.c b/setup.c\nindex 967f289f1ef..4a15b105676 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -910,7 +910,9 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t\t\treturn setup_bare_git_dir(&cwd, offset, nongit_ok);\n \n \t\toffset_parent = offset;\n-\t\twhile (--offset_parent > ceil_offset && cwd.buf[offset_parent] != '/');\n+\t\twhile (--offset_parent > ceil_offset &&\n+\t\t       !is_dir_sep(cwd.buf[offset_parent]))\n+\t\t\t; /* continue */\n \t\tif (offset_parent <= ceil_offset)\n \t\t\treturn setup_nongit(cwd.buf, nongit_ok);\n \t\tif (one_filesystem) {\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313710","messageId":"69394fd8d91a3723fed34ad1a9051300df3cc7b3.1489098170.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489098170.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 03/11] Prepare setup_discovered_git_directory() the root directory","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-09T22:23:59Z","receivedAt":"2017-03-09T22:24:20Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Currently, the offset parameter (indicating what part of the cwd\nparameter corresponds to the current directory after discovering the\n.git/ directory) is set to 0 when we are running in the root directory.\n\nHowever, in the next patches we will avoid changing the current working\ndirectory while searching for the .git/ directory, meaning that the\noffset corresponding to the root directory will have to be 1 to reflect\nthat this directory is characterized by the path \"/\" (and not \"\").\n\nSo let's make sure that setup_discovered_git_directory() only tries to\nappend the trailing slash to non-root directories.\n\nNote: the setup_bare_git_directory() does not need a corresponding\nchange, as it does not want to return a prefix.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 4a15b105676..20a1f0f870e 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -721,8 +721,10 @@ static const char *setup_discovered_git_dir(const char *gitdir,\n \tif (offset == cwd->len)\n \t\treturn NULL;\n \n-\t/* Make \"offset\" point to past the '/', and add a '/' at the end */\n-\toffset++;\n+\t/* Make \"offset\" point past the '/' (already the case for root dirs) */\n+\tif (offset != offset_1st_component(cwd->buf))\n+\t\toffset++;\n+\t/* Add a '/' at the end */\n \tstrbuf_addch(cwd, '/');\n \treturn cwd->buf + offset;\n }\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313711","messageId":"bafa0c8e7a760aa26add5e3f757062c230fbd6a9.1489098170.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489098170.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 04/11] setup_git_directory_1(): avoid changing global state","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-09T22:24:02Z","receivedAt":"2017-03-09T22:24:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"For historical reasons, Git searches for the .git/ directory (or the\n.git file) by changing the working directory successively to the parent\ndirectory of the current directory, until either anything was found or\nuntil a ceiling or a mount point is hit.\n\nFurther global state may be changed in case a .git/ directory was found.\n\nWe do have a use case, though, where we would like to find the .git/\ndirectory without having any global state touched, though: when we read\nthe early config e.g. for the pager or for alias expansion.\n\nLet's just move all of code that changes any global state out of the\nfunction `setup_git_directory_gently_1()` into\n`setup_git_directory_gently()`.\n\nIn subsequent patches, we will use the _1() function in a new\n`discover_git_directory()` function that we will then use for the early\nconfig code.\n\nNote: the new loop is a *little* tricky, as we have to handle the root\ndirectory specially: we cannot simply strip away the last component\nincluding the slash, as the root directory only has that slash. To remedy\nthat, we introduce the `min_offset` variable that holds the minimal length\nof an absolute path, and using that to special-case the root directory,\nincluding an early exit before trying to find the parent of the root\ndirectory.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 193 +++++++++++++++++++++++++++++++++++++++-------------------------\n 1 file changed, 118 insertions(+), 75 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 20a1f0f870e..a7bb09608c0 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -818,50 +818,49 @@ static int canonicalize_ceiling_entry(struct string_list_item *item,\n \t}\n }\n \n+enum discovery_result {\n+\tGIT_DIR_NONE = 0,\n+\tGIT_DIR_EXPLICIT,\n+\tGIT_DIR_DISCOVERED,\n+\tGIT_DIR_BARE,\n+\t/* these are errors */\n+\tGIT_DIR_HIT_CEILING = -1,\n+\tGIT_DIR_HIT_MOUNT_POINT = -2\n+};\n+\n /*\n  * We cannot decide in this function whether we are in the work tree or\n  * not, since the config can only be read _after_ this function was called.\n+ *\n+ * Also, we avoid changing any global state (such as the current working\n+ * directory) to allow early callers.\n+ *\n+ * The directory where the search should start needs to be passed in via the\n+ * `dir` parameter; upon return, the `dir` buffer will contain the path of\n+ * the directory where the search ended, and `gitdir` will contain the path of\n+ * the discovered .git/ directory, if any. This path may be relative against\n+ * `dir` (i.e. *not* necessarily the cwd).\n  */\n-static const char *setup_git_directory_gently_1(int *nongit_ok)\n+static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n+\t\t\t\t\t\t\t  struct strbuf *gitdir)\n {\n \tconst char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);\n \tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n-\tstatic struct strbuf cwd = STRBUF_INIT;\n-\tconst char *gitdirenv, *ret;\n-\tchar *gitfile;\n-\tint offset, offset_parent, ceil_offset = -1;\n+\tconst char *gitdirenv;\n+\tint ceil_offset = -1, min_offset = has_dos_drive_prefix(dir->buf) ? 3 : 1;\n \tdev_t current_device = 0;\n \tint one_filesystem = 1;\n \n \t/*\n-\t * We may have read an incomplete configuration before\n-\t * setting-up the git directory. If so, clear the cache so\n-\t * that the next queries to the configuration reload complete\n-\t * configuration (including the per-repo config file that we\n-\t * ignored previously).\n-\t */\n-\tgit_config_clear();\n-\n-\t/*\n-\t * Let's assume that we are in a git repository.\n-\t * If it turns out later that we are somewhere else, the value will be\n-\t * updated accordingly.\n-\t */\n-\tif (nongit_ok)\n-\t\t*nongit_ok = 0;\n-\n-\tif (strbuf_getcwd(&cwd))\n-\t\tdie_errno(_(\"Unable to read current working directory\"));\n-\toffset = cwd.len;\n-\n-\t/*\n \t * If GIT_DIR is set explicitly, we're not going\n \t * to do any discovery, but we still do repository\n \t * validation.\n \t */\n \tgitdirenv = getenv(GIT_DIR_ENVIRONMENT);\n-\tif (gitdirenv)\n-\t\treturn setup_explicit_git_dir(gitdirenv, &cwd, nongit_ok);\n+\tif (gitdirenv) {\n+\t\tstrbuf_addstr(gitdir, gitdirenv);\n+\t\treturn GIT_DIR_EXPLICIT;\n+\t}\n \n \tif (env_ceiling_dirs) {\n \t\tint empty_entry_found = 0;\n@@ -869,15 +868,15 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);\n \t\tfilter_string_list(&ceiling_dirs, 0,\n \t\t\t\t   canonicalize_ceiling_entry, &empty_entry_found);\n-\t\tceil_offset = longest_ancestor_length(cwd.buf, &ceiling_dirs);\n+\t\tceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs);\n \t\tstring_list_clear(&ceiling_dirs, 0);\n \t}\n \n-\tif (ceil_offset < 0 && has_dos_drive_prefix(cwd.buf))\n-\t\tceil_offset = 1;\n+\tif (ceil_offset < 0)\n+\t\tceil_offset = min_offset - 2;\n \n \t/*\n-\t * Test in the following order (relative to the cwd):\n+\t * Test in the following order (relative to the dir):\n \t * - .git (file containing \"gitdir: <path>\")\n \t * - .git/\n \t * - ./ (bare)\n@@ -889,63 +888,104 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t */\n \tone_filesystem = !git_env_bool(\"GIT_DISCOVERY_ACROSS_FILESYSTEM\", 0);\n \tif (one_filesystem)\n-\t\tcurrent_device = get_device_or_die(\".\", NULL, 0);\n+\t\tcurrent_device = get_device_or_die(dir->buf, NULL, 0);\n \tfor (;;) {\n-\t\tgitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);\n-\t\tif (gitfile)\n-\t\t\tgitdirenv = gitfile = xstrdup(gitfile);\n-\t\telse {\n-\t\t\tif (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))\n-\t\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n+\t\tint offset = dir->len;\n+\n+\t\tif (offset > min_offset)\n+\t\t\tstrbuf_addch(dir, '/');\n+\t\tstrbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);\n+\t\tgitdirenv = read_gitfile(dir->buf);\n+\t\tif (!gitdirenv && is_git_directory(dir->buf))\n+\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n+\t\tstrbuf_setlen(dir, offset);\n+\t\tif (gitdirenv) {\n+\t\t\tstrbuf_addstr(gitdir, gitdirenv);\n+\t\t\treturn GIT_DIR_DISCOVERED;\n \t\t}\n \n-\t\tif (gitdirenv) {\n-\t\t\tret = setup_discovered_git_dir(gitdirenv,\n-\t\t\t\t\t\t       &cwd, offset,\n-\t\t\t\t\t\t       nongit_ok);\n-\t\t\tfree(gitfile);\n-\t\t\treturn ret;\n+\t\tif (is_git_directory(dir->buf)) {\n+\t\t\tstrbuf_addstr(gitdir, \".\");\n+\t\t\treturn GIT_DIR_BARE;\n \t\t}\n-\t\tfree(gitfile);\n \n-\t\tif (is_git_directory(\".\"))\n-\t\t\treturn setup_bare_git_dir(&cwd, offset, nongit_ok);\n+\t\tif (offset <= min_offset)\n+\t\t\treturn GIT_DIR_HIT_CEILING;\n \n-\t\toffset_parent = offset;\n-\t\twhile (--offset_parent > ceil_offset &&\n-\t\t       !is_dir_sep(cwd.buf[offset_parent]))\n+\t\twhile (--offset > ceil_offset && !is_dir_sep(dir->buf[offset]))\n \t\t\t; /* continue */\n-\t\tif (offset_parent <= ceil_offset)\n-\t\t\treturn setup_nongit(cwd.buf, nongit_ok);\n-\t\tif (one_filesystem) {\n-\t\t\tdev_t parent_device = get_device_or_die(\"..\", cwd.buf,\n-\t\t\t\t\t\t\t\toffset);\n-\t\t\tif (parent_device != current_device) {\n-\t\t\t\tif (nongit_ok) {\n-\t\t\t\t\tif (chdir(cwd.buf))\n-\t\t\t\t\t\tdie_errno(_(\"Cannot come back to cwd\"));\n-\t\t\t\t\t*nongit_ok = 1;\n-\t\t\t\t\treturn NULL;\n-\t\t\t\t}\n-\t\t\t\tstrbuf_setlen(&cwd, offset);\n-\t\t\t\tdie(_(\"Not a git repository (or any parent up to mount point %s)\\n\"\n-\t\t\t\t\"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\"),\n-\t\t\t\t    cwd.buf);\n-\t\t\t}\n-\t\t}\n-\t\tif (chdir(\"..\")) {\n-\t\t\tstrbuf_setlen(&cwd, offset);\n-\t\t\tdie_errno(_(\"Cannot change to '%s/..'\"), cwd.buf);\n-\t\t}\n-\t\toffset = offset_parent;\n+\t\tif (offset <= ceil_offset)\n+\t\t\treturn GIT_DIR_HIT_CEILING;\n+\n+\t\tstrbuf_setlen(dir, offset > min_offset ?  offset : min_offset);\n+\t\tif (one_filesystem &&\n+\t\t    current_device != get_device_or_die(dir->buf, NULL, offset))\n+\t\t\treturn GIT_DIR_HIT_MOUNT_POINT;\n \t}\n }\n \n const char *setup_git_directory_gently(int *nongit_ok)\n {\n+\tstatic struct strbuf cwd = STRBUF_INIT;\n+\tstruct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT;\n \tconst char *prefix;\n \n-\tprefix = setup_git_directory_gently_1(nongit_ok);\n+\t/*\n+\t * We may have read an incomplete configuration before\n+\t * setting-up the git directory. If so, clear the cache so\n+\t * that the next queries to the configuration reload complete\n+\t * configuration (including the per-repo config file that we\n+\t * ignored previously).\n+\t */\n+\tgit_config_clear();\n+\n+\t/*\n+\t * Let's assume that we are in a git repository.\n+\t * If it turns out later that we are somewhere else, the value will be\n+\t * updated accordingly.\n+\t */\n+\tif (nongit_ok)\n+\t\t*nongit_ok = 0;\n+\n+\tif (strbuf_getcwd(&cwd))\n+\t\tdie_errno(_(\"Unable to read current working directory\"));\n+\tstrbuf_addbuf(&dir, &cwd);\n+\n+\tswitch (setup_git_directory_gently_1(&dir, &gitdir)) {\n+\tcase GIT_DIR_NONE:\n+\t\tprefix = NULL;\n+\t\tbreak;\n+\tcase GIT_DIR_EXPLICIT:\n+\t\tprefix = setup_explicit_git_dir(gitdir.buf, &cwd, nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_DISCOVERED:\n+\t\tif (dir.len < cwd.len && chdir(dir.buf))\n+\t\t\tdie(_(\"Cannot change to '%s'\"), dir.buf);\n+\t\tprefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len,\n+\t\t\t\t\t\t  nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_BARE:\n+\t\tif (dir.len < cwd.len && chdir(dir.buf))\n+\t\t\tdie(_(\"Cannot change to '%s'\"), dir.buf);\n+\t\tprefix = setup_bare_git_dir(&cwd, dir.len, nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_HIT_CEILING:\n+\t\tprefix = setup_nongit(cwd.buf, nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_HIT_MOUNT_POINT:\n+\t\tif (nongit_ok) {\n+\t\t\t*nongit_ok = 1;\n+\t\t\tstrbuf_release(&cwd);\n+\t\t\tstrbuf_release(&dir);\n+\t\t\treturn NULL;\n+\t\t}\n+\t\tdie(_(\"Not a git repository (or any parent up to mount point %s)\\n\"\n+\t\t      \"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\"),\n+\t\t    dir.buf);\n+\tdefault:\n+\t\tdie(\"BUG: unhandled setup_git_directory_1() result\");\n+\t}\n+\n \tif (prefix)\n \t\tsetenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);\n \telse\n@@ -954,6 +994,9 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \tstartup_info->have_repository = !nongit_ok || !*nongit_ok;\n \tstartup_info->prefix = prefix;\n \n+\tstrbuf_release(&dir);\n+\tstrbuf_release(&gitdir);\n+\n \treturn prefix;\n }\n \n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313712","messageId":"81afe73dd398d24127683a57953ea34c7d9b92c6.1489098170.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489098170.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 05/11] Introduce the discover_git_directory() function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-09T22:24:45Z","receivedAt":"2017-03-09T22:25:07Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"We modified the setup_git_directory_gently_1() function earlier to make\nit possible to discover the GIT_DIR without changing global state.\n\nHowever, it is still a bit cumbersome to use if you only need to figure\nout the (possibly absolute) path of the .git/ directory. Let's just\nprovide a convenient wrapper function with an easier signature that\n*just* discovers the .git/ directory.\n\nWe will use it in a subsequent patch to fix the early config.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n cache.h |  7 +++++++\n setup.c | 43 +++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 50 insertions(+)\n\ndiff --git a/cache.h b/cache.h\nindex 80b6372cf76..5218726cf88 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -518,6 +518,13 @@ extern void set_git_work_tree(const char *tree);\n #define ALTERNATE_DB_ENVIRONMENT \"GIT_ALTERNATE_OBJECT_DIRECTORIES\"\n \n extern void setup_work_tree(void);\n+/*\n+ * Find GIT_DIR of the repository that contains the current working directory,\n+ * without changing the working directory or other global state. The result is\n+ * appended to gitdir. The return value is either NULL if no repository was\n+ * found, or pointing to the path inside gitdir's buffer.\n+ */\n+extern const char *discover_git_directory(struct strbuf *gitdir);\n extern const char *setup_git_directory_gently(int *);\n extern const char *setup_git_directory(void);\n extern char *prefix_path(const char *prefix, int len, const char *path);\ndiff --git a/setup.c b/setup.c\nindex a7bb09608c0..43f522fa996 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -924,6 +924,49 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \t}\n }\n \n+const char *discover_git_directory(struct strbuf *gitdir)\n+{\n+\tstruct strbuf dir = STRBUF_INIT, err = STRBUF_INIT;\n+\tsize_t gitdir_offset = gitdir->len, cwd_len;\n+\tstruct repository_format candidate;\n+\n+\tif (strbuf_getcwd(&dir))\n+\t\treturn NULL;\n+\n+\tcwd_len = dir.len;\n+\tif (setup_git_directory_gently_1(&dir, gitdir) <= 0) {\n+\t\tstrbuf_release(&dir);\n+\t\treturn NULL;\n+\t}\n+\n+\t/*\n+\t * The returned gitdir is relative to dir, and if dir does not reflect\n+\t * the current working directory, we simply make the gitdir absolute.\n+\t */\n+\tif (dir.len < cwd_len && !is_absolute_path(gitdir->buf + gitdir_offset)) {\n+\t\t/* Avoid a trailing \"/.\" */\n+\t\tif (!strcmp(\".\", gitdir->buf + gitdir_offset))\n+\t\t\tstrbuf_setlen(gitdir, gitdir_offset);\n+\t\telse\n+\t\t\tstrbuf_addch(&dir, '/');\n+\t\tstrbuf_insert(gitdir, gitdir_offset, dir.buf, dir.len);\n+\t}\n+\n+\tstrbuf_reset(&dir);\n+\tstrbuf_addf(&dir, \"%s/config\", gitdir->buf + gitdir_offset);\n+\tread_repository_format(&candidate, dir.buf);\n+\tstrbuf_release(&dir);\n+\n+\tif (verify_repository_format(&candidate, &err) < 0) {\n+\t\twarning(\"ignoring git dir '%s': %s\",\n+\t\t\tgitdir->buf + gitdir_offset, err.buf);\n+\t\tstrbuf_release(&err);\n+\t\treturn NULL;\n+\t}\n+\n+\treturn gitdir->buf + gitdir_offset;\n+}\n+\n const char *setup_git_directory_gently(int *nongit_ok)\n {\n \tstatic struct strbuf cwd = STRBUF_INIT;\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313713","messageId":"2bd62f5bfab404b672b148e18502d00bf94d9e59.1489098170.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489098170.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 07/11] read_early_config(): avoid .git/config hack when unneeded","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-09T22:24:59Z","receivedAt":"2017-03-09T22:25:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"So far, we only look whether the startup_info claims to have seen a\ngit_dir.\n\nHowever, do_git_config_sequence() (and consequently the\ngit_config_with_options() call used by read_early_config() asks the\nhave_git_dir() function whether we have a .git/ directory, which in turn\nalso looks at git_dir and at the environment variable GIT_DIR. And when\nthis is the case, the repository config is handled already, so we do not\nhave to do that again explicitly.\n\nLet's just use the same function, have_git_dir(), to determine whether we\nhave to handle .git/config explicitly.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n config.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 9cfbeafd04c..068fa4dcfa6 100644\n--- a/config.c\n+++ b/config.c\n@@ -1427,14 +1427,15 @@ void read_early_config(config_fn_t cb, void *data)\n \t * core.repositoryformatversion), we'll read whatever is in .git/config\n \t * blindly. Similarly, if we _are_ in a repository, but not at the\n \t * root, we'll fail to find .git/config (because it's really\n-\t * ../.git/config, etc). See t7006 for a complete set of failures.\n+\t * ../.git/config, etc), unless setup_git_directory() was already called.\n+\t * See t7006 for a complete set of failures.\n \t *\n \t * However, we have historically provided this hack because it does\n \t * work some of the time (namely when you are at the top-level of a\n \t * valid repository), and would rarely make things worse (i.e., you do\n \t * not generally have a .git/config file sitting around).\n \t */\n-\tif (!startup_info->have_repository) {\n+\tif (!have_git_dir()) {\n \t\tstruct git_config_source repo_config;\n \n \t\tmemset(&repo_config, 0, sizeof(repo_config));\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313714","messageId":"40053216acd5190a3f26564664d09b35def8cced.1489098170.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489098170.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 08/11] read_early_config(): really discover .git/","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-09T22:25:03Z","receivedAt":"2017-03-09T22:25:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Earlier, we punted and simply assumed that we are in the top-level\ndirectory of the project, and that there is no .git file but a .git/\ndirectory so that we can read directly from .git/config.\n\nHowever, that is not necessarily true. We may be in a subdirectory. Or\n.git may be a gitfile. Or the environment variable GIT_DIR may be set.\n\nTo remedy this situation, we just refactored the way\nsetup_git_directory() discovers the .git/ directory, to make it\nreusable, and more importantly, to leave all global variables and the\ncurrent working directory alone.\n\nLet's discover the .git/ directory correctly in read_early_config() by\nusing that new function.\n\nThis fixes 4 known breakages in t7006.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n config.c         | 31 ++++++++++++-------------------\n t/t7006-pager.sh |  8 ++++----\n 2 files changed, 16 insertions(+), 23 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 068fa4dcfa6..a88df53fdbc 100644\n--- a/config.c\n+++ b/config.c\n@@ -1414,34 +1414,27 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \n void read_early_config(config_fn_t cb, void *data)\n {\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n \tgit_config_with_options(cb, data, NULL, 1);\n \n \t/*\n-\t * Note that this is a really dirty hack that does the wrong thing in\n-\t * many cases. The crux of the problem is that we cannot run\n-\t * setup_git_directory() early on in git's setup, so we have no idea if\n-\t * we are in a repository or not, and therefore are not sure whether\n-\t * and how to read repository-local config.\n-\t *\n-\t * So if we _aren't_ in a repository (or we are but we would reject its\n-\t * core.repositoryformatversion), we'll read whatever is in .git/config\n-\t * blindly. Similarly, if we _are_ in a repository, but not at the\n-\t * root, we'll fail to find .git/config (because it's really\n-\t * ../.git/config, etc), unless setup_git_directory() was already called.\n-\t * See t7006 for a complete set of failures.\n-\t *\n-\t * However, we have historically provided this hack because it does\n-\t * work some of the time (namely when you are at the top-level of a\n-\t * valid repository), and would rarely make things worse (i.e., you do\n-\t * not generally have a .git/config file sitting around).\n+\t * When setup_git_directory() was not yet asked to discover the\n+\t * GIT_DIR, we ask discover_git_directory() to figure out whether there\n+\t * is any repository config we should use (but unlike\n+\t * setup_git_directory_gently(), no global state is changed, most\n+\t * notably, the current working directory is still the same after the\n+\t * call).\n \t */\n-\tif (!have_git_dir()) {\n+\tif (!have_git_dir() && discover_git_directory(&buf)) {\n \t\tstruct git_config_source repo_config;\n \n \t\tmemset(&repo_config, 0, sizeof(repo_config));\n-\t\trepo_config.file = \".git/config\";\n+\t\tstrbuf_addstr(&buf, \"/config\");\n+\t\trepo_config.file = buf.buf;\n \t\tgit_config_with_options(cb, data, &repo_config, 1);\n \t}\n+\tstrbuf_release(&buf);\n }\n \n static void git_config_check_init(void);\ndiff --git a/t/t7006-pager.sh b/t/t7006-pager.sh\nindex 304ae06c600..4f3794d415e 100755\n--- a/t/t7006-pager.sh\n+++ b/t/t7006-pager.sh\n@@ -360,19 +360,19 @@ test_pager_choices                       'git aliasedlog'\n test_default_pager        expect_success 'git -p aliasedlog'\n test_PAGER_overrides      expect_success 'git -p aliasedlog'\n test_core_pager_overrides expect_success 'git -p aliasedlog'\n-test_core_pager_subdir    expect_failure 'git -p aliasedlog'\n+test_core_pager_subdir    expect_success 'git -p aliasedlog'\n test_GIT_PAGER_overrides  expect_success 'git -p aliasedlog'\n \n test_default_pager        expect_success 'git -p true'\n test_PAGER_overrides      expect_success 'git -p true'\n test_core_pager_overrides expect_success 'git -p true'\n-test_core_pager_subdir    expect_failure 'git -p true'\n+test_core_pager_subdir    expect_success 'git -p true'\n test_GIT_PAGER_overrides  expect_success 'git -p true'\n \n test_default_pager        expect_success test_must_fail 'git -p request-pull'\n test_PAGER_overrides      expect_success test_must_fail 'git -p request-pull'\n test_core_pager_overrides expect_success test_must_fail 'git -p request-pull'\n-test_core_pager_subdir    expect_failure test_must_fail 'git -p request-pull'\n+test_core_pager_subdir    expect_success test_must_fail 'git -p request-pull'\n test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p request-pull'\n \n test_default_pager        expect_success test_must_fail 'git -p'\n@@ -380,7 +380,7 @@ test_PAGER_overrides      expect_success test_must_fail 'git -p'\n test_local_config_ignored expect_failure test_must_fail 'git -p'\n test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p'\n \n-test_expect_failure TTY 'core.pager in repo config works and retains cwd' '\n+test_expect_success TTY 'core.pager in repo config works and retains cwd' '\n \tsane_unset GIT_PAGER &&\n \ttest_config core.pager \"cat >cwd-retained\" &&\n \t(\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313715","messageId":"79d52d2a0ba5a120fa5f2e4cc973513a75b2679d.1489098170.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489098170.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 06/11] Make read_early_config() reusable","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-09T22:24:55Z","receivedAt":"2017-03-09T22:25:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The pager configuration needs to be read early, possibly before\ndiscovering any .git/ directory.\n\nLet's not hide this function in pager.c, but make it available to other\ncallers.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n cache.h  |  1 +\n config.c | 31 +++++++++++++++++++++++++++++++\n pager.c  | 31 -------------------------------\n 3 files changed, 32 insertions(+), 31 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 5218726cf88..e7b57457e73 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1804,6 +1804,7 @@ extern int git_config_from_blob_sha1(config_fn_t fn, const char *name,\n \t\t\t\t     const unsigned char *sha1, void *data);\n extern void git_config_push_parameter(const char *text);\n extern int git_config_from_parameters(config_fn_t fn, void *data);\n+extern void read_early_config(config_fn_t cb, void *data);\n extern void git_config(config_fn_t fn, void *);\n extern int git_config_with_options(config_fn_t fn, void *,\n \t\t\t\t   struct git_config_source *config_source,\ndiff --git a/config.c b/config.c\nindex c6b874a7bf7..9cfbeafd04c 100644\n--- a/config.c\n+++ b/config.c\n@@ -1412,6 +1412,37 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \t}\n }\n \n+void read_early_config(config_fn_t cb, void *data)\n+{\n+\tgit_config_with_options(cb, data, NULL, 1);\n+\n+\t/*\n+\t * Note that this is a really dirty hack that does the wrong thing in\n+\t * many cases. The crux of the problem is that we cannot run\n+\t * setup_git_directory() early on in git's setup, so we have no idea if\n+\t * we are in a repository or not, and therefore are not sure whether\n+\t * and how to read repository-local config.\n+\t *\n+\t * So if we _aren't_ in a repository (or we are but we would reject its\n+\t * core.repositoryformatversion), we'll read whatever is in .git/config\n+\t * blindly. Similarly, if we _are_ in a repository, but not at the\n+\t * root, we'll fail to find .git/config (because it's really\n+\t * ../.git/config, etc). See t7006 for a complete set of failures.\n+\t *\n+\t * However, we have historically provided this hack because it does\n+\t * work some of the time (namely when you are at the top-level of a\n+\t * valid repository), and would rarely make things worse (i.e., you do\n+\t * not generally have a .git/config file sitting around).\n+\t */\n+\tif (!startup_info->have_repository) {\n+\t\tstruct git_config_source repo_config;\n+\n+\t\tmemset(&repo_config, 0, sizeof(repo_config));\n+\t\trepo_config.file = \".git/config\";\n+\t\tgit_config_with_options(cb, data, &repo_config, 1);\n+\t}\n+}\n+\n static void git_config_check_init(void);\n \n void git_config(config_fn_t fn, void *data)\ndiff --git a/pager.c b/pager.c\nindex ae796433630..73ca8bc3b17 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -43,37 +43,6 @@ static int core_pager_config(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n-static void read_early_config(config_fn_t cb, void *data)\n-{\n-\tgit_config_with_options(cb, data, NULL, 1);\n-\n-\t/*\n-\t * Note that this is a really dirty hack that does the wrong thing in\n-\t * many cases. The crux of the problem is that we cannot run\n-\t * setup_git_directory() early on in git's setup, so we have no idea if\n-\t * we are in a repository or not, and therefore are not sure whether\n-\t * and how to read repository-local config.\n-\t *\n-\t * So if we _aren't_ in a repository (or we are but we would reject its\n-\t * core.repositoryformatversion), we'll read whatever is in .git/config\n-\t * blindly. Similarly, if we _are_ in a repository, but not at the\n-\t * root, we'll fail to find .git/config (because it's really\n-\t * ../.git/config, etc). See t7006 for a complete set of failures.\n-\t *\n-\t * However, we have historically provided this hack because it does\n-\t * work some of the time (namely when you are at the top-level of a\n-\t * valid repository), and would rarely make things worse (i.e., you do\n-\t * not generally have a .git/config file sitting around).\n-\t */\n-\tif (!startup_info->have_repository) {\n-\t\tstruct git_config_source repo_config;\n-\n-\t\tmemset(&repo_config, 0, sizeof(repo_config));\n-\t\trepo_config.file = \".git/config\";\n-\t\tgit_config_with_options(cb, data, &repo_config, 1);\n-\t}\n-}\n-\n const char *git_pager(int stdout_is_tty)\n {\n \tconst char *pager;\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313716","messageId":"72b36d27cefc0060f170b33f19cf504bb0338c1c.1489098170.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489098170.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 09/11] Test read_early_config()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-09T22:25:06Z","receivedAt":"2017-03-09T22:25:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"So far, we had no explicit tests of that function.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/helper/test-config.c  | 15 +++++++++++++++\n t/t1309-early-config.sh | 50 +++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 65 insertions(+)\n create mode 100755 t/t1309-early-config.sh\n\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 83a4f2ab869..8e3ed6a76cb 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -66,6 +66,16 @@ static int iterate_cb(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n+static int early_config_cb(const char *var, const char *value, void *vdata)\n+{\n+\tconst char *key = vdata;\n+\n+\tif (!strcmp(key, var))\n+\t\tprintf(\"%s\\n\", value);\n+\n+\treturn 0;\n+}\n+\n int cmd_main(int argc, const char **argv)\n {\n \tint i, val;\n@@ -73,6 +83,11 @@ int cmd_main(int argc, const char **argv)\n \tconst struct string_list *strptr;\n \tstruct config_set cs;\n \n+\tif (argc == 3 && !strcmp(argv[1], \"read_early_config\")) {\n+\t\tread_early_config(early_config_cb, (void *)argv[2]);\n+\t\treturn 0;\n+\t}\n+\n \tsetup_git_directory();\n \n \tgit_configset_init(&cs);\ndiff --git a/t/t1309-early-config.sh b/t/t1309-early-config.sh\nnew file mode 100755\nindex 00000000000..0c55dee514c\n--- /dev/null\n+++ b/t/t1309-early-config.sh\n@@ -0,0 +1,50 @@\n+#!/bin/sh\n+\n+test_description='Test read_early_config()'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'read early config' '\n+\ttest_config early.config correct &&\n+\ttest-config read_early_config early.config >output &&\n+\ttest correct = \"$(cat output)\"\n+'\n+\n+test_expect_success 'in a sub-directory' '\n+\ttest_config early.config sub &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest sub = \"$(cat output)\"\n+'\n+\n+test_expect_success 'ceiling' '\n+\ttest_config early.config ceiling &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tGIT_CEILING_DIRECTORIES=\"$PWD\" &&\n+\t\texport GIT_CEILING_DIRECTORIES &&\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest -z \"$(cat output)\"\n+'\n+\n+test_expect_success 'ceiling #2' '\n+\tmkdir -p xdg/git &&\n+\tgit config -f xdg/git/config early.config xdg &&\n+\ttest_config early.config ceiling &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tXDG_CONFIG_HOME=\"$PWD\"/xdg &&\n+\t\tGIT_CEILING_DIRECTORIES=\"$PWD\" &&\n+\t\texport GIT_CEILING_DIRECTORIES XDG_CONFIG_HOME &&\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest xdg = \"$(cat output)\"\n+'\n+\n+test_done\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313717","messageId":"a1e24f1b31773f4d2f7f06ab7d5870e920211d51.1489098170.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489098170.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 10/11] setup_git_directory_gently_1(): avoid die()ing","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-09T22:25:28Z","receivedAt":"2017-03-09T22:25:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This function now has a new caller in addition to setup_git_directory():\nthe newly introduced discover_git_directory(). That function wants to\ndiscover the current .git/ directory, and in case of a corrupted one\nsimply pretend that there is none to be found.\n\nExample: if a stale .git file exists in the parent directory, and the\nuser calls `git -p init`, we want Git to simply *not* read any\nrepository config for the pager (instead of aborting with a message that\nthe .git file is corrupt).\n\nLet's actually pretend that there was no GIT_DIR to be found in that case\nwhen being called from discover_git_directory(), but keep the previous\nbehavior (i.e. to die()) for the setup_git_directory() case.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 26 ++++++++++++++++++--------\n 1 file changed, 18 insertions(+), 8 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 43f522fa996..b0a28f609e2 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -825,7 +825,8 @@ enum discovery_result {\n \tGIT_DIR_BARE,\n \t/* these are errors */\n \tGIT_DIR_HIT_CEILING = -1,\n-\tGIT_DIR_HIT_MOUNT_POINT = -2\n+\tGIT_DIR_HIT_MOUNT_POINT = -2,\n+\tGIT_DIR_INVALID_GITFILE = -3\n };\n \n /*\n@@ -842,7 +843,8 @@ enum discovery_result {\n  * `dir` (i.e. *not* necessarily the cwd).\n  */\n static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n-\t\t\t\t\t\t\t  struct strbuf *gitdir)\n+\t\t\t\t\t\t\t  struct strbuf *gitdir,\n+\t\t\t\t\t\t\t  int die_on_error)\n {\n \tconst char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);\n \tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n@@ -890,14 +892,22 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \tif (one_filesystem)\n \t\tcurrent_device = get_device_or_die(dir->buf, NULL, 0);\n \tfor (;;) {\n-\t\tint offset = dir->len;\n+\t\tint offset = dir->len, error_code = 0;\n \n \t\tif (offset > min_offset)\n \t\t\tstrbuf_addch(dir, '/');\n \t\tstrbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);\n-\t\tgitdirenv = read_gitfile(dir->buf);\n-\t\tif (!gitdirenv && is_git_directory(dir->buf))\n-\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n+\t\tgitdirenv = read_gitfile_gently(dir->buf, die_on_error ?\n+\t\t\t\t\t\tNULL : &error_code);\n+\t\tif (!gitdirenv) {\n+\t\t\tif (die_on_error ||\n+\t\t\t    error_code == READ_GITFILE_ERR_NOT_A_FILE) {\n+\t\t\t\tif (is_git_directory(dir->buf))\n+\t\t\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n+\t\t\t} else if (error_code &&\n+\t\t\t\t   error_code != READ_GITFILE_ERR_STAT_FAILED)\n+\t\t\t\treturn GIT_DIR_INVALID_GITFILE;\n+\t\t}\n \t\tstrbuf_setlen(dir, offset);\n \t\tif (gitdirenv) {\n \t\t\tstrbuf_addstr(gitdir, gitdirenv);\n@@ -934,7 +944,7 @@ const char *discover_git_directory(struct strbuf *gitdir)\n \t\treturn NULL;\n \n \tcwd_len = dir.len;\n-\tif (setup_git_directory_gently_1(&dir, gitdir) <= 0) {\n+\tif (setup_git_directory_gently_1(&dir, gitdir, 0) <= 0) {\n \t\tstrbuf_release(&dir);\n \t\treturn NULL;\n \t}\n@@ -994,7 +1004,7 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \t\tdie_errno(_(\"Unable to read current working directory\"));\n \tstrbuf_addbuf(&dir, &cwd);\n \n-\tswitch (setup_git_directory_gently_1(&dir, &gitdir)) {\n+\tswitch (setup_git_directory_gently_1(&dir, &gitdir, 1)) {\n \tcase GIT_DIR_NONE:\n \t\tprefix = NULL;\n \t\tbreak;\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313718","messageId":"5dc405f0170324cd811732729633416986d252d7.1489098170.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489098170.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 11/11] t1309: document cases where we would want early config not to die()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-09T22:25:33Z","receivedAt":"2017-03-09T22:25:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Jeff King came up with a couple examples that demonstrate how the new\nread_early_config() that looks harder for the current .git/ directory\ncould die() in an undesirable way.\n\nLet's add those cases to the test script, to document what we would like\nto happen when early config encounters problems.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t1309-early-config.sh | 25 +++++++++++++++++++++++++\n 1 file changed, 25 insertions(+)\n\ndiff --git a/t/t1309-early-config.sh b/t/t1309-early-config.sh\nindex 0c55dee514c..027eca63a3c 100755\n--- a/t/t1309-early-config.sh\n+++ b/t/t1309-early-config.sh\n@@ -47,4 +47,29 @@ test_expect_success 'ceiling #2' '\n \ttest xdg = \"$(cat output)\"\n '\n \n+test_with_config ()\n+{\n+\trm -rf throwaway &&\n+\tgit init throwaway &&\n+\t(\n+\t\tcd throwaway &&\n+\t\techo \"$*\" >.git/config &&\n+\t\ttest-config read_early_config early.config\n+\t)\n+}\n+\n+test_expect_success 'ignore .git/ with incompatible repository version' '\n+\ttest_with_config \"[core]repositoryformatversion = 999999\" 2>err &&\n+\tgrep \"warning:.* Expected git repo version <= [1-9]\" err\n+'\n+\n+test_expect_failure 'ignore .git/ with invalid repository version' '\n+\ttest_with_config \"[core]repositoryformatversion = invalid\"\n+'\n+\n+\n+test_expect_failure 'ignore .git/ with invalid config' '\n+\ttest_with_config \"[\"\n+'\n+\n test_done\n-- \n2.12.0.windows.1.7.g94dafc3b124\n"},{"id":"313755","messageId":"xmqqmvctytk0.fsf@gitster.mtv.corp.google.com","threadId":"44653","inReplyTo":"20170309121620.rld6scydqru72nbk@sigill.intra.peff.net","subject":"Re: [PATCH v3 0/9] Fix the early config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-03-10T16:39:59Z","receivedAt":"2017-03-10T16:40:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n>> > > Yes, exactly.  It would have been less confusing if I picked something\n>> > > that passed nongit_ok. Like hash-object:\n>> \n>> ... or like testing the early config directly?\n>\n> I was trying to demonstrate that the problem existed already without\n> your patch series.\n\nYup, they are not new breakages introduced by Dscho's change.\n\n>> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>> Subject: [PATCH] t1309: document cases where we would want early config not to\n>>  die()\n>> \n>> Jeff King came up with a couple examples that demonstrate how the new\n>> read_early_config() that looks harder for the current .git/ directory\n>> could die() in an undesirable way.\n>> \n>> Let's add those cases to the test script, to document what we would like\n>> to happen when early config encounters problems.\n>\n> Yep, these all look fine.\n\nOK.  Thanks, both.\n"},{"id":"313770","messageId":"xmqqefy5yn4k.fsf@gitster.mtv.corp.google.com","threadId":"44653","inReplyTo":"a1e24f1b31773f4d2f7f06ab7d5870e920211d51.1489098170.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v5 10/11] setup_git_directory_gently_1(): avoid die()ing","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-03-10T18:58:51Z","receivedAt":"2017-03-10T18:59:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> @@ -890,14 +892,22 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n>  \tif (one_filesystem)\n>  \t\tcurrent_device = get_device_or_die(dir->buf, NULL, 0);\n>  \tfor (;;) {\n> -\t\tint offset = dir->len;\n> +\t\tint offset = dir->len, error_code = 0;\n>  \n>  \t\tif (offset > min_offset)\n>  \t\t\tstrbuf_addch(dir, '/');\n>  \t\tstrbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);\n> -\t\tgitdirenv = read_gitfile(dir->buf);\n> -\t\tif (!gitdirenv && is_git_directory(dir->buf))\n> -\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n> +\t\tgitdirenv = read_gitfile_gently(dir->buf, die_on_error ?\n> +\t\t\t\t\t\tNULL : &error_code);\n> +\t\tif (!gitdirenv) {\n\nWe tried to read \".git\" as a regular file, but couldn't.  There are\nsome cases but I am having trouble to convince myself all cases are\ncovered correctly here, so let me follow the code aloud.\n\n> +\t\t\tif (die_on_error ||\n> +\t\t\t    error_code == READ_GITFILE_ERR_NOT_A_FILE) {\n> +\t\t\t\tif (is_git_directory(dir->buf))\n> +\t\t\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n\nIf we are told to die on error, but if it is a Git directory, then\nwe do not have to (and want to) die and instead report that\ndirectory as discovered.\n\nIf we are to report the failure status instead of dying, we also do\nwant to recover when the read_gitfile_gentrly() failed only because\nit was a real Git directory.  READ_GITFILE_ERR_NOT_A_FILE could be a\nsignal for that, and we recover after making sure it is a Git\ndirectory.\n\nAmong the READ_GITFILE_ERR_* codes, ERR_NOT_A_FILE is the only one\nwe attempt this recovery.  All others just take the \"else if\" thing\nbelow.\n\nWhat happens when is_git_directory() test here does not pass,\nthough?  Let's say stat() in read_gitfile_gently() found a FIFO\nthere, it gives us ERR_NOT_A_FILE, is_git_directory() would say\n\"Nah\", and then ...?  Don't we want the other side for this if()\nstatement that returns failure?\n\n> +\t\t\t} else if (error_code &&\n> +\t\t\t\t   error_code != READ_GITFILE_ERR_STAT_FAILED)\n> +\t\t\t\treturn GIT_DIR_INVALID_GITFILE;\n> +\t\t}\n\nOn the other hand, if read_gitfile_gently() didn't say \"we found\nsomething that is not a regular file\" we come here.  If the error\ncame because there wasn't \".git\" in the directory we are looking at,\ni.e. stat(2) in read_gitfile_gently() gave ENOENT, it is perfectly\nnormal and we just want to go one level up.\n\nBut shouldn't read_gitfile_gently() be inspecting errno when it sees\na stat() failure?  If there _is_ \".git\" but we failed to stat it for\nwhatever reason (EACCES, ELOOP,...), we do not want to go up but\ngive the INVALID_GITFILE from here, just like other cases, no?\nFor that I imagine that ERR_STAT_FAILED needs to be split into two,\none for true ERR_STAT_FAILED (from which we cannot recover) and the\nother for ERR_ENOENT to signal us that there is nothing there (which\nallows us to go up).\n\nBy the way, is the \"error_code &&\" needed?  Unless the original path\ngiven to read_gitfile_gently() is a NULL pointer, the only time its\nreturn value is NULL is when it has non-zero error_code.\n\n\n>  \t\tstrbuf_setlen(dir, offset);\n>  \t\tif (gitdirenv) {\n>  \t\t\tstrbuf_addstr(gitdir, gitdirenv);\n> @@ -934,7 +944,7 @@ const char *discover_git_directory(struct strbuf *gitdir)\n>  \t\treturn NULL;\n>  \n>  \tcwd_len = dir.len;\n> -\tif (setup_git_directory_gently_1(&dir, gitdir) <= 0) {\n> +\tif (setup_git_directory_gently_1(&dir, gitdir, 0) <= 0) {\n>  \t\tstrbuf_release(&dir);\n>  \t\treturn NULL;\n>  \t}\n> @@ -994,7 +1004,7 @@ const char *setup_git_directory_gently(int *nongit_ok)\n>  \t\tdie_errno(_(\"Unable to read current working directory\"));\n>  \tstrbuf_addbuf(&dir, &cwd);\n>  \n> -\tswitch (setup_git_directory_gently_1(&dir, &gitdir)) {\n> +\tswitch (setup_git_directory_gently_1(&dir, &gitdir, 1)) {\n>  \tcase GIT_DIR_NONE:\n>  \t\tprefix = NULL;\n>  \t\tbreak;\n"},{"id":"313774","messageId":"xmqqa88tymys.fsf@gitster.mtv.corp.google.com","threadId":"44653","inReplyTo":"72b36d27cefc0060f170b33f19cf504bb0338c1c.1489098170.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v5 09/11] Test read_early_config()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-03-10T19:02:19Z","receivedAt":"2017-03-10T19:02:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> Subject: Re: [PATCH v5 09/11] Test read_early_config()\n\nLet's retitle it to\n\n\tt1309: test read_early_config()\n\n> So far, we had no explicit tests of that function.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  t/helper/test-config.c  | 15 +++++++++++++++\n>  t/t1309-early-config.sh | 50 +++++++++++++++++++++++++++++++++++++++++++++++++\n>  2 files changed, 65 insertions(+)\n>  create mode 100755 t/t1309-early-config.sh\n>\n> diff --git a/t/helper/test-config.c b/t/helper/test-config.c\n> index 83a4f2ab869..8e3ed6a76cb 100644\n> --- a/t/helper/test-config.c\n> +++ b/t/helper/test-config.c\n> @@ -66,6 +66,16 @@ static int iterate_cb(const char *var, const char *value, void *data)\n>  \treturn 0;\n>  }\n>  \n> +static int early_config_cb(const char *var, const char *value, void *vdata)\n> +{\n> +\tconst char *key = vdata;\n> +\n> +\tif (!strcmp(key, var))\n> +\t\tprintf(\"%s\\n\", value);\n> +\n> +\treturn 0;\n> +}\n> +\n>  int cmd_main(int argc, const char **argv)\n>  {\n>  \tint i, val;\n> @@ -73,6 +83,11 @@ int cmd_main(int argc, const char **argv)\n>  \tconst struct string_list *strptr;\n>  \tstruct config_set cs;\n>  \n> +\tif (argc == 3 && !strcmp(argv[1], \"read_early_config\")) {\n> +\t\tread_early_config(early_config_cb, (void *)argv[2]);\n> +\t\treturn 0;\n> +\t}\n> +\n>  \tsetup_git_directory();\n\nMakes perfect sense to have this as the very beginning, before we\neven do the usual setup ;-)\n\n"},{"id":"313777","messageId":"xmqq4lz1ylgv.fsf@gitster.mtv.corp.google.com","threadId":"44653","inReplyTo":"bafa0c8e7a760aa26add5e3f757062c230fbd6a9.1489098170.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v5 04/11] setup_git_directory_1(): avoid changing global state","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-03-10T19:34:40Z","receivedAt":"2017-03-10T19:34:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n>  /*\n>   * We cannot decide in this function whether we are in the work tree or\n>   * not, since the config can only be read _after_ this function was called.\n> + *\n> + * Also, we avoid changing any global state (such as the current working\n> + * directory) to allow early callers.\n> + *\n> + * The directory where the search should start needs to be passed in via the\n> + * `dir` parameter; upon return, the `dir` buffer will contain the path of\n> + * the directory where the search ended, and `gitdir` will contain the path of\n> + * the discovered .git/ directory, if any. This path may be relative against\n> + * `dir` (i.e. *not* necessarily the cwd).\n\nI had to read the last sentence three times because my earlier two\nattempts misread/misinterpreted as \"this may be relative to cwd, but\nnot necessarily, because this may be relative to dir\".  The correct\nreading is \"when this is relative, it is relative to dir.  It would\nnot be relative to cwd if you start with dir that is not cwd\".\n\nIt would be nicer if all readers can get to that without re-reading\nthree times.\n\n> -static const char *setup_git_directory_gently_1(int *nongit_ok)\n> +static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n> ...\n> @@ -889,63 +888,104 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n>  \t */\n>  \tone_filesystem = !git_env_bool(\"GIT_DISCOVERY_ACROSS_FILESYSTEM\", 0);\n>  \tif (one_filesystem)\n> -\t\tcurrent_device = get_device_or_die(\".\", NULL, 0);\n> +\t\tcurrent_device = get_device_or_die(dir->buf, NULL, 0);\n>  \tfor (;;) {\n> -\t\tgitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);\n> -\t\tif (gitfile)\n> -\t\t\tgitdirenv = gitfile = xstrdup(gitfile);\n> -\t\telse {\n> -\t\t\tif (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))\n> -\t\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n> +\t\tint offset = dir->len;\n> +\n> +\t\tif (offset > min_offset)\n> +\t\t\tstrbuf_addch(dir, '/');\n> +\t\tstrbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);\n> +\t\tgitdirenv = read_gitfile(dir->buf);\n> +\t\tif (!gitdirenv && is_git_directory(dir->buf))\n> +\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n> +\t\tstrbuf_setlen(dir, offset);\n> +\t\tif (gitdirenv) {\n> +\t\t\tstrbuf_addstr(gitdir, gitdirenv);\n> +\t\t\treturn GIT_DIR_DISCOVERED;\n>  \t\t}\n\nI commented on this part more extensively in a later step of the\nseries after the read_gitfile() call is replaced with the non-dying\nversion.  I see that issues in corner cases are inherited from the\ncode even before this step.  \n\nWe'd either want to at least document the corner cases that are not\nhandled with /* NEEDSWORK: */ in-code comments , or address them in\na follow-up series before we forget.  They are not new problems, so\nI am OK if we choose to leave them broken, as people haven't triggered\nthem in the current code.\n\nThanks.\n"},{"id":"313900","messageId":"alpine.DEB.2.20.1703131819160.3767@virtualbox","threadId":"44653","inReplyTo":"xmqqa88tymys.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v5 09/11] Test read_early_config()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-13T17:19:57Z","receivedAt":"2017-03-13T17:20:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Fri, 10 Mar 2017, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > Subject: Re: [PATCH v5 09/11] Test read_early_config()\n> \n> Let's retitle it to\n> \n> \tt1309: test read_early_config()\n\nI specifically avoided that, as it would sound as if I modified t1309. But\nI *added* that file.\n\nCiao,\nDscho\n"},{"id":"313907","messageId":"xmqq37eht741.fsf@gitster.mtv.corp.google.com","threadId":"44653","inReplyTo":"alpine.DEB.2.20.1703131819160.3767@virtualbox","subject":"Re: [PATCH v5 09/11] Test read_early_config()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-03-13T17:32:46Z","receivedAt":"2017-03-13T17:32:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n>> \n>> > Subject: Re: [PATCH v5 09/11] Test read_early_config()\n>> \n>> Let's retitle it to\n>> \n>> \tt1309: test read_early_config()\n>\n> I specifically avoided that, as it would sound as if I modified t1309. But\n> I *added* that file.\n\nThen please stop specifically avoiding that style.\n\nFor a reader of the shortlog, a title that identifies the area (and\nin t/ scripts, it is common to give the test file name as the\nidentification) it touches gives useful information, i.e. \"Thanks to\nthis commit, we have a test for read-early-config in t1309 now\".\nDoing s/in t1309 now/somewhere now/ makes the title less useful.\n\nThanks.\n"},{"id":"313935","messageId":"alpine.DEB.2.20.1703131826330.3767@virtualbox","threadId":"44653","inReplyTo":"xmqqefy5yn4k.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v5 10/11] setup_git_directory_gently_1(): avoid die()ing","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-13T19:38:41Z","receivedAt":"2017-03-13T19:39:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nPLEASE NOTE: the purpose of this patch series is to allow the same\nfunction (setup_git_directory_gently_1()) to be the work horse for\nsetup_git_directory() as before, but also for the new\ndiscover_git_directory() function that is introduced to fix\nread_early_config() to avoid hardconfig .git/config.\n\nThe purpose is *not* to change any current behavior. Please do not ask me\nto do that in this patch series.\n\nNow to your comments:\n\nOn Fri, 10 Mar 2017, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > @@ -890,14 +892,22 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n> >  \tif (one_filesystem)\n> >  \t\tcurrent_device = get_device_or_die(dir->buf, NULL, 0);\n> >  \tfor (;;) {\n> > -\t\tint offset = dir->len;\n> > +\t\tint offset = dir->len, error_code = 0;\n> >  \n> >  \t\tif (offset > min_offset)\n> >  \t\t\tstrbuf_addch(dir, '/');\n> >  \t\tstrbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);\n> > -\t\tgitdirenv = read_gitfile(dir->buf);\n> > -\t\tif (!gitdirenv && is_git_directory(dir->buf))\n> > -\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n> > +\t\tgitdirenv = read_gitfile_gently(dir->buf, die_on_error ?\n> > +\t\t\t\t\t\tNULL : &error_code);\n> > +\t\tif (!gitdirenv) {\n> \n> We tried to read \".git\" as a regular file, but couldn't.  There are\n> some cases but I am having trouble to convince myself all cases are\n> covered correctly here, so let me follow the code aloud.\n\nIt is subtle and finicky, I agree.\n\n> > +\t\t\tif (die_on_error ||\n> > +\t\t\t    error_code == READ_GITFILE_ERR_NOT_A_FILE) {\n> > +\t\t\t\tif (is_git_directory(dir->buf))\n> > +\t\t\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n> \n> If we are told to die on error, but if it is a Git directory, then\n> we do not have to (and want to) die and instead report that\n> directory as discovered.\n> \n> If we are to report the failure status instead of dying, we also do\n> want to recover when the read_gitfile_gentrly() failed only because\n> it was a real Git directory.  READ_GITFILE_ERR_NOT_A_FILE could be a\n> signal for that, and we recover after making sure it is a Git\n> directory.\n> \n> Among the READ_GITFILE_ERR_* codes, ERR_NOT_A_FILE is the only one\n> we attempt this recovery.  All others just take the \"else if\" thing\n> below.\n> \n> What happens when is_git_directory() test here does not pass,\n> though?  Let's say stat() in read_gitfile_gently() found a FIFO\n> there, it gives us ERR_NOT_A_FILE, is_git_directory() would say\n> \"Nah\", and then ...?  Don't we want the other side for this if()\n> statement that returns failure?\n\nThe current code as per `master` detects the NOT_A_FILE condition, and as\nthe parameter return_error_code was passed as NULL (because read_gitfile is\nactually #define'd in cache.h to call read_gitfile_gently with NULL as\nsecond parameter), it calls read_gitfile_error_die(). That function\n*ignores* the NOT_A_FILE error condition, and as a consequence returns to\nread_gitfile_gently() which then returns NULL because there *was* an\nerror_code.\n\nThat means that setup_git_directory_gently_1() will retrieve a NULL from\nthe read_gitfile() call, which means it then goes on to test whether it\nis looking at a .git/ directory via is_git_directory() and if that returns\nfalse, the loop will continue to look at the *parent* directory.\n\nThat, in turn, means that what you are asking for is a change in behavior,\nand as I am unwilling to introduce a change in behavior with this patch\nseries, my patch does the exact right thing.\n\n> > +\t\t\t} else if (error_code &&\n> > +\t\t\t\t   error_code != READ_GITFILE_ERR_STAT_FAILED)\n> > +\t\t\t\treturn GIT_DIR_INVALID_GITFILE;\n> > +\t\t}\n> \n> On the other hand, if read_gitfile_gently() didn't say \"we found\n> something that is not a regular file\" we come here.  If the error\n> came because there wasn't \".git\" in the directory we are looking at,\n> i.e. stat(2) in read_gitfile_gently() gave ENOENT, it is perfectly\n> normal and we just want to go one level up.\n> \n> But shouldn't read_gitfile_gently() be inspecting errno when it sees\n> a stat() failure?  If there _is_ \".git\" but we failed to stat it for\n> whatever reason (EACCES, ELOOP,...), we do not want to go up but\n> give the INVALID_GITFILE from here, just like other cases, no?\n> For that I imagine that ERR_STAT_FAILED needs to be split into two,\n> one for true ERR_STAT_FAILED (from which we cannot recover) and the\n> other for ERR_ENOENT to signal us that there is nothing there (which\n> allows us to go up).\n\nTrue. But again, you are asking for a *change in behavior*, which is not\nthe purpose of this patch series.\n\n> By the way, is the \"error_code &&\" needed?\n\nIt is not! I had the order of the if/else blocks completely differently\noriginally [*1*] and just overlooked that the error_code could no longer\nbe 0 in that condition.\n\nCiao,\nDscho\n\nFootnote *1*: Yes, I wanted to construct an English sentence with three\n-ly words in a row.\n"},{"id":"313937","messageId":"xmqqzigpq7qp.fsf@gitster.mtv.corp.google.com","threadId":"44653","inReplyTo":"alpine.DEB.2.20.1703131826330.3767@virtualbox","subject":"Re: [PATCH v5 10/11] setup_git_directory_gently_1(): avoid die()ing","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-03-13T19:47:26Z","receivedAt":"2017-03-13T19:47:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> PLEASE NOTE: the purpose of this patch series is to allow the same\n> function (setup_git_directory_gently_1()) to be the work horse for\n> setup_git_directory() as before, but also for the new\n> discover_git_directory() function that is introduced to fix\n> read_early_config() to avoid hardconfig .git/config.\n>\n> The purpose is *not* to change any current behavior. Please do not ask me\n> to do that in this patch series.\n\nPlease note that pointing out potential problems is only asking for\nconfirmation (\"yes that is bad\") or for enlightenment (\"no, you are\nreading the code wrong and that bad thing does not happen\nbecause...\").  Even in the former case, unless the badness is\nintroduced by the change, pointing out potential problems is *NOT*\nasking to change the patch to fix existing issues.\n\nThe former case may split into two.  \"Yes I agree that is bad and it\nis the same badness as the version without this change\", in which\ncase we may want to leave a \"NEEDSWORK\" comment in-code so that\npeople can notice when browsing the code (but fixing the badness\nwould be outside the scope of the series), and \"yes that is bad and\nwe shouldn't introduce that badness\", in which case we do want to\nfix it in the patch.\n\nSo do not read my (or anybody else's) reviews as _asking_ changes\nand making further fixes, and you'll do better.\n"},{"id":"313942","messageId":"84e77c62345c99911bed5948fdb83bd96d4bfb7f.1489435755.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489435755.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 01/12] t7006: replace dubious test","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-13T20:09:31Z","receivedAt":"2017-03-13T20:09:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The idea of the test case \"git -p - core.pager is not used from\nsubdirectory\" was to verify that the setup_git_directory() function had\nnot been called just to obtain the core.pager setting.\n\nHowever, we are about to fix the early config machinery so that it\n*does* work, without messing up the global state.\n\nOnce that is done, the core.pager setting *will* be used, even when\nrunning from a subdirectory, and that is a Good Thing.\n\nThe intention of that test case, however, was to verify that the\nsetup_git_directory() function has not run, because it changes global\nstate such as the current working directory.\n\nTo keep that spirit, but fix the incorrect assumption, this patch\nreplaces that test case by a new one that verifies that the pager is\nrun in the subdirectory, i.e. that the current working directory has\nnot been changed at the time the pager is configured and launched, even\nif the `rev-parse` command requires a .git/ directory and *will* change\nthe working directory.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t7006-pager.sh | 12 +++++++++++-\n 1 file changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t7006-pager.sh b/t/t7006-pager.sh\nindex c8dc665f2fd..304ae06c600 100755\n--- a/t/t7006-pager.sh\n+++ b/t/t7006-pager.sh\n@@ -378,9 +378,19 @@ test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p request-pull'\n test_default_pager        expect_success test_must_fail 'git -p'\n test_PAGER_overrides      expect_success test_must_fail 'git -p'\n test_local_config_ignored expect_failure test_must_fail 'git -p'\n-test_no_local_config_subdir expect_success test_must_fail 'git -p'\n test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p'\n \n+test_expect_failure TTY 'core.pager in repo config works and retains cwd' '\n+\tsane_unset GIT_PAGER &&\n+\ttest_config core.pager \"cat >cwd-retained\" &&\n+\t(\n+\t\tcd sub &&\n+\t\trm -f cwd-retained &&\n+\t\ttest_terminal git -p rev-parse HEAD &&\n+\t\ttest_path_is_file cwd-retained\n+\t)\n+'\n+\n test_doesnt_paginate      expect_failure test_must_fail 'git -p nonsense'\n \n test_pager_choices                       'git shortlog'\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313943","messageId":"cover.1489435755.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489098170.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 00/12] Fix the early config","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-13T20:09:26Z","receivedAt":"2017-03-13T20:09:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"These patches are an attempt to make Git's startup sequence a bit less\nsurprising.\n\nThe idea here is to refactor setup_git_directory()'s code so that the\nunderlying .git/ directory discovery can be run without changing any\nglobal state nor the current working directory, and then to use that\nfunctionality also in read_early_config().\n\nMy own use case: in the GVFS Git fork, we need to execute pre-command\nand post-command hooks before and after *every* Git command. A previous\nversion of the pre-command/post-command hook support was broken, as it\nused run_hook() which implicitly called setup_git_directory() too early.\nThe discover_git_directory() function (and due to core.hooksPath also\nthe read_early_config() function) helped me fix this.\n\nNotable notes:\n\n- Even if it can cause surprising problems, `init` and `clone` are not\n  special-cased. Rationale: it would introduce technical debt and\n  violate the Principle Of Least Astonishment.\n\n- The read_early_config() function does not cache Git directory\n  discovery nor read values. This is left for another patch series, if\n  it ever becomes necessary.\n\n- The alias handling in git.c could possibly benefit from this work, but\n  again, this is a separate topic from the current patch series.\n\nChanges since v5:\n\n- Reworded comment about `gitdir` being relative to `dir` so that it\n  does not confuse Junio anymore\n\n- Removed a superfluous `error_code &&` in an if() condition\n\n- Reworded the oneline of 9/11\n\n- Added a new patch at the end that marks the two NEEDSWORK issues that\n  Junio identified (but that should not be addressed in this patch\n  series, of course, because the entire idea of the early-config work is\n  to *preserve* the current behavior of setup_git_directory() while making\n  it possible for read_early_config() to reuse the same code paths)\n\n\nJohannes Schindelin (12):\n  t7006: replace dubious test\n  setup_git_directory(): use is_dir_sep() helper\n  Prepare setup_discovered_git_directory() the root directory\n  setup_git_directory_1(): avoid changing global state\n  Introduce the discover_git_directory() function\n  Make read_early_config() reusable\n  read_early_config(): avoid .git/config hack when unneeded\n  read_early_config(): really discover .git/\n  Add t1309 to test read_early_config()\n  setup_git_directory_gently_1(): avoid die()ing\n  t1309: document cases where we would want early config not to die()\n  setup.c: mention unresolved problems\n\n cache.h                 |   8 ++\n config.c                |  25 +++++\n pager.c                 |  31 ------\n setup.c                 | 253 +++++++++++++++++++++++++++++++++---------------\n t/helper/test-config.c  |  15 +++\n t/t1309-early-config.sh |  75 ++++++++++++++\n t/t7006-pager.sh        |  18 +++-\n 7 files changed, 314 insertions(+), 111 deletions(-)\n create mode 100755 t/t1309-early-config.sh\n\n\nbase-commit: d6db3f216544d05e09159756812ccbcb16861d71\nPublished-As: https://github.com/dscho/git/releases/tag/early-config-v6\nFetch-It-Via: git fetch https://github.com/dscho/git early-config-v6\n\nInterdiff vs v5:\n\n diff --git a/setup.c b/setup.c\n index 6733ba5fe82..64f922a9378 100644\n --- a/setup.c\n +++ b/setup.c\n @@ -531,6 +531,7 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)\n  \tssize_t len;\n  \n  \tif (stat(path, &st)) {\n +\t\t/* NEEDSWORK: discern between ENOENT vs other errors */\n  \t\terror_code = READ_GITFILE_ERR_STAT_FAILED;\n  \t\tgoto cleanup_return;\n  \t}\n @@ -839,8 +840,8 @@ enum discovery_result {\n   * The directory where the search should start needs to be passed in via the\n   * `dir` parameter; upon return, the `dir` buffer will contain the path of\n   * the directory where the search ended, and `gitdir` will contain the path of\n - * the discovered .git/ directory, if any. This path may be relative against\n - * `dir` (i.e. *not* necessarily the cwd).\n + * the discovered .git/ directory, if any. If `gitdir` is not absolute, it\n + * is relative to `dir` (i.e. *not* necessarily the cwd).\n   */\n  static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n  \t\t\t\t\t\t\t  struct strbuf *gitdir,\n @@ -902,10 +903,10 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n  \t\tif (!gitdirenv) {\n  \t\t\tif (die_on_error ||\n  \t\t\t    error_code == READ_GITFILE_ERR_NOT_A_FILE) {\n +\t\t\t\t/* NEEDSWORK: fail if .git is not file nor dir */\n  \t\t\t\tif (is_git_directory(dir->buf))\n  \t\t\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n -\t\t\t} else if (error_code &&\n -\t\t\t\t   error_code != READ_GITFILE_ERR_STAT_FAILED)\n +\t\t\t} else if (error_code != READ_GITFILE_ERR_STAT_FAILED)\n  \t\t\t\treturn GIT_DIR_INVALID_GITFILE;\n  \t\t}\n  \t\tstrbuf_setlen(dir, offset);\n\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n"},{"id":"313944","messageId":"dd755b3cb3a8ad409ee126c4b00651ad5b9707c7.1489435755.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489435755.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 02/12] setup_git_directory(): use is_dir_sep() helper","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-13T20:09:35Z","receivedAt":"2017-03-13T20:09:57Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It is okay in practice to test for forward slashes in the output of\ngetcwd(), because we go out of our way to convert backslashes to forward\nslashes in getcwd()'s output on Windows.\n\nStill, the correct way to test for a dir separator is by using the\nhelper function we introduced for that very purpose. It also serves as a\ngood documentation what the code tries to do (not \"how\").\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/setup.c b/setup.c\nindex 8f64fbdfb28..2ac891d4b9a 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -910,7 +910,9 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t\t\treturn setup_bare_git_dir(&cwd, offset, nongit_ok);\n \n \t\toffset_parent = offset;\n-\t\twhile (--offset_parent > ceil_offset && cwd.buf[offset_parent] != '/');\n+\t\twhile (--offset_parent > ceil_offset &&\n+\t\t       !is_dir_sep(cwd.buf[offset_parent]))\n+\t\t\t; /* continue */\n \t\tif (offset_parent <= ceil_offset)\n \t\t\treturn setup_nongit(cwd.buf, nongit_ok);\n \t\tif (one_filesystem) {\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313945","messageId":"8d8be2b095afb215f5bf0e0bb7989c9c9ab3f471.1489435755.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489435755.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 03/12] Prepare setup_discovered_git_directory() the root directory","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-13T20:09:44Z","receivedAt":"2017-03-13T20:10:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Currently, the offset parameter (indicating what part of the cwd\nparameter corresponds to the current directory after discovering the\n.git/ directory) is set to 0 when we are running in the root directory.\n\nHowever, in the next patches we will avoid changing the current working\ndirectory while searching for the .git/ directory, meaning that the\noffset corresponding to the root directory will have to be 1 to reflect\nthat this directory is characterized by the path \"/\" (and not \"\").\n\nSo let's make sure that setup_discovered_git_directory() only tries to\nappend the trailing slash to non-root directories.\n\nNote: the setup_bare_git_directory() does not need a corresponding\nchange, as it does not want to return a prefix.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 2ac891d4b9a..23114cb7aa3 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -721,8 +721,10 @@ static const char *setup_discovered_git_dir(const char *gitdir,\n \tif (offset == cwd->len)\n \t\treturn NULL;\n \n-\t/* Make \"offset\" point to past the '/', and add a '/' at the end */\n-\toffset++;\n+\t/* Make \"offset\" point past the '/' (already the case for root dirs) */\n+\tif (offset != offset_1st_component(cwd->buf))\n+\t\toffset++;\n+\t/* Add a '/' at the end */\n \tstrbuf_addch(cwd, '/');\n \treturn cwd->buf + offset;\n }\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313946","messageId":"20a4dbf772851506f9373ab3992e385ba9e7cb1f.1489435755.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489435755.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 04/12] setup_git_directory_1(): avoid changing global state","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-13T20:10:42Z","receivedAt":"2017-03-13T20:11:05Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"For historical reasons, Git searches for the .git/ directory (or the\n.git file) by changing the working directory successively to the parent\ndirectory of the current directory, until either anything was found or\nuntil a ceiling or a mount point is hit.\n\nFurther global state may be changed in case a .git/ directory was found.\n\nWe do have a use case, though, where we would like to find the .git/\ndirectory without having any global state touched, though: when we read\nthe early config e.g. for the pager or for alias expansion.\n\nLet's just move all of code that changes any global state out of the\nfunction `setup_git_directory_gently_1()` into\n`setup_git_directory_gently()`.\n\nIn subsequent patches, we will use the _1() function in a new\n`discover_git_directory()` function that we will then use for the early\nconfig code.\n\nNote: the new loop is a *little* tricky, as we have to handle the root\ndirectory specially: we cannot simply strip away the last component\nincluding the slash, as the root directory only has that slash. To remedy\nthat, we introduce the `min_offset` variable that holds the minimal length\nof an absolute path, and using that to special-case the root directory,\nincluding an early exit before trying to find the parent of the root\ndirectory.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 193 +++++++++++++++++++++++++++++++++++++++-------------------------\n 1 file changed, 118 insertions(+), 75 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 23114cb7aa3..99a722ed5f9 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -818,50 +818,49 @@ static int canonicalize_ceiling_entry(struct string_list_item *item,\n \t}\n }\n \n+enum discovery_result {\n+\tGIT_DIR_NONE = 0,\n+\tGIT_DIR_EXPLICIT,\n+\tGIT_DIR_DISCOVERED,\n+\tGIT_DIR_BARE,\n+\t/* these are errors */\n+\tGIT_DIR_HIT_CEILING = -1,\n+\tGIT_DIR_HIT_MOUNT_POINT = -2\n+};\n+\n /*\n  * We cannot decide in this function whether we are in the work tree or\n  * not, since the config can only be read _after_ this function was called.\n+ *\n+ * Also, we avoid changing any global state (such as the current working\n+ * directory) to allow early callers.\n+ *\n+ * The directory where the search should start needs to be passed in via the\n+ * `dir` parameter; upon return, the `dir` buffer will contain the path of\n+ * the directory where the search ended, and `gitdir` will contain the path of\n+ * the discovered .git/ directory, if any. If `gitdir` is not absolute, it\n+ * is relative to `dir` (i.e. *not* necessarily the cwd).\n  */\n-static const char *setup_git_directory_gently_1(int *nongit_ok)\n+static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n+\t\t\t\t\t\t\t  struct strbuf *gitdir)\n {\n \tconst char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);\n \tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n-\tstatic struct strbuf cwd = STRBUF_INIT;\n-\tconst char *gitdirenv, *ret;\n-\tchar *gitfile;\n-\tint offset, offset_parent, ceil_offset = -1;\n+\tconst char *gitdirenv;\n+\tint ceil_offset = -1, min_offset = has_dos_drive_prefix(dir->buf) ? 3 : 1;\n \tdev_t current_device = 0;\n \tint one_filesystem = 1;\n \n \t/*\n-\t * We may have read an incomplete configuration before\n-\t * setting-up the git directory. If so, clear the cache so\n-\t * that the next queries to the configuration reload complete\n-\t * configuration (including the per-repo config file that we\n-\t * ignored previously).\n-\t */\n-\tgit_config_clear();\n-\n-\t/*\n-\t * Let's assume that we are in a git repository.\n-\t * If it turns out later that we are somewhere else, the value will be\n-\t * updated accordingly.\n-\t */\n-\tif (nongit_ok)\n-\t\t*nongit_ok = 0;\n-\n-\tif (strbuf_getcwd(&cwd))\n-\t\tdie_errno(_(\"Unable to read current working directory\"));\n-\toffset = cwd.len;\n-\n-\t/*\n \t * If GIT_DIR is set explicitly, we're not going\n \t * to do any discovery, but we still do repository\n \t * validation.\n \t */\n \tgitdirenv = getenv(GIT_DIR_ENVIRONMENT);\n-\tif (gitdirenv)\n-\t\treturn setup_explicit_git_dir(gitdirenv, &cwd, nongit_ok);\n+\tif (gitdirenv) {\n+\t\tstrbuf_addstr(gitdir, gitdirenv);\n+\t\treturn GIT_DIR_EXPLICIT;\n+\t}\n \n \tif (env_ceiling_dirs) {\n \t\tint empty_entry_found = 0;\n@@ -869,15 +868,15 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t\tstring_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);\n \t\tfilter_string_list(&ceiling_dirs, 0,\n \t\t\t\t   canonicalize_ceiling_entry, &empty_entry_found);\n-\t\tceil_offset = longest_ancestor_length(cwd.buf, &ceiling_dirs);\n+\t\tceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs);\n \t\tstring_list_clear(&ceiling_dirs, 0);\n \t}\n \n-\tif (ceil_offset < 0 && has_dos_drive_prefix(cwd.buf))\n-\t\tceil_offset = 1;\n+\tif (ceil_offset < 0)\n+\t\tceil_offset = min_offset - 2;\n \n \t/*\n-\t * Test in the following order (relative to the cwd):\n+\t * Test in the following order (relative to the dir):\n \t * - .git (file containing \"gitdir: <path>\")\n \t * - .git/\n \t * - ./ (bare)\n@@ -889,63 +888,104 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \t */\n \tone_filesystem = !git_env_bool(\"GIT_DISCOVERY_ACROSS_FILESYSTEM\", 0);\n \tif (one_filesystem)\n-\t\tcurrent_device = get_device_or_die(\".\", NULL, 0);\n+\t\tcurrent_device = get_device_or_die(dir->buf, NULL, 0);\n \tfor (;;) {\n-\t\tgitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);\n-\t\tif (gitfile)\n-\t\t\tgitdirenv = gitfile = xstrdup(gitfile);\n-\t\telse {\n-\t\t\tif (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))\n-\t\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n+\t\tint offset = dir->len;\n+\n+\t\tif (offset > min_offset)\n+\t\t\tstrbuf_addch(dir, '/');\n+\t\tstrbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);\n+\t\tgitdirenv = read_gitfile(dir->buf);\n+\t\tif (!gitdirenv && is_git_directory(dir->buf))\n+\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n+\t\tstrbuf_setlen(dir, offset);\n+\t\tif (gitdirenv) {\n+\t\t\tstrbuf_addstr(gitdir, gitdirenv);\n+\t\t\treturn GIT_DIR_DISCOVERED;\n \t\t}\n \n-\t\tif (gitdirenv) {\n-\t\t\tret = setup_discovered_git_dir(gitdirenv,\n-\t\t\t\t\t\t       &cwd, offset,\n-\t\t\t\t\t\t       nongit_ok);\n-\t\t\tfree(gitfile);\n-\t\t\treturn ret;\n+\t\tif (is_git_directory(dir->buf)) {\n+\t\t\tstrbuf_addstr(gitdir, \".\");\n+\t\t\treturn GIT_DIR_BARE;\n \t\t}\n-\t\tfree(gitfile);\n \n-\t\tif (is_git_directory(\".\"))\n-\t\t\treturn setup_bare_git_dir(&cwd, offset, nongit_ok);\n+\t\tif (offset <= min_offset)\n+\t\t\treturn GIT_DIR_HIT_CEILING;\n \n-\t\toffset_parent = offset;\n-\t\twhile (--offset_parent > ceil_offset &&\n-\t\t       !is_dir_sep(cwd.buf[offset_parent]))\n+\t\twhile (--offset > ceil_offset && !is_dir_sep(dir->buf[offset]))\n \t\t\t; /* continue */\n-\t\tif (offset_parent <= ceil_offset)\n-\t\t\treturn setup_nongit(cwd.buf, nongit_ok);\n-\t\tif (one_filesystem) {\n-\t\t\tdev_t parent_device = get_device_or_die(\"..\", cwd.buf,\n-\t\t\t\t\t\t\t\toffset);\n-\t\t\tif (parent_device != current_device) {\n-\t\t\t\tif (nongit_ok) {\n-\t\t\t\t\tif (chdir(cwd.buf))\n-\t\t\t\t\t\tdie_errno(_(\"Cannot come back to cwd\"));\n-\t\t\t\t\t*nongit_ok = 1;\n-\t\t\t\t\treturn NULL;\n-\t\t\t\t}\n-\t\t\t\tstrbuf_setlen(&cwd, offset);\n-\t\t\t\tdie(_(\"Not a git repository (or any parent up to mount point %s)\\n\"\n-\t\t\t\t\"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\"),\n-\t\t\t\t    cwd.buf);\n-\t\t\t}\n-\t\t}\n-\t\tif (chdir(\"..\")) {\n-\t\t\tstrbuf_setlen(&cwd, offset);\n-\t\t\tdie_errno(_(\"Cannot change to '%s/..'\"), cwd.buf);\n-\t\t}\n-\t\toffset = offset_parent;\n+\t\tif (offset <= ceil_offset)\n+\t\t\treturn GIT_DIR_HIT_CEILING;\n+\n+\t\tstrbuf_setlen(dir, offset > min_offset ?  offset : min_offset);\n+\t\tif (one_filesystem &&\n+\t\t    current_device != get_device_or_die(dir->buf, NULL, offset))\n+\t\t\treturn GIT_DIR_HIT_MOUNT_POINT;\n \t}\n }\n \n const char *setup_git_directory_gently(int *nongit_ok)\n {\n+\tstatic struct strbuf cwd = STRBUF_INIT;\n+\tstruct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT;\n \tconst char *prefix;\n \n-\tprefix = setup_git_directory_gently_1(nongit_ok);\n+\t/*\n+\t * We may have read an incomplete configuration before\n+\t * setting-up the git directory. If so, clear the cache so\n+\t * that the next queries to the configuration reload complete\n+\t * configuration (including the per-repo config file that we\n+\t * ignored previously).\n+\t */\n+\tgit_config_clear();\n+\n+\t/*\n+\t * Let's assume that we are in a git repository.\n+\t * If it turns out later that we are somewhere else, the value will be\n+\t * updated accordingly.\n+\t */\n+\tif (nongit_ok)\n+\t\t*nongit_ok = 0;\n+\n+\tif (strbuf_getcwd(&cwd))\n+\t\tdie_errno(_(\"Unable to read current working directory\"));\n+\tstrbuf_addbuf(&dir, &cwd);\n+\n+\tswitch (setup_git_directory_gently_1(&dir, &gitdir)) {\n+\tcase GIT_DIR_NONE:\n+\t\tprefix = NULL;\n+\t\tbreak;\n+\tcase GIT_DIR_EXPLICIT:\n+\t\tprefix = setup_explicit_git_dir(gitdir.buf, &cwd, nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_DISCOVERED:\n+\t\tif (dir.len < cwd.len && chdir(dir.buf))\n+\t\t\tdie(_(\"Cannot change to '%s'\"), dir.buf);\n+\t\tprefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len,\n+\t\t\t\t\t\t  nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_BARE:\n+\t\tif (dir.len < cwd.len && chdir(dir.buf))\n+\t\t\tdie(_(\"Cannot change to '%s'\"), dir.buf);\n+\t\tprefix = setup_bare_git_dir(&cwd, dir.len, nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_HIT_CEILING:\n+\t\tprefix = setup_nongit(cwd.buf, nongit_ok);\n+\t\tbreak;\n+\tcase GIT_DIR_HIT_MOUNT_POINT:\n+\t\tif (nongit_ok) {\n+\t\t\t*nongit_ok = 1;\n+\t\t\tstrbuf_release(&cwd);\n+\t\t\tstrbuf_release(&dir);\n+\t\t\treturn NULL;\n+\t\t}\n+\t\tdie(_(\"Not a git repository (or any parent up to mount point %s)\\n\"\n+\t\t      \"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\"),\n+\t\t    dir.buf);\n+\tdefault:\n+\t\tdie(\"BUG: unhandled setup_git_directory_1() result\");\n+\t}\n+\n \tif (prefix)\n \t\tsetenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);\n \telse\n@@ -954,6 +994,9 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \tstartup_info->have_repository = !nongit_ok || !*nongit_ok;\n \tstartup_info->prefix = prefix;\n \n+\tstrbuf_release(&dir);\n+\tstrbuf_release(&gitdir);\n+\n \treturn prefix;\n }\n \n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313948","messageId":"0bfe47c18ef3c156dc7aa1b201a97b84070c515c.1489435755.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489435755.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 05/12] Introduce the discover_git_directory() function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-13T20:10:45Z","receivedAt":"2017-03-13T20:11:21Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"We modified the setup_git_directory_gently_1() function earlier to make\nit possible to discover the GIT_DIR without changing global state.\n\nHowever, it is still a bit cumbersome to use if you only need to figure\nout the (possibly absolute) path of the .git/ directory. Let's just\nprovide a convenient wrapper function with an easier signature that\n*just* discovers the .git/ directory.\n\nWe will use it in a subsequent patch to fix the early config.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n cache.h |  7 +++++++\n setup.c | 43 +++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 50 insertions(+)\n\ndiff --git a/cache.h b/cache.h\nindex 8c0e6442076..086bd9fa433 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -518,6 +518,13 @@ extern void set_git_work_tree(const char *tree);\n #define ALTERNATE_DB_ENVIRONMENT \"GIT_ALTERNATE_OBJECT_DIRECTORIES\"\n \n extern void setup_work_tree(void);\n+/*\n+ * Find GIT_DIR of the repository that contains the current working directory,\n+ * without changing the working directory or other global state. The result is\n+ * appended to gitdir. The return value is either NULL if no repository was\n+ * found, or pointing to the path inside gitdir's buffer.\n+ */\n+extern const char *discover_git_directory(struct strbuf *gitdir);\n extern const char *setup_git_directory_gently(int *);\n extern const char *setup_git_directory(void);\n extern char *prefix_path(const char *prefix, int len, const char *path);\ndiff --git a/setup.c b/setup.c\nindex 99a722ed5f9..411e8342972 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -924,6 +924,49 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \t}\n }\n \n+const char *discover_git_directory(struct strbuf *gitdir)\n+{\n+\tstruct strbuf dir = STRBUF_INIT, err = STRBUF_INIT;\n+\tsize_t gitdir_offset = gitdir->len, cwd_len;\n+\tstruct repository_format candidate;\n+\n+\tif (strbuf_getcwd(&dir))\n+\t\treturn NULL;\n+\n+\tcwd_len = dir.len;\n+\tif (setup_git_directory_gently_1(&dir, gitdir) <= 0) {\n+\t\tstrbuf_release(&dir);\n+\t\treturn NULL;\n+\t}\n+\n+\t/*\n+\t * The returned gitdir is relative to dir, and if dir does not reflect\n+\t * the current working directory, we simply make the gitdir absolute.\n+\t */\n+\tif (dir.len < cwd_len && !is_absolute_path(gitdir->buf + gitdir_offset)) {\n+\t\t/* Avoid a trailing \"/.\" */\n+\t\tif (!strcmp(\".\", gitdir->buf + gitdir_offset))\n+\t\t\tstrbuf_setlen(gitdir, gitdir_offset);\n+\t\telse\n+\t\t\tstrbuf_addch(&dir, '/');\n+\t\tstrbuf_insert(gitdir, gitdir_offset, dir.buf, dir.len);\n+\t}\n+\n+\tstrbuf_reset(&dir);\n+\tstrbuf_addf(&dir, \"%s/config\", gitdir->buf + gitdir_offset);\n+\tread_repository_format(&candidate, dir.buf);\n+\tstrbuf_release(&dir);\n+\n+\tif (verify_repository_format(&candidate, &err) < 0) {\n+\t\twarning(\"ignoring git dir '%s': %s\",\n+\t\t\tgitdir->buf + gitdir_offset, err.buf);\n+\t\tstrbuf_release(&err);\n+\t\treturn NULL;\n+\t}\n+\n+\treturn gitdir->buf + gitdir_offset;\n+}\n+\n const char *setup_git_directory_gently(int *nongit_ok)\n {\n \tstatic struct strbuf cwd = STRBUF_INIT;\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313949","messageId":"c17676fd4fe5de06c62f316511150255e3d79d5c.1489435755.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489435755.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 08/12] read_early_config(): really discover .git/","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-13T20:11:12Z","receivedAt":"2017-03-13T20:11:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Earlier, we punted and simply assumed that we are in the top-level\ndirectory of the project, and that there is no .git file but a .git/\ndirectory so that we can read directly from .git/config.\n\nHowever, that is not necessarily true. We may be in a subdirectory. Or\n.git may be a gitfile. Or the environment variable GIT_DIR may be set.\n\nTo remedy this situation, we just refactored the way\nsetup_git_directory() discovers the .git/ directory, to make it\nreusable, and more importantly, to leave all global variables and the\ncurrent working directory alone.\n\nLet's discover the .git/ directory correctly in read_early_config() by\nusing that new function.\n\nThis fixes 4 known breakages in t7006.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n config.c         | 31 ++++++++++++-------------------\n t/t7006-pager.sh |  8 ++++----\n 2 files changed, 16 insertions(+), 23 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex fc4625a71dd..030acc50aa2 100644\n--- a/config.c\n+++ b/config.c\n@@ -1505,34 +1505,27 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \n void read_early_config(config_fn_t cb, void *data)\n {\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n \tgit_config_with_options(cb, data, NULL, 1);\n \n \t/*\n-\t * Note that this is a really dirty hack that does the wrong thing in\n-\t * many cases. The crux of the problem is that we cannot run\n-\t * setup_git_directory() early on in git's setup, so we have no idea if\n-\t * we are in a repository or not, and therefore are not sure whether\n-\t * and how to read repository-local config.\n-\t *\n-\t * So if we _aren't_ in a repository (or we are but we would reject its\n-\t * core.repositoryformatversion), we'll read whatever is in .git/config\n-\t * blindly. Similarly, if we _are_ in a repository, but not at the\n-\t * root, we'll fail to find .git/config (because it's really\n-\t * ../.git/config, etc), unless setup_git_directory() was already called.\n-\t * See t7006 for a complete set of failures.\n-\t *\n-\t * However, we have historically provided this hack because it does\n-\t * work some of the time (namely when you are at the top-level of a\n-\t * valid repository), and would rarely make things worse (i.e., you do\n-\t * not generally have a .git/config file sitting around).\n+\t * When setup_git_directory() was not yet asked to discover the\n+\t * GIT_DIR, we ask discover_git_directory() to figure out whether there\n+\t * is any repository config we should use (but unlike\n+\t * setup_git_directory_gently(), no global state is changed, most\n+\t * notably, the current working directory is still the same after the\n+\t * call).\n \t */\n-\tif (!have_git_dir()) {\n+\tif (!have_git_dir() && discover_git_directory(&buf)) {\n \t\tstruct git_config_source repo_config;\n \n \t\tmemset(&repo_config, 0, sizeof(repo_config));\n-\t\trepo_config.file = \".git/config\";\n+\t\tstrbuf_addstr(&buf, \"/config\");\n+\t\trepo_config.file = buf.buf;\n \t\tgit_config_with_options(cb, data, &repo_config, 1);\n \t}\n+\tstrbuf_release(&buf);\n }\n \n static void git_config_check_init(void);\ndiff --git a/t/t7006-pager.sh b/t/t7006-pager.sh\nindex 304ae06c600..4f3794d415e 100755\n--- a/t/t7006-pager.sh\n+++ b/t/t7006-pager.sh\n@@ -360,19 +360,19 @@ test_pager_choices                       'git aliasedlog'\n test_default_pager        expect_success 'git -p aliasedlog'\n test_PAGER_overrides      expect_success 'git -p aliasedlog'\n test_core_pager_overrides expect_success 'git -p aliasedlog'\n-test_core_pager_subdir    expect_failure 'git -p aliasedlog'\n+test_core_pager_subdir    expect_success 'git -p aliasedlog'\n test_GIT_PAGER_overrides  expect_success 'git -p aliasedlog'\n \n test_default_pager        expect_success 'git -p true'\n test_PAGER_overrides      expect_success 'git -p true'\n test_core_pager_overrides expect_success 'git -p true'\n-test_core_pager_subdir    expect_failure 'git -p true'\n+test_core_pager_subdir    expect_success 'git -p true'\n test_GIT_PAGER_overrides  expect_success 'git -p true'\n \n test_default_pager        expect_success test_must_fail 'git -p request-pull'\n test_PAGER_overrides      expect_success test_must_fail 'git -p request-pull'\n test_core_pager_overrides expect_success test_must_fail 'git -p request-pull'\n-test_core_pager_subdir    expect_failure test_must_fail 'git -p request-pull'\n+test_core_pager_subdir    expect_success test_must_fail 'git -p request-pull'\n test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p request-pull'\n \n test_default_pager        expect_success test_must_fail 'git -p'\n@@ -380,7 +380,7 @@ test_PAGER_overrides      expect_success test_must_fail 'git -p'\n test_local_config_ignored expect_failure test_must_fail 'git -p'\n test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p'\n \n-test_expect_failure TTY 'core.pager in repo config works and retains cwd' '\n+test_expect_success TTY 'core.pager in repo config works and retains cwd' '\n \tsane_unset GIT_PAGER &&\n \ttest_config core.pager \"cat >cwd-retained\" &&\n \t(\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313950","messageId":"8358f65bab86ba86589bcef15c01a66dd3a87b3d.1489435755.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489435755.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 07/12] read_early_config(): avoid .git/config hack when unneeded","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-13T20:11:08Z","receivedAt":"2017-03-13T20:11:32Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"So far, we only look whether the startup_info claims to have seen a\ngit_dir.\n\nHowever, do_git_config_sequence() (and consequently the\ngit_config_with_options() call used by read_early_config() asks the\nhave_git_dir() function whether we have a .git/ directory, which in turn\nalso looks at git_dir and at the environment variable GIT_DIR. And when\nthis is the case, the repository config is handled already, so we do not\nhave to do that again explicitly.\n\nLet's just use the same function, have_git_dir(), to determine whether we\nhave to handle .git/config explicitly.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n config.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 8808b132761..fc4625a71dd 100644\n--- a/config.c\n+++ b/config.c\n@@ -1518,14 +1518,15 @@ void read_early_config(config_fn_t cb, void *data)\n \t * core.repositoryformatversion), we'll read whatever is in .git/config\n \t * blindly. Similarly, if we _are_ in a repository, but not at the\n \t * root, we'll fail to find .git/config (because it's really\n-\t * ../.git/config, etc). See t7006 for a complete set of failures.\n+\t * ../.git/config, etc), unless setup_git_directory() was already called.\n+\t * See t7006 for a complete set of failures.\n \t *\n \t * However, we have historically provided this hack because it does\n \t * work some of the time (namely when you are at the top-level of a\n \t * valid repository), and would rarely make things worse (i.e., you do\n \t * not generally have a .git/config file sitting around).\n \t */\n-\tif (!startup_info->have_repository) {\n+\tif (!have_git_dir()) {\n \t\tstruct git_config_source repo_config;\n \n \t\tmemset(&repo_config, 0, sizeof(repo_config));\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313951","messageId":"2418decf16a0e2b4dda66f8dce8ae47b75417a34.1489435755.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489435755.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 06/12] Make read_early_config() reusable","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-13T20:11:04Z","receivedAt":"2017-03-13T20:11:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The pager configuration needs to be read early, possibly before\ndiscovering any .git/ directory.\n\nLet's not hide this function in pager.c, but make it available to other\ncallers.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n cache.h  |  1 +\n config.c | 31 +++++++++++++++++++++++++++++++\n pager.c  | 31 -------------------------------\n 3 files changed, 32 insertions(+), 31 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 086bd9fa433..973cc23d6d4 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1801,6 +1801,7 @@ extern int git_config_from_blob_sha1(config_fn_t fn, const char *name,\n \t\t\t\t     const unsigned char *sha1, void *data);\n extern void git_config_push_parameter(const char *text);\n extern int git_config_from_parameters(config_fn_t fn, void *data);\n+extern void read_early_config(config_fn_t cb, void *data);\n extern void git_config(config_fn_t fn, void *);\n extern int git_config_with_options(config_fn_t fn, void *,\n \t\t\t\t   struct git_config_source *config_source,\ndiff --git a/config.c b/config.c\nindex 0e9e1ebefc0..8808b132761 100644\n--- a/config.c\n+++ b/config.c\n@@ -1503,6 +1503,37 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \t}\n }\n \n+void read_early_config(config_fn_t cb, void *data)\n+{\n+\tgit_config_with_options(cb, data, NULL, 1);\n+\n+\t/*\n+\t * Note that this is a really dirty hack that does the wrong thing in\n+\t * many cases. The crux of the problem is that we cannot run\n+\t * setup_git_directory() early on in git's setup, so we have no idea if\n+\t * we are in a repository or not, and therefore are not sure whether\n+\t * and how to read repository-local config.\n+\t *\n+\t * So if we _aren't_ in a repository (or we are but we would reject its\n+\t * core.repositoryformatversion), we'll read whatever is in .git/config\n+\t * blindly. Similarly, if we _are_ in a repository, but not at the\n+\t * root, we'll fail to find .git/config (because it's really\n+\t * ../.git/config, etc). See t7006 for a complete set of failures.\n+\t *\n+\t * However, we have historically provided this hack because it does\n+\t * work some of the time (namely when you are at the top-level of a\n+\t * valid repository), and would rarely make things worse (i.e., you do\n+\t * not generally have a .git/config file sitting around).\n+\t */\n+\tif (!startup_info->have_repository) {\n+\t\tstruct git_config_source repo_config;\n+\n+\t\tmemset(&repo_config, 0, sizeof(repo_config));\n+\t\trepo_config.file = \".git/config\";\n+\t\tgit_config_with_options(cb, data, &repo_config, 1);\n+\t}\n+}\n+\n static void git_config_check_init(void);\n \n void git_config(config_fn_t fn, void *data)\ndiff --git a/pager.c b/pager.c\nindex ae796433630..73ca8bc3b17 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -43,37 +43,6 @@ static int core_pager_config(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n-static void read_early_config(config_fn_t cb, void *data)\n-{\n-\tgit_config_with_options(cb, data, NULL, 1);\n-\n-\t/*\n-\t * Note that this is a really dirty hack that does the wrong thing in\n-\t * many cases. The crux of the problem is that we cannot run\n-\t * setup_git_directory() early on in git's setup, so we have no idea if\n-\t * we are in a repository or not, and therefore are not sure whether\n-\t * and how to read repository-local config.\n-\t *\n-\t * So if we _aren't_ in a repository (or we are but we would reject its\n-\t * core.repositoryformatversion), we'll read whatever is in .git/config\n-\t * blindly. Similarly, if we _are_ in a repository, but not at the\n-\t * root, we'll fail to find .git/config (because it's really\n-\t * ../.git/config, etc). See t7006 for a complete set of failures.\n-\t *\n-\t * However, we have historically provided this hack because it does\n-\t * work some of the time (namely when you are at the top-level of a\n-\t * valid repository), and would rarely make things worse (i.e., you do\n-\t * not generally have a .git/config file sitting around).\n-\t */\n-\tif (!startup_info->have_repository) {\n-\t\tstruct git_config_source repo_config;\n-\n-\t\tmemset(&repo_config, 0, sizeof(repo_config));\n-\t\trepo_config.file = \".git/config\";\n-\t\tgit_config_with_options(cb, data, &repo_config, 1);\n-\t}\n-}\n-\n const char *git_pager(int stdout_is_tty)\n {\n \tconst char *pager;\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313952","messageId":"6f7131ccaf2f76955983e407491cbc2fcb8ef1ef.1489435755.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489435755.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 09/12] Add t1309 to test read_early_config()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-13T20:11:17Z","receivedAt":"2017-03-13T20:11:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"So far, we had no explicit tests of that function.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/helper/test-config.c  | 15 +++++++++++++++\n t/t1309-early-config.sh | 50 +++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 65 insertions(+)\n create mode 100755 t/t1309-early-config.sh\n\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 83a4f2ab869..8e3ed6a76cb 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -66,6 +66,16 @@ static int iterate_cb(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n+static int early_config_cb(const char *var, const char *value, void *vdata)\n+{\n+\tconst char *key = vdata;\n+\n+\tif (!strcmp(key, var))\n+\t\tprintf(\"%s\\n\", value);\n+\n+\treturn 0;\n+}\n+\n int cmd_main(int argc, const char **argv)\n {\n \tint i, val;\n@@ -73,6 +83,11 @@ int cmd_main(int argc, const char **argv)\n \tconst struct string_list *strptr;\n \tstruct config_set cs;\n \n+\tif (argc == 3 && !strcmp(argv[1], \"read_early_config\")) {\n+\t\tread_early_config(early_config_cb, (void *)argv[2]);\n+\t\treturn 0;\n+\t}\n+\n \tsetup_git_directory();\n \n \tgit_configset_init(&cs);\ndiff --git a/t/t1309-early-config.sh b/t/t1309-early-config.sh\nnew file mode 100755\nindex 00000000000..0c55dee514c\n--- /dev/null\n+++ b/t/t1309-early-config.sh\n@@ -0,0 +1,50 @@\n+#!/bin/sh\n+\n+test_description='Test read_early_config()'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'read early config' '\n+\ttest_config early.config correct &&\n+\ttest-config read_early_config early.config >output &&\n+\ttest correct = \"$(cat output)\"\n+'\n+\n+test_expect_success 'in a sub-directory' '\n+\ttest_config early.config sub &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest sub = \"$(cat output)\"\n+'\n+\n+test_expect_success 'ceiling' '\n+\ttest_config early.config ceiling &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tGIT_CEILING_DIRECTORIES=\"$PWD\" &&\n+\t\texport GIT_CEILING_DIRECTORIES &&\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest -z \"$(cat output)\"\n+'\n+\n+test_expect_success 'ceiling #2' '\n+\tmkdir -p xdg/git &&\n+\tgit config -f xdg/git/config early.config xdg &&\n+\ttest_config early.config ceiling &&\n+\tmkdir -p sub &&\n+\t(\n+\t\tXDG_CONFIG_HOME=\"$PWD\"/xdg &&\n+\t\tGIT_CEILING_DIRECTORIES=\"$PWD\" &&\n+\t\texport GIT_CEILING_DIRECTORIES XDG_CONFIG_HOME &&\n+\t\tcd sub &&\n+\t\ttest-config read_early_config early.config\n+\t) >output &&\n+\ttest xdg = \"$(cat output)\"\n+'\n+\n+test_done\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313953","messageId":"134467b8ead65a6eaab10cf3ce35eb577edf2fa1.1489435755.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489435755.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 10/12] setup_git_directory_gently_1(): avoid die()ing","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-13T20:11:22Z","receivedAt":"2017-03-13T20:11:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This function now has a new caller in addition to setup_git_directory():\nthe newly introduced discover_git_directory(). That function wants to\ndiscover the current .git/ directory, and in case of a corrupted one\nsimply pretend that there is none to be found.\n\nExample: if a stale .git file exists in the parent directory, and the\nuser calls `git -p init`, we want Git to simply *not* read any\nrepository config for the pager (instead of aborting with a message that\nthe .git file is corrupt).\n\nLet's actually pretend that there was no GIT_DIR to be found in that case\nwhen being called from discover_git_directory(), but keep the previous\nbehavior (i.e. to die()) for the setup_git_directory() case.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 25 +++++++++++++++++--------\n 1 file changed, 17 insertions(+), 8 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 411e8342972..f31abf8a990 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -825,7 +825,8 @@ enum discovery_result {\n \tGIT_DIR_BARE,\n \t/* these are errors */\n \tGIT_DIR_HIT_CEILING = -1,\n-\tGIT_DIR_HIT_MOUNT_POINT = -2\n+\tGIT_DIR_HIT_MOUNT_POINT = -2,\n+\tGIT_DIR_INVALID_GITFILE = -3\n };\n \n /*\n@@ -842,7 +843,8 @@ enum discovery_result {\n  * is relative to `dir` (i.e. *not* necessarily the cwd).\n  */\n static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n-\t\t\t\t\t\t\t  struct strbuf *gitdir)\n+\t\t\t\t\t\t\t  struct strbuf *gitdir,\n+\t\t\t\t\t\t\t  int die_on_error)\n {\n \tconst char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);\n \tstruct string_list ceiling_dirs = STRING_LIST_INIT_DUP;\n@@ -890,14 +892,21 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \tif (one_filesystem)\n \t\tcurrent_device = get_device_or_die(dir->buf, NULL, 0);\n \tfor (;;) {\n-\t\tint offset = dir->len;\n+\t\tint offset = dir->len, error_code = 0;\n \n \t\tif (offset > min_offset)\n \t\t\tstrbuf_addch(dir, '/');\n \t\tstrbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);\n-\t\tgitdirenv = read_gitfile(dir->buf);\n-\t\tif (!gitdirenv && is_git_directory(dir->buf))\n-\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n+\t\tgitdirenv = read_gitfile_gently(dir->buf, die_on_error ?\n+\t\t\t\t\t\tNULL : &error_code);\n+\t\tif (!gitdirenv) {\n+\t\t\tif (die_on_error ||\n+\t\t\t    error_code == READ_GITFILE_ERR_NOT_A_FILE) {\n+\t\t\t\tif (is_git_directory(dir->buf))\n+\t\t\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n+\t\t\t} else if (error_code != READ_GITFILE_ERR_STAT_FAILED)\n+\t\t\t\treturn GIT_DIR_INVALID_GITFILE;\n+\t\t}\n \t\tstrbuf_setlen(dir, offset);\n \t\tif (gitdirenv) {\n \t\t\tstrbuf_addstr(gitdir, gitdirenv);\n@@ -934,7 +943,7 @@ const char *discover_git_directory(struct strbuf *gitdir)\n \t\treturn NULL;\n \n \tcwd_len = dir.len;\n-\tif (setup_git_directory_gently_1(&dir, gitdir) <= 0) {\n+\tif (setup_git_directory_gently_1(&dir, gitdir, 0) <= 0) {\n \t\tstrbuf_release(&dir);\n \t\treturn NULL;\n \t}\n@@ -994,7 +1003,7 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \t\tdie_errno(_(\"Unable to read current working directory\"));\n \tstrbuf_addbuf(&dir, &cwd);\n \n-\tswitch (setup_git_directory_gently_1(&dir, &gitdir)) {\n+\tswitch (setup_git_directory_gently_1(&dir, &gitdir, 1)) {\n \tcase GIT_DIR_NONE:\n \t\tprefix = NULL;\n \t\tbreak;\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313954","messageId":"6ee7c8c03f0f537a8ad9da733be1494fbf2e498d.1489435755.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489435755.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 11/12] t1309: document cases where we would want early config not to die()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-13T20:11:26Z","receivedAt":"2017-03-13T20:11:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Jeff King came up with a couple examples that demonstrate how the new\nread_early_config() that looks harder for the current .git/ directory\ncould die() in an undesirable way.\n\nLet's add those cases to the test script, to document what we would like\nto happen when early config encounters problems.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t1309-early-config.sh | 25 +++++++++++++++++++++++++\n 1 file changed, 25 insertions(+)\n\ndiff --git a/t/t1309-early-config.sh b/t/t1309-early-config.sh\nindex 0c55dee514c..027eca63a3c 100755\n--- a/t/t1309-early-config.sh\n+++ b/t/t1309-early-config.sh\n@@ -47,4 +47,29 @@ test_expect_success 'ceiling #2' '\n \ttest xdg = \"$(cat output)\"\n '\n \n+test_with_config ()\n+{\n+\trm -rf throwaway &&\n+\tgit init throwaway &&\n+\t(\n+\t\tcd throwaway &&\n+\t\techo \"$*\" >.git/config &&\n+\t\ttest-config read_early_config early.config\n+\t)\n+}\n+\n+test_expect_success 'ignore .git/ with incompatible repository version' '\n+\ttest_with_config \"[core]repositoryformatversion = 999999\" 2>err &&\n+\tgrep \"warning:.* Expected git repo version <= [1-9]\" err\n+'\n+\n+test_expect_failure 'ignore .git/ with invalid repository version' '\n+\ttest_with_config \"[core]repositoryformatversion = invalid\"\n+'\n+\n+\n+test_expect_failure 'ignore .git/ with invalid config' '\n+\ttest_with_config \"[\"\n+'\n+\n test_done\n-- \n2.12.0.windows.1.7.g94dafc3b124\n\n\n"},{"id":"313955","messageId":"bdfa3845b81531863941e6a97c28eb1afa62dd2c.1489435755.git.johannes.schindelin@gmx.de","threadId":"44653","inReplyTo":"cover.1489435755.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 12/12] setup.c: mention unresolved problems","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-13T20:12:18Z","receivedAt":"2017-03-13T20:12:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"During the review of the `early-config` patch series, two issues have\nbeen identified that have been with us forever.\n\nThe idea of that patch series was to fix the hard-coded (and sometimes\nwrong) .git/config path when looking for the pager configurations. To\nthat end, the patches refactor the helper functions behind the\nfunctionality of setup_git_directory(), to make it reusable without\nchanging any global state. Not to change said functionality.\n\nSo let's just mark the identified problems for later so that we do not\nforget them.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n setup.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/setup.c b/setup.c\nindex f31abf8a990..64f922a9378 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -531,6 +531,7 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)\n \tssize_t len;\n \n \tif (stat(path, &st)) {\n+\t\t/* NEEDSWORK: discern between ENOENT vs other errors */\n \t\terror_code = READ_GITFILE_ERR_STAT_FAILED;\n \t\tgoto cleanup_return;\n \t}\n@@ -902,6 +903,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \t\tif (!gitdirenv) {\n \t\t\tif (die_on_error ||\n \t\t\t    error_code == READ_GITFILE_ERR_NOT_A_FILE) {\n+\t\t\t\t/* NEEDSWORK: fail if .git is not file nor dir */\n \t\t\t\tif (is_git_directory(dir->buf))\n \t\t\t\t\tgitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;\n \t\t\t} else if (error_code != READ_GITFILE_ERR_STAT_FAILED)\n-- \n2.12.0.windows.1.7.g94dafc3b124\n"},{"id":"313958","messageId":"xmqqvardq66u.fsf@gitster.mtv.corp.google.com","threadId":"44653","inReplyTo":"xmqqzigpq7qp.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v5 10/11] setup_git_directory_gently_1(): avoid die()ing","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-03-13T20:20:57Z","receivedAt":"2017-03-13T20:21:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> The former case may split into two.  \"Yes I agree that is bad and it\n> is the same badness as the version without this change\", in which\n> case we may want to leave a \"NEEDSWORK\" comment in-code so that\n> people can notice when browsing the code (but fixing the badness\n> would be outside the scope of the series), and \"yes that is bad and\n> we shouldn't introduce that badness\", in which case we do want to\n> fix it in the patch.\n\nWe however need to be a bit careful here, though.\n\nWhen a patch series is refactoring an existing function to be used\nin different codepaths, an existing issue inherited from the\noriginal code (e.g. perhaps an existing error checking that is\nlooser than ideal) may have been OK in the original context (e.g.\nperhaps it died and refused to run until the user corrected the\nrepository), and it still is OK in the codepath that uses the\nrefactored building blocks to replace the original code, but it may\nnot be acceptable to leave the issue in the original code when a new\ncaller calls the refactored building block (e.g. perhaps the\nrefactoring made it possible for a caller to ask the function not to\ndie and instead act on different kinds of errors in different ways).\n\nSo \"being bug-to-bug compatible with existing code\" is not always a\ngood thing---we need to see case by case if a problem identified in\nthe review as inherited from the original needs to be addressed in\nthe series.\n\nIt would be better to address issues we identify even if it is an\nold one.  After all, any change has potential to introduce new bugs,\nand striving to leave known bug inherited from the old code around\nwould guarantee that the resulting code will be buggier than the\noriginal ;-) \n\nBut in order to keep bisectability, such \"further fixups\" should be\ndone as a follow-up to the series, not as part of it.\n"},{"id":"313959","messageId":"xmqqr320rk3z.fsf@gitster.mtv.corp.google.com","threadId":"44653","inReplyTo":"8d8be2b095afb215f5bf0e0bb7989c9c9ab3f471.1489435755.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v6 03/12] Prepare setup_discovered_git_directory() the root directory","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-03-13T20:34:56Z","receivedAt":"2017-03-13T20:35:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> Subject: Re: [PATCH v6 03/12] Prepare setup_discovered_git_directory() the root directory\n\nI do not think you've changed this title throughout the rerolls, but\nI cannot quite parse it.  Would something like this\n\n    setup.c: only append '/' when not at root in setup_discovered_git_dir()\n\ngive enough information to the readers while making it readable?\n\n> Currently, the offset parameter (indicating what part of the cwd\n> parameter corresponds to the current directory after discovering the\n> .git/ directory) is set to 0 when we are running in the root directory.\n>\n> However, in the next patches we will avoid changing the current working\n> directory while searching for the .git/ directory, meaning that the\n> offset corresponding to the root directory will have to be 1 to reflect\n> that this directory is characterized by the path \"/\" (and not \"\").\n\nOK.  C:\\ would be 3 not 1 but the same logic to compare with\noffset_1st_component() covers the case, which is good.\n\n> So let's make sure that setup_discovered_git_directory() only tries to\n> append the trailing slash to non-root directories.\n>\n> Note: the setup_bare_git_directory() does not need a corresponding\n> change, as it does not want to return a prefix.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  setup.c | 6 ++++--\n>  1 file changed, 4 insertions(+), 2 deletions(-)\n>\n> diff --git a/setup.c b/setup.c\n> index 2ac891d4b9a..23114cb7aa3 100644\n> --- a/setup.c\n> +++ b/setup.c\n> @@ -721,8 +721,10 @@ static const char *setup_discovered_git_dir(const char *gitdir,\n>  \tif (offset == cwd->len)\n>  \t\treturn NULL;\n>  \n> -\t/* Make \"offset\" point to past the '/', and add a '/' at the end */\n> -\toffset++;\n> +\t/* Make \"offset\" point past the '/' (already the case for root dirs) */\n> +\tif (offset != offset_1st_component(cwd->buf))\n> +\t\toffset++;\n> +\t/* Add a '/' at the end */\n>  \tstrbuf_addch(cwd, '/');\n>  \treturn cwd->buf + offset;\n>  }\n"},{"id":"313988","messageId":"alpine.DEB.2.20.1703132243320.3767@virtualbox","threadId":"44653","inReplyTo":"xmqqr320rk3z.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v6 03/12] Prepare setup_discovered_git_directory() the root directory","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-13T21:44:00Z","receivedAt":"2017-03-13T21:44:32Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Mon, 13 Mar 2017, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > Subject: Re: [PATCH v6 03/12] Prepare setup_discovered_git_directory() the root directory\n> \n> I do not think you've changed this title throughout the rerolls, but\n> I cannot quite parse it.\n\nThat is because a \"for\" is missing between \"the\" and \"root\".\n\nCiao,\nDscho\n"},{"id":"313991","messageId":"alpine.DEB.2.20.1703132244400.3767@virtualbox","threadId":"44653","inReplyTo":"xmqqvardq66u.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v5 10/11] setup_git_directory_gently_1(): avoid die()ing","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-03-13T21:46:27Z","receivedAt":"2017-03-13T21:46:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Mon, 13 Mar 2017, Junio C Hamano wrote:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > The former case may split into two.  \"Yes I agree that is bad and it\n> > is the same badness as the version without this change\", in which case\n> > we may want to leave a \"NEEDSWORK\" comment in-code so that people can\n> > notice when browsing the code (but fixing the badness would be outside\n> > the scope of the series), and \"yes that is bad and we shouldn't\n> > introduce that badness\", in which case we do want to fix it in the\n> > patch.\n> \n> We however need to be a bit careful here, though.\n> \n> When a patch series is refactoring an existing function to be used\n> in different codepaths, an existing issue inherited from the\n> original code (e.g. perhaps an existing error checking that is\n> looser than ideal) may have been OK in the original context (e.g.\n> perhaps it died and refused to run until the user corrected the\n> repository), and it still is OK in the codepath that uses the\n> refactored building blocks to replace the original code, but it may\n> not be acceptable to leave the issue in the original code when a new\n> caller calls the refactored building block (e.g. perhaps the\n> refactoring made it possible for a caller to ask the function not to\n> die and instead act on different kinds of errors in different ways).\n\nIn this case, discover_git_directory() is intended to use the exact same\nlogic as setup_git_directory() (including bugs and all) so that they do\ndiscover the same directory.\n\nIt would not do for discover_git_directory() to detect a *different*\ndirectory, no matter how much \"more correct\" one would deem it.\n\nCiao,\nJohannes\n"},{"id":"313997","messageId":"xmqq7f3sn70s.fsf@gitster.mtv.corp.google.com","threadId":"44653","inReplyTo":"cover.1489435755.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v6 00/12] Fix the early config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-03-13T22:31:15Z","receivedAt":"2017-03-13T22:31:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> These patches are an attempt to make Git's startup sequence a bit less\n> surprising.\n\nI think this is ready for 'next', so let's ask reviewers to really\npay attention to this round, wait for a few days and merge it by the\nend of the week at the latest.\n\nThanks.\n"},{"id":"314010","messageId":"xmqqlgs8lpns.fsf@gitster.mtv.corp.google.com","threadId":"44653","inReplyTo":"alpine.DEB.2.20.1703132244400.3767@virtualbox","subject":"Re: [PATCH v5 10/11] setup_git_directory_gently_1(): avoid die()ing","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-03-13T23:31:35Z","receivedAt":"2017-03-13T23:31:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> On Mon, 13 Mar 2017, Junio C Hamano wrote:\n>\n>> When a patch series is refactoring an existing function to be used\n>> in different codepaths, an existing issue inherited from the\n>> original code (e.g. perhaps an existing error checking that is\n>> looser than ideal) may have been OK in the original context (e.g.\n>> perhaps it died and refused to run until the user corrected the\n>> repository), and it still is OK in the codepath that uses the\n>> refactored building blocks to replace the original code, but it may\n>> not be acceptable to leave the issue in the original code when a new\n>> caller calls the refactored building block (e.g. perhaps the\n>> refactoring made it possible for a caller to ask the function not to\n>> die and instead act on different kinds of errors in different ways).\n>\n> In this case, ...\n\nIt becomes somewhat irritating when you get combative and defensive\nunnecessarily.  We already established this case is OK two messages\nago, I think.\n\nThe above is only to make sure that people cannot take the \"issues\nin the original is OK to leave outside the scope of a new series\" in\nmy message out of context and treat it as a general rule to justify\ntheir sloppy patches in the future.  We need to see if issues\ninherited from the original is necessary to fix before refactoring\neven begins on case-by-case basis, seeing what the requirement of\nthe new code that uses the refactored code.  And the case-by-case\nthing we already did for _this_ case.\n\n"},{"id":"314043","messageId":"20170314180101.q7xwn2sekawx7ryv@sigill.intra.peff.net","threadId":"44653","inReplyTo":"xmqq7f3sn70s.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v6 00/12] Fix the early config","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2017-03-14T18:01:01Z","receivedAt":"2017-03-14T18:01:08Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Mar 13, 2017 at 03:31:15PM -0700, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > These patches are an attempt to make Git's startup sequence a bit less\n> > surprising.\n> \n> I think this is ready for 'next', so let's ask reviewers to really\n> pay attention to this round, wait for a few days and merge it by the\n> end of the week at the latest.\n\nI gave it another read-through and didn't find anything worth\nmentioning. I agree it's probably ready for 'next'.\n\n-Peff\n"}]}