{"thread":{"id":"51508","subject":"[PATCH 0/5] Create 'feature.*' config area and some centralized config parsing","startedAt":"2019-07-22T17:54:25Z","lastAt":"2021-09-21T02:22:00Z","messageCount":61,"participants":["Derrick Stolee via GitGitGadget","Johannes Schindelin","Derrick Stolee","Junio C Hamano","Ævar Arnfjörð Bjarmason","Elijah Newren","brian m. carlson","Phillip Wood","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"379148","messageId":"pull.292.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":null,"subject":"[PATCH 0/5] Create 'feature.*' config area and some centralized config parsing","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-22T17:54:20Z","receivedAt":"2019-07-22T17:54:25Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"This is a brand-new thread to replace ds/early-access. The discussion on\nthat thread was very helpful [1].\n\nWith this in mind, I propose instead a set of \"feature.*\" config settings\nthat form groups of \"community recommended\" settings (with some caveats). In\nthe space below, I'll list a set of possible feature names and the implied\nconfig options.\n\nFirst, the main two categories we've discussed so far: many commits and many\nfiles. These two feature sets are for when your repo is large in one of\nthese dimensions. Perhaps there are other settings to include in these?\n\nfeature.manyFiles:\n    index.version = 4\n    core.untrackedCache = true\n\nfeature.manyCommits:\n    core.commitGraph = true\n    gc.writeCommitGraph = true\n    (future: fetch.writeSplitCommitGraph = true)\n\nNote: the fetch.writeSplitCommitGraph does not exist yet, but could be\nintroduced in a later release to write a new commit-graph (with --split) on\nfetch.\n\nThe other category that has been discussed already is that of \"experimental\nfeatures that we generally think are helpful but change behavior slightly in\nsome cases\".\n\nfeature.experimental:\n    pack.useSparse = true\n    merge.directoryRenames = true\n    fetch.negotiationAlgorithm = skipping\n\nSpecifically, this setting is for config values we are not sure will ever be\non by default, but additional testing is needed to be sure. This is\ndifferent than a possible 'feature.preview' setting that would include\nconfig settings that we are committed to updating the defaults in a future\nrelease. There are many ways we can take this idea in the future (including\nmore additions to these categories).\n\nThanks, -Stolee\n\n[1] https://public-inbox.org/git/pull.254.git.gitgitgadget@gmail.com/\n\nDerrick Stolee (5):\n  repo-settings: consolidate some config settings\n  repo-settings: add feature.manyCommits setting\n  repo-settings: parse core.untrackedCache\n  repo-settings: create feature.manyFiles setting\n  repo-settings: create feature.experimental setting\n\n Documentation/config.txt             |   2 +\n Documentation/config/core.txt        |   7 +-\n Documentation/config/feature.txt     |  42 +++++++++++\n Documentation/config/fetch.txt       |   3 +-\n Documentation/config/gc.txt          |   4 +-\n Documentation/config/index.txt       |   1 +\n Documentation/config/merge.txt       |   3 +-\n Documentation/config/pack.txt        |   3 +-\n Makefile                             |   1 +\n builtin/am.c                         |   4 +-\n builtin/gc.c                         |  13 ++--\n builtin/pack-objects.c               |   9 ++-\n builtin/update-index.c               |   7 +-\n commit-graph.c                       |   7 +-\n config.c                             |  24 ------\n fetch-negotiator.c                   |  26 ++++---\n fetch-negotiator.h                   |   5 +-\n fetch-pack.c                         |  11 ++-\n merge-recursive.c                    |  32 ++++----\n merge-recursive.h                    |   1 -\n read-cache.c                         |  31 ++++----\n repo-settings.c                      | 108 +++++++++++++++++++++++++++\n repo-settings.h                      |  29 +++++++\n repository.h                         |   3 +\n t/t1600-index.sh                     |  31 ++++++--\n t/t5552-skipping-fetch-negotiator.sh |  23 ------\n 26 files changed, 303 insertions(+), 127 deletions(-)\n create mode 100644 Documentation/config/feature.txt\n create mode 100644 repo-settings.c\n create mode 100644 repo-settings.h\n\n\nbase-commit: 9c9b961d7eb15fb583a2a812088713a68a85f1c0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-292%2Fderrickstolee%2Frepo-settings%2Fhead-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-292/derrickstolee/repo-settings/head-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/292\n-- \ngitgitgadget\n"},{"id":"379149","messageId":"47ae3e7d4d765a00d14e8892db88a8936d56591b.1563818059.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.git.gitgitgadget@gmail.com","subject":"[PATCH 3/5] repo-settings: parse core.untrackedCache","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-22T17:54:23Z","receivedAt":"2019-07-22T17:54:27Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe core.untrackedCache config setting is slightly complicated,\nso clarify its use and centralize its parsing into the repo\nsettings.\n\nThe default value is \"keep\" (returned as -1), which persists the\nuntracked cache if it exists.\n\nIf the value is set as \"false\" (returned as 0), then remove the\nuntracked cache if it exists.\n\nIf the value is set as \"true\" (returned as 1), then write the\nuntracked cache and persist it.\n\nInstead of relying on magic values of -1, 0, and 1, split these\noptions into bitflags CORE_UNTRACKED_CACHE_KEEP and\nCORE_UNTRACKED_CACHE_WRITE. This allows the use of \"-1\" as a\ndefault value. After parsing the config options, if the value is\nunset we can initialize it to CORE_UNTRACKED_CACHE_KEEP.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/update-index.c |  7 +++++--\n config.c               | 24 ------------------------\n read-cache.c           | 19 +++++++++----------\n repo-settings.c        | 21 +++++++++++++++++++++\n repo-settings.h        |  4 ++++\n 5 files changed, 39 insertions(+), 36 deletions(-)\n\ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex dff2f4b837..6c26bbae80 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -18,6 +18,7 @@\n #include \"dir.h\"\n #include \"split-index.h\"\n #include \"fsmonitor.h\"\n+#include \"repo-settings.h\"\n \n /*\n  * Default to not allowing changes to the list of files. The\n@@ -966,6 +967,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \tstruct parse_opt_ctx_t ctx;\n \tstrbuf_getline_fn getline_fn;\n \tint parseopt_state = PARSE_OPT_UNKNOWN;\n+\tstruct repository *r = the_repository;\n \tstruct option options[] = {\n \t\tOPT_BIT('q', NULL, &refresh_args.flags,\n \t\t\tN_(\"continue refresh even when index needs update\"),\n@@ -1180,11 +1182,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\tremove_split_index(&the_index);\n \t}\n \n+\tprepare_repo_settings(r);\n \tswitch (untracked_cache) {\n \tcase UC_UNSPECIFIED:\n \t\tbreak;\n \tcase UC_DISABLE:\n-\t\tif (git_config_get_untracked_cache() == 1)\n+\t\tif (r->settings->core_untracked_cache & CORE_UNTRACKED_CACHE_WRITE)\n \t\t\twarning(_(\"core.untrackedCache is set to true; \"\n \t\t\t\t  \"remove or change it, if you really want to \"\n \t\t\t\t  \"disable the untracked cache\"));\n@@ -1196,7 +1199,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\treturn !test_if_untracked_cache_is_supported();\n \tcase UC_ENABLE:\n \tcase UC_FORCE:\n-\t\tif (git_config_get_untracked_cache() == 0)\n+\t\tif (r->settings->core_untracked_cache == 0)\n \t\t\twarning(_(\"core.untrackedCache is set to false; \"\n \t\t\t\t  \"remove or change it, if you really want to \"\n \t\t\t\t  \"enable the untracked cache\"));\ndiff --git a/config.c b/config.c\nindex faa57e436c..3241dbc54d 100644\n--- a/config.c\n+++ b/config.c\n@@ -2277,30 +2277,6 @@ int git_config_get_expiry_in_days(const char *key, timestamp_t *expiry, timestam\n \treturn -1; /* thing exists but cannot be parsed */\n }\n \n-int git_config_get_untracked_cache(void)\n-{\n-\tint val = -1;\n-\tconst char *v;\n-\n-\t/* Hack for test programs like test-dump-untracked-cache */\n-\tif (ignore_untracked_cache_config)\n-\t\treturn -1;\n-\n-\tif (!git_config_get_maybe_bool(\"core.untrackedcache\", &val))\n-\t\treturn val;\n-\n-\tif (!git_config_get_value(\"core.untrackedcache\", &v)) {\n-\t\tif (!strcasecmp(v, \"keep\"))\n-\t\t\treturn -1;\n-\n-\t\terror(_(\"unknown core.untrackedCache value '%s'; \"\n-\t\t\t\"using 'keep' default value\"), v);\n-\t\treturn -1;\n-\t}\n-\n-\treturn -1; /* default value */\n-}\n-\n int git_config_get_split_index(void)\n {\n \tint val;\ndiff --git a/read-cache.c b/read-cache.c\nindex ee1aaa8917..e67e6f6e3e 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -1846,18 +1846,17 @@ static void check_ce_order(struct index_state *istate)\n \n static void tweak_untracked_cache(struct index_state *istate)\n {\n-\tswitch (git_config_get_untracked_cache()) {\n-\tcase -1: /* keep: do nothing */\n-\t\tbreak;\n-\tcase 0: /* false */\n+\tstruct repository *r = the_repository;\n+\n+\tprepare_repo_settings(r);\n+\n+\tif (!(r->settings->core_untracked_cache & CORE_UNTRACKED_CACHE_KEEP)) {\n \t\tremove_untracked_cache(istate);\n-\t\tbreak;\n-\tcase 1: /* true */\n-\t\tadd_untracked_cache(istate);\n-\t\tbreak;\n-\tdefault: /* unknown value: do nothing */\n-\t\tbreak;\n+\t\treturn;\n \t}\n+\n+\tif (r->settings->core_untracked_cache & CORE_UNTRACKED_CACHE_WRITE)\n+\t\tadd_untracked_cache(istate);\n }\n \n static void tweak_split_index(struct index_state *istate)\ndiff --git a/repo-settings.c b/repo-settings.c\nindex f328602fd7..807c5a29d6 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -30,6 +30,20 @@ static int git_repo_config(const char *key, const char *value, void *cb)\n \t\trs->index_version = git_config_int(key, value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(key, \"core.untrackedcache\")) {\n+\t\tint bool_value = git_parse_maybe_bool(value);\n+\t\tif (bool_value == 0)\n+\t\t\trs->core_untracked_cache = 0;\n+\t\telse if (bool_value == 1)\n+\t\t\trs->core_untracked_cache = CORE_UNTRACKED_CACHE_KEEP |\n+\t\t\t\t\t\t   CORE_UNTRACKED_CACHE_WRITE;\n+\t\telse if (!strcasecmp(value, \"keep\"))\n+\t\t\trs->core_untracked_cache = CORE_UNTRACKED_CACHE_KEEP;\n+\t\telse\n+\t\t\terror(_(\"unknown core.untrackedCache value '%s'; \"\n+\t\t\t\t\"using 'keep' default value\"), value);\n+\t\treturn 0;\n+\t}\n \n \treturn 1;\n }\n@@ -46,6 +60,13 @@ void prepare_repo_settings(struct repository *r)\n \tr->settings->gc_write_commit_graph = -1;\n \tr->settings->pack_use_sparse = -1;\n \tr->settings->index_version = -1;\n+\tr->settings->core_untracked_cache = -1;\n \n \trepo_config(r, git_repo_config, r->settings);\n+\n+\t/* Hack for test programs like test-dump-untracked-cache */\n+\tif (ignore_untracked_cache_config)\n+\t\tr->settings->core_untracked_cache = CORE_UNTRACKED_CACHE_KEEP;\n+\telse\n+\t\tUPDATE_DEFAULT(r->settings->core_untracked_cache, CORE_UNTRACKED_CACHE_KEEP);\n }\ndiff --git a/repo-settings.h b/repo-settings.h\nindex 1151c2193a..bac9b87d49 100644\n--- a/repo-settings.h\n+++ b/repo-settings.h\n@@ -1,11 +1,15 @@\n #ifndef REPO_SETTINGS_H\n #define REPO_SETTINGS_H\n \n+#define CORE_UNTRACKED_CACHE_WRITE (1 << 0)\n+#define CORE_UNTRACKED_CACHE_KEEP (1 << 1)\n+\n struct repo_settings {\n \tint core_commit_graph;\n \tint gc_write_commit_graph;\n \tint pack_use_sparse;\n \tint index_version;\n+\tint core_untracked_cache;\n };\n \n struct repository;\n-- \ngitgitgadget\n\n"},{"id":"379150","messageId":"e00a1be75b410694374b0d9bd60ab16d67ef6d20.1563818059.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.git.gitgitgadget@gmail.com","subject":"[PATCH 2/5] repo-settings: add feature.manyCommits setting","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-22T17:54:22Z","receivedAt":"2019-07-22T17:54:28Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWhen a repo has many commits, it is helpful to write and read the\ncommit-graph file. Future changes to Git may include new config\nsettings that are benefitial in this scenario.\n\nCreate the 'feature.manyCommits' config setting that changes the\ndefault values of 'core.commitGraph' and 'gc.writeCommitGraph' to\ntrue.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config.txt         |  2 ++\n Documentation/config/core.txt    |  3 ++-\n Documentation/config/feature.txt | 15 +++++++++++++++\n Documentation/config/gc.txt      |  4 ++--\n repo-settings.c                  |  7 +++++++\n 5 files changed, 28 insertions(+), 3 deletions(-)\n create mode 100644 Documentation/config/feature.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex e3f5bc3396..77f3b1486b 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -345,6 +345,8 @@ include::config/difftool.txt[]\n \n include::config/fastimport.txt[]\n \n+include::config/feature.txt[]\n+\n include::config/fetch.txt[]\n \n include::config/format.txt[]\ndiff --git a/Documentation/config/core.txt b/Documentation/config/core.txt\nindex 75538d27e7..d80162681a 100644\n--- a/Documentation/config/core.txt\n+++ b/Documentation/config/core.txt\n@@ -577,7 +577,8 @@ the `GIT_NOTES_REF` environment variable.  See linkgit:git-notes[1].\n \n core.commitGraph::\n \tIf true, then git will read the commit-graph file (if it exists)\n-\tto parse the graph structure of commits. Defaults to false. See\n+\tto parse the graph structure of commits. Defaults to false, unless\n+\t`feature.manyCommits` is enabled. See\n \tlinkgit:git-commit-graph[1] for more information.\n \n core.useReplaceRefs::\ndiff --git a/Documentation/config/feature.txt b/Documentation/config/feature.txt\nnew file mode 100644\nindex 0000000000..f74314ae90\n--- /dev/null\n+++ b/Documentation/config/feature.txt\n@@ -0,0 +1,15 @@\n+feature.*::\n+\tThe config settings that start with `feature.` modify the defaults of\n+\ta group of other config settings. These groups are created by the Git\n+\tdeveloper community as recommended defaults and are subject to change.\n+\tIn particular, new config options may be added with different defaults.\n+\n+feature.manyCommits::\n+\tEnable config options that optimize for repos with many commits. This\n+\tsetting is recommended for repos with at least 100,000 commits. The\n+\tnew default values are:\n++\n+* `core.commitGraph=true` enables reading the commit-graph file.\n++\n+* `gc.writeCommitGraph=true` enables writing the commit-graph file during\n+garbage collection.\n\\ No newline at end of file\ndiff --git a/Documentation/config/gc.txt b/Documentation/config/gc.txt\nindex 02b92b18b5..31a5fc4f75 100644\n--- a/Documentation/config/gc.txt\n+++ b/Documentation/config/gc.txt\n@@ -63,8 +63,8 @@ gc.writeCommitGraph::\n \tIf true, then gc will rewrite the commit-graph file when\n \tlinkgit:git-gc[1] is run. When using `git gc --auto`\n \tthe commit-graph will be updated if housekeeping is\n-\trequired. Default is false. See linkgit:git-commit-graph[1]\n-\tfor details.\n+\trequired. Default is false, unless `feature.manyCommits`\n+\tis enabled. See linkgit:git-commit-graph[1] for details.\n \n gc.logExpiry::\n \tIf the file gc.log exists, then `git gc --auto` will print\ndiff --git a/repo-settings.c b/repo-settings.c\nindex 13a9128f62..f328602fd7 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -3,10 +3,17 @@\n #include \"config.h\"\n #include \"repo-settings.h\"\n \n+#define UPDATE_DEFAULT(s,v) do { if (s == -1) { s = v; } } while(0)\n+\n static int git_repo_config(const char *key, const char *value, void *cb)\n {\n \tstruct repo_settings *rs = (struct repo_settings *)cb;\n \n+\tif (!strcmp(key, \"feature.manycommits\")) {\n+\t\tUPDATE_DEFAULT(rs->core_commit_graph, 1);\n+\t\tUPDATE_DEFAULT(rs->gc_write_commit_graph, 1);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(key, \"core.commitgraph\")) {\n \t\trs->core_commit_graph = git_config_bool(key, value);\n \t\treturn 0;\n-- \ngitgitgadget\n\n"},{"id":"379151","messageId":"63b522a858bdd2fad78da53eae207e64f2f7a6e0.1563818059.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.git.gitgitgadget@gmail.com","subject":"[PATCH 4/5] repo-settings: create feature.manyFiles setting","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-22T17:54:23Z","receivedAt":"2019-07-22T17:54:28Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe feature.manyFiles setting is suitable for repos with many\nfiles in the working directory. By setting index.version=4 and\ncore.untrackedCache=true, commands such as 'git status' should\nimprove.\n\nWhile adding this setting, modify the index version precedence\ntests to check how this setting overrides the default for\nindex.version is unset.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/core.txt    |  4 +++-\n Documentation/config/feature.txt | 12 +++++++++++-\n Documentation/config/index.txt   |  1 +\n repo-settings.c                  |  6 ++++++\n t/t1600-index.sh                 | 31 ++++++++++++++++++++++++++-----\n 5 files changed, 47 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/core.txt b/Documentation/config/core.txt\nindex d80162681a..7a2a33bc8c 100644\n--- a/Documentation/config/core.txt\n+++ b/Documentation/config/core.txt\n@@ -86,7 +86,9 @@ core.untrackedCache::\n \tit will automatically be removed, if set to `false`. Before\n \tsetting it to `true`, you should check that mtime is working\n \tproperly on your system.\n-\tSee linkgit:git-update-index[1]. `keep` by default.\n+\tSee linkgit:git-update-index[1]. `keep` by default, unless\n+\t`feature.manyFiles` is enabled which sets this setting to\n+\t`true` by default.\n \n core.checkStat::\n \tWhen missing or is set to `default`, many fields in the stat\ndiff --git a/Documentation/config/feature.txt b/Documentation/config/feature.txt\nindex f74314ae90..c2d9ef7473 100644\n--- a/Documentation/config/feature.txt\n+++ b/Documentation/config/feature.txt\n@@ -12,4 +12,14 @@ feature.manyCommits::\n * `core.commitGraph=true` enables reading the commit-graph file.\n +\n * `gc.writeCommitGraph=true` enables writing the commit-graph file during\n-garbage collection.\n\\ No newline at end of file\n+garbage collection.\n+\n+feature.manyFiles::\n+\tEnable config options that optimize for repos with many files in the\n+\tworking directory. With many files, commands such as `git status` and\n+\t`git checkout` may be slow and these new defaults improve performance:\n++\n+* `index.version=4` enables path-prefix compression in the index.\n++\n+* `core.untrackedCache=true` enables the untracked cache. This setting assumes\n+that mtime is working on your machine.\n\\ No newline at end of file\ndiff --git a/Documentation/config/index.txt b/Documentation/config/index.txt\nindex f181503041..7cb50b37e9 100644\n--- a/Documentation/config/index.txt\n+++ b/Documentation/config/index.txt\n@@ -24,3 +24,4 @@ index.threads::\n index.version::\n \tSpecify the version with which new index files should be\n \tinitialized.  This does not affect existing repositories.\n+\tIf `feature.manyFiles` is enabled, then the default is 4.\ndiff --git a/repo-settings.c b/repo-settings.c\nindex 807c5a29d6..9e4b8e6268 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -14,6 +14,12 @@ static int git_repo_config(const char *key, const char *value, void *cb)\n \t\tUPDATE_DEFAULT(rs->gc_write_commit_graph, 1);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(key, \"feature.manyfiles\")) {\n+\t\tUPDATE_DEFAULT(rs->index_version, 4);\n+\t\tUPDATE_DEFAULT(rs->core_untracked_cache,\n+\t\t\t       CORE_UNTRACKED_CACHE_KEEP | CORE_UNTRACKED_CACHE_WRITE);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(key, \"core.commitgraph\")) {\n \t\trs->core_commit_graph = git_config_bool(key, value);\n \t\treturn 0;\ndiff --git a/t/t1600-index.sh b/t/t1600-index.sh\nindex 42962ed7d4..c77721b580 100755\n--- a/t/t1600-index.sh\n+++ b/t/t1600-index.sh\n@@ -59,17 +59,38 @@ test_expect_success 'out of bounds index.version issues warning' '\n \t)\n '\n \n-test_expect_success 'GIT_INDEX_VERSION takes precedence over config' '\n+test_index_version () {\n+\tINDEX_VERSION_CONFIG=$1 &&\n+\tFEATURE_MANY_FILES=$2 &&\n+\tENV_VAR_VERSION=$3\n+\tEXPECTED_OUTPUT_VERSION=$4 &&\n \t(\n \t\trm -f .git/index &&\n-\t\tGIT_INDEX_VERSION=4 &&\n-\t\texport GIT_INDEX_VERSION &&\n-\t\tgit config --add index.version 2 &&\n+\t\trm -f .git/config &&\n+\t\tif test \"$INDEX_VERSION_CONFIG\" -ne 0\n+\t\tthen\n+\t\t\tgit config --add index.version $INDEX_VERSION_CONFIG\n+\t\tfi &&\n+\t\tgit config --add feature.manyFiles $FEATURE_MANY_FILES\n+\t\tif test \"$ENV_VAR_VERSION\" -ne 0\n+\t\tthen\n+\t\t\tGIT_INDEX_VERSION=$ENV_VAR_VERSION &&\n+\t\t\texport GIT_INDEX_VERSION\n+\t\telse\n+\t\t\tunset GIT_INDEX_VERSION\n+\t\tfi &&\n \t\tgit add a 2>&1 &&\n-\t\techo 4 >expect &&\n+\t\techo $EXPECTED_OUTPUT_VERSION >expect &&\n \t\ttest-tool index-version <.git/index >actual &&\n \t\ttest_cmp expect actual\n \t)\n+}\n+\n+test_expect_success 'index version config precedence' '\n+\ttest_index_version 2 false 4 4 &&\n+\ttest_index_version 2 true 0 2 &&\n+\ttest_index_version 0 true 0 4 &&\n+\ttest_index_version 0 true 2 2\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"379152","messageId":"72f652b89c71526cc423e7812de66f41a079f181.1563818059.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.git.gitgitgadget@gmail.com","subject":"[PATCH 1/5] repo-settings: consolidate some config settings","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-22T17:54:21Z","receivedAt":"2019-07-22T17:54:30Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThere are a few important config settings that are not loaded\nduring git_default_config. These are instead loaded on-demand.\n\nCentralize these config options to a single scan, and store\nall of the values in a repo_settings struct. The values for\neach setting are initialized as negative to indicate \"unset\".\n\nThis centralization will be particularly important in a later\nchange to introduce \"meta\" config settings that change the\ndefaults for these config settings.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Makefile               |  1 +\n builtin/gc.c           | 13 ++++++-------\n builtin/pack-objects.c |  9 +++++----\n commit-graph.c         |  7 ++++---\n read-cache.c           | 12 +++++++-----\n repo-settings.c        | 44 ++++++++++++++++++++++++++++++++++++++++++\n repo-settings.h        | 15 ++++++++++++++\n repository.h           |  3 +++\n 8 files changed, 85 insertions(+), 19 deletions(-)\n create mode 100644 repo-settings.c\n create mode 100644 repo-settings.h\n\ndiff --git a/Makefile b/Makefile\nindex 11ccea4071..032fe9b5f9 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -964,6 +964,7 @@ LIB_OBJS += refspec.o\n LIB_OBJS += ref-filter.o\n LIB_OBJS += remote.o\n LIB_OBJS += replace-object.o\n+LIB_OBJS += repo-settings.o\n LIB_OBJS += repository.o\n LIB_OBJS += rerere.o\n LIB_OBJS += resolve-undo.o\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex c18efadda5..243be2907b 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -27,6 +27,7 @@\n #include \"pack-objects.h\"\n #include \"blob.h\"\n #include \"tree.h\"\n+#include \"repo-settings.h\"\n \n #define FAILED_RUN \"failed to run %s\"\n \n@@ -41,7 +42,6 @@ static int aggressive_depth = 50;\n static int aggressive_window = 250;\n static int gc_auto_threshold = 6700;\n static int gc_auto_pack_limit = 50;\n-static int gc_write_commit_graph;\n static int detach_auto = 1;\n static timestamp_t gc_log_expire_time;\n static const char *gc_log_expire = \"1.day.ago\";\n@@ -148,7 +148,6 @@ static void gc_config(void)\n \tgit_config_get_int(\"gc.aggressivedepth\", &aggressive_depth);\n \tgit_config_get_int(\"gc.auto\", &gc_auto_threshold);\n \tgit_config_get_int(\"gc.autopacklimit\", &gc_auto_pack_limit);\n-\tgit_config_get_bool(\"gc.writecommitgraph\", &gc_write_commit_graph);\n \tgit_config_get_bool(\"gc.autodetach\", &detach_auto);\n \tgit_config_get_expiry(\"gc.pruneexpire\", &prune_expire);\n \tgit_config_get_expiry(\"gc.worktreepruneexpire\", &prune_worktrees_expire);\n@@ -685,11 +684,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n \t\tclean_pack_garbage();\n \t}\n \n-\tif (gc_write_commit_graph &&\n-\t    write_commit_graph_reachable(get_object_directory(),\n-\t\t\t\t\t !quiet && !daemonized ? COMMIT_GRAPH_PROGRESS : 0,\n-\t\t\t\t\t NULL))\n-\t\treturn 1;\n+\tprepare_repo_settings(the_repository);\n+\tif (the_repository->settings->gc_write_commit_graph == 1)\n+\t\twrite_commit_graph_reachable(get_object_directory(),\n+\t\t\t\t\t     !quiet && !daemonized ? COMMIT_GRAPH_PROGRESS : 0,\n+\t\t\t\t\t     NULL);\n \n \tif (auto_gc && too_many_loose_objects())\n \t\twarning(_(\"There are too many unreachable loose objects; \"\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 267c562b1f..223038b309 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -34,6 +34,7 @@\n #include \"dir.h\"\n #include \"midx.h\"\n #include \"trace2.h\"\n+#include \"repo-settings.h\"\n \n #define IN_PACK(obj) oe_in_pack(&to_pack, obj)\n #define SIZE(obj) oe_size(&to_pack, obj)\n@@ -2709,10 +2710,6 @@ static int git_pack_config(const char *k, const char *v, void *cb)\n \t\tuse_bitmap_index_default = git_config_bool(k, v);\n \t\treturn 0;\n \t}\n-\tif (!strcmp(k, \"pack.usesparse\")) {\n-\t\tsparse = git_config_bool(k, v);\n-\t\treturn 0;\n-\t}\n \tif (!strcmp(k, \"pack.threads\")) {\n \t\tdelta_search_threads = git_config_int(k, v);\n \t\tif (delta_search_threads < 0)\n@@ -3332,6 +3329,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \tread_replace_refs = 0;\n \n \tsparse = git_env_bool(\"GIT_TEST_PACK_SPARSE\", 0);\n+\tprepare_repo_settings(the_repository);\n+\tif (!sparse && the_repository->settings->pack_use_sparse != -1)\n+\t\tsparse = the_repository->settings->pack_use_sparse;\n+\n \treset_pack_idx_option(&pack_idx_opts);\n \tgit_config(git_pack_config, NULL);\n \ndiff --git a/commit-graph.c b/commit-graph.c\nindex b3c4de79b6..48e03aa285 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -16,6 +16,7 @@\n #include \"hashmap.h\"\n #include \"replace-object.h\"\n #include \"progress.h\"\n+#include \"repo-settings.h\"\n \n #define GRAPH_SIGNATURE 0x43475048 /* \"CGPH\" */\n #define GRAPH_CHUNKID_OIDFANOUT 0x4f494446 /* \"OIDF\" */\n@@ -466,7 +467,6 @@ static void prepare_commit_graph_one(struct repository *r, const char *obj_dir)\n static int prepare_commit_graph(struct repository *r)\n {\n \tstruct object_directory *odb;\n-\tint config_value;\n \n \tif (git_env_bool(GIT_TEST_COMMIT_GRAPH_DIE_ON_LOAD, 0))\n \t\tdie(\"dying as requested by the '%s' variable on commit-graph load!\",\n@@ -476,9 +476,10 @@ static int prepare_commit_graph(struct repository *r)\n \t\treturn !!r->objects->commit_graph;\n \tr->objects->commit_graph_attempted = 1;\n \n+\tprepare_repo_settings(r);\n+\n \tif (!git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) &&\n-\t    (repo_config_get_bool(r, \"core.commitgraph\", &config_value) ||\n-\t    !config_value))\n+\t    r->settings->core_commit_graph != 1)\n \t\t/*\n \t\t * This repository is not configured to use commit graphs, so\n \t\t * do not load one. (But report commit_graph_attempted anyway\ndiff --git a/read-cache.c b/read-cache.c\nindex c701f7f8b8..ee1aaa8917 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -25,6 +25,7 @@\n #include \"fsmonitor.h\"\n #include \"thread-utils.h\"\n #include \"progress.h\"\n+#include \"repo-settings.h\"\n \n /* Mask for the name length in ce_flags in the on-disk index */\n \n@@ -1599,16 +1600,17 @@ struct cache_entry *refresh_cache_entry(struct index_state *istate,\n \n #define INDEX_FORMAT_DEFAULT 3\n \n-static unsigned int get_index_format_default(void)\n+static unsigned int get_index_format_default(struct repository *r)\n {\n \tchar *envversion = getenv(\"GIT_INDEX_VERSION\");\n \tchar *endp;\n-\tint value;\n \tunsigned int version = INDEX_FORMAT_DEFAULT;\n \n \tif (!envversion) {\n-\t\tif (!git_config_get_int(\"index.version\", &value))\n-\t\t\tversion = value;\n+\t\tprepare_repo_settings(r);\n+\n+\t\tif (r->settings->index_version >= 0)\n+\t\t\tversion = r->settings->index_version;\n \t\tif (version < INDEX_FORMAT_LB || INDEX_FORMAT_UB < version) {\n \t\t\twarning(_(\"index.version set, but the value is invalid.\\n\"\n \t\t\t\t  \"Using version %i\"), INDEX_FORMAT_DEFAULT);\n@@ -2765,7 +2767,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t}\n \n \tif (!istate->version) {\n-\t\tistate->version = get_index_format_default();\n+\t\tistate->version = get_index_format_default(the_repository);\n \t\tif (git_env_bool(\"GIT_TEST_SPLIT_INDEX\", 0))\n \t\t\tinit_split_index(istate);\n \t}\ndiff --git a/repo-settings.c b/repo-settings.c\nnew file mode 100644\nindex 0000000000..13a9128f62\n--- /dev/null\n+++ b/repo-settings.c\n@@ -0,0 +1,44 @@\n+#include \"cache.h\"\n+#include \"repository.h\"\n+#include \"config.h\"\n+#include \"repo-settings.h\"\n+\n+static int git_repo_config(const char *key, const char *value, void *cb)\n+{\n+\tstruct repo_settings *rs = (struct repo_settings *)cb;\n+\n+\tif (!strcmp(key, \"core.commitgraph\")) {\n+\t\trs->core_commit_graph = git_config_bool(key, value);\n+\t\treturn 0;\n+\t}\n+\tif (!strcmp(key, \"gc.writecommitgraph\")) {\n+\t\trs->gc_write_commit_graph = git_config_bool(key, value);\n+\t\treturn 0;\n+\t}\n+\tif (!strcmp(key, \"pack.usesparse\")) {\n+\t\trs->pack_use_sparse = git_config_bool(key, value);\n+\t\treturn 0;\n+\t}\n+\tif (!strcmp(key, \"index.version\")) {\n+\t\trs->index_version = git_config_int(key, value);\n+\t\treturn 0;\n+\t}\n+\n+\treturn 1;\n+}\n+\n+void prepare_repo_settings(struct repository *r)\n+{\n+\tif (r->settings)\n+\t\treturn;\n+\n+\tr->settings = xmalloc(sizeof(*r->settings));\n+\n+\t/* Defaults */\n+\tr->settings->core_commit_graph = -1;\n+\tr->settings->gc_write_commit_graph = -1;\n+\tr->settings->pack_use_sparse = -1;\n+\tr->settings->index_version = -1;\n+\n+\trepo_config(r, git_repo_config, r->settings);\n+}\ndiff --git a/repo-settings.h b/repo-settings.h\nnew file mode 100644\nindex 0000000000..1151c2193a\n--- /dev/null\n+++ b/repo-settings.h\n@@ -0,0 +1,15 @@\n+#ifndef REPO_SETTINGS_H\n+#define REPO_SETTINGS_H\n+\n+struct repo_settings {\n+\tint core_commit_graph;\n+\tint gc_write_commit_graph;\n+\tint pack_use_sparse;\n+\tint index_version;\n+};\n+\n+struct repository;\n+\n+void prepare_repo_settings(struct repository *r);\n+\n+#endif /* REPO_SETTINGS_H */\ndiff --git a/repository.h b/repository.h\nindex 4fb6a5885f..352afc9cd8 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -4,6 +4,7 @@\n #include \"path.h\"\n \n struct config_set;\n+struct repo_settings;\n struct git_hash_algo;\n struct index_state;\n struct lock_file;\n@@ -72,6 +73,8 @@ struct repository {\n \t */\n \tchar *submodule_prefix;\n \n+\tstruct repo_settings *settings;\n+\n \t/* Subsystems */\n \t/*\n \t * Repository's config which contains key-value pairs from the usual\n-- \ngitgitgadget\n\n"},{"id":"379153","messageId":"da7685936f44a32121ec0568df2a9f23e4003506.1563818059.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.git.gitgitgadget@gmail.com","subject":"[PATCH 5/5] repo-settings: create feature.experimental setting","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-22T17:54:24Z","receivedAt":"2019-07-22T17:54:32Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe 'feature.experimental' setting includes config options that are\nnot committed to become defaults, but could use additional testing.\n\nUpdate the following config settings to take new defaults, and to\nuse the repo_settings struct if not already using them:\n\n* 'pack.useSparse=true'\n\n* 'merge.directoryRenames=true'\n\n* 'fetch.negotiationAlgorithm=skipping'\n\nIn the case of fetch.negotiationAlgorithm, the existing logic\nwould load the config option only when about to use the setting,\nso had a die() statement on an unknown string value. This is\nremoved as now the config is parsed under prepare_repo_settings().\nIn general, this die() is probably misplaced and not valuable.\nA test was removed that checked this die() statement executed.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/feature.txt     | 17 +++++++++++++++\n Documentation/config/fetch.txt       |  3 ++-\n Documentation/config/merge.txt       |  3 ++-\n Documentation/config/pack.txt        |  3 ++-\n builtin/am.c                         |  4 +++-\n fetch-negotiator.c                   | 26 +++++++++++-----------\n fetch-negotiator.h                   |  5 +++--\n fetch-pack.c                         | 11 +++++-----\n merge-recursive.c                    | 32 +++++++++++++++-------------\n merge-recursive.h                    |  1 -\n repo-settings.c                      | 32 +++++++++++++++++++++++++++-\n repo-settings.h                      | 12 ++++++++++-\n t/t5552-skipping-fetch-negotiator.sh | 23 --------------------\n 13 files changed, 107 insertions(+), 65 deletions(-)\n\ndiff --git a/Documentation/config/feature.txt b/Documentation/config/feature.txt\nindex c2d9ef7473..287621b1e8 100644\n--- a/Documentation/config/feature.txt\n+++ b/Documentation/config/feature.txt\n@@ -4,6 +4,23 @@ feature.*::\n \tdeveloper community as recommended defaults and are subject to change.\n \tIn particular, new config options may be added with different defaults.\n \n+feature.experimental::\n+\tEnable config options that are new to Git, and are being considered for\n+\tfuture defaults. Config settings included here may be added or removed\n+\twith each release, including minor version updates. These settings may\n+\thave unintended interactions since they are so new. Please enable this\n+\tsetting if you are interested in providing feedback on experimental\n+\tfeatures. The new default values are:\n++\n+* `pack.useSparse=true` uses a new algorithm when constructing a pack-file\n+which can improve `git push` performance in repos with many files.\n++\n+* `merge.directoryRenames=true` uses a new algorithm for detecting renames by\n+using entire directories at a time instead of single files at a time.\n++\n+* `fetch.negotiationAlgorithm=skipping` may improve fetch negotiation times by\n+skipping more commits at a time, reducing the number of round trips.\n+\n feature.manyCommits::\n \tEnable config options that optimize for repos with many commits. This\n \tsetting is recommended for repos with at least 100,000 commits. The\ndiff --git a/Documentation/config/fetch.txt b/Documentation/config/fetch.txt\nindex ba890b5884..d402110638 100644\n--- a/Documentation/config/fetch.txt\n+++ b/Documentation/config/fetch.txt\n@@ -59,7 +59,8 @@ fetch.negotiationAlgorithm::\n \teffort to converge faster, but may result in a larger-than-necessary\n \tpackfile; The default is \"default\" which instructs Git to use the default algorithm\n \tthat never skips commits (unless the server has acknowledged it or one\n-\tof its descendants).\n+\tof its descendants). If `feature.experimental` is enabled, then this\n+\tsetting defaults to \"skipping\".\n \tUnknown values will cause 'git fetch' to error out.\n +\n See also the `--negotiation-tip` option for linkgit:git-fetch[1].\ndiff --git a/Documentation/config/merge.txt b/Documentation/config/merge.txt\nindex 6a313937f8..e8def2d63c 100644\n--- a/Documentation/config/merge.txt\n+++ b/Documentation/config/merge.txt\n@@ -54,7 +54,8 @@ merge.directoryRenames::\n \tmoved into the new directory.  If set to \"conflict\", a conflict\n \twill be reported for such paths.  If merge.renames is false,\n \tmerge.directoryRenames is ignored and treated as false.  Defaults\n-\tto \"conflict\".\n+\tto \"conflict\" unless `feature.experimental` is enabled and the\n+\tdefault is \"true\".\n \n merge.renormalize::\n \tTell Git that canonical representation of files in the\ndiff --git a/Documentation/config/pack.txt b/Documentation/config/pack.txt\nindex 9cdcfa7324..1d66f0c992 100644\n--- a/Documentation/config/pack.txt\n+++ b/Documentation/config/pack.txt\n@@ -112,7 +112,8 @@ pack.useSparse::\n \tobjects. This can have significant performance benefits when\n \tcomputing a pack to send a small change. However, it is possible\n \tthat extra objects are added to the pack-file if the included\n-\tcommits contain certain types of direct renames.\n+\tcommits contain certain types of direct renames. Default is `false`\n+\tunless `feature.experimental` is enabled.\n \n pack.writeBitmaps (deprecated)::\n \tThis is a deprecated synonym for `repack.writeBitmaps`.\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 1aea657a7f..5f3ee3e6cd 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -34,6 +34,7 @@\n #include \"string-list.h\"\n #include \"packfile.h\"\n #include \"repository.h\"\n+#include \"repo-settings.h\"\n \n /**\n  * Returns the length of the first line of msg.\n@@ -1538,7 +1539,8 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa\n \to.branch1 = \"HEAD\";\n \ttheir_tree_name = xstrfmt(\"%.*s\", linelen(state->msg), state->msg);\n \to.branch2 = their_tree_name;\n-\to.detect_directory_renames = 0;\n+\tprepare_repo_settings(the_repository);\n+\tthe_repository->settings->merge_directory_renames = 0;\n \n \tif (state->quiet)\n \t\to.verbosity = 0;\ndiff --git a/fetch-negotiator.c b/fetch-negotiator.c\nindex d6d685cba0..e2990cf46e 100644\n--- a/fetch-negotiator.c\n+++ b/fetch-negotiator.c\n@@ -2,19 +2,21 @@\n #include \"fetch-negotiator.h\"\n #include \"negotiator/default.h\"\n #include \"negotiator/skipping.h\"\n+#include \"repository.h\"\n+#include \"repo-settings.h\"\n \n-void fetch_negotiator_init(struct fetch_negotiator *negotiator,\n-\t\t\t   const char *algorithm)\n+void fetch_negotiator_init(struct repository *r,\n+\t\t\t   struct fetch_negotiator *negotiator)\n {\n-\tif (algorithm) {\n-\t\tif (!strcmp(algorithm, \"skipping\")) {\n-\t\t\tskipping_negotiator_init(negotiator);\n-\t\t\treturn;\n-\t\t} else if (!strcmp(algorithm, \"default\")) {\n-\t\t\t/* Fall through to default initialization */\n-\t\t} else {\n-\t\t\tdie(\"unknown fetch negotiation algorithm '%s'\", algorithm);\n-\t\t}\n+\tprepare_repo_settings(r);\n+\tswitch(r->settings->fetch_negotiation_algorithm) {\n+\tcase FETCH_NEGOTIATION_SKIPPING:\n+\t\tskipping_negotiator_init(negotiator);\n+\t\treturn;\n+\n+\tcase FETCH_NEGOTIATION_DEFAULT:\n+\tdefault:\n+\t\tdefault_negotiator_init(negotiator);\n+\t\treturn;\n \t}\n-\tdefault_negotiator_init(negotiator);\n }\ndiff --git a/fetch-negotiator.h b/fetch-negotiator.h\nindex 9e3967ce66..ea78868504 100644\n--- a/fetch-negotiator.h\n+++ b/fetch-negotiator.h\n@@ -2,6 +2,7 @@\n #define FETCH_NEGOTIATOR_H\n \n struct commit;\n+struct repository;\n \n /*\n  * An object that supplies the information needed to negotiate the contents of\n@@ -52,7 +53,7 @@ struct fetch_negotiator {\n \tvoid *data;\n };\n \n-void fetch_negotiator_init(struct fetch_negotiator *negotiator,\n-\t\t\t   const char *algorithm);\n+void fetch_negotiator_init(struct repository *r,\n+\t\t\t   struct fetch_negotiator *negotiator);\n \n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 65be043f2a..d81f47c07b 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -36,7 +36,6 @@ static int agent_supported;\n static int server_supports_filtering;\n static struct lock_file shallow_lock;\n static const char *alternate_shallow_file;\n-static char *negotiation_algorithm;\n static struct strbuf fsck_msg_types = STRBUF_INIT;\n \n /* Remember to update object flag allocation in object.h */\n@@ -892,12 +891,13 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,\n \t\t\t\t struct shallow_info *si,\n \t\t\t\t char **pack_lockfile)\n {\n+\tstruct repository *r = the_repository;\n \tstruct ref *ref = copy_ref_list(orig_ref);\n \tstruct object_id oid;\n \tconst char *agent_feature;\n \tint agent_len;\n \tstruct fetch_negotiator negotiator;\n-\tfetch_negotiator_init(&negotiator, negotiation_algorithm);\n+\tfetch_negotiator_init(r, &negotiator);\n \n \tsort_ref_list(&ref, ref_compare_name);\n \tQSORT(sought, nr_sought, cmp_ref_by_name);\n@@ -911,7 +911,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,\n \n \tif (server_supports(\"shallow\"))\n \t\tprint_verbose(args, _(\"Server supports %s\"), \"shallow\");\n-\telse if (args->depth > 0 || is_repository_shallow(the_repository))\n+\telse if (args->depth > 0 || is_repository_shallow(r))\n \t\tdie(_(\"Server does not support shallow clients\"));\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n@@ -1379,6 +1379,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\t\t\t    struct shallow_info *si,\n \t\t\t\t    char **pack_lockfile)\n {\n+\tstruct repository *r = the_repository;\n \tstruct ref *ref = copy_ref_list(orig_ref);\n \tenum fetch_state state = FETCH_CHECK_LOCAL;\n \tstruct oidset common = OIDSET_INIT;\n@@ -1386,7 +1387,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tint in_vain = 0;\n \tint haves_to_send = INITIAL_FLUSH;\n \tstruct fetch_negotiator negotiator;\n-\tfetch_negotiator_init(&negotiator, negotiation_algorithm);\n+\tfetch_negotiator_init(r, &negotiator);\n \tpacket_reader_init(&reader, fd[0], NULL, 0,\n \t\t\t   PACKET_READ_CHOMP_NEWLINE |\n \t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n@@ -1505,8 +1506,6 @@ static void fetch_pack_config(void)\n \tgit_config_get_bool(\"repack.usedeltabaseoffset\", &prefer_ofs_delta);\n \tgit_config_get_bool(\"fetch.fsckobjects\", &fetch_fsck_objects);\n \tgit_config_get_bool(\"transfer.fsckobjects\", &transfer_fsck_objects);\n-\tgit_config_get_string(\"fetch.negotiationalgorithm\",\n-\t\t\t      &negotiation_algorithm);\n \n \tgit_config(fetch_pack_config_cb, NULL);\n }\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 12300131fc..162d5a4753 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -28,6 +28,7 @@\n #include \"submodule.h\"\n #include \"revision.h\"\n #include \"commit-reach.h\"\n+#include \"repo-settings.h\"\n \n struct path_hashmap_entry {\n \tstruct hashmap_entry e;\n@@ -1375,10 +1376,14 @@ static int handle_rename_via_dir(struct merge_options *opt,\n \t * there is no content merge to do; just move the file into the\n \t * desired final location.\n \t */\n+\tstruct repository *r = the_repository;\n \tconst struct rename *ren = ci->ren1;\n \tconst struct diff_filespec *dest = ren->pair->two;\n \tchar *file_path = dest->path;\n-\tint mark_conflicted = (opt->detect_directory_renames == 1);\n+\tint mark_conflicted;\n+\n+\tprepare_repo_settings(r);\n+\tmark_conflicted = (r->settings->merge_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT);\n \tassert(ren->dir_rename_original_dest);\n \n \tif (!opt->call_depth && would_lose_untracked(opt, dest->path)) {\n@@ -2850,6 +2855,7 @@ static int detect_and_process_renames(struct merge_options *opt,\n \t\t\t\t      struct string_list *entries,\n \t\t\t\t      struct rename_info *ri)\n {\n+\tstruct repository *r = the_repository;\n \tstruct diff_queue_struct *head_pairs, *merge_pairs;\n \tstruct hashmap *dir_re_head, *dir_re_merge;\n \tint clean = 1;\n@@ -2863,7 +2869,8 @@ static int detect_and_process_renames(struct merge_options *opt,\n \thead_pairs = get_diffpairs(opt, common, head);\n \tmerge_pairs = get_diffpairs(opt, common, merge);\n \n-\tif (opt->detect_directory_renames) {\n+\tprepare_repo_settings(r);\n+\tif (r->settings->merge_directory_renames) {\n \t\tdir_re_head = get_directory_renames(head_pairs);\n \t\tdir_re_merge = get_directory_renames(merge_pairs);\n \n@@ -3112,6 +3119,7 @@ static int handle_rename_normal(struct merge_options *opt,\n \t\t\t\tconst struct diff_filespec *b,\n \t\t\t\tstruct rename_conflict_info *ci)\n {\n+\tstruct repository *r = the_repository;\n \tstruct rename *ren = ci->ren1;\n \tstruct merge_file_info mfi;\n \tint clean;\n@@ -3121,7 +3129,9 @@ static int handle_rename_normal(struct merge_options *opt,\n \tclean = handle_content_merge(&mfi, opt, path, was_dirty(opt, path),\n \t\t\t\t     o, a, b, ci);\n \n-\tif (clean && opt->detect_directory_renames == 1 &&\n+\tprepare_repo_settings(r);\n+\tif (clean &&\n+\t    r->settings->merge_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT &&\n \t    ren->dir_rename_original_dest) {\n \t\tif (update_stages(opt, path,\n \t\t\t\t  NULL,\n@@ -3155,6 +3165,7 @@ static void dir_rename_warning(const char *msg,\n static int warn_about_dir_renamed_entries(struct merge_options *opt,\n \t\t\t\t\t  struct rename *ren)\n {\n+\tstruct repository *r = the_repository;\n \tconst char *msg;\n \tint clean = 1, is_add;\n \n@@ -3166,12 +3177,13 @@ static int warn_about_dir_renamed_entries(struct merge_options *opt,\n \t\treturn clean;\n \n \t/* Sanity checks */\n-\tassert(opt->detect_directory_renames > 0);\n+\tprepare_repo_settings(r);\n+\tassert(r->settings->merge_directory_renames > 0);\n \tassert(ren->dir_rename_original_type == 'A' ||\n \t       ren->dir_rename_original_type == 'R');\n \n \t/* Check whether to treat directory renames as a conflict */\n-\tclean = (opt->detect_directory_renames == 2);\n+\tclean = (r->settings->merge_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE);\n \n \tis_add = (ren->dir_rename_original_type == 'A');\n \tif (ren->dir_rename_original_type == 'A' && clean) {\n@@ -3662,15 +3674,6 @@ static void merge_recursive_config(struct merge_options *opt)\n \t\topt->merge_detect_rename = git_config_rename(\"merge.renames\", value);\n \t\tfree(value);\n \t}\n-\tif (!git_config_get_string(\"merge.directoryrenames\", &value)) {\n-\t\tint boolval = git_parse_maybe_bool(value);\n-\t\tif (0 <= boolval) {\n-\t\t\topt->detect_directory_renames = boolval ? 2 : 0;\n-\t\t} else if (!strcasecmp(value, \"conflict\")) {\n-\t\t\topt->detect_directory_renames = 1;\n-\t\t} /* avoid erroring on values from future versions of git */\n-\t\tfree(value);\n-\t}\n \tgit_config(git_xmerge_config, NULL);\n }\n \n@@ -3687,7 +3690,6 @@ void init_merge_options(struct merge_options *opt,\n \topt->renormalize = 0;\n \topt->diff_detect_rename = -1;\n \topt->merge_detect_rename = -1;\n-\topt->detect_directory_renames = 1;\n \tmerge_recursive_config(opt);\n \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n \tif (merge_verbosity)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex c2b7bb65c6..b8eba244ee 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -22,7 +22,6 @@ struct merge_options {\n \tunsigned renormalize : 1;\n \tlong xdl_opts;\n \tint verbosity;\n-\tint detect_directory_renames;\n \tint diff_detect_rename;\n \tint merge_detect_rename;\n \tint diff_rename_limit;\ndiff --git a/repo-settings.c b/repo-settings.c\nindex 9e4b8e6268..5e9249c437 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -9,6 +9,12 @@ static int git_repo_config(const char *key, const char *value, void *cb)\n {\n \tstruct repo_settings *rs = (struct repo_settings *)cb;\n \n+\tif (!strcmp(key, \"feature.experimental\")) {\n+\t\tUPDATE_DEFAULT(rs->pack_use_sparse, 1);\n+\t\tUPDATE_DEFAULT(rs->merge_directory_renames, MERGE_DIRECTORY_RENAMES_TRUE);\n+\t\tUPDATE_DEFAULT(rs->fetch_negotiation_algorithm, FETCH_NEGOTIATION_SKIPPING);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(key, \"feature.manycommits\")) {\n \t\tUPDATE_DEFAULT(rs->core_commit_graph, 1);\n \t\tUPDATE_DEFAULT(rs->gc_write_commit_graph, 1);\n@@ -50,6 +56,23 @@ static int git_repo_config(const char *key, const char *value, void *cb)\n \t\t\t\t\"using 'keep' default value\"), value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(key, \"merge.directoryrenames\")) {\n+\t\tint bool_value = git_parse_maybe_bool(value);\n+\t\tif (0 <= bool_value) {\n+\t\t\trs->merge_directory_renames = bool_value ? MERGE_DIRECTORY_RENAMES_TRUE : 0;\n+\t\t} else if (!strcasecmp(value, \"conflict\")) {\n+\t\t\trs->merge_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n+\t\t}\n+\t\treturn 0;\n+\t}\n+\tif (!strcmp(key, \"fetch.negotiationalgorithm\"))\t{\n+\t\tif (!strcasecmp(value, \"skipping\")) {\n+\t\t\trs->fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;\n+\t\t} else {\n+\t\t\trs->fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT;\n+\t\t}\n+\t\treturn 0;\n+\t}\n \n \treturn 1;\n }\n@@ -64,10 +87,14 @@ void prepare_repo_settings(struct repository *r)\n \t/* Defaults */\n \tr->settings->core_commit_graph = -1;\n \tr->settings->gc_write_commit_graph = -1;\n-\tr->settings->pack_use_sparse = -1;\n+\n \tr->settings->index_version = -1;\n \tr->settings->core_untracked_cache = -1;\n \n+\tr->settings->pack_use_sparse = -1;\n+\tr->settings->merge_directory_renames = -1;\n+\tr->settings->fetch_negotiation_algorithm = -1;\n+\n \trepo_config(r, git_repo_config, r->settings);\n \n \t/* Hack for test programs like test-dump-untracked-cache */\n@@ -75,4 +102,7 @@ void prepare_repo_settings(struct repository *r)\n \t\tr->settings->core_untracked_cache = CORE_UNTRACKED_CACHE_KEEP;\n \telse\n \t\tUPDATE_DEFAULT(r->settings->core_untracked_cache, CORE_UNTRACKED_CACHE_KEEP);\n+\n+\tUPDATE_DEFAULT(r->settings->merge_directory_renames, MERGE_DIRECTORY_RENAMES_CONFLICT);\n+\tUPDATE_DEFAULT(r->settings->fetch_negotiation_algorithm, FETCH_NEGOTIATION_DEFAULT);\n }\ndiff --git a/repo-settings.h b/repo-settings.h\nindex bac9b87d49..cecf7d0028 100644\n--- a/repo-settings.h\n+++ b/repo-settings.h\n@@ -4,12 +4,22 @@\n #define CORE_UNTRACKED_CACHE_WRITE (1 << 0)\n #define CORE_UNTRACKED_CACHE_KEEP (1 << 1)\n \n+#define MERGE_DIRECTORY_RENAMES_CONFLICT 1\n+#define MERGE_DIRECTORY_RENAMES_TRUE 2\n+\n+#define FETCH_NEGOTIATION_DEFAULT 1\n+#define FETCH_NEGOTIATION_SKIPPING 2\n+\n struct repo_settings {\n \tint core_commit_graph;\n \tint gc_write_commit_graph;\n-\tint pack_use_sparse;\n+\n \tint index_version;\n \tint core_untracked_cache;\n+\n+\tint pack_use_sparse;\n+\tint merge_directory_renames;\n+\tint fetch_negotiation_algorithm;\n };\n \n struct repository;\ndiff --git a/t/t5552-skipping-fetch-negotiator.sh b/t/t5552-skipping-fetch-negotiator.sh\nindex 8a14be51a1..f70cbcc9ca 100755\n--- a/t/t5552-skipping-fetch-negotiator.sh\n+++ b/t/t5552-skipping-fetch-negotiator.sh\n@@ -60,29 +60,6 @@ test_expect_success 'commits with no parents are sent regardless of skip distanc\n \thave_not_sent c6 c4 c3\n '\n \n-test_expect_success 'unknown fetch.negotiationAlgorithm values error out' '\n-\trm -rf server client trace &&\n-\tgit init server &&\n-\ttest_commit -C server to_fetch &&\n-\n-\tgit init client &&\n-\ttest_commit -C client on_client &&\n-\tgit -C client checkout on_client &&\n-\n-\ttest_config -C client fetch.negotiationAlgorithm invalid &&\n-\ttest_must_fail git -C client fetch \"$(pwd)/server\" 2>err &&\n-\ttest_i18ngrep \"unknown fetch negotiation algorithm\" err &&\n-\n-\t# Explicit \"default\" value\n-\ttest_config -C client fetch.negotiationAlgorithm default &&\n-\tgit -C client -c fetch.negotiationAlgorithm=default fetch \"$(pwd)/server\" &&\n-\n-\t# Implementation detail: If there is nothing to fetch, we will not error out\n-\ttest_config -C client fetch.negotiationAlgorithm invalid &&\n-\tgit -C client fetch \"$(pwd)/server\" 2>err &&\n-\ttest_i18ngrep ! \"unknown fetch negotiation algorithm\" err\n-'\n-\n test_expect_success 'when two skips collide, favor the larger one' '\n \trm -rf server client trace &&\n \tgit init server &&\n-- \ngitgitgadget\n"},{"id":"379166","messageId":"nycvar.QRO.7.76.6.1907231512130.21907@tvgsbejvaqbjf.bet","threadId":"51508","inReplyTo":"72f652b89c71526cc423e7812de66f41a079f181.1563818059.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/5] repo-settings: consolidate some config settings","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-23T13:12:17Z","receivedAt":"2019-07-23T13:13:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stolee,\n\nOn Mon, 22 Jul 2019, Derrick Stolee via GitGitGadget wrote:\n\n> diff --git a/builtin/gc.c b/builtin/gc.c\n> index c18efadda5..243be2907b 100644\n> --- a/builtin/gc.c\n> +++ b/builtin/gc.c\n> @@ -27,6 +27,7 @@\n>  #include \"pack-objects.h\"\n>  #include \"blob.h\"\n>  #include \"tree.h\"\n> +#include \"repo-settings.h\"\n>\n>  #define FAILED_RUN \"failed to run %s\"\n>\n> @@ -41,7 +42,6 @@ static int aggressive_depth = 50;\n>  static int aggressive_window = 250;\n>  static int gc_auto_threshold = 6700;\n>  static int gc_auto_pack_limit = 50;\n> -static int gc_write_commit_graph;\n\nI _really_ like that direction. Anything that removes global state will\nimprove Git's source code.\n\n> [...]\n> diff --git a/read-cache.c b/read-cache.c\n> index c701f7f8b8..ee1aaa8917 100644\n> --- a/read-cache.c\n> +++ b/read-cache.c\n> [...]\n> @@ -2765,7 +2767,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n>  \t}\n>\n>  \tif (!istate->version) {\n> -\t\tistate->version = get_index_format_default();\n> +\t\tistate->version = get_index_format_default(the_repository);\n\nIt is too bad that `read-cache.h` is not `the_repository`-free at the\nmoment...\n\n>  \t\tif (git_env_bool(\"GIT_TEST_SPLIT_INDEX\", 0))\n>  \t\t\tinit_split_index(istate);\n>  \t}\n> diff --git a/repo-settings.c b/repo-settings.c\n> new file mode 100644\n> index 0000000000..13a9128f62\n> --- /dev/null\n> +++ b/repo-settings.c\n> @@ -0,0 +1,44 @@\n> +#include \"cache.h\"\n> +#include \"repository.h\"\n> +#include \"config.h\"\n> +#include \"repo-settings.h\"\n> +\n> +static int git_repo_config(const char *key, const char *value, void *cb)\n> +{\n> +\tstruct repo_settings *rs = (struct repo_settings *)cb;\n> +\n> +\tif (!strcmp(key, \"core.commitgraph\")) {\n> +\t\trs->core_commit_graph = git_config_bool(key, value);\n> +\t\treturn 0;\n> +\t}\n> +\tif (!strcmp(key, \"gc.writecommitgraph\")) {\n> +\t\trs->gc_write_commit_graph = git_config_bool(key, value);\n> +\t\treturn 0;\n> +\t}\n> +\tif (!strcmp(key, \"pack.usesparse\")) {\n> +\t\trs->pack_use_sparse = git_config_bool(key, value);\n> +\t\treturn 0;\n> +\t}\n> +\tif (!strcmp(key, \"index.version\")) {\n> +\t\trs->index_version = git_config_int(key, value);\n> +\t\treturn 0;\n> +\t}\n\nI would actually prefer to use the `repo_config_get_*()` family here.\nThat way, we really avoid re-parsing the config.\n\n> +\n> +\treturn 1;\n> +}\n> +\n> +void prepare_repo_settings(struct repository *r)\n> +{\n> +\tif (r->settings)\n> +\t\treturn;\n> +\n> +\tr->settings = xmalloc(sizeof(*r->settings));\n> +\n> +\t/* Defaults */\n> +\tr->settings->core_commit_graph = -1;\n> +\tr->settings->gc_write_commit_graph = -1;\n> +\tr->settings->pack_use_sparse = -1;\n> +\tr->settings->index_version = -1;\n> +\n> +\trepo_config(r, git_repo_config, r->settings);\n> +}\n> diff --git a/repo-settings.h b/repo-settings.h\n> new file mode 100644\n> index 0000000000..1151c2193a\n> --- /dev/null\n> +++ b/repo-settings.h\n> @@ -0,0 +1,15 @@\n> +#ifndef REPO_SETTINGS_H\n> +#define REPO_SETTINGS_H\n> +\n> +struct repo_settings {\n> +\tint core_commit_graph;\n> +\tint gc_write_commit_graph;\n> +\tint pack_use_sparse;\n> +\tint index_version;\n> +};\n> +\n> +struct repository;\n> +\n> +void prepare_repo_settings(struct repository *r);\n\nHmm. I can see that you wanted to encapsulate this, but I do not really\nagree that this needs to be encapsulated away from `repository.h`. I'd\nrather declare `struct repo_settings` in `repository.h` and then make\nthe `settings` a field of that type (as opposed to a pointer to that\ntype). In general, I like to avoid unnecessary `malloc()`s, and this\nhere instance is one of them.\n\nThanks,\nDscho\n\n> +\n> +#endif /* REPO_SETTINGS_H */\n> diff --git a/repository.h b/repository.h\n> index 4fb6a5885f..352afc9cd8 100644\n> --- a/repository.h\n> +++ b/repository.h\n> @@ -4,6 +4,7 @@\n>  #include \"path.h\"\n>\n>  struct config_set;\n> +struct repo_settings;\n>  struct git_hash_algo;\n>  struct index_state;\n>  struct lock_file;\n> @@ -72,6 +73,8 @@ struct repository {\n>  \t */\n>  \tchar *submodule_prefix;\n>\n> +\tstruct repo_settings *settings;\n> +\n>  \t/* Subsystems */\n>  \t/*\n>  \t * Repository's config which contains key-value pairs from the usual\n> --\n> gitgitgadget\n>\n>\n"},{"id":"379168","messageId":"nycvar.QRO.7.76.6.1907231544200.21907@tvgsbejvaqbjf.bet","threadId":"51508","inReplyTo":"e00a1be75b410694374b0d9bd60ab16d67ef6d20.1563818059.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/5] repo-settings: add feature.manyCommits setting","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-23T14:53:38Z","receivedAt":"2019-07-23T14:53:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stolee,\n\nOn Mon, 22 Jul 2019, Derrick Stolee via GitGitGadget wrote:\n\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> When a repo has many commits, it is helpful to write and read the\n> commit-graph file. Future changes to Git may include new config\n> settings that are benefitial in this scenario.\n\ns/benefitial/beneficial/\n\n>\n> Create the 'feature.manyCommits' config setting that changes the\n> default values of 'core.commitGraph' and 'gc.writeCommitGraph' to\n> true.\n\nGreat!\n\n> diff --git a/repo-settings.c b/repo-settings.c\n> index 13a9128f62..f328602fd7 100644\n> --- a/repo-settings.c\n> +++ b/repo-settings.c\n> @@ -3,10 +3,17 @@\n>  #include \"config.h\"\n>  #include \"repo-settings.h\"\n>\n> +#define UPDATE_DEFAULT(s,v) do { if (s == -1) { s = v; } } while(0)\n> +\n>  static int git_repo_config(const char *key, const char *value, void *cb)\n>  {\n>  \tstruct repo_settings *rs = (struct repo_settings *)cb;\n>\n> +\tif (!strcmp(key, \"feature.manycommits\")) {\n> +\t\tUPDATE_DEFAULT(rs->core_commit_graph, 1);\n> +\t\tUPDATE_DEFAULT(rs->gc_write_commit_graph, 1);\n> +\t\treturn 0;\n> +\t}\n>  \tif (!strcmp(key, \"core.commitgraph\")) {\n>  \t\trs->core_commit_graph = git_config_bool(key, value);\n>  \t\treturn 0;\n\nOkay, this one is tricky. The behavior I would want is for\n`feature.manycommits` to override the _default_. And if I set\n`feature.manycommits = false` (e.g. via `git -c\nfeature.manycommits=false ...`), I would want the default to \"go back\".\n\nSo I'd really rather see this as\n\n\tif (!repo_config_get_bool(r, \"feature.manycommits\", &b) && b) {\n\t\trs->core_commit_graph = 1;\n\t\trs->gc_write_commit_graph = 1;\n\t}\n\n\t[...]\n\n\trepo_config_get_bool(r, \"core.commitgraph\", &rs->core_commit_graph);\n\nI.e. override the default only if `feature.manyCommits` was true *in the\nend*.\n\nIn any case, we really need to make sure to handle the `= false` case\ncorrectly here. We might want to override the setting from the\ncommand-line, or it might be set in `~/.gitconfig` and need to be\noverridden in a local repository. We need to handle it.\n\nOtherwise this looks good!\nDscho\n"},{"id":"379169","messageId":"nycvar.QRO.7.76.6.1907231656580.21907@tvgsbejvaqbjf.bet","threadId":"51508","inReplyTo":"47ae3e7d4d765a00d14e8892db88a8936d56591b.1563818059.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 3/5] repo-settings: parse core.untrackedCache","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-23T15:04:36Z","receivedAt":"2019-07-23T15:04:51Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stolee,\n\nOn Mon, 22 Jul 2019, Derrick Stolee via GitGitGadget wrote:\n\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> The core.untrackedCache config setting is slightly complicated,\n> so clarify its use and centralize its parsing into the repo\n> settings.\n>\n> The default value is \"keep\" (returned as -1), which persists the\n> untracked cache if it exists.\n>\n> If the value is set as \"false\" (returned as 0), then remove the\n> untracked cache if it exists.\n>\n> If the value is set as \"true\" (returned as 1), then write the\n> untracked cache and persist it.\n>\n> Instead of relying on magic values of -1, 0, and 1, split these\n> options into bitflags CORE_UNTRACKED_CACHE_KEEP and\n> CORE_UNTRACKED_CACHE_WRITE. This allows the use of \"-1\" as a\n> default value. After parsing the config options, if the value is\n> unset we can initialize it to CORE_UNTRACKED_CACHE_KEEP.\n\nNice!\n\n> [...]\n> diff --git a/read-cache.c b/read-cache.c\n> index ee1aaa8917..e67e6f6e3e 100644\n> --- a/read-cache.c\n> +++ b/read-cache.c\n> @@ -1846,18 +1846,17 @@ static void check_ce_order(struct index_state *istate)\n>\n>  static void tweak_untracked_cache(struct index_state *istate)\n>  {\n> -\tswitch (git_config_get_untracked_cache()) {\n> -\tcase -1: /* keep: do nothing */\n> -\t\tbreak;\n> -\tcase 0: /* false */\n> +\tstruct repository *r = the_repository;\n> +\n> +\tprepare_repo_settings(r);\n> +\n> +\tif (!(r->settings->core_untracked_cache & CORE_UNTRACKED_CACHE_KEEP)) {\n>  \t\tremove_untracked_cache(istate);\n> -\t\tbreak;\n> -\tcase 1: /* true */\n> -\t\tadd_untracked_cache(istate);\n> -\t\tbreak;\n> -\tdefault: /* unknown value: do nothing */\n> -\t\tbreak;\n> +\t\treturn;\n>  \t}\n> +\n> +\tif (r->settings->core_untracked_cache & CORE_UNTRACKED_CACHE_WRITE)\n> +\t\tadd_untracked_cache(istate);\n\nThis changes the flow in a subtle way: in the\n`CORE_UNTRACKED_CACHE_WRITE` case, we used to _not_ remove the untracked\ncache, but now we do.\n\nI _think_ what you would want to do is replace the `!(..._KEEP)`\ncondition by `..._REMOVE`.\n\n>  }\n>\n>  static void tweak_split_index(struct index_state *istate)\n> diff --git a/repo-settings.c b/repo-settings.c\n> index f328602fd7..807c5a29d6 100644\n> --- a/repo-settings.c\n> +++ b/repo-settings.c\n> @@ -30,6 +30,20 @@ static int git_repo_config(const char *key, const char *value, void *cb)\n>  \t\trs->index_version = git_config_int(key, value);\n>  \t\treturn 0;\n>  \t}\n> +\tif (!strcmp(key, \"core.untrackedcache\")) {\n> +\t\tint bool_value = git_parse_maybe_bool(value);\n> +\t\tif (bool_value == 0)\n> +\t\t\trs->core_untracked_cache = 0;\n> +\t\telse if (bool_value == 1)\n> +\t\t\trs->core_untracked_cache = CORE_UNTRACKED_CACHE_KEEP |\n> +\t\t\t\t\t\t   CORE_UNTRACKED_CACHE_WRITE;\n> +\t\telse if (!strcasecmp(value, \"keep\"))\n> +\t\t\trs->core_untracked_cache = CORE_UNTRACKED_CACHE_KEEP;\n> +\t\telse\n> +\t\t\terror(_(\"unknown core.untrackedCache value '%s'; \"\n> +\t\t\t\t\"using 'keep' default value\"), value);\n> +\t\treturn 0;\n> +\t}\n>\n>  \treturn 1;\n>  }\n> @@ -46,6 +60,13 @@ void prepare_repo_settings(struct repository *r)\n>  \tr->settings->gc_write_commit_graph = -1;\n>  \tr->settings->pack_use_sparse = -1;\n>  \tr->settings->index_version = -1;\n> +\tr->settings->core_untracked_cache = -1;\n\nAt this point at the latest, I am starting to wonder whether it would\nnot make more sense to use `memset(..., -1, sizeof(struct\nrepo_settings)` instead.\n\n>\n>  \trepo_config(r, git_repo_config, r->settings);\n> +\n> +\t/* Hack for test programs like test-dump-untracked-cache */\n> +\tif (ignore_untracked_cache_config)\n> +\t\tr->settings->core_untracked_cache = CORE_UNTRACKED_CACHE_KEEP;\n> +\telse\n> +\t\tUPDATE_DEFAULT(r->settings->core_untracked_cache, CORE_UNTRACKED_CACHE_KEEP);\n>  }\n> diff --git a/repo-settings.h b/repo-settings.h\n> index 1151c2193a..bac9b87d49 100644\n> --- a/repo-settings.h\n> +++ b/repo-settings.h\n> @@ -1,11 +1,15 @@\n>  #ifndef REPO_SETTINGS_H\n>  #define REPO_SETTINGS_H\n>\n> +#define CORE_UNTRACKED_CACHE_WRITE (1 << 0)\n> +#define CORE_UNTRACKED_CACHE_KEEP (1 << 1)\n\nI think it would read even nicer as an enum. In any case, using `1<<1`\nsuggests that this is a bit field, but I don't think that is what we\nactually want here. Instead, what `core_untracked_cache` seems to be (at\nleast from my point of view) is a mode, where any two modes are mutually\nexclusive.\n\nFor example, what is the difference between `(_KEEP | _WRITE)` and\n`(_WRITE)` supposed to be? That the latter writes a fresh untracked\ncache without looking at the previous one? That's not how the existing\ncode behaves, though...\n\nCiao,\nDscho\n\n> +\n>  struct repo_settings {\n>  \tint core_commit_graph;\n>  \tint gc_write_commit_graph;\n>  \tint pack_use_sparse;\n>  \tint index_version;\n> +\tint core_untracked_cache;\n>  };\n>\n>  struct repository;\n> --\n> gitgitgadget\n>\n>\n"},{"id":"379170","messageId":"nycvar.QRO.7.76.6.1907231708280.21907@tvgsbejvaqbjf.bet","threadId":"51508","inReplyTo":"da7685936f44a32121ec0568df2a9f23e4003506.1563818059.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 5/5] repo-settings: create feature.experimental setting","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-23T15:20:17Z","receivedAt":"2019-07-23T15:20:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stolee,\n\nOn Mon, 22 Jul 2019, Derrick Stolee via GitGitGadget wrote:\n\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> The 'feature.experimental' setting includes config options that are\n> not committed to become defaults, but could use additional testing.\n>\n> Update the following config settings to take new defaults, and to\n> use the repo_settings struct if not already using them:\n>\n> * 'pack.useSparse=true'\n>\n> * 'merge.directoryRenames=true'\n>\n> * 'fetch.negotiationAlgorithm=skipping'\n>\n> In the case of fetch.negotiationAlgorithm, the existing logic\n> would load the config option only when about to use the setting,\n> so had a die() statement on an unknown string value. This is\n> removed as now the config is parsed under prepare_repo_settings().\n> In general, this die() is probably misplaced and not valuable.\n> A test was removed that checked this die() statement executed.\n\nGood.\n\n> [...]\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index 12300131fc..162d5a4753 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -28,6 +28,7 @@\n>  #include \"submodule.h\"\n>  #include \"revision.h\"\n>  #include \"commit-reach.h\"\n> +#include \"repo-settings.h\"\n>\n>  struct path_hashmap_entry {\n>  \tstruct hashmap_entry e;\n> @@ -1375,10 +1376,14 @@ static int handle_rename_via_dir(struct merge_options *opt,\n>  \t * there is no content merge to do; just move the file into the\n>  \t * desired final location.\n>  \t */\n> +\tstruct repository *r = the_repository;\n>  \tconst struct rename *ren = ci->ren1;\n>  \tconst struct diff_filespec *dest = ren->pair->two;\n>  \tchar *file_path = dest->path;\n> -\tint mark_conflicted = (opt->detect_directory_renames == 1);\n\nI actually don't think that we want to do that; the `opt` parameter is\npassed to the merge recursive algorithm specifically so that it can be\noverridden by the caller.\n\nInstead, what we should do, I think, is to change `init_merge_options()`\n(which already gets a parameter of type `struct repository *`) so that\nit does not hard-code the `detect_directory_renames` value to `1` but to\nquery the repo settings instead.\n\n> +\tint mark_conflicted;\n> +\n> +\tprepare_repo_settings(r);\n> +\tmark_conflicted = (r->settings->merge_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT);\n>  \tassert(ren->dir_rename_original_dest);\n>\n>  \tif (!opt->call_depth && would_lose_untracked(opt, dest->path)) {\n> @@ -2850,6 +2855,7 @@ static int detect_and_process_renames(struct merge_options *opt,\n>  \t\t\t\t      struct string_list *entries,\n>  \t\t\t\t      struct rename_info *ri)\n>  {\n> +\tstruct repository *r = the_repository;\n>  \tstruct diff_queue_struct *head_pairs, *merge_pairs;\n>  \tstruct hashmap *dir_re_head, *dir_re_merge;\n>  \tint clean = 1;\n> @@ -2863,7 +2869,8 @@ static int detect_and_process_renames(struct merge_options *opt,\n>  \thead_pairs = get_diffpairs(opt, common, head);\n>  \tmerge_pairs = get_diffpairs(opt, common, merge);\n>\n> -\tif (opt->detect_directory_renames) {\n> +\tprepare_repo_settings(r);\n> +\tif (r->settings->merge_directory_renames) {\n>  \t\tdir_re_head = get_directory_renames(head_pairs);\n>  \t\tdir_re_merge = get_directory_renames(merge_pairs);\n>\n> @@ -3112,6 +3119,7 @@ static int handle_rename_normal(struct merge_options *opt,\n>  \t\t\t\tconst struct diff_filespec *b,\n>  \t\t\t\tstruct rename_conflict_info *ci)\n>  {\n> +\tstruct repository *r = the_repository;\n>  \tstruct rename *ren = ci->ren1;\n>  \tstruct merge_file_info mfi;\n>  \tint clean;\n> @@ -3121,7 +3129,9 @@ static int handle_rename_normal(struct merge_options *opt,\n>  \tclean = handle_content_merge(&mfi, opt, path, was_dirty(opt, path),\n>  \t\t\t\t     o, a, b, ci);\n>\n> -\tif (clean && opt->detect_directory_renames == 1 &&\n> +\tprepare_repo_settings(r);\n> +\tif (clean &&\n> +\t    r->settings->merge_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT &&\n>  \t    ren->dir_rename_original_dest) {\n>  \t\tif (update_stages(opt, path,\n>  \t\t\t\t  NULL,\n> @@ -3155,6 +3165,7 @@ static void dir_rename_warning(const char *msg,\n>  static int warn_about_dir_renamed_entries(struct merge_options *opt,\n>  \t\t\t\t\t  struct rename *ren)\n>  {\n> +\tstruct repository *r = the_repository;\n>  \tconst char *msg;\n>  \tint clean = 1, is_add;\n>\n> @@ -3166,12 +3177,13 @@ static int warn_about_dir_renamed_entries(struct merge_options *opt,\n>  \t\treturn clean;\n>\n>  \t/* Sanity checks */\n> -\tassert(opt->detect_directory_renames > 0);\n> +\tprepare_repo_settings(r);\n> +\tassert(r->settings->merge_directory_renames > 0);\n>  \tassert(ren->dir_rename_original_type == 'A' ||\n>  \t       ren->dir_rename_original_type == 'R');\n>\n>  \t/* Check whether to treat directory renames as a conflict */\n> -\tclean = (opt->detect_directory_renames == 2);\n> +\tclean = (r->settings->merge_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE);\n>\n>  \tis_add = (ren->dir_rename_original_type == 'A');\n>  \tif (ren->dir_rename_original_type == 'A' && clean) {\n> @@ -3662,15 +3674,6 @@ static void merge_recursive_config(struct merge_options *opt)\n>  \t\topt->merge_detect_rename = git_config_rename(\"merge.renames\", value);\n>  \t\tfree(value);\n>  \t}\n> -\tif (!git_config_get_string(\"merge.directoryrenames\", &value)) {\n> -\t\tint boolval = git_parse_maybe_bool(value);\n> -\t\tif (0 <= boolval) {\n> -\t\t\topt->detect_directory_renames = boolval ? 2 : 0;\n> -\t\t} else if (!strcasecmp(value, \"conflict\")) {\n> -\t\t\topt->detect_directory_renames = 1;\n> -\t\t} /* avoid erroring on values from future versions of git */\n> -\t\tfree(value);\n> -\t}\n>  \tgit_config(git_xmerge_config, NULL);\n>  }\n>\n> @@ -3687,7 +3690,6 @@ void init_merge_options(struct merge_options *opt,\n>  \topt->renormalize = 0;\n>  \topt->diff_detect_rename = -1;\n>  \topt->merge_detect_rename = -1;\n> -\topt->detect_directory_renames = 1;\n\nIn other words: here.\n\n>  \tmerge_recursive_config(opt);\n>  \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n>  \tif (merge_verbosity)\n> diff --git a/merge-recursive.h b/merge-recursive.h\n> index c2b7bb65c6..b8eba244ee 100644\n> --- a/merge-recursive.h\n> +++ b/merge-recursive.h\n> @@ -22,7 +22,6 @@ struct merge_options {\n>  \tunsigned renormalize : 1;\n>  \tlong xdl_opts;\n>  \tint verbosity;\n> -\tint detect_directory_renames;\n>  \tint diff_detect_rename;\n>  \tint merge_detect_rename;\n>  \tint diff_rename_limit;\n> diff --git a/repo-settings.c b/repo-settings.c\n> index 9e4b8e6268..5e9249c437 100644\n> --- a/repo-settings.c\n> +++ b/repo-settings.c\n> @@ -9,6 +9,12 @@ static int git_repo_config(const char *key, const char *value, void *cb)\n>  {\n>  \tstruct repo_settings *rs = (struct repo_settings *)cb;\n>\n> +\tif (!strcmp(key, \"feature.experimental\")) {\n> +\t\tUPDATE_DEFAULT(rs->pack_use_sparse, 1);\n> +\t\tUPDATE_DEFAULT(rs->merge_directory_renames, MERGE_DIRECTORY_RENAMES_TRUE);\n> +\t\tUPDATE_DEFAULT(rs->fetch_negotiation_algorithm, FETCH_NEGOTIATION_SKIPPING);\n> +\t\treturn 0;\n> +\t}\n>  \tif (!strcmp(key, \"feature.manycommits\")) {\n>  \t\tUPDATE_DEFAULT(rs->core_commit_graph, 1);\n>  \t\tUPDATE_DEFAULT(rs->gc_write_commit_graph, 1);\n> @@ -50,6 +56,23 @@ static int git_repo_config(const char *key, const char *value, void *cb)\n>  \t\t\t\t\"using 'keep' default value\"), value);\n>  \t\treturn 0;\n>  \t}\n> +\tif (!strcmp(key, \"merge.directoryrenames\")) {\n> +\t\tint bool_value = git_parse_maybe_bool(value);\n> +\t\tif (0 <= bool_value) {\n> +\t\t\trs->merge_directory_renames = bool_value ? MERGE_DIRECTORY_RENAMES_TRUE : 0;\n> +\t\t} else if (!strcasecmp(value, \"conflict\")) {\n> +\t\t\trs->merge_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n> +\t\t}\n> +\t\treturn 0;\n> +\t}\n> +\tif (!strcmp(key, \"fetch.negotiationalgorithm\"))\t{\n> +\t\tif (!strcasecmp(value, \"skipping\")) {\n> +\t\t\trs->fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;\n> +\t\t} else {\n> +\t\t\trs->fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT;\n> +\t\t}\n> +\t\treturn 0;\n> +\t}\n>\n>  \treturn 1;\n>  }\n> @@ -64,10 +87,14 @@ void prepare_repo_settings(struct repository *r)\n>  \t/* Defaults */\n>  \tr->settings->core_commit_graph = -1;\n>  \tr->settings->gc_write_commit_graph = -1;\n> -\tr->settings->pack_use_sparse = -1;\n> +\n>  \tr->settings->index_version = -1;\n>  \tr->settings->core_untracked_cache = -1;\n>\n> +\tr->settings->pack_use_sparse = -1;\n\nThis reordering at this stage in the patch series is a bit confusing.\nMaybe add it at the location where you want it to end up to begin with?\n\nOr use `memset(..., -1, ...)`.\n\n> +\tr->settings->merge_directory_renames = -1;\n> +\tr->settings->fetch_negotiation_algorithm = -1;\n> +\n>  \trepo_config(r, git_repo_config, r->settings);\n>\n>  \t/* Hack for test programs like test-dump-untracked-cache */\n> @@ -75,4 +102,7 @@ void prepare_repo_settings(struct repository *r)\n>  \t\tr->settings->core_untracked_cache = CORE_UNTRACKED_CACHE_KEEP;\n>  \telse\n>  \t\tUPDATE_DEFAULT(r->settings->core_untracked_cache, CORE_UNTRACKED_CACHE_KEEP);\n> +\n> +\tUPDATE_DEFAULT(r->settings->merge_directory_renames, MERGE_DIRECTORY_RENAMES_CONFLICT);\n> +\tUPDATE_DEFAULT(r->settings->fetch_negotiation_algorithm, FETCH_NEGOTIATION_DEFAULT);\n>  }\n> diff --git a/repo-settings.h b/repo-settings.h\n> index bac9b87d49..cecf7d0028 100644\n> --- a/repo-settings.h\n> +++ b/repo-settings.h\n> @@ -4,12 +4,22 @@\n>  #define CORE_UNTRACKED_CACHE_WRITE (1 << 0)\n>  #define CORE_UNTRACKED_CACHE_KEEP (1 << 1)\n>\n> +#define MERGE_DIRECTORY_RENAMES_CONFLICT 1\n> +#define MERGE_DIRECTORY_RENAMES_TRUE 2\n> +\n> +#define FETCH_NEGOTIATION_DEFAULT 1\n> +#define FETCH_NEGOTIATION_SKIPPING 2\n> +\n\nAgain, I'd prefer enums.\n\nThe rest looks pretty fine to me (I have to admit that I only glanced\nover the tests, though).\n\nThanks!\nDscho\n\n>  struct repo_settings {\n>  \tint core_commit_graph;\n>  \tint gc_write_commit_graph;\n> -\tint pack_use_sparse;\n> +\n>  \tint index_version;\n>  \tint core_untracked_cache;\n> +\n> +\tint pack_use_sparse;\n> +\tint merge_directory_renames;\n> +\tint fetch_negotiation_algorithm;\n>  };\n>\n>  struct repository;\n> diff --git a/t/t5552-skipping-fetch-negotiator.sh b/t/t5552-skipping-fetch-negotiator.sh\n> index 8a14be51a1..f70cbcc9ca 100755\n> --- a/t/t5552-skipping-fetch-negotiator.sh\n> +++ b/t/t5552-skipping-fetch-negotiator.sh\n> @@ -60,29 +60,6 @@ test_expect_success 'commits with no parents are sent regardless of skip distanc\n>  \thave_not_sent c6 c4 c3\n>  '\n>\n> -test_expect_success 'unknown fetch.negotiationAlgorithm values error out' '\n> -\trm -rf server client trace &&\n> -\tgit init server &&\n> -\ttest_commit -C server to_fetch &&\n> -\n> -\tgit init client &&\n> -\ttest_commit -C client on_client &&\n> -\tgit -C client checkout on_client &&\n> -\n> -\ttest_config -C client fetch.negotiationAlgorithm invalid &&\n> -\ttest_must_fail git -C client fetch \"$(pwd)/server\" 2>err &&\n> -\ttest_i18ngrep \"unknown fetch negotiation algorithm\" err &&\n> -\n> -\t# Explicit \"default\" value\n> -\ttest_config -C client fetch.negotiationAlgorithm default &&\n> -\tgit -C client -c fetch.negotiationAlgorithm=default fetch \"$(pwd)/server\" &&\n> -\n> -\t# Implementation detail: If there is nothing to fetch, we will not error out\n> -\ttest_config -C client fetch.negotiationAlgorithm invalid &&\n> -\tgit -C client fetch \"$(pwd)/server\" 2>err &&\n> -\ttest_i18ngrep ! \"unknown fetch negotiation algorithm\" err\n> -'\n> -\n>  test_expect_success 'when two skips collide, favor the larger one' '\n>  \trm -rf server client trace &&\n>  \tgit init server &&\n> --\n> gitgitgadget\n>\n"},{"id":"379197","messageId":"bd96b1f1-3369-e130-dbed-8634589cd8e3@gmail.com","threadId":"51508","inReplyTo":"nycvar.QRO.7.76.6.1907231544200.21907@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 2/5] repo-settings: add feature.manyCommits setting","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-07-24T10:41:15Z","receivedAt":"2019-07-24T10:41:19Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/23/2019 10:53 AM, Johannes Schindelin wrote:\n> Hi Stolee,\n> \n> On Mon, 22 Jul 2019, Derrick Stolee via GitGitGadget wrote:\n> \n>> From: Derrick Stolee <dstolee@microsoft.com>\n>>\n>> When a repo has many commits, it is helpful to write and read the\n>> commit-graph file. Future changes to Git may include new config\n>> settings that are benefitial in this scenario.\n> \n> s/benefitial/beneficial/\n> \n>>\n>> Create the 'feature.manyCommits' config setting that changes the\n>> default values of 'core.commitGraph' and 'gc.writeCommitGraph' to\n>> true.\n> \n> Great!\n> \n>> diff --git a/repo-settings.c b/repo-settings.c\n>> index 13a9128f62..f328602fd7 100644\n>> --- a/repo-settings.c\n>> +++ b/repo-settings.c\n>> @@ -3,10 +3,17 @@\n>>  #include \"config.h\"\n>>  #include \"repo-settings.h\"\n>>\n>> +#define UPDATE_DEFAULT(s,v) do { if (s == -1) { s = v; } } while(0)\n>> +\n>>  static int git_repo_config(const char *key, const char *value, void *cb)\n>>  {\n>>  \tstruct repo_settings *rs = (struct repo_settings *)cb;\n>>\n>> +\tif (!strcmp(key, \"feature.manycommits\")) {\n>> +\t\tUPDATE_DEFAULT(rs->core_commit_graph, 1);\n>> +\t\tUPDATE_DEFAULT(rs->gc_write_commit_graph, 1);\n>> +\t\treturn 0;\n>> +\t}\n>>  \tif (!strcmp(key, \"core.commitgraph\")) {\n>>  \t\trs->core_commit_graph = git_config_bool(key, value);\n>>  \t\treturn 0;\n> \n> Okay, this one is tricky. The behavior I would want is for\n> `feature.manycommits` to override the _default_. And if I set\n> `feature.manycommits = false` (e.g. via `git -c\n> feature.manycommits=false ...`), I would want the default to \"go back\".\n> \n> So I'd really rather see this as\n> \n> \tif (!repo_config_get_bool(r, \"feature.manycommits\", &b) && b) {\n> \t\trs->core_commit_graph = 1;\n> \t\trs->gc_write_commit_graph = 1;\n> \t}\n> \n> \t[...]\n> \n> \trepo_config_get_bool(r, \"core.commitgraph\", &rs->core_commit_graph);\n> \n> I.e. override the default only if `feature.manyCommits` was true *in the\n> end*.\n> \n> In any case, we really need to make sure to handle the `= false` case\n> correctly here. We might want to override the setting from the\n> command-line, or it might be set in `~/.gitconfig` and need to be\n> overridden in a local repository. We need to handle it.\n\nI had forgotten about this interaction. Thanks for finding it!\n\nThe way I would fix it is to add an \"int feature_many_commits\" to\nthe repo_settings struct, then check its value at the very end of\nprepare_repo_settings() and then UPDATE_DEFAULT() for the settings\nwe want.\n\nThanks!\n-Stolee\n"},{"id":"379216","messageId":"6d7de6f0-f7cb-ba6b-85e9-fc466944ed6b@gmail.com","threadId":"51508","inReplyTo":"nycvar.QRO.7.76.6.1907231656580.21907@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 3/5] repo-settings: parse core.untrackedCache","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-07-24T19:27:08Z","receivedAt":"2019-07-24T20:34:16Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"\n\nOn 7/23/2019 11:04 AM, Johannes Schindelin wrote:\n> Hi Stolee,\n> \n> On Mon, 22 Jul 2019, Derrick Stolee via GitGitGadget wrote:\n> \n>> From: Derrick Stolee <dstolee@microsoft.com>\n>>\n>> The core.untrackedCache config setting is slightly complicated,\n>> so clarify its use and centralize its parsing into the repo\n>> settings.\n>>\n>> The default value is \"keep\" (returned as -1), which persists the\n>> untracked cache if it exists.\n>>\n>> If the value is set as \"false\" (returned as 0), then remove the\n>> untracked cache if it exists.\n>>\n>> If the value is set as \"true\" (returned as 1), then write the\n>> untracked cache and persist it.\n>>\n>> Instead of relying on magic values of -1, 0, and 1, split these\n>> options into bitflags CORE_UNTRACKED_CACHE_KEEP and\n>> CORE_UNTRACKED_CACHE_WRITE. This allows the use of \"-1\" as a\n>> default value. After parsing the config options, if the value is\n>> unset we can initialize it to CORE_UNTRACKED_CACHE_KEEP.\n> \n> Nice!\n> \n>> [...]\n>> diff --git a/read-cache.c b/read-cache.c\n>> index ee1aaa8917..e67e6f6e3e 100644\n>> --- a/read-cache.c\n>> +++ b/read-cache.c\n>> @@ -1846,18 +1846,17 @@ static void check_ce_order(struct index_state *istate)\n>>\n>>  static void tweak_untracked_cache(struct index_state *istate)\n>>  {\n>> -\tswitch (git_config_get_untracked_cache()) {\n>> -\tcase -1: /* keep: do nothing */\n>> -\t\tbreak;\n>> -\tcase 0: /* false */\n>> +\tstruct repository *r = the_repository;\n>> +\n>> +\tprepare_repo_settings(r);\n>> +\n>> +\tif (!(r->settings->core_untracked_cache & CORE_UNTRACKED_CACHE_KEEP)) {\n>>  \t\tremove_untracked_cache(istate);\n>> -\t\tbreak;\n>> -\tcase 1: /* true */\n>> -\t\tadd_untracked_cache(istate);\n>> -\t\tbreak;\n>> -\tdefault: /* unknown value: do nothing */\n>> -\t\tbreak;\n>> +\t\treturn;\n>>  \t}\n>> +\n>> +\tif (r->settings->core_untracked_cache & CORE_UNTRACKED_CACHE_WRITE)\n>> +\t\tadd_untracked_cache(istate);\n> \n> This changes the flow in a subtle way: in the\n> `CORE_UNTRACKED_CACHE_WRITE` case, we used to _not_ remove the untracked\n> cache, but now we do.\n> \n> I _think_ what you would want to do is replace the `!(..._KEEP)`\n> condition by `..._REMOVE`.\n\nI believe the code as written is correct, but confusing. The value is not an enum, but instead a bitflag. When the config setting is given as \"true\", then both _KEEP and _WRITE are set, so the flow is identical.\n\nHowever, you already suggested switching to an enum, in which case using _REMOVE would be clearer.\n\n> \n>>  }\n>>\n>>  static void tweak_split_index(struct index_state *istate)\n>> diff --git a/repo-settings.c b/repo-settings.c\n>> index f328602fd7..807c5a29d6 100644\n>> --- a/repo-settings.c\n>> +++ b/repo-settings.c\n>> @@ -30,6 +30,20 @@ static int git_repo_config(const char *key, const char *value, void *cb)\n>>  \t\trs->index_version = git_config_int(key, value);\n>>  \t\treturn 0;\n>>  \t}\n>> +\tif (!strcmp(key, \"core.untrackedcache\")) {\n>> +\t\tint bool_value = git_parse_maybe_bool(value);\n>> +\t\tif (bool_value == 0)\n>> +\t\t\trs->core_untracked_cache = 0;\n>> +\t\telse if (bool_value == 1)\n>> +\t\t\trs->core_untracked_cache = CORE_UNTRACKED_CACHE_KEEP |\n>> +\t\t\t\t\t\t   CORE_UNTRACKED_CACHE_WRITE;\n>> +\t\telse if (!strcasecmp(value, \"keep\"))\n>> +\t\t\trs->core_untracked_cache = CORE_UNTRACKED_CACHE_KEEP;\n>> +\t\telse\n>> +\t\t\terror(_(\"unknown core.untrackedCache value '%s'; \"\n>> +\t\t\t\t\"using 'keep' default value\"), value);\n>> +\t\treturn 0;\n>> +\t}\n>>\n>>  \treturn 1;\n>>  }\n>> @@ -46,6 +60,13 @@ void prepare_repo_settings(struct repository *r)\n>>  \tr->settings->gc_write_commit_graph = -1;\n>>  \tr->settings->pack_use_sparse = -1;\n>>  \tr->settings->index_version = -1;\n>> +\tr->settings->core_untracked_cache = -1;\n> \n> At this point at the latest, I am starting to wonder whether it would\n> not make more sense to use `memset(..., -1, sizeof(struct\n> repo_settings)` instead.\n> \n>>\n>>  \trepo_config(r, git_repo_config, r->settings);\n>> +\n>> +\t/* Hack for test programs like test-dump-untracked-cache */\n>> +\tif (ignore_untracked_cache_config)\n>> +\t\tr->settings->core_untracked_cache = CORE_UNTRACKED_CACHE_KEEP;\n>> +\telse\n>> +\t\tUPDATE_DEFAULT(r->settings->core_untracked_cache, CORE_UNTRACKED_CACHE_KEEP);\n>>  }\n>> diff --git a/repo-settings.h b/repo-settings.h\n>> index 1151c2193a..bac9b87d49 100644\n>> --- a/repo-settings.h\n>> +++ b/repo-settings.h\n>> @@ -1,11 +1,15 @@\n>>  #ifndef REPO_SETTINGS_H\n>>  #define REPO_SETTINGS_H\n>>\n>> +#define CORE_UNTRACKED_CACHE_WRITE (1 << 0)\n>> +#define CORE_UNTRACKED_CACHE_KEEP (1 << 1)\n> \n> I think it would read even nicer as an enum. In any case, using `1<<1`\n> suggests that this is a bit field, but I don't think that is what we\n> actually want here. Instead, what `core_untracked_cache` seems to be (at\n> least from my point of view) is a mode, where any two modes are mutually\n> exclusive.\n> \n> For example, what is the difference between `(_KEEP | _WRITE)` and\n> `(_WRITE)` supposed to be? That the latter writes a fresh untracked\n> cache without looking at the previous one? That's not how the existing\n> code behaves, though...\n\nYes, there is no reason to have _WRITE without also _KEEP. An enum is better. Thanks!\n\n> \n> Ciao,\n> Dscho\n> \n>> +\n>>  struct repo_settings {\n>>  \tint core_commit_graph;\n>>  \tint gc_write_commit_graph;\n>>  \tint pack_use_sparse;\n>>  \tint index_version;\n>> +\tint core_untracked_cache;\n>>  };\n>>\n>>  struct repository;\n>> --\n>> gitgitgadget\n>>\n>>\n"},{"id":"379233","messageId":"b9843571-5358-a33e-40cf-0d6d30d30766@gmail.com","threadId":"51508","inReplyTo":"nycvar.QRO.7.76.6.1907231708280.21907@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 5/5] repo-settings: create feature.experimental setting","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-07-25T01:47:27Z","receivedAt":"2019-07-25T01:47:32Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/23/2019 11:20 AM, Johannes Schindelin wrote:\n> Hi Stolee,\n> \n> On Mon, 22 Jul 2019, Derrick Stolee via GitGitGadget wrote:\n>>  struct path_hashmap_entry {\n>>  \tstruct hashmap_entry e;\n>> @@ -1375,10 +1376,14 @@ static int handle_rename_via_dir(struct merge_options *opt,\n>>  \t * there is no content merge to do; just move the file into the\n>>  \t * desired final location.\n>>  \t */\n>> +\tstruct repository *r = the_repository;\n>>  \tconst struct rename *ren = ci->ren1;\n>>  \tconst struct diff_filespec *dest = ren->pair->two;\n>>  \tchar *file_path = dest->path;\n>> -\tint mark_conflicted = (opt->detect_directory_renames == 1);\n> \n> I actually don't think that we want to do that; the `opt` parameter is\n> passed to the merge recursive algorithm specifically so that it can be\n> overridden by the caller.\n> \n> Instead, what we should do, I think, is to change `init_merge_options()`\n> (which already gets a parameter of type `struct repository *`) so that\n> it does not hard-code the `detect_directory_renames` value to `1` but to\n> query the repo settings instead.\n\n[snip]\n>> @@ -3687,7 +3690,6 @@ void init_merge_options(struct merge_options *opt,\n>>  \topt->renormalize = 0;\n>>  \topt->diff_detect_rename = -1;\n>>  \topt->merge_detect_rename = -1;\n>> -\topt->detect_directory_renames = 1;\n> \n> In other words: here.\n\nYes, thanks! Your suggestion makes much more sense and makes the diff\nmuch simpler. No reason to couple too closely to the config storage here.\n\nI think you gave me enough feedback to merit a v2 before others chime\nin, so I'll send one after I validate my current version.\n\nThanks,\n-Stolee\n"},{"id":"379234","messageId":"597ab7d621983b5ad35006a2530aa70b452e895f.1564021392.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 1/5] repo-settings: consolidate some config settings","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-25T02:23:15Z","receivedAt":"2019-07-25T02:23:19Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThere are a few important config settings that are not loaded\nduring git_default_config. These are instead loaded on-demand.\n\nCentralize these config options to a single scan, and store\nall of the values in a repo_settings struct. The values for\neach setting are initialized as negative to indicate \"unset\".\n\nThis centralization will be particularly important in a later\nchange to introduce \"meta\" config settings that change the\ndefaults for these config settings.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Makefile               |  1 +\n builtin/gc.c           | 13 ++++++-------\n builtin/pack-objects.c |  9 +++++----\n commit-graph.c         |  7 ++++---\n read-cache.c           | 12 +++++++-----\n repo-settings.c        | 28 ++++++++++++++++++++++++++++\n repo-settings.h        | 17 +++++++++++++++++\n repository.h           |  5 +++++\n 8 files changed, 73 insertions(+), 19 deletions(-)\n create mode 100644 repo-settings.c\n create mode 100644 repo-settings.h\n\ndiff --git a/Makefile b/Makefile\nindex 11ccea4071..032fe9b5f9 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -964,6 +964,7 @@ LIB_OBJS += refspec.o\n LIB_OBJS += ref-filter.o\n LIB_OBJS += remote.o\n LIB_OBJS += replace-object.o\n+LIB_OBJS += repo-settings.o\n LIB_OBJS += repository.o\n LIB_OBJS += rerere.o\n LIB_OBJS += resolve-undo.o\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex c18efadda5..5e365049aa 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -27,6 +27,7 @@\n #include \"pack-objects.h\"\n #include \"blob.h\"\n #include \"tree.h\"\n+#include \"repo-settings.h\"\n \n #define FAILED_RUN \"failed to run %s\"\n \n@@ -41,7 +42,6 @@ static int aggressive_depth = 50;\n static int aggressive_window = 250;\n static int gc_auto_threshold = 6700;\n static int gc_auto_pack_limit = 50;\n-static int gc_write_commit_graph;\n static int detach_auto = 1;\n static timestamp_t gc_log_expire_time;\n static const char *gc_log_expire = \"1.day.ago\";\n@@ -148,7 +148,6 @@ static void gc_config(void)\n \tgit_config_get_int(\"gc.aggressivedepth\", &aggressive_depth);\n \tgit_config_get_int(\"gc.auto\", &gc_auto_threshold);\n \tgit_config_get_int(\"gc.autopacklimit\", &gc_auto_pack_limit);\n-\tgit_config_get_bool(\"gc.writecommitgraph\", &gc_write_commit_graph);\n \tgit_config_get_bool(\"gc.autodetach\", &detach_auto);\n \tgit_config_get_expiry(\"gc.pruneexpire\", &prune_expire);\n \tgit_config_get_expiry(\"gc.worktreepruneexpire\", &prune_worktrees_expire);\n@@ -685,11 +684,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n \t\tclean_pack_garbage();\n \t}\n \n-\tif (gc_write_commit_graph &&\n-\t    write_commit_graph_reachable(get_object_directory(),\n-\t\t\t\t\t !quiet && !daemonized ? COMMIT_GRAPH_PROGRESS : 0,\n-\t\t\t\t\t NULL))\n-\t\treturn 1;\n+\tprepare_repo_settings(the_repository);\n+\tif (the_repository->settings.gc_write_commit_graph == 1)\n+\t\twrite_commit_graph_reachable(get_object_directory(),\n+\t\t\t\t\t     !quiet && !daemonized ? COMMIT_GRAPH_PROGRESS : 0,\n+\t\t\t\t\t     NULL);\n \n \tif (auto_gc && too_many_loose_objects())\n \t\twarning(_(\"There are too many unreachable loose objects; \"\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 267c562b1f..20bcd57033 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -34,6 +34,7 @@\n #include \"dir.h\"\n #include \"midx.h\"\n #include \"trace2.h\"\n+#include \"repo-settings.h\"\n \n #define IN_PACK(obj) oe_in_pack(&to_pack, obj)\n #define SIZE(obj) oe_size(&to_pack, obj)\n@@ -2709,10 +2710,6 @@ static int git_pack_config(const char *k, const char *v, void *cb)\n \t\tuse_bitmap_index_default = git_config_bool(k, v);\n \t\treturn 0;\n \t}\n-\tif (!strcmp(k, \"pack.usesparse\")) {\n-\t\tsparse = git_config_bool(k, v);\n-\t\treturn 0;\n-\t}\n \tif (!strcmp(k, \"pack.threads\")) {\n \t\tdelta_search_threads = git_config_int(k, v);\n \t\tif (delta_search_threads < 0)\n@@ -3332,6 +3329,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \tread_replace_refs = 0;\n \n \tsparse = git_env_bool(\"GIT_TEST_PACK_SPARSE\", 0);\n+\tprepare_repo_settings(the_repository);\n+\tif (!sparse && the_repository->settings.pack_use_sparse != -1)\n+\t\tsparse = the_repository->settings.pack_use_sparse;\n+\n \treset_pack_idx_option(&pack_idx_opts);\n \tgit_config(git_pack_config, NULL);\n \ndiff --git a/commit-graph.c b/commit-graph.c\nindex b3c4de79b6..0c2dadab9b 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -16,6 +16,7 @@\n #include \"hashmap.h\"\n #include \"replace-object.h\"\n #include \"progress.h\"\n+#include \"repo-settings.h\"\n \n #define GRAPH_SIGNATURE 0x43475048 /* \"CGPH\" */\n #define GRAPH_CHUNKID_OIDFANOUT 0x4f494446 /* \"OIDF\" */\n@@ -466,7 +467,6 @@ static void prepare_commit_graph_one(struct repository *r, const char *obj_dir)\n static int prepare_commit_graph(struct repository *r)\n {\n \tstruct object_directory *odb;\n-\tint config_value;\n \n \tif (git_env_bool(GIT_TEST_COMMIT_GRAPH_DIE_ON_LOAD, 0))\n \t\tdie(\"dying as requested by the '%s' variable on commit-graph load!\",\n@@ -476,9 +476,10 @@ static int prepare_commit_graph(struct repository *r)\n \t\treturn !!r->objects->commit_graph;\n \tr->objects->commit_graph_attempted = 1;\n \n+\tprepare_repo_settings(r);\n+\n \tif (!git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) &&\n-\t    (repo_config_get_bool(r, \"core.commitgraph\", &config_value) ||\n-\t    !config_value))\n+\t    r->settings.core_commit_graph != 1)\n \t\t/*\n \t\t * This repository is not configured to use commit graphs, so\n \t\t * do not load one. (But report commit_graph_attempted anyway\ndiff --git a/read-cache.c b/read-cache.c\nindex c701f7f8b8..bda665a25a 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -25,6 +25,7 @@\n #include \"fsmonitor.h\"\n #include \"thread-utils.h\"\n #include \"progress.h\"\n+#include \"repo-settings.h\"\n \n /* Mask for the name length in ce_flags in the on-disk index */\n \n@@ -1599,16 +1600,17 @@ struct cache_entry *refresh_cache_entry(struct index_state *istate,\n \n #define INDEX_FORMAT_DEFAULT 3\n \n-static unsigned int get_index_format_default(void)\n+static unsigned int get_index_format_default(struct repository *r)\n {\n \tchar *envversion = getenv(\"GIT_INDEX_VERSION\");\n \tchar *endp;\n-\tint value;\n \tunsigned int version = INDEX_FORMAT_DEFAULT;\n \n \tif (!envversion) {\n-\t\tif (!git_config_get_int(\"index.version\", &value))\n-\t\t\tversion = value;\n+\t\tprepare_repo_settings(r);\n+\n+\t\tif (r->settings.index_version >= 0)\n+\t\t\tversion = r->settings.index_version;\n \t\tif (version < INDEX_FORMAT_LB || INDEX_FORMAT_UB < version) {\n \t\t\twarning(_(\"index.version set, but the value is invalid.\\n\"\n \t\t\t\t  \"Using version %i\"), INDEX_FORMAT_DEFAULT);\n@@ -2765,7 +2767,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t}\n \n \tif (!istate->version) {\n-\t\tistate->version = get_index_format_default();\n+\t\tistate->version = get_index_format_default(the_repository);\n \t\tif (git_env_bool(\"GIT_TEST_SPLIT_INDEX\", 0))\n \t\t\tinit_split_index(istate);\n \t}\ndiff --git a/repo-settings.c b/repo-settings.c\nnew file mode 100644\nindex 0000000000..2c33810cc6\n--- /dev/null\n+++ b/repo-settings.c\n@@ -0,0 +1,28 @@\n+#include \"cache.h\"\n+#include \"repository.h\"\n+#include \"config.h\"\n+#include \"repo-settings.h\"\n+\n+void prepare_repo_settings(struct repository *r)\n+{\n+\tint value;\n+\n+\tif (r->settings_initialized)\n+\t\treturn;\n+\n+\t/* Defaults */\n+\tmemset(&r->settings, -1, sizeof(r->settings));\n+\n+\tif (!repo_config_get_bool(r, \"core.commitgraph\", &value))\n+\t\tr->settings.core_commit_graph = value;\n+\tif (!repo_config_get_bool(r, \"gc.writecommitgraph\", &value))\n+\t\tr->settings.gc_write_commit_graph = value;\n+\n+\tif (!repo_config_get_bool(r, \"index.version\", &value))\n+\t\tr->settings.index_version = value;\n+\n+\tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n+\t\tr->settings.pack_use_sparse = value;\n+\n+\tr->settings_initialized = 1;\n+}\ndiff --git a/repo-settings.h b/repo-settings.h\nnew file mode 100644\nindex 0000000000..89fb0159bf\n--- /dev/null\n+++ b/repo-settings.h\n@@ -0,0 +1,17 @@\n+#ifndef REPO_SETTINGS_H\n+#define REPO_SETTINGS_H\n+\n+struct repo_settings {\n+\tint core_commit_graph;\n+\tint gc_write_commit_graph;\n+\n+\tint index_version;\n+\n+\tint pack_use_sparse;\n+};\n+\n+struct repository;\n+\n+void prepare_repo_settings(struct repository *r);\n+\n+#endif /* REPO_SETTINGS_H */\ndiff --git a/repository.h b/repository.h\nindex 4fb6a5885f..a817486825 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -2,8 +2,10 @@\n #define REPOSITORY_H\n \n #include \"path.h\"\n+#include \"repo-settings.h\"\n \n struct config_set;\n+struct repo_settings;\n struct git_hash_algo;\n struct index_state;\n struct lock_file;\n@@ -72,6 +74,9 @@ struct repository {\n \t */\n \tchar *submodule_prefix;\n \n+\tint settings_initialized;\n+\tstruct repo_settings settings;\n+\n \t/* Subsystems */\n \t/*\n \t * Repository's config which contains key-value pairs from the usual\n-- \ngitgitgadget\n\n"},{"id":"379235","messageId":"86380c78321528221c6b0dd6520e714104dbcd18.1564021393.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 2/5] repo-settings: add feature.manyCommits setting","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-25T02:23:15Z","receivedAt":"2019-07-25T02:23:20Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWhen a repo has many commits, it is helpful to write and read the\ncommit-graph file. Future changes to Git may include new config\nsettings that are beneficial in this scenario.\n\nCreate the 'feature.manyCommits' config setting that changes the\ndefault values of 'core.commitGraph' and 'gc.writeCommitGraph' to\ntrue.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config.txt         |  2 ++\n Documentation/config/core.txt    |  3 ++-\n Documentation/config/feature.txt | 15 +++++++++++++++\n Documentation/config/gc.txt      |  4 ++--\n repo-settings.c                  |  7 +++++++\n 5 files changed, 28 insertions(+), 3 deletions(-)\n create mode 100644 Documentation/config/feature.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex e3f5bc3396..77f3b1486b 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -345,6 +345,8 @@ include::config/difftool.txt[]\n \n include::config/fastimport.txt[]\n \n+include::config/feature.txt[]\n+\n include::config/fetch.txt[]\n \n include::config/format.txt[]\ndiff --git a/Documentation/config/core.txt b/Documentation/config/core.txt\nindex 75538d27e7..d80162681a 100644\n--- a/Documentation/config/core.txt\n+++ b/Documentation/config/core.txt\n@@ -577,7 +577,8 @@ the `GIT_NOTES_REF` environment variable.  See linkgit:git-notes[1].\n \n core.commitGraph::\n \tIf true, then git will read the commit-graph file (if it exists)\n-\tto parse the graph structure of commits. Defaults to false. See\n+\tto parse the graph structure of commits. Defaults to false, unless\n+\t`feature.manyCommits` is enabled. See\n \tlinkgit:git-commit-graph[1] for more information.\n \n core.useReplaceRefs::\ndiff --git a/Documentation/config/feature.txt b/Documentation/config/feature.txt\nnew file mode 100644\nindex 0000000000..f74314ae90\n--- /dev/null\n+++ b/Documentation/config/feature.txt\n@@ -0,0 +1,15 @@\n+feature.*::\n+\tThe config settings that start with `feature.` modify the defaults of\n+\ta group of other config settings. These groups are created by the Git\n+\tdeveloper community as recommended defaults and are subject to change.\n+\tIn particular, new config options may be added with different defaults.\n+\n+feature.manyCommits::\n+\tEnable config options that optimize for repos with many commits. This\n+\tsetting is recommended for repos with at least 100,000 commits. The\n+\tnew default values are:\n++\n+* `core.commitGraph=true` enables reading the commit-graph file.\n++\n+* `gc.writeCommitGraph=true` enables writing the commit-graph file during\n+garbage collection.\n\\ No newline at end of file\ndiff --git a/Documentation/config/gc.txt b/Documentation/config/gc.txt\nindex 02b92b18b5..31a5fc4f75 100644\n--- a/Documentation/config/gc.txt\n+++ b/Documentation/config/gc.txt\n@@ -63,8 +63,8 @@ gc.writeCommitGraph::\n \tIf true, then gc will rewrite the commit-graph file when\n \tlinkgit:git-gc[1] is run. When using `git gc --auto`\n \tthe commit-graph will be updated if housekeeping is\n-\trequired. Default is false. See linkgit:git-commit-graph[1]\n-\tfor details.\n+\trequired. Default is false, unless `feature.manyCommits`\n+\tis enabled. See linkgit:git-commit-graph[1] for details.\n \n gc.logExpiry::\n \tIf the file gc.log exists, then `git gc --auto` will print\ndiff --git a/repo-settings.c b/repo-settings.c\nindex 2c33810cc6..3face7d8b9 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -3,6 +3,8 @@\n #include \"config.h\"\n #include \"repo-settings.h\"\n \n+#define UPDATE_DEFAULT(s,v) do { if (s == -1) { s = v; } } while(0)\n+\n void prepare_repo_settings(struct repository *r)\n {\n \tint value;\n@@ -24,5 +26,10 @@ void prepare_repo_settings(struct repository *r)\n \tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n \t\tr->settings.pack_use_sparse = value;\n \n+\tif (!repo_config_get_bool(r, \"feature.manycommits\", &value) && value) {\n+\t\tUPDATE_DEFAULT(r->settings.core_commit_graph, 1);\n+\t\tUPDATE_DEFAULT(r->settings.gc_write_commit_graph, 1);\n+\t}\n+\n \tr->settings_initialized = 1;\n }\n-- \ngitgitgadget\n\n"},{"id":"379236","messageId":"49be7a73454b13b04270531eece87a2c6f6d3189.1564021393.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 3/5] repo-settings: parse core.untrackedCache","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-25T02:23:16Z","receivedAt":"2019-07-25T02:23:24Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe core.untrackedCache config setting is slightly complicated,\nso clarify its use and centralize its parsing into the repo\nsettings.\n\nThe default value is \"keep\" (returned as -1), which persists the\nuntracked cache if it exists.\n\nIf the value is set as \"false\" (returned as 0), then remove the\nuntracked cache if it exists.\n\nIf the value is set as \"true\" (returned as 1), then write the\nuntracked cache and persist it.\n\nInstead of relying on magic values of -1, 0, and 1, split these\noptions into an enum. This allows the use of \"-1\" as a\ndefault value. After parsing the config options, if the value is\nunset we can initialize it to UNTRACKED_CACHE_KEEP.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/update-index.c |  7 +++++--\n config.c               | 24 ------------------------\n read-cache.c           | 19 +++++++++----------\n repo-settings.c        | 21 ++++++++++++++++++++-\n repo-settings.h        |  8 ++++++++\n 5 files changed, 42 insertions(+), 37 deletions(-)\n\ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex dff2f4b837..5ced51c1ee 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -18,6 +18,7 @@\n #include \"dir.h\"\n #include \"split-index.h\"\n #include \"fsmonitor.h\"\n+#include \"repo-settings.h\"\n \n /*\n  * Default to not allowing changes to the list of files. The\n@@ -966,6 +967,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \tstruct parse_opt_ctx_t ctx;\n \tstrbuf_getline_fn getline_fn;\n \tint parseopt_state = PARSE_OPT_UNKNOWN;\n+\tstruct repository *r = the_repository;\n \tstruct option options[] = {\n \t\tOPT_BIT('q', NULL, &refresh_args.flags,\n \t\t\tN_(\"continue refresh even when index needs update\"),\n@@ -1180,11 +1182,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\tremove_split_index(&the_index);\n \t}\n \n+\tprepare_repo_settings(r);\n \tswitch (untracked_cache) {\n \tcase UC_UNSPECIFIED:\n \t\tbreak;\n \tcase UC_DISABLE:\n-\t\tif (git_config_get_untracked_cache() == 1)\n+\t\tif (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE)\n \t\t\twarning(_(\"core.untrackedCache is set to true; \"\n \t\t\t\t  \"remove or change it, if you really want to \"\n \t\t\t\t  \"disable the untracked cache\"));\n@@ -1196,7 +1199,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\treturn !test_if_untracked_cache_is_supported();\n \tcase UC_ENABLE:\n \tcase UC_FORCE:\n-\t\tif (git_config_get_untracked_cache() == 0)\n+\t\tif (r->settings.core_untracked_cache == UNTRACKED_CACHE_REMOVE)\n \t\t\twarning(_(\"core.untrackedCache is set to false; \"\n \t\t\t\t  \"remove or change it, if you really want to \"\n \t\t\t\t  \"enable the untracked cache\"));\ndiff --git a/config.c b/config.c\nindex faa57e436c..3241dbc54d 100644\n--- a/config.c\n+++ b/config.c\n@@ -2277,30 +2277,6 @@ int git_config_get_expiry_in_days(const char *key, timestamp_t *expiry, timestam\n \treturn -1; /* thing exists but cannot be parsed */\n }\n \n-int git_config_get_untracked_cache(void)\n-{\n-\tint val = -1;\n-\tconst char *v;\n-\n-\t/* Hack for test programs like test-dump-untracked-cache */\n-\tif (ignore_untracked_cache_config)\n-\t\treturn -1;\n-\n-\tif (!git_config_get_maybe_bool(\"core.untrackedcache\", &val))\n-\t\treturn val;\n-\n-\tif (!git_config_get_value(\"core.untrackedcache\", &v)) {\n-\t\tif (!strcasecmp(v, \"keep\"))\n-\t\t\treturn -1;\n-\n-\t\terror(_(\"unknown core.untrackedCache value '%s'; \"\n-\t\t\t\"using 'keep' default value\"), v);\n-\t\treturn -1;\n-\t}\n-\n-\treturn -1; /* default value */\n-}\n-\n int git_config_get_split_index(void)\n {\n \tint val;\ndiff --git a/read-cache.c b/read-cache.c\nindex bda665a25a..3761b9a171 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -1846,18 +1846,17 @@ static void check_ce_order(struct index_state *istate)\n \n static void tweak_untracked_cache(struct index_state *istate)\n {\n-\tswitch (git_config_get_untracked_cache()) {\n-\tcase -1: /* keep: do nothing */\n-\t\tbreak;\n-\tcase 0: /* false */\n+\tstruct repository *r = the_repository;\n+\n+\tprepare_repo_settings(r);\n+\n+\tif (r->settings.core_untracked_cache  == UNTRACKED_CACHE_REMOVE) {\n \t\tremove_untracked_cache(istate);\n-\t\tbreak;\n-\tcase 1: /* true */\n-\t\tadd_untracked_cache(istate);\n-\t\tbreak;\n-\tdefault: /* unknown value: do nothing */\n-\t\tbreak;\n+\t\treturn;\n \t}\n+\n+\tif (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE)\n+\t\tadd_untracked_cache(istate);\n }\n \n static void tweak_split_index(struct index_state *istate)\ndiff --git a/repo-settings.c b/repo-settings.c\nindex 3face7d8b9..65b980403c 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -8,6 +8,7 @@\n void prepare_repo_settings(struct repository *r)\n {\n \tint value;\n+\tchar *strval;\n \n \tif (r->settings_initialized)\n \t\treturn;\n@@ -22,14 +23,32 @@ void prepare_repo_settings(struct repository *r)\n \n \tif (!repo_config_get_bool(r, \"index.version\", &value))\n \t\tr->settings.index_version = value;\n+\tif (!repo_config_get_maybe_bool(r, \"core.untrackedcache\", &value)) {\n+\t\tif (value == 0)\n+\t\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_REMOVE;\n+\t\telse\n+\t\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_WRITE;\n+\t} else if (!repo_config_get_string(r, \"core.untrackedcache\", &strval)) {\n+\t\tif (!strcasecmp(strval, \"keep\"))\n+\t\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;\n+\n+\t\tfree(strval);\n+\t}\n+\n \n \tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n \t\tr->settings.pack_use_sparse = value;\n-\n+\t\n \tif (!repo_config_get_bool(r, \"feature.manycommits\", &value) && value) {\n \t\tUPDATE_DEFAULT(r->settings.core_commit_graph, 1);\n \t\tUPDATE_DEFAULT(r->settings.gc_write_commit_graph, 1);\n \t}\n \n+\t/* Hack for test programs like test-dump-untracked-cache */\n+\tif (ignore_untracked_cache_config)\n+\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;\n+\telse\n+\t\tUPDATE_DEFAULT(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);\n+\n \tr->settings_initialized = 1;\n }\ndiff --git a/repo-settings.h b/repo-settings.h\nindex 89fb0159bf..e9b028e1a9 100644\n--- a/repo-settings.h\n+++ b/repo-settings.h\n@@ -1,11 +1,19 @@\n #ifndef REPO_SETTINGS_H\n #define REPO_SETTINGS_H\n \n+enum untracked_cache_setting {\n+\tUNTRACKED_CACHE_UNSET = -1,\n+\tUNTRACKED_CACHE_REMOVE = 0,\n+\tUNTRACKED_CACHE_KEEP = 1,\n+\tUNTRACKED_CACHE_WRITE = 2\n+};\n+\n struct repo_settings {\n \tint core_commit_graph;\n \tint gc_write_commit_graph;\n \n \tint index_version;\n+\tenum untracked_cache_setting core_untracked_cache;\n \n \tint pack_use_sparse;\n };\n-- \ngitgitgadget\n\n"},{"id":"379237","messageId":"86a5a0c5895b4cd99a3d42469abe8142be7ac380.1564021393.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 4/5] repo-settings: create feature.manyFiles setting","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-25T02:23:17Z","receivedAt":"2019-07-25T02:23:24Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe feature.manyFiles setting is suitable for repos with many\nfiles in the working directory. By setting index.version=4 and\ncore.untrackedCache=true, commands such as 'git status' should\nimprove.\n\nWhile adding this setting, modify the index version precedence\ntests to check how this setting overrides the default for\nindex.version is unset.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/core.txt    |  4 +++-\n Documentation/config/feature.txt | 12 +++++++++++-\n Documentation/config/index.txt   |  1 +\n repo-settings.c                  |  4 ++++\n t/t1600-index.sh                 | 31 ++++++++++++++++++++++++++-----\n 5 files changed, 45 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/core.txt b/Documentation/config/core.txt\nindex d80162681a..7a2a33bc8c 100644\n--- a/Documentation/config/core.txt\n+++ b/Documentation/config/core.txt\n@@ -86,7 +86,9 @@ core.untrackedCache::\n \tit will automatically be removed, if set to `false`. Before\n \tsetting it to `true`, you should check that mtime is working\n \tproperly on your system.\n-\tSee linkgit:git-update-index[1]. `keep` by default.\n+\tSee linkgit:git-update-index[1]. `keep` by default, unless\n+\t`feature.manyFiles` is enabled which sets this setting to\n+\t`true` by default.\n \n core.checkStat::\n \tWhen missing or is set to `default`, many fields in the stat\ndiff --git a/Documentation/config/feature.txt b/Documentation/config/feature.txt\nindex f74314ae90..c2d9ef7473 100644\n--- a/Documentation/config/feature.txt\n+++ b/Documentation/config/feature.txt\n@@ -12,4 +12,14 @@ feature.manyCommits::\n * `core.commitGraph=true` enables reading the commit-graph file.\n +\n * `gc.writeCommitGraph=true` enables writing the commit-graph file during\n-garbage collection.\n\\ No newline at end of file\n+garbage collection.\n+\n+feature.manyFiles::\n+\tEnable config options that optimize for repos with many files in the\n+\tworking directory. With many files, commands such as `git status` and\n+\t`git checkout` may be slow and these new defaults improve performance:\n++\n+* `index.version=4` enables path-prefix compression in the index.\n++\n+* `core.untrackedCache=true` enables the untracked cache. This setting assumes\n+that mtime is working on your machine.\n\\ No newline at end of file\ndiff --git a/Documentation/config/index.txt b/Documentation/config/index.txt\nindex f181503041..7cb50b37e9 100644\n--- a/Documentation/config/index.txt\n+++ b/Documentation/config/index.txt\n@@ -24,3 +24,4 @@ index.threads::\n index.version::\n \tSpecify the version with which new index files should be\n \tinitialized.  This does not affect existing repositories.\n+\tIf `feature.manyFiles` is enabled, then the default is 4.\ndiff --git a/repo-settings.c b/repo-settings.c\nindex 65b980403c..3a5cf33814 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -43,6 +43,10 @@ void prepare_repo_settings(struct repository *r)\n \t\tUPDATE_DEFAULT(r->settings.core_commit_graph, 1);\n \t\tUPDATE_DEFAULT(r->settings.gc_write_commit_graph, 1);\n \t}\n+\tif (!repo_config_get_bool(r, \"feature.manyfiles\", &value) && value) {\n+\t\tUPDATE_DEFAULT(r->settings.index_version, 4);\n+\t\tUPDATE_DEFAULT(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE);\n+\t}\n \n \t/* Hack for test programs like test-dump-untracked-cache */\n \tif (ignore_untracked_cache_config)\ndiff --git a/t/t1600-index.sh b/t/t1600-index.sh\nindex 42962ed7d4..c77721b580 100755\n--- a/t/t1600-index.sh\n+++ b/t/t1600-index.sh\n@@ -59,17 +59,38 @@ test_expect_success 'out of bounds index.version issues warning' '\n \t)\n '\n \n-test_expect_success 'GIT_INDEX_VERSION takes precedence over config' '\n+test_index_version () {\n+\tINDEX_VERSION_CONFIG=$1 &&\n+\tFEATURE_MANY_FILES=$2 &&\n+\tENV_VAR_VERSION=$3\n+\tEXPECTED_OUTPUT_VERSION=$4 &&\n \t(\n \t\trm -f .git/index &&\n-\t\tGIT_INDEX_VERSION=4 &&\n-\t\texport GIT_INDEX_VERSION &&\n-\t\tgit config --add index.version 2 &&\n+\t\trm -f .git/config &&\n+\t\tif test \"$INDEX_VERSION_CONFIG\" -ne 0\n+\t\tthen\n+\t\t\tgit config --add index.version $INDEX_VERSION_CONFIG\n+\t\tfi &&\n+\t\tgit config --add feature.manyFiles $FEATURE_MANY_FILES\n+\t\tif test \"$ENV_VAR_VERSION\" -ne 0\n+\t\tthen\n+\t\t\tGIT_INDEX_VERSION=$ENV_VAR_VERSION &&\n+\t\t\texport GIT_INDEX_VERSION\n+\t\telse\n+\t\t\tunset GIT_INDEX_VERSION\n+\t\tfi &&\n \t\tgit add a 2>&1 &&\n-\t\techo 4 >expect &&\n+\t\techo $EXPECTED_OUTPUT_VERSION >expect &&\n \t\ttest-tool index-version <.git/index >actual &&\n \t\ttest_cmp expect actual\n \t)\n+}\n+\n+test_expect_success 'index version config precedence' '\n+\ttest_index_version 2 false 4 4 &&\n+\ttest_index_version 2 true 0 2 &&\n+\ttest_index_version 0 true 0 4 &&\n+\ttest_index_version 0 true 2 2\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"379238","messageId":"b8a631683a116f48ddf7ac3ff98db7f81aa2bcc0.1564021393.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 5/5] repo-settings: create feature.experimental setting","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-25T02:23:18Z","receivedAt":"2019-07-25T02:23:25Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe 'feature.experimental' setting includes config options that are\nnot committed to become defaults, but could use additional testing.\n\nUpdate the following config settings to take new defaults, and to\nuse the repo_settings struct if not already using them:\n\n* 'pack.useSparse=true'\n\n* 'merge.directoryRenames=true'\n\n* 'fetch.negotiationAlgorithm=skipping'\n\nIn the case of fetch.negotiationAlgorithm, the existing logic\nwould load the config option only when about to use the setting,\nso had a die() statement on an unknown string value. This is\nremoved as now the config is parsed under prepare_repo_settings().\nIn general, this die() is probably misplaced and not valuable.\nA test was removed that checked this die() statement executed.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/feature.txt     | 17 +++++++++++++++++\n Documentation/config/fetch.txt       |  3 ++-\n Documentation/config/merge.txt       |  3 ++-\n Documentation/config/pack.txt        |  3 ++-\n builtin/am.c                         |  1 +\n fetch-negotiator.c                   | 26 ++++++++++++++------------\n fetch-negotiator.h                   |  5 +++--\n fetch-pack.c                         | 11 +++++------\n merge-recursive.c                    | 15 ++++++---------\n repo-settings.c                      | 20 ++++++++++++++++++++\n repo-settings.h                      | 16 ++++++++++++++++\n t/t5552-skipping-fetch-negotiator.sh | 23 -----------------------\n 12 files changed, 88 insertions(+), 55 deletions(-)\n\ndiff --git a/Documentation/config/feature.txt b/Documentation/config/feature.txt\nindex c2d9ef7473..287621b1e8 100644\n--- a/Documentation/config/feature.txt\n+++ b/Documentation/config/feature.txt\n@@ -4,6 +4,23 @@ feature.*::\n \tdeveloper community as recommended defaults and are subject to change.\n \tIn particular, new config options may be added with different defaults.\n \n+feature.experimental::\n+\tEnable config options that are new to Git, and are being considered for\n+\tfuture defaults. Config settings included here may be added or removed\n+\twith each release, including minor version updates. These settings may\n+\thave unintended interactions since they are so new. Please enable this\n+\tsetting if you are interested in providing feedback on experimental\n+\tfeatures. The new default values are:\n++\n+* `pack.useSparse=true` uses a new algorithm when constructing a pack-file\n+which can improve `git push` performance in repos with many files.\n++\n+* `merge.directoryRenames=true` uses a new algorithm for detecting renames by\n+using entire directories at a time instead of single files at a time.\n++\n+* `fetch.negotiationAlgorithm=skipping` may improve fetch negotiation times by\n+skipping more commits at a time, reducing the number of round trips.\n+\n feature.manyCommits::\n \tEnable config options that optimize for repos with many commits. This\n \tsetting is recommended for repos with at least 100,000 commits. The\ndiff --git a/Documentation/config/fetch.txt b/Documentation/config/fetch.txt\nindex ba890b5884..d402110638 100644\n--- a/Documentation/config/fetch.txt\n+++ b/Documentation/config/fetch.txt\n@@ -59,7 +59,8 @@ fetch.negotiationAlgorithm::\n \teffort to converge faster, but may result in a larger-than-necessary\n \tpackfile; The default is \"default\" which instructs Git to use the default algorithm\n \tthat never skips commits (unless the server has acknowledged it or one\n-\tof its descendants).\n+\tof its descendants). If `feature.experimental` is enabled, then this\n+\tsetting defaults to \"skipping\".\n \tUnknown values will cause 'git fetch' to error out.\n +\n See also the `--negotiation-tip` option for linkgit:git-fetch[1].\ndiff --git a/Documentation/config/merge.txt b/Documentation/config/merge.txt\nindex 6a313937f8..e8def2d63c 100644\n--- a/Documentation/config/merge.txt\n+++ b/Documentation/config/merge.txt\n@@ -54,7 +54,8 @@ merge.directoryRenames::\n \tmoved into the new directory.  If set to \"conflict\", a conflict\n \twill be reported for such paths.  If merge.renames is false,\n \tmerge.directoryRenames is ignored and treated as false.  Defaults\n-\tto \"conflict\".\n+\tto \"conflict\" unless `feature.experimental` is enabled and the\n+\tdefault is \"true\".\n \n merge.renormalize::\n \tTell Git that canonical representation of files in the\ndiff --git a/Documentation/config/pack.txt b/Documentation/config/pack.txt\nindex 9cdcfa7324..1d66f0c992 100644\n--- a/Documentation/config/pack.txt\n+++ b/Documentation/config/pack.txt\n@@ -112,7 +112,8 @@ pack.useSparse::\n \tobjects. This can have significant performance benefits when\n \tcomputing a pack to send a small change. However, it is possible\n \tthat extra objects are added to the pack-file if the included\n-\tcommits contain certain types of direct renames.\n+\tcommits contain certain types of direct renames. Default is `false`\n+\tunless `feature.experimental` is enabled.\n \n pack.writeBitmaps (deprecated)::\n \tThis is a deprecated synonym for `repack.writeBitmaps`.\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 1aea657a7f..912c6155b8 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -34,6 +34,7 @@\n #include \"string-list.h\"\n #include \"packfile.h\"\n #include \"repository.h\"\n+#include \"repo-settings.h\"\n \n /**\n  * Returns the length of the first line of msg.\ndiff --git a/fetch-negotiator.c b/fetch-negotiator.c\nindex d6d685cba0..1a68be1a2e 100644\n--- a/fetch-negotiator.c\n+++ b/fetch-negotiator.c\n@@ -2,19 +2,21 @@\n #include \"fetch-negotiator.h\"\n #include \"negotiator/default.h\"\n #include \"negotiator/skipping.h\"\n+#include \"repository.h\"\n+#include \"repo-settings.h\"\n \n-void fetch_negotiator_init(struct fetch_negotiator *negotiator,\n-\t\t\t   const char *algorithm)\n+void fetch_negotiator_init(struct repository *r,\n+\t\t\t   struct fetch_negotiator *negotiator)\n {\n-\tif (algorithm) {\n-\t\tif (!strcmp(algorithm, \"skipping\")) {\n-\t\t\tskipping_negotiator_init(negotiator);\n-\t\t\treturn;\n-\t\t} else if (!strcmp(algorithm, \"default\")) {\n-\t\t\t/* Fall through to default initialization */\n-\t\t} else {\n-\t\t\tdie(\"unknown fetch negotiation algorithm '%s'\", algorithm);\n-\t\t}\n+\tprepare_repo_settings(r);\n+\tswitch(r->settings.fetch_negotiation_algorithm) {\n+\tcase FETCH_NEGOTIATION_SKIPPING:\n+\t\tskipping_negotiator_init(negotiator);\n+\t\treturn;\n+\n+\tcase FETCH_NEGOTIATION_DEFAULT:\n+\tdefault:\n+\t\tdefault_negotiator_init(negotiator);\n+\t\treturn;\n \t}\n-\tdefault_negotiator_init(negotiator);\n }\ndiff --git a/fetch-negotiator.h b/fetch-negotiator.h\nindex 9e3967ce66..ea78868504 100644\n--- a/fetch-negotiator.h\n+++ b/fetch-negotiator.h\n@@ -2,6 +2,7 @@\n #define FETCH_NEGOTIATOR_H\n \n struct commit;\n+struct repository;\n \n /*\n  * An object that supplies the information needed to negotiate the contents of\n@@ -52,7 +53,7 @@ struct fetch_negotiator {\n \tvoid *data;\n };\n \n-void fetch_negotiator_init(struct fetch_negotiator *negotiator,\n-\t\t\t   const char *algorithm);\n+void fetch_negotiator_init(struct repository *r,\n+\t\t\t   struct fetch_negotiator *negotiator);\n \n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 65be043f2a..d81f47c07b 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -36,7 +36,6 @@ static int agent_supported;\n static int server_supports_filtering;\n static struct lock_file shallow_lock;\n static const char *alternate_shallow_file;\n-static char *negotiation_algorithm;\n static struct strbuf fsck_msg_types = STRBUF_INIT;\n \n /* Remember to update object flag allocation in object.h */\n@@ -892,12 +891,13 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,\n \t\t\t\t struct shallow_info *si,\n \t\t\t\t char **pack_lockfile)\n {\n+\tstruct repository *r = the_repository;\n \tstruct ref *ref = copy_ref_list(orig_ref);\n \tstruct object_id oid;\n \tconst char *agent_feature;\n \tint agent_len;\n \tstruct fetch_negotiator negotiator;\n-\tfetch_negotiator_init(&negotiator, negotiation_algorithm);\n+\tfetch_negotiator_init(r, &negotiator);\n \n \tsort_ref_list(&ref, ref_compare_name);\n \tQSORT(sought, nr_sought, cmp_ref_by_name);\n@@ -911,7 +911,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,\n \n \tif (server_supports(\"shallow\"))\n \t\tprint_verbose(args, _(\"Server supports %s\"), \"shallow\");\n-\telse if (args->depth > 0 || is_repository_shallow(the_repository))\n+\telse if (args->depth > 0 || is_repository_shallow(r))\n \t\tdie(_(\"Server does not support shallow clients\"));\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n@@ -1379,6 +1379,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\t\t\t    struct shallow_info *si,\n \t\t\t\t    char **pack_lockfile)\n {\n+\tstruct repository *r = the_repository;\n \tstruct ref *ref = copy_ref_list(orig_ref);\n \tenum fetch_state state = FETCH_CHECK_LOCAL;\n \tstruct oidset common = OIDSET_INIT;\n@@ -1386,7 +1387,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tint in_vain = 0;\n \tint haves_to_send = INITIAL_FLUSH;\n \tstruct fetch_negotiator negotiator;\n-\tfetch_negotiator_init(&negotiator, negotiation_algorithm);\n+\tfetch_negotiator_init(r, &negotiator);\n \tpacket_reader_init(&reader, fd[0], NULL, 0,\n \t\t\t   PACKET_READ_CHOMP_NEWLINE |\n \t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n@@ -1505,8 +1506,6 @@ static void fetch_pack_config(void)\n \tgit_config_get_bool(\"repack.usedeltabaseoffset\", &prefer_ofs_delta);\n \tgit_config_get_bool(\"fetch.fsckobjects\", &fetch_fsck_objects);\n \tgit_config_get_bool(\"transfer.fsckobjects\", &transfer_fsck_objects);\n-\tgit_config_get_string(\"fetch.negotiationalgorithm\",\n-\t\t\t      &negotiation_algorithm);\n \n \tgit_config(fetch_pack_config_cb, NULL);\n }\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 12300131fc..f39c31c9c4 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -28,6 +28,7 @@\n #include \"submodule.h\"\n #include \"revision.h\"\n #include \"commit-reach.h\"\n+#include \"repo-settings.h\"\n \n struct path_hashmap_entry {\n \tstruct hashmap_entry e;\n@@ -3662,15 +3663,6 @@ static void merge_recursive_config(struct merge_options *opt)\n \t\topt->merge_detect_rename = git_config_rename(\"merge.renames\", value);\n \t\tfree(value);\n \t}\n-\tif (!git_config_get_string(\"merge.directoryrenames\", &value)) {\n-\t\tint boolval = git_parse_maybe_bool(value);\n-\t\tif (0 <= boolval) {\n-\t\t\topt->detect_directory_renames = boolval ? 2 : 0;\n-\t\t} else if (!strcasecmp(value, \"conflict\")) {\n-\t\t\topt->detect_directory_renames = 1;\n-\t\t} /* avoid erroring on values from future versions of git */\n-\t\tfree(value);\n-\t}\n \tgit_config(git_xmerge_config, NULL);\n }\n \n@@ -3688,6 +3680,11 @@ void init_merge_options(struct merge_options *opt,\n \topt->diff_detect_rename = -1;\n \topt->merge_detect_rename = -1;\n \topt->detect_directory_renames = 1;\n+\n+\tprepare_repo_settings(repo);\n+\tif (repo->settings.merge_directory_renames >= 0)\n+\t\topt->detect_directory_renames = repo->settings.merge_directory_renames;\n+\n \tmerge_recursive_config(opt);\n \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n \tif (merge_verbosity)\ndiff --git a/repo-settings.c b/repo-settings.c\nindex 3a5cf33814..bfb3288fda 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -35,6 +35,18 @@ void prepare_repo_settings(struct repository *r)\n \t\tfree(strval);\n \t}\n \n+\tif (!repo_config_get_maybe_bool(r, \"merge.directoryrenames\", &value))\n+\t\tr->settings.merge_directory_renames = value ? MERGE_DIRECTORY_RENAMES_TRUE : 0;\n+\telse if (!repo_config_get_string(r, \"merge.directoryrenames\", &strval)) {\n+\t\tif (!strcasecmp(strval, \"conflict\"))\n+\t\t\tr->settings.merge_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n+\t}\n+\tif (!repo_config_get_string(r, \"fetch.negotiationalgorithm\", &strval)) {\n+\t\tif (!strcasecmp(strval, \"skipping\"))\n+\t\t\tr->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;\n+\t\telse\n+\t\t\tr->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT;\n+\t}\n \n \tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n \t\tr->settings.pack_use_sparse = value;\n@@ -47,6 +59,11 @@ void prepare_repo_settings(struct repository *r)\n \t\tUPDATE_DEFAULT(r->settings.index_version, 4);\n \t\tUPDATE_DEFAULT(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE);\n \t}\n+\tif (!repo_config_get_bool(r, \"feature.experimental\", &value) && value) {\n+\t\tUPDATE_DEFAULT(r->settings.pack_use_sparse, 1);\n+\t\tUPDATE_DEFAULT(r->settings.merge_directory_renames, MERGE_DIRECTORY_RENAMES_TRUE);\n+\t\tUPDATE_DEFAULT(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_SKIPPING);\n+\t}\n \n \t/* Hack for test programs like test-dump-untracked-cache */\n \tif (ignore_untracked_cache_config)\n@@ -54,5 +71,8 @@ void prepare_repo_settings(struct repository *r)\n \telse\n \t\tUPDATE_DEFAULT(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);\n \n+\tUPDATE_DEFAULT(r->settings.merge_directory_renames, MERGE_DIRECTORY_RENAMES_CONFLICT);\n+\tUPDATE_DEFAULT(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_DEFAULT);\n+\n \tr->settings_initialized = 1;\n }\ndiff --git a/repo-settings.h b/repo-settings.h\nindex e9b028e1a9..95d3392df2 100644\n--- a/repo-settings.h\n+++ b/repo-settings.h\n@@ -8,6 +8,20 @@ enum untracked_cache_setting {\n \tUNTRACKED_CACHE_WRITE = 2\n };\n \n+enum merge_directory_renames_setting {\n+\tMERGE_DIRECTORY_RENAMES_UNSET = -1,\n+\tMERGE_DIRECTORY_RENAMES_NONE = 0,\n+\tMERGE_DIRECTORY_RENAMES_CONFLICT = 1,\n+\tMERGE_DIRECTORY_RENAMES_TRUE = 2,\n+};\n+\n+enum fetch_negotiation_setting {\n+\tFETCH_NEGOTIATION_UNSET = -1,\n+\tFETCH_NEGOTIATION_NONE = 0,\n+\tFETCH_NEGOTIATION_DEFAULT = 1,\n+\tFETCH_NEGOTIATION_SKIPPING = 2,\n+};\n+\n struct repo_settings {\n \tint core_commit_graph;\n \tint gc_write_commit_graph;\n@@ -16,6 +30,8 @@ struct repo_settings {\n \tenum untracked_cache_setting core_untracked_cache;\n \n \tint pack_use_sparse;\n+\tenum merge_directory_renames_setting merge_directory_renames;\n+\tenum fetch_negotiation_setting fetch_negotiation_algorithm;\n };\n \n struct repository;\ndiff --git a/t/t5552-skipping-fetch-negotiator.sh b/t/t5552-skipping-fetch-negotiator.sh\nindex 8a14be51a1..f70cbcc9ca 100755\n--- a/t/t5552-skipping-fetch-negotiator.sh\n+++ b/t/t5552-skipping-fetch-negotiator.sh\n@@ -60,29 +60,6 @@ test_expect_success 'commits with no parents are sent regardless of skip distanc\n \thave_not_sent c6 c4 c3\n '\n \n-test_expect_success 'unknown fetch.negotiationAlgorithm values error out' '\n-\trm -rf server client trace &&\n-\tgit init server &&\n-\ttest_commit -C server to_fetch &&\n-\n-\tgit init client &&\n-\ttest_commit -C client on_client &&\n-\tgit -C client checkout on_client &&\n-\n-\ttest_config -C client fetch.negotiationAlgorithm invalid &&\n-\ttest_must_fail git -C client fetch \"$(pwd)/server\" 2>err &&\n-\ttest_i18ngrep \"unknown fetch negotiation algorithm\" err &&\n-\n-\t# Explicit \"default\" value\n-\ttest_config -C client fetch.negotiationAlgorithm default &&\n-\tgit -C client -c fetch.negotiationAlgorithm=default fetch \"$(pwd)/server\" &&\n-\n-\t# Implementation detail: If there is nothing to fetch, we will not error out\n-\ttest_config -C client fetch.negotiationAlgorithm invalid &&\n-\tgit -C client fetch \"$(pwd)/server\" 2>err &&\n-\ttest_i18ngrep ! \"unknown fetch negotiation algorithm\" err\n-'\n-\n test_expect_success 'when two skips collide, favor the larger one' '\n \trm -rf server client trace &&\n \tgit init server &&\n-- \ngitgitgadget\n"},{"id":"379239","messageId":"pull.292.v2.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.git.gitgitgadget@gmail.com","subject":"[PATCH v2 0/5] Create 'feature.*' config area and some centralized config parsing","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-25T02:23:14Z","receivedAt":"2019-07-25T02:23:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"This is a brand-new thread to replace ds/early-access. The discussion on\nthat thread was very helpful [1].\n\nWith this in mind, I propose instead a set of \"feature.*\" config settings\nthat form groups of \"community recommended\" settings (with some caveats). In\nthe space below, I'll list a set of possible feature names and the implied\nconfig options.\n\nFirst, the main two categories we've discussed so far: many commits and many\nfiles. These two feature sets are for when your repo is large in one of\nthese dimensions. Perhaps there are other settings to include in these?\n\nfeature.manyFiles:\n    index.version = 4\n    core.untrackedCache = true\n\nfeature.manyCommits:\n    core.commitGraph = true\n    gc.writeCommitGraph = true\n    (future: fetch.writeSplitCommitGraph = true)\n\nNote: the fetch.writeSplitCommitGraph does not exist yet, but could be\nintroduced in a later release to write a new commit-graph (with --split) on\nfetch.\n\nThe other category that has been discussed already is that of \"experimental\nfeatures that we generally think are helpful but change behavior slightly in\nsome cases\".\n\nfeature.experimental:\n    pack.useSparse = true\n    merge.directoryRenames = true\n    fetch.negotiationAlgorithm = skipping\n\nSpecifically, this setting is for config values we are not sure will ever be\non by default, but additional testing is needed to be sure. This is\ndifferent than a possible 'feature.preview' setting that would include\nconfig settings that we are committed to updating the defaults in a future\nrelease. There are many ways we can take this idea in the future (including\nmore additions to these categories).\n\nThanks, -Stolee\n\nUpdates in V2: I'm responding to Dscho's comments early, as they were very\nvaluable.\n\n * Rearranged how we are checking for the feature.* settings, so if one\n   layer turns the setting on and a later layer turns it off, we do not\n   adjust the defaults.\n   \n   \n * Switched to using enums for the non-boolean config options.\n   \n   \n * Placed the repo_settings struct directly in the repository struct.\n   \n   \n * All struct members are initialized to -1 using memset.\n   \n   \n * The config changes around directory rename detection is greatly\n   simplified.\n   \n   \n\n[1] https://public-inbox.org/git/pull.254.git.gitgitgadget@gmail.com/\n\nDerrick Stolee (5):\n  repo-settings: consolidate some config settings\n  repo-settings: add feature.manyCommits setting\n  repo-settings: parse core.untrackedCache\n  repo-settings: create feature.manyFiles setting\n  repo-settings: create feature.experimental setting\n\n Documentation/config.txt             |  2 +\n Documentation/config/core.txt        |  7 ++-\n Documentation/config/feature.txt     | 42 +++++++++++++++\n Documentation/config/fetch.txt       |  3 +-\n Documentation/config/gc.txt          |  4 +-\n Documentation/config/index.txt       |  1 +\n Documentation/config/merge.txt       |  3 +-\n Documentation/config/pack.txt        |  3 +-\n Makefile                             |  1 +\n builtin/am.c                         |  1 +\n builtin/gc.c                         | 13 +++--\n builtin/pack-objects.c               |  9 ++--\n builtin/update-index.c               |  7 ++-\n commit-graph.c                       |  7 +--\n config.c                             | 24 ---------\n fetch-negotiator.c                   | 26 +++++-----\n fetch-negotiator.h                   |  5 +-\n fetch-pack.c                         | 11 ++--\n merge-recursive.c                    | 15 +++---\n read-cache.c                         | 31 +++++------\n repo-settings.c                      | 78 ++++++++++++++++++++++++++++\n repo-settings.h                      | 41 +++++++++++++++\n repository.h                         |  5 ++\n t/t1600-index.sh                     | 31 +++++++++--\n t/t5552-skipping-fetch-negotiator.sh | 23 --------\n 25 files changed, 274 insertions(+), 119 deletions(-)\n create mode 100644 Documentation/config/feature.txt\n create mode 100644 repo-settings.c\n create mode 100644 repo-settings.h\n\n\nbase-commit: 9c9b961d7eb15fb583a2a812088713a68a85f1c0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-292%2Fderrickstolee%2Frepo-settings%2Fhead-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-292/derrickstolee/repo-settings/head-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/292\n\nRange-diff vs v1:\n\n 1:  72f652b89c ! 1:  597ab7d621 repo-settings: consolidate some config settings\n     @@ -64,7 +64,7 @@\n      -\t\t\t\t\t NULL))\n      -\t\treturn 1;\n      +\tprepare_repo_settings(the_repository);\n     -+\tif (the_repository->settings->gc_write_commit_graph == 1)\n     ++\tif (the_repository->settings.gc_write_commit_graph == 1)\n      +\t\twrite_commit_graph_reachable(get_object_directory(),\n      +\t\t\t\t\t     !quiet && !daemonized ? COMMIT_GRAPH_PROGRESS : 0,\n      +\t\t\t\t\t     NULL);\n     @@ -99,8 +99,8 @@\n       \n       \tsparse = git_env_bool(\"GIT_TEST_PACK_SPARSE\", 0);\n      +\tprepare_repo_settings(the_repository);\n     -+\tif (!sparse && the_repository->settings->pack_use_sparse != -1)\n     -+\t\tsparse = the_repository->settings->pack_use_sparse;\n     ++\tif (!sparse && the_repository->settings.pack_use_sparse != -1)\n     ++\t\tsparse = the_repository->settings.pack_use_sparse;\n      +\n       \treset_pack_idx_option(&pack_idx_opts);\n       \tgit_config(git_pack_config, NULL);\n     @@ -134,7 +134,7 @@\n       \tif (!git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) &&\n      -\t    (repo_config_get_bool(r, \"core.commitgraph\", &config_value) ||\n      -\t    !config_value))\n     -+\t    r->settings->core_commit_graph != 1)\n     ++\t    r->settings.core_commit_graph != 1)\n       \t\t/*\n       \t\t * This repository is not configured to use commit graphs, so\n       \t\t * do not load one. (But report commit_graph_attempted anyway\n     @@ -167,8 +167,8 @@\n      -\t\t\tversion = value;\n      +\t\tprepare_repo_settings(r);\n      +\n     -+\t\tif (r->settings->index_version >= 0)\n     -+\t\t\tversion = r->settings->index_version;\n     ++\t\tif (r->settings.index_version >= 0)\n     ++\t\t\tversion = r->settings.index_version;\n       \t\tif (version < INDEX_FORMAT_LB || INDEX_FORMAT_UB < version) {\n       \t\t\twarning(_(\"index.version set, but the value is invalid.\\n\"\n       \t\t\t\t  \"Using version %i\"), INDEX_FORMAT_DEFAULT);\n     @@ -192,44 +192,28 @@\n      +#include \"config.h\"\n      +#include \"repo-settings.h\"\n      +\n     -+static int git_repo_config(const char *key, const char *value, void *cb)\n     ++void prepare_repo_settings(struct repository *r)\n      +{\n     -+\tstruct repo_settings *rs = (struct repo_settings *)cb;\n     ++\tint value;\n      +\n     -+\tif (!strcmp(key, \"core.commitgraph\")) {\n     -+\t\trs->core_commit_graph = git_config_bool(key, value);\n     -+\t\treturn 0;\n     -+\t}\n     -+\tif (!strcmp(key, \"gc.writecommitgraph\")) {\n     -+\t\trs->gc_write_commit_graph = git_config_bool(key, value);\n     -+\t\treturn 0;\n     -+\t}\n     -+\tif (!strcmp(key, \"pack.usesparse\")) {\n     -+\t\trs->pack_use_sparse = git_config_bool(key, value);\n     -+\t\treturn 0;\n     -+\t}\n     -+\tif (!strcmp(key, \"index.version\")) {\n     -+\t\trs->index_version = git_config_int(key, value);\n     -+\t\treturn 0;\n     -+\t}\n     ++\tif (r->settings_initialized)\n     ++\t\treturn;\n      +\n     -+\treturn 1;\n     -+}\n     ++\t/* Defaults */\n     ++\tmemset(&r->settings, -1, sizeof(r->settings));\n      +\n     -+void prepare_repo_settings(struct repository *r)\n     -+{\n     -+\tif (r->settings)\n     -+\t\treturn;\n     ++\tif (!repo_config_get_bool(r, \"core.commitgraph\", &value))\n     ++\t\tr->settings.core_commit_graph = value;\n     ++\tif (!repo_config_get_bool(r, \"gc.writecommitgraph\", &value))\n     ++\t\tr->settings.gc_write_commit_graph = value;\n      +\n     -+\tr->settings = xmalloc(sizeof(*r->settings));\n     ++\tif (!repo_config_get_bool(r, \"index.version\", &value))\n     ++\t\tr->settings.index_version = value;\n      +\n     -+\t/* Defaults */\n     -+\tr->settings->core_commit_graph = -1;\n     -+\tr->settings->gc_write_commit_graph = -1;\n     -+\tr->settings->pack_use_sparse = -1;\n     -+\tr->settings->index_version = -1;\n     ++\tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n     ++\t\tr->settings.pack_use_sparse = value;\n      +\n     -+\trepo_config(r, git_repo_config, r->settings);\n     ++\tr->settings_initialized = 1;\n      +}\n      \n       diff --git a/repo-settings.h b/repo-settings.h\n     @@ -243,8 +227,10 @@\n      +struct repo_settings {\n      +\tint core_commit_graph;\n      +\tint gc_write_commit_graph;\n     -+\tint pack_use_sparse;\n     ++\n      +\tint index_version;\n     ++\n     ++\tint pack_use_sparse;\n      +};\n      +\n      +struct repository;\n     @@ -257,7 +243,10 @@\n       --- a/repository.h\n       +++ b/repository.h\n      @@\n     + #define REPOSITORY_H\n     + \n       #include \"path.h\"\n     ++#include \"repo-settings.h\"\n       \n       struct config_set;\n      +struct repo_settings;\n     @@ -268,7 +257,8 @@\n       \t */\n       \tchar *submodule_prefix;\n       \n     -+\tstruct repo_settings *settings;\n     ++\tint settings_initialized;\n     ++\tstruct repo_settings settings;\n      +\n       \t/* Subsystems */\n       \t/*\n 2:  e00a1be75b ! 2:  86380c7832 repo-settings: add feature.manyCommits setting\n     @@ -4,7 +4,7 @@\n      \n          When a repo has many commits, it is helpful to write and read the\n          commit-graph file. Future changes to Git may include new config\n     -    settings that are benefitial in this scenario.\n     +    settings that are beneficial in this scenario.\n      \n          Create the 'feature.manyCommits' config setting that changes the\n          default values of 'core.commitGraph' and 'gc.writeCommitGraph' to\n     @@ -85,15 +85,17 @@\n       \n      +#define UPDATE_DEFAULT(s,v) do { if (s == -1) { s = v; } } while(0)\n      +\n     - static int git_repo_config(const char *key, const char *value, void *cb)\n     + void prepare_repo_settings(struct repository *r)\n       {\n     - \tstruct repo_settings *rs = (struct repo_settings *)cb;\n     + \tint value;\n     +@@\n     + \tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n     + \t\tr->settings.pack_use_sparse = value;\n       \n     -+\tif (!strcmp(key, \"feature.manycommits\")) {\n     -+\t\tUPDATE_DEFAULT(rs->core_commit_graph, 1);\n     -+\t\tUPDATE_DEFAULT(rs->gc_write_commit_graph, 1);\n     -+\t\treturn 0;\n     ++\tif (!repo_config_get_bool(r, \"feature.manycommits\", &value) && value) {\n     ++\t\tUPDATE_DEFAULT(r->settings.core_commit_graph, 1);\n     ++\t\tUPDATE_DEFAULT(r->settings.gc_write_commit_graph, 1);\n      +\t}\n     - \tif (!strcmp(key, \"core.commitgraph\")) {\n     - \t\trs->core_commit_graph = git_config_bool(key, value);\n     - \t\treturn 0;\n     ++\n     + \tr->settings_initialized = 1;\n     + }\n 3:  47ae3e7d4d ! 3:  49be7a7345 repo-settings: parse core.untrackedCache\n     @@ -16,10 +16,9 @@\n          untracked cache and persist it.\n      \n          Instead of relying on magic values of -1, 0, and 1, split these\n     -    options into bitflags CORE_UNTRACKED_CACHE_KEEP and\n     -    CORE_UNTRACKED_CACHE_WRITE. This allows the use of \"-1\" as a\n     +    options into an enum. This allows the use of \"-1\" as a\n          default value. After parsing the config options, if the value is\n     -    unset we can initialize it to CORE_UNTRACKED_CACHE_KEEP.\n     +    unset we can initialize it to UNTRACKED_CACHE_KEEP.\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n      \n     @@ -52,7 +51,7 @@\n       \t\tbreak;\n       \tcase UC_DISABLE:\n      -\t\tif (git_config_get_untracked_cache() == 1)\n     -+\t\tif (r->settings->core_untracked_cache & CORE_UNTRACKED_CACHE_WRITE)\n     ++\t\tif (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE)\n       \t\t\twarning(_(\"core.untrackedCache is set to true; \"\n       \t\t\t\t  \"remove or change it, if you really want to \"\n       \t\t\t\t  \"disable the untracked cache\"));\n     @@ -61,7 +60,7 @@\n       \tcase UC_ENABLE:\n       \tcase UC_FORCE:\n      -\t\tif (git_config_get_untracked_cache() == 0)\n     -+\t\tif (r->settings->core_untracked_cache == 0)\n     ++\t\tif (r->settings.core_untracked_cache == UNTRACKED_CACHE_REMOVE)\n       \t\t\twarning(_(\"core.untrackedCache is set to false; \"\n       \t\t\t\t  \"remove or change it, if you really want to \"\n       \t\t\t\t  \"enable the untracked cache\"));\n     @@ -116,7 +115,7 @@\n      +\n      +\tprepare_repo_settings(r);\n      +\n     -+\tif (!(r->settings->core_untracked_cache & CORE_UNTRACKED_CACHE_KEEP)) {\n     ++\tif (r->settings.core_untracked_cache  == UNTRACKED_CACHE_REMOVE) {\n       \t\tremove_untracked_cache(istate);\n      -\t\tbreak;\n      -\tcase 1: /* true */\n     @@ -127,7 +126,7 @@\n      +\t\treturn;\n       \t}\n      +\n     -+\tif (r->settings->core_untracked_cache & CORE_UNTRACKED_CACHE_WRITE)\n     ++\tif (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE)\n      +\t\tadd_untracked_cache(istate);\n       }\n       \n     @@ -137,39 +136,46 @@\n       --- a/repo-settings.c\n       +++ b/repo-settings.c\n      @@\n     - \t\trs->index_version = git_config_int(key, value);\n     - \t\treturn 0;\n     - \t}\n     -+\tif (!strcmp(key, \"core.untrackedcache\")) {\n     -+\t\tint bool_value = git_parse_maybe_bool(value);\n     -+\t\tif (bool_value == 0)\n     -+\t\t\trs->core_untracked_cache = 0;\n     -+\t\telse if (bool_value == 1)\n     -+\t\t\trs->core_untracked_cache = CORE_UNTRACKED_CACHE_KEEP |\n     -+\t\t\t\t\t\t   CORE_UNTRACKED_CACHE_WRITE;\n     -+\t\telse if (!strcasecmp(value, \"keep\"))\n     -+\t\t\trs->core_untracked_cache = CORE_UNTRACKED_CACHE_KEEP;\n     -+\t\telse\n     -+\t\t\terror(_(\"unknown core.untrackedCache value '%s'; \"\n     -+\t\t\t\t\"using 'keep' default value\"), value);\n     -+\t\treturn 0;\n     -+\t}\n     + void prepare_repo_settings(struct repository *r)\n     + {\n     + \tint value;\n     ++\tchar *strval;\n       \n     - \treturn 1;\n     - }\n     + \tif (r->settings_initialized)\n     + \t\treturn;\n      @@\n     - \tr->settings->gc_write_commit_graph = -1;\n     - \tr->settings->pack_use_sparse = -1;\n     - \tr->settings->index_version = -1;\n     -+\tr->settings->core_untracked_cache = -1;\n       \n     - \trepo_config(r, git_repo_config, r->settings);\n     + \tif (!repo_config_get_bool(r, \"index.version\", &value))\n     + \t\tr->settings.index_version = value;\n     ++\tif (!repo_config_get_maybe_bool(r, \"core.untrackedcache\", &value)) {\n     ++\t\tif (value == 0)\n     ++\t\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_REMOVE;\n     ++\t\telse\n     ++\t\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_WRITE;\n     ++\t} else if (!repo_config_get_string(r, \"core.untrackedcache\", &strval)) {\n     ++\t\tif (!strcasecmp(strval, \"keep\"))\n     ++\t\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;\n     ++\n     ++\t\tfree(strval);\n     ++\t}\n      +\n     + \n     + \tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n     + \t\tr->settings.pack_use_sparse = value;\n     +-\n     ++\t\n     + \tif (!repo_config_get_bool(r, \"feature.manycommits\", &value) && value) {\n     + \t\tUPDATE_DEFAULT(r->settings.core_commit_graph, 1);\n     + \t\tUPDATE_DEFAULT(r->settings.gc_write_commit_graph, 1);\n     + \t}\n     + \n      +\t/* Hack for test programs like test-dump-untracked-cache */\n      +\tif (ignore_untracked_cache_config)\n     -+\t\tr->settings->core_untracked_cache = CORE_UNTRACKED_CACHE_KEEP;\n     ++\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;\n      +\telse\n     -+\t\tUPDATE_DEFAULT(r->settings->core_untracked_cache, CORE_UNTRACKED_CACHE_KEEP);\n     ++\t\tUPDATE_DEFAULT(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);\n     ++\n     + \tr->settings_initialized = 1;\n       }\n      \n       diff --git a/repo-settings.h b/repo-settings.h\n     @@ -179,15 +185,19 @@\n       #ifndef REPO_SETTINGS_H\n       #define REPO_SETTINGS_H\n       \n     -+#define CORE_UNTRACKED_CACHE_WRITE (1 << 0)\n     -+#define CORE_UNTRACKED_CACHE_KEEP (1 << 1)\n     ++enum untracked_cache_setting {\n     ++\tUNTRACKED_CACHE_UNSET = -1,\n     ++\tUNTRACKED_CACHE_REMOVE = 0,\n     ++\tUNTRACKED_CACHE_KEEP = 1,\n     ++\tUNTRACKED_CACHE_WRITE = 2\n     ++};\n      +\n       struct repo_settings {\n       \tint core_commit_graph;\n       \tint gc_write_commit_graph;\n     - \tint pack_use_sparse;\n     + \n       \tint index_version;\n     -+\tint core_untracked_cache;\n     - };\n     ++\tenum untracked_cache_setting core_untracked_cache;\n       \n     - struct repository;\n     + \tint pack_use_sparse;\n     + };\n 4:  63b522a858 ! 4:  86a5a0c589 repo-settings: create feature.manyFiles setting\n     @@ -63,18 +63,16 @@\n       --- a/repo-settings.c\n       +++ b/repo-settings.c\n      @@\n     - \t\tUPDATE_DEFAULT(rs->gc_write_commit_graph, 1);\n     - \t\treturn 0;\n     + \t\tUPDATE_DEFAULT(r->settings.core_commit_graph, 1);\n     + \t\tUPDATE_DEFAULT(r->settings.gc_write_commit_graph, 1);\n       \t}\n     -+\tif (!strcmp(key, \"feature.manyfiles\")) {\n     -+\t\tUPDATE_DEFAULT(rs->index_version, 4);\n     -+\t\tUPDATE_DEFAULT(rs->core_untracked_cache,\n     -+\t\t\t       CORE_UNTRACKED_CACHE_KEEP | CORE_UNTRACKED_CACHE_WRITE);\n     -+\t\treturn 0;\n     ++\tif (!repo_config_get_bool(r, \"feature.manyfiles\", &value) && value) {\n     ++\t\tUPDATE_DEFAULT(r->settings.index_version, 4);\n     ++\t\tUPDATE_DEFAULT(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE);\n      +\t}\n     - \tif (!strcmp(key, \"core.commitgraph\")) {\n     - \t\trs->core_commit_graph = git_config_bool(key, value);\n     - \t\treturn 0;\n     + \n     + \t/* Hack for test programs like test-dump-untracked-cache */\n     + \tif (ignore_untracked_cache_config)\n      \n       diff --git a/t/t1600-index.sh b/t/t1600-index.sh\n       --- a/t/t1600-index.sh\n 5:  da7685936f ! 5:  b8a631683a repo-settings: create feature.experimental setting\n     @@ -104,16 +104,6 @@\n       \n       /**\n        * Returns the length of the first line of msg.\n     -@@\n     - \to.branch1 = \"HEAD\";\n     - \ttheir_tree_name = xstrfmt(\"%.*s\", linelen(state->msg), state->msg);\n     - \to.branch2 = their_tree_name;\n     --\to.detect_directory_renames = 0;\n     -+\tprepare_repo_settings(the_repository);\n     -+\tthe_repository->settings->merge_directory_renames = 0;\n     - \n     - \tif (state->quiet)\n     - \t\to.verbosity = 0;\n      \n       diff --git a/fetch-negotiator.c b/fetch-negotiator.c\n       --- a/fetch-negotiator.c\n     @@ -140,7 +130,7 @@\n      -\t\t\tdie(\"unknown fetch negotiation algorithm '%s'\", algorithm);\n      -\t\t}\n      +\tprepare_repo_settings(r);\n     -+\tswitch(r->settings->fetch_negotiation_algorithm) {\n     ++\tswitch(r->settings.fetch_negotiation_algorithm) {\n      +\tcase FETCH_NEGOTIATION_SKIPPING:\n      +\t\tskipping_negotiator_init(negotiator);\n      +\t\treturn;\n     @@ -248,83 +238,6 @@\n       \n       struct path_hashmap_entry {\n       \tstruct hashmap_entry e;\n     -@@\n     - \t * there is no content merge to do; just move the file into the\n     - \t * desired final location.\n     - \t */\n     -+\tstruct repository *r = the_repository;\n     - \tconst struct rename *ren = ci->ren1;\n     - \tconst struct diff_filespec *dest = ren->pair->two;\n     - \tchar *file_path = dest->path;\n     --\tint mark_conflicted = (opt->detect_directory_renames == 1);\n     -+\tint mark_conflicted;\n     -+\n     -+\tprepare_repo_settings(r);\n     -+\tmark_conflicted = (r->settings->merge_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT);\n     - \tassert(ren->dir_rename_original_dest);\n     - \n     - \tif (!opt->call_depth && would_lose_untracked(opt, dest->path)) {\n     -@@\n     - \t\t\t\t      struct string_list *entries,\n     - \t\t\t\t      struct rename_info *ri)\n     - {\n     -+\tstruct repository *r = the_repository;\n     - \tstruct diff_queue_struct *head_pairs, *merge_pairs;\n     - \tstruct hashmap *dir_re_head, *dir_re_merge;\n     - \tint clean = 1;\n     -@@\n     - \thead_pairs = get_diffpairs(opt, common, head);\n     - \tmerge_pairs = get_diffpairs(opt, common, merge);\n     - \n     --\tif (opt->detect_directory_renames) {\n     -+\tprepare_repo_settings(r);\n     -+\tif (r->settings->merge_directory_renames) {\n     - \t\tdir_re_head = get_directory_renames(head_pairs);\n     - \t\tdir_re_merge = get_directory_renames(merge_pairs);\n     - \n     -@@\n     - \t\t\t\tconst struct diff_filespec *b,\n     - \t\t\t\tstruct rename_conflict_info *ci)\n     - {\n     -+\tstruct repository *r = the_repository;\n     - \tstruct rename *ren = ci->ren1;\n     - \tstruct merge_file_info mfi;\n     - \tint clean;\n     -@@\n     - \tclean = handle_content_merge(&mfi, opt, path, was_dirty(opt, path),\n     - \t\t\t\t     o, a, b, ci);\n     - \n     --\tif (clean && opt->detect_directory_renames == 1 &&\n     -+\tprepare_repo_settings(r);\n     -+\tif (clean &&\n     -+\t    r->settings->merge_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT &&\n     - \t    ren->dir_rename_original_dest) {\n     - \t\tif (update_stages(opt, path,\n     - \t\t\t\t  NULL,\n     -@@\n     - static int warn_about_dir_renamed_entries(struct merge_options *opt,\n     - \t\t\t\t\t  struct rename *ren)\n     - {\n     -+\tstruct repository *r = the_repository;\n     - \tconst char *msg;\n     - \tint clean = 1, is_add;\n     - \n     -@@\n     - \t\treturn clean;\n     - \n     - \t/* Sanity checks */\n     --\tassert(opt->detect_directory_renames > 0);\n     -+\tprepare_repo_settings(r);\n     -+\tassert(r->settings->merge_directory_renames > 0);\n     - \tassert(ren->dir_rename_original_type == 'A' ||\n     - \t       ren->dir_rename_original_type == 'R');\n     - \n     - \t/* Check whether to treat directory renames as a conflict */\n     --\tclean = (opt->detect_directory_renames == 2);\n     -+\tclean = (r->settings->merge_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE);\n     - \n     - \tis_add = (ren->dir_rename_original_type == 'A');\n     - \tif (ren->dir_rename_original_type == 'A' && clean) {\n      @@\n       \t\topt->merge_detect_rename = git_config_rename(\"merge.renames\", value);\n       \t\tfree(value);\n     @@ -342,115 +255,92 @@\n       }\n       \n      @@\n     - \topt->renormalize = 0;\n       \topt->diff_detect_rename = -1;\n       \topt->merge_detect_rename = -1;\n     --\topt->detect_directory_renames = 1;\n     + \topt->detect_directory_renames = 1;\n     ++\n     ++\tprepare_repo_settings(repo);\n     ++\tif (repo->settings.merge_directory_renames >= 0)\n     ++\t\topt->detect_directory_renames = repo->settings.merge_directory_renames;\n     ++\n       \tmerge_recursive_config(opt);\n       \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n       \tif (merge_verbosity)\n      \n     - diff --git a/merge-recursive.h b/merge-recursive.h\n     - --- a/merge-recursive.h\n     - +++ b/merge-recursive.h\n     -@@\n     - \tunsigned renormalize : 1;\n     - \tlong xdl_opts;\n     - \tint verbosity;\n     --\tint detect_directory_renames;\n     - \tint diff_detect_rename;\n     - \tint merge_detect_rename;\n     - \tint diff_rename_limit;\n     -\n       diff --git a/repo-settings.c b/repo-settings.c\n       --- a/repo-settings.c\n       +++ b/repo-settings.c\n      @@\n     - {\n     - \tstruct repo_settings *rs = (struct repo_settings *)cb;\n     - \n     -+\tif (!strcmp(key, \"feature.experimental\")) {\n     -+\t\tUPDATE_DEFAULT(rs->pack_use_sparse, 1);\n     -+\t\tUPDATE_DEFAULT(rs->merge_directory_renames, MERGE_DIRECTORY_RENAMES_TRUE);\n     -+\t\tUPDATE_DEFAULT(rs->fetch_negotiation_algorithm, FETCH_NEGOTIATION_SKIPPING);\n     -+\t\treturn 0;\n     -+\t}\n     - \tif (!strcmp(key, \"feature.manycommits\")) {\n     - \t\tUPDATE_DEFAULT(rs->core_commit_graph, 1);\n     - \t\tUPDATE_DEFAULT(rs->gc_write_commit_graph, 1);\n     -@@\n     - \t\t\t\t\"using 'keep' default value\"), value);\n     - \t\treturn 0;\n     + \t\tfree(strval);\n       \t}\n     -+\tif (!strcmp(key, \"merge.directoryrenames\")) {\n     -+\t\tint bool_value = git_parse_maybe_bool(value);\n     -+\t\tif (0 <= bool_value) {\n     -+\t\t\trs->merge_directory_renames = bool_value ? MERGE_DIRECTORY_RENAMES_TRUE : 0;\n     -+\t\t} else if (!strcasecmp(value, \"conflict\")) {\n     -+\t\t\trs->merge_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n     -+\t\t}\n     -+\t\treturn 0;\n     + \n     ++\tif (!repo_config_get_maybe_bool(r, \"merge.directoryrenames\", &value))\n     ++\t\tr->settings.merge_directory_renames = value ? MERGE_DIRECTORY_RENAMES_TRUE : 0;\n     ++\telse if (!repo_config_get_string(r, \"merge.directoryrenames\", &strval)) {\n     ++\t\tif (!strcasecmp(strval, \"conflict\"))\n     ++\t\t\tr->settings.merge_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n      +\t}\n     -+\tif (!strcmp(key, \"fetch.negotiationalgorithm\"))\t{\n     -+\t\tif (!strcasecmp(value, \"skipping\")) {\n     -+\t\t\trs->fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;\n     -+\t\t} else {\n     -+\t\t\trs->fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT;\n     -+\t\t}\n     -+\t\treturn 0;\n     ++\tif (!repo_config_get_string(r, \"fetch.negotiationalgorithm\", &strval)) {\n     ++\t\tif (!strcasecmp(strval, \"skipping\"))\n     ++\t\t\tr->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;\n     ++\t\telse\n     ++\t\t\tr->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT;\n      +\t}\n       \n     - \treturn 1;\n     - }\n     + \tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n     + \t\tr->settings.pack_use_sparse = value;\n      @@\n     - \t/* Defaults */\n     - \tr->settings->core_commit_graph = -1;\n     - \tr->settings->gc_write_commit_graph = -1;\n     --\tr->settings->pack_use_sparse = -1;\n     -+\n     - \tr->settings->index_version = -1;\n     - \tr->settings->core_untracked_cache = -1;\n     - \n     -+\tr->settings->pack_use_sparse = -1;\n     -+\tr->settings->merge_directory_renames = -1;\n     -+\tr->settings->fetch_negotiation_algorithm = -1;\n     -+\n     - \trepo_config(r, git_repo_config, r->settings);\n     + \t\tUPDATE_DEFAULT(r->settings.index_version, 4);\n     + \t\tUPDATE_DEFAULT(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE);\n     + \t}\n     ++\tif (!repo_config_get_bool(r, \"feature.experimental\", &value) && value) {\n     ++\t\tUPDATE_DEFAULT(r->settings.pack_use_sparse, 1);\n     ++\t\tUPDATE_DEFAULT(r->settings.merge_directory_renames, MERGE_DIRECTORY_RENAMES_TRUE);\n     ++\t\tUPDATE_DEFAULT(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_SKIPPING);\n     ++\t}\n       \n       \t/* Hack for test programs like test-dump-untracked-cache */\n     + \tif (ignore_untracked_cache_config)\n      @@\n     - \t\tr->settings->core_untracked_cache = CORE_UNTRACKED_CACHE_KEEP;\n       \telse\n     - \t\tUPDATE_DEFAULT(r->settings->core_untracked_cache, CORE_UNTRACKED_CACHE_KEEP);\n     + \t\tUPDATE_DEFAULT(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);\n     + \n     ++\tUPDATE_DEFAULT(r->settings.merge_directory_renames, MERGE_DIRECTORY_RENAMES_CONFLICT);\n     ++\tUPDATE_DEFAULT(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_DEFAULT);\n      +\n     -+\tUPDATE_DEFAULT(r->settings->merge_directory_renames, MERGE_DIRECTORY_RENAMES_CONFLICT);\n     -+\tUPDATE_DEFAULT(r->settings->fetch_negotiation_algorithm, FETCH_NEGOTIATION_DEFAULT);\n     + \tr->settings_initialized = 1;\n       }\n      \n       diff --git a/repo-settings.h b/repo-settings.h\n       --- a/repo-settings.h\n       +++ b/repo-settings.h\n      @@\n     - #define CORE_UNTRACKED_CACHE_WRITE (1 << 0)\n     - #define CORE_UNTRACKED_CACHE_KEEP (1 << 1)\n     + \tUNTRACKED_CACHE_WRITE = 2\n     + };\n       \n     -+#define MERGE_DIRECTORY_RENAMES_CONFLICT 1\n     -+#define MERGE_DIRECTORY_RENAMES_TRUE 2\n     ++enum merge_directory_renames_setting {\n     ++\tMERGE_DIRECTORY_RENAMES_UNSET = -1,\n     ++\tMERGE_DIRECTORY_RENAMES_NONE = 0,\n     ++\tMERGE_DIRECTORY_RENAMES_CONFLICT = 1,\n     ++\tMERGE_DIRECTORY_RENAMES_TRUE = 2,\n     ++};\n      +\n     -+#define FETCH_NEGOTIATION_DEFAULT 1\n     -+#define FETCH_NEGOTIATION_SKIPPING 2\n     ++enum fetch_negotiation_setting {\n     ++\tFETCH_NEGOTIATION_UNSET = -1,\n     ++\tFETCH_NEGOTIATION_NONE = 0,\n     ++\tFETCH_NEGOTIATION_DEFAULT = 1,\n     ++\tFETCH_NEGOTIATION_SKIPPING = 2,\n     ++};\n      +\n       struct repo_settings {\n       \tint core_commit_graph;\n       \tint gc_write_commit_graph;\n     --\tint pack_use_sparse;\n     -+\n     - \tint index_version;\n     - \tint core_untracked_cache;\n     -+\n     -+\tint pack_use_sparse;\n     -+\tint merge_directory_renames;\n     -+\tint fetch_negotiation_algorithm;\n     +@@\n     + \tenum untracked_cache_setting core_untracked_cache;\n     + \n     + \tint pack_use_sparse;\n     ++\tenum merge_directory_renames_setting merge_directory_renames;\n     ++\tenum fetch_negotiation_setting fetch_negotiation_algorithm;\n       };\n       \n       struct repository;\n\n-- \ngitgitgadget\n"},{"id":"379246","messageId":"nycvar.QRO.7.76.6.1907251128280.21907@tvgsbejvaqbjf.bet","threadId":"51508","inReplyTo":"597ab7d621983b5ad35006a2530aa70b452e895f.1564021392.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 1/5] repo-settings: consolidate some config settings","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-25T09:30:40Z","receivedAt":"2019-07-25T09:30:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stolee,\n\nOn Wed, 24 Jul 2019, Derrick Stolee via GitGitGadget wrote:\n\n> diff --git a/repo-settings.h b/repo-settings.h\n> new file mode 100644\n> index 0000000000..89fb0159bf\n> --- /dev/null\n> +++ b/repo-settings.h\n> @@ -0,0 +1,17 @@\n> +#ifndef REPO_SETTINGS_H\n> +#define REPO_SETTINGS_H\n> +\n> +struct repo_settings {\n> +\tint core_commit_graph;\n> +\tint gc_write_commit_graph;\n> +\n> +\tint index_version;\n> +\n> +\tint pack_use_sparse;\n> +};\n> +\n> +struct repository;\n> +\n> +void prepare_repo_settings(struct repository *r);\n> +\n> +#endif /* REPO_SETTINGS_H */\n> diff --git a/repository.h b/repository.h\n> index 4fb6a5885f..a817486825 100644\n> --- a/repository.h\n> +++ b/repository.h\n> @@ -2,8 +2,10 @@\n>  #define REPOSITORY_H\n>\n>  #include \"path.h\"\n> +#include \"repo-settings.h\"\n\nI still think that the `repo_settings` struct could just as easily be\ndeclared in `repository.h`. No need to invent a new header file.\n\n>\n>  struct config_set;\n> +struct repo_settings;\n\nIn any case, this is no longer necessary.\n\n>  struct git_hash_algo;\n>  struct index_state;\n>  struct lock_file;\n> @@ -72,6 +74,9 @@ struct repository {\n>  \t */\n>  \tchar *submodule_prefix;\n>\n> +\tint settings_initialized;\n\nOr maybe\n\n\tunsigned settings_initialized:1;\n\n?\n\n> +\tstruct repo_settings settings;\n> +\n\nOr maybe even fold the `initialized` flag into that struct?\n\nThanks,\nDscho\n\n>  \t/* Subsystems */\n>  \t/*\n>  \t * Repository's config which contains key-value pairs from the usual\n> --\n> gitgitgadget\n>\n>\n"},{"id":"379247","messageId":"nycvar.QRO.7.76.6.1907251131450.21907@tvgsbejvaqbjf.bet","threadId":"51508","inReplyTo":"49be7a73454b13b04270531eece87a2c6f6d3189.1564021393.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 3/5] repo-settings: parse core.untrackedCache","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-25T09:36:28Z","receivedAt":"2019-07-25T09:36:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stolee,\n\nOn Wed, 24 Jul 2019, Derrick Stolee via GitGitGadget wrote:\n\n> diff --git a/builtin/update-index.c b/builtin/update-index.c\n> index dff2f4b837..5ced51c1ee 100644\n> --- a/builtin/update-index.c\n> +++ b/builtin/update-index.c\n> @@ -18,6 +18,7 @@\n>  #include \"dir.h\"\n>  #include \"split-index.h\"\n>  #include \"fsmonitor.h\"\n> +#include \"repo-settings.h\"\n\nSorry, I only realized 10 seconds ago that these `#include`s are no\nlonger necessary because `repo-settings` is already included from\n`repository.h` (and needs to be, because the `repository` struct now\ncontains a full `struct repo_settings` field, no longer just a pointer).\n\nBut really, I would just fold the `repo-settings.h` declarations\ndirectly into `repository.h` (but keep `repo-settings.c`, of course!) as\nit make everything a bit simpler.\n\nThanks,\nDscho\n\n"},{"id":"379248","messageId":"nycvar.QRO.7.76.6.1907251138090.21907@tvgsbejvaqbjf.bet","threadId":"51508","inReplyTo":"pull.292.v2.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 0/5] Create 'feature.*' config area and some centralized config parsing","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-25T09:40:14Z","receivedAt":"2019-07-25T09:40:24Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stolee,\n\nOn Wed, 24 Jul 2019, Derrick Stolee via GitGitGadget wrote:\n\n> Updates in V2: I'm responding to Dscho's comments early, as they were very\n> valuable.\n>\n>  * Rearranged how we are checking for the feature.* settings, so if one\n>    layer turns the setting on and a later layer turns it off, we do not\n>    adjust the defaults.\n>\n>\n>  * Switched to using enums for the non-boolean config options.\n>\n>\n>  * Placed the repo_settings struct directly in the repository struct.\n>\n>\n>  * All struct members are initialized to -1 using memset.\n>\n>\n>  * The config changes around directory rename detection is greatly\n>    simplified.\n\nI left only a couple of comments, and quite honestly, I am really happy\nwith the direction this is going.\n\nIt even leaves the door open to let users define their custom feature\ngroups, at least I have a couple of ideas how that could be accomplished\nbuilding on top of `repo-settings.c`.\n\nThanks!\nDscho\n"},{"id":"379596","messageId":"c0129066a02b39535110ae592c16ca0e5d6d6c24.1564515324.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 2/5] repo-settings: add feature.manyCommits setting","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-30T19:35:27Z","receivedAt":"2019-07-30T19:35:32Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWhen a repo has many commits, it is helpful to write and read the\ncommit-graph file. Future changes to Git may include new config\nsettings that are beneficial in this scenario.\n\nCreate the 'feature.manyCommits' config setting that changes the\ndefault values of 'core.commitGraph' and 'gc.writeCommitGraph' to\ntrue.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config.txt         |  2 ++\n Documentation/config/core.txt    |  3 ++-\n Documentation/config/feature.txt | 15 +++++++++++++++\n Documentation/config/gc.txt      |  4 ++--\n repo-settings.c                  |  7 +++++++\n 5 files changed, 28 insertions(+), 3 deletions(-)\n create mode 100644 Documentation/config/feature.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex e3f5bc3396..77f3b1486b 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -345,6 +345,8 @@ include::config/difftool.txt[]\n \n include::config/fastimport.txt[]\n \n+include::config/feature.txt[]\n+\n include::config/fetch.txt[]\n \n include::config/format.txt[]\ndiff --git a/Documentation/config/core.txt b/Documentation/config/core.txt\nindex 75538d27e7..d80162681a 100644\n--- a/Documentation/config/core.txt\n+++ b/Documentation/config/core.txt\n@@ -577,7 +577,8 @@ the `GIT_NOTES_REF` environment variable.  See linkgit:git-notes[1].\n \n core.commitGraph::\n \tIf true, then git will read the commit-graph file (if it exists)\n-\tto parse the graph structure of commits. Defaults to false. See\n+\tto parse the graph structure of commits. Defaults to false, unless\n+\t`feature.manyCommits` is enabled. See\n \tlinkgit:git-commit-graph[1] for more information.\n \n core.useReplaceRefs::\ndiff --git a/Documentation/config/feature.txt b/Documentation/config/feature.txt\nnew file mode 100644\nindex 0000000000..f74314ae90\n--- /dev/null\n+++ b/Documentation/config/feature.txt\n@@ -0,0 +1,15 @@\n+feature.*::\n+\tThe config settings that start with `feature.` modify the defaults of\n+\ta group of other config settings. These groups are created by the Git\n+\tdeveloper community as recommended defaults and are subject to change.\n+\tIn particular, new config options may be added with different defaults.\n+\n+feature.manyCommits::\n+\tEnable config options that optimize for repos with many commits. This\n+\tsetting is recommended for repos with at least 100,000 commits. The\n+\tnew default values are:\n++\n+* `core.commitGraph=true` enables reading the commit-graph file.\n++\n+* `gc.writeCommitGraph=true` enables writing the commit-graph file during\n+garbage collection.\n\\ No newline at end of file\ndiff --git a/Documentation/config/gc.txt b/Documentation/config/gc.txt\nindex 02b92b18b5..31a5fc4f75 100644\n--- a/Documentation/config/gc.txt\n+++ b/Documentation/config/gc.txt\n@@ -63,8 +63,8 @@ gc.writeCommitGraph::\n \tIf true, then gc will rewrite the commit-graph file when\n \tlinkgit:git-gc[1] is run. When using `git gc --auto`\n \tthe commit-graph will be updated if housekeeping is\n-\trequired. Default is false. See linkgit:git-commit-graph[1]\n-\tfor details.\n+\trequired. Default is false, unless `feature.manyCommits`\n+\tis enabled. See linkgit:git-commit-graph[1] for details.\n \n gc.logExpiry::\n \tIf the file gc.log exists, then `git gc --auto` will print\ndiff --git a/repo-settings.c b/repo-settings.c\nindex 309577f6bc..fc05f4fbc4 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -2,6 +2,8 @@\n #include \"config.h\"\n #include \"repository.h\"\n \n+#define UPDATE_DEFAULT(s,v) do { if (s == -1) { s = v; } } while(0)\n+\n void prepare_repo_settings(struct repository *r)\n {\n \tint value;\n@@ -22,4 +24,9 @@ void prepare_repo_settings(struct repository *r)\n \n \tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n \t\tr->settings.pack_use_sparse = value;\n+\n+\tif (!repo_config_get_bool(r, \"feature.manycommits\", &value) && value) {\n+\t\tUPDATE_DEFAULT(r->settings.core_commit_graph, 1);\n+\t\tUPDATE_DEFAULT(r->settings.gc_write_commit_graph, 1);\n+\t}\n }\n-- \ngitgitgadget\n\n"},{"id":"379597","messageId":"4d0db57ecb4d241b61ba1f9c6f23aedc6b9bb4d9.1564515324.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 1/5] repo-settings: consolidate some config settings","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-30T19:35:26Z","receivedAt":"2019-07-30T19:35:33Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThere are a few important config settings that are not loaded\nduring git_default_config. These are instead loaded on-demand.\n\nCentralize these config options to a single scan, and store\nall of the values in a repo_settings struct. The values for\neach setting are initialized as negative to indicate \"unset\".\n\nThis centralization will be particularly important in a later\nchange to introduce \"meta\" config settings that change the\ndefaults for these config settings.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Makefile               |  1 +\n builtin/gc.c           | 12 +++++-------\n builtin/pack-objects.c |  8 ++++----\n commit-graph.c         |  6 +++---\n read-cache.c           | 11 ++++++-----\n repo-settings.c        | 25 +++++++++++++++++++++++++\n repository.h           | 15 +++++++++++++++\n 7 files changed, 59 insertions(+), 19 deletions(-)\n create mode 100644 repo-settings.c\n\ndiff --git a/Makefile b/Makefile\nindex 11ccea4071..032fe9b5f9 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -964,6 +964,7 @@ LIB_OBJS += refspec.o\n LIB_OBJS += ref-filter.o\n LIB_OBJS += remote.o\n LIB_OBJS += replace-object.o\n+LIB_OBJS += repo-settings.o\n LIB_OBJS += repository.o\n LIB_OBJS += rerere.o\n LIB_OBJS += resolve-undo.o\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex c18efadda5..4b8fbb9965 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -41,7 +41,6 @@ static int aggressive_depth = 50;\n static int aggressive_window = 250;\n static int gc_auto_threshold = 6700;\n static int gc_auto_pack_limit = 50;\n-static int gc_write_commit_graph;\n static int detach_auto = 1;\n static timestamp_t gc_log_expire_time;\n static const char *gc_log_expire = \"1.day.ago\";\n@@ -148,7 +147,6 @@ static void gc_config(void)\n \tgit_config_get_int(\"gc.aggressivedepth\", &aggressive_depth);\n \tgit_config_get_int(\"gc.auto\", &gc_auto_threshold);\n \tgit_config_get_int(\"gc.autopacklimit\", &gc_auto_pack_limit);\n-\tgit_config_get_bool(\"gc.writecommitgraph\", &gc_write_commit_graph);\n \tgit_config_get_bool(\"gc.autodetach\", &detach_auto);\n \tgit_config_get_expiry(\"gc.pruneexpire\", &prune_expire);\n \tgit_config_get_expiry(\"gc.worktreepruneexpire\", &prune_worktrees_expire);\n@@ -685,11 +683,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n \t\tclean_pack_garbage();\n \t}\n \n-\tif (gc_write_commit_graph &&\n-\t    write_commit_graph_reachable(get_object_directory(),\n-\t\t\t\t\t !quiet && !daemonized ? COMMIT_GRAPH_PROGRESS : 0,\n-\t\t\t\t\t NULL))\n-\t\treturn 1;\n+\tprepare_repo_settings(the_repository);\n+\tif (the_repository->settings.gc_write_commit_graph == 1)\n+\t\twrite_commit_graph_reachable(get_object_directory(),\n+\t\t\t\t\t     !quiet && !daemonized ? COMMIT_GRAPH_PROGRESS : 0,\n+\t\t\t\t\t     NULL);\n \n \tif (auto_gc && too_many_loose_objects())\n \t\twarning(_(\"There are too many unreachable loose objects; \"\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 267c562b1f..f9f7181f3f 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -2709,10 +2709,6 @@ static int git_pack_config(const char *k, const char *v, void *cb)\n \t\tuse_bitmap_index_default = git_config_bool(k, v);\n \t\treturn 0;\n \t}\n-\tif (!strcmp(k, \"pack.usesparse\")) {\n-\t\tsparse = git_config_bool(k, v);\n-\t\treturn 0;\n-\t}\n \tif (!strcmp(k, \"pack.threads\")) {\n \t\tdelta_search_threads = git_config_int(k, v);\n \t\tif (delta_search_threads < 0)\n@@ -3332,6 +3328,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \tread_replace_refs = 0;\n \n \tsparse = git_env_bool(\"GIT_TEST_PACK_SPARSE\", 0);\n+\tprepare_repo_settings(the_repository);\n+\tif (!sparse && the_repository->settings.pack_use_sparse != -1)\n+\t\tsparse = the_repository->settings.pack_use_sparse;\n+\n \treset_pack_idx_option(&pack_idx_opts);\n \tgit_config(git_pack_config, NULL);\n \ndiff --git a/commit-graph.c b/commit-graph.c\nindex b3c4de79b6..7854e491b2 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -466,7 +466,6 @@ static void prepare_commit_graph_one(struct repository *r, const char *obj_dir)\n static int prepare_commit_graph(struct repository *r)\n {\n \tstruct object_directory *odb;\n-\tint config_value;\n \n \tif (git_env_bool(GIT_TEST_COMMIT_GRAPH_DIE_ON_LOAD, 0))\n \t\tdie(\"dying as requested by the '%s' variable on commit-graph load!\",\n@@ -476,9 +475,10 @@ static int prepare_commit_graph(struct repository *r)\n \t\treturn !!r->objects->commit_graph;\n \tr->objects->commit_graph_attempted = 1;\n \n+\tprepare_repo_settings(r);\n+\n \tif (!git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) &&\n-\t    (repo_config_get_bool(r, \"core.commitgraph\", &config_value) ||\n-\t    !config_value))\n+\t    r->settings.core_commit_graph != 1)\n \t\t/*\n \t\t * This repository is not configured to use commit graphs, so\n \t\t * do not load one. (But report commit_graph_attempted anyway\ndiff --git a/read-cache.c b/read-cache.c\nindex c701f7f8b8..59dbebc15d 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -1599,16 +1599,17 @@ struct cache_entry *refresh_cache_entry(struct index_state *istate,\n \n #define INDEX_FORMAT_DEFAULT 3\n \n-static unsigned int get_index_format_default(void)\n+static unsigned int get_index_format_default(struct repository *r)\n {\n \tchar *envversion = getenv(\"GIT_INDEX_VERSION\");\n \tchar *endp;\n-\tint value;\n \tunsigned int version = INDEX_FORMAT_DEFAULT;\n \n \tif (!envversion) {\n-\t\tif (!git_config_get_int(\"index.version\", &value))\n-\t\t\tversion = value;\n+\t\tprepare_repo_settings(r);\n+\n+\t\tif (r->settings.index_version >= 0)\n+\t\t\tversion = r->settings.index_version;\n \t\tif (version < INDEX_FORMAT_LB || INDEX_FORMAT_UB < version) {\n \t\t\twarning(_(\"index.version set, but the value is invalid.\\n\"\n \t\t\t\t  \"Using version %i\"), INDEX_FORMAT_DEFAULT);\n@@ -2765,7 +2766,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t}\n \n \tif (!istate->version) {\n-\t\tistate->version = get_index_format_default();\n+\t\tistate->version = get_index_format_default(the_repository);\n \t\tif (git_env_bool(\"GIT_TEST_SPLIT_INDEX\", 0))\n \t\t\tinit_split_index(istate);\n \t}\ndiff --git a/repo-settings.c b/repo-settings.c\nnew file mode 100644\nindex 0000000000..309577f6bc\n--- /dev/null\n+++ b/repo-settings.c\n@@ -0,0 +1,25 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"repository.h\"\n+\n+void prepare_repo_settings(struct repository *r)\n+{\n+\tint value;\n+\n+\tif (r->settings.initialized)\n+\t\treturn;\n+\n+\t/* Defaults */\n+\tmemset(&r->settings, -1, sizeof(r->settings));\n+\n+\tif (!repo_config_get_bool(r, \"core.commitgraph\", &value))\n+\t\tr->settings.core_commit_graph = value;\n+\tif (!repo_config_get_bool(r, \"gc.writecommitgraph\", &value))\n+\t\tr->settings.gc_write_commit_graph = value;\n+\n+\tif (!repo_config_get_bool(r, \"index.version\", &value))\n+\t\tr->settings.index_version = value;\n+\n+\tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n+\t\tr->settings.pack_use_sparse = value;\n+}\ndiff --git a/repository.h b/repository.h\nindex 4fb6a5885f..2bb2bc3eea 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -4,6 +4,7 @@\n #include \"path.h\"\n \n struct config_set;\n+struct repo_settings;\n struct git_hash_algo;\n struct index_state;\n struct lock_file;\n@@ -11,6 +12,17 @@ struct pathspec;\n struct raw_object_store;\n struct submodule_cache;\n \n+struct repo_settings {\n+\tint initialized;\n+\n+\tint core_commit_graph;\n+\tint gc_write_commit_graph;\n+\n+\tint index_version;\n+\n+\tint pack_use_sparse;\n+};\n+\n struct repository {\n \t/* Environment */\n \t/*\n@@ -72,6 +84,8 @@ struct repository {\n \t */\n \tchar *submodule_prefix;\n \n+\tstruct repo_settings settings;\n+\n \t/* Subsystems */\n \t/*\n \t * Repository's config which contains key-value pairs from the usual\n@@ -157,5 +171,6 @@ int repo_read_index_unmerged(struct repository *);\n  */\n void repo_update_index_if_able(struct repository *, struct lock_file *);\n \n+void prepare_repo_settings(struct repository *r);\n \n #endif /* REPOSITORY_H */\n-- \ngitgitgadget\n\n"},{"id":"379598","messageId":"0d4774d45fee108981a4ad7316367f78c9f23fa1.1564515324.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 4/5] repo-settings: create feature.manyFiles setting","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-30T19:35:28Z","receivedAt":"2019-07-30T19:35:34Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe feature.manyFiles setting is suitable for repos with many\nfiles in the working directory. By setting index.version=4 and\ncore.untrackedCache=true, commands such as 'git status' should\nimprove.\n\nWhile adding this setting, modify the index version precedence\ntests to check how this setting overrides the default for\nindex.version is unset.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/core.txt    |  4 +++-\n Documentation/config/feature.txt | 12 +++++++++++-\n Documentation/config/index.txt   |  1 +\n repo-settings.c                  |  4 ++++\n t/t1600-index.sh                 | 31 ++++++++++++++++++++++++++-----\n 5 files changed, 45 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/core.txt b/Documentation/config/core.txt\nindex d80162681a..7a2a33bc8c 100644\n--- a/Documentation/config/core.txt\n+++ b/Documentation/config/core.txt\n@@ -86,7 +86,9 @@ core.untrackedCache::\n \tit will automatically be removed, if set to `false`. Before\n \tsetting it to `true`, you should check that mtime is working\n \tproperly on your system.\n-\tSee linkgit:git-update-index[1]. `keep` by default.\n+\tSee linkgit:git-update-index[1]. `keep` by default, unless\n+\t`feature.manyFiles` is enabled which sets this setting to\n+\t`true` by default.\n \n core.checkStat::\n \tWhen missing or is set to `default`, many fields in the stat\ndiff --git a/Documentation/config/feature.txt b/Documentation/config/feature.txt\nindex f74314ae90..c2d9ef7473 100644\n--- a/Documentation/config/feature.txt\n+++ b/Documentation/config/feature.txt\n@@ -12,4 +12,14 @@ feature.manyCommits::\n * `core.commitGraph=true` enables reading the commit-graph file.\n +\n * `gc.writeCommitGraph=true` enables writing the commit-graph file during\n-garbage collection.\n\\ No newline at end of file\n+garbage collection.\n+\n+feature.manyFiles::\n+\tEnable config options that optimize for repos with many files in the\n+\tworking directory. With many files, commands such as `git status` and\n+\t`git checkout` may be slow and these new defaults improve performance:\n++\n+* `index.version=4` enables path-prefix compression in the index.\n++\n+* `core.untrackedCache=true` enables the untracked cache. This setting assumes\n+that mtime is working on your machine.\n\\ No newline at end of file\ndiff --git a/Documentation/config/index.txt b/Documentation/config/index.txt\nindex f181503041..7cb50b37e9 100644\n--- a/Documentation/config/index.txt\n+++ b/Documentation/config/index.txt\n@@ -24,3 +24,4 @@ index.threads::\n index.version::\n \tSpecify the version with which new index files should be\n \tinitialized.  This does not affect existing repositories.\n+\tIf `feature.manyFiles` is enabled, then the default is 4.\ndiff --git a/repo-settings.c b/repo-settings.c\nindex 9ed83e6e54..af93696343 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -42,6 +42,10 @@ void prepare_repo_settings(struct repository *r)\n \t\tUPDATE_DEFAULT(r->settings.core_commit_graph, 1);\n \t\tUPDATE_DEFAULT(r->settings.gc_write_commit_graph, 1);\n \t}\n+\tif (!repo_config_get_bool(r, \"feature.manyfiles\", &value) && value) {\n+\t\tUPDATE_DEFAULT(r->settings.index_version, 4);\n+\t\tUPDATE_DEFAULT(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE);\n+\t}\n \n \t/* Hack for test programs like test-dump-untracked-cache */\n \tif (ignore_untracked_cache_config)\ndiff --git a/t/t1600-index.sh b/t/t1600-index.sh\nindex 42962ed7d4..c77721b580 100755\n--- a/t/t1600-index.sh\n+++ b/t/t1600-index.sh\n@@ -59,17 +59,38 @@ test_expect_success 'out of bounds index.version issues warning' '\n \t)\n '\n \n-test_expect_success 'GIT_INDEX_VERSION takes precedence over config' '\n+test_index_version () {\n+\tINDEX_VERSION_CONFIG=$1 &&\n+\tFEATURE_MANY_FILES=$2 &&\n+\tENV_VAR_VERSION=$3\n+\tEXPECTED_OUTPUT_VERSION=$4 &&\n \t(\n \t\trm -f .git/index &&\n-\t\tGIT_INDEX_VERSION=4 &&\n-\t\texport GIT_INDEX_VERSION &&\n-\t\tgit config --add index.version 2 &&\n+\t\trm -f .git/config &&\n+\t\tif test \"$INDEX_VERSION_CONFIG\" -ne 0\n+\t\tthen\n+\t\t\tgit config --add index.version $INDEX_VERSION_CONFIG\n+\t\tfi &&\n+\t\tgit config --add feature.manyFiles $FEATURE_MANY_FILES\n+\t\tif test \"$ENV_VAR_VERSION\" -ne 0\n+\t\tthen\n+\t\t\tGIT_INDEX_VERSION=$ENV_VAR_VERSION &&\n+\t\t\texport GIT_INDEX_VERSION\n+\t\telse\n+\t\t\tunset GIT_INDEX_VERSION\n+\t\tfi &&\n \t\tgit add a 2>&1 &&\n-\t\techo 4 >expect &&\n+\t\techo $EXPECTED_OUTPUT_VERSION >expect &&\n \t\ttest-tool index-version <.git/index >actual &&\n \t\ttest_cmp expect actual\n \t)\n+}\n+\n+test_expect_success 'index version config precedence' '\n+\ttest_index_version 2 false 4 4 &&\n+\ttest_index_version 2 true 0 2 &&\n+\ttest_index_version 0 true 0 4 &&\n+\ttest_index_version 0 true 2 2\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"379599","messageId":"2e153fac22dc5c27fc85efc802785edc0c9d78ad.1564515324.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 5/5] repo-settings: create feature.experimental setting","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-30T19:35:29Z","receivedAt":"2019-07-30T19:35:38Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe 'feature.experimental' setting includes config options that are\nnot committed to become defaults, but could use additional testing.\n\nUpdate the following config settings to take new defaults, and to\nuse the repo_settings struct if not already using them:\n\n* 'pack.useSparse=true'\n\n* 'merge.directoryRenames=true'\n\n* 'fetch.negotiationAlgorithm=skipping'\n\nIn the case of fetch.negotiationAlgorithm, the existing logic\nwould load the config option only when about to use the setting,\nso had a die() statement on an unknown string value. This is\nremoved as now the config is parsed under prepare_repo_settings().\nIn general, this die() is probably misplaced and not valuable.\nA test was removed that checked this die() statement executed.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/feature.txt     | 17 +++++++++++++++++\n Documentation/config/fetch.txt       |  3 ++-\n Documentation/config/merge.txt       |  3 ++-\n Documentation/config/pack.txt        |  3 ++-\n fetch-negotiator.c                   | 25 +++++++++++++------------\n fetch-negotiator.h                   |  5 +++--\n fetch-pack.c                         | 11 +++++------\n merge-recursive.c                    | 14 +++++---------\n repo-settings.c                      | 19 +++++++++++++++++++\n repository.h                         | 16 ++++++++++++++++\n t/t5552-skipping-fetch-negotiator.sh | 23 -----------------------\n 11 files changed, 84 insertions(+), 55 deletions(-)\n\ndiff --git a/Documentation/config/feature.txt b/Documentation/config/feature.txt\nindex c2d9ef7473..287621b1e8 100644\n--- a/Documentation/config/feature.txt\n+++ b/Documentation/config/feature.txt\n@@ -4,6 +4,23 @@ feature.*::\n \tdeveloper community as recommended defaults and are subject to change.\n \tIn particular, new config options may be added with different defaults.\n \n+feature.experimental::\n+\tEnable config options that are new to Git, and are being considered for\n+\tfuture defaults. Config settings included here may be added or removed\n+\twith each release, including minor version updates. These settings may\n+\thave unintended interactions since they are so new. Please enable this\n+\tsetting if you are interested in providing feedback on experimental\n+\tfeatures. The new default values are:\n++\n+* `pack.useSparse=true` uses a new algorithm when constructing a pack-file\n+which can improve `git push` performance in repos with many files.\n++\n+* `merge.directoryRenames=true` uses a new algorithm for detecting renames by\n+using entire directories at a time instead of single files at a time.\n++\n+* `fetch.negotiationAlgorithm=skipping` may improve fetch negotiation times by\n+skipping more commits at a time, reducing the number of round trips.\n+\n feature.manyCommits::\n \tEnable config options that optimize for repos with many commits. This\n \tsetting is recommended for repos with at least 100,000 commits. The\ndiff --git a/Documentation/config/fetch.txt b/Documentation/config/fetch.txt\nindex ba890b5884..d402110638 100644\n--- a/Documentation/config/fetch.txt\n+++ b/Documentation/config/fetch.txt\n@@ -59,7 +59,8 @@ fetch.negotiationAlgorithm::\n \teffort to converge faster, but may result in a larger-than-necessary\n \tpackfile; The default is \"default\" which instructs Git to use the default algorithm\n \tthat never skips commits (unless the server has acknowledged it or one\n-\tof its descendants).\n+\tof its descendants). If `feature.experimental` is enabled, then this\n+\tsetting defaults to \"skipping\".\n \tUnknown values will cause 'git fetch' to error out.\n +\n See also the `--negotiation-tip` option for linkgit:git-fetch[1].\ndiff --git a/Documentation/config/merge.txt b/Documentation/config/merge.txt\nindex 6a313937f8..e8def2d63c 100644\n--- a/Documentation/config/merge.txt\n+++ b/Documentation/config/merge.txt\n@@ -54,7 +54,8 @@ merge.directoryRenames::\n \tmoved into the new directory.  If set to \"conflict\", a conflict\n \twill be reported for such paths.  If merge.renames is false,\n \tmerge.directoryRenames is ignored and treated as false.  Defaults\n-\tto \"conflict\".\n+\tto \"conflict\" unless `feature.experimental` is enabled and the\n+\tdefault is \"true\".\n \n merge.renormalize::\n \tTell Git that canonical representation of files in the\ndiff --git a/Documentation/config/pack.txt b/Documentation/config/pack.txt\nindex 9cdcfa7324..1d66f0c992 100644\n--- a/Documentation/config/pack.txt\n+++ b/Documentation/config/pack.txt\n@@ -112,7 +112,8 @@ pack.useSparse::\n \tobjects. This can have significant performance benefits when\n \tcomputing a pack to send a small change. However, it is possible\n \tthat extra objects are added to the pack-file if the included\n-\tcommits contain certain types of direct renames.\n+\tcommits contain certain types of direct renames. Default is `false`\n+\tunless `feature.experimental` is enabled.\n \n pack.writeBitmaps (deprecated)::\n \tThis is a deprecated synonym for `repack.writeBitmaps`.\ndiff --git a/fetch-negotiator.c b/fetch-negotiator.c\nindex d6d685cba0..0a1357dc9d 100644\n--- a/fetch-negotiator.c\n+++ b/fetch-negotiator.c\n@@ -2,19 +2,20 @@\n #include \"fetch-negotiator.h\"\n #include \"negotiator/default.h\"\n #include \"negotiator/skipping.h\"\n+#include \"repository.h\"\n \n-void fetch_negotiator_init(struct fetch_negotiator *negotiator,\n-\t\t\t   const char *algorithm)\n+void fetch_negotiator_init(struct repository *r,\n+\t\t\t   struct fetch_negotiator *negotiator)\n {\n-\tif (algorithm) {\n-\t\tif (!strcmp(algorithm, \"skipping\")) {\n-\t\t\tskipping_negotiator_init(negotiator);\n-\t\t\treturn;\n-\t\t} else if (!strcmp(algorithm, \"default\")) {\n-\t\t\t/* Fall through to default initialization */\n-\t\t} else {\n-\t\t\tdie(\"unknown fetch negotiation algorithm '%s'\", algorithm);\n-\t\t}\n+\tprepare_repo_settings(r);\n+\tswitch(r->settings.fetch_negotiation_algorithm) {\n+\tcase FETCH_NEGOTIATION_SKIPPING:\n+\t\tskipping_negotiator_init(negotiator);\n+\t\treturn;\n+\n+\tcase FETCH_NEGOTIATION_DEFAULT:\n+\tdefault:\n+\t\tdefault_negotiator_init(negotiator);\n+\t\treturn;\n \t}\n-\tdefault_negotiator_init(negotiator);\n }\ndiff --git a/fetch-negotiator.h b/fetch-negotiator.h\nindex 9e3967ce66..ea78868504 100644\n--- a/fetch-negotiator.h\n+++ b/fetch-negotiator.h\n@@ -2,6 +2,7 @@\n #define FETCH_NEGOTIATOR_H\n \n struct commit;\n+struct repository;\n \n /*\n  * An object that supplies the information needed to negotiate the contents of\n@@ -52,7 +53,7 @@ struct fetch_negotiator {\n \tvoid *data;\n };\n \n-void fetch_negotiator_init(struct fetch_negotiator *negotiator,\n-\t\t\t   const char *algorithm);\n+void fetch_negotiator_init(struct repository *r,\n+\t\t\t   struct fetch_negotiator *negotiator);\n \n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 65be043f2a..d81f47c07b 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -36,7 +36,6 @@ static int agent_supported;\n static int server_supports_filtering;\n static struct lock_file shallow_lock;\n static const char *alternate_shallow_file;\n-static char *negotiation_algorithm;\n static struct strbuf fsck_msg_types = STRBUF_INIT;\n \n /* Remember to update object flag allocation in object.h */\n@@ -892,12 +891,13 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,\n \t\t\t\t struct shallow_info *si,\n \t\t\t\t char **pack_lockfile)\n {\n+\tstruct repository *r = the_repository;\n \tstruct ref *ref = copy_ref_list(orig_ref);\n \tstruct object_id oid;\n \tconst char *agent_feature;\n \tint agent_len;\n \tstruct fetch_negotiator negotiator;\n-\tfetch_negotiator_init(&negotiator, negotiation_algorithm);\n+\tfetch_negotiator_init(r, &negotiator);\n \n \tsort_ref_list(&ref, ref_compare_name);\n \tQSORT(sought, nr_sought, cmp_ref_by_name);\n@@ -911,7 +911,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,\n \n \tif (server_supports(\"shallow\"))\n \t\tprint_verbose(args, _(\"Server supports %s\"), \"shallow\");\n-\telse if (args->depth > 0 || is_repository_shallow(the_repository))\n+\telse if (args->depth > 0 || is_repository_shallow(r))\n \t\tdie(_(\"Server does not support shallow clients\"));\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n@@ -1379,6 +1379,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\t\t\t    struct shallow_info *si,\n \t\t\t\t    char **pack_lockfile)\n {\n+\tstruct repository *r = the_repository;\n \tstruct ref *ref = copy_ref_list(orig_ref);\n \tenum fetch_state state = FETCH_CHECK_LOCAL;\n \tstruct oidset common = OIDSET_INIT;\n@@ -1386,7 +1387,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tint in_vain = 0;\n \tint haves_to_send = INITIAL_FLUSH;\n \tstruct fetch_negotiator negotiator;\n-\tfetch_negotiator_init(&negotiator, negotiation_algorithm);\n+\tfetch_negotiator_init(r, &negotiator);\n \tpacket_reader_init(&reader, fd[0], NULL, 0,\n \t\t\t   PACKET_READ_CHOMP_NEWLINE |\n \t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n@@ -1505,8 +1506,6 @@ static void fetch_pack_config(void)\n \tgit_config_get_bool(\"repack.usedeltabaseoffset\", &prefer_ofs_delta);\n \tgit_config_get_bool(\"fetch.fsckobjects\", &fetch_fsck_objects);\n \tgit_config_get_bool(\"transfer.fsckobjects\", &transfer_fsck_objects);\n-\tgit_config_get_string(\"fetch.negotiationalgorithm\",\n-\t\t\t      &negotiation_algorithm);\n \n \tgit_config(fetch_pack_config_cb, NULL);\n }\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 12300131fc..4f77b4b3bb 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3662,15 +3662,6 @@ static void merge_recursive_config(struct merge_options *opt)\n \t\topt->merge_detect_rename = git_config_rename(\"merge.renames\", value);\n \t\tfree(value);\n \t}\n-\tif (!git_config_get_string(\"merge.directoryrenames\", &value)) {\n-\t\tint boolval = git_parse_maybe_bool(value);\n-\t\tif (0 <= boolval) {\n-\t\t\topt->detect_directory_renames = boolval ? 2 : 0;\n-\t\t} else if (!strcasecmp(value, \"conflict\")) {\n-\t\t\topt->detect_directory_renames = 1;\n-\t\t} /* avoid erroring on values from future versions of git */\n-\t\tfree(value);\n-\t}\n \tgit_config(git_xmerge_config, NULL);\n }\n \n@@ -3688,6 +3679,11 @@ void init_merge_options(struct merge_options *opt,\n \topt->diff_detect_rename = -1;\n \topt->merge_detect_rename = -1;\n \topt->detect_directory_renames = 1;\n+\n+\tprepare_repo_settings(repo);\n+\tif (repo->settings.merge_directory_renames >= 0)\n+\t\topt->detect_directory_renames = repo->settings.merge_directory_renames;\n+\n \tmerge_recursive_config(opt);\n \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n \tif (merge_verbosity)\ndiff --git a/repo-settings.c b/repo-settings.c\nindex af93696343..e0673938c0 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -34,6 +34,18 @@ void prepare_repo_settings(struct repository *r)\n \t\tfree(strval);\n \t}\n \n+\tif (!repo_config_get_maybe_bool(r, \"merge.directoryrenames\", &value))\n+\t\tr->settings.merge_directory_renames = value ? MERGE_DIRECTORY_RENAMES_TRUE : 0;\n+\telse if (!repo_config_get_string(r, \"merge.directoryrenames\", &strval)) {\n+\t\tif (!strcasecmp(strval, \"conflict\"))\n+\t\t\tr->settings.merge_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n+\t}\n+\tif (!repo_config_get_string(r, \"fetch.negotiationalgorithm\", &strval)) {\n+\t\tif (!strcasecmp(strval, \"skipping\"))\n+\t\t\tr->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;\n+\t\telse\n+\t\t\tr->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT;\n+\t}\n \n \tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n \t\tr->settings.pack_use_sparse = value;\n@@ -46,6 +58,11 @@ void prepare_repo_settings(struct repository *r)\n \t\tUPDATE_DEFAULT(r->settings.index_version, 4);\n \t\tUPDATE_DEFAULT(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE);\n \t}\n+\tif (!repo_config_get_bool(r, \"feature.experimental\", &value) && value) {\n+\t\tUPDATE_DEFAULT(r->settings.pack_use_sparse, 1);\n+\t\tUPDATE_DEFAULT(r->settings.merge_directory_renames, MERGE_DIRECTORY_RENAMES_TRUE);\n+\t\tUPDATE_DEFAULT(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_SKIPPING);\n+\t}\n \n \t/* Hack for test programs like test-dump-untracked-cache */\n \tif (ignore_untracked_cache_config)\n@@ -53,4 +70,6 @@ void prepare_repo_settings(struct repository *r)\n \telse\n \t\tUPDATE_DEFAULT(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);\n \n+\tUPDATE_DEFAULT(r->settings.merge_directory_renames, MERGE_DIRECTORY_RENAMES_CONFLICT);\n+\tUPDATE_DEFAULT(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_DEFAULT);\n }\ndiff --git a/repository.h b/repository.h\nindex e7a72e2341..b8e52dd48f 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -19,6 +19,20 @@ enum untracked_cache_setting {\n \tUNTRACKED_CACHE_WRITE = 2\n };\n \n+enum merge_directory_renames_setting {\n+\tMERGE_DIRECTORY_RENAMES_UNSET = -1,\n+\tMERGE_DIRECTORY_RENAMES_NONE = 0,\n+\tMERGE_DIRECTORY_RENAMES_CONFLICT = 1,\n+\tMERGE_DIRECTORY_RENAMES_TRUE = 2,\n+};\n+\n+enum fetch_negotiation_setting {\n+\tFETCH_NEGOTIATION_UNSET = -1,\n+\tFETCH_NEGOTIATION_NONE = 0,\n+\tFETCH_NEGOTIATION_DEFAULT = 1,\n+\tFETCH_NEGOTIATION_SKIPPING = 2,\n+};\n+\n struct repo_settings {\n \tint initialized;\n \n@@ -29,6 +43,8 @@ struct repo_settings {\n \tenum untracked_cache_setting core_untracked_cache;\n \n \tint pack_use_sparse;\n+\tenum merge_directory_renames_setting merge_directory_renames;\n+\tenum fetch_negotiation_setting fetch_negotiation_algorithm;\n };\n \n struct repository {\ndiff --git a/t/t5552-skipping-fetch-negotiator.sh b/t/t5552-skipping-fetch-negotiator.sh\nindex 8a14be51a1..f70cbcc9ca 100755\n--- a/t/t5552-skipping-fetch-negotiator.sh\n+++ b/t/t5552-skipping-fetch-negotiator.sh\n@@ -60,29 +60,6 @@ test_expect_success 'commits with no parents are sent regardless of skip distanc\n \thave_not_sent c6 c4 c3\n '\n \n-test_expect_success 'unknown fetch.negotiationAlgorithm values error out' '\n-\trm -rf server client trace &&\n-\tgit init server &&\n-\ttest_commit -C server to_fetch &&\n-\n-\tgit init client &&\n-\ttest_commit -C client on_client &&\n-\tgit -C client checkout on_client &&\n-\n-\ttest_config -C client fetch.negotiationAlgorithm invalid &&\n-\ttest_must_fail git -C client fetch \"$(pwd)/server\" 2>err &&\n-\ttest_i18ngrep \"unknown fetch negotiation algorithm\" err &&\n-\n-\t# Explicit \"default\" value\n-\ttest_config -C client fetch.negotiationAlgorithm default &&\n-\tgit -C client -c fetch.negotiationAlgorithm=default fetch \"$(pwd)/server\" &&\n-\n-\t# Implementation detail: If there is nothing to fetch, we will not error out\n-\ttest_config -C client fetch.negotiationAlgorithm invalid &&\n-\tgit -C client fetch \"$(pwd)/server\" 2>err &&\n-\ttest_i18ngrep ! \"unknown fetch negotiation algorithm\" err\n-'\n-\n test_expect_success 'when two skips collide, favor the larger one' '\n \trm -rf server client trace &&\n \tgit init server &&\n-- \ngitgitgadget\n"},{"id":"379600","messageId":"pull.292.v3.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v3 0/5] Create 'feature.*' config area and some centralized config parsing","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-30T19:35:25Z","receivedAt":"2019-07-30T19:35:40Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"This is a brand-new thread to replace ds/early-access. The discussion on\nthat thread was very helpful [1].\n\nWith this in mind, I propose instead a set of \"feature.*\" config settings\nthat form groups of \"community recommended\" settings (with some caveats). In\nthe space below, I'll list a set of possible feature names and the implied\nconfig options.\n\nFirst, the main two categories we've discussed so far: many commits and many\nfiles. These two feature sets are for when your repo is large in one of\nthese dimensions. Perhaps there are other settings to include in these?\n\nfeature.manyFiles:\n    index.version = 4\n    core.untrackedCache = true\n\nfeature.manyCommits:\n    core.commitGraph = true\n    gc.writeCommitGraph = true\n    (future: fetch.writeSplitCommitGraph = true)\n\nNote: the fetch.writeSplitCommitGraph does not exist yet, but could be\nintroduced in a later release to write a new commit-graph (with --split) on\nfetch.\n\nThe other category that has been discussed already is that of \"experimental\nfeatures that we generally think are helpful but change behavior slightly in\nsome cases\".\n\nfeature.experimental:\n    pack.useSparse = true\n    merge.directoryRenames = true\n    fetch.negotiationAlgorithm = skipping\n\nSpecifically, this setting is for config values we are not sure will ever be\non by default, but additional testing is needed to be sure. This is\ndifferent than a possible 'feature.preview' setting that would include\nconfig settings that we are committed to updating the defaults in a future\nrelease. There are many ways we can take this idea in the future (including\nmore additions to these categories).\n\nThanks, -Stolee\n\nUpdates in V2: I'm responding to Dscho's comments early, as they were very\nvaluable.\n\n * Rearranged how we are checking for the feature.* settings, so if one\n   layer turns the setting on and a later layer turns it off, we do not\n   adjust the defaults.\n   \n   \n * Switched to using enums for the non-boolean config options.\n   \n   \n * Placed the repo_settings struct directly in the repository struct.\n   \n   \n * All struct members are initialized to -1 using memset.\n   \n   \n * The config changes around directory rename detection is greatly\n   simplified.\n   \n   \n\nUpdates in V3: These are more Dscho comments, and are quite small.\n\n * Remove repo-settings.h by placing the struct and function definitions in\n   repository.h.\n   \n   \n * Move the initialization int into \"struct repo_settings\".\n   \n   \n\n[1] https://public-inbox.org/git/pull.254.git.gitgitgadget@gmail.com/\n\nDerrick Stolee (5):\n  repo-settings: consolidate some config settings\n  repo-settings: add feature.manyCommits setting\n  repo-settings: parse core.untrackedCache\n  repo-settings: create feature.manyFiles setting\n  repo-settings: create feature.experimental setting\n\n Documentation/config.txt             |  2 +\n Documentation/config/core.txt        |  7 ++-\n Documentation/config/feature.txt     | 42 ++++++++++++++++\n Documentation/config/fetch.txt       |  3 +-\n Documentation/config/gc.txt          |  4 +-\n Documentation/config/index.txt       |  1 +\n Documentation/config/merge.txt       |  3 +-\n Documentation/config/pack.txt        |  3 +-\n Makefile                             |  1 +\n builtin/gc.c                         | 12 ++---\n builtin/pack-objects.c               |  8 +--\n builtin/update-index.c               |  6 ++-\n commit-graph.c                       |  6 +--\n config.c                             | 24 ---------\n fetch-negotiator.c                   | 25 +++++-----\n fetch-negotiator.h                   |  5 +-\n fetch-pack.c                         | 11 ++--\n merge-recursive.c                    | 14 ++----\n read-cache.c                         | 30 +++++------\n repo-settings.c                      | 75 ++++++++++++++++++++++++++++\n repository.h                         | 39 +++++++++++++++\n t/t1600-index.sh                     | 31 ++++++++++--\n t/t5552-skipping-fetch-negotiator.sh | 23 ---------\n 23 files changed, 256 insertions(+), 119 deletions(-)\n create mode 100644 Documentation/config/feature.txt\n create mode 100644 repo-settings.c\n\n\nbase-commit: 9c9b961d7eb15fb583a2a812088713a68a85f1c0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-292%2Fderrickstolee%2Frepo-settings%2Fhead-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-292/derrickstolee/repo-settings/head-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/292\n\nRange-diff vs v2:\n\n 1:  597ab7d621 ! 1:  4d0db57ecb repo-settings: consolidate some config settings\n     @@ -30,14 +30,6 @@\n       diff --git a/builtin/gc.c b/builtin/gc.c\n       --- a/builtin/gc.c\n       +++ b/builtin/gc.c\n     -@@\n     - #include \"pack-objects.h\"\n     - #include \"blob.h\"\n     - #include \"tree.h\"\n     -+#include \"repo-settings.h\"\n     - \n     - #define FAILED_RUN \"failed to run %s\"\n     - \n      @@\n       static int aggressive_window = 250;\n       static int gc_auto_threshold = 6700;\n     @@ -75,14 +67,6 @@\n       diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\n       --- a/builtin/pack-objects.c\n       +++ b/builtin/pack-objects.c\n     -@@\n     - #include \"dir.h\"\n     - #include \"midx.h\"\n     - #include \"trace2.h\"\n     -+#include \"repo-settings.h\"\n     - \n     - #define IN_PACK(obj) oe_in_pack(&to_pack, obj)\n     - #define SIZE(obj) oe_size(&to_pack, obj)\n      @@\n       \t\tuse_bitmap_index_default = git_config_bool(k, v);\n       \t\treturn 0;\n     @@ -109,14 +93,6 @@\n       diff --git a/commit-graph.c b/commit-graph.c\n       --- a/commit-graph.c\n       +++ b/commit-graph.c\n     -@@\n     - #include \"hashmap.h\"\n     - #include \"replace-object.h\"\n     - #include \"progress.h\"\n     -+#include \"repo-settings.h\"\n     - \n     - #define GRAPH_SIGNATURE 0x43475048 /* \"CGPH\" */\n     - #define GRAPH_CHUNKID_OIDFANOUT 0x4f494446 /* \"OIDF\" */\n      @@\n       static int prepare_commit_graph(struct repository *r)\n       {\n     @@ -142,14 +118,6 @@\n       diff --git a/read-cache.c b/read-cache.c\n       --- a/read-cache.c\n       +++ b/read-cache.c\n     -@@\n     - #include \"fsmonitor.h\"\n     - #include \"thread-utils.h\"\n     - #include \"progress.h\"\n     -+#include \"repo-settings.h\"\n     - \n     - /* Mask for the name length in ce_flags in the on-disk index */\n     - \n      @@\n       \n       #define INDEX_FORMAT_DEFAULT 3\n     @@ -188,15 +156,14 @@\n       +++ b/repo-settings.c\n      @@\n      +#include \"cache.h\"\n     -+#include \"repository.h\"\n      +#include \"config.h\"\n     -+#include \"repo-settings.h\"\n     ++#include \"repository.h\"\n      +\n      +void prepare_repo_settings(struct repository *r)\n      +{\n      +\tint value;\n      +\n     -+\tif (r->settings_initialized)\n     ++\tif (r->settings.initialized)\n      +\t\treturn;\n      +\n      +\t/* Defaults */\n     @@ -212,54 +179,50 @@\n      +\n      +\tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n      +\t\tr->settings.pack_use_sparse = value;\n     -+\n     -+\tr->settings_initialized = 1;\n      +}\n      \n     - diff --git a/repo-settings.h b/repo-settings.h\n     - new file mode 100644\n     - --- /dev/null\n     - +++ b/repo-settings.h\n     -@@\n     -+#ifndef REPO_SETTINGS_H\n     -+#define REPO_SETTINGS_H\n     -+\n     -+struct repo_settings {\n     -+\tint core_commit_graph;\n     -+\tint gc_write_commit_graph;\n     -+\n     -+\tint index_version;\n     -+\n     -+\tint pack_use_sparse;\n     -+};\n     -+\n     -+struct repository;\n     -+\n     -+void prepare_repo_settings(struct repository *r);\n     -+\n     -+#endif /* REPO_SETTINGS_H */\n     -\n       diff --git a/repository.h b/repository.h\n       --- a/repository.h\n       +++ b/repository.h\n      @@\n     - #define REPOSITORY_H\n     - \n       #include \"path.h\"\n     -+#include \"repo-settings.h\"\n       \n       struct config_set;\n      +struct repo_settings;\n       struct git_hash_algo;\n       struct index_state;\n       struct lock_file;\n     +@@\n     + struct raw_object_store;\n     + struct submodule_cache;\n     + \n     ++struct repo_settings {\n     ++\tint initialized;\n     ++\n     ++\tint core_commit_graph;\n     ++\tint gc_write_commit_graph;\n     ++\n     ++\tint index_version;\n     ++\n     ++\tint pack_use_sparse;\n     ++};\n     ++\n     + struct repository {\n     + \t/* Environment */\n     + \t/*\n      @@\n       \t */\n       \tchar *submodule_prefix;\n       \n     -+\tint settings_initialized;\n      +\tstruct repo_settings settings;\n      +\n       \t/* Subsystems */\n       \t/*\n       \t * Repository's config which contains key-value pairs from the usual\n     +@@\n     +  */\n     + void repo_update_index_if_able(struct repository *, struct lock_file *);\n     + \n     ++void prepare_repo_settings(struct repository *r);\n     + \n     + #endif /* REPOSITORY_H */\n 2:  86380c7832 ! 2:  c0129066a0 repo-settings: add feature.manyCommits setting\n     @@ -81,7 +81,7 @@\n       +++ b/repo-settings.c\n      @@\n       #include \"config.h\"\n     - #include \"repo-settings.h\"\n     + #include \"repository.h\"\n       \n      +#define UPDATE_DEFAULT(s,v) do { if (s == -1) { s = v; } } while(0)\n      +\n     @@ -89,13 +89,12 @@\n       {\n       \tint value;\n      @@\n     + \n       \tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n       \t\tr->settings.pack_use_sparse = value;\n     - \n     ++\n      +\tif (!repo_config_get_bool(r, \"feature.manycommits\", &value) && value) {\n      +\t\tUPDATE_DEFAULT(r->settings.core_commit_graph, 1);\n      +\t\tUPDATE_DEFAULT(r->settings.gc_write_commit_graph, 1);\n      +\t}\n     -+\n     - \tr->settings_initialized = 1;\n       }\n 3:  49be7a7345 ! 3:  b0a2d0e188 repo-settings: parse core.untrackedCache\n     @@ -25,14 +25,6 @@\n       diff --git a/builtin/update-index.c b/builtin/update-index.c\n       --- a/builtin/update-index.c\n       +++ b/builtin/update-index.c\n     -@@\n     - #include \"dir.h\"\n     - #include \"split-index.h\"\n     - #include \"fsmonitor.h\"\n     -+#include \"repo-settings.h\"\n     - \n     - /*\n     -  * Default to not allowing changes to the list of files. The\n      @@\n       \tstruct parse_opt_ctx_t ctx;\n       \tstrbuf_getline_fn getline_fn;\n     @@ -141,7 +133,7 @@\n       \tint value;\n      +\tchar *strval;\n       \n     - \tif (r->settings_initialized)\n     + \tif (r->settings.initialized)\n       \t\treturn;\n      @@\n       \n     @@ -168,22 +160,21 @@\n       \t\tUPDATE_DEFAULT(r->settings.core_commit_graph, 1);\n       \t\tUPDATE_DEFAULT(r->settings.gc_write_commit_graph, 1);\n       \t}\n     - \n     ++\n      +\t/* Hack for test programs like test-dump-untracked-cache */\n      +\tif (ignore_untracked_cache_config)\n      +\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;\n      +\telse\n      +\t\tUPDATE_DEFAULT(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);\n      +\n     - \tr->settings_initialized = 1;\n       }\n      \n     - diff --git a/repo-settings.h b/repo-settings.h\n     - --- a/repo-settings.h\n     - +++ b/repo-settings.h\n     + diff --git a/repository.h b/repository.h\n     + --- a/repository.h\n     + +++ b/repository.h\n      @@\n     - #ifndef REPO_SETTINGS_H\n     - #define REPO_SETTINGS_H\n     + struct raw_object_store;\n     + struct submodule_cache;\n       \n      +enum untracked_cache_setting {\n      +\tUNTRACKED_CACHE_UNSET = -1,\n     @@ -193,7 +184,9 @@\n      +};\n      +\n       struct repo_settings {\n     - \tint core_commit_graph;\n     + \tint initialized;\n     + \n     +@@\n       \tint gc_write_commit_graph;\n       \n       \tint index_version;\n 4:  86a5a0c589 = 4:  0d4774d45f repo-settings: create feature.manyFiles setting\n 5:  b8a631683a ! 5:  2e153fac22 repo-settings: create feature.experimental setting\n     @@ -93,18 +93,6 @@\n       pack.writeBitmaps (deprecated)::\n       \tThis is a deprecated synonym for `repack.writeBitmaps`.\n      \n     - diff --git a/builtin/am.c b/builtin/am.c\n     - --- a/builtin/am.c\n     - +++ b/builtin/am.c\n     -@@\n     - #include \"string-list.h\"\n     - #include \"packfile.h\"\n     - #include \"repository.h\"\n     -+#include \"repo-settings.h\"\n     - \n     - /**\n     -  * Returns the length of the first line of msg.\n     -\n       diff --git a/fetch-negotiator.c b/fetch-negotiator.c\n       --- a/fetch-negotiator.c\n       +++ b/fetch-negotiator.c\n     @@ -113,7 +101,6 @@\n       #include \"negotiator/default.h\"\n       #include \"negotiator/skipping.h\"\n      +#include \"repository.h\"\n     -+#include \"repo-settings.h\"\n       \n      -void fetch_negotiator_init(struct fetch_negotiator *negotiator,\n      -\t\t\t   const char *algorithm)\n     @@ -230,14 +217,6 @@\n       diff --git a/merge-recursive.c b/merge-recursive.c\n       --- a/merge-recursive.c\n       +++ b/merge-recursive.c\n     -@@\n     - #include \"submodule.h\"\n     - #include \"revision.h\"\n     - #include \"commit-reach.h\"\n     -+#include \"repo-settings.h\"\n     - \n     - struct path_hashmap_entry {\n     - \tstruct hashmap_entry e;\n      @@\n       \t\topt->merge_detect_rename = git_config_rename(\"merge.renames\", value);\n       \t\tfree(value);\n     @@ -307,13 +286,11 @@\n       \n      +\tUPDATE_DEFAULT(r->settings.merge_directory_renames, MERGE_DIRECTORY_RENAMES_CONFLICT);\n      +\tUPDATE_DEFAULT(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_DEFAULT);\n     -+\n     - \tr->settings_initialized = 1;\n       }\n      \n     - diff --git a/repo-settings.h b/repo-settings.h\n     - --- a/repo-settings.h\n     - +++ b/repo-settings.h\n     + diff --git a/repository.h b/repository.h\n     + --- a/repository.h\n     + +++ b/repository.h\n      @@\n       \tUNTRACKED_CACHE_WRITE = 2\n       };\n     @@ -333,8 +310,8 @@\n      +};\n      +\n       struct repo_settings {\n     - \tint core_commit_graph;\n     - \tint gc_write_commit_graph;\n     + \tint initialized;\n     + \n      @@\n       \tenum untracked_cache_setting core_untracked_cache;\n       \n     @@ -343,7 +320,7 @@\n      +\tenum fetch_negotiation_setting fetch_negotiation_algorithm;\n       };\n       \n     - struct repository;\n     + struct repository {\n      \n       diff --git a/t/t5552-skipping-fetch-negotiator.sh b/t/t5552-skipping-fetch-negotiator.sh\n       --- a/t/t5552-skipping-fetch-negotiator.sh\n\n-- \ngitgitgadget\n"},{"id":"379601","messageId":"b0a2d0e18800da01ab1b17faca34eb011fb36128.1564515324.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 3/5] repo-settings: parse core.untrackedCache","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-07-30T19:35:28Z","receivedAt":"2019-07-30T19:35:41Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe core.untrackedCache config setting is slightly complicated,\nso clarify its use and centralize its parsing into the repo\nsettings.\n\nThe default value is \"keep\" (returned as -1), which persists the\nuntracked cache if it exists.\n\nIf the value is set as \"false\" (returned as 0), then remove the\nuntracked cache if it exists.\n\nIf the value is set as \"true\" (returned as 1), then write the\nuntracked cache and persist it.\n\nInstead of relying on magic values of -1, 0, and 1, split these\noptions into an enum. This allows the use of \"-1\" as a\ndefault value. After parsing the config options, if the value is\nunset we can initialize it to UNTRACKED_CACHE_KEEP.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/update-index.c |  6 ++++--\n config.c               | 24 ------------------------\n read-cache.c           | 19 +++++++++----------\n repo-settings.c        | 22 +++++++++++++++++++++-\n repository.h           |  8 ++++++++\n 5 files changed, 42 insertions(+), 37 deletions(-)\n\ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex dff2f4b837..49302d98c5 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -966,6 +966,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \tstruct parse_opt_ctx_t ctx;\n \tstrbuf_getline_fn getline_fn;\n \tint parseopt_state = PARSE_OPT_UNKNOWN;\n+\tstruct repository *r = the_repository;\n \tstruct option options[] = {\n \t\tOPT_BIT('q', NULL, &refresh_args.flags,\n \t\t\tN_(\"continue refresh even when index needs update\"),\n@@ -1180,11 +1181,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\tremove_split_index(&the_index);\n \t}\n \n+\tprepare_repo_settings(r);\n \tswitch (untracked_cache) {\n \tcase UC_UNSPECIFIED:\n \t\tbreak;\n \tcase UC_DISABLE:\n-\t\tif (git_config_get_untracked_cache() == 1)\n+\t\tif (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE)\n \t\t\twarning(_(\"core.untrackedCache is set to true; \"\n \t\t\t\t  \"remove or change it, if you really want to \"\n \t\t\t\t  \"disable the untracked cache\"));\n@@ -1196,7 +1198,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\treturn !test_if_untracked_cache_is_supported();\n \tcase UC_ENABLE:\n \tcase UC_FORCE:\n-\t\tif (git_config_get_untracked_cache() == 0)\n+\t\tif (r->settings.core_untracked_cache == UNTRACKED_CACHE_REMOVE)\n \t\t\twarning(_(\"core.untrackedCache is set to false; \"\n \t\t\t\t  \"remove or change it, if you really want to \"\n \t\t\t\t  \"enable the untracked cache\"));\ndiff --git a/config.c b/config.c\nindex faa57e436c..3241dbc54d 100644\n--- a/config.c\n+++ b/config.c\n@@ -2277,30 +2277,6 @@ int git_config_get_expiry_in_days(const char *key, timestamp_t *expiry, timestam\n \treturn -1; /* thing exists but cannot be parsed */\n }\n \n-int git_config_get_untracked_cache(void)\n-{\n-\tint val = -1;\n-\tconst char *v;\n-\n-\t/* Hack for test programs like test-dump-untracked-cache */\n-\tif (ignore_untracked_cache_config)\n-\t\treturn -1;\n-\n-\tif (!git_config_get_maybe_bool(\"core.untrackedcache\", &val))\n-\t\treturn val;\n-\n-\tif (!git_config_get_value(\"core.untrackedcache\", &v)) {\n-\t\tif (!strcasecmp(v, \"keep\"))\n-\t\t\treturn -1;\n-\n-\t\terror(_(\"unknown core.untrackedCache value '%s'; \"\n-\t\t\t\"using 'keep' default value\"), v);\n-\t\treturn -1;\n-\t}\n-\n-\treturn -1; /* default value */\n-}\n-\n int git_config_get_split_index(void)\n {\n \tint val;\ndiff --git a/read-cache.c b/read-cache.c\nindex 59dbebc15d..7a07286164 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -1845,18 +1845,17 @@ static void check_ce_order(struct index_state *istate)\n \n static void tweak_untracked_cache(struct index_state *istate)\n {\n-\tswitch (git_config_get_untracked_cache()) {\n-\tcase -1: /* keep: do nothing */\n-\t\tbreak;\n-\tcase 0: /* false */\n+\tstruct repository *r = the_repository;\n+\n+\tprepare_repo_settings(r);\n+\n+\tif (r->settings.core_untracked_cache  == UNTRACKED_CACHE_REMOVE) {\n \t\tremove_untracked_cache(istate);\n-\t\tbreak;\n-\tcase 1: /* true */\n-\t\tadd_untracked_cache(istate);\n-\t\tbreak;\n-\tdefault: /* unknown value: do nothing */\n-\t\tbreak;\n+\t\treturn;\n \t}\n+\n+\tif (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE)\n+\t\tadd_untracked_cache(istate);\n }\n \n static void tweak_split_index(struct index_state *istate)\ndiff --git a/repo-settings.c b/repo-settings.c\nindex fc05f4fbc4..9ed83e6e54 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -7,6 +7,7 @@\n void prepare_repo_settings(struct repository *r)\n {\n \tint value;\n+\tchar *strval;\n \n \tif (r->settings.initialized)\n \t\treturn;\n@@ -21,12 +22,31 @@ void prepare_repo_settings(struct repository *r)\n \n \tif (!repo_config_get_bool(r, \"index.version\", &value))\n \t\tr->settings.index_version = value;\n+\tif (!repo_config_get_maybe_bool(r, \"core.untrackedcache\", &value)) {\n+\t\tif (value == 0)\n+\t\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_REMOVE;\n+\t\telse\n+\t\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_WRITE;\n+\t} else if (!repo_config_get_string(r, \"core.untrackedcache\", &strval)) {\n+\t\tif (!strcasecmp(strval, \"keep\"))\n+\t\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;\n+\n+\t\tfree(strval);\n+\t}\n+\n \n \tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n \t\tr->settings.pack_use_sparse = value;\n-\n+\t\n \tif (!repo_config_get_bool(r, \"feature.manycommits\", &value) && value) {\n \t\tUPDATE_DEFAULT(r->settings.core_commit_graph, 1);\n \t\tUPDATE_DEFAULT(r->settings.gc_write_commit_graph, 1);\n \t}\n+\n+\t/* Hack for test programs like test-dump-untracked-cache */\n+\tif (ignore_untracked_cache_config)\n+\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;\n+\telse\n+\t\tUPDATE_DEFAULT(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);\n+\n }\ndiff --git a/repository.h b/repository.h\nindex 2bb2bc3eea..e7a72e2341 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -12,6 +12,13 @@ struct pathspec;\n struct raw_object_store;\n struct submodule_cache;\n \n+enum untracked_cache_setting {\n+\tUNTRACKED_CACHE_UNSET = -1,\n+\tUNTRACKED_CACHE_REMOVE = 0,\n+\tUNTRACKED_CACHE_KEEP = 1,\n+\tUNTRACKED_CACHE_WRITE = 2\n+};\n+\n struct repo_settings {\n \tint initialized;\n \n@@ -19,6 +26,7 @@ struct repo_settings {\n \tint gc_write_commit_graph;\n \n \tint index_version;\n+\tenum untracked_cache_setting core_untracked_cache;\n \n \tint pack_use_sparse;\n };\n-- \ngitgitgadget\n\n"},{"id":"379611","messageId":"xmqqlfwfs10c.fsf@gitster-ct.c.googlers.com","threadId":"51508","inReplyTo":"4d0db57ecb4d241b61ba1f9c6f23aedc6b9bb4d9.1564515324.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 1/5] repo-settings: consolidate some config settings","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-30T20:47:31Z","receivedAt":"2019-07-30T20:47:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> diff --git a/repository.h b/repository.h\n> index 4fb6a5885f..2bb2bc3eea 100644\n> --- a/repository.h\n> +++ b/repository.h\n> @@ -4,6 +4,7 @@\n>  #include \"path.h\"\n>  \n>  struct config_set;\n> +struct repo_settings;\n\nGiven that the next hunk you introduce the real thing, and nobody\nrefers to it until then, I do not see why we want to have a forward\ndeclaration.\n\n>  struct git_hash_algo;\n>  struct index_state;\n>  struct lock_file;\n> @@ -11,6 +12,17 @@ struct pathspec;\n>  struct raw_object_store;\n>  struct submodule_cache;\n>  \n> +struct repo_settings {\n> +\tint initialized;\n> +\n> +\tint core_commit_graph;\n> +\tint gc_write_commit_graph;\n> +\n> +\tint index_version;\n> +\n> +\tint pack_use_sparse;\n> +};\n> +\n>  struct repository {\n>  \t/* Environment */\n>  \t/*\n> @@ -72,6 +84,8 @@ struct repository {\n>  \t */\n>  \tchar *submodule_prefix;\n>  \n> +\tstruct repo_settings settings;\n> +\n>  \t/* Subsystems */\n>  \t/*\n>  \t * Repository's config which contains key-value pairs from the usual\n> @@ -157,5 +171,6 @@ int repo_read_index_unmerged(struct repository *);\n>   */\n>  void repo_update_index_if_able(struct repository *, struct lock_file *);\n>  \n> +void prepare_repo_settings(struct repository *r);\n>  \n>  #endif /* REPOSITORY_H */\n"},{"id":"379613","messageId":"xmqqh873s0jy.fsf@gitster-ct.c.googlers.com","threadId":"51508","inReplyTo":"c0129066a02b39535110ae592c16ca0e5d6d6c24.1564515324.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 2/5] repo-settings: add feature.manyCommits setting","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-30T20:57:21Z","receivedAt":"2019-07-30T20:57:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> diff --git a/Documentation/config/feature.txt b/Documentation/config/feature.txt\n> new file mode 100644\n> index 0000000000..f74314ae90\n> --- /dev/null\n> +++ b/Documentation/config/feature.txt\n> @@ -0,0 +1,15 @@\n> +feature.*::\n> +...\n> +* `gc.writeCommitGraph=true` enables writing the commit-graph file during\n> +garbage collection.\n> \\ No newline at end of file\n\nNo newline at end of file\n\n> diff --git a/Documentation/config/gc.txt b/Documentation/config/gc.txt\n> index 02b92b18b5..31a5fc4f75 100644\n> --- a/Documentation/config/gc.txt\n> +++ b/Documentation/config/gc.txt\n> @@ -63,8 +63,8 @@ gc.writeCommitGraph::\n>  \tIf true, then gc will rewrite the commit-graph file when\n>  \tlinkgit:git-gc[1] is run. When using `git gc --auto`\n>  \tthe commit-graph will be updated if housekeeping is\n> -\trequired. Default is false. See linkgit:git-commit-graph[1]\n> -\tfor details.\n> +\trequired. Default is false, unless `feature.manyCommits`\n> +\tis enabled. See linkgit:git-commit-graph[1] for details.\n>  \n>  gc.logExpiry::\n>  \tIf the file gc.log exists, then `git gc --auto` will print\n> diff --git a/repo-settings.c b/repo-settings.c\n> index 309577f6bc..fc05f4fbc4 100644\n> --- a/repo-settings.c\n> +++ b/repo-settings.c\n> @@ -2,6 +2,8 @@\n>  #include \"config.h\"\n>  #include \"repository.h\"\n>  \n> +#define UPDATE_DEFAULT(s,v) do { if (s == -1) { s = v; } } while(0)\n\nA few points:\n\n 1. give 's' and 'v' a bit better name, perhaps 'slot' and 'value'?\n\n 2. \"do { if ((s) == -1) { (s) = (v); } } while(0)\"\n\n 3. When we learn to set default values for variables that are not\n    boolean in the future, we will regret that we did not name it\n    UPDATE_DEFAULT_BOOL(slot, value).\n\n"},{"id":"379664","messageId":"nycvar.QRO.7.76.6.1907311515210.21907@tvgsbejvaqbjf.bet","threadId":"51508","inReplyTo":"xmqqh873s0jy.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 2/5] repo-settings: add feature.manyCommits setting","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-31T13:17:59Z","receivedAt":"2019-07-31T13:18:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Tue, 30 Jul 2019, Junio C Hamano wrote:\n\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > +#define UPDATE_DEFAULT(s,v) do { if (s == -1) { s = v; } } while(0)\n>\n> [...]\n>  3. When we learn to set default values for variables that are not\n>     boolean in the future, we will regret that we did not name it\n>     UPDATE_DEFAULT_BOOL(slot, value).\n\nOn the other hand, as we never promised any kind of API (and this is not\neven an internal API to begin with), it will be _easy_ to rename it in\nthe unlikely event that we would ever introduce non-boolean defaults to\noverride, wouldn't you agree?\n\nWe have plenty of precedent where patch series start by refactoring,\nwhether it is to rename functions or variables or files or extracting\nfunctions. Preparing for a future that might never come strikes me as\npremature optimization.\n\nCiao,\nDscho\n"},{"id":"379668","messageId":"87mugu5jtm.fsf@evledraar.gmail.com","threadId":"51508","inReplyTo":"c0129066a02b39535110ae592c16ca0e5d6d6c24.1564515324.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 2/5] repo-settings: add feature.manyCommits setting","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-07-31T15:01:57Z","receivedAt":"2019-07-31T15:02:02Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Jul 30 2019, Derrick Stolee via GitGitGadget wrote:\n\n> +feature.*::\n> +\tThe config settings that start with `feature.` modify the defaults of\n> +\ta group of other config settings. These groups are created by the Git\n> +\tdeveloper community as recommended defaults and are subject to change.\n> +\tIn particular, new config options may be added with different defaults.\n> +\n> +feature.manyCommits::\n> +\tEnable config options that optimize for repos with many commits. This\n> +\tsetting is recommended for repos with at least 100,000 commits. The\n> +\tnew default values are:\n> ++\n> +* `core.commitGraph=true` enables reading the commit-graph file.\n> ++\n> +* `gc.writeCommitGraph=true` enables writing the commit-graph file during\n> +garbage collection.\n\nDuring the whole new commit graph format discussion (which has now\nlanded) we discussed just auto toggling this:\nhttps://public-inbox.org/git/87zhobr4fl.fsf@evledraar.gmail.com/\n\nThis looks fine, but have we backed out of simply enabling this at this\npoint? I don't see why not, regardless of commit count...\n"},{"id":"379687","messageId":"xmqq1ry6ryr5.fsf@gitster-ct.c.googlers.com","threadId":"51508","inReplyTo":"nycvar.QRO.7.76.6.1907311515210.21907@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 2/5] repo-settings: add feature.manyCommits setting","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-31T15:48:30Z","receivedAt":"2019-07-31T15:48:40Z","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 Tue, 30 Jul 2019, Junio C Hamano wrote:\n>\n>> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>\n>> > +#define UPDATE_DEFAULT(s,v) do { if (s == -1) { s = v; } } while(0)\n>>\n>> [...]\n>>  3. When we learn to set default values for variables that are not\n>>     boolean in the future, we will regret that we did not name it\n>>     UPDATE_DEFAULT_BOOL(slot, value).\n>\n> On the other hand, as we never promised any kind of API (and this is not\n> even an internal API to begin with), it will be _easy_ to rename it in\n> the unlikely event that we would ever introduce non-boolean defaults to\n> override, wouldn't you agree?\n\nI agree that it is easy to say that it is easy to rename it later\nand burden somebody else with the task.\n\nI know that the renaming itself is easy, when you limit yourself\nwithin the scope of a single topic, whether done now or later.  I\nalso know that having to worry about other topics in flight has\nnon-zero cost.  I also know that you are not the one who will bear\nit---I will be.\n\nSo from my point of view, if we can make a prediction, even with\nlimited knowledge that a name may need to be renamed in the future,\nit is better not pick such a name and instead use one that we think\nit has a better chance of surviving without needing a rename.\n\n"},{"id":"379768","messageId":"f8445819-f5b7-44da-8610-6f9d81475228@gmail.com","threadId":"51508","inReplyTo":"87mugu5jtm.fsf@evledraar.gmail.com","subject":"Re: [PATCH v3 2/5] repo-settings: add feature.manyCommits setting","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-08-01T18:27:22Z","receivedAt":"2019-08-01T18:27:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/31/2019 11:01 AM, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Tue, Jul 30 2019, Derrick Stolee via GitGitGadget wrote:\n> \n>> +feature.*::\n>> +\tThe config settings that start with `feature.` modify the defaults of\n>> +\ta group of other config settings. These groups are created by the Git\n>> +\tdeveloper community as recommended defaults and are subject to change.\n>> +\tIn particular, new config options may be added with different defaults.\n>> +\n>> +feature.manyCommits::\n>> +\tEnable config options that optimize for repos with many commits. This\n>> +\tsetting is recommended for repos with at least 100,000 commits. The\n>> +\tnew default values are:\n>> ++\n>> +* `core.commitGraph=true` enables reading the commit-graph file.\n>> ++\n>> +* `gc.writeCommitGraph=true` enables writing the commit-graph file during\n>> +garbage collection.\n> \n> During the whole new commit graph format discussion (which has now\n> landed) we discussed just auto toggling this:\n> https://public-inbox.org/git/87zhobr4fl.fsf@evledraar.gmail.com/\n> \n> This looks fine, but have we backed out of simply enabling this at this\n> point? I don't see why not, regardless of commit count...\n\nI would be happy to drop feature.manyCommits and instead swap the\ndefaults of core.commitGraph and gc.writeCommitGraph to true if we think\nthat is what we want to do for 2.24.0. We can use the repo settings and\nUPDATE_DEFAULT[_BOOL] for this purpose.\n\nThanks,\n-Stolee\n\n"},{"id":"380153","messageId":"CABPp-BGppXSt9i4SXTizgPXap-YgHBwHTVrvwDpg98zvpYRwcQ@mail.gmail.com","threadId":"51508","inReplyTo":"2e153fac22dc5c27fc85efc802785edc0c9d78ad.1564515324.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 5/5] repo-settings: create feature.experimental setting","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-08T18:34:29Z","receivedAt":"2019-08-08T18:34:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Sorry for the late reply...\n\nOn Tue, Jul 30, 2019 at 3:49 PM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> --- a/Documentation/config/merge.txt\n> +++ b/Documentation/config/merge.txt\n> @@ -54,7 +54,8 @@ merge.directoryRenames::\n>         moved into the new directory.  If set to \"conflict\", a conflict\n>         will be reported for such paths.  If merge.renames is false,\n>         merge.directoryRenames is ignored and treated as false.  Defaults\n> -       to \"conflict\".\n> +       to \"conflict\" unless `feature.experimental` is enabled and the\n> +       default is \"true\".\n\nI have a hard time parsing that changed sentence.  Perhaps something like:\n   ...unless `feature.experimental` is enabled in which case the\ndefault is \"true\".\n?\n\n...\n> diff --git a/repo-settings.c b/repo-settings.c\n> index af93696343..e0673938c0 100644\n> --- a/repo-settings.c\n> +++ b/repo-settings.c\n> @@ -34,6 +34,18 @@ void prepare_repo_settings(struct repository *r)\n>                 free(strval);\n>         }\n>\n> +       if (!repo_config_get_maybe_bool(r, \"merge.directoryrenames\", &value))\n> +               r->settings.merge_directory_renames = value ? MERGE_DIRECTORY_RENAMES_TRUE : 0;\n\nShouldn't that be \"MERGE_DIRECTORY_RENAMES_NONE\" instead of \"0\"?\n\n> diff --git a/repository.h b/repository.h\n> index e7a72e2341..b8e52dd48f 100644\n> --- a/repository.h\n> +++ b/repository.h\n> @@ -19,6 +19,20 @@ enum untracked_cache_setting {\n>         UNTRACKED_CACHE_WRITE = 2\n>  };\n>\n> +enum merge_directory_renames_setting {\n> +       MERGE_DIRECTORY_RENAMES_UNSET = -1,\n> +       MERGE_DIRECTORY_RENAMES_NONE = 0,\n> +       MERGE_DIRECTORY_RENAMES_CONFLICT = 1,\n> +       MERGE_DIRECTORY_RENAMES_TRUE = 2,\n> +};\n\nThanks for adding these; makes things much nicer.  :-)\n\n\nCheers,\nElijah\n"},{"id":"380154","messageId":"c9598b37-9941-15eb-c599-b7f0e921a1b2@gmail.com","threadId":"51508","inReplyTo":"CABPp-BGppXSt9i4SXTizgPXap-YgHBwHTVrvwDpg98zvpYRwcQ@mail.gmail.com","subject":"Re: [PATCH v3 5/5] repo-settings: create feature.experimental setting","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-08-08T18:48:46Z","receivedAt":"2019-08-08T18:48:49Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 8/8/2019 2:34 PM, Elijah Newren wrote:\n> Sorry for the late reply...\n> \n> On Tue, Jul 30, 2019 at 3:49 PM Derrick Stolee via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> --- a/Documentation/config/merge.txt\n>> +++ b/Documentation/config/merge.txt\n>> @@ -54,7 +54,8 @@ merge.directoryRenames::\n>>         moved into the new directory.  If set to \"conflict\", a conflict\n>>         will be reported for such paths.  If merge.renames is false,\n>>         merge.directoryRenames is ignored and treated as false.  Defaults\n>> -       to \"conflict\".\n>> +       to \"conflict\" unless `feature.experimental` is enabled and the\n>> +       default is \"true\".\n> \n> I have a hard time parsing that changed sentence.  Perhaps something like:\n>    ...unless `feature.experimental` is enabled in which case the\n> default is \"true\".\n> ?\n> \n> ...\n>> diff --git a/repo-settings.c b/repo-settings.c\n>> index af93696343..e0673938c0 100644\n>> --- a/repo-settings.c\n>> +++ b/repo-settings.c\n>> @@ -34,6 +34,18 @@ void prepare_repo_settings(struct repository *r)\n>>                 free(strval);\n>>         }\n>>\n>> +       if (!repo_config_get_maybe_bool(r, \"merge.directoryrenames\", &value))\n>> +               r->settings.merge_directory_renames = value ? MERGE_DIRECTORY_RENAMES_TRUE : 0;\n> \n> Shouldn't that be \"MERGE_DIRECTORY_RENAMES_NONE\" instead of \"0\"?\n> \n>> diff --git a/repository.h b/repository.h\n>> index e7a72e2341..b8e52dd48f 100644\n>> --- a/repository.h\n>> +++ b/repository.h\n>> @@ -19,6 +19,20 @@ enum untracked_cache_setting {\n>>         UNTRACKED_CACHE_WRITE = 2\n>>  };\n>>\n>> +enum merge_directory_renames_setting {\n>> +       MERGE_DIRECTORY_RENAMES_UNSET = -1,\n>> +       MERGE_DIRECTORY_RENAMES_NONE = 0,\n>> +       MERGE_DIRECTORY_RENAMES_CONFLICT = 1,\n>> +       MERGE_DIRECTORY_RENAMES_TRUE = 2,\n>> +};\n> \n> Thanks for adding these; makes things much nicer.  :-)\n\nThanks for the feedback! It's just in time for me to send a v4.\n\n-Stolee\n"},{"id":"380155","messageId":"xmqqd0hffpoz.fsf@gitster-ct.c.googlers.com","threadId":"51508","inReplyTo":"CABPp-BGppXSt9i4SXTizgPXap-YgHBwHTVrvwDpg98zvpYRwcQ@mail.gmail.com","subject":"Re: [PATCH v3 5/5] repo-settings: create feature.experimental setting","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-08T18:59:56Z","receivedAt":"2019-08-08T19:00:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n>> --- a/Documentation/config/merge.txt\n>> +++ b/Documentation/config/merge.txt\n>> @@ -54,7 +54,8 @@ merge.directoryRenames::\n>>         moved into the new directory.  If set to \"conflict\", a conflict\n>>         will be reported for such paths.  If merge.renames is false,\n>>         merge.directoryRenames is ignored and treated as false.  Defaults\n>> -       to \"conflict\".\n>> +       to \"conflict\" unless `feature.experimental` is enabled and the\n>> +       default is \"true\".\n>\n> I have a hard time parsing that changed sentence.  Perhaps something like:\n>    ...unless `feature.experimental` is enabled in which case the\n> default is \"true\".\n> ?\n\nThat reads better.\n\nBut I am not sure about the wisdom of controlling between conflict\nand true with this feature macro in the first place.\n\nBetween \"conflict\" and \"true\", the former forces the end user to\nverify (or allows the end user to veto) the auto resolution by the\nheuristics and is always a safer if more cumbersome option.  It's\nnot like blindly trusting the directory rename heuristics is the\nbright future for all users, is it?\n\nI would not set rerere.autoUpdate to true when feature.experimental\nis set; for exactly the same reason, I do not find it reasonable to\nset this to true with feature.experimental macro.\n\n> ...\n>> diff --git a/repo-settings.c b/repo-settings.c\n>> index af93696343..e0673938c0 100644\n>> --- a/repo-settings.c\n>> +++ b/repo-settings.c\n>> @@ -34,6 +34,18 @@ void prepare_repo_settings(struct repository *r)\n>>                 free(strval);\n>>         }\n>>\n>> +       if (!repo_config_get_maybe_bool(r, \"merge.directoryrenames\", &value))\n>> +               r->settings.merge_directory_renames = value ? MERGE_DIRECTORY_RENAMES_TRUE : 0;\n>\n> Shouldn't that be \"MERGE_DIRECTORY_RENAMES_NONE\" instead of \"0\"?\n>\n>> diff --git a/repository.h b/repository.h\n>> index e7a72e2341..b8e52dd48f 100644\n>> --- a/repository.h\n>> +++ b/repository.h\n>> @@ -19,6 +19,20 @@ enum untracked_cache_setting {\n>>         UNTRACKED_CACHE_WRITE = 2\n>>  };\n>>\n>> +enum merge_directory_renames_setting {\n>> +       MERGE_DIRECTORY_RENAMES_UNSET = -1,\n>> +       MERGE_DIRECTORY_RENAMES_NONE = 0,\n>> +       MERGE_DIRECTORY_RENAMES_CONFLICT = 1,\n>> +       MERGE_DIRECTORY_RENAMES_TRUE = 2,\n>> +};\n>\n> Thanks for adding these; makes things much nicer.  :-)\n>\n>\n> Cheers,\n> Elijah\n"},{"id":"380158","messageId":"4fe2cb63-2d6c-d8d5-5531-9ea6d624a85a@gmail.com","threadId":"51508","inReplyTo":"xmqqd0hffpoz.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 5/5] repo-settings: create feature.experimental setting","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-08-08T19:12:31Z","receivedAt":"2019-08-08T19:12:42Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 8/8/2019 2:59 PM, Junio C Hamano wrote:\n> Elijah Newren <newren@gmail.com> writes:\n> \n>>> --- a/Documentation/config/merge.txt\n>>> +++ b/Documentation/config/merge.txt\n>>> @@ -54,7 +54,8 @@ merge.directoryRenames::\n>>>         moved into the new directory.  If set to \"conflict\", a conflict\n>>>         will be reported for such paths.  If merge.renames is false,\n>>>         merge.directoryRenames is ignored and treated as false.  Defaults\n>>> -       to \"conflict\".\n>>> +       to \"conflict\" unless `feature.experimental` is enabled and the\n>>> +       default is \"true\".\n>>\n>> I have a hard time parsing that changed sentence.  Perhaps something like:\n>>    ...unless `feature.experimental` is enabled in which case the\n>> default is \"true\".\n>> ?\n> \n> That reads better.\n> \n> But I am not sure about the wisdom of controlling between conflict\n> and true with this feature macro in the first place.\n> \n> Between \"conflict\" and \"true\", the former forces the end user to\n> verify (or allows the end user to veto) the auto resolution by the\n> heuristics and is always a safer if more cumbersome option.  It's\n> not like blindly trusting the directory rename heuristics is the\n> bright future for all users, is it?\n> \n> I would not set rerere.autoUpdate to true when feature.experimental\n> is set; for exactly the same reason, I do not find it reasonable to\n> set this to true with feature.experimental macro.\n\nOK. I can remove it from the feature.experimental variable.\n\nShall I keep the enum logic and the use of repo-settings.c? I can split\nthem out into a separate patch.\n\n-Stolee\n\n"},{"id":"380160","messageId":"CABPp-BH2EHGEmOj3kpeTMV2PpKkRBpPn3FuBfNDHKA8=BkKxFQ@mail.gmail.com","threadId":"51508","inReplyTo":"xmqqd0hffpoz.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 5/5] repo-settings: create feature.experimental setting","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-08T19:19:14Z","receivedAt":"2019-08-08T19:19:28Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Aug 8, 2019 at 12:00 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> >> --- a/Documentation/config/merge.txt\n> >> +++ b/Documentation/config/merge.txt\n> >> @@ -54,7 +54,8 @@ merge.directoryRenames::\n> >>         moved into the new directory.  If set to \"conflict\", a conflict\n> >>         will be reported for such paths.  If merge.renames is false,\n> >>         merge.directoryRenames is ignored and treated as false.  Defaults\n> >> -       to \"conflict\".\n> >> +       to \"conflict\" unless `feature.experimental` is enabled and the\n> >> +       default is \"true\".\n> >\n> > I have a hard time parsing that changed sentence.  Perhaps something like:\n> >    ...unless `feature.experimental` is enabled in which case the\n> > default is \"true\".\n> > ?\n>\n> That reads better.\n\n:-)\n\n> But I am not sure about the wisdom of controlling between conflict\n> and true with this feature macro in the first place.\n>\n> Between \"conflict\" and \"true\", the former forces the end user to\n> verify (or allows the end user to veto) the auto resolution by the\n> heuristics and is always a safer if more cumbersome option.  It's\n> not like blindly trusting the directory rename heuristics is the\n> bright future for all users, is it?\n\nI'm afraid I don't quite know what the intended usecase for\nfeature.experimental is, and I'm unfamiliar with both pack.useSparse\nand fetch.negotiationAlgorithm for comparison.  If the intended\nusecase is stuff that is expected to become the future for all users,\nthen I agree with you; \"conflict\" is the right default for both now\nand in the future so feature.experimental should not change it.\nHowever, Stollee's commit message started with \"The\n'feature.experimental' setting includes config options that are not\ncommitted to become defaults\".\n\nIf these are settings that are \"intended\" but not \"committed\" to\nbecome defaults, then yes, you're absolutely right.  (And in such a\ncase, it might be nice to add that distinction to the commit message.)\n If there is some other purpose, it depends on what that purpose is.\n"},{"id":"380164","messageId":"xmqqv9v7e7zf.fsf@gitster-ct.c.googlers.com","threadId":"51508","inReplyTo":"CABPp-BH2EHGEmOj3kpeTMV2PpKkRBpPn3FuBfNDHKA8=BkKxFQ@mail.gmail.com","subject":"Re: [PATCH v3 5/5] repo-settings: create feature.experimental setting","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-08T20:07:48Z","receivedAt":"2019-08-08T20:07:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> However, Stollee's commit message started with \"The\n> 'feature.experimental' setting includes config options that are not\n> committed to become defaults\".\n>\n> If these are settings that are \"intended\" but not \"committed\" to\n> become defaults, then yes, you're absolutely right.  (And in such a\n> case, it might be nice to add that distinction to the commit message.)\n\nMy understanding is that the only purpose of feature.experimental\nis: \"we want to change the default of X this way, but we do not know\nif we missed an important use case that would be harmed by such a\nchange, so let's see if those who volunteered to be guinea pigs by\nsetting feature.experimental to true find glitches in our thinking\nthat led us to think the change of the default is a good idea\".\n\n"},{"id":"380168","messageId":"CABPp-BGo5spDbk34YwAPGqT1Xgsr1dHm6OhxY1xKFQxQaotBig@mail.gmail.com","threadId":"51508","inReplyTo":"4fe2cb63-2d6c-d8d5-5531-9ea6d624a85a@gmail.com","subject":"Re: [PATCH v3 5/5] repo-settings: create feature.experimental setting","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-08T20:31:43Z","receivedAt":"2019-08-08T20:31:57Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Aug 8, 2019 at 12:12 PM Derrick Stolee <stolee@gmail.com> wrote:\n>\n> On 8/8/2019 2:59 PM, Junio C Hamano wrote:\n> > Elijah Newren <newren@gmail.com> writes:\n> >\n> >>> --- a/Documentation/config/merge.txt\n> >>> +++ b/Documentation/config/merge.txt\n> >>> @@ -54,7 +54,8 @@ merge.directoryRenames::\n> >>>         moved into the new directory.  If set to \"conflict\", a conflict\n> >>>         will be reported for such paths.  If merge.renames is false,\n> >>>         merge.directoryRenames is ignored and treated as false.  Defaults\n> >>> -       to \"conflict\".\n> >>> +       to \"conflict\" unless `feature.experimental` is enabled and the\n> >>> +       default is \"true\".\n> >>\n> >> I have a hard time parsing that changed sentence.  Perhaps something like:\n> >>    ...unless `feature.experimental` is enabled in which case the\n> >> default is \"true\".\n> >> ?\n> >\n> > That reads better.\n> >\n> > But I am not sure about the wisdom of controlling between conflict\n> > and true with this feature macro in the first place.\n> >\n> > Between \"conflict\" and \"true\", the former forces the end user to\n> > verify (or allows the end user to veto) the auto resolution by the\n> > heuristics and is always a safer if more cumbersome option.  It's\n> > not like blindly trusting the directory rename heuristics is the\n> > bright future for all users, is it?\n> >\n> > I would not set rerere.autoUpdate to true when feature.experimental\n> > is set; for exactly the same reason, I do not find it reasonable to\n> > set this to true with feature.experimental macro.\n>\n> OK. I can remove it from the feature.experimental variable.\n>\n> Shall I keep the enum logic and the use of repo-settings.c? I can split\n> them out into a separate patch.\n\nGood question.  In part, I was looking at this ds/feature-macros\nseries because my cleanup-merge-api series[1] has some minor conflicts\nwith it.  I'm a little unsure what route I should take with my series\nnow.  Some choices:\n  * keep this logic in your series in a separate patch, and have me\nrebase my series on yours.\n  * drop this logic from your series since it may not be needed\nanymore, making our two series independent again.\n  * move this logic into a separate patch of yours but making that\npatch part of my series instead; that'd be easy with the enum logic,\nbut the repo-settings.c stuff appears to depend on your other\npatches...\n\nThoughts or preferences?\n\n[1] https://public-inbox.org/git/20190726155258.28561-1-newren@gmail.com/\n"},{"id":"380169","messageId":"c2e8d473-b7fd-eed1-d73f-a9fcd9f73447@gmail.com","threadId":"51508","inReplyTo":"xmqqv9v7e7zf.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 5/5] repo-settings: create feature.experimental setting","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-08-08T20:46:29Z","receivedAt":"2019-08-08T20:46:33Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 8/8/2019 4:07 PM, Junio C Hamano wrote:\n> Elijah Newren <newren@gmail.com> writes:\n> \n>> However, Stollee's commit message started with \"The\n>> 'feature.experimental' setting includes config options that are not\n>> committed to become defaults\".\n>>\n>> If these are settings that are \"intended\" but not \"committed\" to\n>> become defaults, then yes, you're absolutely right.  (And in such a\n>> case, it might be nice to add that distinction to the commit message.)\n> \n> My understanding is that the only purpose of feature.experimental\n> is: \"we want to change the default of X this way, but we do not know\n> if we missed an important use case that would be harmed by such a\n> change, so let's see if those who volunteered to be guinea pigs by\n> setting feature.experimental to true find glitches in our thinking\n> that led us to think the change of the default is a good idea\".\n\nThis is the basic idea. \"Here are some new features that we think\nare good, please opt-in to test them out and find corner cases.\"\n\nThanks,\n-Stolee\n\n"},{"id":"380170","messageId":"1a2bf8e1-5cc1-c626-9dfc-dd6dc58fa0ad@gmail.com","threadId":"51508","inReplyTo":"CABPp-BGo5spDbk34YwAPGqT1Xgsr1dHm6OhxY1xKFQxQaotBig@mail.gmail.com","subject":"Re: [PATCH v3 5/5] repo-settings: create feature.experimental setting","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-08-08T20:49:54Z","receivedAt":"2019-08-08T20:49:57Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 8/8/2019 4:31 PM, Elijah Newren wrote:\n> On Thu, Aug 8, 2019 at 12:12 PM Derrick Stolee <stolee@gmail.com> wrote:\n>>\n>> On 8/8/2019 2:59 PM, Junio C Hamano wrote:\n>>> Elijah Newren <newren@gmail.com> writes:\n>>>\n>>>>> --- a/Documentation/config/merge.txt\n>>>>> +++ b/Documentation/config/merge.txt\n>>>>> @@ -54,7 +54,8 @@ merge.directoryRenames::\n>>>>>         moved into the new directory.  If set to \"conflict\", a conflict\n>>>>>         will be reported for such paths.  If merge.renames is false,\n>>>>>         merge.directoryRenames is ignored and treated as false.  Defaults\n>>>>> -       to \"conflict\".\n>>>>> +       to \"conflict\" unless `feature.experimental` is enabled and the\n>>>>> +       default is \"true\".\n>>>>\n>>>> I have a hard time parsing that changed sentence.  Perhaps something like:\n>>>>    ...unless `feature.experimental` is enabled in which case the\n>>>> default is \"true\".\n>>>> ?\n>>>\n>>> That reads better.\n>>>\n>>> But I am not sure about the wisdom of controlling between conflict\n>>> and true with this feature macro in the first place.\n>>>\n>>> Between \"conflict\" and \"true\", the former forces the end user to\n>>> verify (or allows the end user to veto) the auto resolution by the\n>>> heuristics and is always a safer if more cumbersome option.  It's\n>>> not like blindly trusting the directory rename heuristics is the\n>>> bright future for all users, is it?\n>>>\n>>> I would not set rerere.autoUpdate to true when feature.experimental\n>>> is set; for exactly the same reason, I do not find it reasonable to\n>>> set this to true with feature.experimental macro.\n>>\n>> OK. I can remove it from the feature.experimental variable.\n>>\n>> Shall I keep the enum logic and the use of repo-settings.c? I can split\n>> them out into a separate patch.\n> \n> Good question.  In part, I was looking at this ds/feature-macros\n> series because my cleanup-merge-api series[1] has some minor conflicts\n> with it.  I'm a little unsure what route I should take with my series\n> now.  Some choices:\n>   * keep this logic in your series in a separate patch, and have me\n> rebase my series on yours.\n>   * drop this logic from your series since it may not be needed\n> anymore, making our two series independent again.\n>   * move this logic into a separate patch of yours but making that\n> patch part of my series instead; that'd be easy with the enum logic,\n> but the repo-settings.c stuff appears to depend on your other\n> patches...\n> \n> Thoughts or preferences?\n\nI've done the work in my GGG PR [1] to split the commit into two,\nwith the merge.directoryRenames on top. I created a tag [2] at that\ncommit and I'll send my v4 without changes to merge.directoryRenames.\n\nI'll let you decide if you like placing the config in repo-settings\nor not (or want to adapt the enum logic in a different place).\n\nThanks,\n-Stolee\n\n[1] https://github.com/gitgitgadget/git/pull/292\n[2] https://github.com/derrickstolee/git/releases/tag/merge.directoryRenames\n"},{"id":"380404","messageId":"6bf3584a60aea5dac84c50674e0a1db01116f920.1565721461.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 2/6] t6501: use 'git gc' in quiet mode","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-13T18:37:45Z","receivedAt":"2019-08-13T18:37:48Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nt6501-freshen-objects.sh sends the standard error from\n'git gc' to a file and verifies that it is empty. This\nis intended as a way to ensure no warnings are written\nduring the operation. However, as the commit-graph is\nadded as a step to 'git gc', its progress will appear\nin the output.\n\nPass the '-q' argument to avoid a failing test case\nwhen progress is written.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/t6501-freshen-objects.sh | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/t/t6501-freshen-objects.sh b/t/t6501-freshen-objects.sh\nindex 033871ee5f..f30b4849b6 100755\n--- a/t/t6501-freshen-objects.sh\n+++ b/t/t6501-freshen-objects.sh\n@@ -137,7 +137,7 @@ test_expect_success 'do not complain about existing broken links (commit)' '\n \tsome message\n \tEOF\n \tcommit=$(git hash-object -t commit -w broken-commit) &&\n-\tgit gc 2>stderr &&\n+\tgit gc -q 2>stderr &&\n \tverbose git cat-file -e $commit &&\n \ttest_must_be_empty stderr\n '\n@@ -147,7 +147,7 @@ test_expect_success 'do not complain about existing broken links (tree)' '\n \t100644 blob 0000000000000000000000000000000000000003\tfoo\n \tEOF\n \ttree=$(git mktree --missing <broken-tree) &&\n-\tgit gc 2>stderr &&\n+\tgit gc -q 2>stderr &&\n \tgit cat-file -e $tree &&\n \ttest_must_be_empty stderr\n '\n@@ -162,7 +162,7 @@ test_expect_success 'do not complain about existing broken links (tag)' '\n \tthis is a broken tag\n \tEOF\n \ttag=$(git hash-object -t tag -w broken-tag) &&\n-\tgit gc 2>stderr &&\n+\tgit gc -q 2>stderr &&\n \tgit cat-file -e $tag &&\n \ttest_must_be_empty stderr\n '\n-- \ngitgitgadget\n\n"},{"id":"380405","messageId":"1a0b30b88a5f4eff45541e5eeffd65bbfc2cee31.1565721461.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 1/6] repo-settings: consolidate some config settings","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-13T18:37:43Z","receivedAt":"2019-08-13T18:37:49Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThere are a few important config settings that are not loaded\nduring git_default_config. These are instead loaded on-demand.\n\nCentralize these config options to a single scan, and store\nall of the values in a repo_settings struct. The values for\neach setting are initialized as negative to indicate \"unset\".\n\nThis centralization will be particularly important in a later\nchange to introduce \"meta\" config settings that change the\ndefaults for these config settings.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Makefile               |  1 +\n builtin/gc.c           | 12 +++++-------\n builtin/pack-objects.c |  8 ++++----\n commit-graph.c         |  6 +++---\n read-cache.c           | 11 ++++++-----\n repo-settings.c        | 25 +++++++++++++++++++++++++\n repository.h           | 14 ++++++++++++++\n 7 files changed, 58 insertions(+), 19 deletions(-)\n create mode 100644 repo-settings.c\n\ndiff --git a/Makefile b/Makefile\nindex 11ccea4071..032fe9b5f9 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -964,6 +964,7 @@ LIB_OBJS += refspec.o\n LIB_OBJS += ref-filter.o\n LIB_OBJS += remote.o\n LIB_OBJS += replace-object.o\n+LIB_OBJS += repo-settings.o\n LIB_OBJS += repository.o\n LIB_OBJS += rerere.o\n LIB_OBJS += resolve-undo.o\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex c18efadda5..4b8fbb9965 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -41,7 +41,6 @@ static int aggressive_depth = 50;\n static int aggressive_window = 250;\n static int gc_auto_threshold = 6700;\n static int gc_auto_pack_limit = 50;\n-static int gc_write_commit_graph;\n static int detach_auto = 1;\n static timestamp_t gc_log_expire_time;\n static const char *gc_log_expire = \"1.day.ago\";\n@@ -148,7 +147,6 @@ static void gc_config(void)\n \tgit_config_get_int(\"gc.aggressivedepth\", &aggressive_depth);\n \tgit_config_get_int(\"gc.auto\", &gc_auto_threshold);\n \tgit_config_get_int(\"gc.autopacklimit\", &gc_auto_pack_limit);\n-\tgit_config_get_bool(\"gc.writecommitgraph\", &gc_write_commit_graph);\n \tgit_config_get_bool(\"gc.autodetach\", &detach_auto);\n \tgit_config_get_expiry(\"gc.pruneexpire\", &prune_expire);\n \tgit_config_get_expiry(\"gc.worktreepruneexpire\", &prune_worktrees_expire);\n@@ -685,11 +683,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n \t\tclean_pack_garbage();\n \t}\n \n-\tif (gc_write_commit_graph &&\n-\t    write_commit_graph_reachable(get_object_directory(),\n-\t\t\t\t\t !quiet && !daemonized ? COMMIT_GRAPH_PROGRESS : 0,\n-\t\t\t\t\t NULL))\n-\t\treturn 1;\n+\tprepare_repo_settings(the_repository);\n+\tif (the_repository->settings.gc_write_commit_graph == 1)\n+\t\twrite_commit_graph_reachable(get_object_directory(),\n+\t\t\t\t\t     !quiet && !daemonized ? COMMIT_GRAPH_PROGRESS : 0,\n+\t\t\t\t\t     NULL);\n \n \tif (auto_gc && too_many_loose_objects())\n \t\twarning(_(\"There are too many unreachable loose objects; \"\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 267c562b1f..f9f7181f3f 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -2709,10 +2709,6 @@ static int git_pack_config(const char *k, const char *v, void *cb)\n \t\tuse_bitmap_index_default = git_config_bool(k, v);\n \t\treturn 0;\n \t}\n-\tif (!strcmp(k, \"pack.usesparse\")) {\n-\t\tsparse = git_config_bool(k, v);\n-\t\treturn 0;\n-\t}\n \tif (!strcmp(k, \"pack.threads\")) {\n \t\tdelta_search_threads = git_config_int(k, v);\n \t\tif (delta_search_threads < 0)\n@@ -3332,6 +3328,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \tread_replace_refs = 0;\n \n \tsparse = git_env_bool(\"GIT_TEST_PACK_SPARSE\", 0);\n+\tprepare_repo_settings(the_repository);\n+\tif (!sparse && the_repository->settings.pack_use_sparse != -1)\n+\t\tsparse = the_repository->settings.pack_use_sparse;\n+\n \treset_pack_idx_option(&pack_idx_opts);\n \tgit_config(git_pack_config, NULL);\n \ndiff --git a/commit-graph.c b/commit-graph.c\nindex b3c4de79b6..7854e491b2 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -466,7 +466,6 @@ static void prepare_commit_graph_one(struct repository *r, const char *obj_dir)\n static int prepare_commit_graph(struct repository *r)\n {\n \tstruct object_directory *odb;\n-\tint config_value;\n \n \tif (git_env_bool(GIT_TEST_COMMIT_GRAPH_DIE_ON_LOAD, 0))\n \t\tdie(\"dying as requested by the '%s' variable on commit-graph load!\",\n@@ -476,9 +475,10 @@ static int prepare_commit_graph(struct repository *r)\n \t\treturn !!r->objects->commit_graph;\n \tr->objects->commit_graph_attempted = 1;\n \n+\tprepare_repo_settings(r);\n+\n \tif (!git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) &&\n-\t    (repo_config_get_bool(r, \"core.commitgraph\", &config_value) ||\n-\t    !config_value))\n+\t    r->settings.core_commit_graph != 1)\n \t\t/*\n \t\t * This repository is not configured to use commit graphs, so\n \t\t * do not load one. (But report commit_graph_attempted anyway\ndiff --git a/read-cache.c b/read-cache.c\nindex c701f7f8b8..59dbebc15d 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -1599,16 +1599,17 @@ struct cache_entry *refresh_cache_entry(struct index_state *istate,\n \n #define INDEX_FORMAT_DEFAULT 3\n \n-static unsigned int get_index_format_default(void)\n+static unsigned int get_index_format_default(struct repository *r)\n {\n \tchar *envversion = getenv(\"GIT_INDEX_VERSION\");\n \tchar *endp;\n-\tint value;\n \tunsigned int version = INDEX_FORMAT_DEFAULT;\n \n \tif (!envversion) {\n-\t\tif (!git_config_get_int(\"index.version\", &value))\n-\t\t\tversion = value;\n+\t\tprepare_repo_settings(r);\n+\n+\t\tif (r->settings.index_version >= 0)\n+\t\t\tversion = r->settings.index_version;\n \t\tif (version < INDEX_FORMAT_LB || INDEX_FORMAT_UB < version) {\n \t\t\twarning(_(\"index.version set, but the value is invalid.\\n\"\n \t\t\t\t  \"Using version %i\"), INDEX_FORMAT_DEFAULT);\n@@ -2765,7 +2766,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t}\n \n \tif (!istate->version) {\n-\t\tistate->version = get_index_format_default();\n+\t\tistate->version = get_index_format_default(the_repository);\n \t\tif (git_env_bool(\"GIT_TEST_SPLIT_INDEX\", 0))\n \t\t\tinit_split_index(istate);\n \t}\ndiff --git a/repo-settings.c b/repo-settings.c\nnew file mode 100644\nindex 0000000000..309577f6bc\n--- /dev/null\n+++ b/repo-settings.c\n@@ -0,0 +1,25 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"repository.h\"\n+\n+void prepare_repo_settings(struct repository *r)\n+{\n+\tint value;\n+\n+\tif (r->settings.initialized)\n+\t\treturn;\n+\n+\t/* Defaults */\n+\tmemset(&r->settings, -1, sizeof(r->settings));\n+\n+\tif (!repo_config_get_bool(r, \"core.commitgraph\", &value))\n+\t\tr->settings.core_commit_graph = value;\n+\tif (!repo_config_get_bool(r, \"gc.writecommitgraph\", &value))\n+\t\tr->settings.gc_write_commit_graph = value;\n+\n+\tif (!repo_config_get_bool(r, \"index.version\", &value))\n+\t\tr->settings.index_version = value;\n+\n+\tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n+\t\tr->settings.pack_use_sparse = value;\n+}\ndiff --git a/repository.h b/repository.h\nindex 4fb6a5885f..cc285ad327 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -11,6 +11,17 @@ struct pathspec;\n struct raw_object_store;\n struct submodule_cache;\n \n+struct repo_settings {\n+\tint initialized;\n+\n+\tint core_commit_graph;\n+\tint gc_write_commit_graph;\n+\n+\tint index_version;\n+\n+\tint pack_use_sparse;\n+};\n+\n struct repository {\n \t/* Environment */\n \t/*\n@@ -72,6 +83,8 @@ struct repository {\n \t */\n \tchar *submodule_prefix;\n \n+\tstruct repo_settings settings;\n+\n \t/* Subsystems */\n \t/*\n \t * Repository's config which contains key-value pairs from the usual\n@@ -157,5 +170,6 @@ int repo_read_index_unmerged(struct repository *);\n  */\n void repo_update_index_if_able(struct repository *, struct lock_file *);\n \n+void prepare_repo_settings(struct repository *r);\n \n #endif /* REPOSITORY_H */\n-- \ngitgitgadget\n\n"},{"id":"380406","messageId":"ec0abffd8b8d929599fbacde18a7e98f49a4c63b.1565721461.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 4/6] repo-settings: parse core.untrackedCache","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-13T18:37:46Z","receivedAt":"2019-08-13T18:37:49Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe core.untrackedCache config setting is slightly complicated,\nso clarify its use and centralize its parsing into the repo\nsettings.\n\nThe default value is \"keep\" (returned as -1), which persists the\nuntracked cache if it exists.\n\nIf the value is set as \"false\" (returned as 0), then remove the\nuntracked cache if it exists.\n\nIf the value is set as \"true\" (returned as 1), then write the\nuntracked cache and persist it.\n\nInstead of relying on magic values of -1, 0, and 1, split these\noptions into an enum. This allows the use of \"-1\" as a\ndefault value. After parsing the config options, if the value is\nunset we can initialize it to UNTRACKED_CACHE_KEEP.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/update-index.c |  6 ++++--\n config.c               | 24 ------------------------\n read-cache.c           | 19 +++++++++----------\n repo-settings.c        | 19 +++++++++++++++++++\n repository.h           |  8 ++++++++\n 5 files changed, 40 insertions(+), 36 deletions(-)\n\ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex dff2f4b837..49302d98c5 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -966,6 +966,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \tstruct parse_opt_ctx_t ctx;\n \tstrbuf_getline_fn getline_fn;\n \tint parseopt_state = PARSE_OPT_UNKNOWN;\n+\tstruct repository *r = the_repository;\n \tstruct option options[] = {\n \t\tOPT_BIT('q', NULL, &refresh_args.flags,\n \t\t\tN_(\"continue refresh even when index needs update\"),\n@@ -1180,11 +1181,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\tremove_split_index(&the_index);\n \t}\n \n+\tprepare_repo_settings(r);\n \tswitch (untracked_cache) {\n \tcase UC_UNSPECIFIED:\n \t\tbreak;\n \tcase UC_DISABLE:\n-\t\tif (git_config_get_untracked_cache() == 1)\n+\t\tif (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE)\n \t\t\twarning(_(\"core.untrackedCache is set to true; \"\n \t\t\t\t  \"remove or change it, if you really want to \"\n \t\t\t\t  \"disable the untracked cache\"));\n@@ -1196,7 +1198,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)\n \t\treturn !test_if_untracked_cache_is_supported();\n \tcase UC_ENABLE:\n \tcase UC_FORCE:\n-\t\tif (git_config_get_untracked_cache() == 0)\n+\t\tif (r->settings.core_untracked_cache == UNTRACKED_CACHE_REMOVE)\n \t\t\twarning(_(\"core.untrackedCache is set to false; \"\n \t\t\t\t  \"remove or change it, if you really want to \"\n \t\t\t\t  \"enable the untracked cache\"));\ndiff --git a/config.c b/config.c\nindex faa57e436c..3241dbc54d 100644\n--- a/config.c\n+++ b/config.c\n@@ -2277,30 +2277,6 @@ int git_config_get_expiry_in_days(const char *key, timestamp_t *expiry, timestam\n \treturn -1; /* thing exists but cannot be parsed */\n }\n \n-int git_config_get_untracked_cache(void)\n-{\n-\tint val = -1;\n-\tconst char *v;\n-\n-\t/* Hack for test programs like test-dump-untracked-cache */\n-\tif (ignore_untracked_cache_config)\n-\t\treturn -1;\n-\n-\tif (!git_config_get_maybe_bool(\"core.untrackedcache\", &val))\n-\t\treturn val;\n-\n-\tif (!git_config_get_value(\"core.untrackedcache\", &v)) {\n-\t\tif (!strcasecmp(v, \"keep\"))\n-\t\t\treturn -1;\n-\n-\t\terror(_(\"unknown core.untrackedCache value '%s'; \"\n-\t\t\t\"using 'keep' default value\"), v);\n-\t\treturn -1;\n-\t}\n-\n-\treturn -1; /* default value */\n-}\n-\n int git_config_get_split_index(void)\n {\n \tint val;\ndiff --git a/read-cache.c b/read-cache.c\nindex 59dbebc15d..7a07286164 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -1845,18 +1845,17 @@ static void check_ce_order(struct index_state *istate)\n \n static void tweak_untracked_cache(struct index_state *istate)\n {\n-\tswitch (git_config_get_untracked_cache()) {\n-\tcase -1: /* keep: do nothing */\n-\t\tbreak;\n-\tcase 0: /* false */\n+\tstruct repository *r = the_repository;\n+\n+\tprepare_repo_settings(r);\n+\n+\tif (r->settings.core_untracked_cache  == UNTRACKED_CACHE_REMOVE) {\n \t\tremove_untracked_cache(istate);\n-\t\tbreak;\n-\tcase 1: /* true */\n-\t\tadd_untracked_cache(istate);\n-\t\tbreak;\n-\tdefault: /* unknown value: do nothing */\n-\t\tbreak;\n+\t\treturn;\n \t}\n+\n+\tif (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE)\n+\t\tadd_untracked_cache(istate);\n }\n \n static void tweak_split_index(struct index_state *istate)\ndiff --git a/repo-settings.c b/repo-settings.c\nindex d00b675687..abbc6566f8 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -7,6 +7,7 @@\n void prepare_repo_settings(struct repository *r)\n {\n \tint value;\n+\tchar *strval;\n \n \tif (r->settings.initialized)\n \t\treturn;\n@@ -23,7 +24,25 @@ void prepare_repo_settings(struct repository *r)\n \n \tif (!repo_config_get_bool(r, \"index.version\", &value))\n \t\tr->settings.index_version = value;\n+\tif (!repo_config_get_maybe_bool(r, \"core.untrackedcache\", &value)) {\n+\t\tif (value == 0)\n+\t\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_REMOVE;\n+\t\telse\n+\t\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_WRITE;\n+\t} else if (!repo_config_get_string(r, \"core.untrackedcache\", &strval)) {\n+\t\tif (!strcasecmp(strval, \"keep\"))\n+\t\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;\n+\n+\t\tfree(strval);\n+\t}\n+\n \n \tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n \t\tr->settings.pack_use_sparse = value;\n+\n+\t/* Hack for test programs like test-dump-untracked-cache */\n+\tif (ignore_untracked_cache_config)\n+\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;\n+\telse\n+\t\tUPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);\n }\ndiff --git a/repository.h b/repository.h\nindex cc285ad327..cf7ff0778c 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -11,6 +11,13 @@ struct pathspec;\n struct raw_object_store;\n struct submodule_cache;\n \n+enum untracked_cache_setting {\n+\tUNTRACKED_CACHE_UNSET = -1,\n+\tUNTRACKED_CACHE_REMOVE = 0,\n+\tUNTRACKED_CACHE_KEEP = 1,\n+\tUNTRACKED_CACHE_WRITE = 2\n+};\n+\n struct repo_settings {\n \tint initialized;\n \n@@ -18,6 +25,7 @@ struct repo_settings {\n \tint gc_write_commit_graph;\n \n \tint index_version;\n+\tenum untracked_cache_setting core_untracked_cache;\n \n \tint pack_use_sparse;\n };\n-- \ngitgitgadget\n\n"},{"id":"380407","messageId":"082fc572884ec9e91e3bdd76dd2a7801f28b414a.1565721461.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 5/6] repo-settings: create feature.manyFiles setting","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-13T18:37:47Z","receivedAt":"2019-08-13T18:37:51Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe feature.manyFiles setting is suitable for repos with many\nfiles in the working directory. By setting index.version=4 and\ncore.untrackedCache=true, commands such as 'git status' should\nimprove.\n\nWhile adding this setting, modify the index version precedence\ntests to check how this setting overrides the default for\nindex.version is unset.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config.txt         |  2 ++\n Documentation/config/core.txt    |  4 +++-\n Documentation/config/feature.txt | 15 +++++++++++++++\n Documentation/config/index.txt   |  1 +\n repo-settings.c                  |  5 ++++-\n t/t1600-index.sh                 | 31 ++++++++++++++++++++++++++-----\n 6 files changed, 51 insertions(+), 7 deletions(-)\n create mode 100644 Documentation/config/feature.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex e3f5bc3396..77f3b1486b 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -345,6 +345,8 @@ include::config/difftool.txt[]\n \n include::config/fastimport.txt[]\n \n+include::config/feature.txt[]\n+\n include::config/fetch.txt[]\n \n include::config/format.txt[]\ndiff --git a/Documentation/config/core.txt b/Documentation/config/core.txt\nindex e66d79fd76..852d2ba37a 100644\n--- a/Documentation/config/core.txt\n+++ b/Documentation/config/core.txt\n@@ -86,7 +86,9 @@ core.untrackedCache::\n \tit will automatically be removed, if set to `false`. Before\n \tsetting it to `true`, you should check that mtime is working\n \tproperly on your system.\n-\tSee linkgit:git-update-index[1]. `keep` by default.\n+\tSee linkgit:git-update-index[1]. `keep` by default, unless\n+\t`feature.manyFiles` is enabled which sets this setting to\n+\t`true` by default.\n \n core.checkStat::\n \tWhen missing or is set to `default`, many fields in the stat\ndiff --git a/Documentation/config/feature.txt b/Documentation/config/feature.txt\nnew file mode 100644\nindex 0000000000..8ea198a642\n--- /dev/null\n+++ b/Documentation/config/feature.txt\n@@ -0,0 +1,15 @@\n+feature.*::\n+\tThe config settings that start with `feature.` modify the defaults of\n+\ta group of other config settings. These groups are created by the Git\n+\tdeveloper community as recommended defaults and are subject to change.\n+\tIn particular, new config options may be added with different defaults.\n+\n+feature.manyFiles::\n+\tEnable config options that optimize for repos with many files in the\n+\tworking directory. With many files, commands such as `git status` and\n+\t`git checkout` may be slow and these new defaults improve performance:\n++\n+* `index.version=4` enables path-prefix compression in the index.\n++\n+* `core.untrackedCache=true` enables the untracked cache. This setting assumes\n+that mtime is working on your machine.\ndiff --git a/Documentation/config/index.txt b/Documentation/config/index.txt\nindex f181503041..7cb50b37e9 100644\n--- a/Documentation/config/index.txt\n+++ b/Documentation/config/index.txt\n@@ -24,3 +24,4 @@ index.threads::\n index.version::\n \tSpecify the version with which new index files should be\n \tinitialized.  This does not affect existing repositories.\n+\tIf `feature.manyFiles` is enabled, then the default is 4.\ndiff --git a/repo-settings.c b/repo-settings.c\nindex abbc6566f8..d5bf9069f4 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -36,9 +36,12 @@ void prepare_repo_settings(struct repository *r)\n \t\tfree(strval);\n \t}\n \n-\n \tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n \t\tr->settings.pack_use_sparse = value;\n+\tif (!repo_config_get_bool(r, \"feature.manyfiles\", &value) && value) {\n+\t\tUPDATE_DEFAULT_BOOL(r->settings.index_version, 4);\n+\t\tUPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE);\n+\t}\n \n \t/* Hack for test programs like test-dump-untracked-cache */\n \tif (ignore_untracked_cache_config)\ndiff --git a/t/t1600-index.sh b/t/t1600-index.sh\nindex 42962ed7d4..c77721b580 100755\n--- a/t/t1600-index.sh\n+++ b/t/t1600-index.sh\n@@ -59,17 +59,38 @@ test_expect_success 'out of bounds index.version issues warning' '\n \t)\n '\n \n-test_expect_success 'GIT_INDEX_VERSION takes precedence over config' '\n+test_index_version () {\n+\tINDEX_VERSION_CONFIG=$1 &&\n+\tFEATURE_MANY_FILES=$2 &&\n+\tENV_VAR_VERSION=$3\n+\tEXPECTED_OUTPUT_VERSION=$4 &&\n \t(\n \t\trm -f .git/index &&\n-\t\tGIT_INDEX_VERSION=4 &&\n-\t\texport GIT_INDEX_VERSION &&\n-\t\tgit config --add index.version 2 &&\n+\t\trm -f .git/config &&\n+\t\tif test \"$INDEX_VERSION_CONFIG\" -ne 0\n+\t\tthen\n+\t\t\tgit config --add index.version $INDEX_VERSION_CONFIG\n+\t\tfi &&\n+\t\tgit config --add feature.manyFiles $FEATURE_MANY_FILES\n+\t\tif test \"$ENV_VAR_VERSION\" -ne 0\n+\t\tthen\n+\t\t\tGIT_INDEX_VERSION=$ENV_VAR_VERSION &&\n+\t\t\texport GIT_INDEX_VERSION\n+\t\telse\n+\t\t\tunset GIT_INDEX_VERSION\n+\t\tfi &&\n \t\tgit add a 2>&1 &&\n-\t\techo 4 >expect &&\n+\t\techo $EXPECTED_OUTPUT_VERSION >expect &&\n \t\ttest-tool index-version <.git/index >actual &&\n \t\ttest_cmp expect actual\n \t)\n+}\n+\n+test_expect_success 'index version config precedence' '\n+\ttest_index_version 2 false 4 4 &&\n+\ttest_index_version 2 true 0 2 &&\n+\ttest_index_version 0 true 0 4 &&\n+\ttest_index_version 0 true 2 2\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"380408","messageId":"pull.292.v4.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v4 0/6] Create 'feature.*' config area and some centralized config parsing","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-13T18:37:42Z","receivedAt":"2019-08-13T18:37:52Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"This is a brand-new thread to replace ds/early-access. The discussion on\nthat thread was very helpful [1].\n\nWith this in mind, I propose instead a set of \"feature.*\" config settings\nthat form groups of \"community recommended\" settings (with some caveats). In\nthe space below, I'll list a set of possible feature names and the implied\nconfig options.\n\nFirst, the main two categories we've discussed so far: many commits and many\nfiles. These two feature sets are for when your repo is large in one of\nthese dimensions. Perhaps there are other settings to include in these?\n\nfeature.manyFiles:\n    index.version = 4\n    core.untrackedCache = true\n\nUPDATE: Instead of \"feature.manyCommits\", this series includes making\ncore.commitGraph and gc.writeCommitGraph true by default!\n\nThe other category that has been discussed already is that of \"experimental\nfeatures that we generally think are helpful but change behavior slightly in\nsome cases\".\n\nfeature.experimental:\n    pack.useSparse = true\n    merge.directoryRenames = true\n    fetch.negotiationAlgorithm = skipping\n\nSpecifically, this setting is for config values we are not sure will ever be\non by default, but additional testing is needed to be sure. This is\ndifferent than a possible 'feature.preview' setting that would include\nconfig settings that we are committed to updating the defaults in a future\nrelease. There are many ways we can take this idea in the future (including\nmore additions to these categories).\n\nThanks, -Stolee\n\nUpdates in V2: I'm responding to Dscho's comments early, as they were very\nvaluable.\n\n * Rearranged how we are checking for the feature.* settings, so if one\n   layer turns the setting on and a later layer turns it off, we do not\n   adjust the defaults.\n   \n   \n * Switched to using enums for the non-boolean config options.\n   \n   \n * Placed the repo_settings struct directly in the repository struct.\n   \n   \n * All struct members are initialized to -1 using memset.\n   \n   \n * The config changes around directory rename detection is greatly\n   simplified.\n   \n   \n\nUpdates in V3: These are more Dscho comments, and are quite small.\n\n * Remove repo-settings.h by placing the struct and function definitions in\n   repository.h.\n   \n   \n * Move the initialization int into \"struct repo_settings\".\n   \n   \n\nUpdates in V4:\n\n * Remove feature.manyCommits in favor of turning commit-graph on by\n   default.\n   \n   \n * Update docs and 0 constant in feature.experimental around\n   merge.directoryRenames.\n   \n   \n\n[1] https://public-inbox.org/git/pull.254.git.gitgitgadget@gmail.com/\n\nDerrick Stolee (6):\n  repo-settings: consolidate some config settings\n  t6501: use 'git gc' in quiet mode\n  commit-graph: turn on commit-graph by default\n  repo-settings: parse core.untrackedCache\n  repo-settings: create feature.manyFiles setting\n  repo-settings: create feature.experimental setting\n\n Documentation/config.txt             |  2 +\n Documentation/config/core.txt        |  6 ++-\n Documentation/config/feature.txt     | 29 +++++++++++++\n Documentation/config/fetch.txt       |  3 +-\n Documentation/config/gc.txt          |  2 +-\n Documentation/config/index.txt       |  1 +\n Documentation/config/pack.txt        |  3 +-\n Makefile                             |  1 +\n builtin/gc.c                         | 12 +++---\n builtin/pack-objects.c               |  8 ++--\n builtin/update-index.c               |  6 ++-\n commit-graph.c                       |  6 +--\n config.c                             | 24 -----------\n fetch-negotiator.c                   | 25 +++++------\n fetch-negotiator.h                   |  5 ++-\n fetch-pack.c                         | 11 +++--\n read-cache.c                         | 30 ++++++-------\n repo-settings.c                      | 64 ++++++++++++++++++++++++++++\n repository.h                         | 30 +++++++++++++\n t/t0410-partial-clone.sh             |  2 +-\n t/t1600-index.sh                     | 31 +++++++++++---\n t/t5307-pack-missing-commit.sh       |  4 +-\n t/t5324-split-commit-graph.sh        |  2 +\n t/t5552-skipping-fetch-negotiator.sh | 23 ----------\n t/t6011-rev-list-with-bad-commit.sh  |  2 +-\n t/t6501-freshen-objects.sh           |  6 +--\n 26 files changed, 223 insertions(+), 115 deletions(-)\n create mode 100644 Documentation/config/feature.txt\n create mode 100644 repo-settings.c\n\n\nbase-commit: 9c9b961d7eb15fb583a2a812088713a68a85f1c0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-292%2Fderrickstolee%2Frepo-settings%2Fhead-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-292/derrickstolee/repo-settings/head-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/292\n\nRange-diff vs v3:\n\n 1:  4d0db57ecb ! 1:  1a0b30b88a repo-settings: consolidate some config settings\n     @@ -184,14 +184,6 @@\n       diff --git a/repository.h b/repository.h\n       --- a/repository.h\n       +++ b/repository.h\n     -@@\n     - #include \"path.h\"\n     - \n     - struct config_set;\n     -+struct repo_settings;\n     - struct git_hash_algo;\n     - struct index_state;\n     - struct lock_file;\n      @@\n       struct raw_object_store;\n       struct submodule_cache;\n 2:  c0129066a0 < -:  ---------- repo-settings: add feature.manyCommits setting\n -:  ---------- > 2:  6bf3584a60 t6501: use 'git gc' in quiet mode\n -:  ---------- > 3:  651e2d526b commit-graph: turn on commit-graph by default\n 3:  b0a2d0e188 ! 4:  ec0abffd8b repo-settings: parse core.untrackedCache\n     @@ -154,19 +154,12 @@\n       \n       \tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n       \t\tr->settings.pack_use_sparse = value;\n     --\n     -+\t\n     - \tif (!repo_config_get_bool(r, \"feature.manycommits\", &value) && value) {\n     - \t\tUPDATE_DEFAULT(r->settings.core_commit_graph, 1);\n     - \t\tUPDATE_DEFAULT(r->settings.gc_write_commit_graph, 1);\n     - \t}\n      +\n      +\t/* Hack for test programs like test-dump-untracked-cache */\n      +\tif (ignore_untracked_cache_config)\n      +\t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;\n      +\telse\n     -+\t\tUPDATE_DEFAULT(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);\n     -+\n     ++\t\tUPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);\n       }\n      \n       diff --git a/repository.h b/repository.h\n 4:  0d4774d45f ! 5:  082fc57288 repo-settings: create feature.manyFiles setting\n     @@ -13,6 +13,19 @@\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n      \n     + diff --git a/Documentation/config.txt b/Documentation/config.txt\n     + --- a/Documentation/config.txt\n     + +++ b/Documentation/config.txt\n     +@@\n     + \n     + include::config/fastimport.txt[]\n     + \n     ++include::config/feature.txt[]\n     ++\n     + include::config/fetch.txt[]\n     + \n     + include::config/format.txt[]\n     +\n       diff --git a/Documentation/config/core.txt b/Documentation/config/core.txt\n       --- a/Documentation/config/core.txt\n       +++ b/Documentation/config/core.txt\n     @@ -29,15 +42,15 @@\n       \tWhen missing or is set to `default`, many fields in the stat\n      \n       diff --git a/Documentation/config/feature.txt b/Documentation/config/feature.txt\n     - --- a/Documentation/config/feature.txt\n     + new file mode 100644\n     + --- /dev/null\n       +++ b/Documentation/config/feature.txt\n      @@\n     - * `core.commitGraph=true` enables reading the commit-graph file.\n     - +\n     - * `gc.writeCommitGraph=true` enables writing the commit-graph file during\n     --garbage collection.\n     - \\ No newline at end of file\n     -+garbage collection.\n     ++feature.*::\n     ++\tThe config settings that start with `feature.` modify the defaults of\n     ++\ta group of other config settings. These groups are created by the Git\n     ++\tdeveloper community as recommended defaults and are subject to change.\n     ++\tIn particular, new config options may be added with different defaults.\n      +\n      +feature.manyFiles::\n      +\tEnable config options that optimize for repos with many files in the\n     @@ -48,7 +61,6 @@\n      ++\n      +* `core.untrackedCache=true` enables the untracked cache. This setting assumes\n      +that mtime is working on your machine.\n     - \\ No newline at end of file\n      \n       diff --git a/Documentation/config/index.txt b/Documentation/config/index.txt\n       --- a/Documentation/config/index.txt\n     @@ -63,12 +75,15 @@\n       --- a/repo-settings.c\n       +++ b/repo-settings.c\n      @@\n     - \t\tUPDATE_DEFAULT(r->settings.core_commit_graph, 1);\n     - \t\tUPDATE_DEFAULT(r->settings.gc_write_commit_graph, 1);\n     + \t\tfree(strval);\n       \t}\n     + \n     +-\n     + \tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n     + \t\tr->settings.pack_use_sparse = value;\n      +\tif (!repo_config_get_bool(r, \"feature.manyfiles\", &value) && value) {\n     -+\t\tUPDATE_DEFAULT(r->settings.index_version, 4);\n     -+\t\tUPDATE_DEFAULT(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE);\n     ++\t\tUPDATE_DEFAULT_BOOL(r->settings.index_version, 4);\n     ++\t\tUPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE);\n      +\t}\n       \n       \t/* Hack for test programs like test-dump-untracked-cache */\n 5:  2e153fac22 ! 6:  f30e25fcd6 repo-settings: create feature.experimental setting\n     @@ -10,8 +10,6 @@\n      \n          * 'pack.useSparse=true'\n      \n     -    * 'merge.directoryRenames=true'\n     -\n          * 'fetch.negotiationAlgorithm=skipping'\n      \n          In the case of fetch.negotiationAlgorithm, the existing logic\n     @@ -41,15 +39,12 @@\n      +* `pack.useSparse=true` uses a new algorithm when constructing a pack-file\n      +which can improve `git push` performance in repos with many files.\n      ++\n     -+* `merge.directoryRenames=true` uses a new algorithm for detecting renames by\n     -+using entire directories at a time instead of single files at a time.\n     -++\n      +* `fetch.negotiationAlgorithm=skipping` may improve fetch negotiation times by\n      +skipping more commits at a time, reducing the number of round trips.\n      +\n     - feature.manyCommits::\n     - \tEnable config options that optimize for repos with many commits. This\n     - \tsetting is recommended for repos with at least 100,000 commits. The\n     + feature.manyFiles::\n     + \tEnable config options that optimize for repos with many files in the\n     + \tworking directory. With many files, commands such as `git status` and\n      \n       diff --git a/Documentation/config/fetch.txt b/Documentation/config/fetch.txt\n       --- a/Documentation/config/fetch.txt\n     @@ -65,20 +60,6 @@\n       +\n       See also the `--negotiation-tip` option for linkgit:git-fetch[1].\n      \n     - diff --git a/Documentation/config/merge.txt b/Documentation/config/merge.txt\n     - --- a/Documentation/config/merge.txt\n     - +++ b/Documentation/config/merge.txt\n     -@@\n     - \tmoved into the new directory.  If set to \"conflict\", a conflict\n     - \twill be reported for such paths.  If merge.renames is false,\n     - \tmerge.directoryRenames is ignored and treated as false.  Defaults\n     --\tto \"conflict\".\n     -+\tto \"conflict\" unless `feature.experimental` is enabled and the\n     -+\tdefault is \"true\".\n     - \n     - merge.renormalize::\n     - \tTell Git that canonical representation of files in the\n     -\n       diff --git a/Documentation/config/pack.txt b/Documentation/config/pack.txt\n       --- a/Documentation/config/pack.txt\n       +++ b/Documentation/config/pack.txt\n     @@ -214,38 +195,6 @@\n       \tgit_config(fetch_pack_config_cb, NULL);\n       }\n      \n     - diff --git a/merge-recursive.c b/merge-recursive.c\n     - --- a/merge-recursive.c\n     - +++ b/merge-recursive.c\n     -@@\n     - \t\topt->merge_detect_rename = git_config_rename(\"merge.renames\", value);\n     - \t\tfree(value);\n     - \t}\n     --\tif (!git_config_get_string(\"merge.directoryrenames\", &value)) {\n     --\t\tint boolval = git_parse_maybe_bool(value);\n     --\t\tif (0 <= boolval) {\n     --\t\t\topt->detect_directory_renames = boolval ? 2 : 0;\n     --\t\t} else if (!strcasecmp(value, \"conflict\")) {\n     --\t\t\topt->detect_directory_renames = 1;\n     --\t\t} /* avoid erroring on values from future versions of git */\n     --\t\tfree(value);\n     --\t}\n     - \tgit_config(git_xmerge_config, NULL);\n     - }\n     - \n     -@@\n     - \topt->diff_detect_rename = -1;\n     - \topt->merge_detect_rename = -1;\n     - \topt->detect_directory_renames = 1;\n     -+\n     -+\tprepare_repo_settings(repo);\n     -+\tif (repo->settings.merge_directory_renames >= 0)\n     -+\t\topt->detect_directory_renames = repo->settings.merge_directory_renames;\n     -+\n     - \tmerge_recursive_config(opt);\n     - \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n     - \tif (merge_verbosity)\n     -\n       diff --git a/repo-settings.c b/repo-settings.c\n       --- a/repo-settings.c\n       +++ b/repo-settings.c\n     @@ -253,39 +202,31 @@\n       \t\tfree(strval);\n       \t}\n       \n     -+\tif (!repo_config_get_maybe_bool(r, \"merge.directoryrenames\", &value))\n     -+\t\tr->settings.merge_directory_renames = value ? MERGE_DIRECTORY_RENAMES_TRUE : 0;\n     -+\telse if (!repo_config_get_string(r, \"merge.directoryrenames\", &strval)) {\n     -+\t\tif (!strcasecmp(strval, \"conflict\"))\n     -+\t\t\tr->settings.merge_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n     -+\t}\n      +\tif (!repo_config_get_string(r, \"fetch.negotiationalgorithm\", &strval)) {\n      +\t\tif (!strcasecmp(strval, \"skipping\"))\n      +\t\t\tr->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;\n      +\t\telse\n      +\t\t\tr->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT;\n      +\t}\n     - \n     ++\n       \tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n       \t\tr->settings.pack_use_sparse = value;\n     -@@\n     - \t\tUPDATE_DEFAULT(r->settings.index_version, 4);\n     - \t\tUPDATE_DEFAULT(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE);\n     + \tif (!repo_config_get_bool(r, \"feature.manyfiles\", &value) && value) {\n     + \t\tUPDATE_DEFAULT_BOOL(r->settings.index_version, 4);\n     + \t\tUPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE);\n       \t}\n      +\tif (!repo_config_get_bool(r, \"feature.experimental\", &value) && value) {\n     -+\t\tUPDATE_DEFAULT(r->settings.pack_use_sparse, 1);\n     -+\t\tUPDATE_DEFAULT(r->settings.merge_directory_renames, MERGE_DIRECTORY_RENAMES_TRUE);\n     -+\t\tUPDATE_DEFAULT(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_SKIPPING);\n     ++\t\tUPDATE_DEFAULT_BOOL(r->settings.pack_use_sparse, 1);\n     ++\t\tUPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_SKIPPING);\n      +\t}\n       \n       \t/* Hack for test programs like test-dump-untracked-cache */\n       \tif (ignore_untracked_cache_config)\n     -@@\n     + \t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;\n       \telse\n     - \t\tUPDATE_DEFAULT(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);\n     - \n     -+\tUPDATE_DEFAULT(r->settings.merge_directory_renames, MERGE_DIRECTORY_RENAMES_CONFLICT);\n     -+\tUPDATE_DEFAULT(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_DEFAULT);\n     + \t\tUPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);\n     ++\n     ++\tUPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_DEFAULT);\n       }\n      \n       diff --git a/repository.h b/repository.h\n     @@ -295,13 +236,6 @@\n       \tUNTRACKED_CACHE_WRITE = 2\n       };\n       \n     -+enum merge_directory_renames_setting {\n     -+\tMERGE_DIRECTORY_RENAMES_UNSET = -1,\n     -+\tMERGE_DIRECTORY_RENAMES_NONE = 0,\n     -+\tMERGE_DIRECTORY_RENAMES_CONFLICT = 1,\n     -+\tMERGE_DIRECTORY_RENAMES_TRUE = 2,\n     -+};\n     -+\n      +enum fetch_negotiation_setting {\n      +\tFETCH_NEGOTIATION_UNSET = -1,\n      +\tFETCH_NEGOTIATION_NONE = 0,\n     @@ -316,7 +250,6 @@\n       \tenum untracked_cache_setting core_untracked_cache;\n       \n       \tint pack_use_sparse;\n     -+\tenum merge_directory_renames_setting merge_directory_renames;\n      +\tenum fetch_negotiation_setting fetch_negotiation_algorithm;\n       };\n       \n\n-- \ngitgitgadget\n"},{"id":"380409","messageId":"651e2d526b2a3f6c63c64cfe1f049eef94d39c07.1565721461.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 3/6] commit-graph: turn on commit-graph by default","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-13T18:37:45Z","receivedAt":"2019-08-13T18:37:52Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe commit-graph feature has seen a lot of activity in the past\nyear or so since it was introduced. The feature is a critical\nperformance enhancement for medium- to large-sized repos, and\ndoes not significantly hurt small repos.\n\nChange the defaults for core.commitGraph and gc.writeCommitGraph\nto true so users benefit from this feature by default.\n\nThere are several places in the test suite where the environment\nvariable GIT_TEST_COMMIT_GRAPH is disabled to avoid reading a\ncommit-graph, if it exists. The config option overrides the\nenvironment, so swap these. Some GIT_TEST_COMMIT_GRAPH assignments\nremain, and those are to avoid writing a commit-graph when a new\ncommit is created.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/core.txt       | 2 +-\n Documentation/config/gc.txt         | 2 +-\n repo-settings.c                     | 4 ++++\n t/t0410-partial-clone.sh            | 2 +-\n t/t5307-pack-missing-commit.sh      | 4 ++--\n t/t5324-split-commit-graph.sh       | 2 ++\n t/t6011-rev-list-with-bad-commit.sh | 2 +-\n 7 files changed, 12 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config/core.txt b/Documentation/config/core.txt\nindex 75538d27e7..e66d79fd76 100644\n--- a/Documentation/config/core.txt\n+++ b/Documentation/config/core.txt\n@@ -577,7 +577,7 @@ the `GIT_NOTES_REF` environment variable.  See linkgit:git-notes[1].\n \n core.commitGraph::\n \tIf true, then git will read the commit-graph file (if it exists)\n-\tto parse the graph structure of commits. Defaults to false. See\n+\tto parse the graph structure of commits. Defaults to true. See\n \tlinkgit:git-commit-graph[1] for more information.\n \n core.useReplaceRefs::\ndiff --git a/Documentation/config/gc.txt b/Documentation/config/gc.txt\nindex 02b92b18b5..00ea0a678e 100644\n--- a/Documentation/config/gc.txt\n+++ b/Documentation/config/gc.txt\n@@ -63,7 +63,7 @@ gc.writeCommitGraph::\n \tIf true, then gc will rewrite the commit-graph file when\n \tlinkgit:git-gc[1] is run. When using `git gc --auto`\n \tthe commit-graph will be updated if housekeeping is\n-\trequired. Default is false. See linkgit:git-commit-graph[1]\n+\trequired. Default is true. See linkgit:git-commit-graph[1]\n \tfor details.\n \n gc.logExpiry::\ndiff --git a/repo-settings.c b/repo-settings.c\nindex 309577f6bc..d00b675687 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -2,6 +2,8 @@\n #include \"config.h\"\n #include \"repository.h\"\n \n+#define UPDATE_DEFAULT_BOOL(s,v) do { if (s == -1) { s = v; } } while(0)\n+\n void prepare_repo_settings(struct repository *r)\n {\n \tint value;\n@@ -16,6 +18,8 @@ void prepare_repo_settings(struct repository *r)\n \t\tr->settings.core_commit_graph = value;\n \tif (!repo_config_get_bool(r, \"gc.writecommitgraph\", &value))\n \t\tr->settings.gc_write_commit_graph = value;\n+\tUPDATE_DEFAULT_BOOL(r->settings.core_commit_graph, 1);\n+\tUPDATE_DEFAULT_BOOL(r->settings.gc_write_commit_graph, 1);\n \n \tif (!repo_config_get_bool(r, \"index.version\", &value))\n \t\tr->settings.index_version = value;\ndiff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh\nindex 5bd892f2f7..181ffa44e9 100755\n--- a/t/t0410-partial-clone.sh\n+++ b/t/t0410-partial-clone.sh\n@@ -234,7 +234,7 @@ test_expect_success 'rev-list stops traversal at missing and promised commit' '\n \n \tgit -C repo config core.repositoryformatversion 1 &&\n \tgit -C repo config extensions.partialclone \"arbitrary string\" &&\n-\tGIT_TEST_COMMIT_GRAPH=0 git -C repo rev-list --exclude-promisor-objects --objects bar >out &&\n+\tGIT_TEST_COMMIT_GRAPH=0 git -C repo -c core.commitGraph=false rev-list --exclude-promisor-objects --objects bar >out &&\n \tgrep $(git -C repo rev-parse bar) out &&\n \t! grep $FOO out\n '\ndiff --git a/t/t5307-pack-missing-commit.sh b/t/t5307-pack-missing-commit.sh\nindex dacb440b27..f4338abb78 100755\n--- a/t/t5307-pack-missing-commit.sh\n+++ b/t/t5307-pack-missing-commit.sh\n@@ -24,11 +24,11 @@ test_expect_success 'check corruption' '\n '\n \n test_expect_success 'rev-list notices corruption (1)' '\n-\ttest_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list HEAD\n+\ttest_must_fail env GIT_TEST_COMMIT_GRAPH=0 git -c core.commitGraph=false rev-list HEAD\n '\n \n test_expect_success 'rev-list notices corruption (2)' '\n-\ttest_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list --objects HEAD\n+\ttest_must_fail env GIT_TEST_COMMIT_GRAPH=0 git -c core.commitGraph=false rev-list --objects HEAD\n '\n \n test_expect_success 'pack-objects notices corruption' '\ndiff --git a/t/t5324-split-commit-graph.sh b/t/t5324-split-commit-graph.sh\nindex 03f45a1ed9..19aa40de15 100755\n--- a/t/t5324-split-commit-graph.sh\n+++ b/t/t5324-split-commit-graph.sh\n@@ -8,6 +8,7 @@ GIT_TEST_COMMIT_GRAPH=0\n test_expect_success 'setup repo' '\n \tgit init &&\n \tgit config core.commitGraph true &&\n+\tgit config gc.writeCommitGraph false &&\n \tinfodir=\".git/objects/info\" &&\n \tgraphdir=\"$infodir/commit-graphs\" &&\n \ttest_oid_init\n@@ -332,6 +333,7 @@ test_expect_success 'split across alternate where alternate is not split' '\n \tgit clone --no-hardlinks . alt-split &&\n \t(\n \t\tcd alt-split &&\n+\t\trm -f .git/objects/info/commit-graph &&\n \t\techo \"$(pwd)\"/../.git/objects >.git/objects/info/alternates &&\n \t\ttest_commit 18 &&\n \t\tgit commit-graph write --reachable --split &&\ndiff --git a/t/t6011-rev-list-with-bad-commit.sh b/t/t6011-rev-list-with-bad-commit.sh\nindex 545b461e51..bad02cf5b8 100755\n--- a/t/t6011-rev-list-with-bad-commit.sh\n+++ b/t/t6011-rev-list-with-bad-commit.sh\n@@ -42,7 +42,7 @@ test_expect_success 'corrupt second commit object' \\\n    '\n \n test_expect_success 'rev-list should fail' '\n-\ttest_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list --all > /dev/null\n+\ttest_must_fail env GIT_TEST_COMMIT_GRAPH=0 git -c core.commitGraph=false rev-list --all > /dev/null\n '\n \n test_expect_success 'git repack _MUST_ fail' \\\n-- \ngitgitgadget\n\n"},{"id":"380410","messageId":"f30e25fcd67a4d1c36b3a43f9e953655eb73f964.1565721461.git.gitgitgadget@gmail.com","threadId":"51508","inReplyTo":"pull.292.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 6/6] repo-settings: create feature.experimental setting","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-08-13T18:37:48Z","receivedAt":"2019-08-13T18:37:55Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe 'feature.experimental' setting includes config options that are\nnot committed to become defaults, but could use additional testing.\n\nUpdate the following config settings to take new defaults, and to\nuse the repo_settings struct if not already using them:\n\n* 'pack.useSparse=true'\n\n* 'fetch.negotiationAlgorithm=skipping'\n\nIn the case of fetch.negotiationAlgorithm, the existing logic\nwould load the config option only when about to use the setting,\nso had a die() statement on an unknown string value. This is\nremoved as now the config is parsed under prepare_repo_settings().\nIn general, this die() is probably misplaced and not valuable.\nA test was removed that checked this die() statement executed.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/feature.txt     | 14 ++++++++++++++\n Documentation/config/fetch.txt       |  3 ++-\n Documentation/config/pack.txt        |  3 ++-\n fetch-negotiator.c                   | 25 +++++++++++++------------\n fetch-negotiator.h                   |  5 +++--\n fetch-pack.c                         | 11 +++++------\n repo-settings.c                      | 13 +++++++++++++\n repository.h                         |  8 ++++++++\n t/t5552-skipping-fetch-negotiator.sh | 23 -----------------------\n 9 files changed, 60 insertions(+), 45 deletions(-)\n\ndiff --git a/Documentation/config/feature.txt b/Documentation/config/feature.txt\nindex 8ea198a642..545522f306 100644\n--- a/Documentation/config/feature.txt\n+++ b/Documentation/config/feature.txt\n@@ -4,6 +4,20 @@ feature.*::\n \tdeveloper community as recommended defaults and are subject to change.\n \tIn particular, new config options may be added with different defaults.\n \n+feature.experimental::\n+\tEnable config options that are new to Git, and are being considered for\n+\tfuture defaults. Config settings included here may be added or removed\n+\twith each release, including minor version updates. These settings may\n+\thave unintended interactions since they are so new. Please enable this\n+\tsetting if you are interested in providing feedback on experimental\n+\tfeatures. The new default values are:\n++\n+* `pack.useSparse=true` uses a new algorithm when constructing a pack-file\n+which can improve `git push` performance in repos with many files.\n++\n+* `fetch.negotiationAlgorithm=skipping` may improve fetch negotiation times by\n+skipping more commits at a time, reducing the number of round trips.\n+\n feature.manyFiles::\n \tEnable config options that optimize for repos with many files in the\n \tworking directory. With many files, commands such as `git status` and\ndiff --git a/Documentation/config/fetch.txt b/Documentation/config/fetch.txt\nindex ba890b5884..d402110638 100644\n--- a/Documentation/config/fetch.txt\n+++ b/Documentation/config/fetch.txt\n@@ -59,7 +59,8 @@ fetch.negotiationAlgorithm::\n \teffort to converge faster, but may result in a larger-than-necessary\n \tpackfile; The default is \"default\" which instructs Git to use the default algorithm\n \tthat never skips commits (unless the server has acknowledged it or one\n-\tof its descendants).\n+\tof its descendants). If `feature.experimental` is enabled, then this\n+\tsetting defaults to \"skipping\".\n \tUnknown values will cause 'git fetch' to error out.\n +\n See also the `--negotiation-tip` option for linkgit:git-fetch[1].\ndiff --git a/Documentation/config/pack.txt b/Documentation/config/pack.txt\nindex 9cdcfa7324..1d66f0c992 100644\n--- a/Documentation/config/pack.txt\n+++ b/Documentation/config/pack.txt\n@@ -112,7 +112,8 @@ pack.useSparse::\n \tobjects. This can have significant performance benefits when\n \tcomputing a pack to send a small change. However, it is possible\n \tthat extra objects are added to the pack-file if the included\n-\tcommits contain certain types of direct renames.\n+\tcommits contain certain types of direct renames. Default is `false`\n+\tunless `feature.experimental` is enabled.\n \n pack.writeBitmaps (deprecated)::\n \tThis is a deprecated synonym for `repack.writeBitmaps`.\ndiff --git a/fetch-negotiator.c b/fetch-negotiator.c\nindex d6d685cba0..0a1357dc9d 100644\n--- a/fetch-negotiator.c\n+++ b/fetch-negotiator.c\n@@ -2,19 +2,20 @@\n #include \"fetch-negotiator.h\"\n #include \"negotiator/default.h\"\n #include \"negotiator/skipping.h\"\n+#include \"repository.h\"\n \n-void fetch_negotiator_init(struct fetch_negotiator *negotiator,\n-\t\t\t   const char *algorithm)\n+void fetch_negotiator_init(struct repository *r,\n+\t\t\t   struct fetch_negotiator *negotiator)\n {\n-\tif (algorithm) {\n-\t\tif (!strcmp(algorithm, \"skipping\")) {\n-\t\t\tskipping_negotiator_init(negotiator);\n-\t\t\treturn;\n-\t\t} else if (!strcmp(algorithm, \"default\")) {\n-\t\t\t/* Fall through to default initialization */\n-\t\t} else {\n-\t\t\tdie(\"unknown fetch negotiation algorithm '%s'\", algorithm);\n-\t\t}\n+\tprepare_repo_settings(r);\n+\tswitch(r->settings.fetch_negotiation_algorithm) {\n+\tcase FETCH_NEGOTIATION_SKIPPING:\n+\t\tskipping_negotiator_init(negotiator);\n+\t\treturn;\n+\n+\tcase FETCH_NEGOTIATION_DEFAULT:\n+\tdefault:\n+\t\tdefault_negotiator_init(negotiator);\n+\t\treturn;\n \t}\n-\tdefault_negotiator_init(negotiator);\n }\ndiff --git a/fetch-negotiator.h b/fetch-negotiator.h\nindex 9e3967ce66..ea78868504 100644\n--- a/fetch-negotiator.h\n+++ b/fetch-negotiator.h\n@@ -2,6 +2,7 @@\n #define FETCH_NEGOTIATOR_H\n \n struct commit;\n+struct repository;\n \n /*\n  * An object that supplies the information needed to negotiate the contents of\n@@ -52,7 +53,7 @@ struct fetch_negotiator {\n \tvoid *data;\n };\n \n-void fetch_negotiator_init(struct fetch_negotiator *negotiator,\n-\t\t\t   const char *algorithm);\n+void fetch_negotiator_init(struct repository *r,\n+\t\t\t   struct fetch_negotiator *negotiator);\n \n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 65be043f2a..d81f47c07b 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -36,7 +36,6 @@ static int agent_supported;\n static int server_supports_filtering;\n static struct lock_file shallow_lock;\n static const char *alternate_shallow_file;\n-static char *negotiation_algorithm;\n static struct strbuf fsck_msg_types = STRBUF_INIT;\n \n /* Remember to update object flag allocation in object.h */\n@@ -892,12 +891,13 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,\n \t\t\t\t struct shallow_info *si,\n \t\t\t\t char **pack_lockfile)\n {\n+\tstruct repository *r = the_repository;\n \tstruct ref *ref = copy_ref_list(orig_ref);\n \tstruct object_id oid;\n \tconst char *agent_feature;\n \tint agent_len;\n \tstruct fetch_negotiator negotiator;\n-\tfetch_negotiator_init(&negotiator, negotiation_algorithm);\n+\tfetch_negotiator_init(r, &negotiator);\n \n \tsort_ref_list(&ref, ref_compare_name);\n \tQSORT(sought, nr_sought, cmp_ref_by_name);\n@@ -911,7 +911,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,\n \n \tif (server_supports(\"shallow\"))\n \t\tprint_verbose(args, _(\"Server supports %s\"), \"shallow\");\n-\telse if (args->depth > 0 || is_repository_shallow(the_repository))\n+\telse if (args->depth > 0 || is_repository_shallow(r))\n \t\tdie(_(\"Server does not support shallow clients\"));\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n@@ -1379,6 +1379,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\t\t\t    struct shallow_info *si,\n \t\t\t\t    char **pack_lockfile)\n {\n+\tstruct repository *r = the_repository;\n \tstruct ref *ref = copy_ref_list(orig_ref);\n \tenum fetch_state state = FETCH_CHECK_LOCAL;\n \tstruct oidset common = OIDSET_INIT;\n@@ -1386,7 +1387,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tint in_vain = 0;\n \tint haves_to_send = INITIAL_FLUSH;\n \tstruct fetch_negotiator negotiator;\n-\tfetch_negotiator_init(&negotiator, negotiation_algorithm);\n+\tfetch_negotiator_init(r, &negotiator);\n \tpacket_reader_init(&reader, fd[0], NULL, 0,\n \t\t\t   PACKET_READ_CHOMP_NEWLINE |\n \t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n@@ -1505,8 +1506,6 @@ static void fetch_pack_config(void)\n \tgit_config_get_bool(\"repack.usedeltabaseoffset\", &prefer_ofs_delta);\n \tgit_config_get_bool(\"fetch.fsckobjects\", &fetch_fsck_objects);\n \tgit_config_get_bool(\"transfer.fsckobjects\", &transfer_fsck_objects);\n-\tgit_config_get_string(\"fetch.negotiationalgorithm\",\n-\t\t\t      &negotiation_algorithm);\n \n \tgit_config(fetch_pack_config_cb, NULL);\n }\ndiff --git a/repo-settings.c b/repo-settings.c\nindex d5bf9069f4..3779b85c17 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -36,16 +36,29 @@ void prepare_repo_settings(struct repository *r)\n \t\tfree(strval);\n \t}\n \n+\tif (!repo_config_get_string(r, \"fetch.negotiationalgorithm\", &strval)) {\n+\t\tif (!strcasecmp(strval, \"skipping\"))\n+\t\t\tr->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;\n+\t\telse\n+\t\t\tr->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT;\n+\t}\n+\n \tif (!repo_config_get_bool(r, \"pack.usesparse\", &value))\n \t\tr->settings.pack_use_sparse = value;\n \tif (!repo_config_get_bool(r, \"feature.manyfiles\", &value) && value) {\n \t\tUPDATE_DEFAULT_BOOL(r->settings.index_version, 4);\n \t\tUPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE);\n \t}\n+\tif (!repo_config_get_bool(r, \"feature.experimental\", &value) && value) {\n+\t\tUPDATE_DEFAULT_BOOL(r->settings.pack_use_sparse, 1);\n+\t\tUPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_SKIPPING);\n+\t}\n \n \t/* Hack for test programs like test-dump-untracked-cache */\n \tif (ignore_untracked_cache_config)\n \t\tr->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;\n \telse\n \t\tUPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);\n+\n+\tUPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_DEFAULT);\n }\ndiff --git a/repository.h b/repository.h\nindex cf7ff0778c..4da275e73f 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -18,6 +18,13 @@ enum untracked_cache_setting {\n \tUNTRACKED_CACHE_WRITE = 2\n };\n \n+enum fetch_negotiation_setting {\n+\tFETCH_NEGOTIATION_UNSET = -1,\n+\tFETCH_NEGOTIATION_NONE = 0,\n+\tFETCH_NEGOTIATION_DEFAULT = 1,\n+\tFETCH_NEGOTIATION_SKIPPING = 2,\n+};\n+\n struct repo_settings {\n \tint initialized;\n \n@@ -28,6 +35,7 @@ struct repo_settings {\n \tenum untracked_cache_setting core_untracked_cache;\n \n \tint pack_use_sparse;\n+\tenum fetch_negotiation_setting fetch_negotiation_algorithm;\n };\n \n struct repository {\ndiff --git a/t/t5552-skipping-fetch-negotiator.sh b/t/t5552-skipping-fetch-negotiator.sh\nindex 8a14be51a1..f70cbcc9ca 100755\n--- a/t/t5552-skipping-fetch-negotiator.sh\n+++ b/t/t5552-skipping-fetch-negotiator.sh\n@@ -60,29 +60,6 @@ test_expect_success 'commits with no parents are sent regardless of skip distanc\n \thave_not_sent c6 c4 c3\n '\n \n-test_expect_success 'unknown fetch.negotiationAlgorithm values error out' '\n-\trm -rf server client trace &&\n-\tgit init server &&\n-\ttest_commit -C server to_fetch &&\n-\n-\tgit init client &&\n-\ttest_commit -C client on_client &&\n-\tgit -C client checkout on_client &&\n-\n-\ttest_config -C client fetch.negotiationAlgorithm invalid &&\n-\ttest_must_fail git -C client fetch \"$(pwd)/server\" 2>err &&\n-\ttest_i18ngrep \"unknown fetch negotiation algorithm\" err &&\n-\n-\t# Explicit \"default\" value\n-\ttest_config -C client fetch.negotiationAlgorithm default &&\n-\tgit -C client -c fetch.negotiationAlgorithm=default fetch \"$(pwd)/server\" &&\n-\n-\t# Implementation detail: If there is nothing to fetch, we will not error out\n-\ttest_config -C client fetch.negotiationAlgorithm invalid &&\n-\tgit -C client fetch \"$(pwd)/server\" 2>err &&\n-\ttest_i18ngrep ! \"unknown fetch negotiation algorithm\" err\n-'\n-\n test_expect_success 'when two skips collide, favor the larger one' '\n \trm -rf server client trace &&\n \tgit init server &&\n-- \ngitgitgadget\n"},{"id":"380417","messageId":"xmqqmugc7p6e.fsf@gitster-ct.c.googlers.com","threadId":"51508","inReplyTo":"pull.292.v4.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 0/6] Create 'feature.*' config area and some centralized config parsing","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-13T21:04:09Z","receivedAt":"2019-08-13T21:04:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n>  * Update docs and 0 constant in feature.experimental around\n>    merge.directoryRenames.\n\nI somehow thought that the concensus was that *.experimental is for\nthings that should become the future default, and this one is not\nsomething we would want to make the default.\n\n"},{"id":"380418","messageId":"xmqqimr07ozp.fsf@gitster-ct.c.googlers.com","threadId":"51508","inReplyTo":"xmqqmugc7p6e.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v4 0/6] Create 'feature.*' config area and some centralized config parsing","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-13T21:08:10Z","receivedAt":"2019-08-13T21:08:15Z","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> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>>  * Update docs and 0 constant in feature.experimental around\n>>    merge.directoryRenames.\n>\n> I somehow thought that the concensus was that *.experimental is for\n> things that should become the future default, and this one is not\n> something we would want to make the default.\n\nAh, I see merge.directoryRenames is gone.  Sorry for a confusion.\n\n\n\n"},{"id":"380431","messageId":"b7fc8353-abcb-742e-3dc4-4a404bbc55eb@gmail.com","threadId":"51508","inReplyTo":"xmqqimr07ozp.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v4 0/6] Create 'feature.*' config area and some centralized config parsing","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-08-14T10:32:32Z","receivedAt":"2019-08-14T10:32:54Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 8/13/2019 5:08 PM, Junio C Hamano wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n>> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>\n>>>  * Update docs and 0 constant in feature.experimental around\n>>>    merge.directoryRenames.\n>>\n>> I somehow thought that the concensus was that *.experimental is for\n>> things that should become the future default, and this one is not\n>> something we would want to make the default.\n> \n> Ah, I see merge.directoryRenames is gone.  Sorry for a confusion.\n\nConfusion is my fault. This was a terrible way to say, \"I cleaned up the\nadjustments to merge.directoryRenames AND removed it from features.experimental\"\n\nThanks,\n-Stolee\n"},{"id":"380432","messageId":"60fdb58a-3f83-5e65-9286-7164f42b60b4@gmail.com","threadId":"51508","inReplyTo":"b7fc8353-abcb-742e-3dc4-4a404bbc55eb@gmail.com","subject":"Re: [PATCH v4 0/6] Create 'feature.*' config area and some centralized config parsing","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-08-14T10:38:28Z","receivedAt":"2019-08-14T10:38:34Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 8/14/2019 6:32 AM, Derrick Stolee wrote:\n> On 8/13/2019 5:08 PM, Junio C Hamano wrote:\n>> Junio C Hamano <gitster@pobox.com> writes:\n>>\n>>> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>>\n>>>>  * Update docs and 0 constant in feature.experimental around\n>>>>    merge.directoryRenames.\n>>>\n>>> I somehow thought that the concensus was that *.experimental is for\n>>> things that should become the future default, and this one is not\n>>> something we would want to make the default.\n>>\n>> Ah, I see merge.directoryRenames is gone.  Sorry for a confusion.\n> \n> Confusion is my fault. This was a terrible way to say, \"I cleaned up the\n> adjustments to merge.directoryRenames AND removed it from features.experimental\"\n\nIt's too early for me to say things... let's try this again.\n\n1. merge.directoryRenames is removed from this series.\n\n2. The commit where I did the cleanup according to Elijahs' recommendation is \n   available as a tag, as mentioned in [1].\n\n[1] https://public-inbox.org/git/1a2bf8e1-5cc1-c626-9dfc-dd6dc58fa0ad@gmail.com/\n"},{"id":"436390","messageId":"xmqq8rzsdqlq.fsf@gitster.g","threadId":"51508","inReplyTo":"651e2d526b2a3f6c63c64cfe1f049eef94d39c07.1565721461.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 3/6] commit-graph: turn on commit-graph by default","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-09-20T00:42:57Z","receivedAt":"2021-09-20T00:43:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> diff --git a/repo-settings.c b/repo-settings.c\n> index 309577f6bc..d00b675687 100644\n> --- a/repo-settings.c\n> +++ b/repo-settings.c\n> @@ -2,6 +2,8 @@\n>  #include \"config.h\"\n>  #include \"repository.h\"\n>  \n> +#define UPDATE_DEFAULT_BOOL(s,v) do { if (s == -1) { s = v; } } while(0)\n> +\n>  void prepare_repo_settings(struct repository *r)\n>  {\n>  \tint value;\n> @@ -16,6 +18,8 @@ void prepare_repo_settings(struct repository *r)\n>  \t\tr->settings.core_commit_graph = value;\n>  \tif (!repo_config_get_bool(r, \"gc.writecommitgraph\", &value))\n>  \t\tr->settings.gc_write_commit_graph = value;\n> +\tUPDATE_DEFAULT_BOOL(r->settings.core_commit_graph, 1);\n> +\tUPDATE_DEFAULT_BOOL(r->settings.gc_write_commit_graph, 1);\n\n\nThis is a \"review comment\" that is more than 2 years late X-<, but I\nnoticed that this is used to muck with a structure that was\ninitialized by filling it with \\0377 bytes.\n\n+           /* Defaults */\n+           memset(&r->settings, -1, sizeof(r->settings));\n\nbut the structure is is full of \"int\" and \"enum\", so apparently this\nworks only on 2's complement architecture.\n\n        struct repo_settings {\n                int initialized;\n\n                int core_commit_graph;\n                int commit_graph_read_changed_paths;\n                int gc_write_commit_graph;\n                int fetch_write_commit_graph;\n\n                int index_version;\n                enum untracked_cache_setting core_untracked_cache;\n\n                int pack_use_sparse;\n                enum fetch_negotiation_setting fetch_negotiation_algorithm;\n\n                int core_multi_pack_index;\n\n                unsigned command_requires_full_index:1,\n                         sparse_index:1;\n        };\n\nI see that the earliest iteration of this series [*1*] set the\ndefault explicitly using assignments of the correct types, like\nthis:\n\n\n+void prepare_repo_settings(struct repository *r)\n+{\n+       if (r->settings)\n+          return;\n+\n+       r->settings = xmalloc(sizeof(*r->settings));\n+\n+       /* Defaults */\n+       r->settings->core_commit_graph = -1;\n+       r->settings->gc_write_commit_graph = -1;\n+       r->settings->pack_use_sparse = -1;\n+       r->settings->index_version = -1;\n+ ...\n\nwhich I think should be a reasonable starting point to fix the\ncurrent code.\n\nAnother thing I noticed is that while it may have been only for\nsetting the default value for a boolean variable initially, other\nchanges abuse the macro to set an arbitrary integer values to\ninteger members of the structure, e.g. c6cc4c5a (repo-settings:\ncreate feature.manyFiles setting, 2019-08-13) sets 4 to the\nindex_version (naturally, the choice between 0 and 1 does not make\nmuch sense for the member), and ad0fb659 (repo-settings: parse\ncore.untrackedCache, 2019-08-13) stuffs UNTRACKED_CACHE_* enum to\ncore_untracked_cache.  The UPDATE_DEFAULT_BOOL() macro should be\nrenamed to UPDATE_DEFAULT_INT() at least, I would think, to save\nreaders from confusion.\n\nThanks.\n\n\n[Reference]\n\n*1* https://lore.kernel.org/git/72f652b89c71526cc423e7812de66f41a079f181.1563818059.git.gitgitgadget@gmail.com/\n"},{"id":"436391","messageId":"YUfjIjgk2IwcJNI8@camp.crustytoothpaste.net","threadId":"51508","inReplyTo":"xmqq8rzsdqlq.fsf@gitster.g","subject":"Re: [PATCH v4 3/6] commit-graph: turn on commit-graph by default","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2021-09-20T01:25:54Z","receivedAt":"2021-09-20T01:26:34Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2021-09-20 at 00:42:57, Junio C Hamano wrote:\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n> > diff --git a/repo-settings.c b/repo-settings.c\n> > index 309577f6bc..d00b675687 100644\n> > --- a/repo-settings.c\n> > +++ b/repo-settings.c\n> > @@ -2,6 +2,8 @@\n> >  #include \"config.h\"\n> >  #include \"repository.h\"\n> >  \n> > +#define UPDATE_DEFAULT_BOOL(s,v) do { if (s == -1) { s = v; } } while(0)\n> > +\n> >  void prepare_repo_settings(struct repository *r)\n> >  {\n> >  \tint value;\n> > @@ -16,6 +18,8 @@ void prepare_repo_settings(struct repository *r)\n> >  \t\tr->settings.core_commit_graph = value;\n> >  \tif (!repo_config_get_bool(r, \"gc.writecommitgraph\", &value))\n> >  \t\tr->settings.gc_write_commit_graph = value;\n> > +\tUPDATE_DEFAULT_BOOL(r->settings.core_commit_graph, 1);\n> > +\tUPDATE_DEFAULT_BOOL(r->settings.gc_write_commit_graph, 1);\n> \n> \n> This is a \"review comment\" that is more than 2 years late X-<, but I\n> noticed that this is used to muck with a structure that was\n> initialized by filling it with \\0377 bytes.\n> \n> +           /* Defaults */\n> +           memset(&r->settings, -1, sizeof(r->settings));\n> \n> but the structure is is full of \"int\" and \"enum\", so apparently this\n> works only on 2's complement architecture.\n\nThis statement is true, but are there systems capable of running Git\nwhich don't use two's complement?  Rust requires two's complement signed\nintegers, and there's a proposal[0] to the C++ working group to only\nsupport two's complement because \"[t]o the author’s knowledge no modern\nmachine uses both C++ and a signed integer representation other than\ntwo’s complement\".  That proposal goes on to note that none of MSVC,\nGCC, or LLVM support other options.\n\nPersonally I am not aware of any modern processor which provides signed\ninteger types using other than two's complement.\n\n[0] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0907r4.html\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"436392","messageId":"87fsu0avaz.fsf@evledraar.gmail.com","threadId":"51508","inReplyTo":"xmqq8rzsdqlq.fsf@gitster.g","subject":"Re: [PATCH v4 3/6] commit-graph: turn on commit-graph by default","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-09-20T01:22:20Z","receivedAt":"2021-09-20T01:29:45Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sun, Sep 19 2021, Junio C Hamano wrote:\n\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> diff --git a/repo-settings.c b/repo-settings.c\n>> index 309577f6bc..d00b675687 100644\n>> --- a/repo-settings.c\n>> +++ b/repo-settings.c\n>> @@ -2,6 +2,8 @@\n>>  #include \"config.h\"\n>>  #include \"repository.h\"\n>>  \n>> +#define UPDATE_DEFAULT_BOOL(s,v) do { if (s == -1) { s = v; } } while(0)\n>> +\n>>  void prepare_repo_settings(struct repository *r)\n>>  {\n>>  \tint value;\n>> @@ -16,6 +18,8 @@ void prepare_repo_settings(struct repository *r)\n>>  \t\tr->settings.core_commit_graph = value;\n>>  \tif (!repo_config_get_bool(r, \"gc.writecommitgraph\", &value))\n>>  \t\tr->settings.gc_write_commit_graph = value;\n>> +\tUPDATE_DEFAULT_BOOL(r->settings.core_commit_graph, 1);\n>> +\tUPDATE_DEFAULT_BOOL(r->settings.gc_write_commit_graph, 1);\n>\n>\n> This is a \"review comment\" that is more than 2 years late X-<, but I\n> noticed that this is used to muck with a structure that was\n> initialized by filling it with \\0377 bytes.\n>\n> +           /* Defaults */\n> +           memset(&r->settings, -1, sizeof(r->settings));\n>\n> but the structure is is full of \"int\" and \"enum\", so apparently this\n> works only on 2's complement architecture.\n>\n>         struct repo_settings {\n>                 int initialized;\n>\n>                 int core_commit_graph;\n>                 int commit_graph_read_changed_paths;\n>                 int gc_write_commit_graph;\n>                 int fetch_write_commit_graph;\n>\n>                 int index_version;\n>                 enum untracked_cache_setting core_untracked_cache;\n>\n>                 int pack_use_sparse;\n>                 enum fetch_negotiation_setting fetch_negotiation_algorithm;\n>\n>                 int core_multi_pack_index;\n>\n>                 unsigned command_requires_full_index:1,\n>                          sparse_index:1;\n>         };\n>\n> I see that the earliest iteration of this series [*1*] set the\n> default explicitly using assignments of the correct types, like\n> this:\n>\n>\n> +void prepare_repo_settings(struct repository *r)\n> +{\n> +       if (r->settings)\n> +          return;\n> +\n> +       r->settings = xmalloc(sizeof(*r->settings));\n> +\n> +       /* Defaults */\n> +       r->settings->core_commit_graph = -1;\n> +       r->settings->gc_write_commit_graph = -1;\n> +       r->settings->pack_use_sparse = -1;\n> +       r->settings->index_version = -1;\n> + ...\n>\n> which I think should be a reasonable starting point to fix the\n> current code.\n>\n> Another thing I noticed is that while it may have been only for\n> setting the default value for a boolean variable initially, other\n> changes abuse the macro to set an arbitrary integer values to\n> integer members of the structure, e.g. c6cc4c5a (repo-settings:\n> create feature.manyFiles setting, 2019-08-13) sets 4 to the\n> index_version (naturally, the choice between 0 and 1 does not make\n> much sense for the member), and ad0fb659 (repo-settings: parse\n> core.untrackedCache, 2019-08-13) stuffs UNTRACKED_CACHE_* enum to\n> core_untracked_cache.  The UPDATE_DEFAULT_BOOL() macro should be\n> renamed to UPDATE_DEFAULT_INT() at least, I would think, to save\n> readers from confusion.\n\nYes this is all a bit weird and/or broken, but I'm a bit perplexed at\nthis reply to a 2+ year old E-Mail given my outstanding series to fix\nall these issues you've noted here[1] posted in the last few days, and\nyou having read (at least part of) it[1].\n\nBut then again, the last patch you left a comment on was 3/5. It's 4/5\nthat fixes all the issues you note above[2] :)\n\nThe macro is gone, so is the memset to -1 and other weird emergent\nbehavior. We can rely on repo_init() having zero'd the structure for us,\nand we just proceed to set sensible defaults in a way that doesn't stomp\nover the types in the struct.\n\n1. https://lore.kernel.org/git/cover-v3-0.5-00000000000-20210919T084703Z-avarab@gmail.com/\n2. https://lore.kernel.org/git/patch-v3-4.5-28286a61162-20210919T084703Z-avarab@gmail.com/\n\n> [Reference]\n>\n> *1* https://lore.kernel.org/git/72f652b89c71526cc423e7812de66f41a079f181.1563818059.git.gitgitgadget@gmail.com/\n\n"},{"id":"436414","messageId":"8841729f-9310-c393-caa9-4b209909ea5d@gmail.com","threadId":"51508","inReplyTo":"YUfjIjgk2IwcJNI8@camp.crustytoothpaste.net","subject":"Re: [PATCH v4 3/6] commit-graph: turn on commit-graph by default","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2021-09-20T12:53:47Z","receivedAt":"2021-09-20T12:53:52Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 20/09/2021 02:25, brian m. carlson wrote:\n> On 2021-09-20 at 00:42:57, Junio C Hamano wrote:\n>> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>\n>>> diff --git a/repo-settings.c b/repo-settings.c\n>>> index 309577f6bc..d00b675687 100644\n>>> --- a/repo-settings.c\n>>> +++ b/repo-settings.c\n>>> @@ -2,6 +2,8 @@\n>>>   #include \"config.h\"\n>>>   #include \"repository.h\"\n>>>   \n>>> +#define UPDATE_DEFAULT_BOOL(s,v) do { if (s == -1) { s = v; } } while(0)\n>>> +\n>>>   void prepare_repo_settings(struct repository *r)\n>>>   {\n>>>   \tint value;\n>>> @@ -16,6 +18,8 @@ void prepare_repo_settings(struct repository *r)\n>>>   \t\tr->settings.core_commit_graph = value;\n>>>   \tif (!repo_config_get_bool(r, \"gc.writecommitgraph\", &value))\n>>>   \t\tr->settings.gc_write_commit_graph = value;\n>>> +\tUPDATE_DEFAULT_BOOL(r->settings.core_commit_graph, 1);\n>>> +\tUPDATE_DEFAULT_BOOL(r->settings.gc_write_commit_graph, 1);\n>>\n>>\n>> This is a \"review comment\" that is more than 2 years late X-<, but I\n>> noticed that this is used to muck with a structure that was\n>> initialized by filling it with \\0377 bytes.\n>>\n>> +           /* Defaults */\n>> +           memset(&r->settings, -1, sizeof(r->settings));\n>>\n>> but the structure is is full of \"int\" and \"enum\", so apparently this\n>> works only on 2's complement architecture.\n> \n> This statement is true, but are there systems capable of running Git\n> which don't use two's complement?  Rust requires two's complement signed\n> integers, and there's a proposal[0] to the C++ working group to only\n> support two's complement because \"[t]o the author’s knowledge no modern\n> machine uses both C++ and a signed integer representation other than\n> two’s complement\".  That proposal goes on to note that none of MSVC,\n> GCC, or LLVM support other options.\n\nA similar proposal [1] is included in the draft of the next C standard \n[2]. As integer representation is implementation defined I believe this \ncode has well defined behavior on 2's complement implementations. If an \nenum has no negative members then the compiler may choose an unsigned \nrepresentation but even then the comparison to -1 is well defined. In \nthis case I'm pretty sure the enums all have -1 as a member so are \nsigned. Using memset() to initialize the struct eases future maintenance \nwhen members are added or removed and seems to me to be a sensible \ndesign choice.\n\nBest Wishes\n\nPhillip\n\n[1] http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2412.pdf\n[2] http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2596.pdf\n\n> Personally I am not aware of any modern processor which provides signed\n> integer types using other than two's complement.\n> \n> [0] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0907r4.html\n> \n\n"},{"id":"436415","messageId":"8735pzbbtk.fsf@evledraar.gmail.com","threadId":"51508","inReplyTo":"8841729f-9310-c393-caa9-4b209909ea5d@gmail.com","subject":"Re: [PATCH v4 3/6] commit-graph: turn on commit-graph by default","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-09-20T13:30:38Z","receivedAt":"2021-09-20T13:45:35Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Sep 20 2021, Phillip Wood wrote:\n\n> On 20/09/2021 02:25, brian m. carlson wrote:\n>> On 2021-09-20 at 00:42:57, Junio C Hamano wrote:\n>>> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>>\n>>>> diff --git a/repo-settings.c b/repo-settings.c\n>>>> index 309577f6bc..d00b675687 100644\n>>>> --- a/repo-settings.c\n>>>> +++ b/repo-settings.c\n>>>> @@ -2,6 +2,8 @@\n>>>>   #include \"config.h\"\n>>>>   #include \"repository.h\"\n>>>>   +#define UPDATE_DEFAULT_BOOL(s,v) do { if (s == -1) { s = v; } }\n>>>> while(0)\n>>>> +\n>>>>   void prepare_repo_settings(struct repository *r)\n>>>>   {\n>>>>   \tint value;\n>>>> @@ -16,6 +18,8 @@ void prepare_repo_settings(struct repository *r)\n>>>>   \t\tr->settings.core_commit_graph = value;\n>>>>   \tif (!repo_config_get_bool(r, \"gc.writecommitgraph\", &value))\n>>>>   \t\tr->settings.gc_write_commit_graph = value;\n>>>> +\tUPDATE_DEFAULT_BOOL(r->settings.core_commit_graph, 1);\n>>>> +\tUPDATE_DEFAULT_BOOL(r->settings.gc_write_commit_graph, 1);\n>>>\n>>>\n>>> This is a \"review comment\" that is more than 2 years late X-<, but I\n>>> noticed that this is used to muck with a structure that was\n>>> initialized by filling it with \\0377 bytes.\n>>>\n>>> +           /* Defaults */\n>>> +           memset(&r->settings, -1, sizeof(r->settings));\n>>>\n>>> but the structure is is full of \"int\" and \"enum\", so apparently this\n>>> works only on 2's complement architecture.\n>> This statement is true, but are there systems capable of running Git\n>> which don't use two's complement?  Rust requires two's complement signed\n>> integers, and there's a proposal[0] to the C++ working group to only\n>> support two's complement because \"[t]o the author’s knowledge no modern\n>> machine uses both C++ and a signed integer representation other than\n>> two’s complement\".  That proposal goes on to note that none of MSVC,\n>> GCC, or LLVM support other options.\n>\n> A similar proposal [1] is included in the draft of the next C standard\n> [2]. As integer representation is implementation defined I believe\n> this code has well defined behavior on 2's complement\n> implementations. If an enum has no negative members then the compiler\n> may choose an unsigned representation but even then the comparison to\n> -1 is well defined.\n\nThat's informative, thanks.\n\n> In this case I'm pretty sure the enums all have -1\n> as a member so are signed. Using memset() to initialize the struct\n> eases future maintenance when members are added or removed and seems\n> to me to be a sensible design choice.\n\nIt's really not sensible at all in this particular case, as I think my\n[1] which gets rid of the pattern convincingly argues.\n\nI.e. the only reason it had a memset() of -1 after we'd already memset\nit to 0 was because the function was tripping over itself and setting\ndefaults in the wrong order for no good reason.\n\nI.e. it was doing things like (pseudocode);\n\n    memset(&data, -1, ...)\n    if_config_is_true_set(\"x.y\", data.x_y);\n    if (data.x_y == -1)\n        data.x_y = x_y_default();\n\nWhen we can instead just do:\n\n    data.x_y = x_y_default();\n    set_if_cfg_key_exists(\"x.y\", &data.x_y);\n\nWhich is how we e.g. handle options parsing, we have hardcoded defaults,\nthen read defaults from config, then set options, in that order.\n\nWe don't set options, then check if each value is still -1, if so read\nconfig etc. Just read them in priority order, doing it any other way is\njust make-work for something that's the equivalent of a simple\nshort-circuit || operation.\n\nAnyway, there are other cases where we need to read something and\ndistinguish e.g. false/true from \"unset\", and there a -1,0,1 tri-state\nserves us well.\n\nBut even in those cases what repo-settings.c was doing of memsetting the\nentire struct to -1 (2's compliment aside) just makes for needlessly\nhard to read code.\n\nIf we've got some members that need -1 defaults we should instead have\nthat in an *_INIT macro or equivalent. The pre-[1] repo-settings.c also\nhas code like this pseudocode:\n\n    data.a_b = -1; /* default for a bi-state, not tri-state variable */\n    set_if_cfg_key_exists(\"a.b\", &data.a_b);\n    if (data.a_b == -1)\n        data.a_b = 1; /* on by default */\n\nWhich, urm, you can just do as:\n\n    data.a_b = 1; /* on by default */\n    set_if_cfg_key_exists(\"a.b\", &data.a_b);\n\nI.e. the setup for things that never wanted or cared about being set to\n-1 was complicated by them needing to un-set themselves from a -1\ndefault they never wanted.\n\nThus the anti-pattern, yes set defaults for some members to -1, but not\nthe entire struct. The only value we should memset a whole bag-of-stuff\nconfig struct to is 0, as that's the only sensible default & plays well\nwith other C semantics.\n\n1. https://lore.kernel.org/git/patch-v3-4.5-28286a61162-20210919T084703Z-avarab@gmail.com/\n\n>\n> Best Wishes\n>\n> Phillip\n>\n> [1] http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2412.pdf\n> [2] http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2596.pdf\n>\n>> Personally I am not aware of any modern processor which provides signed\n>> integer types using other than two's complement.\n>> [0]\n>> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0907r4.html\n>> \n\n"},{"id":"436452","messageId":"YUjekM1tscZJykzr@coredump.intra.peff.net","threadId":"51508","inReplyTo":"8735pzbbtk.fsf@evledraar.gmail.com","subject":"Re: [PATCH v4 3/6] commit-graph: turn on commit-graph by default","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-09-20T19:18:40Z","receivedAt":"2021-09-20T19:20:51Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Sep 20, 2021 at 03:30:38PM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> Thus the anti-pattern, yes set defaults for some members to -1, but not\n> the entire struct. The only value we should memset a whole bag-of-stuff\n> config struct to is 0, as that's the only sensible default & plays well\n> with other C semantics.\n\nFWIW, I agree. I had to scratch my head for a moment at why a memset of\n\"-1\" would work at all on multi-byte types. I think it's better avoided\nin the name of readability and obviousness, not to mention the trap it\nleaves for items that don't sensibly initialize with it (like, say,\npointers).\n\nAs an aside, memset to 0 is _also_ undefined for pointers, but we long\nago decided not to care, as no real-world systems have a problem with\nthis.\n\n-Peff\n"},{"id":"436525","messageId":"25817fb9-ec3e-4c26-0dff-a353463867e4@gmail.com","threadId":"51508","inReplyTo":"8735pzbbtk.fsf@evledraar.gmail.com","subject":"Re: [PATCH v4 3/6] commit-graph: turn on commit-graph by default","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2021-09-20T18:00:26Z","receivedAt":"2021-09-21T02:22:00Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 20/09/2021 14:30, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Mon, Sep 20 2021, Phillip Wood wrote:\n> \n>> On 20/09/2021 02:25, brian m. carlson wrote:\n>>> On 2021-09-20 at 00:42:57, Junio C Hamano wrote:\n>>>> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>>>\n>>>>> diff --git a/repo-settings.c b/repo-settings.c\n>>>>> index 309577f6bc..d00b675687 100644\n>>>>> --- a/repo-settings.c\n>>>>> +++ b/repo-settings.c\n>>>>> @@ -2,6 +2,8 @@\n>>>>>    #include \"config.h\"\n>>>>>    #include \"repository.h\"\n>>>>>    +#define UPDATE_DEFAULT_BOOL(s,v) do { if (s == -1) { s = v; } }\n>>>>> while(0)\n>>>>> +\n>>>>>    void prepare_repo_settings(struct repository *r)\n>>>>>    {\n>>>>>    \tint value;\n>>>>> @@ -16,6 +18,8 @@ void prepare_repo_settings(struct repository *r)\n>>>>>    \t\tr->settings.core_commit_graph = value;\n>>>>>    \tif (!repo_config_get_bool(r, \"gc.writecommitgraph\", &value))\n>>>>>    \t\tr->settings.gc_write_commit_graph = value;\n>>>>> +\tUPDATE_DEFAULT_BOOL(r->settings.core_commit_graph, 1);\n>>>>> +\tUPDATE_DEFAULT_BOOL(r->settings.gc_write_commit_graph, 1);\n>>>>\n>>>>\n>>>> This is a \"review comment\" that is more than 2 years late X-<, but I\n>>>> noticed that this is used to muck with a structure that was\n>>>> initialized by filling it with \\0377 bytes.\n>>>>\n>>>> +           /* Defaults */\n>>>> +           memset(&r->settings, -1, sizeof(r->settings));\n>>>>\n>>>> but the structure is is full of \"int\" and \"enum\", so apparently this\n>>>> works only on 2's complement architecture.\n>>> This statement is true, but are there systems capable of running Git\n>>> which don't use two's complement?  Rust requires two's complement signed\n>>> integers, and there's a proposal[0] to the C++ working group to only\n>>> support two's complement because \"[t]o the author’s knowledge no modern\n>>> machine uses both C++ and a signed integer representation other than\n>>> two’s complement\".  That proposal goes on to note that none of MSVC,\n>>> GCC, or LLVM support other options.\n>>\n>> A similar proposal [1] is included in the draft of the next C standard\n>> [2]. As integer representation is implementation defined I believe\n>> this code has well defined behavior on 2's complement\n>> implementations. If an enum has no negative members then the compiler\n>> may choose an unsigned representation but even then the comparison to\n>> -1 is well defined.\n> \n> That's informative, thanks.\n> \n>> In this case I'm pretty sure the enums all have -1\n>> as a member so are signed. Using memset() to initialize the struct\n>> eases future maintenance when members are added or removed and seems\n>> to me to be a sensible design choice.\n> \n> It's really not sensible at all in this particular case, as I think my\n> [1] which gets rid of the pattern convincingly argues.\n\nI meant that it was a sensible way to initialize all the struct members \nto -1, I did not mean to comment either way on whether such an \ninitialization was sensible. If we can just store the default and then \nupdate from the user's config that sounds sensible.\n\nBest Wishes\n\nPhillip\n\n> I.e. the only reason it had a memset() of -1 after we'd already memset\n> it to 0 was because the function was tripping over itself and setting\n> defaults in the wrong order for no good reason.\n> \n> I.e. it was doing things like (pseudocode);\n> \n>      memset(&data, -1, ...)\n>      if_config_is_true_set(\"x.y\", data.x_y);\n>      if (data.x_y == -1)\n>          data.x_y = x_y_default();\n> \n> When we can instead just do:\n> \n>      data.x_y = x_y_default();\n>      set_if_cfg_key_exists(\"x.y\", &data.x_y);\n> \n> Which is how we e.g. handle options parsing, we have hardcoded defaults,\n> then read defaults from config, then set options, in that order.\n> \n> We don't set options, then check if each value is still -1, if so read\n> config etc. Just read them in priority order, doing it any other way is\n> just make-work for something that's the equivalent of a simple\n> short-circuit || operation.\n> \n> Anyway, there are other cases where we need to read something and\n> distinguish e.g. false/true from \"unset\", and there a -1,0,1 tri-state\n> serves us well.\n> \n> But even in those cases what repo-settings.c was doing of memsetting the\n> entire struct to -1 (2's compliment aside) just makes for needlessly\n> hard to read code.\n> \n> If we've got some members that need -1 defaults we should instead have\n> that in an *_INIT macro or equivalent. The pre-[1] repo-settings.c also\n> has code like this pseudocode:\n> \n>      data.a_b = -1; /* default for a bi-state, not tri-state variable */\n>      set_if_cfg_key_exists(\"a.b\", &data.a_b);\n>      if (data.a_b == -1)\n>          data.a_b = 1; /* on by default */\n> \n> Which, urm, you can just do as:\n> \n>      data.a_b = 1; /* on by default */\n>      set_if_cfg_key_exists(\"a.b\", &data.a_b);\n> \n> I.e. the setup for things that never wanted or cared about being set to\n> -1 was complicated by them needing to un-set themselves from a -1\n> default they never wanted.\n> \n> Thus the anti-pattern, yes set defaults for some members to -1, but not\n> the entire struct. The only value we should memset a whole bag-of-stuff\n> config struct to is 0, as that's the only sensible default & plays well\n> with other C semantics.\n> \n> 1. https://lore.kernel.org/git/patch-v3-4.5-28286a61162-20210919T084703Z-avarab@gmail.com/\n> \n>>\n>> Best Wishes\n>>\n>> Phillip\n>>\n>> [1] http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2412.pdf\n>> [2] http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2596.pdf\n>>\n>>> Personally I am not aware of any modern processor which provides signed\n>>> integer types using other than two's complement.\n>>> [0]\n>>> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0907r4.html\n>>>\n> \n\n"}]}