{"thread":{"id":"55195","subject":"[PATCH 00/20] Sparse Index: Design, Format, Tests","startedAt":"2021-02-23T20:15:15Z","lastAt":"2021-04-01T06:01:44Z","messageCount":203,"participants":["Derrick Stolee via GitGitGadget","Elijah Newren","Martin Ågren","Derrick Stolee","SZEDER Gábor","Junio C Hamano","Ævar Arnfjörð Bjarmason"],"isPatch":true,"patchVersion":1,"patchTotal":20},"messages":[{"id":"417595","messageId":"pull.883.git.1614111270.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":null,"subject":"[PATCH 00/20] Sparse Index: Design, Format, Tests","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:09Z","receivedAt":"2021-02-23T20:15:15Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"Here is the first full patch series submission coming out of the\nsparse-index RFC [1].\n\n[1]\nhttps://lore.kernel.org/git/pull.847.git.1611596533.gitgitgadget@gmail.com/\n\nI won't waste too much space here, because PATCH 1 includes a sizeable\ndesign document that describes the feature, the reasoning behind it, and my\nplan for getting this implemented widely throughout the codebase.\n\nThere are some new things here that were not in the RFC:\n\n * Design doc and format updates. (Patch 1)\n * Performance test script. (Patches 2 and 20)\n\nNotably missing in this series from the RFC:\n\n * The mega-patch inserting ensure_full_index() throughout the codebase.\n   That will be a follow-up series to this one.\n * The integrations with git status and git add to demonstrate the improved\n   performance. Those will also appear in their own series later.\n\nI plan to keep my latest work in this area in my 'sparse-index/wip' branch\n[2]. It includes all of the work from the RFC right now, updated with the\nwork from this series.\n\n[2] https://github.com/derrickstolee/git/tree/sparse-index/wip\n\nThanks, -Stolee\n\nDerrick Stolee (20):\n  sparse-index: design doc and format update\n  t/perf: add performance test for sparse operations\n  t1092: clean up script quoting\n  sparse-index: add guard to ensure full index\n  sparse-index: implement ensure_full_index()\n  t1092: compare sparse-checkout to sparse-index\n  test-read-cache: print cache entries with --table\n  test-tool: don't force full index\n  unpack-trees: ensure full index\n  sparse-checkout: hold pattern list in index\n  sparse-index: convert from full to sparse\n  submodule: sparse-index should not collapse links\n  unpack-trees: allow sparse directories\n  sparse-index: check index conversion happens\n  sparse-index: create extension for compatibility\n  sparse-checkout: toggle sparse index from builtin\n  sparse-checkout: disable sparse-index\n  cache-tree: integrate with sparse directory entries\n  sparse-index: loose integration with cache_tree_verify()\n  p2000: add sparse-index repos\n\n Documentation/config/extensions.txt      |   7 +\n Documentation/git-sparse-checkout.txt    |  14 ++\n Documentation/technical/index-format.txt |   7 +\n Documentation/technical/sparse-index.txt | 167 +++++++++++++\n Makefile                                 |   1 +\n builtin/sparse-checkout.c                |  44 +++-\n cache-tree.c                             |  40 ++++\n cache.h                                  |  12 +-\n read-cache.c                             |  35 ++-\n repo-settings.c                          |  15 ++\n repository.c                             |  11 +-\n repository.h                             |   3 +\n setup.c                                  |   3 +\n sparse-index.c                           | 290 +++++++++++++++++++++++\n sparse-index.h                           |  11 +\n t/README                                 |   3 +\n t/helper/test-read-cache.c               |  61 ++++-\n t/perf/p2000-sparse-operations.sh        | 104 ++++++++\n t/t1091-sparse-checkout-builtin.sh       |  13 +\n t/t1092-sparse-checkout-compatibility.sh | 136 +++++++++--\n unpack-trees.c                           |  16 +-\n 21 files changed, 953 insertions(+), 40 deletions(-)\n create mode 100644 Documentation/technical/sparse-index.txt\n create mode 100644 sparse-index.c\n create mode 100644 sparse-index.h\n create mode 100755 t/perf/p2000-sparse-operations.sh\n\n\nbase-commit: 966e671106b2fd38301e7c344c754fd118d0bb07\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-883%2Fderrickstolee%2Fsparse-index%2Fformat-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-883/derrickstolee/sparse-index/format-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/883\n-- \ngitgitgadget\n"},{"id":"417596","messageId":"daa9a6bcefbce977606484d502f5cfd7fca63ecc.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 01/20] sparse-index: design doc and format update","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:10Z","receivedAt":"2021-02-23T20:15:17Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThis begins a long effort to update the index format to allow sparse\ndirectory entries. This should result in a significant improvement to\nGit commands when HEAD contains millions of files, but the user has\nselected many fewer files to keep in their sparse-checkout definition.\n\nCurrently, the index format is only updated in the presence of\nextensions.sparseIndex instead of increasing a file format version\nnumber. This is temporary, and index v5 is part of the plan for future\nwork in this area.\n\nThe design document details many of the reasons for embarking on this\nwork, and also the plan for completing it safely.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/technical/index-format.txt |   7 +\n Documentation/technical/sparse-index.txt | 167 +++++++++++++++++++++++\n 2 files changed, 174 insertions(+)\n create mode 100644 Documentation/technical/sparse-index.txt\n\ndiff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt\nindex b633482b1bdf..387126582556 100644\n--- a/Documentation/technical/index-format.txt\n+++ b/Documentation/technical/index-format.txt\n@@ -44,6 +44,13 @@ Git index format\n   localization, no special casing of directory separator '/'). Entries\n   with the same name are sorted by their stage field.\n \n+  An index entry typically represents a file. However, if sparse-checkout\n+  is enabled in cone mode (`core.sparseCheckoutCone` is enabled) and the\n+  `extensions.sparseIndex` extension is enabled, then the index may\n+  contain entries for directories outside of the sparse-checkout definition.\n+  These entries have mode `0040000`, include the `SKIP_WORKTREE` bit, and\n+  the path ends in a directory separator.\n+\n   32-bit ctime seconds, the last time a file's metadata changed\n     this is stat(2) data\n \ndiff --git a/Documentation/technical/sparse-index.txt b/Documentation/technical/sparse-index.txt\nnew file mode 100644\nindex 000000000000..9070836f0655\n--- /dev/null\n+++ b/Documentation/technical/sparse-index.txt\n@@ -0,0 +1,167 @@\n+Git Sparse-Index Design Document\n+================================\n+\n+The sparse-checkout feature allows users to focus a working directory on\n+a subset of the files at HEAD. The cone mode patterns, enabled by\n+`core.sparseCheckoutCone`, allow for very fast pattern matching to\n+discover which files at HEAD belong in the sparse-checkout cone.\n+\n+Three important scale dimensions for a Git worktree are:\n+\n+* `HEAD`: How many files are present at `HEAD`?\n+\n+* Populated: How many files are within the sparse-checkout cone.\n+\n+* Modified: How many files has the user modified in the working directory?\n+\n+We will use big-O notation -- O(X) -- to denote how expensive certain\n+operations are in terms of these dimensions.\n+\n+These dimensions are ordered by their magnitude: users (typically) modify\n+fewer files than are populated, and we can only populate files at `HEAD`.\n+These dimensions are also ordered by how expensive they are per item: it\n+is expensive to detect a modified file than it is to write one that we\n+know must be populated; changing `HEAD` only really requires updating the\n+index.\n+\n+Problems occur if there is an extreme imbalance in these dimensions. For\n+example, if `HEAD` contains millions of paths but the populated set has\n+only tens of thousands, then commands like `git status` and `git add` can\n+be dominated by operations that require O(`HEAD`) operations instead of\n+O(Populated). Primarily, the cost is in parsing and rewriting the index,\n+which is filled primarily with files at `HEAD` that are marked with the\n+`SKIP_WORKTREE` bit.\n+\n+The sparse-index intends to take these commands that read and modify the\n+index from O(`HEAD`) to O(Populated). To do this, we need to modify the\n+index format in a significant way: add \"sparse directory\" entries.\n+\n+With cone mode patterns, it is possible to detect when an entire\n+directory will have its contents outside of the sparse-checkout definition.\n+Instead of listing all of the files it contains as individual entries, a\n+sparse-index contains an entry with the directory name, referencing the\n+object ID of the tree at `HEAD` and marked with the `SKIP_WORKTREE` bit.\n+If we need to discover the details for paths within that directory, we\n+can parse trees to find that list.\n+\n+This addition of sparse-directory entries violates expectations about the\n+index format and its in-memory data structure. There are many consumers in\n+the codebase that expect to iterate through all of the index entries and\n+see only files. In addition, they expect to see all files at `HEAD`. One\n+way to handle this is to parse trees to replace a sparse-directory entry\n+with all of the files within that tree as the index is loaded. However,\n+parsing trees is slower than parsing the index format, so that is a slower\n+operation than if we left the index alone.\n+\n+The implementation plan below follows four phases to slowly integrate with\n+the sparse-index. The intention is to incrementally update Git commands to\n+interact safely with the sparse-index without significant slowdowns. This\n+may not always be possible, but the hope is that the primary commands that\n+users need in their daily work are dramatically improved.\n+\n+Phase I: Format and initial speedups\n+------------------------------------\n+\n+During this phase, Git learns to enable the sparse-index and safely parse\n+one. Protections are put in place so that every consumer of the in-memory\n+data structure can operate with its current assumption of every file at\n+`HEAD`.\n+\n+At first, every index parse will expand the sparse-directory entries into\n+the full list of paths at `HEAD`. This will be slower in all cases. The\n+only noticable change in behavior will be that the serialized index file\n+contains sparse-directory entries.\n+\n+To start, we use a new repository extension, `extensions.sparseIndex`, to\n+allow inserting sparse-directory entries into indexes with file format\n+versions 2, 3, and 4. This prevents Git versions that do not understand\n+the sparse-index from operating on one, but it also prevents other\n+operations that do not use the index at all. A new format, index v5, will\n+be introduced that includes sparse-directory entries by default. It might\n+also introduce other features that have been considered for improving the\n+index, as well.\n+\n+Next, consumers of the index will be guarded against operating on a\n+sparse-index by inserting calls to `ensure_full_index()` or\n+`expand_index_to_path()`. After these guards are in place, we can begin\n+leaving sparse-directory entries in the in-memory index structure.\n+\n+Even after inserting these guards, we will keep expanding sparse-indexes\n+for most Git commands using the `command_requires_full_index` repository\n+setting. This setting will be on by default and disabled one builtin at a\n+time until we have sufficient confidence that all of the index operations\n+are properly guarded.\n+\n+To complete this phase, the commands `git status` and `git add` will be\n+integrated with the sparse-index so that they operate with O(Populated)\n+performance. They will be carefully tested for operations within and\n+outside the sparse-checkout definition.\n+\n+Phase II: Careful integrations\n+------------------------------\n+\n+This phase focuses on ensuring that all index extensions and APIs work\n+well with a sparse-index. This requires significant increases to our test\n+coverage, especially for operations that interact with the working\n+directory outside of the sparse-checkout definition. Some of these\n+behaviors may not be the desirable ones, such as some tests already\n+marked for failure in `t1092-sparse-checkout-compatibility.sh`.\n+\n+The index extensions that may require special integrations are:\n+\n+* FS Monitor\n+* Untracked cache\n+\n+While integrating with these features, we should look for patterns that\n+might lead to better APIs for interacting with the index. Coalescing\n+common usage patterns into an API call can reduce the number of places\n+where sparse-directories need to be handled carefully.\n+\n+Phase III: Important command speedups\n+-------------------------------------\n+\n+At this point, the patterns for testing and implementing sparse-directory\n+logic should be relatively stable. This phase focuses on updating some of\n+the most common builtins that use the index to operate as O(Populated).\n+Here is a potential list of commands that could be valuable to integrate\n+at this point:\n+\n+* `git commit`\n+* `git checkout`\n+* `git merge`\n+* `git rebase`\n+\n+Along with `git status` and `git add`, these commands cover the majority\n+of users' interactions with the working directory. In addition, we can\n+integrate with these commands:\n+\n+* `git grep`\n+* `git rm`\n+\n+These have been proposed as some whose behavior could change when in a\n+repo with a sparse-checkout definition. It would be good to include this\n+behavior automatically when using a sparse-index. Some clarity is needed\n+to make the behavior switch clear to the user.\n+\n+This phase is the first where parallel work might be possible without too\n+much conflicts between topics.\n+\n+Phase IV: The long tail\n+-----------------------\n+\n+This last phase is less a \"phase\" and more \"the new normal\" after all of\n+the previous work.\n+\n+To start, the `command_requires_full_index` option could be removed in\n+favor of expanding only when hitting an API guard.\n+\n+There are many Git commands that could use special attention to operate as\n+O(Populated), while some might be so rare that it is acceptable to leave\n+them with additional overhead when a sparse-index is present.\n+\n+Here are some commands that might be useful to update:\n+\n+* `git sparse-checkout set`\n+* `git am`\n+* `git clean`\n+* `git stash`\n-- \ngitgitgadget\n\n"},{"id":"417597","messageId":"6e783c88821e3b86f6ce976e5673dc1df8992c8f.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 03/20] t1092: clean up script quoting","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:12Z","receivedAt":"2021-02-23T20:15: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\nThis test was introduced in 19a0acc83e4 (t1092: test interesting\nsparse-checkout scenarios, 2021-01-23), but these issues with quoting\nwere not noticed until starting this follow-up series. The old mechanism\nwould drop quoting such as in\n\n   test_all_match git commit -m \"touch README.md\"\n\nThe above happened to work because README.md is a file in the\nrepository, so 'git commit -m touch REAMDE.md' would succeed by\naccident.\n\nOther cases included quoting for no good reason, so clean that up now.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/t1092-sparse-checkout-compatibility.sh | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 8cd3e5a8d227..3725d3997e70 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -96,20 +96,20 @@ init_repos () {\n run_on_sparse () {\n \t(\n \t\tcd sparse-checkout &&\n-\t\t$* >../sparse-checkout-out 2>../sparse-checkout-err\n+\t\t\"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n \t)\n }\n \n run_on_all () {\n \t(\n \t\tcd full-checkout &&\n-\t\t$* >../full-checkout-out 2>../full-checkout-err\n+\t\t\"$@\" >../full-checkout-out 2>../full-checkout-err\n \t) &&\n-\trun_on_sparse $*\n+\trun_on_sparse \"$@\"\n }\n \n test_all_match () {\n-\trun_on_all $* &&\n+\trun_on_all \"$@\" &&\n \ttest_cmp full-checkout-out sparse-checkout-out &&\n \ttest_cmp full-checkout-err sparse-checkout-err\n }\n@@ -119,7 +119,7 @@ test_expect_success 'status with options' '\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git status --porcelain=v2 -z -u &&\n \ttest_all_match git status --porcelain=v2 -uno &&\n-\trun_on_all \"touch README.md\" &&\n+\trun_on_all touch README.md &&\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git status --porcelain=v2 -z -u &&\n \ttest_all_match git status --porcelain=v2 -uno &&\n@@ -135,7 +135,7 @@ test_expect_success 'add, commit, checkout' '\n \twrite_script edit-contents <<-\\EOF &&\n \techo text >>$1\n \tEOF\n-\trun_on_all \"../edit-contents README.md\" &&\n+\trun_on_all ../edit-contents README.md &&\n \n \ttest_all_match git add README.md &&\n \ttest_all_match git status --porcelain=v2 &&\n@@ -144,7 +144,7 @@ test_expect_success 'add, commit, checkout' '\n \ttest_all_match git checkout HEAD~1 &&\n \ttest_all_match git checkout - &&\n \n-\trun_on_all \"../edit-contents README.md\" &&\n+\trun_on_all ../edit-contents README.md &&\n \n \ttest_all_match git add -A &&\n \ttest_all_match git status --porcelain=v2 &&\n@@ -153,7 +153,7 @@ test_expect_success 'add, commit, checkout' '\n \ttest_all_match git checkout HEAD~1 &&\n \ttest_all_match git checkout - &&\n \n-\trun_on_all \"../edit-contents deep/newfile\" &&\n+\trun_on_all ../edit-contents deep/newfile &&\n \n \ttest_all_match git status --porcelain=v2 -uno &&\n \ttest_all_match git status --porcelain=v2 &&\n@@ -186,7 +186,7 @@ test_expect_success 'diff --staged' '\n \twrite_script edit-contents <<-\\EOF &&\n \techo text >>README.md\n \tEOF\n-\trun_on_all \"../edit-contents\" &&\n+\trun_on_all ../edit-contents &&\n \n \ttest_all_match git diff &&\n \ttest_all_match git diff --staged &&\n@@ -280,7 +280,7 @@ test_expect_success 'clean' '\n \techo bogus >>.gitignore &&\n \trun_on_all cp ../.gitignore . &&\n \ttest_all_match git add .gitignore &&\n-\ttest_all_match git commit -m ignore-bogus-files &&\n+\ttest_all_match git commit -m \"ignore bogus files\" &&\n \n \trun_on_sparse mkdir folder1 &&\n \trun_on_all touch folder1/bogus &&\n-- \ngitgitgadget\n\n"},{"id":"417598","messageId":"a8c6322a3dbe1130dd2026b600a896e86d54a95d.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 02/20] t/perf: add performance test for sparse operations","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:11Z","receivedAt":"2021-02-23T20:15: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\nCreate a test script that takes the default performance test (the Git\ncodebase) and multiplies it by 256 using four layers of duplicated\ntrees of width four. This results in nearly one million blob entries in\nthe index. Then, we can clone this repository with sparse-checkout\npatterns that demonstrate four copies of the initial repository. Each\nclone will use a different index format or mode so peformance can be\ntested across the different options.\n\nNote that the initial repo is stripped of submodules before doing the\ncopies. This preserves the expected data shape of the sparse index,\nbecause directories containing submodules are not collapsed to a sparse\ndirectory entry.\n\nRun a few Git commands on these clones, especially those that use the\nindex (status, add, commit).\n\nHere are the results on my Linux machine:\n\nTest\n--------------------------------------------------------------\n2000.2: git status (full-index-v3)             0.37(0.30+0.09)\n2000.3: git status (full-index-v4)             0.39(0.32+0.10)\n2000.4: git add -A (full-index-v3)             1.42(1.06+0.20)\n2000.5: git add -A (full-index-v4)             1.26(0.98+0.16)\n2000.6: git add . (full-index-v3)              1.40(1.04+0.18)\n2000.7: git add . (full-index-v4)              1.26(0.98+0.17)\n2000.8: git commit -a -m A (full-index-v3)     1.42(1.11+0.16)\n2000.9: git commit -a -m A (full-index-v4)     1.33(1.08+0.16)\n\nIt is perhaps noteworthy that there is an improvement when using index\nversion 4. This is because the v3 index uses 108 MiB while the v4\nindex uses 80 MiB. Since the repeated portions of the directories are\nvery short (f3/f1/f2, for example) this ratio is less pronounced than in\nsimilarly-sized real repositories.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/perf/p2000-sparse-operations.sh | 87 +++++++++++++++++++++++++++++++\n 1 file changed, 87 insertions(+)\n create mode 100755 t/perf/p2000-sparse-operations.sh\n\ndiff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh\nnew file mode 100755\nindex 000000000000..52597683376e\n--- /dev/null\n+++ b/t/perf/p2000-sparse-operations.sh\n@@ -0,0 +1,87 @@\n+#!/bin/sh\n+\n+test_description=\"test performance of Git operations using the index\"\n+\n+. ./perf-lib.sh\n+\n+test_perf_default_repo\n+\n+SPARSE_CONE=f2/f4/f1\n+\n+test_expect_success 'setup repo and indexes' '\n+\tgit reset --hard HEAD &&\n+\t# Remove submodules from the example repo, because our\n+\t# duplication of the entire repo creates an unlikly data shape.\n+\tgit config --file .gitmodules --get-regexp \"submodule.*.path\" >modules &&\n+\trm -f .gitmodules &&\n+\tgit add .gitmodules &&\n+\tfor module in $(awk \"{print \\$2}\" modules)\n+\tdo\n+\t\tgit rm $module || return 1\n+\tdone &&\n+\tgit add . &&\n+\tgit commit -m \"remove submodules\" &&\n+\n+\techo bogus >a &&\n+\tcp a b &&\n+\tgit add a b &&\n+\tgit commit -m \"level 0\" &&\n+\tBLOB=$(git rev-parse HEAD:a) &&\n+\tOLD_COMMIT=$(git rev-parse HEAD) &&\n+\tOLD_TREE=$(git rev-parse HEAD^{tree}) &&\n+\n+\tfor i in $(test_seq 1 4)\n+\tdo\n+\t\tcat >in <<-EOF &&\n+\t\t\t100755 blob $BLOB\ta\n+\t\t\t040000 tree $OLD_TREE\tf1\n+\t\t\t040000 tree $OLD_TREE\tf2\n+\t\t\t040000 tree $OLD_TREE\tf3\n+\t\t\t040000 tree $OLD_TREE\tf4\n+\t\tEOF\n+\t\tNEW_TREE=$(git mktree <in) &&\n+\t\tNEW_COMMIT=$(git commit-tree $NEW_TREE -p $OLD_COMMIT -m \"level $i\") &&\n+\t\tOLD_TREE=$NEW_TREE &&\n+\t\tOLD_COMMIT=$NEW_COMMIT || return 1\n+\tdone &&\n+\n+\tgit sparse-checkout init --cone &&\n+\tgit branch -f wide $OLD_COMMIT &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v3 &&\n+\t(\n+\t\tcd full-index-v3 &&\n+\t\tgit sparse-checkout init --cone &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 3 &&\n+\t\tgit update-index --index-version=3\n+\t) &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v4 &&\n+\t(\n+\t\tcd full-index-v4 &&\n+\t\tgit sparse-checkout init --cone &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 4 &&\n+\t\tgit update-index --index-version=4\n+\t)\n+'\n+\n+test_perf_on_all () {\n+\tcommand=\"$@\"\n+\tfor repo in full-index-v3 full-index-v4\n+\tdo\n+\t\ttest_perf \"$command ($repo)\" \"\n+\t\t\t(\n+\t\t\t\tcd $repo &&\n+\t\t\t\techo >>$SPARSE_CONE/a &&\n+\t\t\t\t$command\n+\t\t\t)\n+\t\t\"\n+\tdone\n+}\n+\n+test_perf_on_all git status\n+test_perf_on_all git add -A\n+test_perf_on_all git add .\n+test_perf_on_all git commit -a -m A\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"417599","messageId":"01da4c48a1fa94188faf45ab1e23b98ecb4164d5.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 04/20] sparse-index: add guard to ensure full index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:13Z","receivedAt":"2021-02-23T20:15:22Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nUpcoming changes will introduce modifications to the index format that\nallow sparse directories. It will be useful to have a mechanism for\nconverting those sparse index files into full indexes by walking the\ntree at those sparse directories. Name this method ensure_full_index()\nas it will guarantee that the index is fully expanded.\n\nThis method is not implemented yet, and instead we focus on the\nscaffolding to declare it and call it at the appropriate time.\n\nAdd a 'command_requires_full_index' member to struct repo_settings. This\nwill be an indicator that we need the index in full mode to do certain\nindex operations. This starts as being true for every command, then we\nwill set it to false as some commands integrate with sparse indexes.\n\nIf 'command_requires_full_index' is true, then we will immediately\nexpand a sparse index to a full one upon reading from disk. This\nsuffices for now, but we will want to add more callers to\nensure_full_index() later.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Makefile        |  1 +\n repo-settings.c |  8 ++++++++\n repository.c    | 11 ++++++++++-\n repository.h    |  2 ++\n sparse-index.c  |  8 ++++++++\n sparse-index.h  |  7 +++++++\n 6 files changed, 36 insertions(+), 1 deletion(-)\n create mode 100644 sparse-index.c\n create mode 100644 sparse-index.h\n\ndiff --git a/Makefile b/Makefile\nindex 5a239cac20e3..3bf61699238d 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -980,6 +980,7 @@ LIB_OBJS += setup.o\n LIB_OBJS += shallow.o\n LIB_OBJS += sideband.o\n LIB_OBJS += sigchain.o\n+LIB_OBJS += sparse-index.o\n LIB_OBJS += split-index.o\n LIB_OBJS += stable-qsort.o\n LIB_OBJS += strbuf.o\ndiff --git a/repo-settings.c b/repo-settings.c\nindex f7fff0f5ab83..d63569e4041e 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -77,4 +77,12 @@ void prepare_repo_settings(struct repository *r)\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+\t/*\n+\t * This setting guards all index reads to require a full index\n+\t * over a sparse index. After suitable guards are placed in the\n+\t * codebase around uses of the index, this setting will be\n+\t * removed.\n+\t */\n+\tr->settings.command_requires_full_index = 1;\n }\ndiff --git a/repository.c b/repository.c\nindex c98298acd017..a8acae002f71 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -10,6 +10,7 @@\n #include \"object.h\"\n #include \"lockfile.h\"\n #include \"submodule-config.h\"\n+#include \"sparse-index.h\"\n \n /* The main repository */\n static struct repository the_repo;\n@@ -261,6 +262,8 @@ void repo_clear(struct repository *repo)\n \n int repo_read_index(struct repository *repo)\n {\n+\tint res;\n+\n \tif (!repo->index)\n \t\trepo->index = xcalloc(1, sizeof(*repo->index));\n \n@@ -270,7 +273,13 @@ int repo_read_index(struct repository *repo)\n \telse if (repo->index->repo != repo)\n \t\tBUG(\"repo's index should point back at itself\");\n \n-\treturn read_index_from(repo->index, repo->index_file, repo->gitdir);\n+\tres = read_index_from(repo->index, repo->index_file, repo->gitdir);\n+\n+\tprepare_repo_settings(repo);\n+\tif (repo->settings.command_requires_full_index)\n+\t\tensure_full_index(repo->index);\n+\n+\treturn res;\n }\n \n int repo_hold_locked_index(struct repository *repo,\ndiff --git a/repository.h b/repository.h\nindex b385ca3c94b6..e06a23015697 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -41,6 +41,8 @@ struct repo_settings {\n \tenum fetch_negotiation_setting fetch_negotiation_algorithm;\n \n \tint core_multi_pack_index;\n+\n+\tunsigned command_requires_full_index:1;\n };\n \n struct repository {\ndiff --git a/sparse-index.c b/sparse-index.c\nnew file mode 100644\nindex 000000000000..82183ead563b\n--- /dev/null\n+++ b/sparse-index.c\n@@ -0,0 +1,8 @@\n+#include \"cache.h\"\n+#include \"repository.h\"\n+#include \"sparse-index.h\"\n+\n+void ensure_full_index(struct index_state *istate)\n+{\n+\t/* intentionally left blank */\n+}\ndiff --git a/sparse-index.h b/sparse-index.h\nnew file mode 100644\nindex 000000000000..09a20d036c46\n--- /dev/null\n+++ b/sparse-index.h\n@@ -0,0 +1,7 @@\n+#ifndef SPARSE_INDEX_H__\n+#define SPARSE_INDEX_H__\n+\n+struct index_state;\n+void ensure_full_index(struct index_state *istate);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"417600","messageId":"2b83989fbcd3d464a3172eeb7cfea2e06e4f3785.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 05/20] sparse-index: implement ensure_full_index()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:14Z","receivedAt":"2021-02-23T20:15: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\nWe will mark an in-memory index_state as having sparse directory entries\nwith the sparse_index bit. These currently cannot exist, but we will add\na mechanism for collapsing a full index to a sparse one in a later\nchange. That will happen at write time, so we must first allow parsing\nthe format before writing it.\n\nCommands or methods that require a full index in order to operate can\ncall ensure_full_index() to expand that index in-memory. This requires\nparsing trees using that index's repository.\n\nSparse directory entries have a specific 'ce_mode' value. The macro\nS_ISSPARSEDIR(ce->ce_mode) can check if a cache_entry 'ce' has this type.\nThis ce_mode is not possible with the existing index formats, so we don't\nalso verify all properties of a sparse-directory entry, which are:\n\n 1. ce->ce_mode == 0040000\n 2. ce->flags & CE_SKIP_WORKTREE is true\n 3. ce->name[ce->namelen - 1] == '/' (ends in dir separator)\n 4. ce->oid references a tree object.\n\nThese are all semi-enforced in ensure_full_index() to some extent. Any\ndeviation will cause a warning at minimum or a failure in the worst\ncase.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache.h        |  7 +++-\n read-cache.c   |  9 +++++\n sparse-index.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++++-\n 3 files changed, 109 insertions(+), 2 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex d92814961405..1336c8d7435e 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -204,6 +204,8 @@ struct cache_entry {\n #error \"CE_EXTENDED_FLAGS out of range\"\n #endif\n \n+#define S_ISSPARSEDIR(m) ((m) == S_IFDIR)\n+\n /* Forward structure decls */\n struct pathspec;\n struct child_process;\n@@ -319,7 +321,8 @@ struct index_state {\n \t\t drop_cache_tree : 1,\n \t\t updated_workdir : 1,\n \t\t updated_skipworktree : 1,\n-\t\t fsmonitor_has_run_once : 1;\n+\t\t fsmonitor_has_run_once : 1,\n+\t\t sparse_index : 1;\n \tstruct hashmap name_hash;\n \tstruct hashmap dir_hash;\n \tstruct object_id oid;\n@@ -722,6 +725,8 @@ int read_index_from(struct index_state *, const char *path,\n \t\t    const char *gitdir);\n int is_index_unborn(struct index_state *);\n \n+void ensure_full_index(struct index_state *istate);\n+\n /* For use with `write_locked_index()`. */\n #define COMMIT_LOCK\t\t(1 << 0)\n #define SKIP_IF_UNCHANGED\t(1 << 1)\ndiff --git a/read-cache.c b/read-cache.c\nindex 29144cf879e7..97dbf2434f30 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -101,6 +101,9 @@ static const char *alternate_index_output;\n \n static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n {\n+\tif (S_ISSPARSEDIR(ce->ce_mode))\n+\t\tistate->sparse_index = 1;\n+\n \tistate->cache[nr] = ce;\n \tadd_name_hash(istate, ce);\n }\n@@ -2255,6 +2258,12 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)\n \ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\",\n \t\t\t   istate->cache_nr);\n \n+\tif (!istate->repo)\n+\t\tistate->repo = the_repository;\n+\tprepare_repo_settings(istate->repo);\n+\tif (istate->repo->settings.command_requires_full_index)\n+\t\tensure_full_index(istate);\n+\n \treturn istate->cache_nr;\n \n unmap:\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 82183ead563b..316cb949b74b 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -1,8 +1,101 @@\n #include \"cache.h\"\n #include \"repository.h\"\n #include \"sparse-index.h\"\n+#include \"tree.h\"\n+#include \"pathspec.h\"\n+#include \"trace2.h\"\n+\n+static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n+{\n+\tALLOC_GROW(istate->cache, nr + 1, istate->cache_alloc);\n+\n+\tistate->cache[nr] = ce;\n+\tadd_name_hash(istate, ce);\n+}\n+\n+static int add_path_to_index(const struct object_id *oid,\n+\t\t\t\tstruct strbuf *base, const char *path,\n+\t\t\t\tunsigned int mode, int stage, void *context)\n+{\n+\tstruct index_state *istate = (struct index_state *)context;\n+\tstruct cache_entry *ce;\n+\tsize_t len = base->len;\n+\n+\tif (S_ISDIR(mode))\n+\t\treturn READ_TREE_RECURSIVE;\n+\n+\tstrbuf_addstr(base, path);\n+\n+\tce = make_cache_entry(istate, mode, oid, base->buf, 0, 0);\n+\tce->ce_flags |= CE_SKIP_WORKTREE;\n+\tset_index_entry(istate, istate->cache_nr++, ce);\n+\n+\tstrbuf_setlen(base, len);\n+\treturn 0;\n+}\n \n void ensure_full_index(struct index_state *istate)\n {\n-\t/* intentionally left blank */\n+\tint i;\n+\tstruct index_state *full;\n+\n+\tif (!istate || !istate->sparse_index)\n+\t\treturn;\n+\n+\tif (!istate->repo)\n+\t\tistate->repo = the_repository;\n+\n+\ttrace2_region_enter(\"index\", \"ensure_full_index\", istate->repo);\n+\n+\t/* initialize basics of new index */\n+\tfull = xcalloc(1, sizeof(struct index_state));\n+\tmemcpy(full, istate, sizeof(struct index_state));\n+\n+\t/* then change the necessary things */\n+\tfull->sparse_index = 0;\n+\tfull->cache_alloc = (3 * istate->cache_alloc) / 2;\n+\tfull->cache_nr = 0;\n+\tALLOC_ARRAY(full->cache, full->cache_alloc);\n+\n+\tfor (i = 0; i < istate->cache_nr; i++) {\n+\t\tstruct cache_entry *ce = istate->cache[i];\n+\t\tstruct tree *tree;\n+\t\tstruct pathspec ps;\n+\n+\t\tif (!S_ISSPARSEDIR(ce->ce_mode)) {\n+\t\t\tset_index_entry(full, full->cache_nr++, ce);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!(ce->ce_flags & CE_SKIP_WORKTREE))\n+\t\t\twarning(_(\"index entry is a directory, but not sparse (%08x)\"),\n+\t\t\t\tce->ce_flags);\n+\n+\t\t/* recursively walk into cd->name */\n+\t\ttree = lookup_tree(istate->repo, &ce->oid);\n+\n+\t\tmemset(&ps, 0, sizeof(ps));\n+\t\tps.recursive = 1;\n+\t\tps.has_wildcard = 1;\n+\t\tps.max_depth = -1;\n+\n+\t\tread_tree_recursive(istate->repo, tree,\n+\t\t\t\t    ce->name, strlen(ce->name),\n+\t\t\t\t    0, &ps,\n+\t\t\t\t    add_path_to_index, full);\n+\n+\t\t/* free directory entries. full entries are re-used */\n+\t\tdiscard_cache_entry(ce);\n+\t}\n+\n+\t/* Copy back into original index. */\n+\tmemcpy(&istate->name_hash, &full->name_hash, sizeof(full->name_hash));\n+\tistate->sparse_index = 0;\n+\tfree(istate->cache);\n+\tistate->cache = full->cache;\n+\tistate->cache_nr = full->cache_nr;\n+\tistate->cache_alloc = full->cache_alloc;\n+\n+\tfree(full);\n+\n+\ttrace2_region_leave(\"index\", \"ensure_full_index\", istate->repo);\n }\n-- \ngitgitgadget\n\n"},{"id":"417601","messageId":"7d4627574bb8dc3e3a6d0ebd62dc2855ed61a904.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 14/20] sparse-index: check index conversion happens","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:23Z","receivedAt":"2021-02-23T20:15: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\nAdd a test case that uses test_region to ensure that we are truly\nexpanding a sparse index to a full one, then converting back to sparse\nwhen writing the index. As we integrate more Git commands with the\nsparse index, we will convert these commands to check that we do _not_\nconvert the sparse index to a full index and instead stay sparse the\nentire time.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/t1092-sparse-checkout-compatibility.sh | 18 ++++++++++++++++++\n 1 file changed, 18 insertions(+)\n\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex b38fab6455d9..bfc9e28ef0e1 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -391,4 +391,22 @@ test_expect_success 'submodule handling' '\n \tgrep \"160000 commit $(git -C initial-repo rev-parse HEAD)\tmodules/sub\" cache\n '\n \n+test_expect_success 'sparse-index is expanded and converted back' '\n+\tinit_repos &&\n+\n+\t(\n+\t\tGIT_TEST_SPARSE_INDEX=1 &&\n+\t\texport GIT_TEST_SPARSE_INDEX &&\n+\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" reset --hard &&\n+\t\ttest_region index convert_to_sparse trace2.txt &&\n+\t\ttest_region index ensure_full_index trace2.txt &&\n+\n+\t\trm trace2.txt &&\n+\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" status -uno &&\n+\t\ttest_region index ensure_full_index trace2.txt\n+\t)\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"417602","messageId":"6d6b230e3318007150aebefebc16dfb8b9b6c401.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 16/20] sparse-checkout: toggle sparse index from builtin","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:25Z","receivedAt":"2021-02-23T20:16:00Z","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 sparse index extension is used to signal that index writes should be\nin sparse mode. This was only updated using GIT_TEST_SPARSE_INDEX=1.\n\nAdd a '--[no-]sparse-index' option to 'git sparse-checkout init' that\nspecifies if the sparse index should be used. It also updates the index\nto use the correct format, either way. Add a warning in the\ndocumentation that the use of a repository extension might reduce\ncompatibility with third-party tools. 'git sparse-checkout init' already\nsets extension.worktreeConfig, which places most sparse-checkout users\noutside of the scope of most third-party tools.\n\nUpdate t1092-sparse-checkout-compatibility.sh to use this CLI instead of\nGIT_TEST_SPARSE_INDEX=1.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-sparse-checkout.txt    | 14 +++++++++\n builtin/sparse-checkout.c                | 17 ++++++++++-\n sparse-index.c                           | 37 +++++++++++++++--------\n sparse-index.h                           |  3 ++\n t/t1092-sparse-checkout-compatibility.sh | 38 +++++++++++-------------\n 5 files changed, 76 insertions(+), 33 deletions(-)\n\ndiff --git a/Documentation/git-sparse-checkout.txt b/Documentation/git-sparse-checkout.txt\nindex a0eeaeb02ee3..b51b8450cfd9 100644\n--- a/Documentation/git-sparse-checkout.txt\n+++ b/Documentation/git-sparse-checkout.txt\n@@ -45,6 +45,20 @@ To avoid interfering with other worktrees, it first enables the\n When `--cone` is provided, the `core.sparseCheckoutCone` setting is\n also set, allowing for better performance with a limited set of\n patterns (see 'CONE PATTERN SET' below).\n++\n+Use the `--[no-]sparse-index` option to toggle the use of the sparse\n+index format. This reduces the size of the index to be more closely\n+aligned with your sparse-checkout definition. This can have significant\n+performance advantages for commands such as `git status` or `git add`.\n+This feature is still experimental. Some commands might be slower with\n+a sparse index until they are properly integrated with the feature.\n++\n+**WARNING:** Using a sparse index requires modifying the index in a way\n+that is not completely understood by other tools. Enabling sparse index\n+enables the `extensions.spareseIndex` config value, which might cause\n+other tools to stop working with your repository. If you have trouble with\n+this compatibility, then run `git sparse-checkout sparse-index disable` to\n+remove this config and rewrite your index to not be sparse.\n \n 'set'::\n \tWrite a set of patterns to the sparse-checkout file, as given as\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex e00b82af727b..ca63e2c64e95 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -14,6 +14,7 @@\n #include \"unpack-trees.h\"\n #include \"wt-status.h\"\n #include \"quote.h\"\n+#include \"sparse-index.h\"\n \n static const char *empty_base = \"\";\n \n@@ -283,12 +284,13 @@ static int set_config(enum sparse_checkout_mode mode)\n }\n \n static char const * const builtin_sparse_checkout_init_usage[] = {\n-\tN_(\"git sparse-checkout init [--cone]\"),\n+\tN_(\"git sparse-checkout init [--cone] [--[no-]sparse-index]\"),\n \tNULL\n };\n \n static struct sparse_checkout_init_opts {\n \tint cone_mode;\n+\tint sparse_index;\n } init_opts;\n \n static int sparse_checkout_init(int argc, const char **argv)\n@@ -303,11 +305,15 @@ static int sparse_checkout_init(int argc, const char **argv)\n \tstatic struct option builtin_sparse_checkout_init_options[] = {\n \t\tOPT_BOOL(0, \"cone\", &init_opts.cone_mode,\n \t\t\t N_(\"initialize the sparse-checkout in cone mode\")),\n+\t\tOPT_BOOL(0, \"sparse-index\", &init_opts.sparse_index,\n+\t\t\t N_(\"toggle the use of a sparse index\")),\n \t\tOPT_END(),\n \t};\n \n \trepo_read_index(the_repository);\n \n+\tinit_opts.sparse_index = -1;\n+\n \targc = parse_options(argc, argv, NULL,\n \t\t\t     builtin_sparse_checkout_init_options,\n \t\t\t     builtin_sparse_checkout_init_usage, 0);\n@@ -326,6 +332,15 @@ static int sparse_checkout_init(int argc, const char **argv)\n \tsparse_filename = get_sparse_checkout_filename();\n \tres = add_patterns_from_file_to_list(sparse_filename, \"\", 0, &pl, NULL);\n \n+\tif (init_opts.sparse_index >= 0) {\n+\t\tif (set_sparse_index_config(the_repository, init_opts.sparse_index) < 0)\n+\t\t\tdie(_(\"failed to modify sparse-index config\"));\n+\n+\t\t/* force an index rewrite */\n+\t\trepo_read_index(the_repository);\n+\t\tthe_repository->index->updated_workdir = 1;\n+\t}\n+\n \t/* If we already have a sparse-checkout file, use it. */\n \tif (res >= 0) {\n \t\tfree(sparse_filename);\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 97b0d0c57857..a991c5331e9e 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -104,23 +104,37 @@ static int convert_to_sparse_rec(struct index_state *istate,\n \n static int enable_sparse_index(struct repository *repo)\n {\n-\tconst char *config_path = repo_git_path(repo, \"config.worktree\");\n+\tint res;\n \n \tif (upgrade_repository_format(1) < 0) {\n \t\twarning(_(\"unable to upgrade repository format to enable sparse-index\"));\n \t\treturn -1;\n \t}\n-\tgit_config_set_in_file_gently(config_path,\n-\t\t\t\t      \"extensions.sparseIndex\",\n-\t\t\t\t      \"true\");\n+\tres = git_config_set_gently(\"extensions.sparseindex\", \"true\");\n \n \tprepare_repo_settings(repo);\n \trepo->settings.sparse_index = 1;\n-\treturn 0;\n+\treturn res;\n+}\n+\n+int set_sparse_index_config(struct repository *repo, int enable)\n+{\n+\tint res;\n+\n+\tif (enable)\n+\t\treturn enable_sparse_index(repo);\n+\n+\t/* Don't downgrade repository format, just remove the extension. */\n+\tres = git_config_set_gently(\"extensions.sparseindex\", NULL);\n+\n+\tprepare_repo_settings(repo);\n+\trepo->settings.sparse_index = 0;\n+\treturn res;\n }\n \n int convert_to_sparse(struct index_state *istate)\n {\n+\tint test_env;\n \tif (istate->split_index || istate->sparse_index ||\n \t    !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n \t\treturn 0;\n@@ -129,14 +143,13 @@ int convert_to_sparse(struct index_state *istate)\n \t\tistate->repo = the_repository;\n \n \t/*\n-\t * The GIT_TEST_SPARSE_INDEX environment variable triggers the\n-\t * extensions.sparseIndex config variable to be on.\n+\t * If GIT_TEST_SPARSE_INDEX=1, then trigger extensions.sparseIndex\n+\t * to be fully enabled. If GIT_TEST_SPARSE_INDEX=0 (set explicitly),\n+\t * then purposefully disable the setting.\n \t */\n-\tif (git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0)) {\n-\t\tint err = enable_sparse_index(istate->repo);\n-\t\tif (err < 0)\n-\t\t\treturn err;\n-\t}\n+\ttest_env = git_env_bool(\"GIT_TEST_SPARSE_INDEX\", -1);\n+\tif (test_env >= 0)\n+\t\tset_sparse_index_config(istate->repo, test_env);\n \n \t/*\n \t * Only convert to sparse if extensions.sparseIndex is set.\ndiff --git a/sparse-index.h b/sparse-index.h\nindex 64380e121d80..39dcc859735e 100644\n--- a/sparse-index.h\n+++ b/sparse-index.h\n@@ -5,4 +5,7 @@ struct index_state;\n void ensure_full_index(struct index_state *istate);\n int convert_to_sparse(struct index_state *istate);\n \n+struct repository;\n+int set_sparse_index_config(struct repository *repo, int enable);\n+\n #endif\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex bfc9e28ef0e1..9c2bc4d25f66 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -4,6 +4,7 @@ test_description='compare full workdir to sparse workdir'\n \n GIT_TEST_CHECK_CACHE_TREE=0\n GIT_TEST_SPLIT_INDEX=0\n+GIT_TEST_SPARSE_INDEX=\n \n . ./test-lib.sh\n \n@@ -98,25 +99,26 @@ init_repos () {\n \t# initialize sparse-checkout definitions\n \tgit -C sparse-checkout sparse-checkout init --cone &&\n \tgit -C sparse-checkout sparse-checkout set deep &&\n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout init --cone &&\n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep\n+\tgit -C sparse-index sparse-checkout init --cone --sparse-index &&\n+\ttest_cmp_config -C sparse-index true extensions.sparseindex &&\n+\tgit -C sparse-index sparse-checkout set deep\n }\n \n run_on_sparse () {\n \t(\n \t\tcd sparse-checkout &&\n-\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n+\t\t\"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n \t) &&\n \t(\n \t\tcd sparse-index &&\n-\t\tGIT_TEST_SPARSE_INDEX=1 \"$@\" >../sparse-index-out 2>../sparse-index-err\n+\t\t\"$@\" >../sparse-index-out 2>../sparse-index-err\n \t)\n }\n \n run_on_all () {\n \t(\n \t\tcd full-checkout &&\n-\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../full-checkout-out 2>../full-checkout-err\n+\t\t\"$@\" >../full-checkout-out 2>../full-checkout-err\n \t) &&\n \trun_on_sparse \"$@\"\n }\n@@ -146,7 +148,7 @@ test_expect_success 'sparse-index contents' '\n \t\t\t|| return 1\n \tdone &&\n \n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set folder1 &&\n+\tgit -C sparse-index sparse-checkout set folder1 &&\n \n \ttest-tool -C sparse-index read-cache --table >cache &&\n \tfor dir in deep folder2 x\n@@ -156,7 +158,7 @@ test_expect_success 'sparse-index contents' '\n \t\t\t|| return 1\n \tdone &&\n \n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep/deeper1 &&\n+\tgit -C sparse-index sparse-checkout set deep/deeper1 &&\n \n \ttest-tool -C sparse-index read-cache --table >cache &&\n \tfor dir in deep/deeper2 folder1 folder2 x\n@@ -394,19 +396,15 @@ test_expect_success 'submodule handling' '\n test_expect_success 'sparse-index is expanded and converted back' '\n \tinit_repos &&\n \n-\t(\n-\t\tGIT_TEST_SPARSE_INDEX=1 &&\n-\t\texport GIT_TEST_SPARSE_INDEX &&\n-\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n-\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" reset --hard &&\n-\t\ttest_region index convert_to_sparse trace2.txt &&\n-\t\ttest_region index ensure_full_index trace2.txt &&\n-\n-\t\trm trace2.txt &&\n-\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n-\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" status -uno &&\n-\t\ttest_region index ensure_full_index trace2.txt\n-\t)\n+\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\tgit -C sparse-index -c core.fsmonitor=\"\" reset --hard &&\n+\ttest_region index convert_to_sparse trace2.txt &&\n+\ttest_region index ensure_full_index trace2.txt &&\n+\n+\trm trace2.txt &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\tgit -C sparse-index -c core.fsmonitor=\"\" status -uno &&\n+\ttest_region index ensure_full_index trace2.txt\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"417603","messageId":"3d92df7a0cf9655dd34895f106cfac26ea44ad94.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 07/20] test-read-cache: print cache entries with --table","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:16Z","receivedAt":"2021-02-23T20:16:05Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThis table is helpful for discovering data in the index to ensure it is\nbeing written correctly, especially as we build and test the\nsparse-index. This table includes an output format similar to 'git\nls-tree', but should not be compared to that directly. The biggest\nreasons are that 'git ls-tree' includes a tree entry for every\nsubdirectory, even those that would not appear as a sparse directory in\na sparse-index. Further, 'git ls-tree' does not use a trailing directory\nseparator for its tree rows.\n\nThis does not print the stat() information for the blobs. That could be\nadded in a future change with another option. The tests that are added\nin the next few changes care only about the object types and IDs.\n\nTo make the option parsing slightly more robust, wrap the string\ncomparisons in a loop adapted from test-dir-iterator.c.\n\nCare must be taken with the final check for the 'cnt' variable. We\ncontinue the expectation that the numerical value is the final argument.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/helper/test-read-cache.c | 50 ++++++++++++++++++++++++++++++--------\n 1 file changed, 40 insertions(+), 10 deletions(-)\n\ndiff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c\nindex 244977a29bdf..e4c3492f7d3e 100644\n--- a/t/helper/test-read-cache.c\n+++ b/t/helper/test-read-cache.c\n@@ -2,35 +2,65 @@\n #include \"cache.h\"\n #include \"config.h\"\n \n+static void print_cache_entry(struct cache_entry *ce)\n+{\n+\tprintf(\"%06o \", ce->ce_mode & 0777777);\n+\n+\tif (S_ISSPARSEDIR(ce->ce_mode))\n+\t\tprintf(\"tree \");\n+\telse if (S_ISGITLINK(ce->ce_mode))\n+\t\tprintf(\"commit \");\n+\telse\n+\t\tprintf(\"blob \");\n+\n+\tprintf(\"%s\\t%s\\n\",\n+\t       oid_to_hex(&ce->oid),\n+\t       ce->name);\n+}\n+\n+static void print_cache(struct index_state *cache)\n+{\n+\tint i;\n+\tfor (i = 0; i < the_index.cache_nr; i++)\n+\t\tprint_cache_entry(the_index.cache[i]);\n+}\n+\n int cmd__read_cache(int argc, const char **argv)\n {\n+\tstruct repository *r = the_repository;\n \tint i, cnt = 1;\n \tconst char *name = NULL;\n+\tint table = 0;\n \n-\tif (argc > 1 && skip_prefix(argv[1], \"--print-and-refresh=\", &name)) {\n-\t\targc--;\n-\t\targv++;\n+\tfor (++argv, --argc; *argv && starts_with(*argv, \"--\"); ++argv, --argc) {\n+\t\tif (skip_prefix(*argv, \"--print-and-refresh=\", &name))\n+\t\t\tcontinue;\n+\t\tif (!strcmp(*argv, \"--table\"))\n+\t\t\ttable = 1;\n \t}\n \n-\tif (argc == 2)\n-\t\tcnt = strtol(argv[1], NULL, 0);\n+\tif (argc == 1)\n+\t\tcnt = strtol(argv[0], NULL, 0);\n \tsetup_git_directory();\n \tgit_config(git_default_config, NULL);\n+\n \tfor (i = 0; i < cnt; i++) {\n-\t\tread_cache();\n+\t\trepo_read_index(r);\n \t\tif (name) {\n \t\t\tint pos;\n \n-\t\t\trefresh_index(&the_index, REFRESH_QUIET,\n+\t\t\trefresh_index(r->index, REFRESH_QUIET,\n \t\t\t\t      NULL, NULL, NULL);\n-\t\t\tpos = index_name_pos(&the_index, name, strlen(name));\n+\t\t\tpos = index_name_pos(r->index, name, strlen(name));\n \t\t\tif (pos < 0)\n \t\t\t\tdie(\"%s not in index\", name);\n \t\t\tprintf(\"%s is%s up to date\\n\", name,\n-\t\t\t       ce_uptodate(the_index.cache[pos]) ? \"\" : \" not\");\n+\t\t\t       ce_uptodate(r->index->cache[pos]) ? \"\" : \" not\");\n \t\t\twrite_file(name, \"%d\\n\", i);\n \t\t}\n-\t\tdiscard_cache();\n+\t\tif (table)\n+\t\t\tprint_cache(r->index);\n+\t\tdiscard_index(r->index);\n \t}\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"417604","messageId":"c9910a37579c9a26f7721b60d82060e1766d050c.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 06/20] t1092: compare sparse-checkout to sparse-index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:15Z","receivedAt":"2021-02-23T20:16:10Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nAdd a new 'sparse-index' repo alongside the 'full-checkout' and\n'sparse-checkout' repos in t1092-sparse-checkout-compatibility.sh. Also\nadd run_on_sparse and test_sparse_match helpers. These helpers will be\nused when the sparse index is implemented.\n\nAdd GIT_TEST_SPARSE_INDEX environment variable to enable the\nsparse-index by default. This will be intended to use across the entire\ntest suite, except that it will only affect cases where the\nsparse-checkout feature is enabled.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/README                                 |  3 +++\n t/t1092-sparse-checkout-compatibility.sh | 24 ++++++++++++++++++++----\n 2 files changed, 23 insertions(+), 4 deletions(-)\n\ndiff --git a/t/README b/t/README\nindex 593d4a4e270c..b98bc563aab5 100644\n--- a/t/README\n+++ b/t/README\n@@ -439,6 +439,9 @@ and \"sha256\".\n GIT_TEST_WRITE_REV_INDEX=<boolean>, when true enables the\n 'pack.writeReverseIndex' setting.\n \n+GIT_TEST_SPARSE_INDEX=<boolean>, when true enables index writes to use the\n+sparse-index format by default.\n+\n Naming Tests\n ------------\n \ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 3725d3997e70..71d6f9e4c014 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -7,6 +7,7 @@ test_description='compare full workdir to sparse workdir'\n test_expect_success 'setup' '\n \tgit init initial-repo &&\n \t(\n+\t\tGIT_TEST_SPARSE_INDEX=0 &&\n \t\tcd initial-repo &&\n \t\techo a >a &&\n \t\techo \"after deep\" >e &&\n@@ -87,23 +88,32 @@ init_repos () {\n \n \tcp -r initial-repo sparse-checkout &&\n \tgit -C sparse-checkout reset --hard &&\n-\tgit -C sparse-checkout sparse-checkout init --cone &&\n+\n+\tcp -r initial-repo sparse-index &&\n+\tgit -C sparse-index reset --hard &&\n \n \t# initialize sparse-checkout definitions\n-\tgit -C sparse-checkout sparse-checkout set deep\n+\tgit -C sparse-checkout sparse-checkout init --cone &&\n+\tgit -C sparse-checkout sparse-checkout set deep &&\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout init --cone &&\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep\n }\n \n run_on_sparse () {\n \t(\n \t\tcd sparse-checkout &&\n-\t\t\"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n+\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n+\t) &&\n+\t(\n+\t\tcd sparse-index &&\n+\t\tGIT_TEST_SPARSE_INDEX=1 \"$@\" >../sparse-index-out 2>../sparse-index-err\n \t)\n }\n \n run_on_all () {\n \t(\n \t\tcd full-checkout &&\n-\t\t\"$@\" >../full-checkout-out 2>../full-checkout-err\n+\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../full-checkout-out 2>../full-checkout-err\n \t) &&\n \trun_on_sparse \"$@\"\n }\n@@ -114,6 +124,12 @@ test_all_match () {\n \ttest_cmp full-checkout-err sparse-checkout-err\n }\n \n+test_sparse_match () {\n+\trun_on_sparse $* &&\n+\ttest_cmp sparse-checkout-out sparse-index-out &&\n+\ttest_cmp sparse-checkout-err sparse-index-err\n+}\n+\n test_expect_success 'status with options' '\n \tinit_repos &&\n \ttest_all_match git status --porcelain=v2 &&\n-- \ngitgitgadget\n\n"},{"id":"417605","messageId":"a2d77c23a0cbbe090b4ed6d840f608cffd4248fd.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 11/20] sparse-index: convert from full to sparse","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:20Z","receivedAt":"2021-02-23T20:16:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nIf we have a full index, then we can convert it to a sparse index by\nreplacing directories outside of the sparse cone with sparse directory\nentries. The convert_to_sparse() method does this, when the situation is\nappropriate.\n\nFor now, we avoid converting the index to a sparse index if:\n\n 1. the index is split.\n 2. the index is already sparse.\n 3. sparse-checkout is disabled.\n 4. sparse-checkout does not use cone mode.\n\nFinally, we currently limit the conversion to when the\nGIT_TEST_SPARSE_INDEX environment variable is enabled. A mode using Git\nconfig will be added in a later change.\n\nThe trickiest thing about this conversion is that we might not be able\nto mark a directory as a sparse directory just because it is outside the\nsparse cone. There might be unmerged files within that directory, so we\nneed to look for those. Also, if there is some strange reason why a file\nis not marked with CE_SKIP_WORKTREE, then we should give up on\nconverting that directory. There is still hope that some of its\nsubdirectories might be able to convert to sparse, so we keep looking\ndeeper.\n\nThe conversion process is assisted by the cache-tree extension. This is\ncalculated from the full index if it does not already exist. We then\nabandon the cache-tree as it no longer applies to the newly-sparse\nindex. Thus, this cache-tree will be recalculated in every\nsparse-full-sparse round-trip until we integrate the cache-tree\nextension with the sparse index.\n\nSome Git commands use the index after writing it. For example, 'git add'\nwill update the index, then write it to disk, then read its entries to\nreport information. To keep the in-memory index in a full state after\nwriting, we re-expand it to a full one after the write. This is wasteful\nfor commands that only write the index and do not read from it again,\nbut that is only the case until we make those commands \"sparse aware.\"\n\nWe can compare the behavior of the sparse-index in\nt1092-sparse-checkout-compability.sh by using GIT_TEST_SPARSE_INDEX=1\nwhen operating on the 'sparse-index' repo. We can also compare the two\nsparse repos directly, such as comparing their indexes (when expanded to\nfull in the case of the 'sparse-index' repo). We also verify that the\nindex is actually populated with sparse directory entries.\n\nThe 'checkout and reset (mixed)' test is marked for failure when\ncomparing a sparse repo to a full repo, but we can compare the two\nsparse-checkout cases directly to ensure that we are not changing the\nbehavior when using a sparse index.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache-tree.c                             |   3 +\n cache.h                                  |   2 +\n read-cache.c                             |  26 ++++-\n sparse-index.c                           | 139 +++++++++++++++++++++++\n sparse-index.h                           |   1 +\n t/t1092-sparse-checkout-compatibility.sh |  61 +++++++++-\n 6 files changed, 227 insertions(+), 5 deletions(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 2fb483d3c083..5f07a39e501e 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -6,6 +6,7 @@\n #include \"object-store.h\"\n #include \"replace-object.h\"\n #include \"promisor-remote.h\"\n+#include \"sparse-index.h\"\n \n #ifndef DEBUG_CACHE_TREE\n #define DEBUG_CACHE_TREE 0\n@@ -442,6 +443,8 @@ int cache_tree_update(struct index_state *istate, int flags)\n \tif (i)\n \t\treturn i;\n \n+\tensure_full_index(istate);\n+\n \tif (!istate->cache_tree)\n \t\tistate->cache_tree = cache_tree();\n \ndiff --git a/cache.h b/cache.h\nindex d75b352f38d3..e8b7d3b4fb33 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -251,6 +251,8 @@ static inline unsigned int create_ce_mode(unsigned int mode)\n {\n \tif (S_ISLNK(mode))\n \t\treturn S_IFLNK;\n+\tif (mode == S_IFDIR)\n+\t\treturn S_IFDIR;\n \tif (S_ISDIR(mode) || S_ISGITLINK(mode))\n \t\treturn S_IFGITLINK;\n \treturn S_IFREG | ce_permissions(mode);\ndiff --git a/read-cache.c b/read-cache.c\nindex 97dbf2434f30..67acbf202f4e 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 \"sparse-index.h\"\n \n /* Mask for the name length in ce_flags in the on-disk index */\n \n@@ -1002,8 +1003,14 @@ int verify_path(const char *path, unsigned mode)\n \n \t\t\tc = *path++;\n \t\t\tif ((c == '.' && !verify_dotfile(path, mode)) ||\n-\t\t\t    is_dir_sep(c) || c == '\\0')\n+\t\t\t    is_dir_sep(c))\n \t\t\t\treturn 0;\n+\t\t\t/*\n+\t\t\t * allow terminating directory separators for\n+\t\t\t * sparse directory enries.\n+\t\t\t */\n+\t\t\tif (c == '\\0')\n+\t\t\t\treturn S_ISDIR(mode);\n \t\t} else if (c == '\\\\' && protect_ntfs) {\n \t\t\tif (is_ntfs_dotgit(path))\n \t\t\t\treturn 0;\n@@ -3061,6 +3068,14 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n \t\t\t\t unsigned flags)\n {\n \tint ret;\n+\tint was_full = !istate->sparse_index;\n+\n+\tret = convert_to_sparse(istate);\n+\n+\tif (ret) {\n+\t\twarning(_(\"failed to convert to a sparse-index\"));\n+\t\treturn ret;\n+\t}\n \n \t/*\n \t * TODO trace2: replace \"the_repository\" with the actual repo instance\n@@ -3072,6 +3087,9 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n \ttrace2_region_leave_printf(\"index\", \"do_write_index\", the_repository,\n \t\t\t\t   \"%s\", get_lock_file_path(lock));\n \n+\tif (was_full)\n+\t\tensure_full_index(istate);\n+\n \tif (ret)\n \t\treturn ret;\n \tif (flags & COMMIT_LOCK)\n@@ -3162,9 +3180,10 @@ static int write_shared_index(struct index_state *istate,\n \t\t\t      struct tempfile **temp)\n {\n \tstruct split_index *si = istate->split_index;\n-\tint ret;\n+\tint ret, was_full = !istate->sparse_index;\n \n \tmove_cache_to_base_index(istate);\n+\tconvert_to_sparse(istate);\n \n \ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n \t\t\t\t   the_repository, \"%s\", get_tempfile_path(*temp));\n@@ -3172,6 +3191,9 @@ static int write_shared_index(struct index_state *istate,\n \ttrace2_region_leave_printf(\"index\", \"shared/do_write_index\",\n \t\t\t\t   the_repository, \"%s\", get_tempfile_path(*temp));\n \n+\tif (was_full)\n+\t\tensure_full_index(istate);\n+\n \tif (ret)\n \t\treturn ret;\n \tret = adjust_shared_perm(get_tempfile_path(*temp));\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 316cb949b74b..cb1f85635fbc 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -4,6 +4,145 @@\n #include \"tree.h\"\n #include \"pathspec.h\"\n #include \"trace2.h\"\n+#include \"cache-tree.h\"\n+#include \"config.h\"\n+#include \"dir.h\"\n+#include \"fsmonitor.h\"\n+\n+static struct cache_entry *construct_sparse_dir_entry(\n+\t\t\t\tstruct index_state *istate,\n+\t\t\t\tconst char *sparse_dir,\n+\t\t\t\tstruct cache_tree *tree)\n+{\n+\tstruct cache_entry *de;\n+\n+\tde = make_cache_entry(istate, S_IFDIR, &tree->oid, sparse_dir, 0, 0);\n+\n+\tde->ce_flags |= CE_SKIP_WORKTREE;\n+\treturn de;\n+}\n+\n+/*\n+ * Returns the number of entries \"inserted\" into the index.\n+ */\n+static int convert_to_sparse_rec(struct index_state *istate,\n+\t\t\t\t int num_converted,\n+\t\t\t\t int start, int end,\n+\t\t\t\t const char *ct_path, size_t ct_pathlen,\n+\t\t\t\t struct cache_tree *ct)\n+{\n+\tint i, can_convert = 1;\n+\tint start_converted = num_converted;\n+\tenum pattern_match_result match;\n+\tint dtype;\n+\tstruct strbuf child_path = STRBUF_INIT;\n+\tstruct pattern_list *pl = istate->sparse_checkout_patterns;\n+\n+\t/*\n+\t * Is the current path outside of the sparse cone?\n+\t * Then check if the region can be replaced by a sparse\n+\t * directory entry (everything is sparse and merged).\n+\t */\n+\tmatch = path_matches_pattern_list(ct_path, ct_pathlen,\n+\t\t\t\t\t  NULL, &dtype, pl, istate);\n+\tif (match != NOT_MATCHED)\n+\t\tcan_convert = 0;\n+\n+\tfor (i = start; can_convert && i < end; i++) {\n+\t\tstruct cache_entry *ce = istate->cache[i];\n+\n+\t\tif (ce_stage(ce) ||\n+\t\t    !(ce->ce_flags & CE_SKIP_WORKTREE))\n+\t\t\tcan_convert = 0;\n+\t}\n+\n+\tif (can_convert) {\n+\t\tstruct cache_entry *se;\n+\t\tse = construct_sparse_dir_entry(istate, ct_path, ct);\n+\n+\t\tistate->cache[num_converted++] = se;\n+\t\treturn 1;\n+\t}\n+\n+\tfor (i = start; i < end; ) {\n+\t\tint count, span, pos = -1;\n+\t\tconst char *base, *slash;\n+\t\tstruct cache_entry *ce = istate->cache[i];\n+\n+\t\t/*\n+\t\t * Detect if this is a normal entry oustide of any subtree\n+\t\t * entry.\n+\t\t */\n+\t\tbase = ce->name + ct_pathlen;\n+\t\tslash = strchr(base, '/');\n+\n+\t\tif (slash)\n+\t\t\tpos = cache_tree_subtree_pos(ct, base, slash - base);\n+\n+\t\tif (pos < 0) {\n+\t\t\tistate->cache[num_converted++] = ce;\n+\t\t\ti++;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tstrbuf_setlen(&child_path, 0);\n+\t\tstrbuf_add(&child_path, ce->name, slash - ce->name + 1);\n+\n+\t\tspan = ct->down[pos]->cache_tree->entry_count;\n+\t\tcount = convert_to_sparse_rec(istate,\n+\t\t\t\t\t      num_converted, i, i + span,\n+\t\t\t\t\t      child_path.buf, child_path.len,\n+\t\t\t\t\t      ct->down[pos]->cache_tree);\n+\t\tnum_converted += count;\n+\t\ti += span;\n+\t}\n+\n+\tstrbuf_release(&child_path);\n+\treturn num_converted - start_converted;\n+}\n+\n+int convert_to_sparse(struct index_state *istate)\n+{\n+\tif (istate->split_index || istate->sparse_index ||\n+\t    !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n+\t\treturn 0;\n+\n+\t/*\n+\t * For now, only create a sparse index with the\n+\t * GIT_TEST_SPARSE_INDEX environment variable. We will relax\n+\t * this once we have a proper way to opt-in (and later still,\n+\t * opt-out).\n+\t */\n+\tif (!git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0))\n+\t\treturn 0;\n+\n+\tif (!istate->sparse_checkout_patterns) {\n+\t\tistate->sparse_checkout_patterns = xcalloc(1, sizeof(struct pattern_list));\n+\t\tif (get_sparse_checkout_patterns(istate->sparse_checkout_patterns) < 0)\n+\t\t\treturn 0;\n+\t}\n+\n+\tif (!istate->sparse_checkout_patterns->use_cone_patterns) {\n+\t\twarning(_(\"attempting to use sparse-index without cone mode\"));\n+\t\treturn -1;\n+\t}\n+\n+\tif (cache_tree_update(istate, 0)) {\n+\t\twarning(_(\"unable to update cache-tree, staying full\"));\n+\t\treturn -1;\n+\t}\n+\n+\tremove_fsmonitor(istate);\n+\n+\ttrace2_region_enter(\"index\", \"convert_to_sparse\", istate->repo);\n+\tistate->cache_nr = convert_to_sparse_rec(istate,\n+\t\t\t\t\t\t 0, 0, istate->cache_nr,\n+\t\t\t\t\t\t \"\", 0, istate->cache_tree);\n+\tistate->drop_cache_tree = 1;\n+\tistate->sparse_index = 1;\n+\ttrace2_region_leave(\"index\", \"convert_to_sparse\", istate->repo);\n+\treturn 0;\n+}\n \n static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n {\ndiff --git a/sparse-index.h b/sparse-index.h\nindex 09a20d036c46..64380e121d80 100644\n--- a/sparse-index.h\n+++ b/sparse-index.h\n@@ -3,5 +3,6 @@\n \n struct index_state;\n void ensure_full_index(struct index_state *istate);\n+int convert_to_sparse(struct index_state *istate);\n \n #endif\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 4d789fe86b9d..ca87033d30b0 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -2,6 +2,9 @@\n \n test_description='compare full workdir to sparse workdir'\n \n+GIT_TEST_CHECK_CACHE_TREE=0\n+GIT_TEST_SPLIT_INDEX=0\n+\n . ./test-lib.sh\n \n test_expect_success 'setup' '\n@@ -121,15 +124,49 @@ run_on_all () {\n test_all_match () {\n \trun_on_all \"$@\" &&\n \ttest_cmp full-checkout-out sparse-checkout-out &&\n-\ttest_cmp full-checkout-err sparse-checkout-err\n+\ttest_cmp full-checkout-out sparse-index-out &&\n+\ttest_cmp full-checkout-err sparse-checkout-err &&\n+\ttest_cmp full-checkout-err sparse-index-err\n }\n \n test_sparse_match () {\n-\trun_on_sparse $* &&\n+\trun_on_sparse \"$@\" &&\n \ttest_cmp sparse-checkout-out sparse-index-out &&\n \ttest_cmp sparse-checkout-err sparse-index-err\n }\n \n+test_expect_success 'sparse-index contents' '\n+\tinit_repos &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tfor dir in folder1 folder2 x\n+\tdo\n+\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n+\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n+\t\t\t|| return 1\n+\tdone &&\n+\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set folder1 &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tfor dir in deep folder2 x\n+\tdo\n+\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n+\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n+\t\t\t|| return 1\n+\tdone &&\n+\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep/deeper1 &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tfor dir in deep/deeper2 folder1 folder2 x\n+\tdo\n+\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n+\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n+\t\t\t|| return 1\n+\tdone\n+'\n+\n test_expect_success 'expanded in-memory index matches full index' '\n \tinit_repos &&\n \ttest_sparse_match test-tool read-cache --expand --table\n@@ -137,6 +174,7 @@ test_expect_success 'expanded in-memory index matches full index' '\n \n test_expect_success 'status with options' '\n \tinit_repos &&\n+\ttest_sparse_match ls &&\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git status --porcelain=v2 -z -u &&\n \ttest_all_match git status --porcelain=v2 -uno &&\n@@ -273,6 +311,17 @@ test_expect_failure 'checkout and reset (mixed)' '\n \ttest_all_match git reset update-folder2\n '\n \n+# Ensure that sparse-index behaves identically to\n+# sparse-checkout with a full index.\n+test_expect_success 'checkout and reset (mixed) [sparse]' '\n+\tinit_repos &&\n+\n+\ttest_sparse_match git checkout -b reset-test update-deep &&\n+\ttest_sparse_match git reset deepest &&\n+\ttest_sparse_match git reset update-folder1 &&\n+\ttest_sparse_match git reset update-folder2\n+'\n+\n test_expect_success 'merge' '\n \tinit_repos &&\n \n@@ -309,14 +358,20 @@ test_expect_success 'clean' '\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git clean -f &&\n \ttest_all_match git status --porcelain=v2 &&\n+\ttest_sparse_match ls &&\n+\ttest_sparse_match ls folder1 &&\n \n \ttest_all_match git clean -xf &&\n \ttest_all_match git status --porcelain=v2 &&\n+\ttest_sparse_match ls &&\n+\ttest_sparse_match ls folder1 &&\n \n \ttest_all_match git clean -xdf &&\n \ttest_all_match git status --porcelain=v2 &&\n+\ttest_sparse_match ls &&\n+\ttest_sparse_match ls folder1 &&\n \n-\ttest_path_is_dir sparse-checkout/folder1\n+\ttest_sparse_match test_path_is_dir folder1\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"417606","messageId":"f86d3dc154d172912c23bdc157afb9d9cc545a85.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 10/20] sparse-checkout: hold pattern list in index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:19Z","receivedAt":"2021-02-23T20:16: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\nAs we modify the sparse-checkout definition, we perform index operations\non a pattern_list that only exists in-memory. This allows easy backing\nout in case the index update fails.\n\nHowever, if the index write itself cares about the sparse-checkout\npattern set, we need access to that in-memory copy. Place a pointer to\na 'struct pattern_list' in the index so we can access this on-demand.\nThis will be used in the next change which uses the sparse-checkout\ndefinition to filter out directories that are outsie the sparse cone.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/sparse-checkout.c | 17 ++++++++++-------\n cache.h                   |  2 ++\n 2 files changed, 12 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex 2306a9ad98e0..e00b82af727b 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -110,6 +110,8 @@ static int update_working_directory(struct pattern_list *pl)\n \tif (is_index_unborn(r->index))\n \t\treturn UPDATE_SPARSITY_SUCCESS;\n \n+\tr->index->sparse_checkout_patterns = pl;\n+\n \tmemset(&o, 0, sizeof(o));\n \to.verbose_update = isatty(2);\n \to.update = 1;\n@@ -138,6 +140,7 @@ static int update_working_directory(struct pattern_list *pl)\n \telse\n \t\trollback_lock_file(&lock_file);\n \n+\tr->index->sparse_checkout_patterns = NULL;\n \treturn result;\n }\n \n@@ -517,19 +520,18 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)\n {\n \tint result;\n \tint changed_config = 0;\n-\tstruct pattern_list pl;\n-\tmemset(&pl, 0, sizeof(pl));\n+\tstruct pattern_list *pl = xcalloc(1, sizeof(*pl));\n \n \tswitch (m) {\n \tcase ADD:\n \t\tif (core_sparse_checkout_cone)\n-\t\t\tadd_patterns_cone_mode(argc, argv, &pl);\n+\t\t\tadd_patterns_cone_mode(argc, argv, pl);\n \t\telse\n-\t\t\tadd_patterns_literal(argc, argv, &pl);\n+\t\t\tadd_patterns_literal(argc, argv, pl);\n \t\tbreak;\n \n \tcase REPLACE:\n-\t\tadd_patterns_from_input(&pl, argc, argv);\n+\t\tadd_patterns_from_input(pl, argc, argv);\n \t\tbreak;\n \t}\n \n@@ -539,12 +541,13 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)\n \t\tchanged_config = 1;\n \t}\n \n-\tresult = write_patterns_and_update(&pl);\n+\tresult = write_patterns_and_update(pl);\n \n \tif (result && changed_config)\n \t\tset_config(MODE_NO_PATTERNS);\n \n-\tclear_pattern_list(&pl);\n+\tclear_pattern_list(pl);\n+\tfree(pl);\n \treturn result;\n }\n \ndiff --git a/cache.h b/cache.h\nindex 1336c8d7435e..d75b352f38d3 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -307,6 +307,7 @@ static inline unsigned int canon_mode(unsigned int mode)\n struct split_index;\n struct untracked_cache;\n struct progress;\n+struct pattern_list;\n \n struct index_state {\n \tstruct cache_entry **cache;\n@@ -332,6 +333,7 @@ struct index_state {\n \tstruct mem_pool *ce_mem_pool;\n \tstruct progress *progress;\n \tstruct repository *repo;\n+\tstruct pattern_list *sparse_checkout_patterns;\n };\n \n /* Name hashing */\n-- \ngitgitgadget\n\n"},{"id":"417607","messageId":"e71f033c2871cf4a1ed1f1344429027ec69a150b.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 09/20] unpack-trees: ensure full index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:18Z","receivedAt":"2021-02-23T20:16:43Z","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 next change will translate full indexes into sparse indexes at write\ntime. The existing logic provides a way for every sparse index to be\nexpanded to a full index at read time. However, there are cases where an\nindex is written and then continues to be used in-memory to perform\nfurther updates.\n\nunpack_trees() is frequently called after such a write. In particular,\ncommands like 'git reset' do this double-update of the index.\n\nEnsure that we have a full index when entering unpack_trees(), but only\nwhen command_requires_full_index is true. This is always true at the\nmoment, but we will later relax that after unpack_trees() is updated to\nhandle sparse directory entries.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n unpack-trees.c | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex f5f668f532d8..4dd99219073a 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -1567,6 +1567,7 @@ static int verify_absent(const struct cache_entry *,\n  */\n int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)\n {\n+\tstruct repository *repo = the_repository;\n \tint i, ret;\n \tstatic struct cache_entry *dfc;\n \tstruct pattern_list pl;\n@@ -1578,6 +1579,12 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n \ttrace_performance_enter();\n \ttrace2_region_enter(\"unpack_trees\", \"unpack_trees\", the_repository);\n \n+\tprepare_repo_settings(repo);\n+\tif (repo->settings.command_requires_full_index) {\n+\t\tensure_full_index(o->src_index);\n+\t\tensure_full_index(o->dst_index);\n+\t}\n+\n \tif (!core_apply_sparse_checkout || !o->update)\n \t\to->skip_sparse_checkout = 1;\n \tif (!o->skip_sparse_checkout && !o->pl) {\n-- \ngitgitgadget\n\n"},{"id":"417608","messageId":"94373e2bfbbcdef85f3e9389e6239a44d3d0a598.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 08/20] test-tool: don't force full index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:17Z","receivedAt":"2021-02-23T20:16:45Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWe will use 'test-tool read-cache --table' to check that a sparse\nindex is written as part of init_repos. Since we will no longer always\nexpand a sparse index into a full index, add an '--expand' parameter\nthat adds a call to ensure_full_index() so we can compare a sparse index\ndirectly against a full index, or at least what the in-memory index\nlooks like when expanded in this way.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/helper/test-read-cache.c               | 13 ++++++++++++-\n t/t1092-sparse-checkout-compatibility.sh |  5 +++++\n 2 files changed, 17 insertions(+), 1 deletion(-)\n\ndiff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c\nindex e4c3492f7d3e..4780429dca6b 100644\n--- a/t/helper/test-read-cache.c\n+++ b/t/helper/test-read-cache.c\n@@ -1,6 +1,7 @@\n #include \"test-tool.h\"\n #include \"cache.h\"\n #include \"config.h\"\n+#include \"sparse-index.h\"\n \n static void print_cache_entry(struct cache_entry *ce)\n {\n@@ -30,13 +31,19 @@ int cmd__read_cache(int argc, const char **argv)\n \tstruct repository *r = the_repository;\n \tint i, cnt = 1;\n \tconst char *name = NULL;\n-\tint table = 0;\n+\tint table = 0, expand = 0;\n+\n+\tinitialize_the_repository();\n+\tprepare_repo_settings(r);\n+\tr->settings.command_requires_full_index = 0;\n \n \tfor (++argv, --argc; *argv && starts_with(*argv, \"--\"); ++argv, --argc) {\n \t\tif (skip_prefix(*argv, \"--print-and-refresh=\", &name))\n \t\t\tcontinue;\n \t\tif (!strcmp(*argv, \"--table\"))\n \t\t\ttable = 1;\n+\t\telse if (!strcmp(*argv, \"--expand\"))\n+\t\t\texpand = 1;\n \t}\n \n \tif (argc == 1)\n@@ -46,6 +53,10 @@ int cmd__read_cache(int argc, const char **argv)\n \n \tfor (i = 0; i < cnt; i++) {\n \t\trepo_read_index(r);\n+\n+\t\tif (expand)\n+\t\t\tensure_full_index(r->index);\n+\n \t\tif (name) {\n \t\t\tint pos;\n \ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 71d6f9e4c014..4d789fe86b9d 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -130,6 +130,11 @@ test_sparse_match () {\n \ttest_cmp sparse-checkout-err sparse-index-err\n }\n \n+test_expect_success 'expanded in-memory index matches full index' '\n+\tinit_repos &&\n+\ttest_sparse_match test-tool read-cache --expand --table\n+'\n+\n test_expect_success 'status with options' '\n \tinit_repos &&\n \ttest_all_match git status --porcelain=v2 &&\n-- \ngitgitgadget\n\n"},{"id":"417610","messageId":"fda23f07e6a20408b0a10c8944d54e7c65a1d629.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 13/20] unpack-trees: allow sparse directories","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:22Z","receivedAt":"2021-02-23T20:16:53Z","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 index_pos_by_traverse_info() currently throws a BUG() when a\ndirectory entry exists exactly in the index. We need to consider that it\nis possible to have a directory in a sparse index as long as that entry\nis itself marked with the skip-worktree bit.\n\nThe negation of the 'pos' variable must be conditioned to only when it\nstarts as negative. This is identical behavior as before when the index\nis full.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n unpack-trees.c | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 4dd99219073a..b324eec2a5d1 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -746,9 +746,12 @@ static int index_pos_by_traverse_info(struct name_entry *names,\n \tstrbuf_make_traverse_path(&name, info, names->path, names->pathlen);\n \tstrbuf_addch(&name, '/');\n \tpos = index_name_pos(o->src_index, name.buf, name.len);\n-\tif (pos >= 0)\n-\t\tBUG(\"This is a directory and should not exist in index\");\n-\tpos = -pos - 1;\n+\tif (pos >= 0) {\n+\t\tif (!o->src_index->sparse_index ||\n+\t\t    !(o->src_index->cache[pos]->ce_flags & CE_SKIP_WORKTREE))\n+\t\t\tBUG(\"This is a directory and should not exist in index\");\n+\t} else\n+\t\tpos = -pos - 1;\n \tif (pos >= o->src_index->cache_nr ||\n \t    !starts_with(o->src_index->cache[pos]->name, name.buf) ||\n \t    (pos > 0 && starts_with(o->src_index->cache[pos-1]->name, name.buf)))\n-- \ngitgitgadget\n\n"},{"id":"417611","messageId":"4405a9115c3b65119d7411025a17f0a9fb0cbd1c.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 12/20] submodule: sparse-index should not collapse links","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:21Z","receivedAt":"2021-02-23T20:17:05Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nA submodule is stored as a \"Git link\" that actually points to a commit\nwithin a submodule. Submodules are populated or not depending on\nsubmodule configuration, not sparse-checkout. To ensure that the\nsparse-index feature integrates correctly with submodules, we should not\ncollapse a directory if there is a Git link within its range.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n sparse-index.c                           |  1 +\n t/t1092-sparse-checkout-compatibility.sh | 17 +++++++++++++++++\n 2 files changed, 18 insertions(+)\n\ndiff --git a/sparse-index.c b/sparse-index.c\nindex cb1f85635fbc..14029fafc750 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -52,6 +52,7 @@ static int convert_to_sparse_rec(struct index_state *istate,\n \t\tstruct cache_entry *ce = istate->cache[i];\n \n \t\tif (ce_stage(ce) ||\n+\t\t    S_ISGITLINK(ce->ce_mode) ||\n \t\t    !(ce->ce_flags & CE_SKIP_WORKTREE))\n \t\t\tcan_convert = 0;\n \t}\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex ca87033d30b0..b38fab6455d9 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -374,4 +374,21 @@ test_expect_success 'clean' '\n \ttest_sparse_match test_path_is_dir folder1\n '\n \n+test_expect_success 'submodule handling' '\n+\tinit_repos &&\n+\n+\ttest_all_match mkdir modules &&\n+\ttest_all_match touch modules/a &&\n+\ttest_all_match git add modules &&\n+\ttest_all_match git commit -m \"add modules directory\" &&\n+\n+\trun_on_all git submodule add \"$(pwd)/initial-repo\" modules/sub &&\n+\ttest_all_match git commit -m \"add submodule\" &&\n+\n+\t# having a submodule prevents \"modules\" from collapse\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tgrep \"100644 blob .*\tmodules/a\" cache &&\n+\tgrep \"160000 commit $(git -C initial-repo rev-parse HEAD)\tmodules/sub\" cache\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"417612","messageId":"564503f7878475f220024def9e3bf20e4d518436.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 15/20] sparse-index: create extension for compatibility","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:24Z","receivedAt":"2021-02-23T20:17:22Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nPreviously, we enabled the sparse index format only using\nGIT_TEST_SPARSE_INDEX=1. This is not a feasible direction for users to\nactually select this mode. Further, sparse directory entries are not\nunderstood by the index formats as advertised.\n\nWe _could_ add a new index version that explicitly adds these\ncapabilities, but there are nuances to index formats 2, 3, and 4 that\nare still valuable to select as options. For now, create a repo\nextension, \"extensions.sparseIndex\", that specifies that the tool\nreading this repository must understand sparse directory entries.\n\nThis change only encodes the extension and enables it when\nGIT_TEST_SPARSE_INDEX=1. Later, we will add a more user-friendly CLI\nmechanism.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/extensions.txt |  7 ++++++\n cache.h                             |  1 +\n repo-settings.c                     |  7 ++++++\n repository.h                        |  3 ++-\n setup.c                             |  3 +++\n sparse-index.c                      | 38 +++++++++++++++++++++++++----\n 6 files changed, 53 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config/extensions.txt b/Documentation/config/extensions.txt\nindex 4e23d73cdcad..5c86b3648732 100644\n--- a/Documentation/config/extensions.txt\n+++ b/Documentation/config/extensions.txt\n@@ -6,3 +6,10 @@ extensions.objectFormat::\n Note that this setting should only be set by linkgit:git-init[1] or\n linkgit:git-clone[1].  Trying to change it after initialization will not\n work and will produce hard-to-diagnose issues.\n+\n+extensions.sparseIndex::\n+\tWhen combined with `core.sparseCheckout=true` and\n+\t`core.sparseCheckoutCone=true`, the index may contain entries\n+\tcorresponding to directories outside of the sparse-checkout\n+\tdefinition. Versions of Git that do not understand this extension\n+\tdo not expect directory entries in the index.\ndiff --git a/cache.h b/cache.h\nindex e8b7d3b4fb33..eea61fba7568 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1053,6 +1053,7 @@ struct repository_format {\n \tint worktree_config;\n \tint is_bare;\n \tint hash_algo;\n+\tint sparse_index;\n \tchar *work_tree;\n \tstruct string_list unknown_extensions;\n \tstruct string_list v1_only_extensions;\ndiff --git a/repo-settings.c b/repo-settings.c\nindex d63569e4041e..9677d50f9238 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -85,4 +85,11 @@ void prepare_repo_settings(struct repository *r)\n \t * removed.\n \t */\n \tr->settings.command_requires_full_index = 1;\n+\n+\t/*\n+\t * Initialize this as off.\n+\t */\n+\tr->settings.sparse_index = 0;\n+\tif (!repo_config_get_bool(r, \"extensions.sparseindex\", &value) && value)\n+\t\tr->settings.sparse_index = 1;\n }\ndiff --git a/repository.h b/repository.h\nindex e06a23015697..a45f7520fd9e 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -42,7 +42,8 @@ struct repo_settings {\n \n \tint core_multi_pack_index;\n \n-\tunsigned command_requires_full_index:1;\n+\tunsigned command_requires_full_index:1,\n+\t\t sparse_index:1;\n };\n \n struct repository {\ndiff --git a/setup.c b/setup.c\nindex c04cd25a30df..cd8394564613 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -500,6 +500,9 @@ static enum extension_result handle_extension(const char *var,\n \t\t\treturn error(\"invalid value for 'extensions.objectformat'\");\n \t\tdata->hash_algo = format;\n \t\treturn EXTENSION_OK;\n+\t} else if (!strcmp(ext, \"sparseindex\")) {\n+\t\tdata->sparse_index = 1;\n+\t\treturn EXTENSION_OK;\n \t}\n \treturn EXTENSION_UNKNOWN;\n }\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 14029fafc750..97b0d0c57857 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -102,19 +102,47 @@ static int convert_to_sparse_rec(struct index_state *istate,\n \treturn num_converted - start_converted;\n }\n \n+static int enable_sparse_index(struct repository *repo)\n+{\n+\tconst char *config_path = repo_git_path(repo, \"config.worktree\");\n+\n+\tif (upgrade_repository_format(1) < 0) {\n+\t\twarning(_(\"unable to upgrade repository format to enable sparse-index\"));\n+\t\treturn -1;\n+\t}\n+\tgit_config_set_in_file_gently(config_path,\n+\t\t\t\t      \"extensions.sparseIndex\",\n+\t\t\t\t      \"true\");\n+\n+\tprepare_repo_settings(repo);\n+\trepo->settings.sparse_index = 1;\n+\treturn 0;\n+}\n+\n int convert_to_sparse(struct index_state *istate)\n {\n \tif (istate->split_index || istate->sparse_index ||\n \t    !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n \t\treturn 0;\n \n+\tif (!istate->repo)\n+\t\tistate->repo = the_repository;\n+\n+\t/*\n+\t * The GIT_TEST_SPARSE_INDEX environment variable triggers the\n+\t * extensions.sparseIndex config variable to be on.\n+\t */\n+\tif (git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0)) {\n+\t\tint err = enable_sparse_index(istate->repo);\n+\t\tif (err < 0)\n+\t\t\treturn err;\n+\t}\n+\n \t/*\n-\t * For now, only create a sparse index with the\n-\t * GIT_TEST_SPARSE_INDEX environment variable. We will relax\n-\t * this once we have a proper way to opt-in (and later still,\n-\t * opt-out).\n+\t * Only convert to sparse if extensions.sparseIndex is set.\n \t */\n-\tif (!git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0))\n+\tprepare_repo_settings(istate->repo);\n+\tif (!istate->repo->settings.sparse_index)\n \t\treturn 0;\n \n \tif (!istate->sparse_checkout_patterns) {\n-- \ngitgitgadget\n\n"},{"id":"417613","messageId":"bcf960ef2362e32e2f8d7e06a31a925936f62bc1.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 17/20] sparse-checkout: disable sparse-index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:26Z","receivedAt":"2021-02-23T20:17:29Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWe use 'git sparse-checkout init --cone --sparse-index' to toggle the\nsparse-index feature. It makes sense to also disable it when running\n'git sparse-checkout disable'. This is particularly important because it\nremoves the extensions.sparseIndex config option, allowing other tools\nto use this Git repository again.\n\nThis does mean that 'git sparse-checkout init' will not re-enable the\nsparse-index feature, even if it was previously enabled.\n\nWhile testing this feature, I noticed that the sparse-index was not\nbeing written on the first run, but by a second. This was caught by the\ncall to 'test-tool read-cache --table'. This requires adjusting some\nassignments to core_apply_sparse_checkout and pl.use_cone_patterns in\nthe sparse_checkout_init() logic.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/sparse-checkout.c          | 10 +++++++++-\n t/t1091-sparse-checkout-builtin.sh | 13 +++++++++++++\n 2 files changed, 22 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex ca63e2c64e95..585343fa1972 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -280,6 +280,9 @@ static int set_config(enum sparse_checkout_mode mode)\n \t\t\t\t      \"core.sparseCheckoutCone\",\n \t\t\t\t      mode == MODE_CONE_PATTERNS ? \"true\" : NULL);\n \n+\tif (mode == MODE_NO_PATTERNS)\n+\t\tset_sparse_index_config(the_repository, 0);\n+\n \treturn 0;\n }\n \n@@ -341,10 +344,11 @@ static int sparse_checkout_init(int argc, const char **argv)\n \t\tthe_repository->index->updated_workdir = 1;\n \t}\n \n+\tcore_apply_sparse_checkout = 1;\n+\n \t/* If we already have a sparse-checkout file, use it. */\n \tif (res >= 0) {\n \t\tfree(sparse_filename);\n-\t\tcore_apply_sparse_checkout = 1;\n \t\treturn update_working_directory(NULL);\n \t}\n \n@@ -366,6 +370,7 @@ static int sparse_checkout_init(int argc, const char **argv)\n \tadd_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);\n \tstrbuf_addstr(&pattern, \"!/*/\");\n \tadd_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);\n+\tpl.use_cone_patterns = init_opts.cone_mode;\n \n \treturn write_patterns_and_update(&pl);\n }\n@@ -632,6 +637,9 @@ static int sparse_checkout_disable(int argc, const char **argv)\n \tstrbuf_addstr(&match_all, \"/*\");\n \tadd_pattern(strbuf_detach(&match_all, NULL), empty_base, 0, &pl, 0);\n \n+\tprepare_repo_settings(the_repository);\n+\tthe_repository->settings.sparse_index = 0;\n+\n \tif (update_working_directory(&pl))\n \t\tdie(_(\"error while refreshing working directory\"));\n \ndiff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh\nindex fc64e9ed99f4..ff1ad570a255 100755\n--- a/t/t1091-sparse-checkout-builtin.sh\n+++ b/t/t1091-sparse-checkout-builtin.sh\n@@ -205,6 +205,19 @@ test_expect_success 'sparse-checkout disable' '\n \tcheck_files repo a deep folder1 folder2\n '\n \n+test_expect_success 'sparse-index enabled and disabled' '\n+\tgit -C repo sparse-checkout init --cone --sparse-index &&\n+\ttest_cmp_config -C repo true extensions.sparseIndex &&\n+\ttest-tool -C repo read-cache --table >cache &&\n+\tgrep \" tree \" cache &&\n+\n+\tgit -C repo sparse-checkout disable &&\n+\ttest-tool -C repo read-cache --table >cache &&\n+\t! grep \" tree \" cache &&\n+\tgit -C repo config --list >config &&\n+\t! grep extensions.sparseindex config\n+'\n+\n test_expect_success 'cone mode: init and set' '\n \tgit -C repo sparse-checkout init --cone &&\n \tgit -C repo config --list >config &&\n-- \ngitgitgadget\n\n"},{"id":"417614","messageId":"e6afec58674e1bb0ba1634bbf5666ede0eb509d2.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 18/20] cache-tree: integrate with sparse directory entries","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:27Z","receivedAt":"2021-02-23T20:17:35Z","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 cache-tree extension was previously disabled with sparse indexes.\nHowever, the cache-tree is an important performance feature for commands\nlike 'git status' and 'git add'. Integrate it with sparse directory\nentries.\n\nWhen writing a sparse index, completely clear and recalculate the cache\ntree. By starting from scratch, the only integration necessary is to\ncheck if we hit a sparse directory entry and create a leaf of the\ncache-tree that has an entry_count of one and no subtrees.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache-tree.c   | 18 ++++++++++++++++++\n sparse-index.c | 10 +++++++++-\n 2 files changed, 27 insertions(+), 1 deletion(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 5f07a39e501e..950a9615db8f 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -256,6 +256,24 @@ static int update_one(struct cache_tree *it,\n \n \t*skip_count = 0;\n \n+\t/*\n+\t * If the first entry of this region is a sparse directory\n+\t * entry corresponding exactly to 'base', then this cache_tree\n+\t * struct is a \"leaf\" in the data structure, pointing to the\n+\t * tree OID specified in the entry.\n+\t */\n+\tif (entries > 0) {\n+\t\tconst struct cache_entry *ce = cache[0];\n+\n+\t\tif (S_ISSPARSEDIR(ce->ce_mode) &&\n+\t\t    ce->ce_namelen == baselen &&\n+\t\t    !strncmp(ce->name, base, baselen)) {\n+\t\t\tit->entry_count = 1;\n+\t\t\toidcpy(&it->oid, &ce->oid);\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n \tif (0 <= it->entry_count && has_object_file(&it->oid))\n \t\treturn it->entry_count;\n \ndiff --git a/sparse-index.c b/sparse-index.c\nindex a991c5331e9e..e541f251b37a 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -180,7 +180,11 @@ int convert_to_sparse(struct index_state *istate)\n \tistate->cache_nr = convert_to_sparse_rec(istate,\n \t\t\t\t\t\t 0, 0, istate->cache_nr,\n \t\t\t\t\t\t \"\", 0, istate->cache_tree);\n-\tistate->drop_cache_tree = 1;\n+\n+\t/* Clear and recompute the cache-tree */\n+\tcache_tree_free(&istate->cache_tree);\n+\tcache_tree_update(istate, 0);\n+\n \tistate->sparse_index = 1;\n \ttrace2_region_leave(\"index\", \"convert_to_sparse\", istate->repo);\n \treturn 0;\n@@ -278,5 +282,9 @@ void ensure_full_index(struct index_state *istate)\n \n \tfree(full);\n \n+\t/* Clear and recompute the cache-tree */\n+\tcache_tree_free(&istate->cache_tree);\n+\tcache_tree_update(istate, 0);\n+\n \ttrace2_region_leave(\"index\", \"ensure_full_index\", istate->repo);\n }\n-- \ngitgitgadget\n\n"},{"id":"417615","messageId":"a738b0ba8ab4e0f8a1a5004031db9cc09f17be47.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 20/20] p2000: add sparse-index repos","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:29Z","receivedAt":"2021-02-23T20:18:06Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\np2000-sparse-operations.sh compares different Git commands in\nrepositories with many files at HEAD but using sparse-checkout to focus\non a small portion of those files.\n\nAdd extra copies of the repository that use the sparse-index format so\nwe can track how that affects the performance of different commands.\n\nAt this point in time, the sparse-index is 100% overhead from the CPU\nfront, and this is measurable in these tests:\n\nTest\n---------------------------------------------------------------\n2000.2: git status (full-index-v3)              0.59(0.51+0.12)\n2000.3: git status (full-index-v4)              0.59(0.52+0.11)\n2000.4: git status (sparse-index-v3)            1.40(1.32+0.12)\n2000.5: git status (sparse-index-v4)            1.41(1.36+0.08)\n2000.6: git add -A (full-index-v3)              2.32(1.97+0.19)\n2000.7: git add -A (full-index-v4)              2.17(1.92+0.14)\n2000.8: git add -A (sparse-index-v3)            2.31(2.21+0.15)\n2000.9: git add -A (sparse-index-v4)            2.30(2.20+0.13)\n2000.10: git add . (full-index-v3)              2.39(2.02+0.20)\n2000.11: git add . (full-index-v4)              2.20(1.94+0.16)\n2000.12: git add . (sparse-index-v3)            2.36(2.27+0.12)\n2000.13: git add . (sparse-index-v4)            2.33(2.21+0.16)\n2000.14: git commit -a -m A (full-index-v3)     2.47(2.12+0.20)\n2000.15: git commit -a -m A (full-index-v4)     2.26(2.00+0.17)\n2000.16: git commit -a -m A (sparse-index-v3)   3.01(2.92+0.16)\n2000.17: git commit -a -m A (sparse-index-v4)   3.01(2.94+0.15)\n\nNote that there is very little difference between the v3 and v4 index\nformats when the sparse-index is enabled. This is primarily due to the\nfact that the relative file sizes are the same, and the command time is\nmostly taken up by parsing tree objects to expand the sparse index into\na full one.\n\nWith the current file layout, the index file sizes are given by this\ntable:\n\n       |  full index | sparse index |\n       +-------------+--------------+\n    v3 |     108 MiB |      1.6 MiB |\n    v4 |      80 MiB |      1.2 MiB |\n\nFuture updates will improve the performance of Git commands when the\nindex is sparse.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/perf/p2000-sparse-operations.sh | 19 ++++++++++++++++++-\n 1 file changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh\nindex 52597683376e..f9c7f3c6e27e 100755\n--- a/t/perf/p2000-sparse-operations.sh\n+++ b/t/perf/p2000-sparse-operations.sh\n@@ -62,12 +62,29 @@ test_expect_success 'setup repo and indexes' '\n \t\tgit sparse-checkout set $SPARSE_CONE &&\n \t\tgit config index.version 4 &&\n \t\tgit update-index --index-version=4\n+\t) &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v3 &&\n+\t(\n+\t\tcd sparse-index-v3 &&\n+\t\tgit sparse-checkout init --cone --sparse-index &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 3 &&\n+\t\tgit update-index --index-version=3\n+\t) &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v4 &&\n+\t(\n+\t\tcd sparse-index-v4 &&\n+\t\tgit sparse-checkout init --cone --sparse-index &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 4 &&\n+\t\tgit update-index --index-version=4\n \t)\n '\n \n test_perf_on_all () {\n \tcommand=\"$@\"\n-\tfor repo in full-index-v3 full-index-v4\n+\tfor repo in full-index-v3 full-index-v4 \\\n+\t\t    sparse-index-v3 sparse-index-v4\n \tdo\n \t\ttest_perf \"$command ($repo)\" \"\n \t\t\t(\n-- \ngitgitgadget\n"},{"id":"417616","messageId":"2be4981fe6987db02b7694b099b1026f2a6defba.1614111270.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH 19/20] sparse-index: loose integration with cache_tree_verify()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-02-23T20:14:28Z","receivedAt":"2021-02-23T20:18: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 cache_tree_verify() method is run when GIT_TEST_CHECK_CACHE_TREE\nis enabled, which it is by default in the test suite. The logic must\nbe adjusted for the presence of these directory entries.\n\nFor now, leave the test as a simple check for whether the directory\nentry is sparse. Do not go any further until needed.\n\nThis allows us to re-enable GIT_TEST_CHECK_CACHE_TREE in\nt1092-sparse-checkout-compatibility.sh. Further,\np2000-sparse-operations.sh uses the test suite and hence this is enabled\nfor all tests. We need to integrate with it before we run our\nperformance tests with a sparse-index.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache-tree.c                             | 19 +++++++++++++++++++\n t/t1092-sparse-checkout-compatibility.sh |  1 -\n 2 files changed, 19 insertions(+), 1 deletion(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 950a9615db8f..11bf1fcae6e1 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -808,6 +808,19 @@ int cache_tree_matches_traversal(struct cache_tree *root,\n \treturn 0;\n }\n \n+static void verify_one_sparse(struct repository *r,\n+\t\t\t      struct index_state *istate,\n+\t\t\t      struct cache_tree *it,\n+\t\t\t      struct strbuf *path,\n+\t\t\t      int pos)\n+{\n+\tstruct cache_entry *ce = istate->cache[pos];\n+\n+\tif (!S_ISSPARSEDIR(ce->ce_mode))\n+\t\tBUG(\"directory '%s' is present in index, but not sparse\",\n+\t\t    path->buf);\n+}\n+\n static void verify_one(struct repository *r,\n \t\t       struct index_state *istate,\n \t\t       struct cache_tree *it,\n@@ -830,6 +843,12 @@ static void verify_one(struct repository *r,\n \n \tif (path->len) {\n \t\tpos = index_name_pos(istate, path->buf, path->len);\n+\n+\t\tif (pos >= 0) {\n+\t\t\tverify_one_sparse(r, istate, it, path, pos);\n+\t\t\treturn;\n+\t\t}\n+\n \t\tpos = -pos - 1;\n \t} else {\n \t\tpos = 0;\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 9c2bc4d25f66..c2624176c2e0 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -2,7 +2,6 @@\n \n test_description='compare full workdir to sparse workdir'\n \n-GIT_TEST_CHECK_CACHE_TREE=0\n GIT_TEST_SPLIT_INDEX=0\n GIT_TEST_SPARSE_INDEX=\n \n-- \ngitgitgadget\n\n"},{"id":"417637","messageId":"CABPp-BEf5F_BT69_V0vOq9CdcNEce6qR5nk+2XZsdS5EeLV0_g@mail.gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/20] Sparse Index: Design, Format, Tests","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-02-23T23:49:35Z","receivedAt":"2021-02-23T23:58:26Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> Here is the first full patch series submission coming out of the\n> sparse-index RFC [1].\n\nWahoo!  I'll be reading these over the next few days.\n\n> [1]\n> https://lore.kernel.org/git/pull.847.git.1611596533.gitgitgadget@gmail.com/\n>\n> I won't waste too much space here, because PATCH 1 includes a sizeable\n> design document that describes the feature, the reasoning behind it, and my\n> plan for getting this implemented widely throughout the codebase.\n>\n> There are some new things here that were not in the RFC:\n>\n>  * Design doc and format updates. (Patch 1)\n>  * Performance test script. (Patches 2 and 20)\n>\n> Notably missing in this series from the RFC:\n>\n>  * The mega-patch inserting ensure_full_index() throughout the codebase.\n>    That will be a follow-up series to this one.\n>  * The integrations with git status and git add to demonstrate the improved\n>    performance. Those will also appear in their own series later.\n>\n> I plan to keep my latest work in this area in my 'sparse-index/wip' branch\n> [2]. It includes all of the work from the RFC right now, updated with the\n> work from this series.\n>\n> [2] https://github.com/derrickstolee/git/tree/sparse-index/wip\n>\n> Thanks, -Stolee\n>\n> Derrick Stolee (20):\n>   sparse-index: design doc and format update\n>   t/perf: add performance test for sparse operations\n>   t1092: clean up script quoting\n>   sparse-index: add guard to ensure full index\n>   sparse-index: implement ensure_full_index()\n>   t1092: compare sparse-checkout to sparse-index\n>   test-read-cache: print cache entries with --table\n>   test-tool: don't force full index\n>   unpack-trees: ensure full index\n>   sparse-checkout: hold pattern list in index\n>   sparse-index: convert from full to sparse\n>   submodule: sparse-index should not collapse links\n>   unpack-trees: allow sparse directories\n>   sparse-index: check index conversion happens\n>   sparse-index: create extension for compatibility\n>   sparse-checkout: toggle sparse index from builtin\n>   sparse-checkout: disable sparse-index\n>   cache-tree: integrate with sparse directory entries\n>   sparse-index: loose integration with cache_tree_verify()\n>   p2000: add sparse-index repos\n>\n>  Documentation/config/extensions.txt      |   7 +\n>  Documentation/git-sparse-checkout.txt    |  14 ++\n>  Documentation/technical/index-format.txt |   7 +\n>  Documentation/technical/sparse-index.txt | 167 +++++++++++++\n>  Makefile                                 |   1 +\n>  builtin/sparse-checkout.c                |  44 +++-\n>  cache-tree.c                             |  40 ++++\n>  cache.h                                  |  12 +-\n>  read-cache.c                             |  35 ++-\n>  repo-settings.c                          |  15 ++\n>  repository.c                             |  11 +-\n>  repository.h                             |   3 +\n>  setup.c                                  |   3 +\n>  sparse-index.c                           | 290 +++++++++++++++++++++++\n>  sparse-index.h                           |  11 +\n>  t/README                                 |   3 +\n>  t/helper/test-read-cache.c               |  61 ++++-\n>  t/perf/p2000-sparse-operations.sh        | 104 ++++++++\n>  t/t1091-sparse-checkout-builtin.sh       |  13 +\n>  t/t1092-sparse-checkout-compatibility.sh | 136 +++++++++--\n>  unpack-trees.c                           |  16 +-\n>  21 files changed, 953 insertions(+), 40 deletions(-)\n>  create mode 100644 Documentation/technical/sparse-index.txt\n>  create mode 100644 sparse-index.c\n>  create mode 100644 sparse-index.h\n>  create mode 100755 t/perf/p2000-sparse-operations.sh\n>\n>\n> base-commit: 966e671106b2fd38301e7c344c754fd118d0bb07\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-883%2Fderrickstolee%2Fsparse-index%2Fformat-v1\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-883/derrickstolee/sparse-index/format-v1\n> Pull-Request: https://github.com/gitgitgadget/git/pull/883\n> --\n> gitgitgadget\n"},{"id":"417642","messageId":"CABPp-BHwRHhYE3BPxCG+fRFWf2dCZ60rnO=ykztquKoOp_x_QQ@mail.gmail.com","threadId":"55195","inReplyTo":"daa9a6bcefbce977606484d502f5cfd7fca63ecc.1614111270.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 01/20] sparse-index: design doc and format update","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-02-24T01:13:34Z","receivedAt":"2021-02-24T01:29:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> This begins a long effort to update the index format to allow sparse\n> directory entries. This should result in a significant improvement to\n> Git commands when HEAD contains millions of files, but the user has\n> selected many fewer files to keep in their sparse-checkout definition.\n>\n> Currently, the index format is only updated in the presence of\n> extensions.sparseIndex instead of increasing a file format version\n> number. This is temporary, and index v5 is part of the plan for future\n> work in this area.\n>\n> The design document details many of the reasons for embarking on this\n> work, and also the plan for completing it safely.\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  Documentation/technical/index-format.txt |   7 +\n>  Documentation/technical/sparse-index.txt | 167 +++++++++++++++++++++++\n>  2 files changed, 174 insertions(+)\n>  create mode 100644 Documentation/technical/sparse-index.txt\n>\n> diff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt\n> index b633482b1bdf..387126582556 100644\n> --- a/Documentation/technical/index-format.txt\n> +++ b/Documentation/technical/index-format.txt\n> @@ -44,6 +44,13 @@ Git index format\n>    localization, no special casing of directory separator '/'). Entries\n>    with the same name are sorted by their stage field.\n>\n> +  An index entry typically represents a file. However, if sparse-checkout\n> +  is enabled in cone mode (`core.sparseCheckoutCone` is enabled) and the\n> +  `extensions.sparseIndex` extension is enabled, then the index may\n> +  contain entries for directories outside of the sparse-checkout definition.\n> +  These entries have mode `0040000`, include the `SKIP_WORKTREE` bit, and\n> +  the path ends in a directory separator.\n> +\n>    32-bit ctime seconds, the last time a file's metadata changed\n>      this is stat(2) data\n>\n> diff --git a/Documentation/technical/sparse-index.txt b/Documentation/technical/sparse-index.txt\n> new file mode 100644\n> index 000000000000..9070836f0655\n> --- /dev/null\n> +++ b/Documentation/technical/sparse-index.txt\n> @@ -0,0 +1,167 @@\n> +Git Sparse-Index Design Document\n> +================================\n> +\n> +The sparse-checkout feature allows users to focus a working directory on\n> +a subset of the files at HEAD. The cone mode patterns, enabled by\n> +`core.sparseCheckoutCone`, allow for very fast pattern matching to\n> +discover which files at HEAD belong in the sparse-checkout cone.\n> +\n> +Three important scale dimensions for a Git worktree are:\n> +\n> +* `HEAD`: How many files are present at `HEAD`?\n> +\n> +* Populated: How many files are within the sparse-checkout cone.\n> +\n> +* Modified: How many files has the user modified in the working directory?\n> +\n> +We will use big-O notation -- O(X) -- to denote how expensive certain\n> +operations are in terms of these dimensions.\n> +\n> +These dimensions are ordered by their magnitude: users (typically) modify\n> +fewer files than are populated, and we can only populate files at `HEAD`.\n> +These dimensions are also ordered by how expensive they are per item: it\n> +is expensive to detect a modified file than it is to write one that we\n> +know must be populated; changing `HEAD` only really requires updating the\n> +index.\n> +\n> +Problems occur if there is an extreme imbalance in these dimensions. For\n> +example, if `HEAD` contains millions of paths but the populated set has\n> +only tens of thousands, then commands like `git status` and `git add` can\n> +be dominated by operations that require O(`HEAD`) operations instead of\n> +O(Populated). Primarily, the cost is in parsing and rewriting the index,\n> +which is filled primarily with files at `HEAD` that are marked with the\n> +`SKIP_WORKTREE` bit.\n> +\n> +The sparse-index intends to take these commands that read and modify the\n> +index from O(`HEAD`) to O(Populated). To do this, we need to modify the\n> +index format in a significant way: add \"sparse directory\" entries.\n> +\n> +With cone mode patterns, it is possible to detect when an entire\n> +directory will have its contents outside of the sparse-checkout definition.\n> +Instead of listing all of the files it contains as individual entries, a\n> +sparse-index contains an entry with the directory name, referencing the\n> +object ID of the tree at `HEAD` and marked with the `SKIP_WORKTREE` bit.\n> +If we need to discover the details for paths within that directory, we\n> +can parse trees to find that list.\n> +\n> +This addition of sparse-directory entries violates expectations about the\n\nViolates current expectations, yes.  Documentation tends to live a\nlong time, and I suspect that 2-3 years from now reading this sentence\nmight be jarring since we'll have modified the code to have an updated\nset of expectations.  Maybe a simple \"As of time of writing, ...\" at\nthe beginning of the sentence here?  Or maybe I'm just being overly\nworried...\n\n> +index format and its in-memory data structure. There are many consumers in\n> +the codebase that expect to iterate through all of the index entries and\n> +see only files. In addition, they expect to see all files at `HEAD`. One\n> +way to handle this is to parse trees to replace a sparse-directory entry\n> +with all of the files within that tree as the index is loaded. However,\n> +parsing trees is slower than parsing the index format, so that is a slower\n> +operation than if we left the index alone.\n> +\n> +The implementation plan below follows four phases to slowly integrate with\n> +the sparse-index. The intention is to incrementally update Git commands to\n> +interact safely with the sparse-index without significant slowdowns. This\n> +may not always be possible, but the hope is that the primary commands that\n> +users need in their daily work are dramatically improved.\n> +\n> +Phase I: Format and initial speedups\n> +------------------------------------\n> +\n> +During this phase, Git learns to enable the sparse-index and safely parse\n> +one. Protections are put in place so that every consumer of the in-memory\n> +data structure can operate with its current assumption of every file at\n> +`HEAD`.\n> +\n> +At first, every index parse will expand the sparse-directory entries into\n> +the full list of paths at `HEAD`. This will be slower in all cases. The\n> +only noticable change in behavior will be that the serialized index file\n\nnoticeable\n\n> +contains sparse-directory entries.\n> +\n> +To start, we use a new repository extension, `extensions.sparseIndex`, to\n> +allow inserting sparse-directory entries into indexes with file format\n> +versions 2, 3, and 4. This prevents Git versions that do not understand\n> +the sparse-index from operating on one, but it also prevents other\n> +operations that do not use the index at all. A new format, index v5, will\n> +be introduced that includes sparse-directory entries by default. It might\n> +also introduce other features that have been considered for improving the\n> +index, as well.\n> +\n> +Next, consumers of the index will be guarded against operating on a\n> +sparse-index by inserting calls to `ensure_full_index()` or\n> +`expand_index_to_path()`. After these guards are in place, we can begin\n> +leaving sparse-directory entries in the in-memory index structure.\n> +\n> +Even after inserting these guards, we will keep expanding sparse-indexes\n> +for most Git commands using the `command_requires_full_index` repository\n> +setting. This setting will be on by default and disabled one builtin at a\n> +time until we have sufficient confidence that all of the index operations\n> +are properly guarded.\n> +\n> +To complete this phase, the commands `git status` and `git add` will be\n> +integrated with the sparse-index so that they operate with O(Populated)\n> +performance. They will be carefully tested for operations within and\n> +outside the sparse-checkout definition.\n\nGood plan so far, but there's something else bugging me a little here.\nOne thing we noticed with our usage of `sparse-checkout` is that\nalthough unimportant _tracked_ files go away, leftover build files and\nother untracked files stick around.  So, although 'git status'\nshouldn't have to check the tracked files anymore, it is still going\nto have to look at each of the *untracked* files and compare to\n.gitignore files in order to correctly classify each file as ignored\nor just plain untracked.  Our `sparsify` tool has for a long time\ntried to warn about such files when changing the sparsity\npatterns/modules and had an --remove-old-ignores option for clearing\nout ignored files that are found within directories that are sparse\n(Meaning the directories where all files under them are marked\nSKIP_WORKTREE.). I was never sure whether a warning was enough, or if\npushing that option more made sense, but about a month ago my\ncolleagues made the tool just auto-invoke that option from other\nsparsify invocations.  To my knowledge, there have been no complaints\nabout that being automatically turned on; but there were\ncomplaints/confusion before about the directories being left around.\n(Of course, non-ignored files are still left around by that option.)\n\nI'm worried that since sparse-checkout doesn't do anything to help\nwith all these untracked/ignored files, we might not get all the\nperformance improvements we want from a `git status` with sparse\ndirectories.  We'll be dropping from walking O(2*HEAD) files (1 source\n+ 1 object file) down to O(HEAD) files (just the object files) rather\nthan actually getting down to O(Populated).\n\n> +\n> +Phase II: Careful integrations\n> +------------------------------\n> +\n> +This phase focuses on ensuring that all index extensions and APIs work\n> +well with a sparse-index. This requires significant increases to our test\n> +coverage, especially for operations that interact with the working\n> +directory outside of the sparse-checkout definition. Some of these\n> +behaviors may not be the desirable ones, such as some tests already\n> +marked for failure in `t1092-sparse-checkout-compatibility.sh`.\n> +\n> +The index extensions that may require special integrations are:\n> +\n> +* FS Monitor\n> +* Untracked cache\n> +\n> +While integrating with these features, we should look for patterns that\n> +might lead to better APIs for interacting with the index. Coalescing\n> +common usage patterns into an API call can reduce the number of places\n> +where sparse-directories need to be handled carefully.\n\nMakes sense.\n\n> +Phase III: Important command speedups\n> +-------------------------------------\n> +\n> +At this point, the patterns for testing and implementing sparse-directory\n> +logic should be relatively stable. This phase focuses on updating some of\n> +the most common builtins that use the index to operate as O(Populated).\n> +Here is a potential list of commands that could be valuable to integrate\n> +at this point:\n> +\n> +* `git commit`\n> +* `git checkout`\n> +* `git merge`\n> +* `git rebase`\n> +\n> +Along with `git status` and `git add`, these commands cover the majority\n> +of users' interactions with the working directory.\n\nSounds like a good plan as well.\n\nI hope we get to make this specific to the merge-ort backend.  It\nlocalizes the index-related code to (a) a call to unpack_trees()\ncalled from checkout-like code (which would probably automatically be\nhandled by your updates to git checkout), and (b) a single function\nnamed record_conflicted_index_entries().  I feel it should be pretty\neasy to update.\n\nIn contrast, the idea of attempting to update merge-recursive with\nthis kind of change sounds overwhelming.\n\n>  In addition, we can\n> +integrate with these commands:\n> +\n> +* `git grep`\n> +* `git rm`\n> +\n> +These have been proposed as some whose behavior could change when in a\n> +repo with a sparse-checkout definition. It would be good to include this\n> +behavior automatically when using a sparse-index. Some clarity is needed\n> +to make the behavior switch clear to the user.\n\nIs this leftover from before recent events?  I think this portion of\nthe document should just be stricken.\n\nI argued about how these were buggy as-is due SKIP_WORKTREE always\nhaving been an incomplete implementation of an idea at [1], but didn't\nhear a further response from you.  I'm curious if you disagreed with\nmy reasoning, or it just slipped through the cracks in a busy schedule\nand this portion of the document was leftover from before.  In my\nopinion, both commands are just buggy and should be fixed for general\nsparse-checkout usage cases, not just for sparse-index.\n\nAs for git grep, it has options for searching the working tree\n(default) OR searching the index (--cached) OR searching an old commit\n(passing a REVISION).  But never some combination or more than one of\nthese.  The fact that it combined some in the cases of SKIP_WORKTREE\nentries looks entirely like a bug to me.  For the same reasons I\nargued that --untracked and --cached are incompatible[2], we shouldn't\nbe combining results from searching the working tree and searching the\nindex.  Luckily, this fix has already been submitted[3] and picked up\nin mt/grep-sparse-checkout and is marked in the cooking emails as\n\"Will merge to next\".\n\nAs for git rm, I'll quote from my email to Matheus:\n\n\"\"\"As far as the longer term discussion about making git rm configurable...\n_If_ it comes up again in the future, I will argue that if git rm\nshould have configuration to delete paths outside the sparsity\nspecification, then git add should have configuration to add paths\noutside the sparsity specification that happen to be present despite\nbeing SKIP_WORKTREE, that git diff with no revision arguments (nor\n--cached) should have configuration to diff against paths that are\nSKIP_WORKTREE but happen to be present, that git status should have\nconfiguration to report on changes to paths that are SKIP_WORKTREE but\nhappen to be present, that git checkout should have configuration to\nwrite files to the working tree despite matching sparsity paths, etc.\nAnd I'll argue that you do ALL of those or you're being inconsistent.\nI hope that people see these are actually all the same request and\nthat it is horribly inconsistent to do some of these and not others,\nand that at least by the time I get to mentioning checkout that they\nrealize it's a crazy request.  We should just tell users to extend\ntheir sparsity if they want the working copy (and commands that\ninteract with the working copy) to handle the additional paths.  Maybe\nI'm just really biased, but I don't see how this makes sense.  I would\nargue more about it, but no one has responded.  My plan was to just\nfix the default behavior, and then see if anyone ever actually cared\nenough to come back and ask for more configurability.\"\"\"\n\nAlso, for rm, Matheus has already submitted the fix[4], though at\nJunio's request he separated out some fixes for git-add as a separate\npreliminary series[5] and then will resubmit the other `add` and `rm`\nfixes.\n\n[1] https://lore.kernel.org/git/CABPp-BHwNoVnooqDFPAsZxBT9aR5Dwk5D9sDRCvYSb8akxAJgA@mail.gmail.com/\n[2] https://lore.kernel.org/git/xmqqtuql0yfp.fsf@gitster.c.googlers.com/\n[3] https://lore.kernel.org/git/5f3f7ac77039d41d1692ceae4b0c5df3bb45b74a.1612901326.git.matheus.bernardino@usp.br/\n[4] https://lore.kernel.org/git/61a77cd5f45ba02c7dff4b7932abdebb17c1667f.1613593946.git.matheus.bernardino@usp.br/\n[5] https://lore.kernel.org/git/cover.1614037664.git.matheus.bernardino@usp.br/\n\nAnyway, that's a long way of saying I think this section of your\ndocument is already obsolete.  (Which is a good thing -- less work to\ndo to get sparse-index working.  Thanks, Matheus!).\n\n> +This phase is the first where parallel work might be possible without too\n> +much conflicts between topics.\n> +\n> +Phase IV: The long tail\n> +-----------------------\n> +\n> +This last phase is less a \"phase\" and more \"the new normal\" after all of\n> +the previous work.\n> +\n> +To start, the `command_requires_full_index` option could be removed in\n> +favor of expanding only when hitting an API guard.\n> +\n> +There are many Git commands that could use special attention to operate as\n> +O(Populated), while some might be so rare that it is acceptable to leave\n> +them with additional overhead when a sparse-index is present.\n> +\n> +Here are some commands that might be useful to update:\n> +\n> +* `git sparse-checkout set`\n> +* `git am`\n> +* `git clean`\n> +* `git stash`\n\nOh, man, git stash is definitely in need of work.  It's still a\nminimalistic transliteration of shell to C, complete with lots of\nprocess forking and piping output between various low-level commands.\nIt might be interesting to rewrite this in terms of the merge\nmachinery, though its separate stashing of staged stuff, unstaged\nstuff, and possibly untracked stuff means that there is a sequence of\ntwo or three merges needed and interesting failure handling to do if\nthose merges fail, especially if the user uses --index.  But I\ndigress...\n\n\nAnyway, overall, very nicely written and planned out.  Thanks for\ntaking the time to write this all up.\n"},{"id":"417643","messageId":"CABPp-BGYP4OdVnUW0t_Veus-bzMPNH97MSNBdyH=e-iuN2b2jw@mail.gmail.com","threadId":"55195","inReplyTo":"a8c6322a3dbe1130dd2026b600a896e86d54a95d.1614111270.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 02/20] t/perf: add performance test for sparse operations","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-02-24T02:30:39Z","receivedAt":"2021-02-24T02:31:47Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> Create a test script that takes the default performance test (the Git\n> codebase) and multiplies it by 256 using four layers of duplicated\n> trees of width four. This results in nearly one million blob entries in\n> the index. Then, we can clone this repository with sparse-checkout\n> patterns that demonstrate four copies of the initial repository. Each\n> clone will use a different index format or mode so peformance can be\n> tested across the different options.\n>\n> Note that the initial repo is stripped of submodules before doing the\n> copies. This preserves the expected data shape of the sparse index,\n> because directories containing submodules are not collapsed to a sparse\n> directory entry.\n>\n> Run a few Git commands on these clones, especially those that use the\n> index (status, add, commit).\n>\n> Here are the results on my Linux machine:\n>\n> Test\n> --------------------------------------------------------------\n> 2000.2: git status (full-index-v3)             0.37(0.30+0.09)\n> 2000.3: git status (full-index-v4)             0.39(0.32+0.10)\n> 2000.4: git add -A (full-index-v3)             1.42(1.06+0.20)\n> 2000.5: git add -A (full-index-v4)             1.26(0.98+0.16)\n> 2000.6: git add . (full-index-v3)              1.40(1.04+0.18)\n> 2000.7: git add . (full-index-v4)              1.26(0.98+0.17)\n> 2000.8: git commit -a -m A (full-index-v3)     1.42(1.11+0.16)\n> 2000.9: git commit -a -m A (full-index-v4)     1.33(1.08+0.16)\n>\n> It is perhaps noteworthy that there is an improvement when using index\n> version 4. This is because the v3 index uses 108 MiB while the v4\n> index uses 80 MiB. Since the repeated portions of the directories are\n> very short (f3/f1/f2, for example) this ratio is less pronounced than in\n> similarly-sized real repositories.\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  t/perf/p2000-sparse-operations.sh | 87 +++++++++++++++++++++++++++++++\n>  1 file changed, 87 insertions(+)\n>  create mode 100755 t/perf/p2000-sparse-operations.sh\n>\n> diff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh\n> new file mode 100755\n> index 000000000000..52597683376e\n> --- /dev/null\n> +++ b/t/perf/p2000-sparse-operations.sh\n> @@ -0,0 +1,87 @@\n> +#!/bin/sh\n> +\n> +test_description=\"test performance of Git operations using the index\"\n> +\n> +. ./perf-lib.sh\n> +\n> +test_perf_default_repo\n> +\n> +SPARSE_CONE=f2/f4/f1\n> +\n> +test_expect_success 'setup repo and indexes' '\n> +       git reset --hard HEAD &&\n> +       # Remove submodules from the example repo, because our\n> +       # duplication of the entire repo creates an unlikly data shape.\n> +       git config --file .gitmodules --get-regexp \"submodule.*.path\" >modules &&\n> +       rm -f .gitmodules &&\n> +       git add .gitmodules &&\n\nWhy not `git rm [-f] .gitmodules` instead of these two commands?  Is\nthere something special about .gitmodules that requires this special\nhandling?\n\n> +       for module in $(awk \"{print \\$2}\" modules)\n> +       do\n> +               git rm $module || return 1\n> +       done &&\n> +       git add . &&\n\nWhat does the `git add .` do?  I don't see any changes there weren't\nalready git-add'ed or git-rm'ed.\n\n> +       git commit -m \"remove submodules\" &&\n> +\n> +       echo bogus >a &&\n> +       cp a b &&\n> +       git add a b &&\n> +       git commit -m \"level 0\" &&\n> +       BLOB=$(git rev-parse HEAD:a) &&\n> +       OLD_COMMIT=$(git rev-parse HEAD) &&\n> +       OLD_TREE=$(git rev-parse HEAD^{tree}) &&\n> +\n> +       for i in $(test_seq 1 4)\n> +       do\n> +               cat >in <<-EOF &&\n> +                       100755 blob $BLOB       a\n> +                       040000 tree $OLD_TREE   f1\n> +                       040000 tree $OLD_TREE   f2\n> +                       040000 tree $OLD_TREE   f3\n> +                       040000 tree $OLD_TREE   f4\n> +               EOF\n> +               NEW_TREE=$(git mktree <in) &&\n> +               NEW_COMMIT=$(git commit-tree $NEW_TREE -p $OLD_COMMIT -m \"level $i\") &&\n> +               OLD_TREE=$NEW_TREE &&\n> +               OLD_COMMIT=$NEW_COMMIT || return 1\n> +       done &&\n> +\n> +       git sparse-checkout init --cone &&\n> +       git branch -f wide $OLD_COMMIT &&\n> +       git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v3 &&\n> +       (\n> +               cd full-index-v3 &&\n> +               git sparse-checkout init --cone &&\n> +               git sparse-checkout set $SPARSE_CONE &&\n> +               git config index.version 3 &&\n> +               git update-index --index-version=3\n> +       ) &&\n> +       git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v4 &&\n> +       (\n> +               cd full-index-v4 &&\n> +               git sparse-checkout init --cone &&\n> +               git sparse-checkout set $SPARSE_CONE &&\n> +               git config index.version 4 &&\n> +               git update-index --index-version=4\n> +       )\n> +'\n> +\n> +test_perf_on_all () {\n> +       command=\"$@\"\n> +       for repo in full-index-v3 full-index-v4\n> +       do\n> +               test_perf \"$command ($repo)\" \"\n> +                       (\n> +                               cd $repo &&\n> +                               echo >>$SPARSE_CONE/a &&\n> +                               $command\n> +                       )\n> +               \"\n> +       done\n> +}\n> +\n> +test_perf_on_all git status\n> +test_perf_on_all git add -A\n> +test_perf_on_all git add .\n> +test_perf_on_all git commit -a -m A\n> +\n> +test_done\n> --\n> gitgitgadget\n\nOther than the two minor questions, the rest looks good to me.\n"},{"id":"417644","messageId":"CABPp-BGBXviGFJS=pTGFA_dmoXxTuO560R1ru0phy13cyT8+ig@mail.gmail.com","threadId":"55195","inReplyTo":"01da4c48a1fa94188faf45ab1e23b98ecb4164d5.1614111270.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 04/20] sparse-index: add guard to ensure full index","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-02-24T02:44:31Z","receivedAt":"2021-02-24T02:45:27Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> Upcoming changes will introduce modifications to the index format that\n> allow sparse directories. It will be useful to have a mechanism for\n> converting those sparse index files into full indexes by walking the\n> tree at those sparse directories. Name this method ensure_full_index()\n> as it will guarantee that the index is fully expanded.\n>\n> This method is not implemented yet, and instead we focus on the\n> scaffolding to declare it and call it at the appropriate time.\n>\n> Add a 'command_requires_full_index' member to struct repo_settings. This\n> will be an indicator that we need the index in full mode to do certain\n> index operations. This starts as being true for every command, then we\n> will set it to false as some commands integrate with sparse indexes.\n>\n> If 'command_requires_full_index' is true, then we will immediately\n> expand a sparse index to a full one upon reading from disk. This\n> suffices for now, but we will want to add more callers to\n> ensure_full_index() later.\n\nSame as 01/27 of your RFC series; looks good.\n\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  Makefile        |  1 +\n>  repo-settings.c |  8 ++++++++\n>  repository.c    | 11 ++++++++++-\n>  repository.h    |  2 ++\n>  sparse-index.c  |  8 ++++++++\n>  sparse-index.h  |  7 +++++++\n>  6 files changed, 36 insertions(+), 1 deletion(-)\n>  create mode 100644 sparse-index.c\n>  create mode 100644 sparse-index.h\n>\n> diff --git a/Makefile b/Makefile\n> index 5a239cac20e3..3bf61699238d 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -980,6 +980,7 @@ LIB_OBJS += setup.o\n>  LIB_OBJS += shallow.o\n>  LIB_OBJS += sideband.o\n>  LIB_OBJS += sigchain.o\n> +LIB_OBJS += sparse-index.o\n>  LIB_OBJS += split-index.o\n>  LIB_OBJS += stable-qsort.o\n>  LIB_OBJS += strbuf.o\n> diff --git a/repo-settings.c b/repo-settings.c\n> index f7fff0f5ab83..d63569e4041e 100644\n> --- a/repo-settings.c\n> +++ b/repo-settings.c\n> @@ -77,4 +77,12 @@ void prepare_repo_settings(struct repository *r)\n>                 UPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);\n>\n>         UPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_DEFAULT);\n> +\n> +       /*\n> +        * This setting guards all index reads to require a full index\n> +        * over a sparse index. After suitable guards are placed in the\n> +        * codebase around uses of the index, this setting will be\n> +        * removed.\n> +        */\n> +       r->settings.command_requires_full_index = 1;\n>  }\n> diff --git a/repository.c b/repository.c\n> index c98298acd017..a8acae002f71 100644\n> --- a/repository.c\n> +++ b/repository.c\n> @@ -10,6 +10,7 @@\n>  #include \"object.h\"\n>  #include \"lockfile.h\"\n>  #include \"submodule-config.h\"\n> +#include \"sparse-index.h\"\n>\n>  /* The main repository */\n>  static struct repository the_repo;\n> @@ -261,6 +262,8 @@ void repo_clear(struct repository *repo)\n>\n>  int repo_read_index(struct repository *repo)\n>  {\n> +       int res;\n> +\n>         if (!repo->index)\n>                 repo->index = xcalloc(1, sizeof(*repo->index));\n>\n> @@ -270,7 +273,13 @@ int repo_read_index(struct repository *repo)\n>         else if (repo->index->repo != repo)\n>                 BUG(\"repo's index should point back at itself\");\n>\n> -       return read_index_from(repo->index, repo->index_file, repo->gitdir);\n> +       res = read_index_from(repo->index, repo->index_file, repo->gitdir);\n> +\n> +       prepare_repo_settings(repo);\n> +       if (repo->settings.command_requires_full_index)\n> +               ensure_full_index(repo->index);\n> +\n> +       return res;\n>  }\n>\n>  int repo_hold_locked_index(struct repository *repo,\n> diff --git a/repository.h b/repository.h\n> index b385ca3c94b6..e06a23015697 100644\n> --- a/repository.h\n> +++ b/repository.h\n> @@ -41,6 +41,8 @@ struct repo_settings {\n>         enum fetch_negotiation_setting fetch_negotiation_algorithm;\n>\n>         int core_multi_pack_index;\n> +\n> +       unsigned command_requires_full_index:1;\n>  };\n>\n>  struct repository {\n> diff --git a/sparse-index.c b/sparse-index.c\n> new file mode 100644\n> index 000000000000..82183ead563b\n> --- /dev/null\n> +++ b/sparse-index.c\n> @@ -0,0 +1,8 @@\n> +#include \"cache.h\"\n> +#include \"repository.h\"\n> +#include \"sparse-index.h\"\n> +\n> +void ensure_full_index(struct index_state *istate)\n> +{\n> +       /* intentionally left blank */\n> +}\n> diff --git a/sparse-index.h b/sparse-index.h\n> new file mode 100644\n> index 000000000000..09a20d036c46\n> --- /dev/null\n> +++ b/sparse-index.h\n> @@ -0,0 +1,7 @@\n> +#ifndef SPARSE_INDEX_H__\n> +#define SPARSE_INDEX_H__\n> +\n> +struct index_state;\n> +void ensure_full_index(struct index_state *istate);\n> +\n> +#endif\n> --\n> gitgitgadget\n>\n"},{"id":"417645","messageId":"CABPp-BENXoDXqF1=wNsPvSVBrpJhJfcEhgDtPmCuf13un9ovXw@mail.gmail.com","threadId":"55195","inReplyTo":"2b83989fbcd3d464a3172eeb7cfea2e06e4f3785.1614111270.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 05/20] sparse-index: implement ensure_full_index()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-02-24T03:20:05Z","receivedAt":"2021-02-24T03:20:59Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> We will mark an in-memory index_state as having sparse directory entries\n> with the sparse_index bit. These currently cannot exist, but we will add\n> a mechanism for collapsing a full index to a sparse one in a later\n> change. That will happen at write time, so we must first allow parsing\n> the format before writing it.\n>\n> Commands or methods that require a full index in order to operate can\n> call ensure_full_index() to expand that index in-memory. This requires\n> parsing trees using that index's repository.\n>\n> Sparse directory entries have a specific 'ce_mode' value. The macro\n> S_ISSPARSEDIR(ce->ce_mode) can check if a cache_entry 'ce' has this type.\n> This ce_mode is not possible with the existing index formats, so we don't\n> also verify all properties of a sparse-directory entry, which are:\n>\n>  1. ce->ce_mode == 0040000\n>  2. ce->flags & CE_SKIP_WORKTREE is true\n>  3. ce->name[ce->namelen - 1] == '/' (ends in dir separator)\n>  4. ce->oid references a tree object.\n>\n> These are all semi-enforced in ensure_full_index() to some extent. Any\n> deviation will cause a warning at minimum or a failure in the worst\n> case.\n\nThanks for spelling these all out; looks good.\n\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  cache.h        |  7 +++-\n>  read-cache.c   |  9 +++++\n>  sparse-index.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++++-\n>  3 files changed, 109 insertions(+), 2 deletions(-)\n>\n> diff --git a/cache.h b/cache.h\n> index d92814961405..1336c8d7435e 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -204,6 +204,8 @@ struct cache_entry {\n>  #error \"CE_EXTENDED_FLAGS out of range\"\n>  #endif\n>\n> +#define S_ISSPARSEDIR(m) ((m) == S_IFDIR)\n\nMuch nicer, thanks.  :-)\n\n> +\n>  /* Forward structure decls */\n>  struct pathspec;\n>  struct child_process;\n> @@ -319,7 +321,8 @@ struct index_state {\n>                  drop_cache_tree : 1,\n>                  updated_workdir : 1,\n>                  updated_skipworktree : 1,\n> -                fsmonitor_has_run_once : 1;\n> +                fsmonitor_has_run_once : 1,\n> +                sparse_index : 1;\n>         struct hashmap name_hash;\n>         struct hashmap dir_hash;\n>         struct object_id oid;\n> @@ -722,6 +725,8 @@ int read_index_from(struct index_state *, const char *path,\n>                     const char *gitdir);\n>  int is_index_unborn(struct index_state *);\n>\n> +void ensure_full_index(struct index_state *istate);\n> +\n>  /* For use with `write_locked_index()`. */\n>  #define COMMIT_LOCK            (1 << 0)\n>  #define SKIP_IF_UNCHANGED      (1 << 1)\n> diff --git a/read-cache.c b/read-cache.c\n> index 29144cf879e7..97dbf2434f30 100644\n> --- a/read-cache.c\n> +++ b/read-cache.c\n> @@ -101,6 +101,9 @@ static const char *alternate_index_output;\n>\n>  static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n>  {\n> +       if (S_ISSPARSEDIR(ce->ce_mode))\n> +               istate->sparse_index = 1;\n\nA very minor question -- someone who sees \"sparse_index\" could\nprobably easily think either \"index with missing entries, due to\nhaving a SKIP_WORKTREE directory instead\" or perhaps \"index when using\nthe sparse-checkout feature, i.e. it has some SKIP_WORKTREE entries in\nit\".  From the code here, clearly the former is your intent.  I wonder\nif it'd help to have a small comment near the declaration of\nsparse_index to mention its intent.\n\n> +\n>         istate->cache[nr] = ce;\n>         add_name_hash(istate, ce);\n>  }\n> @@ -2255,6 +2258,12 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)\n>         trace2_data_intmax(\"index\", the_repository, \"read/cache_nr\",\n>                            istate->cache_nr);\n>\n> +       if (!istate->repo)\n> +               istate->repo = the_repository;\n> +       prepare_repo_settings(istate->repo);\n> +       if (istate->repo->settings.command_requires_full_index)\n> +               ensure_full_index(istate);\n> +\n>         return istate->cache_nr;\n>\n>  unmap:\n> diff --git a/sparse-index.c b/sparse-index.c\n> index 82183ead563b..316cb949b74b 100644\n> --- a/sparse-index.c\n> +++ b/sparse-index.c\n> @@ -1,8 +1,101 @@\n>  #include \"cache.h\"\n>  #include \"repository.h\"\n>  #include \"sparse-index.h\"\n> +#include \"tree.h\"\n> +#include \"pathspec.h\"\n> +#include \"trace2.h\"\n> +\n> +static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n> +{\n> +       ALLOC_GROW(istate->cache, nr + 1, istate->cache_alloc);\n> +\n> +       istate->cache[nr] = ce;\n> +       add_name_hash(istate, ce);\n> +}\n> +\n> +static int add_path_to_index(const struct object_id *oid,\n> +                               struct strbuf *base, const char *path,\n> +                               unsigned int mode, int stage, void *context)\n> +{\n> +       struct index_state *istate = (struct index_state *)context;\n> +       struct cache_entry *ce;\n> +       size_t len = base->len;\n> +\n> +       if (S_ISDIR(mode))\n> +               return READ_TREE_RECURSIVE;\n> +\n> +       strbuf_addstr(base, path);\n> +\n> +       ce = make_cache_entry(istate, mode, oid, base->buf, 0, 0);\n> +       ce->ce_flags |= CE_SKIP_WORKTREE;\n> +       set_index_entry(istate, istate->cache_nr++, ce);\n> +\n> +       strbuf_setlen(base, len);\n> +       return 0;\n> +}\n>\n>  void ensure_full_index(struct index_state *istate)\n>  {\n> -       /* intentionally left blank */\n> +       int i;\n> +       struct index_state *full;\n> +\n> +       if (!istate || !istate->sparse_index)\n> +               return;\n> +\n> +       if (!istate->repo)\n> +               istate->repo = the_repository;\n> +\n> +       trace2_region_enter(\"index\", \"ensure_full_index\", istate->repo);\n> +\n> +       /* initialize basics of new index */\n> +       full = xcalloc(1, sizeof(struct index_state));\n> +       memcpy(full, istate, sizeof(struct index_state));\n> +\n> +       /* then change the necessary things */\n> +       full->sparse_index = 0;\n> +       full->cache_alloc = (3 * istate->cache_alloc) / 2;\n> +       full->cache_nr = 0;\n> +       ALLOC_ARRAY(full->cache, full->cache_alloc);\n> +\n> +       for (i = 0; i < istate->cache_nr; i++) {\n> +               struct cache_entry *ce = istate->cache[i];\n> +               struct tree *tree;\n> +               struct pathspec ps;\n> +\n> +               if (!S_ISSPARSEDIR(ce->ce_mode)) {\n> +                       set_index_entry(full, full->cache_nr++, ce);\n> +                       continue;\n> +               }\n> +               if (!(ce->ce_flags & CE_SKIP_WORKTREE))\n> +                       warning(_(\"index entry is a directory, but not sparse (%08x)\"),\n> +                               ce->ce_flags);\n> +\n> +               /* recursively walk into cd->name */\n> +               tree = lookup_tree(istate->repo, &ce->oid);\n> +\n> +               memset(&ps, 0, sizeof(ps));\n> +               ps.recursive = 1;\n> +               ps.has_wildcard = 1;\n> +               ps.max_depth = -1;\n> +\n> +               read_tree_recursive(istate->repo, tree,\n> +                                   ce->name, strlen(ce->name),\n> +                                   0, &ps,\n> +                                   add_path_to_index, full);\n> +\n> +               /* free directory entries. full entries are re-used */\n> +               discard_cache_entry(ce);\n> +       }\n> +\n> +       /* Copy back into original index. */\n> +       memcpy(&istate->name_hash, &full->name_hash, sizeof(full->name_hash));\n> +       istate->sparse_index = 0;\n> +       free(istate->cache);\n\nThanks for fixing that leak that from the RFC series.\n\n> +       istate->cache = full->cache;\n> +       istate->cache_nr = full->cache_nr;\n> +       istate->cache_alloc = full->cache_alloc;\n> +\n> +       free(full);\n> +\n> +       trace2_region_leave(\"index\", \"ensure_full_index\", istate->repo);\n>  }\n> --\n> gitgitgadget\n\nLooks good to me.\n"},{"id":"417704","messageId":"CAN0heSqPTASn61KKGvWEA3ronaWEPWGP23LUg36JiMxxHEd0zQ@mail.gmail.com","threadId":"55195","inReplyTo":"6d6b230e3318007150aebefebc16dfb8b9b6c401.1614111270.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 16/20] sparse-checkout: toggle sparse index from builtin","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2021-02-24T19:11:27Z","receivedAt":"2021-02-24T19:13:56Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"On Wed, 24 Feb 2021 at 00:57, Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> +that is not completely understood by other tools. Enabling sparse index\n> +enables the `extensions.spareseIndex` config value, which might cause\n\ns/sparese/sparse\n\n> +other tools to stop working with your repository. If you have trouble with\n> +this compatibility, then run `git sparse-checkout sparse-index disable` to\n> +remove this config and rewrite your index to not be sparse.\n\nWhile I'm commenting on this..:\n\nThere are several \"layers\" here, for lack of a better term. \"Enabling foo\nenables bar which may cause baz. If you fail due to baz, try dropping\nbar by dropping foo.\" If I remove any mention of the config variable from\nyour text, I get the following.\n\n Enabling sparse index might cause other tools to stop working with your\n repository. If you have trouble with this compatibility, then run `git\n sparse-checkout sparse-index disable` to rewrite your index to not be\n sparse.\n\nI'm tempted to suggest such a rewrite to relieve readers of knowing of\nthe middle step, which you could say is more of an implementation\ndetail. But if we think that the symptoms / error messages might involve\n\"extensions.sparseIndex\" or, as would be the case with an older Git\ninstallation,\n\n  fatal: unknown repository extensions found:\n          sparseindex\n\nmaybe there is some value in mentioning the config item by name. Just\nthinking out loud, really, and I don't have any strong opinion. I only\ncame here to point out the typo in the docs.\n\nMartin\n"},{"id":"417792","messageId":"CABPp-BFt3nJ0bdY7CVyEYEnv_qDf=6znWQN1g3EHa_pVCwZ5_A@mail.gmail.com","threadId":"55195","inReplyTo":"c9910a37579c9a26f7721b60d82060e1766d050c.1614111270.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 06/20] t1092: compare sparse-checkout to sparse-index","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-02-25T06:37:59Z","receivedAt":"2021-02-25T06:39:11Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> Add a new 'sparse-index' repo alongside the 'full-checkout' and\n> 'sparse-checkout' repos in t1092-sparse-checkout-compatibility.sh. Also\n> add run_on_sparse and test_sparse_match helpers. These helpers will be\n> used when the sparse index is implemented.\n>\n> Add GIT_TEST_SPARSE_INDEX environment variable to enable the\n> sparse-index by default. This will be intended to use across the entire\n> test suite, except that it will only affect cases where the\n> sparse-checkout feature is enabled.\n\nThis last sentence was a bit awkward to read.  \"will be intended to\nuse\" -> \"is intended to be used\"?\n\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  t/README                                 |  3 +++\n>  t/t1092-sparse-checkout-compatibility.sh | 24 ++++++++++++++++++++----\n>  2 files changed, 23 insertions(+), 4 deletions(-)\n>\n> diff --git a/t/README b/t/README\n> index 593d4a4e270c..b98bc563aab5 100644\n> --- a/t/README\n> +++ b/t/README\n> @@ -439,6 +439,9 @@ and \"sha256\".\n>  GIT_TEST_WRITE_REV_INDEX=<boolean>, when true enables the\n>  'pack.writeReverseIndex' setting.\n>\n> +GIT_TEST_SPARSE_INDEX=<boolean>, when true enables index writes to use the\n> +sparse-index format by default.\n> +\n>  Naming Tests\n>  ------------\n>\n> diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\n> index 3725d3997e70..71d6f9e4c014 100755\n> --- a/t/t1092-sparse-checkout-compatibility.sh\n> +++ b/t/t1092-sparse-checkout-compatibility.sh\n> @@ -7,6 +7,7 @@ test_description='compare full workdir to sparse workdir'\n>  test_expect_success 'setup' '\n>         git init initial-repo &&\n>         (\n> +               GIT_TEST_SPARSE_INDEX=0 &&\n>                 cd initial-repo &&\n>                 echo a >a &&\n>                 echo \"after deep\" >e &&\n> @@ -87,23 +88,32 @@ init_repos () {\n>\n>         cp -r initial-repo sparse-checkout &&\n>         git -C sparse-checkout reset --hard &&\n> -       git -C sparse-checkout sparse-checkout init --cone &&\n> +\n> +       cp -r initial-repo sparse-index &&\n> +       git -C sparse-index reset --hard &&\n>\n>         # initialize sparse-checkout definitions\n> -       git -C sparse-checkout sparse-checkout set deep\n> +       git -C sparse-checkout sparse-checkout init --cone &&\n> +       git -C sparse-checkout sparse-checkout set deep &&\n> +       GIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout init --cone &&\n> +       GIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep\n>  }\n>\n>  run_on_sparse () {\n>         (\n>                 cd sparse-checkout &&\n> -               \"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n> +               GIT_TEST_SPARSE_INDEX=0 \"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n> +       ) &&\n> +       (\n> +               cd sparse-index &&\n> +               GIT_TEST_SPARSE_INDEX=1 \"$@\" >../sparse-index-out 2>../sparse-index-err\n>         )\n>  }\n>\n>  run_on_all () {\n>         (\n>                 cd full-checkout &&\n> -               \"$@\" >../full-checkout-out 2>../full-checkout-err\n> +               GIT_TEST_SPARSE_INDEX=0 \"$@\" >../full-checkout-out 2>../full-checkout-err\n>         ) &&\n>         run_on_sparse \"$@\"\n>  }\n> @@ -114,6 +124,12 @@ test_all_match () {\n>         test_cmp full-checkout-err sparse-checkout-err\n>  }\n>\n> +test_sparse_match () {\n> +       run_on_sparse $* &&\n\nShould this be\n   run_on_sparse \"$@\"\nin order to allow arguments with spaces?\n\n> +       test_cmp sparse-checkout-out sparse-index-out &&\n> +       test_cmp sparse-checkout-err sparse-index-err\n> +}\n> +\n>  test_expect_success 'status with options' '\n>         init_repos &&\n>         test_all_match git status --porcelain=v2 &&\n> --\n> gitgitgadget\n\nOther than those minor comments, looks good to me.\n"},{"id":"417794","messageId":"CABPp-BGspVmXFNpAoF-8wcb4eyjyivM4Cpcy4wRfjL6kMxcu6Q@mail.gmail.com","threadId":"55195","inReplyTo":"3d92df7a0cf9655dd34895f106cfac26ea44ad94.1614111270.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 07/20] test-read-cache: print cache entries with --table","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-02-25T07:02:46Z","receivedAt":"2021-02-25T07:03:42Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> This table is helpful for discovering data in the index to ensure it is\n> being written correctly, especially as we build and test the\n> sparse-index. This table includes an output format similar to 'git\n> ls-tree', but should not be compared to that directly. The biggest\n> reasons are that 'git ls-tree' includes a tree entry for every\n> subdirectory, even those that would not appear as a sparse directory in\n> a sparse-index. Further, 'git ls-tree' does not use a trailing directory\n> separator for its tree rows.\n>\n> This does not print the stat() information for the blobs. That could be\n> added in a future change with another option. The tests that are added\n> in the next few changes care only about the object types and IDs.\n>\n> To make the option parsing slightly more robust, wrap the string\n> comparisons in a loop adapted from test-dir-iterator.c.\n>\n> Care must be taken with the final check for the 'cnt' variable. We\n> continue the expectation that the numerical value is the final argument.\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  t/helper/test-read-cache.c | 50 ++++++++++++++++++++++++++++++--------\n>  1 file changed, 40 insertions(+), 10 deletions(-)\n>\n> diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c\n> index 244977a29bdf..e4c3492f7d3e 100644\n> --- a/t/helper/test-read-cache.c\n> +++ b/t/helper/test-read-cache.c\n> @@ -2,35 +2,65 @@\n>  #include \"cache.h\"\n>  #include \"config.h\"\n>\n> +static void print_cache_entry(struct cache_entry *ce)\n> +{\n> +       printf(\"%06o \", ce->ce_mode & 0777777);\n\nThis constant is curious.  I think it's because you want to strip off\nthe special in-memory bits of the ce_mode where git stores extra data,\nwhich would be everything beyond the first 16 bits (as noted in a\ncomment near the beginning of cache.h).  But here you keep the first\n18 bits.  Is CE_UPDATE and CE_REMOVE just 0 in the cases you've viewed\nso this works (but you really should use 0177777 or 0xFFFF), or am I\noff in my guess of what you're trying to do and you do want to see\nthose two flags?\n\nIt also seems surprising to me that this constant isn't already\ndefined somewhere in cache.h or as some variant of S_IFMT, though I'm\nnot finding it.\n\n> +\n> +       if (S_ISSPARSEDIR(ce->ce_mode))\n> +               printf(\"tree \");\n> +       else if (S_ISGITLINK(ce->ce_mode))\n> +               printf(\"commit \");\n> +       else\n> +               printf(\"blob \");\n\nPerhaps make use of the tree_type, commit_type, and blob_type global constants?\n\n> +\n> +       printf(\"%s\\t%s\\n\",\n> +              oid_to_hex(&ce->oid),\n> +              ce->name);\n> +}\n> +\n> +static void print_cache(struct index_state *cache)\n> +{\n> +       int i;\n> +       for (i = 0; i < the_index.cache_nr; i++)\n> +               print_cache_entry(the_index.cache[i]);\n\nWhy are you passing cache as a parameter, then ignoring it and using the_index?\n\n> +}\n> +\n>  int cmd__read_cache(int argc, const char **argv)\n>  {\n> +       struct repository *r = the_repository;\n>         int i, cnt = 1;\n>         const char *name = NULL;\n> +       int table = 0;\n>\n> -       if (argc > 1 && skip_prefix(argv[1], \"--print-and-refresh=\", &name)) {\n> -               argc--;\n> -               argv++;\n> +       for (++argv, --argc; *argv && starts_with(*argv, \"--\"); ++argv, --argc) {\n> +               if (skip_prefix(*argv, \"--print-and-refresh=\", &name))\n> +                       continue;\n> +               if (!strcmp(*argv, \"--table\"))\n> +                       table = 1;\n>         }\n>\n> -       if (argc == 2)\n> -               cnt = strtol(argv[1], NULL, 0);\n> +       if (argc == 1)\n> +               cnt = strtol(argv[0], NULL, 0);\n>         setup_git_directory();\n>         git_config(git_default_config, NULL);\n> +\n>         for (i = 0; i < cnt; i++) {\n> -               read_cache();\n> +               repo_read_index(r);\n>                 if (name) {\n>                         int pos;\n>\n> -                       refresh_index(&the_index, REFRESH_QUIET,\n> +                       refresh_index(r->index, REFRESH_QUIET,\n>                                       NULL, NULL, NULL);\n> -                       pos = index_name_pos(&the_index, name, strlen(name));\n> +                       pos = index_name_pos(r->index, name, strlen(name));\n>                         if (pos < 0)\n>                                 die(\"%s not in index\", name);\n>                         printf(\"%s is%s up to date\\n\", name,\n> -                              ce_uptodate(the_index.cache[pos]) ? \"\" : \" not\");\n> +                              ce_uptodate(r->index->cache[pos]) ? \"\" : \" not\");\n>                         write_file(name, \"%d\\n\", i);\n>                 }\n> -               discard_cache();\n> +               if (table)\n> +                       print_cache(r->index);\n> +               discard_index(r->index);\n>         }\n>         return 0;\n>  }\n> --\n> gitgitgadget\n"},{"id":"417796","messageId":"CABPp-BFH1ixDYxy2O1C=5DwWu99BDnow_F8gXQQFdhpEC=OTbA@mail.gmail.com","threadId":"55195","inReplyTo":"f86d3dc154d172912c23bdc157afb9d9cc545a85.1614111270.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 10/20] sparse-checkout: hold pattern list in index","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-02-25T07:14:41Z","receivedAt":"2021-02-25T07:16:16Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> As we modify the sparse-checkout definition, we perform index operations\n> on a pattern_list that only exists in-memory. This allows easy backing\n> out in case the index update fails.\n>\n> However, if the index write itself cares about the sparse-checkout\n> pattern set, we need access to that in-memory copy. Place a pointer to\n> a 'struct pattern_list' in the index so we can access this on-demand.\n> This will be used in the next change which uses the sparse-checkout\n> definition to filter out directories that are outsie the sparse cone.\n\nLooks like you still have the \"outsie\" typo.  ;-)\n\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  builtin/sparse-checkout.c | 17 ++++++++++-------\n>  cache.h                   |  2 ++\n>  2 files changed, 12 insertions(+), 7 deletions(-)\n>\n> diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\n> index 2306a9ad98e0..e00b82af727b 100644\n> --- a/builtin/sparse-checkout.c\n> +++ b/builtin/sparse-checkout.c\n> @@ -110,6 +110,8 @@ static int update_working_directory(struct pattern_list *pl)\n>         if (is_index_unborn(r->index))\n>                 return UPDATE_SPARSITY_SUCCESS;\n>\n> +       r->index->sparse_checkout_patterns = pl;\n> +\n>         memset(&o, 0, sizeof(o));\n>         o.verbose_update = isatty(2);\n>         o.update = 1;\n> @@ -138,6 +140,7 @@ static int update_working_directory(struct pattern_list *pl)\n>         else\n>                 rollback_lock_file(&lock_file);\n>\n> +       r->index->sparse_checkout_patterns = NULL;\n>         return result;\n>  }\n>\n> @@ -517,19 +520,18 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)\n>  {\n>         int result;\n>         int changed_config = 0;\n> -       struct pattern_list pl;\n> -       memset(&pl, 0, sizeof(pl));\n> +       struct pattern_list *pl = xcalloc(1, sizeof(*pl));\n>\n>         switch (m) {\n>         case ADD:\n>                 if (core_sparse_checkout_cone)\n> -                       add_patterns_cone_mode(argc, argv, &pl);\n> +                       add_patterns_cone_mode(argc, argv, pl);\n>                 else\n> -                       add_patterns_literal(argc, argv, &pl);\n> +                       add_patterns_literal(argc, argv, pl);\n>                 break;\n>\n>         case REPLACE:\n> -               add_patterns_from_input(&pl, argc, argv);\n> +               add_patterns_from_input(pl, argc, argv);\n>                 break;\n>         }\n>\n> @@ -539,12 +541,13 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)\n>                 changed_config = 1;\n>         }\n>\n> -       result = write_patterns_and_update(&pl);\n> +       result = write_patterns_and_update(pl);\n>\n>         if (result && changed_config)\n>                 set_config(MODE_NO_PATTERNS);\n>\n> -       clear_pattern_list(&pl);\n> +       clear_pattern_list(pl);\n> +       free(pl);\n>         return result;\n>  }\n>\n> diff --git a/cache.h b/cache.h\n> index 1336c8d7435e..d75b352f38d3 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -307,6 +307,7 @@ static inline unsigned int canon_mode(unsigned int mode)\n>  struct split_index;\n>  struct untracked_cache;\n>  struct progress;\n> +struct pattern_list;\n>\n>  struct index_state {\n>         struct cache_entry **cache;\n> @@ -332,6 +333,7 @@ struct index_state {\n>         struct mem_pool *ce_mem_pool;\n>         struct progress *progress;\n>         struct repository *repo;\n> +       struct pattern_list *sparse_checkout_patterns;\n>  };\n>\n>  /* Name hashing */\n> --\n> gitgitgadget\n>\n"},{"id":"417799","messageId":"CABPp-BE70+QVRm0z+Sfpt3V++O2VFkayHySTapFbJyD8VxKyBg@mail.gmail.com","threadId":"55195","inReplyTo":"a2d77c23a0cbbe090b4ed6d840f608cffd4248fd.1614111270.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 11/20] sparse-index: convert from full to sparse","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-02-25T07:33:37Z","receivedAt":"2021-02-25T07:35:14Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> If we have a full index, then we can convert it to a sparse index by\n> replacing directories outside of the sparse cone with sparse directory\n> entries. The convert_to_sparse() method does this, when the situation is\n> appropriate.\n>\n> For now, we avoid converting the index to a sparse index if:\n>\n>  1. the index is split.\n>  2. the index is already sparse.\n>  3. sparse-checkout is disabled.\n>  4. sparse-checkout does not use cone mode.\n>\n> Finally, we currently limit the conversion to when the\n> GIT_TEST_SPARSE_INDEX environment variable is enabled. A mode using Git\n> config will be added in a later change.\n>\n> The trickiest thing about this conversion is that we might not be able\n> to mark a directory as a sparse directory just because it is outside the\n> sparse cone. There might be unmerged files within that directory, so we\n> need to look for those. Also, if there is some strange reason why a file\n> is not marked with CE_SKIP_WORKTREE, then we should give up on\n> converting that directory. There is still hope that some of its\n> subdirectories might be able to convert to sparse, so we keep looking\n> deeper.\n>\n> The conversion process is assisted by the cache-tree extension. This is\n> calculated from the full index if it does not already exist. We then\n> abandon the cache-tree as it no longer applies to the newly-sparse\n> index. Thus, this cache-tree will be recalculated in every\n> sparse-full-sparse round-trip until we integrate the cache-tree\n> extension with the sparse index.\n>\n> Some Git commands use the index after writing it. For example, 'git add'\n> will update the index, then write it to disk, then read its entries to\n> report information. To keep the in-memory index in a full state after\n> writing, we re-expand it to a full one after the write. This is wasteful\n> for commands that only write the index and do not read from it again,\n> but that is only the case until we make those commands \"sparse aware.\"\n>\n> We can compare the behavior of the sparse-index in\n> t1092-sparse-checkout-compability.sh by using GIT_TEST_SPARSE_INDEX=1\n> when operating on the 'sparse-index' repo. We can also compare the two\n> sparse repos directly, such as comparing their indexes (when expanded to\n> full in the case of the 'sparse-index' repo). We also verify that the\n> index is actually populated with sparse directory entries.\n>\n> The 'checkout and reset (mixed)' test is marked for failure when\n> comparing a sparse repo to a full repo, but we can compare the two\n> sparse-checkout cases directly to ensure that we are not changing the\n> behavior when using a sparse index.\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  cache-tree.c                             |   3 +\n>  cache.h                                  |   2 +\n>  read-cache.c                             |  26 ++++-\n>  sparse-index.c                           | 139 +++++++++++++++++++++++\n>  sparse-index.h                           |   1 +\n>  t/t1092-sparse-checkout-compatibility.sh |  61 +++++++++-\n>  6 files changed, 227 insertions(+), 5 deletions(-)\n>\n> diff --git a/cache-tree.c b/cache-tree.c\n> index 2fb483d3c083..5f07a39e501e 100644\n> --- a/cache-tree.c\n> +++ b/cache-tree.c\n> @@ -6,6 +6,7 @@\n>  #include \"object-store.h\"\n>  #include \"replace-object.h\"\n>  #include \"promisor-remote.h\"\n> +#include \"sparse-index.h\"\n>\n>  #ifndef DEBUG_CACHE_TREE\n>  #define DEBUG_CACHE_TREE 0\n> @@ -442,6 +443,8 @@ int cache_tree_update(struct index_state *istate, int flags)\n>         if (i)\n>                 return i;\n>\n> +       ensure_full_index(istate);\n> +\n>         if (!istate->cache_tree)\n>                 istate->cache_tree = cache_tree();\n>\n> diff --git a/cache.h b/cache.h\n> index d75b352f38d3..e8b7d3b4fb33 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -251,6 +251,8 @@ static inline unsigned int create_ce_mode(unsigned int mode)\n>  {\n>         if (S_ISLNK(mode))\n>                 return S_IFLNK;\n> +       if (mode == S_IFDIR)\n> +               return S_IFDIR;\n>         if (S_ISDIR(mode) || S_ISGITLINK(mode))\n>                 return S_IFGITLINK;\n>         return S_IFREG | ce_permissions(mode);\n> diff --git a/read-cache.c b/read-cache.c\n> index 97dbf2434f30..67acbf202f4e 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 \"sparse-index.h\"\n>\n>  /* Mask for the name length in ce_flags in the on-disk index */\n>\n> @@ -1002,8 +1003,14 @@ int verify_path(const char *path, unsigned mode)\n>\n>                         c = *path++;\n>                         if ((c == '.' && !verify_dotfile(path, mode)) ||\n> -                           is_dir_sep(c) || c == '\\0')\n> +                           is_dir_sep(c))\n>                                 return 0;\n> +                       /*\n> +                        * allow terminating directory separators for\n> +                        * sparse directory enries.\n\nenries -> entries\n\n> +                        */\n> +                       if (c == '\\0')\n> +                               return S_ISDIR(mode);\n\nYaay, much simpler (than the RFC version).\n\n>                 } else if (c == '\\\\' && protect_ntfs) {\n>                         if (is_ntfs_dotgit(path))\n>                                 return 0;\n> @@ -3061,6 +3068,14 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n>                                  unsigned flags)\n>  {\n>         int ret;\n> +       int was_full = !istate->sparse_index;\n> +\n> +       ret = convert_to_sparse(istate);\n> +\n> +       if (ret) {\n> +               warning(_(\"failed to convert to a sparse-index\"));\n> +               return ret;\n> +       }\n>\n>         /*\n>          * TODO trace2: replace \"the_repository\" with the actual repo instance\n> @@ -3072,6 +3087,9 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n>         trace2_region_leave_printf(\"index\", \"do_write_index\", the_repository,\n>                                    \"%s\", get_lock_file_path(lock));\n>\n> +       if (was_full)\n> +               ensure_full_index(istate);\n> +\n>         if (ret)\n>                 return ret;\n>         if (flags & COMMIT_LOCK)\n> @@ -3162,9 +3180,10 @@ static int write_shared_index(struct index_state *istate,\n>                               struct tempfile **temp)\n>  {\n>         struct split_index *si = istate->split_index;\n> -       int ret;\n> +       int ret, was_full = !istate->sparse_index;\n>\n>         move_cache_to_base_index(istate);\n> +       convert_to_sparse(istate);\n>\n>         trace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n>                                    the_repository, \"%s\", get_tempfile_path(*temp));\n> @@ -3172,6 +3191,9 @@ static int write_shared_index(struct index_state *istate,\n>         trace2_region_leave_printf(\"index\", \"shared/do_write_index\",\n>                                    the_repository, \"%s\", get_tempfile_path(*temp));\n>\n> +       if (was_full)\n> +               ensure_full_index(istate);\n> +\n>         if (ret)\n>                 return ret;\n>         ret = adjust_shared_perm(get_tempfile_path(*temp));\n> diff --git a/sparse-index.c b/sparse-index.c\n> index 316cb949b74b..cb1f85635fbc 100644\n> --- a/sparse-index.c\n> +++ b/sparse-index.c\n> @@ -4,6 +4,145 @@\n>  #include \"tree.h\"\n>  #include \"pathspec.h\"\n>  #include \"trace2.h\"\n> +#include \"cache-tree.h\"\n> +#include \"config.h\"\n> +#include \"dir.h\"\n> +#include \"fsmonitor.h\"\n> +\n> +static struct cache_entry *construct_sparse_dir_entry(\n> +                               struct index_state *istate,\n> +                               const char *sparse_dir,\n> +                               struct cache_tree *tree)\n> +{\n> +       struct cache_entry *de;\n> +\n> +       de = make_cache_entry(istate, S_IFDIR, &tree->oid, sparse_dir, 0, 0);\n> +\n> +       de->ce_flags |= CE_SKIP_WORKTREE;\n> +       return de;\n> +}\n> +\n> +/*\n> + * Returns the number of entries \"inserted\" into the index.\n> + */\n> +static int convert_to_sparse_rec(struct index_state *istate,\n> +                                int num_converted,\n> +                                int start, int end,\n> +                                const char *ct_path, size_t ct_pathlen,\n> +                                struct cache_tree *ct)\n> +{\n> +       int i, can_convert = 1;\n> +       int start_converted = num_converted;\n> +       enum pattern_match_result match;\n> +       int dtype;\n> +       struct strbuf child_path = STRBUF_INIT;\n> +       struct pattern_list *pl = istate->sparse_checkout_patterns;\n> +\n> +       /*\n> +        * Is the current path outside of the sparse cone?\n> +        * Then check if the region can be replaced by a sparse\n> +        * directory entry (everything is sparse and merged).\n> +        */\n> +       match = path_matches_pattern_list(ct_path, ct_pathlen,\n> +                                         NULL, &dtype, pl, istate);\n> +       if (match != NOT_MATCHED)\n> +               can_convert = 0;\n\nNot sure if you saw my comments on the flow control at\nhttps://lore.kernel.org/git/CABPp-BE9wPwmC0=pA4p1_QSRDHrO8RzqfJQdE2NxYZsYL_Rcig@mail.gmail.com/\n(the typos elsewhere seem to still be present).  If you saw it and\ndecided against it, that's fine, just wanted the idea to at least be\nfloated.\n\n> +\n> +       for (i = start; can_convert && i < end; i++) {\n> +               struct cache_entry *ce = istate->cache[i];\n> +\n> +               if (ce_stage(ce) ||\n> +                   !(ce->ce_flags & CE_SKIP_WORKTREE))\n> +                       can_convert = 0;\n> +       }\n> +\n> +       if (can_convert) {\n> +               struct cache_entry *se;\n> +               se = construct_sparse_dir_entry(istate, ct_path, ct);\n> +\n> +               istate->cache[num_converted++] = se;\n> +               return 1;\n> +       }\n> +\n> +       for (i = start; i < end; ) {\n> +               int count, span, pos = -1;\n> +               const char *base, *slash;\n> +               struct cache_entry *ce = istate->cache[i];\n> +\n> +               /*\n> +                * Detect if this is a normal entry oustide of any subtree\n\ns/oustide/outside/\n\n> +                * entry.\n> +                */\n> +               base = ce->name + ct_pathlen;\n> +               slash = strchr(base, '/');\n> +\n> +               if (slash)\n> +                       pos = cache_tree_subtree_pos(ct, base, slash - base);\n> +\n> +               if (pos < 0) {\n> +                       istate->cache[num_converted++] = ce;\n> +                       i++;\n> +                       continue;\n> +               }\n> +\n> +               strbuf_setlen(&child_path, 0);\n> +               strbuf_add(&child_path, ce->name, slash - ce->name + 1);\n> +\n> +               span = ct->down[pos]->cache_tree->entry_count;\n> +               count = convert_to_sparse_rec(istate,\n> +                                             num_converted, i, i + span,\n> +                                             child_path.buf, child_path.len,\n> +                                             ct->down[pos]->cache_tree);\n> +               num_converted += count;\n> +               i += span;\n> +       }\n> +\n> +       strbuf_release(&child_path);\n> +       return num_converted - start_converted;\n> +}\n> +\n> +int convert_to_sparse(struct index_state *istate)\n> +{\n> +       if (istate->split_index || istate->sparse_index ||\n> +           !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n> +               return 0;\n> +\n> +       /*\n> +        * For now, only create a sparse index with the\n> +        * GIT_TEST_SPARSE_INDEX environment variable. We will relax\n> +        * this once we have a proper way to opt-in (and later still,\n> +        * opt-out).\n> +        */\n> +       if (!git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0))\n> +               return 0;\n> +\n> +       if (!istate->sparse_checkout_patterns) {\n> +               istate->sparse_checkout_patterns = xcalloc(1, sizeof(struct pattern_list));\n> +               if (get_sparse_checkout_patterns(istate->sparse_checkout_patterns) < 0)\n> +                       return 0;\n> +       }\n> +\n> +       if (!istate->sparse_checkout_patterns->use_cone_patterns) {\n> +               warning(_(\"attempting to use sparse-index without cone mode\"));\n> +               return -1;\n> +       }\n> +\n> +       if (cache_tree_update(istate, 0)) {\n> +               warning(_(\"unable to update cache-tree, staying full\"));\n> +               return -1;\n> +       }\n> +\n> +       remove_fsmonitor(istate);\n> +\n> +       trace2_region_enter(\"index\", \"convert_to_sparse\", istate->repo);\n> +       istate->cache_nr = convert_to_sparse_rec(istate,\n> +                                                0, 0, istate->cache_nr,\n> +                                                \"\", 0, istate->cache_tree);\n> +       istate->drop_cache_tree = 1;\n> +       istate->sparse_index = 1;\n> +       trace2_region_leave(\"index\", \"convert_to_sparse\", istate->repo);\n> +       return 0;\n> +}\n>\n>  static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n>  {\n> diff --git a/sparse-index.h b/sparse-index.h\n> index 09a20d036c46..64380e121d80 100644\n> --- a/sparse-index.h\n> +++ b/sparse-index.h\n> @@ -3,5 +3,6 @@\n>\n>  struct index_state;\n>  void ensure_full_index(struct index_state *istate);\n> +int convert_to_sparse(struct index_state *istate);\n>\n>  #endif\n> diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\n> index 4d789fe86b9d..ca87033d30b0 100755\n> --- a/t/t1092-sparse-checkout-compatibility.sh\n> +++ b/t/t1092-sparse-checkout-compatibility.sh\n> @@ -2,6 +2,9 @@\n>\n>  test_description='compare full workdir to sparse workdir'\n>\n> +GIT_TEST_CHECK_CACHE_TREE=0\n\nSame question as I posted for the RFC series:\n\nWhy do you need to set this?  I vaguely remember needing to mess with\nthis when working with sparse checkouts because it did weird stuff but\nI don't remember details.  But since your patch touches cache_trees, it\nseems weird to show up without explanation.\n\n> +GIT_TEST_SPLIT_INDEX=0\n> +\n>  . ./test-lib.sh\n>\n>  test_expect_success 'setup' '\n> @@ -121,15 +124,49 @@ run_on_all () {\n>  test_all_match () {\n>         run_on_all \"$@\" &&\n>         test_cmp full-checkout-out sparse-checkout-out &&\n> -       test_cmp full-checkout-err sparse-checkout-err\n> +       test_cmp full-checkout-out sparse-index-out &&\n> +       test_cmp full-checkout-err sparse-checkout-err &&\n> +       test_cmp full-checkout-err sparse-index-err\n>  }\n>\n>  test_sparse_match () {\n> -       run_on_sparse $* &&\n> +       run_on_sparse \"$@\" &&\n>         test_cmp sparse-checkout-out sparse-index-out &&\n>         test_cmp sparse-checkout-err sparse-index-err\n>  }\n>\n> +test_expect_success 'sparse-index contents' '\n> +       init_repos &&\n> +\n> +       test-tool -C sparse-index read-cache --table >cache &&\n> +       for dir in folder1 folder2 x\n> +       do\n> +               TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n> +               grep \"040000 tree $TREE $dir/\" cache \\\n> +                       || return 1\n> +       done &&\n\nThanks for making the output look more like ls-tree output; it's\neasier to parse that way, at least for me.\n\n> +\n> +       GIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set folder1 &&\n> +\n> +       test-tool -C sparse-index read-cache --table >cache &&\n> +       for dir in deep folder2 x\n> +       do\n> +               TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n> +               grep \"040000 tree $TREE $dir/\" cache \\\n> +                       || return 1\n> +       done &&\n> +\n> +       GIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep/deeper1 &&\n> +\n> +       test-tool -C sparse-index read-cache --table >cache &&\n> +       for dir in deep/deeper2 folder1 folder2 x\n> +       do\n> +               TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n> +               grep \"040000 tree $TREE $dir/\" cache \\\n> +                       || return 1\n> +       done\n> +'\n> +\n>  test_expect_success 'expanded in-memory index matches full index' '\n>         init_repos &&\n>         test_sparse_match test-tool read-cache --expand --table\n> @@ -137,6 +174,7 @@ test_expect_success 'expanded in-memory index matches full index' '\n>\n>  test_expect_success 'status with options' '\n>         init_repos &&\n> +       test_sparse_match ls &&\n>         test_all_match git status --porcelain=v2 &&\n>         test_all_match git status --porcelain=v2 -z -u &&\n>         test_all_match git status --porcelain=v2 -uno &&\n> @@ -273,6 +311,17 @@ test_expect_failure 'checkout and reset (mixed)' '\n>         test_all_match git reset update-folder2\n>  '\n>\n> +# Ensure that sparse-index behaves identically to\n> +# sparse-checkout with a full index.\n> +test_expect_success 'checkout and reset (mixed) [sparse]' '\n> +       init_repos &&\n> +\n> +       test_sparse_match git checkout -b reset-test update-deep &&\n> +       test_sparse_match git reset deepest &&\n> +       test_sparse_match git reset update-folder1 &&\n> +       test_sparse_match git reset update-folder2\n> +'\n> +\n>  test_expect_success 'merge' '\n>         init_repos &&\n>\n> @@ -309,14 +358,20 @@ test_expect_success 'clean' '\n>         test_all_match git status --porcelain=v2 &&\n>         test_all_match git clean -f &&\n>         test_all_match git status --porcelain=v2 &&\n> +       test_sparse_match ls &&\n> +       test_sparse_match ls folder1 &&\n>\n>         test_all_match git clean -xf &&\n>         test_all_match git status --porcelain=v2 &&\n> +       test_sparse_match ls &&\n> +       test_sparse_match ls folder1 &&\n>\n>         test_all_match git clean -xdf &&\n>         test_all_match git status --porcelain=v2 &&\n> +       test_sparse_match ls &&\n> +       test_sparse_match ls folder1 &&\n>\n> -       test_path_is_dir sparse-checkout/folder1\n> +       test_sparse_match test_path_is_dir folder1\n>  '\n>\n>  test_done\n> --\n> gitgitgadget\n\nI mostly read over the range-diff since it was much shorter.  You've\naddressed a number of questions/comments I had on the RFC version, but\nthere's still some I didn't see a response to so I reposted them here.\n"},{"id":"417800","messageId":"CABPp-BEMNavqAEG54VYkZvSiJ4=X3vZUXsubCmFfk3yxUWOjXQ@mail.gmail.com","threadId":"55195","inReplyTo":"fda23f07e6a20408b0a10c8944d54e7c65a1d629.1614111270.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 13/20] unpack-trees: allow sparse directories","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-02-25T07:40:30Z","receivedAt":"2021-02-25T07:42:32Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> The index_pos_by_traverse_info() currently throws a BUG() when a\n> directory entry exists exactly in the index. We need to consider that it\n> is possible to have a directory in a sparse index as long as that entry\n> is itself marked with the skip-worktree bit.\n>\n> The negation of the 'pos' variable must be conditioned to only when it\n> starts as negative. This is identical behavior as before when the index\n> is full.\n\nSame comment on the second paragraph as I made in the RFC series --\nhttps://lore.kernel.org/git/CABPp-BGPJgA4guWHVm3AVS=hM0fTixUpRvJe5i9NnHT-3QJMfw@mail.gmail.com/.\nI apologize if I'm repeating stuff you chose to not change, but I\ndidn't see a response and given the three typos left in previous\npatches, I'm unsure whether it was unaddressed on purpose or on\naccident.\n\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  unpack-trees.c | 9 ++++++---\n>  1 file changed, 6 insertions(+), 3 deletions(-)\n>\n> diff --git a/unpack-trees.c b/unpack-trees.c\n> index 4dd99219073a..b324eec2a5d1 100644\n> --- a/unpack-trees.c\n> +++ b/unpack-trees.c\n> @@ -746,9 +746,12 @@ static int index_pos_by_traverse_info(struct name_entry *names,\n>         strbuf_make_traverse_path(&name, info, names->path, names->pathlen);\n>         strbuf_addch(&name, '/');\n>         pos = index_name_pos(o->src_index, name.buf, name.len);\n> -       if (pos >= 0)\n> -               BUG(\"This is a directory and should not exist in index\");\n> -       pos = -pos - 1;\n> +       if (pos >= 0) {\n> +               if (!o->src_index->sparse_index ||\n> +                   !(o->src_index->cache[pos]->ce_flags & CE_SKIP_WORKTREE))\n> +                       BUG(\"This is a directory and should not exist in index\");\n> +       } else\n> +               pos = -pos - 1;\n>         if (pos >= o->src_index->cache_nr ||\n>             !starts_with(o->src_index->cache[pos]->name, name.buf) ||\n>             (pos > 0 && starts_with(o->src_index->cache[pos-1]->name, name.buf)))\n> --\n> gitgitgadget\n>\n"},{"id":"417802","messageId":"CABPp-BFnOgadewk+fSGy1ObHuZQb_-E0roz7HPE_cRHr3aW1UQ@mail.gmail.com","threadId":"55195","inReplyTo":"564503f7878475f220024def9e3bf20e4d518436.1614111270.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 15/20] sparse-index: create extension for compatibility","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-02-25T07:45:34Z","receivedAt":"2021-02-25T07:46:49Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> Previously, we enabled the sparse index format only using\n> GIT_TEST_SPARSE_INDEX=1. This is not a feasible direction for users to\n> actually select this mode. Further, sparse directory entries are not\n> understood by the index formats as advertised.\n>\n> We _could_ add a new index version that explicitly adds these\n> capabilities, but there are nuances to index formats 2, 3, and 4 that\n> are still valuable to select as options. For now, create a repo\n> extension, \"extensions.sparseIndex\", that specifies that the tool\n> reading this repository must understand sparse directory entries.\n\nThis commit is unchanged from the RFC series, but given your comments\nin the design document about how you do intend to create an index\nformat v5 now, do you want to reference that here?\n\n>\n> This change only encodes the extension and enables it when\n> GIT_TEST_SPARSE_INDEX=1. Later, we will add a more user-friendly CLI\n> mechanism.\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  Documentation/config/extensions.txt |  7 ++++++\n>  cache.h                             |  1 +\n>  repo-settings.c                     |  7 ++++++\n>  repository.h                        |  3 ++-\n>  setup.c                             |  3 +++\n>  sparse-index.c                      | 38 +++++++++++++++++++++++++----\n>  6 files changed, 53 insertions(+), 6 deletions(-)\n>\n> diff --git a/Documentation/config/extensions.txt b/Documentation/config/extensions.txt\n> index 4e23d73cdcad..5c86b3648732 100644\n> --- a/Documentation/config/extensions.txt\n> +++ b/Documentation/config/extensions.txt\n> @@ -6,3 +6,10 @@ extensions.objectFormat::\n>  Note that this setting should only be set by linkgit:git-init[1] or\n>  linkgit:git-clone[1].  Trying to change it after initialization will not\n>  work and will produce hard-to-diagnose issues.\n> +\n> +extensions.sparseIndex::\n> +       When combined with `core.sparseCheckout=true` and\n> +       `core.sparseCheckoutCone=true`, the index may contain entries\n> +       corresponding to directories outside of the sparse-checkout\n> +       definition. Versions of Git that do not understand this extension\n> +       do not expect directory entries in the index.\n\nI had a wording suggestion for this paragraph in the RFC series --\nhttps://lore.kernel.org/git/CABPp-BFEJE82k4VgkR=Jf7V7sZxZzo2pHMfAGshhi9_vV6iK0w@mail.gmail.com/.\nLet me know if you just decided to leave it out so I don't bug you\nabout stuff you already considered.\n\n> diff --git a/cache.h b/cache.h\n> index e8b7d3b4fb33..eea61fba7568 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -1053,6 +1053,7 @@ struct repository_format {\n>         int worktree_config;\n>         int is_bare;\n>         int hash_algo;\n> +       int sparse_index;\n>         char *work_tree;\n>         struct string_list unknown_extensions;\n>         struct string_list v1_only_extensions;\n> diff --git a/repo-settings.c b/repo-settings.c\n> index d63569e4041e..9677d50f9238 100644\n> --- a/repo-settings.c\n> +++ b/repo-settings.c\n> @@ -85,4 +85,11 @@ void prepare_repo_settings(struct repository *r)\n>          * removed.\n>          */\n>         r->settings.command_requires_full_index = 1;\n> +\n> +       /*\n> +        * Initialize this as off.\n> +        */\n> +       r->settings.sparse_index = 0;\n> +       if (!repo_config_get_bool(r, \"extensions.sparseindex\", &value) && value)\n> +               r->settings.sparse_index = 1;\n>  }\n> diff --git a/repository.h b/repository.h\n> index e06a23015697..a45f7520fd9e 100644\n> --- a/repository.h\n> +++ b/repository.h\n> @@ -42,7 +42,8 @@ struct repo_settings {\n>\n>         int core_multi_pack_index;\n>\n> -       unsigned command_requires_full_index:1;\n> +       unsigned command_requires_full_index:1,\n> +                sparse_index:1;\n>  };\n>\n>  struct repository {\n> diff --git a/setup.c b/setup.c\n> index c04cd25a30df..cd8394564613 100644\n> --- a/setup.c\n> +++ b/setup.c\n> @@ -500,6 +500,9 @@ static enum extension_result handle_extension(const char *var,\n>                         return error(\"invalid value for 'extensions.objectformat'\");\n>                 data->hash_algo = format;\n>                 return EXTENSION_OK;\n> +       } else if (!strcmp(ext, \"sparseindex\")) {\n> +               data->sparse_index = 1;\n> +               return EXTENSION_OK;\n>         }\n>         return EXTENSION_UNKNOWN;\n>  }\n> diff --git a/sparse-index.c b/sparse-index.c\n> index 14029fafc750..97b0d0c57857 100644\n> --- a/sparse-index.c\n> +++ b/sparse-index.c\n> @@ -102,19 +102,47 @@ static int convert_to_sparse_rec(struct index_state *istate,\n>         return num_converted - start_converted;\n>  }\n>\n> +static int enable_sparse_index(struct repository *repo)\n> +{\n> +       const char *config_path = repo_git_path(repo, \"config.worktree\");\n> +\n> +       if (upgrade_repository_format(1) < 0) {\n> +               warning(_(\"unable to upgrade repository format to enable sparse-index\"));\n> +               return -1;\n> +       }\n> +       git_config_set_in_file_gently(config_path,\n> +                                     \"extensions.sparseIndex\",\n> +                                     \"true\");\n> +\n> +       prepare_repo_settings(repo);\n> +       repo->settings.sparse_index = 1;\n> +       return 0;\n> +}\n> +\n>  int convert_to_sparse(struct index_state *istate)\n>  {\n>         if (istate->split_index || istate->sparse_index ||\n>             !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n>                 return 0;\n>\n> +       if (!istate->repo)\n> +               istate->repo = the_repository;\n> +\n> +       /*\n> +        * The GIT_TEST_SPARSE_INDEX environment variable triggers the\n> +        * extensions.sparseIndex config variable to be on.\n> +        */\n> +       if (git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0)) {\n> +               int err = enable_sparse_index(istate->repo);\n> +               if (err < 0)\n> +                       return err;\n> +       }\n> +\n>         /*\n> -        * For now, only create a sparse index with the\n> -        * GIT_TEST_SPARSE_INDEX environment variable. We will relax\n> -        * this once we have a proper way to opt-in (and later still,\n> -        * opt-out).\n> +        * Only convert to sparse if extensions.sparseIndex is set.\n>          */\n> -       if (!git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0))\n> +       prepare_repo_settings(istate->repo);\n> +       if (!istate->repo->settings.sparse_index)\n>                 return 0;\n>\n>         if (!istate->sparse_checkout_patterns) {\n> --\n> gitgitgadget\n"},{"id":"417821","messageId":"36682d84-826a-a5f0-2309-c4a5fa4b9d73@gmail.com","threadId":"55195","inReplyTo":"CABPp-BHwRHhYE3BPxCG+fRFWf2dCZ60rnO=ykztquKoOp_x_QQ@mail.gmail.com","subject":"Re: [PATCH 01/20] sparse-index: design doc and format update","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-02-25T15:29:32Z","receivedAt":"2021-02-25T15:30:24Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 2/23/2021 8:13 PM, Elijah Newren wrote:\n> On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:>> +This addition of sparse-directory entries violates expectations about the\n> \n> Violates current expectations, yes.  Documentation tends to live a\n> long time, and I suspect that 2-3 years from now reading this sentence\n> might be jarring since we'll have modified the code to have an updated\n> set of expectations.  Maybe a simple \"As of time of writing, ...\" at\n> the beginning of the sentence here?  Or maybe I'm just being overly\n> worried...\n\nI was hoping that the phrase \"this addition of\" places this statement in\na moment of time where sparse-directory entries didn't exist, but now they\nwill. I'm open to alternatives and will give this some thought.\n\n>> +To complete this phase, the commands `git status` and `git add` will be\n>> +integrated with the sparse-index so that they operate with O(Populated)\n>> +performance. They will be carefully tested for operations within and\n>> +outside the sparse-checkout definition.\n> \n> Good plan so far, but there's something else bugging me a little here.\n> One thing we noticed with our usage of `sparse-checkout` is that\n> although unimportant _tracked_ files go away, leftover build files and\n> other untracked files stick around.  So, although 'git status'\n> shouldn't have to check the tracked files anymore, it is still going\n> to have to look at each of the *untracked* files and compare to\n> .gitignore files in order to correctly classify each file as ignored\n> or just plain untracked.  Our `sparsify` tool has for a long time\n> tried to warn about such files when changing the sparsity\n> patterns/modules and had an --remove-old-ignores option for clearing\n> out ignored files that are found within directories that are sparse\n> (Meaning the directories where all files under them are marked\n> SKIP_WORKTREE.). I was never sure whether a warning was enough, or if\n> pushing that option more made sense, but about a month ago my\n> colleagues made the tool just auto-invoke that option from other\n> sparsify invocations.  To my knowledge, there have been no complaints\n> about that being automatically turned on; but there were\n> complaints/confusion before about the directories being left around.\n> (Of course, non-ignored files are still left around by that option.)\n> \n> I'm worried that since sparse-checkout doesn't do anything to help\n> with all these untracked/ignored files, we might not get all the\n> performance improvements we want from a `git status` with sparse\n> directories.  We'll be dropping from walking O(2*HEAD) files (1 source\n> + 1 object file) down to O(HEAD) files (just the object files) rather\n> than actually getting down to O(Populated).\n\nThis definitely seems like a valuable _enhancement_ to sparse-checkout\nthat could occur in parallel.\n\nFor a workaround in the moment: is \"git clean -xdf\" sufficient to help\nthese users?\n\n>> +Phase III: Important command speedups\n>> +-------------------------------------\n>> +\n>> +At this point, the patterns for testing and implementing sparse-directory\n>> +logic should be relatively stable. This phase focuses on updating some of\n>> +the most common builtins that use the index to operate as O(Populated).\n>> +Here is a potential list of commands that could be valuable to integrate\n>> +at this point:\n>> +\n>> +* `git commit`\n>> +* `git checkout`\n>> +* `git merge`\n>> +* `git rebase`\n>> +\n>> +Along with `git status` and `git add`, these commands cover the majority\n>> +of users' interactions with the working directory.\n> \n> Sounds like a good plan as well.\n> \n> I hope we get to make this specific to the merge-ort backend.  It\n> localizes the index-related code to (a) a call to unpack_trees()\n> called from checkout-like code (which would probably automatically be\n> handled by your updates to git checkout), and (b) a single function\n> named record_conflicted_index_entries().  I feel it should be pretty\n> easy to update.\n> \n> In contrast, the idea of attempting to update merge-recursive with\n> this kind of change sounds overwhelming.\n\nYes, I'm hoping to eventually say \"if you are in a sparse-checkout, then\nyou should use ORT by default.\" Then, if someone opts to do merge-recursive\ninstead, then they pay the index expansion cost.\n\nWhile this is very clear in my head, it might be worth mentioning that\nexplicitly here.\n\n>>  In addition, we can\n>> +integrate with these commands:\n>> +\n>> +* `git grep`\n>> +* `git rm`\n>> +\n>> +These have been proposed as some whose behavior could change when in a\n>> +repo with a sparse-checkout definition. It would be good to include this\n>> +behavior automatically when using a sparse-index. Some clarity is needed\n>> +to make the behavior switch clear to the user.\n> \n> Is this leftover from before recent events?  I think this portion of\n> the document should just be stricken.\n> \n> I argued about how these were buggy as-is due SKIP_WORKTREE always\n> having been an incomplete implementation of an idea at [1], but didn't\n> hear a further response from you.  I'm curious if you disagreed with\n> my reasoning, or it just slipped through the cracks in a busy schedule\n> and this portion of the document was leftover from before.  In my\n> opinion, both commands are just buggy and should be fixed for general\n> sparse-checkout usage cases, not just for sparse-index.\n\nThis is definitely a case of \"I've been too busy to read those topics\nin detail.\" I figured that there was something going on that was relevant\nto the sparse-checkout and would affect the sparse-index, but I shelved\nit in my mind until I had space to think about it directly.\n\n> Anyway, that's a long way of saying I think this section of your\n> document is already obsolete.  (Which is a good thing -- less work to\n> do to get sparse-index working.  Thanks, Matheus!).\n\nThank you for your summary, which helps a lot. Thanks, Matheus, too!\nIf those fixes already include coverage for the behavior, then I'll see\nif they also translate to sparse-index tests easily.\n\nI feel like a lot of these later contributions will be more about adding\ntests than actually writing a lot of code.\n\n>> +This phase is the first where parallel work might be possible without too\n>> +much conflicts between topics.\n>> +\n>> +Phase IV: The long tail\n>> +-----------------------\n>> +\n>> +This last phase is less a \"phase\" and more \"the new normal\" after all of\n>> +the previous work.\n>> +\n>> +To start, the `command_requires_full_index` option could be removed in\n>> +favor of expanding only when hitting an API guard.\n>> +\n>> +There are many Git commands that could use special attention to operate as\n>> +O(Populated), while some might be so rare that it is acceptable to leave\n>> +them with additional overhead when a sparse-index is present.\n>> +\n>> +Here are some commands that might be useful to update:\n>> +\n>> +* `git sparse-checkout set`\n>> +* `git am`\n>> +* `git clean`\n>> +* `git stash`\n> \n> Oh, man, git stash is definitely in need of work.  It's still a\n> minimalistic transliteration of shell to C, complete with lots of\n> process forking and piping output between various low-level commands.\n> It might be interesting to rewrite this in terms of the merge\n> machinery, though its separate stashing of staged stuff, unstaged\n> stuff, and possibly untracked stuff means that there is a sequence of\n> two or three merges needed and interesting failure handling to do if\n> those merges fail, especially if the user uses --index.  But I\n> digress...\n\nI would prefer to leave 'git stash' alone, but it's used by enough\npeople that I need to care about it eventually.\n\n> Anyway, overall, very nicely written and planned out.  Thanks for\n> taking the time to write this all up.\n\nThanks for your detailed comments!\n-Stolee\n \n\n"},{"id":"417844","messageId":"CABPp-BHGxmfx+OPpF-qzvChhf-DmAgzE_B_5Pvv+NJ7exJE=TA@mail.gmail.com","threadId":"55195","inReplyTo":"36682d84-826a-a5f0-2309-c4a5fa4b9d73@gmail.com","subject":"Re: [PATCH 01/20] sparse-index: design doc and format update","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-02-25T20:14:14Z","receivedAt":"2021-02-25T20:15:09Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Feb 25, 2021 at 7:29 AM Derrick Stolee <stolee@gmail.com> wrote:\n>\n> On 2/23/2021 8:13 PM, Elijah Newren wrote:\n> > On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n> > <gitgitgadget@gmail.com> wrote:>> +This addition of sparse-directory entries violates expectations about the\n> >\n> > Violates current expectations, yes.  Documentation tends to live a\n> > long time, and I suspect that 2-3 years from now reading this sentence\n> > might be jarring since we'll have modified the code to have an updated\n> > set of expectations.  Maybe a simple \"As of time of writing, ...\" at\n> > the beginning of the sentence here?  Or maybe I'm just being overly\n> > worried...\n>\n> I was hoping that the phrase \"this addition of\" places this statement in\n> a moment of time where sparse-directory entries didn't exist, but now they\n> will. I'm open to alternatives and will give this some thought.\n\nI already listed my only suggestion -- adding a \"As of time of\nwriting,\" at the beginning.  I'm totally open to other\nproposals/suggestions, and it's admittedly a minor point so you can\nfeel free to just ignore it if we can't come up with wording everyone\nlikes.\n\n>\n> >> +To complete this phase, the commands `git status` and `git add` will be\n> >> +integrated with the sparse-index so that they operate with O(Populated)\n> >> +performance. They will be carefully tested for operations within and\n> >> +outside the sparse-checkout definition.\n> >\n> > Good plan so far, but there's something else bugging me a little here.\n> > One thing we noticed with our usage of `sparse-checkout` is that\n> > although unimportant _tracked_ files go away, leftover build files and\n> > other untracked files stick around.  So, although 'git status'\n> > shouldn't have to check the tracked files anymore, it is still going\n> > to have to look at each of the *untracked* files and compare to\n> > .gitignore files in order to correctly classify each file as ignored\n> > or just plain untracked.  Our `sparsify` tool has for a long time\n> > tried to warn about such files when changing the sparsity\n> > patterns/modules and had an --remove-old-ignores option for clearing\n> > out ignored files that are found within directories that are sparse\n> > (Meaning the directories where all files under them are marked\n> > SKIP_WORKTREE.). I was never sure whether a warning was enough, or if\n> > pushing that option more made sense, but about a month ago my\n> > colleagues made the tool just auto-invoke that option from other\n> > sparsify invocations.  To my knowledge, there have been no complaints\n> > about that being automatically turned on; but there were\n> > complaints/confusion before about the directories being left around.\n> > (Of course, non-ignored files are still left around by that option.)\n> >\n> > I'm worried that since sparse-checkout doesn't do anything to help\n> > with all these untracked/ignored files, we might not get all the\n> > performance improvements we want from a `git status` with sparse\n> > directories.  We'll be dropping from walking O(2*HEAD) files (1 source\n> > + 1 object file) down to O(HEAD) files (just the object files) rather\n> > than actually getting down to O(Populated).\n>\n> This definitely seems like a valuable _enhancement_ to sparse-checkout\n> that could occur in parallel.\n\nYes, indeed.  Your discussion of performance just reminded me of it,\nand since this idea might be important in order to drive the costs\ndown to O(populated) in practice, I thought I'd mention it.\n\n> For a workaround in the moment: is \"git clean -xdf\" sufficient to help\n> these users?\n\nNot really; that wouldn't remove the ignored stuff (build files) under\nsparsified directories which is the point.  (Builds build everything\nover here; once you sparsify you have leftover build files from\nprojects you now don't care about.)  If you convert it to \"git clean\n-Xdf\" then you're closer, but that wouldn't just remove builds info\nfrom sparse projects, it'd force users to rebuild all the stuff\nthey're interested in.\n\nIt's close though; what's wanted is basically a special flag that runs\n\"git clean -Xf <long list of sparsified directories>\", without the\nuser having to specify 300 directories.\n\nHowever, for now, since I've got a 'sparsify' script anyway (needed\nfor determining inter-module dependencies and certain directories that\nalways need to be present, etc.), it just has a flag for running \"git\nclean -Xf <long list of sparsified directories>\" since it has logic to\ncompute what all those directories are anyway.\n\n> >> +Phase III: Important command speedups\n> >> +-------------------------------------\n> >> +\n> >> +At this point, the patterns for testing and implementing sparse-directory\n> >> +logic should be relatively stable. This phase focuses on updating some of\n> >> +the most common builtins that use the index to operate as O(Populated).\n> >> +Here is a potential list of commands that could be valuable to integrate\n> >> +at this point:\n> >> +\n> >> +* `git commit`\n> >> +* `git checkout`\n> >> +* `git merge`\n> >> +* `git rebase`\n> >> +\n> >> +Along with `git status` and `git add`, these commands cover the majority\n> >> +of users' interactions with the working directory.\n> >\n> > Sounds like a good plan as well.\n> >\n> > I hope we get to make this specific to the merge-ort backend.  It\n> > localizes the index-related code to (a) a call to unpack_trees()\n> > called from checkout-like code (which would probably automatically be\n> > handled by your updates to git checkout), and (b) a single function\n> > named record_conflicted_index_entries().  I feel it should be pretty\n> > easy to update.\n> >\n> > In contrast, the idea of attempting to update merge-recursive with\n> > this kind of change sounds overwhelming.\n>\n> Yes, I'm hoping to eventually say \"if you are in a sparse-checkout, then\n> you should use ORT by default.\" Then, if someone opts to do merge-recursive\n> instead, then they pay the index expansion cost.\n>\n> While this is very clear in my head, it might be worth mentioning that\n> explicitly here.\n\n:+1:\n\n> >>  In addition, we can\n> >> +integrate with these commands:\n> >> +\n> >> +* `git grep`\n> >> +* `git rm`\n> >> +\n> >> +These have been proposed as some whose behavior could change when in a\n> >> +repo with a sparse-checkout definition. It would be good to include this\n> >> +behavior automatically when using a sparse-index. Some clarity is needed\n> >> +to make the behavior switch clear to the user.\n> >\n> > Is this leftover from before recent events?  I think this portion of\n> > the document should just be stricken.\n> >\n> > I argued about how these were buggy as-is due SKIP_WORKTREE always\n> > having been an incomplete implementation of an idea at [1], but didn't\n> > hear a further response from you.  I'm curious if you disagreed with\n> > my reasoning, or it just slipped through the cracks in a busy schedule\n> > and this portion of the document was leftover from before.  In my\n> > opinion, both commands are just buggy and should be fixed for general\n> > sparse-checkout usage cases, not just for sparse-index.\n>\n> This is definitely a case of \"I've been too busy to read those topics\n> in detail.\" I figured that there was something going on that was relevant\n> to the sparse-checkout and would affect the sparse-index, but I shelved\n> it in my mind until I had space to think about it directly.\n>\n> > Anyway, that's a long way of saying I think this section of your\n> > document is already obsolete.  (Which is a good thing -- less work to\n> > do to get sparse-index working.  Thanks, Matheus!).\n>\n> Thank you for your summary, which helps a lot. Thanks, Matheus, too!\n> If those fixes already include coverage for the behavior, then I'll see\n> if they also translate to sparse-index tests easily.\n>\n> I feel like a lot of these later contributions will be more about adding\n> tests than actually writing a lot of code.\n>\n> >> +This phase is the first where parallel work might be possible without too\n> >> +much conflicts between topics.\n> >> +\n> >> +Phase IV: The long tail\n> >> +-----------------------\n> >> +\n> >> +This last phase is less a \"phase\" and more \"the new normal\" after all of\n> >> +the previous work.\n> >> +\n> >> +To start, the `command_requires_full_index` option could be removed in\n> >> +favor of expanding only when hitting an API guard.\n> >> +\n> >> +There are many Git commands that could use special attention to operate as\n> >> +O(Populated), while some might be so rare that it is acceptable to leave\n> >> +them with additional overhead when a sparse-index is present.\n> >> +\n> >> +Here are some commands that might be useful to update:\n> >> +\n> >> +* `git sparse-checkout set`\n> >> +* `git am`\n> >> +* `git clean`\n> >> +* `git stash`\n> >\n> > Oh, man, git stash is definitely in need of work.  It's still a\n> > minimalistic transliteration of shell to C, complete with lots of\n> > process forking and piping output between various low-level commands.\n> > It might be interesting to rewrite this in terms of the merge\n> > machinery, though its separate stashing of staged stuff, unstaged\n> > stuff, and possibly untracked stuff means that there is a sequence of\n> > two or three merges needed and interesting failure handling to do if\n> > those merges fail, especially if the user uses --index.  But I\n> > digress...\n>\n> I would prefer to leave 'git stash' alone, but it's used by enough\n> people that I need to care about it eventually.\n\nOh, it can definitely come later.  And I agree about the desirability\nof touching that code; I was avoiding it for a long time, but there\nwas one important sparse-checkout-related bug recently[1] so I've\nalready been forced to touch it once.  That might mean I'm\n(eventually) on the hook to make it sparse-index friendly, especially\nsince it might involve using merge-ort to do so...\n\n[1] https://lore.kernel.org/git/pull.919.git.git.1605891222.gitgitgadget@gmail.com/\n\n> > Anyway, overall, very nicely written and planned out.  Thanks for\n> > taking the time to write this all up.\n>\n> Thanks for your detailed comments!\n> -Stolee\n"},{"id":"417914","messageId":"CABPp-BG+gK=Opmjhg-X2GEkvWt9C62hjiESjX=YwDXp=_x1yzg@mail.gmail.com","threadId":"55195","inReplyTo":"CABPp-BEf5F_BT69_V0vOq9CdcNEce6qR5nk+2XZsdS5EeLV0_g@mail.gmail.com","subject":"Re: [PATCH 00/20] Sparse Index: Design, Format, Tests","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-02-26T21:28:26Z","receivedAt":"2021-02-26T21:29:40Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Feb 23, 2021 at 3:49 PM Elijah Newren <newren@gmail.com> wrote:\n>\n> On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> >\n> > Here is the first full patch series submission coming out of the\n> > sparse-index RFC [1].\n>\n> Wahoo!  I'll be reading these over the next few days.\n\nI finally finished the last five patches today, and didn't spot\nanything on those to comment on.\n\nOverall, I find the series well constructed, motivated, and explained.\nI've left various comments on individual patches, but they're mostly\nall minor things that should be easy to cleanup and/or just comment\non.\n"},{"id":"417944","messageId":"20210227123253.GC3590451@szeder.dev","threadId":"55195","inReplyTo":"bcf960ef2362e32e2f8d7e06a31a925936f62bc1.1614111270.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 17/20] sparse-checkout: disable sparse-index","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2021-02-27T12:32:53Z","receivedAt":"2021-02-27T12:33:51Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Tue, Feb 23, 2021 at 08:14:26PM +0000, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <dstolee@microsoft.com>\n> \n> We use 'git sparse-checkout init --cone --sparse-index' to toggle the\n> sparse-index feature. It makes sense to also disable it when running\n> 'git sparse-checkout disable'. This is particularly important because it\n> removes the extensions.sparseIndex config option, allowing other tools\n> to use this Git repository again.\n> \n> This does mean that 'git sparse-checkout init' will not re-enable the\n> sparse-index feature, even if it was previously enabled.\n> \n> While testing this feature, I noticed that the sparse-index was not\n> being written on the first run, but by a second. This was caught by the\n> call to 'test-tool read-cache --table'. This requires adjusting some\n> assignments to core_apply_sparse_checkout and pl.use_cone_patterns in\n> the sparse_checkout_init() logic.\n> \n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  builtin/sparse-checkout.c          | 10 +++++++++-\n>  t/t1091-sparse-checkout-builtin.sh | 13 +++++++++++++\n>  2 files changed, 22 insertions(+), 1 deletion(-)\n> \n> diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\n> index ca63e2c64e95..585343fa1972 100644\n> --- a/builtin/sparse-checkout.c\n> +++ b/builtin/sparse-checkout.c\n> @@ -280,6 +280,9 @@ static int set_config(enum sparse_checkout_mode mode)\n>  \t\t\t\t      \"core.sparseCheckoutCone\",\n>  \t\t\t\t      mode == MODE_CONE_PATTERNS ? \"true\" : NULL);\n>  \n> +\tif (mode == MODE_NO_PATTERNS)\n> +\t\tset_sparse_index_config(the_repository, 0);\n> +\n>  \treturn 0;\n>  }\n>  \n> @@ -341,10 +344,11 @@ static int sparse_checkout_init(int argc, const char **argv)\n>  \t\tthe_repository->index->updated_workdir = 1;\n>  \t}\n>  \n> +\tcore_apply_sparse_checkout = 1;\n> +\n>  \t/* If we already have a sparse-checkout file, use it. */\n>  \tif (res >= 0) {\n>  \t\tfree(sparse_filename);\n> -\t\tcore_apply_sparse_checkout = 1;\n>  \t\treturn update_working_directory(NULL);\n>  \t}\n>  \n> @@ -366,6 +370,7 @@ static int sparse_checkout_init(int argc, const char **argv)\n>  \tadd_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);\n>  \tstrbuf_addstr(&pattern, \"!/*/\");\n>  \tadd_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);\n> +\tpl.use_cone_patterns = init_opts.cone_mode;\n>  \n>  \treturn write_patterns_and_update(&pl);\n>  }\n> @@ -632,6 +637,9 @@ static int sparse_checkout_disable(int argc, const char **argv)\n>  \tstrbuf_addstr(&match_all, \"/*\");\n>  \tadd_pattern(strbuf_detach(&match_all, NULL), empty_base, 0, &pl, 0);\n>  \n> +\tprepare_repo_settings(the_repository);\n> +\tthe_repository->settings.sparse_index = 0;\n> +\n>  \tif (update_working_directory(&pl))\n>  \t\tdie(_(\"error while refreshing working directory\"));\n>  \n> diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh\n> index fc64e9ed99f4..ff1ad570a255 100755\n> --- a/t/t1091-sparse-checkout-builtin.sh\n> +++ b/t/t1091-sparse-checkout-builtin.sh\n> @@ -205,6 +205,19 @@ test_expect_success 'sparse-checkout disable' '\n>  \tcheck_files repo a deep folder1 folder2\n>  '\n>  \n> +test_expect_success 'sparse-index enabled and disabled' '\n> +\tgit -C repo sparse-checkout init --cone --sparse-index &&\n> +\ttest_cmp_config -C repo true extensions.sparseIndex &&\n> +\ttest-tool -C repo read-cache --table >cache &&\n> +\tgrep \" tree \" cache &&\n> +\n> +\tgit -C repo sparse-checkout disable &&\n> +\ttest-tool -C repo read-cache --table >cache &&\n> +\t! grep \" tree \" cache &&\n> +\tgit -C repo config --list >config &&\n> +\t! grep extensions.sparseindex config\n> +'\n\nThis test passes with GIT_TEST_SPLIT_INDEX=1 at the moment, because,\nunfortunately, GIT_TEST_SPLIT_INDEX has been broken for the past two\nyears.  However, if I run it with my WIP fixes for that issue [1],\nthen it will fail:\n\n  +git -C repo sparse-checkout init --cone --sparse-index\n  +test_cmp_config -C repo true extensions.sparseIndex\n  +test-tool -C repo read-cache --table\n  +grep  tree  cache\n  error: last command exited with $?=1\n  not ok 16 - sparse-index enabled and disabled\n\nhttps://travis-ci.com/github/szeder/git-cooking-topics-for-travis-ci/jobs/486702444#L2594\n\n[1] Try to run it with:\n\n      https://github.com/szeder/git split-index-fixes\n\n    The code is, I believe, close to final, the commit messages,\n    however, are far from being finished.\n\n\n> +\n>  test_expect_success 'cone mode: init and set' '\n>  \tgit -C repo sparse-checkout init --cone &&\n>  \tgit -C repo config --list >config &&\n> -- \n> gitgitgadget\n> \n"},{"id":"418635","messageId":"d49c536a-7ca1-cb6f-9703-d3c6fb9802b4@gmail.com","threadId":"55195","inReplyTo":"CABPp-BGYP4OdVnUW0t_Veus-bzMPNH97MSNBdyH=e-iuN2b2jw@mail.gmail.com","subject":"Re: [PATCH 02/20] t/perf: add performance test for sparse operations","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-09T20:03:49Z","receivedAt":"2021-03-09T20:04:53Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 2/23/2021 9:30 PM, Elijah Newren wrote:\n> On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> +test_expect_success 'setup repo and indexes' '\n> +       git reset --hard HEAD &&\n> +       # Remove submodules from the example repo, because our\n> +       # duplication of the entire repo creates an unlikly data shape.\n> +       git config --file .gitmodules --get-regexp \"submodule.*.path\" >modules &&\n> +       rm -f .gitmodules &&\n> +       git add .gitmodules &&\n> Why not `git rm [-f] .gitmodules` instead of these two commands?  Is\n> there something special about .gitmodules that requires this special\n> handling?\n\nNo, I'm just being sloppy. Will clean up.\n\n>> +       for module in $(awk \"{print \\$2}\" modules)\n>> +       do\n>> +               git rm $module || return 1\n>> +       done &&\n>> +       git add . &&\n> What does the `git add .` do?  I don't see any changes there weren't\n> already git-add'ed or git-rm'ed.\n\nSame here. Thanks.\n\n-Stolee\n\n"},{"id":"418637","messageId":"48e9c3d6-407a-1843-2d91-22112410e3f8@gmail.com","threadId":"55195","inReplyTo":"20210227123253.GC3590451@szeder.dev","subject":"Re: [PATCH 17/20] sparse-checkout: disable sparse-index","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-09T20:20:42Z","receivedAt":"2021-03-09T20:21:39Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 2/27/2021 7:32 AM, SZEDER Gábor wrote:\n> On Tue, Feb 23, 2021 at 08:14:26PM +0000, Derrick Stolee via GitGitGadget wrote:\n>> +test_expect_success 'sparse-index enabled and disabled' '\n>> +\tgit -C repo sparse-checkout init --cone --sparse-index &&\n>> +\ttest_cmp_config -C repo true extensions.sparseIndex &&\n>> +\ttest-tool -C repo read-cache --table >cache &&\n>> +\tgrep \" tree \" cache &&\n>> +\n>> +\tgit -C repo sparse-checkout disable &&\n>> +\ttest-tool -C repo read-cache --table >cache &&\n>> +\t! grep \" tree \" cache &&\n>> +\tgit -C repo config --list >config &&\n>> +\t! grep extensions.sparseindex config\n>> +'\n> \n> This test passes with GIT_TEST_SPLIT_INDEX=1 at the moment, because,\n> unfortunately, GIT_TEST_SPLIT_INDEX has been broken for the past two\n> years.  However, if I run it with my WIP fixes for that issue [1],\n> then it will fail:\n> \n>   +git -C repo sparse-checkout init --cone --sparse-index\n>   +test_cmp_config -C repo true extensions.sparseIndex\n>   +test-tool -C repo read-cache --table\n>   +grep  tree  cache\n>   error: last command exited with $?=1\n>   not ok 16 - sparse-index enabled and disabled\n> \n> https://travis-ci.com/github/szeder/git-cooking-topics-for-travis-ci/jobs/486702444#L2594\n> \n> [1] Try to run it with:\n> \n>       https://github.com/szeder/git split-index-fixes\n> \n>     The code is, I believe, close to final, the commit messages,\n>     however, are far from being finished.\n\nI'll keep that in mind. I should have added a variable\nthat disables GIT_TEST_SPLIT_INDEX for this test script,\nsince the sparse-index is (currently) incompatible with\nthe split-index. I bet that the test is failing because\nit isn't actually writing the sparse-directory entry due\nto that short-circuit check.\n\nThanks,\n-Stolee\n"},{"id":"418641","messageId":"466cb063-0481-cfa6-cc3e-8ca26d73c8fb@gmail.com","threadId":"55195","inReplyTo":"CAN0heSqPTASn61KKGvWEA3ronaWEPWGP23LUg36JiMxxHEd0zQ@mail.gmail.com","subject":"Re: [PATCH 16/20] sparse-checkout: toggle sparse index from builtin","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-09T20:52:19Z","receivedAt":"2021-03-09T20:53:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 2/24/2021 2:11 PM, Martin Ågren wrote:\n> On Wed, 24 Feb 2021 at 00:57, Derrick Stolee via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>> +that is not completely understood by other tools. Enabling sparse index\n>> +enables the `extensions.spareseIndex` config value, which might cause\n> \n> s/sparese/sparse\n\nThanks!\n\n \n>> +other tools to stop working with your repository. If you have trouble with\n>> +this compatibility, then run `git sparse-checkout sparse-index disable` to\n>> +remove this config and rewrite your index to not be sparse.\n> \n> While I'm commenting on this..:\n> \n> There are several \"layers\" here, for lack of a better term. \"Enabling foo\n> enables bar which may cause baz. If you fail due to baz, try dropping\n> bar by dropping foo.\" If I remove any mention of the config variable from\n> your text, I get the following.\n> \n>  Enabling sparse index might cause other tools to stop working with your\n>  repository. If you have trouble with this compatibility, then run `git\n>  sparse-checkout sparse-index disable` to rewrite your index to not be\n>  sparse.\n> \n> I'm tempted to suggest such a rewrite to relieve readers of knowing of\n> the middle step, which you could say is more of an implementation\n> detail. But if we think that the symptoms / error messages might involve\n> \"extensions.sparseIndex\" or, as would be the case with an older Git\n> installation,\n> \n>   fatal: unknown repository extensions found:\n>           sparseindex\n> \n> maybe there is some value in mentioning the config item by name. Just\n> thinking out loud, really, and I don't have any strong opinion. I only\n> came here to point out the typo in the docs.\n \nI agree that the layers are confusing. We could rearrange and have\na similar flow to what you recommend by mentioning the extension at\nthe end:\n\n**WARNING:** Using a sparse index requires modifying the index in a way\nthat is not completely understood by other tools. If you have trouble with\nthis compatibility, then run `git sparse-checkout sparse-index disable` to\nrewrite your index to not be sparse. Older versions of Git will not\nunderstand the `sparseIndex` repository extension and may fail to interact\nwith your repository until it is disabled.\n\nThanks,\n-Stolee\n"},{"id":"418645","messageId":"27f113c8-313e-6812-0646-5073891df448@gmail.com","threadId":"55195","inReplyTo":"CABPp-BGspVmXFNpAoF-8wcb4eyjyivM4Cpcy4wRfjL6kMxcu6Q@mail.gmail.com","subject":"Re: [PATCH 07/20] test-read-cache: print cache entries with --table","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-09T21:00:57Z","receivedAt":"2021-03-09T21:01:48Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 2/25/2021 2:02 AM, Elijah Newren wrote:\n> On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Derrick Stolee <dstolee@microsoft.com>\n>>\n>> This table is helpful for discovering data in the index to ensure it is\n>> being written correctly, especially as we build and test the\n>> sparse-index. This table includes an output format similar to 'git\n>> ls-tree', but should not be compared to that directly. The biggest\n>> reasons are that 'git ls-tree' includes a tree entry for every\n>> subdirectory, even those that would not appear as a sparse directory in\n>> a sparse-index. Further, 'git ls-tree' does not use a trailing directory\n>> separator for its tree rows.\n>>\n>> This does not print the stat() information for the blobs. That could be\n>> added in a future change with another option. The tests that are added\n>> in the next few changes care only about the object types and IDs.\n>>\n>> To make the option parsing slightly more robust, wrap the string\n>> comparisons in a loop adapted from test-dir-iterator.c.\n>>\n>> Care must be taken with the final check for the 'cnt' variable. We\n>> continue the expectation that the numerical value is the final argument.\n>>\n>> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n>> ---\n>>  t/helper/test-read-cache.c | 50 ++++++++++++++++++++++++++++++--------\n>>  1 file changed, 40 insertions(+), 10 deletions(-)\n>>\n>> diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c\n>> index 244977a29bdf..e4c3492f7d3e 100644\n>> --- a/t/helper/test-read-cache.c\n>> +++ b/t/helper/test-read-cache.c\n>> @@ -2,35 +2,65 @@\n>>  #include \"cache.h\"\n>>  #include \"config.h\"\n>>\n>> +static void print_cache_entry(struct cache_entry *ce)\n>> +{\n>> +       printf(\"%06o \", ce->ce_mode & 0777777);\n> \n> This constant is curious.  I think it's because you want to strip off\n> the special in-memory bits of the ce_mode where git stores extra data,\n> which would be everything beyond the first 16 bits (as noted in a\n> comment near the beginning of cache.h).  But here you keep the first\n> 18 bits.  Is CE_UPDATE and CE_REMOVE just 0 in the cases you've viewed\n> so this works (but you really should use 0177777 or 0xFFFF), or am I\n> off in my guess of what you're trying to do and you do want to see\n> those two flags?\n\nYou're right, 0177777 is what I want. I'm focusing only on the\nfile permissions bits that are reported by ls-tree.\n\n> It also seems surprising to me that this constant isn't already\n> defined somewhere in cache.h or as some variant of S_IFMT, though I'm\n> not finding it.\n\nI'm not, either.\n\n>> +\n>> +       if (S_ISSPARSEDIR(ce->ce_mode))\n>> +               printf(\"tree \");\n>> +       else if (S_ISGITLINK(ce->ce_mode))\n>> +               printf(\"commit \");\n>> +       else\n>> +               printf(\"blob \");\n> \n> Perhaps make use of the tree_type, commit_type, and blob_type global constants?\n\nToday I Learned...\n\n>> +\n>> +       printf(\"%s\\t%s\\n\",\n>> +              oid_to_hex(&ce->oid),\n>> +              ce->name);\n>> +}\n>> +\n>> +static void print_cache(struct index_state *cache)\n>> +{\n>> +       int i;\n>> +       for (i = 0; i < the_index.cache_nr; i++)\n>> +               print_cache_entry(the_index.cache[i]);\n> \n> Why are you passing cache as a parameter, then ignoring it and using the_index?\n\nGood catch!\n\nThanks,\n-Stolee\n"},{"id":"418646","messageId":"CABPp-BG8Jox=tgVihxOvHXWC2t2wDvDr8BN2VLBmHrJ-r9B3Pg@mail.gmail.com","threadId":"55195","inReplyTo":"466cb063-0481-cfa6-cc3e-8ca26d73c8fb@gmail.com","subject":"Re: [PATCH 16/20] sparse-checkout: toggle sparse index from builtin","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-03-09T21:03:27Z","receivedAt":"2021-03-09T21:04:27Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Mar 9, 2021 at 12:52 PM Derrick Stolee <stolee@gmail.com> wrote:\n>\n> On 2/24/2021 2:11 PM, Martin Ågren wrote:\n> > On Wed, 24 Feb 2021 at 00:57, Derrick Stolee via GitGitGadget\n> > <gitgitgadget@gmail.com> wrote:\n> >> +that is not completely understood by other tools. Enabling sparse index\n> >> +enables the `extensions.spareseIndex` config value, which might cause\n> >\n> > s/sparese/sparse\n>\n> Thanks!\n>\n>\n> >> +other tools to stop working with your repository. If you have trouble with\n> >> +this compatibility, then run `git sparse-checkout sparse-index disable` to\n> >> +remove this config and rewrite your index to not be sparse.\n> >\n> > While I'm commenting on this..:\n> >\n> > There are several \"layers\" here, for lack of a better term. \"Enabling foo\n> > enables bar which may cause baz. If you fail due to baz, try dropping\n> > bar by dropping foo.\" If I remove any mention of the config variable from\n> > your text, I get the following.\n> >\n> >  Enabling sparse index might cause other tools to stop working with your\n> >  repository. If you have trouble with this compatibility, then run `git\n> >  sparse-checkout sparse-index disable` to rewrite your index to not be\n> >  sparse.\n> >\n> > I'm tempted to suggest such a rewrite to relieve readers of knowing of\n> > the middle step, which you could say is more of an implementation\n> > detail. But if we think that the symptoms / error messages might involve\n> > \"extensions.sparseIndex\" or, as would be the case with an older Git\n> > installation,\n> >\n> >   fatal: unknown repository extensions found:\n> >           sparseindex\n> >\n> > maybe there is some value in mentioning the config item by name. Just\n> > thinking out loud, really, and I don't have any strong opinion. I only\n> > came here to point out the typo in the docs.\n>\n> I agree that the layers are confusing. We could rearrange and have\n> a similar flow to what you recommend by mentioning the extension at\n> the end:\n>\n> **WARNING:** Using a sparse index requires modifying the index in a way\n> that is not completely understood by other tools. If you have trouble with\n> this compatibility, then run `git sparse-checkout sparse-index disable` to\n> rewrite your index to not be sparse. Older versions of Git will not\n> understand the `sparseIndex` repository extension and may fail to interact\n> with your repository until it is disabled.\n>\n> Thanks,\n> -Stolee\n\nThis looks pretty good to me, but could we change the first sentence\nto read \"...modifying the index in a way that may not yet be\nunderstood by external tools.\" ?  I'm worried \"other tools\" might make\npeople worry about different builtin commands (e.g. fast-export, log).\nI also prefer \"may\" and \"yet\" because I suspect most external tools\n(e.g. git filter-repo just to name a personal example) won't need to\nread an index format and will thus be unaffected, and any tools that\ndo read the index format will probably eventually learn how to work\nwith the new format.\n"},{"id":"418648","messageId":"ca080280-2f35-60fd-cafa-43b18d06cb1b@gmail.com","threadId":"55195","inReplyTo":"CABPp-BG8Jox=tgVihxOvHXWC2t2wDvDr8BN2VLBmHrJ-r9B3Pg@mail.gmail.com","subject":"Re: [PATCH 16/20] sparse-checkout: toggle sparse index from builtin","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-09T21:10:29Z","receivedAt":"2021-03-09T21:11:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/9/2021 4:03 PM, Elijah Newren wrote:\n> On Tue, Mar 9, 2021 at 12:52 PM Derrick Stolee <stolee@gmail.com> wrote:\n>>\n>> On 2/24/2021 2:11 PM, Martin Ågren wrote:\n>>> There are several \"layers\" here, for lack of a better term. \"Enabling foo\n>>> enables bar which may cause baz. If you fail due to baz, try dropping\n>>> bar by dropping foo.\" If I remove any mention of the config variable from\n>>> your text, I get the following.\n>>>\n>>>  Enabling sparse index might cause other tools to stop working with your\n>>>  repository. If you have trouble with this compatibility, then run `git\n>>>  sparse-checkout sparse-index disable` to rewrite your index to not be\n>>>  sparse.\n>>>\n>>> I'm tempted to suggest such a rewrite to relieve readers of knowing of\n>>> the middle step, which you could say is more of an implementation\n>>> detail. But if we think that the symptoms / error messages might involve\n>>> \"extensions.sparseIndex\" or, as would be the case with an older Git\n>>> installation,\n>>>\n>>>   fatal: unknown repository extensions found:\n>>>           sparseindex\n>>>\n>>> maybe there is some value in mentioning the config item by name. Just\n>>> thinking out loud, really, and I don't have any strong opinion. I only\n>>> came here to point out the typo in the docs.\n>>\n>> I agree that the layers are confusing. We could rearrange and have\n>> a similar flow to what you recommend by mentioning the extension at\n>> the end:\n>>\n>> **WARNING:** Using a sparse index requires modifying the index in a way\n>> that is not completely understood by other tools. If you have trouble with\n>> this compatibility, then run `git sparse-checkout sparse-index disable` to\n>> rewrite your index to not be sparse. Older versions of Git will not\n>> understand the `sparseIndex` repository extension and may fail to interact\n>> with your repository until it is disabled.\n>>\n>> Thanks,\n>> -Stolee\n> \n> This looks pretty good to me, but could we change the first sentence\n> to read \"...modifying the index in a way that may not yet be\n> understood by external tools.\" ?  I'm worried \"other tools\" might make\n> people worry about different builtin commands (e.g. fast-export, log).\n> I also prefer \"may\" and \"yet\" because I suspect most external tools\n> (e.g. git filter-repo just to name a personal example) won't need to\n> read an index format and will thus be unaffected, and any tools that\n> do read the index format will probably eventually learn how to work\n> with the new format.\n\nI can make the change, but I do want to point out that the current\nuse of a repository extension _does_ mean that tools that (correctly)\ninteract with a Git repository should fail even if they don't try to\naccess the index file. This is only something to make this work until\nwe introduce a new index file format version and then can drop the\nextension.\n\n\"git filter-repo\" _should_ be safe because it's really just shelling\nto Git, right? I'm more concerned about tools like libgit2.\n\nThanks,\n-Stolee\n"},{"id":"418649","messageId":"1a99d567-664d-6ce6-89cf-871b04c51674@gmail.com","threadId":"55195","inReplyTo":"CABPp-BE70+QVRm0z+Sfpt3V++O2VFkayHySTapFbJyD8VxKyBg@mail.gmail.com","subject":"Re: [PATCH 11/20] sparse-index: convert from full to sparse","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-09T21:13:48Z","receivedAt":"2021-03-09T21:14:43Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 2/25/2021 2:33 AM, Elijah Newren wrote:\n> On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n\n>> +                       /*\n>> +                        * allow terminating directory separators for\n>> +                        * sparse directory enries.\n> \n> enries -> entries\n\nThanks.\n\n>> +                        */\n>> +                       if (c == '\\0')\n>> +                               return S_ISDIR(mode);\n> \n> Yaay, much simpler (than the RFC version).\n\n>> +       /*\n>> +        * Is the current path outside of the sparse cone?\n>> +        * Then check if the region can be replaced by a sparse\n>> +        * directory entry (everything is sparse and merged).\n>> +        */\n>> +       match = path_matches_pattern_list(ct_path, ct_pathlen,\n>> +                                         NULL, &dtype, pl, istate);\n>> +       if (match != NOT_MATCHED)\n>> +               can_convert = 0;\n> \n> Not sure if you saw my comments on the flow control at\n> https://lore.kernel.org/git/CABPp-BE9wPwmC0=pA4p1_QSRDHrO8RzqfJQdE2NxYZsYL_Rcig@mail.gmail.com/\n> (the typos elsewhere seem to still be present).  If you saw it and\n> decided against it, that's fine, just wanted the idea to at least be\n> floated.\n\nSorry for dropping this one. I _did_ decide against it, and\nprimarily because the \"if (can_convert)\" condition contains\na return statement. I like to use 'gotos' for blocks that\nwill eventually be entered by all paths through the code,\nsuch as \"goto cleanup;\" but here I find the \"can_convert\"\ncheck to be clearer.\n\n>> +               /*\n>> +                * Detect if this is a normal entry oustide of any subtree\n> \n> s/oustide/outside/\n\nGot it.\n\n>> +test_expect_success 'sparse-index contents' '\n>> +       init_repos &&\n>> +\n>> +       test-tool -C sparse-index read-cache --table >cache &&\n>> +       for dir in folder1 folder2 x\n>> +       do\n>> +               TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n>> +               grep \"040000 tree $TREE $dir/\" cache \\\n>> +                       || return 1\n>> +       done &&\n> \n> Thanks for making the output look more like ls-tree output; it's\n> easier to parse that way, at least for me.\n\nExcellent.\n \n> I mostly read over the range-diff since it was much shorter.  You've\n> addressed a number of questions/comments I had on the RFC version, but\n> there's still some I didn't see a response to so I reposted them here.\n \nThanks for being diligent!\n-Stolee\n"},{"id":"418652","messageId":"4d5d12e9-cd14-c195-b267-0e130109f1e0@gmail.com","threadId":"55195","inReplyTo":"CABPp-BEMNavqAEG54VYkZvSiJ4=X3vZUXsubCmFfk3yxUWOjXQ@mail.gmail.com","subject":"Re: [PATCH 13/20] unpack-trees: allow sparse directories","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-09T21:35:32Z","receivedAt":"2021-03-09T21:36:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 2/25/2021 2:40 AM, Elijah Newren wrote:\n> On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Derrick Stolee <dstolee@microsoft.com>\n>>\n>> The index_pos_by_traverse_info() currently throws a BUG() when a\n>> directory entry exists exactly in the index. We need to consider that it\n>> is possible to have a directory in a sparse index as long as that entry\n>> is itself marked with the skip-worktree bit.\n>>\n>> The negation of the 'pos' variable must be conditioned to only when it\n>> starts as negative. This is identical behavior as before when the index\n>> is full.\n> \n> Same comment on the second paragraph as I made in the RFC series --\n> https://lore.kernel.org/git/CABPp-BGPJgA4guWHVm3AVS=hM0fTixUpRvJe5i9NnHT-3QJMfw@mail.gmail.com/.\n> I apologize if I'm repeating stuff you chose to not change, but I\n> didn't see a response and given the three typos left in previous\n> patches, I'm unsure whether it was unaddressed on purpose or on\n> accident.\n\nYes, I dropped this one. How about this?\n\n    The 'pos' variable is assigned a negative value if an exact match is not\n    found. Since a directory name can be an exact match, it is no longer an\n    error to have a nonnegative 'pos' value.\n\nThanks,\n-Stolee\n"},{"id":"418653","messageId":"CABPp-BHRNdgLYq4c+uc5wuiPU894xT4PxAJ8hM80zwmXGhOdww@mail.gmail.com","threadId":"55195","inReplyTo":"ca080280-2f35-60fd-cafa-43b18d06cb1b@gmail.com","subject":"Re: [PATCH 16/20] sparse-checkout: toggle sparse index from builtin","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-03-09T21:38:27Z","receivedAt":"2021-03-09T21:39:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Mar 9, 2021 at 1:10 PM Derrick Stolee <stolee@gmail.com> wrote:\n>\n> On 3/9/2021 4:03 PM, Elijah Newren wrote:\n> > On Tue, Mar 9, 2021 at 12:52 PM Derrick Stolee <stolee@gmail.com> wrote:\n> >>\n> >> On 2/24/2021 2:11 PM, Martin Ågren wrote:\n> >>> There are several \"layers\" here, for lack of a better term. \"Enabling foo\n> >>> enables bar which may cause baz. If you fail due to baz, try dropping\n> >>> bar by dropping foo.\" If I remove any mention of the config variable from\n> >>> your text, I get the following.\n> >>>\n> >>>  Enabling sparse index might cause other tools to stop working with your\n> >>>  repository. If you have trouble with this compatibility, then run `git\n> >>>  sparse-checkout sparse-index disable` to rewrite your index to not be\n> >>>  sparse.\n> >>>\n> >>> I'm tempted to suggest such a rewrite to relieve readers of knowing of\n> >>> the middle step, which you could say is more of an implementation\n> >>> detail. But if we think that the symptoms / error messages might involve\n> >>> \"extensions.sparseIndex\" or, as would be the case with an older Git\n> >>> installation,\n> >>>\n> >>>   fatal: unknown repository extensions found:\n> >>>           sparseindex\n> >>>\n> >>> maybe there is some value in mentioning the config item by name. Just\n> >>> thinking out loud, really, and I don't have any strong opinion. I only\n> >>> came here to point out the typo in the docs.\n> >>\n> >> I agree that the layers are confusing. We could rearrange and have\n> >> a similar flow to what you recommend by mentioning the extension at\n> >> the end:\n> >>\n> >> **WARNING:** Using a sparse index requires modifying the index in a way\n> >> that is not completely understood by other tools. If you have trouble with\n> >> this compatibility, then run `git sparse-checkout sparse-index disable` to\n> >> rewrite your index to not be sparse. Older versions of Git will not\n> >> understand the `sparseIndex` repository extension and may fail to interact\n> >> with your repository until it is disabled.\n> >>\n> >> Thanks,\n> >> -Stolee\n> >\n> > This looks pretty good to me, but could we change the first sentence\n> > to read \"...modifying the index in a way that may not yet be\n> > understood by external tools.\" ?  I'm worried \"other tools\" might make\n> > people worry about different builtin commands (e.g. fast-export, log).\n> > I also prefer \"may\" and \"yet\" because I suspect most external tools\n> > (e.g. git filter-repo just to name a personal example) won't need to\n> > read an index format and will thus be unaffected, and any tools that\n> > do read the index format will probably eventually learn how to work\n> > with the new format.\n>\n> I can make the change, but I do want to point out that the current\n> use of a repository extension _does_ mean that tools that (correctly)\n> interact with a Git repository should fail even if they don't try to\n> access the index file. This is only something to make this work until\n> we introduce a new index file format version and then can drop the\n> extension.\n\nGood point, though...\n\n> \"git filter-repo\" _should_ be safe because it's really just shelling\n> to Git, right? I'm more concerned about tools like libgit2.\n\nYes, libgit2 and jgit and similar tools are clearly going to be\naffected and deeply.  Those are of concern, but I suspect most users\nwhen they see \"external tools\" will be thinking of the large multitude\nof scripts out there that just shell out to git under the hood to\nprovide some higher level wrapper of some sort.  And anything that\noperates that way won't be affected directly by the repository\nextension.\n\nI'm not sure I'd even mark things that shell out to git as _should_ be\nsafe.  In general, scripts can make all kinds of assumptions on\ninterpreting output, and I suspect some of those may become\ninvalidated by this new feature.  We have a recent guidepost that's\nvery close to home on that too -- git stash had *3* different bugs in\nit once sparse-checkouts were introduced, based on the fact that it\nwas designed as a just-shell-out-to-low-level-git-commands script and\nit made assumptions on how those commands worked together.  See\nhttps://lore.kernel.org/git/ccfedc7140dbf63ba26a15f93bd3885180b26517.1606861519.git.gitgitgadget@gmail.com/.\nSure git-stash is a builtin (supposedly, anyway), but external tools\ncan make similar logical jumps.\n"},{"id":"418654","messageId":"CABPp-BFu2N33MDeHXx-JogjQwRpOSqrx41L5cB1OAyLBT8zm9w@mail.gmail.com","threadId":"55195","inReplyTo":"4d5d12e9-cd14-c195-b267-0e130109f1e0@gmail.com","subject":"Re: [PATCH 13/20] unpack-trees: allow sparse directories","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-03-09T21:39:46Z","receivedAt":"2021-03-09T21:40:47Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Mar 9, 2021 at 1:35 PM Derrick Stolee <stolee@gmail.com> wrote:\n>\n> On 2/25/2021 2:40 AM, Elijah Newren wrote:\n> > On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n> > <gitgitgadget@gmail.com> wrote:\n> >>\n> >> From: Derrick Stolee <dstolee@microsoft.com>\n> >>\n> >> The index_pos_by_traverse_info() currently throws a BUG() when a\n> >> directory entry exists exactly in the index. We need to consider that it\n> >> is possible to have a directory in a sparse index as long as that entry\n> >> is itself marked with the skip-worktree bit.\n> >>\n> >> The negation of the 'pos' variable must be conditioned to only when it\n> >> starts as negative. This is identical behavior as before when the index\n> >> is full.\n> >\n> > Same comment on the second paragraph as I made in the RFC series --\n> > https://lore.kernel.org/git/CABPp-BGPJgA4guWHVm3AVS=hM0fTixUpRvJe5i9NnHT-3QJMfw@mail.gmail.com/.\n> > I apologize if I'm repeating stuff you chose to not change, but I\n> > didn't see a response and given the three typos left in previous\n> > patches, I'm unsure whether it was unaddressed on purpose or on\n> > accident.\n>\n> Yes, I dropped this one. How about this?\n>\n>     The 'pos' variable is assigned a negative value if an exact match is not\n>     found. Since a directory name can be an exact match, it is no longer an\n>     error to have a nonnegative 'pos' value.\n\nI like it!\n"},{"id":"418655","messageId":"7a771c46-88ce-201d-3a01-25d73d3d99d3@gmail.com","threadId":"55195","inReplyTo":"CABPp-BFnOgadewk+fSGy1ObHuZQb_-E0roz7HPE_cRHr3aW1UQ@mail.gmail.com","subject":"Re: [PATCH 15/20] sparse-index: create extension for compatibility","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-09T21:45:22Z","receivedAt":"2021-03-09T21:46:09Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 2/25/2021 2:45 AM, Elijah Newren wrote:\n> On Tue, Feb 23, 2021 at 12:14 PM Derrick Stolee via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Derrick Stolee <dstolee@microsoft.com>\n>>\n>> Previously, we enabled the sparse index format only using\n>> GIT_TEST_SPARSE_INDEX=1. This is not a feasible direction for users to\n>> actually select this mode. Further, sparse directory entries are not\n>> understood by the index formats as advertised.\n>>\n>> We _could_ add a new index version that explicitly adds these\n>> capabilities, but there are nuances to index formats 2, 3, and 4 that\n>> are still valuable to select as options. For now, create a repo\n>> extension, \"extensions.sparseIndex\", that specifies that the tool\n>> reading this repository must understand sparse directory entries.\n> \n> This commit is unchanged from the RFC series, but given your comments\n> in the design document about how you do intend to create an index\n> format v5 now, do you want to reference that here?\n\nI'll insert detail about v5.\n \n>> +extensions.sparseIndex::\n>> +       When combined with `core.sparseCheckout=true` and\n>> +       `core.sparseCheckoutCone=true`, the index may contain entries\n>> +       corresponding to directories outside of the sparse-checkout\n>> +       definition. Versions of Git that do not understand this extension\n>> +       do not expect directory entries in the index.\n> \n> I had a wording suggestion for this paragraph in the RFC series --\n> https://lore.kernel.org/git/CABPp-BFEJE82k4VgkR=Jf7V7sZxZzo2pHMfAGshhi9_vV6iK0w@mail.gmail.com/.\n> Let me know if you just decided to leave it out so I don't bug you\n> about stuff you already considered.\n\nI'll take your suggestion, thanks.\n\n-Stolee\n"},{"id":"418697","messageId":"3f6d26ad-fb51-b1ca-2dd9-5e1a47c7c430@gmail.com","threadId":"55195","inReplyTo":"48e9c3d6-407a-1843-2d91-22112410e3f8@gmail.com","subject":"Re: [PATCH 17/20] sparse-checkout: disable sparse-index","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-10T18:20:29Z","receivedAt":"2021-03-10T18:21:19Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/9/2021 3:20 PM, Derrick Stolee wrote:\n> On 2/27/2021 7:32 AM, SZEDER Gábor wrote:\n>> On Tue, Feb 23, 2021 at 08:14:26PM +0000, Derrick Stolee via GitGitGadget wrote:\n>>> +test_expect_success 'sparse-index enabled and disabled' '\n>>> +\tgit -C repo sparse-checkout init --cone --sparse-index &&\n>>> +\ttest_cmp_config -C repo true extensions.sparseIndex &&\n>>> +\ttest-tool -C repo read-cache --table >cache &&\n>>> +\tgrep \" tree \" cache &&\n>>> +\n>>> +\tgit -C repo sparse-checkout disable &&\n>>> +\ttest-tool -C repo read-cache --table >cache &&\n>>> +\t! grep \" tree \" cache &&\n>>> +\tgit -C repo config --list >config &&\n>>> +\t! grep extensions.sparseindex config\n>>> +'\n>>\n>> This test passes with GIT_TEST_SPLIT_INDEX=1 at the moment, because,\n>> unfortunately, GIT_TEST_SPLIT_INDEX has been broken for the past two\n>> years.  However, if I run it with my WIP fixes for that issue [1],\n>> then it will fail:\n>>\n>>   +git -C repo sparse-checkout init --cone --sparse-index\n>>   +test_cmp_config -C repo true extensions.sparseIndex\n>>   +test-tool -C repo read-cache --table\n>>   +grep  tree  cache\n>>   error: last command exited with $?=1\n>>   not ok 16 - sparse-index enabled and disabled\n>>\n>> https://travis-ci.com/github/szeder/git-cooking-topics-for-travis-ci/jobs/486702444#L2594\n>>\n>> [1] Try to run it with:\n>>\n>>       https://github.com/szeder/git split-index-fixes\n>>\n>>     The code is, I believe, close to final, the commit messages,\n>>     however, are far from being finished.\n> \n> I'll keep that in mind. I should have added a variable\n> that disables GIT_TEST_SPLIT_INDEX for this test script,\n> since the sparse-index is (currently) incompatible with\n> the split-index. I bet that the test is failing because\n> it isn't actually writing the sparse-directory entry due\n> to that short-circuit check.\n\nThe next version will include GIT_TEST_SPLIT_INDEX=0 at\nthe start and that will make it work with your branch.\n\nThanks,\n-Stolee\n"},{"id":"418704","messageId":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.git.1614111270.gitgitgadget@gmail.com","subject":"[PATCH v2 00/20] Sparse Index: Design, Format, Tests","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:30:43Z","receivedAt":"2021-03-10T19:31:45Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"Here is the first full patch series submission coming out of the\nsparse-index RFC [1].\n\n[1]\nhttps://lore.kernel.org/git/pull.847.git.1611596533.gitgitgadget@gmail.com/\n\nI won't waste too much space here, because PATCH 1 includes a sizeable\ndesign document that describes the feature, the reasoning behind it, and my\nplan for getting this implemented widely throughout the codebase.\n\nThere are some new things here that were not in the RFC:\n\n * Design doc and format updates. (Patch 1)\n * Performance test script. (Patches 2 and 20)\n\nNotably missing in this series from the RFC:\n\n * The mega-patch inserting ensure_full_index() throughout the codebase.\n   That will be a follow-up series to this one.\n * The integrations with git status and git add to demonstrate the improved\n   performance. Those will also appear in their own series later.\n\nI plan to keep my latest work in this area in my 'sparse-index/wip' branch\n[2]. It includes all of the work from the RFC right now, updated with the\nwork from this series.\n\n[2] https://github.com/derrickstolee/git/tree/sparse-index/wip\n\n\nUpdates in V2\n=============\n\n * Various typos and awkward grammar is fixed.\n * Cleaned up unnecessary commands in p2000-sparse-operations.sh\n * Added a comment to the sparse_index member of struct index_state.\n * Used tree_type, commit_type, and blob_type in test-read-cache.c.\n\nThanks, -Stolee\n\nDerrick Stolee (20):\n  sparse-index: design doc and format update\n  t/perf: add performance test for sparse operations\n  t1092: clean up script quoting\n  sparse-index: add guard to ensure full index\n  sparse-index: implement ensure_full_index()\n  t1092: compare sparse-checkout to sparse-index\n  test-read-cache: print cache entries with --table\n  test-tool: don't force full index\n  unpack-trees: ensure full index\n  sparse-checkout: hold pattern list in index\n  sparse-index: convert from full to sparse\n  submodule: sparse-index should not collapse links\n  unpack-trees: allow sparse directories\n  sparse-index: check index conversion happens\n  sparse-index: create extension for compatibility\n  sparse-checkout: toggle sparse index from builtin\n  sparse-checkout: disable sparse-index\n  cache-tree: integrate with sparse directory entries\n  sparse-index: loose integration with cache_tree_verify()\n  p2000: add sparse-index repos\n\n Documentation/config/extensions.txt      |   8 +\n Documentation/git-sparse-checkout.txt    |  14 ++\n Documentation/technical/index-format.txt |   7 +\n Documentation/technical/sparse-index.txt | 173 ++++++++++++++\n Makefile                                 |   1 +\n builtin/sparse-checkout.c                |  44 +++-\n cache-tree.c                             |  40 ++++\n cache.h                                  |  18 +-\n read-cache.c                             |  35 ++-\n repo-settings.c                          |  15 ++\n repository.c                             |  11 +-\n repository.h                             |   3 +\n setup.c                                  |   3 +\n sparse-index.c                           | 290 +++++++++++++++++++++++\n sparse-index.h                           |  11 +\n t/README                                 |   3 +\n t/helper/test-read-cache.c               |  66 +++++-\n t/perf/p2000-sparse-operations.sh        | 102 ++++++++\n t/t1091-sparse-checkout-builtin.sh       |  13 +\n t/t1092-sparse-checkout-compatibility.sh | 136 +++++++++--\n unpack-trees.c                           |  16 +-\n 21 files changed, 969 insertions(+), 40 deletions(-)\n create mode 100644 Documentation/technical/sparse-index.txt\n create mode 100644 sparse-index.c\n create mode 100644 sparse-index.h\n create mode 100755 t/perf/p2000-sparse-operations.sh\n\n\nbase-commit: 966e671106b2fd38301e7c344c754fd118d0bb07\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-883%2Fderrickstolee%2Fsparse-index%2Fformat-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-883/derrickstolee/sparse-index/format-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/883\n\nRange-diff vs v1:\n\n  1:  daa9a6bcefbc !  1:  2fe413fdac80 sparse-index: design doc and format update\n     @@ Documentation/technical/sparse-index.txt (new)\n      +If we need to discover the details for paths within that directory, we\n      +can parse trees to find that list.\n      +\n     -+This addition of sparse-directory entries violates expectations about the\n     ++At time of writing, sparse-directory entries violate expectations about the\n      +index format and its in-memory data structure. There are many consumers in\n      +the codebase that expect to iterate through all of the index entries and\n      +see only files. In addition, they expect to see all files at `HEAD`. One\n     @@ Documentation/technical/sparse-index.txt (new)\n      +* `git merge`\n      +* `git rebase`\n      +\n     ++Hopefully, commands such as `git merge` and `git rebase` can benefit\n     ++instead from merge algorithms that do not use the index as a data\n     ++structure, such as the merge-ORT strategy. As these topics mature, we\n     ++may enalbe the ORT strategy by default for repositories using the\n     ++sparse-index feature.\n     ++\n      +Along with `git status` and `git add`, these commands cover the majority\n      +of users' interactions with the working directory. In addition, we can\n      +integrate with these commands:\n  2:  a8c6322a3dbe !  2:  540ab5495065 t/perf: add performance test for sparse operations\n     @@ t/perf/p2000-sparse-operations.sh (new)\n      +\t# Remove submodules from the example repo, because our\n      +\t# duplication of the entire repo creates an unlikly data shape.\n      +\tgit config --file .gitmodules --get-regexp \"submodule.*.path\" >modules &&\n     -+\trm -f .gitmodules &&\n     -+\tgit add .gitmodules &&\n     ++\tgit rm -f .gitmodules &&\n      +\tfor module in $(awk \"{print \\$2}\" modules)\n      +\tdo\n      +\t\tgit rm $module || return 1\n      +\tdone &&\n     -+\tgit add . &&\n      +\tgit commit -m \"remove submodules\" &&\n      +\n      +\techo bogus >a &&\n  3:  6e783c88821e =  3:  5cbedb377b37 t1092: clean up script quoting\n  4:  01da4c48a1fa =  4:  6e21f776e883 sparse-index: add guard to ensure full index\n  5:  2b83989fbcd3 !  5:  399ddb0bad56 sparse-index: implement ensure_full_index()\n     @@ cache.h: struct index_state {\n       \t\t updated_skipworktree : 1,\n      -\t\t fsmonitor_has_run_once : 1;\n      +\t\t fsmonitor_has_run_once : 1,\n     ++\n     ++\t\t /*\n     ++\t\t  * sparse_index == 1 when sparse-directory\n     ++\t\t  * entries exist. Requires sparse-checkout\n     ++\t\t  * in cone mode.\n     ++\t\t  */\n      +\t\t sparse_index : 1;\n       \tstruct hashmap name_hash;\n       \tstruct hashmap dir_hash;\n  6:  c9910a37579c =  6:  eac2db5efc22 t1092: compare sparse-checkout to sparse-index\n  7:  3d92df7a0cf9 !  7:  e9c82d2eda82 test-read-cache: print cache entries with --table\n     @@ Commit message\n      \n       ## t/helper/test-read-cache.c ##\n      @@\n     + #include \"test-tool.h\"\n       #include \"cache.h\"\n       #include \"config.h\"\n     - \n     ++#include \"blob.h\"\n     ++#include \"commit.h\"\n     ++#include \"tree.h\"\n     ++\n      +static void print_cache_entry(struct cache_entry *ce)\n      +{\n     -+\tprintf(\"%06o \", ce->ce_mode & 0777777);\n     ++\tconst char *type;\n     ++\tprintf(\"%06o \", ce->ce_mode & 0177777);\n      +\n      +\tif (S_ISSPARSEDIR(ce->ce_mode))\n     -+\t\tprintf(\"tree \");\n     ++\t\ttype = tree_type;\n      +\telse if (S_ISGITLINK(ce->ce_mode))\n     -+\t\tprintf(\"commit \");\n     ++\t\ttype = commit_type;\n      +\telse\n     -+\t\tprintf(\"blob \");\n     ++\t\ttype = blob_type;\n      +\n     -+\tprintf(\"%s\\t%s\\n\",\n     ++\tprintf(\"%s %s\\t%s\\n\",\n     ++\t       type,\n      +\t       oid_to_hex(&ce->oid),\n      +\t       ce->name);\n      +}\n      +\n     -+static void print_cache(struct index_state *cache)\n     ++static void print_cache(struct index_state *istate)\n      +{\n      +\tint i;\n     -+\tfor (i = 0; i < the_index.cache_nr; i++)\n     -+\t\tprint_cache_entry(the_index.cache[i]);\n     ++\tfor (i = 0; i < istate->cache_nr; i++)\n     ++\t\tprint_cache_entry(istate->cache[i]);\n      +}\n     -+\n     + \n       int cmd__read_cache(int argc, const char **argv)\n       {\n      +\tstruct repository *r = the_repository;\n  8:  94373e2bfbbc !  8:  243541fc5820 test-tool: don't force full index\n     @@ Commit message\n      \n       ## t/helper/test-read-cache.c ##\n      @@\n     - #include \"test-tool.h\"\n     - #include \"cache.h\"\n     - #include \"config.h\"\n     + #include \"blob.h\"\n     + #include \"commit.h\"\n     + #include \"tree.h\"\n      +#include \"sparse-index.h\"\n       \n       static void print_cache_entry(struct cache_entry *ce)\n  9:  e71f033c2871 =  9:  48f65093b3da unpack-trees: ensure full index\n 10:  f86d3dc154d1 ! 10:  83aac8b7a1ec sparse-checkout: hold pattern list in index\n     @@ Commit message\n          pattern set, we need access to that in-memory copy. Place a pointer to\n          a 'struct pattern_list' in the index so we can access this on-demand.\n          This will be used in the next change which uses the sparse-checkout\n     -    definition to filter out directories that are outsie the sparse cone.\n     +    definition to filter out directories that are outside the sparse cone.\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n      \n 11:  a2d77c23a0cb ! 11:  f6db0c27a285 sparse-index: convert from full to sparse\n     @@ read-cache.c: int verify_path(const char *path, unsigned mode)\n       \t\t\t\treturn 0;\n      +\t\t\t/*\n      +\t\t\t * allow terminating directory separators for\n     -+\t\t\t * sparse directory enries.\n     ++\t\t\t * sparse directory entries.\n      +\t\t\t */\n      +\t\t\tif (c == '\\0')\n      +\t\t\t\treturn S_ISDIR(mode);\n     @@ sparse-index.c\n      +\t\tstruct cache_entry *ce = istate->cache[i];\n      +\n      +\t\t/*\n     -+\t\t * Detect if this is a normal entry oustide of any subtree\n     ++\t\t * Detect if this is a normal entry outside of any subtree\n      +\t\t * entry.\n      +\t\t */\n      +\t\tbase = ce->name + ct_pathlen;\n 12:  4405a9115c3b = 12:  f2a3e7298798 submodule: sparse-index should not collapse links\n 13:  fda23f07e6a2 ! 13:  6f1ebe6ccc08 unpack-trees: allow sparse directories\n     @@ Commit message\n          is possible to have a directory in a sparse index as long as that entry\n          is itself marked with the skip-worktree bit.\n      \n     -    The negation of the 'pos' variable must be conditioned to only when it\n     -    starts as negative. This is identical behavior as before when the index\n     -    is full.\n     +    The 'pos' variable is assigned a negative value if an exact match is not\n     +    found. Since a directory name can be an exact match, it is no longer an\n     +    error to have a nonnegative 'pos' value.\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n      \n 14:  7d4627574bb8 = 14:  3fa684b315fb sparse-index: check index conversion happens\n 15:  564503f78784 ! 15:  d74576d677f6 sparse-index: create extension for compatibility\n     @@ Commit message\n      \n          We _could_ add a new index version that explicitly adds these\n          capabilities, but there are nuances to index formats 2, 3, and 4 that\n     -    are still valuable to select as options. For now, create a repo\n     -    extension, \"extensions.sparseIndex\", that specifies that the tool\n     -    reading this repository must understand sparse directory entries.\n     +    are still valuable to select as options. Until we add index format\n     +    version 5, create a repo extension, \"extensions.sparseIndex\", that\n     +    specifies that the tool reading this repository must understand sparse\n     +    directory entries.\n      \n          This change only encodes the extension and enables it when\n          GIT_TEST_SPARSE_INDEX=1. Later, we will add a more user-friendly CLI\n     @@ Documentation/config/extensions.txt: extensions.objectFormat::\n      +\tWhen combined with `core.sparseCheckout=true` and\n      +\t`core.sparseCheckoutCone=true`, the index may contain entries\n      +\tcorresponding to directories outside of the sparse-checkout\n     -+\tdefinition. Versions of Git that do not understand this extension\n     -+\tdo not expect directory entries in the index.\n     ++\tdefinition in lieu of containing each path under such directories.\n     ++\tVersions of Git that do not understand this extension do not\n     ++\texpect directory entries in the index.\n      \n       ## cache.h ##\n      @@ cache.h: struct repository_format {\n 16:  6d6b230e3318 ! 16:  e530ca5f668d sparse-checkout: toggle sparse index from builtin\n     @@ Documentation/git-sparse-checkout.txt: To avoid interfering with other worktrees\n      +a sparse index until they are properly integrated with the feature.\n      ++\n      +**WARNING:** Using a sparse index requires modifying the index in a way\n     -+that is not completely understood by other tools. Enabling sparse index\n     -+enables the `extensions.spareseIndex` config value, which might cause\n     -+other tools to stop working with your repository. If you have trouble with\n     -+this compatibility, then run `git sparse-checkout sparse-index disable` to\n     -+remove this config and rewrite your index to not be sparse.\n     ++that is not completely understood by external tools. If you have trouble\n     ++with this compatibility, then run `git sparse-checkout sparse-index disable`\n     ++to rewrite your index to not be sparse. Older versions of Git will not\n     ++understand the `sparseIndex` repository extension and may fail to interact\n     ++with your repository until it is disabled.\n       \n       'set'::\n       \tWrite a set of patterns to the sparse-checkout file, as given as\n 17:  bcf960ef2362 = 17:  42d0da9c5def sparse-checkout: disable sparse-index\n 18:  e6afec58674e = 18:  6bb0976a6295 cache-tree: integrate with sparse directory entries\n 19:  2be4981fe698 = 19:  07f34e80609a sparse-index: loose integration with cache_tree_verify()\n 20:  a738b0ba8ab4 = 20:  41e3b56b9c17 p2000: add sparse-index repos\n\n-- \ngitgitgadget\n"},{"id":"418705","messageId":"2fe413fdac808807e2058caeee6ce86c54a678c0.1615404664.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 01/20] sparse-index: design doc and format update","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:30:44Z","receivedAt":"2021-03-10T19:31:46Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThis begins a long effort to update the index format to allow sparse\ndirectory entries. This should result in a significant improvement to\nGit commands when HEAD contains millions of files, but the user has\nselected many fewer files to keep in their sparse-checkout definition.\n\nCurrently, the index format is only updated in the presence of\nextensions.sparseIndex instead of increasing a file format version\nnumber. This is temporary, and index v5 is part of the plan for future\nwork in this area.\n\nThe design document details many of the reasons for embarking on this\nwork, and also the plan for completing it safely.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/technical/index-format.txt |   7 +\n Documentation/technical/sparse-index.txt | 173 +++++++++++++++++++++++\n 2 files changed, 180 insertions(+)\n create mode 100644 Documentation/technical/sparse-index.txt\n\ndiff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt\nindex b633482b1bdf..387126582556 100644\n--- a/Documentation/technical/index-format.txt\n+++ b/Documentation/technical/index-format.txt\n@@ -44,6 +44,13 @@ Git index format\n   localization, no special casing of directory separator '/'). Entries\n   with the same name are sorted by their stage field.\n \n+  An index entry typically represents a file. However, if sparse-checkout\n+  is enabled in cone mode (`core.sparseCheckoutCone` is enabled) and the\n+  `extensions.sparseIndex` extension is enabled, then the index may\n+  contain entries for directories outside of the sparse-checkout definition.\n+  These entries have mode `0040000`, include the `SKIP_WORKTREE` bit, and\n+  the path ends in a directory separator.\n+\n   32-bit ctime seconds, the last time a file's metadata changed\n     this is stat(2) data\n \ndiff --git a/Documentation/technical/sparse-index.txt b/Documentation/technical/sparse-index.txt\nnew file mode 100644\nindex 000000000000..787a2a0b3b81\n--- /dev/null\n+++ b/Documentation/technical/sparse-index.txt\n@@ -0,0 +1,173 @@\n+Git Sparse-Index Design Document\n+================================\n+\n+The sparse-checkout feature allows users to focus a working directory on\n+a subset of the files at HEAD. The cone mode patterns, enabled by\n+`core.sparseCheckoutCone`, allow for very fast pattern matching to\n+discover which files at HEAD belong in the sparse-checkout cone.\n+\n+Three important scale dimensions for a Git worktree are:\n+\n+* `HEAD`: How many files are present at `HEAD`?\n+\n+* Populated: How many files are within the sparse-checkout cone.\n+\n+* Modified: How many files has the user modified in the working directory?\n+\n+We will use big-O notation -- O(X) -- to denote how expensive certain\n+operations are in terms of these dimensions.\n+\n+These dimensions are ordered by their magnitude: users (typically) modify\n+fewer files than are populated, and we can only populate files at `HEAD`.\n+These dimensions are also ordered by how expensive they are per item: it\n+is expensive to detect a modified file than it is to write one that we\n+know must be populated; changing `HEAD` only really requires updating the\n+index.\n+\n+Problems occur if there is an extreme imbalance in these dimensions. For\n+example, if `HEAD` contains millions of paths but the populated set has\n+only tens of thousands, then commands like `git status` and `git add` can\n+be dominated by operations that require O(`HEAD`) operations instead of\n+O(Populated). Primarily, the cost is in parsing and rewriting the index,\n+which is filled primarily with files at `HEAD` that are marked with the\n+`SKIP_WORKTREE` bit.\n+\n+The sparse-index intends to take these commands that read and modify the\n+index from O(`HEAD`) to O(Populated). To do this, we need to modify the\n+index format in a significant way: add \"sparse directory\" entries.\n+\n+With cone mode patterns, it is possible to detect when an entire\n+directory will have its contents outside of the sparse-checkout definition.\n+Instead of listing all of the files it contains as individual entries, a\n+sparse-index contains an entry with the directory name, referencing the\n+object ID of the tree at `HEAD` and marked with the `SKIP_WORKTREE` bit.\n+If we need to discover the details for paths within that directory, we\n+can parse trees to find that list.\n+\n+At time of writing, sparse-directory entries violate expectations about the\n+index format and its in-memory data structure. There are many consumers in\n+the codebase that expect to iterate through all of the index entries and\n+see only files. In addition, they expect to see all files at `HEAD`. One\n+way to handle this is to parse trees to replace a sparse-directory entry\n+with all of the files within that tree as the index is loaded. However,\n+parsing trees is slower than parsing the index format, so that is a slower\n+operation than if we left the index alone.\n+\n+The implementation plan below follows four phases to slowly integrate with\n+the sparse-index. The intention is to incrementally update Git commands to\n+interact safely with the sparse-index without significant slowdowns. This\n+may not always be possible, but the hope is that the primary commands that\n+users need in their daily work are dramatically improved.\n+\n+Phase I: Format and initial speedups\n+------------------------------------\n+\n+During this phase, Git learns to enable the sparse-index and safely parse\n+one. Protections are put in place so that every consumer of the in-memory\n+data structure can operate with its current assumption of every file at\n+`HEAD`.\n+\n+At first, every index parse will expand the sparse-directory entries into\n+the full list of paths at `HEAD`. This will be slower in all cases. The\n+only noticable change in behavior will be that the serialized index file\n+contains sparse-directory entries.\n+\n+To start, we use a new repository extension, `extensions.sparseIndex`, to\n+allow inserting sparse-directory entries into indexes with file format\n+versions 2, 3, and 4. This prevents Git versions that do not understand\n+the sparse-index from operating on one, but it also prevents other\n+operations that do not use the index at all. A new format, index v5, will\n+be introduced that includes sparse-directory entries by default. It might\n+also introduce other features that have been considered for improving the\n+index, as well.\n+\n+Next, consumers of the index will be guarded against operating on a\n+sparse-index by inserting calls to `ensure_full_index()` or\n+`expand_index_to_path()`. After these guards are in place, we can begin\n+leaving sparse-directory entries in the in-memory index structure.\n+\n+Even after inserting these guards, we will keep expanding sparse-indexes\n+for most Git commands using the `command_requires_full_index` repository\n+setting. This setting will be on by default and disabled one builtin at a\n+time until we have sufficient confidence that all of the index operations\n+are properly guarded.\n+\n+To complete this phase, the commands `git status` and `git add` will be\n+integrated with the sparse-index so that they operate with O(Populated)\n+performance. They will be carefully tested for operations within and\n+outside the sparse-checkout definition.\n+\n+Phase II: Careful integrations\n+------------------------------\n+\n+This phase focuses on ensuring that all index extensions and APIs work\n+well with a sparse-index. This requires significant increases to our test\n+coverage, especially for operations that interact with the working\n+directory outside of the sparse-checkout definition. Some of these\n+behaviors may not be the desirable ones, such as some tests already\n+marked for failure in `t1092-sparse-checkout-compatibility.sh`.\n+\n+The index extensions that may require special integrations are:\n+\n+* FS Monitor\n+* Untracked cache\n+\n+While integrating with these features, we should look for patterns that\n+might lead to better APIs for interacting with the index. Coalescing\n+common usage patterns into an API call can reduce the number of places\n+where sparse-directories need to be handled carefully.\n+\n+Phase III: Important command speedups\n+-------------------------------------\n+\n+At this point, the patterns for testing and implementing sparse-directory\n+logic should be relatively stable. This phase focuses on updating some of\n+the most common builtins that use the index to operate as O(Populated).\n+Here is a potential list of commands that could be valuable to integrate\n+at this point:\n+\n+* `git commit`\n+* `git checkout`\n+* `git merge`\n+* `git rebase`\n+\n+Hopefully, commands such as `git merge` and `git rebase` can benefit\n+instead from merge algorithms that do not use the index as a data\n+structure, such as the merge-ORT strategy. As these topics mature, we\n+may enalbe the ORT strategy by default for repositories using the\n+sparse-index feature.\n+\n+Along with `git status` and `git add`, these commands cover the majority\n+of users' interactions with the working directory. In addition, we can\n+integrate with these commands:\n+\n+* `git grep`\n+* `git rm`\n+\n+These have been proposed as some whose behavior could change when in a\n+repo with a sparse-checkout definition. It would be good to include this\n+behavior automatically when using a sparse-index. Some clarity is needed\n+to make the behavior switch clear to the user.\n+\n+This phase is the first where parallel work might be possible without too\n+much conflicts between topics.\n+\n+Phase IV: The long tail\n+-----------------------\n+\n+This last phase is less a \"phase\" and more \"the new normal\" after all of\n+the previous work.\n+\n+To start, the `command_requires_full_index` option could be removed in\n+favor of expanding only when hitting an API guard.\n+\n+There are many Git commands that could use special attention to operate as\n+O(Populated), while some might be so rare that it is acceptable to leave\n+them with additional overhead when a sparse-index is present.\n+\n+Here are some commands that might be useful to update:\n+\n+* `git sparse-checkout set`\n+* `git am`\n+* `git clean`\n+* `git stash`\n-- \ngitgitgadget\n\n"},{"id":"418706","messageId":"540ab5495065805fbac5b5f782937e29fe4a4398.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 02/20] t/perf: add performance test for sparse operations","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:30:45Z","receivedAt":"2021-03-10T19:31:46Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nCreate a test script that takes the default performance test (the Git\ncodebase) and multiplies it by 256 using four layers of duplicated\ntrees of width four. This results in nearly one million blob entries in\nthe index. Then, we can clone this repository with sparse-checkout\npatterns that demonstrate four copies of the initial repository. Each\nclone will use a different index format or mode so peformance can be\ntested across the different options.\n\nNote that the initial repo is stripped of submodules before doing the\ncopies. This preserves the expected data shape of the sparse index,\nbecause directories containing submodules are not collapsed to a sparse\ndirectory entry.\n\nRun a few Git commands on these clones, especially those that use the\nindex (status, add, commit).\n\nHere are the results on my Linux machine:\n\nTest\n--------------------------------------------------------------\n2000.2: git status (full-index-v3)             0.37(0.30+0.09)\n2000.3: git status (full-index-v4)             0.39(0.32+0.10)\n2000.4: git add -A (full-index-v3)             1.42(1.06+0.20)\n2000.5: git add -A (full-index-v4)             1.26(0.98+0.16)\n2000.6: git add . (full-index-v3)              1.40(1.04+0.18)\n2000.7: git add . (full-index-v4)              1.26(0.98+0.17)\n2000.8: git commit -a -m A (full-index-v3)     1.42(1.11+0.16)\n2000.9: git commit -a -m A (full-index-v4)     1.33(1.08+0.16)\n\nIt is perhaps noteworthy that there is an improvement when using index\nversion 4. This is because the v3 index uses 108 MiB while the v4\nindex uses 80 MiB. Since the repeated portions of the directories are\nvery short (f3/f1/f2, for example) this ratio is less pronounced than in\nsimilarly-sized real repositories.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/perf/p2000-sparse-operations.sh | 85 +++++++++++++++++++++++++++++++\n 1 file changed, 85 insertions(+)\n create mode 100755 t/perf/p2000-sparse-operations.sh\n\ndiff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh\nnew file mode 100755\nindex 000000000000..2fbc81b22119\n--- /dev/null\n+++ b/t/perf/p2000-sparse-operations.sh\n@@ -0,0 +1,85 @@\n+#!/bin/sh\n+\n+test_description=\"test performance of Git operations using the index\"\n+\n+. ./perf-lib.sh\n+\n+test_perf_default_repo\n+\n+SPARSE_CONE=f2/f4/f1\n+\n+test_expect_success 'setup repo and indexes' '\n+\tgit reset --hard HEAD &&\n+\t# Remove submodules from the example repo, because our\n+\t# duplication of the entire repo creates an unlikly data shape.\n+\tgit config --file .gitmodules --get-regexp \"submodule.*.path\" >modules &&\n+\tgit rm -f .gitmodules &&\n+\tfor module in $(awk \"{print \\$2}\" modules)\n+\tdo\n+\t\tgit rm $module || return 1\n+\tdone &&\n+\tgit commit -m \"remove submodules\" &&\n+\n+\techo bogus >a &&\n+\tcp a b &&\n+\tgit add a b &&\n+\tgit commit -m \"level 0\" &&\n+\tBLOB=$(git rev-parse HEAD:a) &&\n+\tOLD_COMMIT=$(git rev-parse HEAD) &&\n+\tOLD_TREE=$(git rev-parse HEAD^{tree}) &&\n+\n+\tfor i in $(test_seq 1 4)\n+\tdo\n+\t\tcat >in <<-EOF &&\n+\t\t\t100755 blob $BLOB\ta\n+\t\t\t040000 tree $OLD_TREE\tf1\n+\t\t\t040000 tree $OLD_TREE\tf2\n+\t\t\t040000 tree $OLD_TREE\tf3\n+\t\t\t040000 tree $OLD_TREE\tf4\n+\t\tEOF\n+\t\tNEW_TREE=$(git mktree <in) &&\n+\t\tNEW_COMMIT=$(git commit-tree $NEW_TREE -p $OLD_COMMIT -m \"level $i\") &&\n+\t\tOLD_TREE=$NEW_TREE &&\n+\t\tOLD_COMMIT=$NEW_COMMIT || return 1\n+\tdone &&\n+\n+\tgit sparse-checkout init --cone &&\n+\tgit branch -f wide $OLD_COMMIT &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v3 &&\n+\t(\n+\t\tcd full-index-v3 &&\n+\t\tgit sparse-checkout init --cone &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 3 &&\n+\t\tgit update-index --index-version=3\n+\t) &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v4 &&\n+\t(\n+\t\tcd full-index-v4 &&\n+\t\tgit sparse-checkout init --cone &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 4 &&\n+\t\tgit update-index --index-version=4\n+\t)\n+'\n+\n+test_perf_on_all () {\n+\tcommand=\"$@\"\n+\tfor repo in full-index-v3 full-index-v4\n+\tdo\n+\t\ttest_perf \"$command ($repo)\" \"\n+\t\t\t(\n+\t\t\t\tcd $repo &&\n+\t\t\t\techo >>$SPARSE_CONE/a &&\n+\t\t\t\t$command\n+\t\t\t)\n+\t\t\"\n+\tdone\n+}\n+\n+test_perf_on_all git status\n+test_perf_on_all git add -A\n+test_perf_on_all git add .\n+test_perf_on_all git commit -a -m A\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"418707","messageId":"399ddb0bad56c69ff9d9591f5e8eacf52cf50a15.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 05/20] sparse-index: implement ensure_full_index()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:30:48Z","receivedAt":"2021-03-10T19:31:46Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWe will mark an in-memory index_state as having sparse directory entries\nwith the sparse_index bit. These currently cannot exist, but we will add\na mechanism for collapsing a full index to a sparse one in a later\nchange. That will happen at write time, so we must first allow parsing\nthe format before writing it.\n\nCommands or methods that require a full index in order to operate can\ncall ensure_full_index() to expand that index in-memory. This requires\nparsing trees using that index's repository.\n\nSparse directory entries have a specific 'ce_mode' value. The macro\nS_ISSPARSEDIR(ce->ce_mode) can check if a cache_entry 'ce' has this type.\nThis ce_mode is not possible with the existing index formats, so we don't\nalso verify all properties of a sparse-directory entry, which are:\n\n 1. ce->ce_mode == 0040000\n 2. ce->flags & CE_SKIP_WORKTREE is true\n 3. ce->name[ce->namelen - 1] == '/' (ends in dir separator)\n 4. ce->oid references a tree object.\n\nThese are all semi-enforced in ensure_full_index() to some extent. Any\ndeviation will cause a warning at minimum or a failure in the worst\ncase.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache.h        | 13 ++++++-\n read-cache.c   |  9 +++++\n sparse-index.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++++-\n 3 files changed, 115 insertions(+), 2 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex d92814961405..1f0b42264606 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -204,6 +204,8 @@ struct cache_entry {\n #error \"CE_EXTENDED_FLAGS out of range\"\n #endif\n \n+#define S_ISSPARSEDIR(m) ((m) == S_IFDIR)\n+\n /* Forward structure decls */\n struct pathspec;\n struct child_process;\n@@ -319,7 +321,14 @@ struct index_state {\n \t\t drop_cache_tree : 1,\n \t\t updated_workdir : 1,\n \t\t updated_skipworktree : 1,\n-\t\t fsmonitor_has_run_once : 1;\n+\t\t fsmonitor_has_run_once : 1,\n+\n+\t\t /*\n+\t\t  * sparse_index == 1 when sparse-directory\n+\t\t  * entries exist. Requires sparse-checkout\n+\t\t  * in cone mode.\n+\t\t  */\n+\t\t sparse_index : 1;\n \tstruct hashmap name_hash;\n \tstruct hashmap dir_hash;\n \tstruct object_id oid;\n@@ -722,6 +731,8 @@ int read_index_from(struct index_state *, const char *path,\n \t\t    const char *gitdir);\n int is_index_unborn(struct index_state *);\n \n+void ensure_full_index(struct index_state *istate);\n+\n /* For use with `write_locked_index()`. */\n #define COMMIT_LOCK\t\t(1 << 0)\n #define SKIP_IF_UNCHANGED\t(1 << 1)\ndiff --git a/read-cache.c b/read-cache.c\nindex 29144cf879e7..97dbf2434f30 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -101,6 +101,9 @@ static const char *alternate_index_output;\n \n static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n {\n+\tif (S_ISSPARSEDIR(ce->ce_mode))\n+\t\tistate->sparse_index = 1;\n+\n \tistate->cache[nr] = ce;\n \tadd_name_hash(istate, ce);\n }\n@@ -2255,6 +2258,12 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)\n \ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\",\n \t\t\t   istate->cache_nr);\n \n+\tif (!istate->repo)\n+\t\tistate->repo = the_repository;\n+\tprepare_repo_settings(istate->repo);\n+\tif (istate->repo->settings.command_requires_full_index)\n+\t\tensure_full_index(istate);\n+\n \treturn istate->cache_nr;\n \n unmap:\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 82183ead563b..316cb949b74b 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -1,8 +1,101 @@\n #include \"cache.h\"\n #include \"repository.h\"\n #include \"sparse-index.h\"\n+#include \"tree.h\"\n+#include \"pathspec.h\"\n+#include \"trace2.h\"\n+\n+static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n+{\n+\tALLOC_GROW(istate->cache, nr + 1, istate->cache_alloc);\n+\n+\tistate->cache[nr] = ce;\n+\tadd_name_hash(istate, ce);\n+}\n+\n+static int add_path_to_index(const struct object_id *oid,\n+\t\t\t\tstruct strbuf *base, const char *path,\n+\t\t\t\tunsigned int mode, int stage, void *context)\n+{\n+\tstruct index_state *istate = (struct index_state *)context;\n+\tstruct cache_entry *ce;\n+\tsize_t len = base->len;\n+\n+\tif (S_ISDIR(mode))\n+\t\treturn READ_TREE_RECURSIVE;\n+\n+\tstrbuf_addstr(base, path);\n+\n+\tce = make_cache_entry(istate, mode, oid, base->buf, 0, 0);\n+\tce->ce_flags |= CE_SKIP_WORKTREE;\n+\tset_index_entry(istate, istate->cache_nr++, ce);\n+\n+\tstrbuf_setlen(base, len);\n+\treturn 0;\n+}\n \n void ensure_full_index(struct index_state *istate)\n {\n-\t/* intentionally left blank */\n+\tint i;\n+\tstruct index_state *full;\n+\n+\tif (!istate || !istate->sparse_index)\n+\t\treturn;\n+\n+\tif (!istate->repo)\n+\t\tistate->repo = the_repository;\n+\n+\ttrace2_region_enter(\"index\", \"ensure_full_index\", istate->repo);\n+\n+\t/* initialize basics of new index */\n+\tfull = xcalloc(1, sizeof(struct index_state));\n+\tmemcpy(full, istate, sizeof(struct index_state));\n+\n+\t/* then change the necessary things */\n+\tfull->sparse_index = 0;\n+\tfull->cache_alloc = (3 * istate->cache_alloc) / 2;\n+\tfull->cache_nr = 0;\n+\tALLOC_ARRAY(full->cache, full->cache_alloc);\n+\n+\tfor (i = 0; i < istate->cache_nr; i++) {\n+\t\tstruct cache_entry *ce = istate->cache[i];\n+\t\tstruct tree *tree;\n+\t\tstruct pathspec ps;\n+\n+\t\tif (!S_ISSPARSEDIR(ce->ce_mode)) {\n+\t\t\tset_index_entry(full, full->cache_nr++, ce);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!(ce->ce_flags & CE_SKIP_WORKTREE))\n+\t\t\twarning(_(\"index entry is a directory, but not sparse (%08x)\"),\n+\t\t\t\tce->ce_flags);\n+\n+\t\t/* recursively walk into cd->name */\n+\t\ttree = lookup_tree(istate->repo, &ce->oid);\n+\n+\t\tmemset(&ps, 0, sizeof(ps));\n+\t\tps.recursive = 1;\n+\t\tps.has_wildcard = 1;\n+\t\tps.max_depth = -1;\n+\n+\t\tread_tree_recursive(istate->repo, tree,\n+\t\t\t\t    ce->name, strlen(ce->name),\n+\t\t\t\t    0, &ps,\n+\t\t\t\t    add_path_to_index, full);\n+\n+\t\t/* free directory entries. full entries are re-used */\n+\t\tdiscard_cache_entry(ce);\n+\t}\n+\n+\t/* Copy back into original index. */\n+\tmemcpy(&istate->name_hash, &full->name_hash, sizeof(full->name_hash));\n+\tistate->sparse_index = 0;\n+\tfree(istate->cache);\n+\tistate->cache = full->cache;\n+\tistate->cache_nr = full->cache_nr;\n+\tistate->cache_alloc = full->cache_alloc;\n+\n+\tfree(full);\n+\n+\ttrace2_region_leave(\"index\", \"ensure_full_index\", istate->repo);\n }\n-- \ngitgitgadget\n\n"},{"id":"418708","messageId":"6e21f776e883cef25f63829caf338298252ebaca.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 04/20] sparse-index: add guard to ensure full index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:30:47Z","receivedAt":"2021-03-10T19:31:46Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nUpcoming changes will introduce modifications to the index format that\nallow sparse directories. It will be useful to have a mechanism for\nconverting those sparse index files into full indexes by walking the\ntree at those sparse directories. Name this method ensure_full_index()\nas it will guarantee that the index is fully expanded.\n\nThis method is not implemented yet, and instead we focus on the\nscaffolding to declare it and call it at the appropriate time.\n\nAdd a 'command_requires_full_index' member to struct repo_settings. This\nwill be an indicator that we need the index in full mode to do certain\nindex operations. This starts as being true for every command, then we\nwill set it to false as some commands integrate with sparse indexes.\n\nIf 'command_requires_full_index' is true, then we will immediately\nexpand a sparse index to a full one upon reading from disk. This\nsuffices for now, but we will want to add more callers to\nensure_full_index() later.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Makefile        |  1 +\n repo-settings.c |  8 ++++++++\n repository.c    | 11 ++++++++++-\n repository.h    |  2 ++\n sparse-index.c  |  8 ++++++++\n sparse-index.h  |  7 +++++++\n 6 files changed, 36 insertions(+), 1 deletion(-)\n create mode 100644 sparse-index.c\n create mode 100644 sparse-index.h\n\ndiff --git a/Makefile b/Makefile\nindex 5a239cac20e3..3bf61699238d 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -980,6 +980,7 @@ LIB_OBJS += setup.o\n LIB_OBJS += shallow.o\n LIB_OBJS += sideband.o\n LIB_OBJS += sigchain.o\n+LIB_OBJS += sparse-index.o\n LIB_OBJS += split-index.o\n LIB_OBJS += stable-qsort.o\n LIB_OBJS += strbuf.o\ndiff --git a/repo-settings.c b/repo-settings.c\nindex f7fff0f5ab83..d63569e4041e 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -77,4 +77,12 @@ void prepare_repo_settings(struct repository *r)\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+\t/*\n+\t * This setting guards all index reads to require a full index\n+\t * over a sparse index. After suitable guards are placed in the\n+\t * codebase around uses of the index, this setting will be\n+\t * removed.\n+\t */\n+\tr->settings.command_requires_full_index = 1;\n }\ndiff --git a/repository.c b/repository.c\nindex c98298acd017..a8acae002f71 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -10,6 +10,7 @@\n #include \"object.h\"\n #include \"lockfile.h\"\n #include \"submodule-config.h\"\n+#include \"sparse-index.h\"\n \n /* The main repository */\n static struct repository the_repo;\n@@ -261,6 +262,8 @@ void repo_clear(struct repository *repo)\n \n int repo_read_index(struct repository *repo)\n {\n+\tint res;\n+\n \tif (!repo->index)\n \t\trepo->index = xcalloc(1, sizeof(*repo->index));\n \n@@ -270,7 +273,13 @@ int repo_read_index(struct repository *repo)\n \telse if (repo->index->repo != repo)\n \t\tBUG(\"repo's index should point back at itself\");\n \n-\treturn read_index_from(repo->index, repo->index_file, repo->gitdir);\n+\tres = read_index_from(repo->index, repo->index_file, repo->gitdir);\n+\n+\tprepare_repo_settings(repo);\n+\tif (repo->settings.command_requires_full_index)\n+\t\tensure_full_index(repo->index);\n+\n+\treturn res;\n }\n \n int repo_hold_locked_index(struct repository *repo,\ndiff --git a/repository.h b/repository.h\nindex b385ca3c94b6..e06a23015697 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -41,6 +41,8 @@ struct repo_settings {\n \tenum fetch_negotiation_setting fetch_negotiation_algorithm;\n \n \tint core_multi_pack_index;\n+\n+\tunsigned command_requires_full_index:1;\n };\n \n struct repository {\ndiff --git a/sparse-index.c b/sparse-index.c\nnew file mode 100644\nindex 000000000000..82183ead563b\n--- /dev/null\n+++ b/sparse-index.c\n@@ -0,0 +1,8 @@\n+#include \"cache.h\"\n+#include \"repository.h\"\n+#include \"sparse-index.h\"\n+\n+void ensure_full_index(struct index_state *istate)\n+{\n+\t/* intentionally left blank */\n+}\ndiff --git a/sparse-index.h b/sparse-index.h\nnew file mode 100644\nindex 000000000000..09a20d036c46\n--- /dev/null\n+++ b/sparse-index.h\n@@ -0,0 +1,7 @@\n+#ifndef SPARSE_INDEX_H__\n+#define SPARSE_INDEX_H__\n+\n+struct index_state;\n+void ensure_full_index(struct index_state *istate);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"418709","messageId":"5cbedb377b37ebdc103d9d94e68b6621bcd3d3cf.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 03/20] t1092: clean up script quoting","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:30:46Z","receivedAt":"2021-03-10T19:31:46Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThis test was introduced in 19a0acc83e4 (t1092: test interesting\nsparse-checkout scenarios, 2021-01-23), but these issues with quoting\nwere not noticed until starting this follow-up series. The old mechanism\nwould drop quoting such as in\n\n   test_all_match git commit -m \"touch README.md\"\n\nThe above happened to work because README.md is a file in the\nrepository, so 'git commit -m touch REAMDE.md' would succeed by\naccident.\n\nOther cases included quoting for no good reason, so clean that up now.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/t1092-sparse-checkout-compatibility.sh | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 8cd3e5a8d227..3725d3997e70 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -96,20 +96,20 @@ init_repos () {\n run_on_sparse () {\n \t(\n \t\tcd sparse-checkout &&\n-\t\t$* >../sparse-checkout-out 2>../sparse-checkout-err\n+\t\t\"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n \t)\n }\n \n run_on_all () {\n \t(\n \t\tcd full-checkout &&\n-\t\t$* >../full-checkout-out 2>../full-checkout-err\n+\t\t\"$@\" >../full-checkout-out 2>../full-checkout-err\n \t) &&\n-\trun_on_sparse $*\n+\trun_on_sparse \"$@\"\n }\n \n test_all_match () {\n-\trun_on_all $* &&\n+\trun_on_all \"$@\" &&\n \ttest_cmp full-checkout-out sparse-checkout-out &&\n \ttest_cmp full-checkout-err sparse-checkout-err\n }\n@@ -119,7 +119,7 @@ test_expect_success 'status with options' '\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git status --porcelain=v2 -z -u &&\n \ttest_all_match git status --porcelain=v2 -uno &&\n-\trun_on_all \"touch README.md\" &&\n+\trun_on_all touch README.md &&\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git status --porcelain=v2 -z -u &&\n \ttest_all_match git status --porcelain=v2 -uno &&\n@@ -135,7 +135,7 @@ test_expect_success 'add, commit, checkout' '\n \twrite_script edit-contents <<-\\EOF &&\n \techo text >>$1\n \tEOF\n-\trun_on_all \"../edit-contents README.md\" &&\n+\trun_on_all ../edit-contents README.md &&\n \n \ttest_all_match git add README.md &&\n \ttest_all_match git status --porcelain=v2 &&\n@@ -144,7 +144,7 @@ test_expect_success 'add, commit, checkout' '\n \ttest_all_match git checkout HEAD~1 &&\n \ttest_all_match git checkout - &&\n \n-\trun_on_all \"../edit-contents README.md\" &&\n+\trun_on_all ../edit-contents README.md &&\n \n \ttest_all_match git add -A &&\n \ttest_all_match git status --porcelain=v2 &&\n@@ -153,7 +153,7 @@ test_expect_success 'add, commit, checkout' '\n \ttest_all_match git checkout HEAD~1 &&\n \ttest_all_match git checkout - &&\n \n-\trun_on_all \"../edit-contents deep/newfile\" &&\n+\trun_on_all ../edit-contents deep/newfile &&\n \n \ttest_all_match git status --porcelain=v2 -uno &&\n \ttest_all_match git status --porcelain=v2 &&\n@@ -186,7 +186,7 @@ test_expect_success 'diff --staged' '\n \twrite_script edit-contents <<-\\EOF &&\n \techo text >>README.md\n \tEOF\n-\trun_on_all \"../edit-contents\" &&\n+\trun_on_all ../edit-contents &&\n \n \ttest_all_match git diff &&\n \ttest_all_match git diff --staged &&\n@@ -280,7 +280,7 @@ test_expect_success 'clean' '\n \techo bogus >>.gitignore &&\n \trun_on_all cp ../.gitignore . &&\n \ttest_all_match git add .gitignore &&\n-\ttest_all_match git commit -m ignore-bogus-files &&\n+\ttest_all_match git commit -m \"ignore bogus files\" &&\n \n \trun_on_sparse mkdir folder1 &&\n \trun_on_all touch folder1/bogus &&\n-- \ngitgitgadget\n\n"},{"id":"418710","messageId":"eac2db5efc2297cffa9aba227e964bcc952d04ff.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 06/20] t1092: compare sparse-checkout to sparse-index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:30:49Z","receivedAt":"2021-03-10T19:32:17Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nAdd a new 'sparse-index' repo alongside the 'full-checkout' and\n'sparse-checkout' repos in t1092-sparse-checkout-compatibility.sh. Also\nadd run_on_sparse and test_sparse_match helpers. These helpers will be\nused when the sparse index is implemented.\n\nAdd GIT_TEST_SPARSE_INDEX environment variable to enable the\nsparse-index by default. This will be intended to use across the entire\ntest suite, except that it will only affect cases where the\nsparse-checkout feature is enabled.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/README                                 |  3 +++\n t/t1092-sparse-checkout-compatibility.sh | 24 ++++++++++++++++++++----\n 2 files changed, 23 insertions(+), 4 deletions(-)\n\ndiff --git a/t/README b/t/README\nindex 593d4a4e270c..b98bc563aab5 100644\n--- a/t/README\n+++ b/t/README\n@@ -439,6 +439,9 @@ and \"sha256\".\n GIT_TEST_WRITE_REV_INDEX=<boolean>, when true enables the\n 'pack.writeReverseIndex' setting.\n \n+GIT_TEST_SPARSE_INDEX=<boolean>, when true enables index writes to use the\n+sparse-index format by default.\n+\n Naming Tests\n ------------\n \ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 3725d3997e70..71d6f9e4c014 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -7,6 +7,7 @@ test_description='compare full workdir to sparse workdir'\n test_expect_success 'setup' '\n \tgit init initial-repo &&\n \t(\n+\t\tGIT_TEST_SPARSE_INDEX=0 &&\n \t\tcd initial-repo &&\n \t\techo a >a &&\n \t\techo \"after deep\" >e &&\n@@ -87,23 +88,32 @@ init_repos () {\n \n \tcp -r initial-repo sparse-checkout &&\n \tgit -C sparse-checkout reset --hard &&\n-\tgit -C sparse-checkout sparse-checkout init --cone &&\n+\n+\tcp -r initial-repo sparse-index &&\n+\tgit -C sparse-index reset --hard &&\n \n \t# initialize sparse-checkout definitions\n-\tgit -C sparse-checkout sparse-checkout set deep\n+\tgit -C sparse-checkout sparse-checkout init --cone &&\n+\tgit -C sparse-checkout sparse-checkout set deep &&\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout init --cone &&\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep\n }\n \n run_on_sparse () {\n \t(\n \t\tcd sparse-checkout &&\n-\t\t\"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n+\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n+\t) &&\n+\t(\n+\t\tcd sparse-index &&\n+\t\tGIT_TEST_SPARSE_INDEX=1 \"$@\" >../sparse-index-out 2>../sparse-index-err\n \t)\n }\n \n run_on_all () {\n \t(\n \t\tcd full-checkout &&\n-\t\t\"$@\" >../full-checkout-out 2>../full-checkout-err\n+\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../full-checkout-out 2>../full-checkout-err\n \t) &&\n \trun_on_sparse \"$@\"\n }\n@@ -114,6 +124,12 @@ test_all_match () {\n \ttest_cmp full-checkout-err sparse-checkout-err\n }\n \n+test_sparse_match () {\n+\trun_on_sparse $* &&\n+\ttest_cmp sparse-checkout-out sparse-index-out &&\n+\ttest_cmp sparse-checkout-err sparse-index-err\n+}\n+\n test_expect_success 'status with options' '\n \tinit_repos &&\n \ttest_all_match git status --porcelain=v2 &&\n-- \ngitgitgadget\n\n"},{"id":"418711","messageId":"e9c82d2eda821ff4c0fe2984d56e12d9db3e2dcc.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 07/20] test-read-cache: print cache entries with --table","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:30:50Z","receivedAt":"2021-03-10T19:32:18Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThis table is helpful for discovering data in the index to ensure it is\nbeing written correctly, especially as we build and test the\nsparse-index. This table includes an output format similar to 'git\nls-tree', but should not be compared to that directly. The biggest\nreasons are that 'git ls-tree' includes a tree entry for every\nsubdirectory, even those that would not appear as a sparse directory in\na sparse-index. Further, 'git ls-tree' does not use a trailing directory\nseparator for its tree rows.\n\nThis does not print the stat() information for the blobs. That could be\nadded in a future change with another option. The tests that are added\nin the next few changes care only about the object types and IDs.\n\nTo make the option parsing slightly more robust, wrap the string\ncomparisons in a loop adapted from test-dir-iterator.c.\n\nCare must be taken with the final check for the 'cnt' variable. We\ncontinue the expectation that the numerical value is the final argument.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/helper/test-read-cache.c | 55 +++++++++++++++++++++++++++++++-------\n 1 file changed, 45 insertions(+), 10 deletions(-)\n\ndiff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c\nindex 244977a29bdf..6cfd8f2de71c 100644\n--- a/t/helper/test-read-cache.c\n+++ b/t/helper/test-read-cache.c\n@@ -1,36 +1,71 @@\n #include \"test-tool.h\"\n #include \"cache.h\"\n #include \"config.h\"\n+#include \"blob.h\"\n+#include \"commit.h\"\n+#include \"tree.h\"\n+\n+static void print_cache_entry(struct cache_entry *ce)\n+{\n+\tconst char *type;\n+\tprintf(\"%06o \", ce->ce_mode & 0177777);\n+\n+\tif (S_ISSPARSEDIR(ce->ce_mode))\n+\t\ttype = tree_type;\n+\telse if (S_ISGITLINK(ce->ce_mode))\n+\t\ttype = commit_type;\n+\telse\n+\t\ttype = blob_type;\n+\n+\tprintf(\"%s %s\\t%s\\n\",\n+\t       type,\n+\t       oid_to_hex(&ce->oid),\n+\t       ce->name);\n+}\n+\n+static void print_cache(struct index_state *istate)\n+{\n+\tint i;\n+\tfor (i = 0; i < istate->cache_nr; i++)\n+\t\tprint_cache_entry(istate->cache[i]);\n+}\n \n int cmd__read_cache(int argc, const char **argv)\n {\n+\tstruct repository *r = the_repository;\n \tint i, cnt = 1;\n \tconst char *name = NULL;\n+\tint table = 0;\n \n-\tif (argc > 1 && skip_prefix(argv[1], \"--print-and-refresh=\", &name)) {\n-\t\targc--;\n-\t\targv++;\n+\tfor (++argv, --argc; *argv && starts_with(*argv, \"--\"); ++argv, --argc) {\n+\t\tif (skip_prefix(*argv, \"--print-and-refresh=\", &name))\n+\t\t\tcontinue;\n+\t\tif (!strcmp(*argv, \"--table\"))\n+\t\t\ttable = 1;\n \t}\n \n-\tif (argc == 2)\n-\t\tcnt = strtol(argv[1], NULL, 0);\n+\tif (argc == 1)\n+\t\tcnt = strtol(argv[0], NULL, 0);\n \tsetup_git_directory();\n \tgit_config(git_default_config, NULL);\n+\n \tfor (i = 0; i < cnt; i++) {\n-\t\tread_cache();\n+\t\trepo_read_index(r);\n \t\tif (name) {\n \t\t\tint pos;\n \n-\t\t\trefresh_index(&the_index, REFRESH_QUIET,\n+\t\t\trefresh_index(r->index, REFRESH_QUIET,\n \t\t\t\t      NULL, NULL, NULL);\n-\t\t\tpos = index_name_pos(&the_index, name, strlen(name));\n+\t\t\tpos = index_name_pos(r->index, name, strlen(name));\n \t\t\tif (pos < 0)\n \t\t\t\tdie(\"%s not in index\", name);\n \t\t\tprintf(\"%s is%s up to date\\n\", name,\n-\t\t\t       ce_uptodate(the_index.cache[pos]) ? \"\" : \" not\");\n+\t\t\t       ce_uptodate(r->index->cache[pos]) ? \"\" : \" not\");\n \t\t\twrite_file(name, \"%d\\n\", i);\n \t\t}\n-\t\tdiscard_cache();\n+\t\tif (table)\n+\t\t\tprint_cache(r->index);\n+\t\tdiscard_index(r->index);\n \t}\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"418712","messageId":"3fa684b315fb02f42481182a986557e47a8cf0fd.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 14/20] sparse-index: check index conversion happens","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:30:57Z","receivedAt":"2021-03-10T19:32:18Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nAdd a test case that uses test_region to ensure that we are truly\nexpanding a sparse index to a full one, then converting back to sparse\nwhen writing the index. As we integrate more Git commands with the\nsparse index, we will convert these commands to check that we do _not_\nconvert the sparse index to a full index and instead stay sparse the\nentire time.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/t1092-sparse-checkout-compatibility.sh | 18 ++++++++++++++++++\n 1 file changed, 18 insertions(+)\n\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex b38fab6455d9..bfc9e28ef0e1 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -391,4 +391,22 @@ test_expect_success 'submodule handling' '\n \tgrep \"160000 commit $(git -C initial-repo rev-parse HEAD)\tmodules/sub\" cache\n '\n \n+test_expect_success 'sparse-index is expanded and converted back' '\n+\tinit_repos &&\n+\n+\t(\n+\t\tGIT_TEST_SPARSE_INDEX=1 &&\n+\t\texport GIT_TEST_SPARSE_INDEX &&\n+\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" reset --hard &&\n+\t\ttest_region index convert_to_sparse trace2.txt &&\n+\t\ttest_region index ensure_full_index trace2.txt &&\n+\n+\t\trm trace2.txt &&\n+\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" status -uno &&\n+\t\ttest_region index ensure_full_index trace2.txt\n+\t)\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"418713","messageId":"243541fc5820572faa518dfc157175a72a7a9ea8.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 08/20] test-tool: don't force full index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:30:51Z","receivedAt":"2021-03-10T19:32:18Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWe will use 'test-tool read-cache --table' to check that a sparse\nindex is written as part of init_repos. Since we will no longer always\nexpand a sparse index into a full index, add an '--expand' parameter\nthat adds a call to ensure_full_index() so we can compare a sparse index\ndirectly against a full index, or at least what the in-memory index\nlooks like when expanded in this way.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/helper/test-read-cache.c               | 13 ++++++++++++-\n t/t1092-sparse-checkout-compatibility.sh |  5 +++++\n 2 files changed, 17 insertions(+), 1 deletion(-)\n\ndiff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c\nindex 6cfd8f2de71c..b52c174acc7a 100644\n--- a/t/helper/test-read-cache.c\n+++ b/t/helper/test-read-cache.c\n@@ -4,6 +4,7 @@\n #include \"blob.h\"\n #include \"commit.h\"\n #include \"tree.h\"\n+#include \"sparse-index.h\"\n \n static void print_cache_entry(struct cache_entry *ce)\n {\n@@ -35,13 +36,19 @@ int cmd__read_cache(int argc, const char **argv)\n \tstruct repository *r = the_repository;\n \tint i, cnt = 1;\n \tconst char *name = NULL;\n-\tint table = 0;\n+\tint table = 0, expand = 0;\n+\n+\tinitialize_the_repository();\n+\tprepare_repo_settings(r);\n+\tr->settings.command_requires_full_index = 0;\n \n \tfor (++argv, --argc; *argv && starts_with(*argv, \"--\"); ++argv, --argc) {\n \t\tif (skip_prefix(*argv, \"--print-and-refresh=\", &name))\n \t\t\tcontinue;\n \t\tif (!strcmp(*argv, \"--table\"))\n \t\t\ttable = 1;\n+\t\telse if (!strcmp(*argv, \"--expand\"))\n+\t\t\texpand = 1;\n \t}\n \n \tif (argc == 1)\n@@ -51,6 +58,10 @@ int cmd__read_cache(int argc, const char **argv)\n \n \tfor (i = 0; i < cnt; i++) {\n \t\trepo_read_index(r);\n+\n+\t\tif (expand)\n+\t\t\tensure_full_index(r->index);\n+\n \t\tif (name) {\n \t\t\tint pos;\n \ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 71d6f9e4c014..4d789fe86b9d 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -130,6 +130,11 @@ test_sparse_match () {\n \ttest_cmp sparse-checkout-err sparse-index-err\n }\n \n+test_expect_success 'expanded in-memory index matches full index' '\n+\tinit_repos &&\n+\ttest_sparse_match test-tool read-cache --expand --table\n+'\n+\n test_expect_success 'status with options' '\n \tinit_repos &&\n \ttest_all_match git status --porcelain=v2 &&\n-- \ngitgitgadget\n\n"},{"id":"418714","messageId":"f2a3e72987988c615fae75032af874735d0e76d8.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 12/20] submodule: sparse-index should not collapse links","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:30:55Z","receivedAt":"2021-03-10T19:32:18Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nA submodule is stored as a \"Git link\" that actually points to a commit\nwithin a submodule. Submodules are populated or not depending on\nsubmodule configuration, not sparse-checkout. To ensure that the\nsparse-index feature integrates correctly with submodules, we should not\ncollapse a directory if there is a Git link within its range.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n sparse-index.c                           |  1 +\n t/t1092-sparse-checkout-compatibility.sh | 17 +++++++++++++++++\n 2 files changed, 18 insertions(+)\n\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 5eb561259bb1..36b4dde7eeda 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -52,6 +52,7 @@ static int convert_to_sparse_rec(struct index_state *istate,\n \t\tstruct cache_entry *ce = istate->cache[i];\n \n \t\tif (ce_stage(ce) ||\n+\t\t    S_ISGITLINK(ce->ce_mode) ||\n \t\t    !(ce->ce_flags & CE_SKIP_WORKTREE))\n \t\t\tcan_convert = 0;\n \t}\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex ca87033d30b0..b38fab6455d9 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -374,4 +374,21 @@ test_expect_success 'clean' '\n \ttest_sparse_match test_path_is_dir folder1\n '\n \n+test_expect_success 'submodule handling' '\n+\tinit_repos &&\n+\n+\ttest_all_match mkdir modules &&\n+\ttest_all_match touch modules/a &&\n+\ttest_all_match git add modules &&\n+\ttest_all_match git commit -m \"add modules directory\" &&\n+\n+\trun_on_all git submodule add \"$(pwd)/initial-repo\" modules/sub &&\n+\ttest_all_match git commit -m \"add submodule\" &&\n+\n+\t# having a submodule prevents \"modules\" from collapse\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tgrep \"100644 blob .*\tmodules/a\" cache &&\n+\tgrep \"160000 commit $(git -C initial-repo rev-parse HEAD)\tmodules/sub\" cache\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"418715","messageId":"6f1ebe6ccc08d10715ee339fe04289db341dfdc4.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 13/20] unpack-trees: allow sparse directories","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:30:56Z","receivedAt":"2021-03-10T19:32:18Z","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 index_pos_by_traverse_info() currently throws a BUG() when a\ndirectory entry exists exactly in the index. We need to consider that it\nis possible to have a directory in a sparse index as long as that entry\nis itself marked with the skip-worktree bit.\n\nThe 'pos' variable is assigned a negative value if an exact match is not\nfound. Since a directory name can be an exact match, it is no longer an\nerror to have a nonnegative 'pos' value.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n unpack-trees.c | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 4dd99219073a..b324eec2a5d1 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -746,9 +746,12 @@ static int index_pos_by_traverse_info(struct name_entry *names,\n \tstrbuf_make_traverse_path(&name, info, names->path, names->pathlen);\n \tstrbuf_addch(&name, '/');\n \tpos = index_name_pos(o->src_index, name.buf, name.len);\n-\tif (pos >= 0)\n-\t\tBUG(\"This is a directory and should not exist in index\");\n-\tpos = -pos - 1;\n+\tif (pos >= 0) {\n+\t\tif (!o->src_index->sparse_index ||\n+\t\t    !(o->src_index->cache[pos]->ce_flags & CE_SKIP_WORKTREE))\n+\t\t\tBUG(\"This is a directory and should not exist in index\");\n+\t} else\n+\t\tpos = -pos - 1;\n \tif (pos >= o->src_index->cache_nr ||\n \t    !starts_with(o->src_index->cache[pos]->name, name.buf) ||\n \t    (pos > 0 && starts_with(o->src_index->cache[pos-1]->name, name.buf)))\n-- \ngitgitgadget\n\n"},{"id":"418716","messageId":"f6db0c27a2854b16a3402e4133629a1f479e113d.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 11/20] sparse-index: convert from full to sparse","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:30:54Z","receivedAt":"2021-03-10T19:32:18Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nIf we have a full index, then we can convert it to a sparse index by\nreplacing directories outside of the sparse cone with sparse directory\nentries. The convert_to_sparse() method does this, when the situation is\nappropriate.\n\nFor now, we avoid converting the index to a sparse index if:\n\n 1. the index is split.\n 2. the index is already sparse.\n 3. sparse-checkout is disabled.\n 4. sparse-checkout does not use cone mode.\n\nFinally, we currently limit the conversion to when the\nGIT_TEST_SPARSE_INDEX environment variable is enabled. A mode using Git\nconfig will be added in a later change.\n\nThe trickiest thing about this conversion is that we might not be able\nto mark a directory as a sparse directory just because it is outside the\nsparse cone. There might be unmerged files within that directory, so we\nneed to look for those. Also, if there is some strange reason why a file\nis not marked with CE_SKIP_WORKTREE, then we should give up on\nconverting that directory. There is still hope that some of its\nsubdirectories might be able to convert to sparse, so we keep looking\ndeeper.\n\nThe conversion process is assisted by the cache-tree extension. This is\ncalculated from the full index if it does not already exist. We then\nabandon the cache-tree as it no longer applies to the newly-sparse\nindex. Thus, this cache-tree will be recalculated in every\nsparse-full-sparse round-trip until we integrate the cache-tree\nextension with the sparse index.\n\nSome Git commands use the index after writing it. For example, 'git add'\nwill update the index, then write it to disk, then read its entries to\nreport information. To keep the in-memory index in a full state after\nwriting, we re-expand it to a full one after the write. This is wasteful\nfor commands that only write the index and do not read from it again,\nbut that is only the case until we make those commands \"sparse aware.\"\n\nWe can compare the behavior of the sparse-index in\nt1092-sparse-checkout-compability.sh by using GIT_TEST_SPARSE_INDEX=1\nwhen operating on the 'sparse-index' repo. We can also compare the two\nsparse repos directly, such as comparing their indexes (when expanded to\nfull in the case of the 'sparse-index' repo). We also verify that the\nindex is actually populated with sparse directory entries.\n\nThe 'checkout and reset (mixed)' test is marked for failure when\ncomparing a sparse repo to a full repo, but we can compare the two\nsparse-checkout cases directly to ensure that we are not changing the\nbehavior when using a sparse index.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache-tree.c                             |   3 +\n cache.h                                  |   2 +\n read-cache.c                             |  26 ++++-\n sparse-index.c                           | 139 +++++++++++++++++++++++\n sparse-index.h                           |   1 +\n t/t1092-sparse-checkout-compatibility.sh |  61 +++++++++-\n 6 files changed, 227 insertions(+), 5 deletions(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 2fb483d3c083..5f07a39e501e 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -6,6 +6,7 @@\n #include \"object-store.h\"\n #include \"replace-object.h\"\n #include \"promisor-remote.h\"\n+#include \"sparse-index.h\"\n \n #ifndef DEBUG_CACHE_TREE\n #define DEBUG_CACHE_TREE 0\n@@ -442,6 +443,8 @@ int cache_tree_update(struct index_state *istate, int flags)\n \tif (i)\n \t\treturn i;\n \n+\tensure_full_index(istate);\n+\n \tif (!istate->cache_tree)\n \t\tistate->cache_tree = cache_tree();\n \ndiff --git a/cache.h b/cache.h\nindex 303411726e10..9217d405b9b8 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -251,6 +251,8 @@ static inline unsigned int create_ce_mode(unsigned int mode)\n {\n \tif (S_ISLNK(mode))\n \t\treturn S_IFLNK;\n+\tif (mode == S_IFDIR)\n+\t\treturn S_IFDIR;\n \tif (S_ISDIR(mode) || S_ISGITLINK(mode))\n \t\treturn S_IFGITLINK;\n \treturn S_IFREG | ce_permissions(mode);\ndiff --git a/read-cache.c b/read-cache.c\nindex 97dbf2434f30..92126b9d23c9 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 \"sparse-index.h\"\n \n /* Mask for the name length in ce_flags in the on-disk index */\n \n@@ -1002,8 +1003,14 @@ int verify_path(const char *path, unsigned mode)\n \n \t\t\tc = *path++;\n \t\t\tif ((c == '.' && !verify_dotfile(path, mode)) ||\n-\t\t\t    is_dir_sep(c) || c == '\\0')\n+\t\t\t    is_dir_sep(c))\n \t\t\t\treturn 0;\n+\t\t\t/*\n+\t\t\t * allow terminating directory separators for\n+\t\t\t * sparse directory entries.\n+\t\t\t */\n+\t\t\tif (c == '\\0')\n+\t\t\t\treturn S_ISDIR(mode);\n \t\t} else if (c == '\\\\' && protect_ntfs) {\n \t\t\tif (is_ntfs_dotgit(path))\n \t\t\t\treturn 0;\n@@ -3061,6 +3068,14 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n \t\t\t\t unsigned flags)\n {\n \tint ret;\n+\tint was_full = !istate->sparse_index;\n+\n+\tret = convert_to_sparse(istate);\n+\n+\tif (ret) {\n+\t\twarning(_(\"failed to convert to a sparse-index\"));\n+\t\treturn ret;\n+\t}\n \n \t/*\n \t * TODO trace2: replace \"the_repository\" with the actual repo instance\n@@ -3072,6 +3087,9 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n \ttrace2_region_leave_printf(\"index\", \"do_write_index\", the_repository,\n \t\t\t\t   \"%s\", get_lock_file_path(lock));\n \n+\tif (was_full)\n+\t\tensure_full_index(istate);\n+\n \tif (ret)\n \t\treturn ret;\n \tif (flags & COMMIT_LOCK)\n@@ -3162,9 +3180,10 @@ static int write_shared_index(struct index_state *istate,\n \t\t\t      struct tempfile **temp)\n {\n \tstruct split_index *si = istate->split_index;\n-\tint ret;\n+\tint ret, was_full = !istate->sparse_index;\n \n \tmove_cache_to_base_index(istate);\n+\tconvert_to_sparse(istate);\n \n \ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n \t\t\t\t   the_repository, \"%s\", get_tempfile_path(*temp));\n@@ -3172,6 +3191,9 @@ static int write_shared_index(struct index_state *istate,\n \ttrace2_region_leave_printf(\"index\", \"shared/do_write_index\",\n \t\t\t\t   the_repository, \"%s\", get_tempfile_path(*temp));\n \n+\tif (was_full)\n+\t\tensure_full_index(istate);\n+\n \tif (ret)\n \t\treturn ret;\n \tret = adjust_shared_perm(get_tempfile_path(*temp));\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 316cb949b74b..5eb561259bb1 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -4,6 +4,145 @@\n #include \"tree.h\"\n #include \"pathspec.h\"\n #include \"trace2.h\"\n+#include \"cache-tree.h\"\n+#include \"config.h\"\n+#include \"dir.h\"\n+#include \"fsmonitor.h\"\n+\n+static struct cache_entry *construct_sparse_dir_entry(\n+\t\t\t\tstruct index_state *istate,\n+\t\t\t\tconst char *sparse_dir,\n+\t\t\t\tstruct cache_tree *tree)\n+{\n+\tstruct cache_entry *de;\n+\n+\tde = make_cache_entry(istate, S_IFDIR, &tree->oid, sparse_dir, 0, 0);\n+\n+\tde->ce_flags |= CE_SKIP_WORKTREE;\n+\treturn de;\n+}\n+\n+/*\n+ * Returns the number of entries \"inserted\" into the index.\n+ */\n+static int convert_to_sparse_rec(struct index_state *istate,\n+\t\t\t\t int num_converted,\n+\t\t\t\t int start, int end,\n+\t\t\t\t const char *ct_path, size_t ct_pathlen,\n+\t\t\t\t struct cache_tree *ct)\n+{\n+\tint i, can_convert = 1;\n+\tint start_converted = num_converted;\n+\tenum pattern_match_result match;\n+\tint dtype;\n+\tstruct strbuf child_path = STRBUF_INIT;\n+\tstruct pattern_list *pl = istate->sparse_checkout_patterns;\n+\n+\t/*\n+\t * Is the current path outside of the sparse cone?\n+\t * Then check if the region can be replaced by a sparse\n+\t * directory entry (everything is sparse and merged).\n+\t */\n+\tmatch = path_matches_pattern_list(ct_path, ct_pathlen,\n+\t\t\t\t\t  NULL, &dtype, pl, istate);\n+\tif (match != NOT_MATCHED)\n+\t\tcan_convert = 0;\n+\n+\tfor (i = start; can_convert && i < end; i++) {\n+\t\tstruct cache_entry *ce = istate->cache[i];\n+\n+\t\tif (ce_stage(ce) ||\n+\t\t    !(ce->ce_flags & CE_SKIP_WORKTREE))\n+\t\t\tcan_convert = 0;\n+\t}\n+\n+\tif (can_convert) {\n+\t\tstruct cache_entry *se;\n+\t\tse = construct_sparse_dir_entry(istate, ct_path, ct);\n+\n+\t\tistate->cache[num_converted++] = se;\n+\t\treturn 1;\n+\t}\n+\n+\tfor (i = start; i < end; ) {\n+\t\tint count, span, pos = -1;\n+\t\tconst char *base, *slash;\n+\t\tstruct cache_entry *ce = istate->cache[i];\n+\n+\t\t/*\n+\t\t * Detect if this is a normal entry outside of any subtree\n+\t\t * entry.\n+\t\t */\n+\t\tbase = ce->name + ct_pathlen;\n+\t\tslash = strchr(base, '/');\n+\n+\t\tif (slash)\n+\t\t\tpos = cache_tree_subtree_pos(ct, base, slash - base);\n+\n+\t\tif (pos < 0) {\n+\t\t\tistate->cache[num_converted++] = ce;\n+\t\t\ti++;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tstrbuf_setlen(&child_path, 0);\n+\t\tstrbuf_add(&child_path, ce->name, slash - ce->name + 1);\n+\n+\t\tspan = ct->down[pos]->cache_tree->entry_count;\n+\t\tcount = convert_to_sparse_rec(istate,\n+\t\t\t\t\t      num_converted, i, i + span,\n+\t\t\t\t\t      child_path.buf, child_path.len,\n+\t\t\t\t\t      ct->down[pos]->cache_tree);\n+\t\tnum_converted += count;\n+\t\ti += span;\n+\t}\n+\n+\tstrbuf_release(&child_path);\n+\treturn num_converted - start_converted;\n+}\n+\n+int convert_to_sparse(struct index_state *istate)\n+{\n+\tif (istate->split_index || istate->sparse_index ||\n+\t    !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n+\t\treturn 0;\n+\n+\t/*\n+\t * For now, only create a sparse index with the\n+\t * GIT_TEST_SPARSE_INDEX environment variable. We will relax\n+\t * this once we have a proper way to opt-in (and later still,\n+\t * opt-out).\n+\t */\n+\tif (!git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0))\n+\t\treturn 0;\n+\n+\tif (!istate->sparse_checkout_patterns) {\n+\t\tistate->sparse_checkout_patterns = xcalloc(1, sizeof(struct pattern_list));\n+\t\tif (get_sparse_checkout_patterns(istate->sparse_checkout_patterns) < 0)\n+\t\t\treturn 0;\n+\t}\n+\n+\tif (!istate->sparse_checkout_patterns->use_cone_patterns) {\n+\t\twarning(_(\"attempting to use sparse-index without cone mode\"));\n+\t\treturn -1;\n+\t}\n+\n+\tif (cache_tree_update(istate, 0)) {\n+\t\twarning(_(\"unable to update cache-tree, staying full\"));\n+\t\treturn -1;\n+\t}\n+\n+\tremove_fsmonitor(istate);\n+\n+\ttrace2_region_enter(\"index\", \"convert_to_sparse\", istate->repo);\n+\tistate->cache_nr = convert_to_sparse_rec(istate,\n+\t\t\t\t\t\t 0, 0, istate->cache_nr,\n+\t\t\t\t\t\t \"\", 0, istate->cache_tree);\n+\tistate->drop_cache_tree = 1;\n+\tistate->sparse_index = 1;\n+\ttrace2_region_leave(\"index\", \"convert_to_sparse\", istate->repo);\n+\treturn 0;\n+}\n \n static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n {\ndiff --git a/sparse-index.h b/sparse-index.h\nindex 09a20d036c46..64380e121d80 100644\n--- a/sparse-index.h\n+++ b/sparse-index.h\n@@ -3,5 +3,6 @@\n \n struct index_state;\n void ensure_full_index(struct index_state *istate);\n+int convert_to_sparse(struct index_state *istate);\n \n #endif\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 4d789fe86b9d..ca87033d30b0 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -2,6 +2,9 @@\n \n test_description='compare full workdir to sparse workdir'\n \n+GIT_TEST_CHECK_CACHE_TREE=0\n+GIT_TEST_SPLIT_INDEX=0\n+\n . ./test-lib.sh\n \n test_expect_success 'setup' '\n@@ -121,15 +124,49 @@ run_on_all () {\n test_all_match () {\n \trun_on_all \"$@\" &&\n \ttest_cmp full-checkout-out sparse-checkout-out &&\n-\ttest_cmp full-checkout-err sparse-checkout-err\n+\ttest_cmp full-checkout-out sparse-index-out &&\n+\ttest_cmp full-checkout-err sparse-checkout-err &&\n+\ttest_cmp full-checkout-err sparse-index-err\n }\n \n test_sparse_match () {\n-\trun_on_sparse $* &&\n+\trun_on_sparse \"$@\" &&\n \ttest_cmp sparse-checkout-out sparse-index-out &&\n \ttest_cmp sparse-checkout-err sparse-index-err\n }\n \n+test_expect_success 'sparse-index contents' '\n+\tinit_repos &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tfor dir in folder1 folder2 x\n+\tdo\n+\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n+\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n+\t\t\t|| return 1\n+\tdone &&\n+\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set folder1 &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tfor dir in deep folder2 x\n+\tdo\n+\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n+\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n+\t\t\t|| return 1\n+\tdone &&\n+\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep/deeper1 &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tfor dir in deep/deeper2 folder1 folder2 x\n+\tdo\n+\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n+\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n+\t\t\t|| return 1\n+\tdone\n+'\n+\n test_expect_success 'expanded in-memory index matches full index' '\n \tinit_repos &&\n \ttest_sparse_match test-tool read-cache --expand --table\n@@ -137,6 +174,7 @@ test_expect_success 'expanded in-memory index matches full index' '\n \n test_expect_success 'status with options' '\n \tinit_repos &&\n+\ttest_sparse_match ls &&\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git status --porcelain=v2 -z -u &&\n \ttest_all_match git status --porcelain=v2 -uno &&\n@@ -273,6 +311,17 @@ test_expect_failure 'checkout and reset (mixed)' '\n \ttest_all_match git reset update-folder2\n '\n \n+# Ensure that sparse-index behaves identically to\n+# sparse-checkout with a full index.\n+test_expect_success 'checkout and reset (mixed) [sparse]' '\n+\tinit_repos &&\n+\n+\ttest_sparse_match git checkout -b reset-test update-deep &&\n+\ttest_sparse_match git reset deepest &&\n+\ttest_sparse_match git reset update-folder1 &&\n+\ttest_sparse_match git reset update-folder2\n+'\n+\n test_expect_success 'merge' '\n \tinit_repos &&\n \n@@ -309,14 +358,20 @@ test_expect_success 'clean' '\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git clean -f &&\n \ttest_all_match git status --porcelain=v2 &&\n+\ttest_sparse_match ls &&\n+\ttest_sparse_match ls folder1 &&\n \n \ttest_all_match git clean -xf &&\n \ttest_all_match git status --porcelain=v2 &&\n+\ttest_sparse_match ls &&\n+\ttest_sparse_match ls folder1 &&\n \n \ttest_all_match git clean -xdf &&\n \ttest_all_match git status --porcelain=v2 &&\n+\ttest_sparse_match ls &&\n+\ttest_sparse_match ls folder1 &&\n \n-\ttest_path_is_dir sparse-checkout/folder1\n+\ttest_sparse_match test_path_is_dir folder1\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"418718","messageId":"48f65093b3da3fdee606e6d52e81795cdfcbbd22.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 09/20] unpack-trees: ensure full index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:30:52Z","receivedAt":"2021-03-10T19:32:18Z","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 next change will translate full indexes into sparse indexes at write\ntime. The existing logic provides a way for every sparse index to be\nexpanded to a full index at read time. However, there are cases where an\nindex is written and then continues to be used in-memory to perform\nfurther updates.\n\nunpack_trees() is frequently called after such a write. In particular,\ncommands like 'git reset' do this double-update of the index.\n\nEnsure that we have a full index when entering unpack_trees(), but only\nwhen command_requires_full_index is true. This is always true at the\nmoment, but we will later relax that after unpack_trees() is updated to\nhandle sparse directory entries.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n unpack-trees.c | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex f5f668f532d8..4dd99219073a 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -1567,6 +1567,7 @@ static int verify_absent(const struct cache_entry *,\n  */\n int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)\n {\n+\tstruct repository *repo = the_repository;\n \tint i, ret;\n \tstatic struct cache_entry *dfc;\n \tstruct pattern_list pl;\n@@ -1578,6 +1579,12 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n \ttrace_performance_enter();\n \ttrace2_region_enter(\"unpack_trees\", \"unpack_trees\", the_repository);\n \n+\tprepare_repo_settings(repo);\n+\tif (repo->settings.command_requires_full_index) {\n+\t\tensure_full_index(o->src_index);\n+\t\tensure_full_index(o->dst_index);\n+\t}\n+\n \tif (!core_apply_sparse_checkout || !o->update)\n \t\to->skip_sparse_checkout = 1;\n \tif (!o->skip_sparse_checkout && !o->pl) {\n-- \ngitgitgadget\n\n"},{"id":"418723","messageId":"d74576d677f61fdfb02ab7fe06912dd139dc97ce.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 15/20] sparse-index: create extension for compatibility","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:30:58Z","receivedAt":"2021-03-10T19:32:18Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nPreviously, we enabled the sparse index format only using\nGIT_TEST_SPARSE_INDEX=1. This is not a feasible direction for users to\nactually select this mode. Further, sparse directory entries are not\nunderstood by the index formats as advertised.\n\nWe _could_ add a new index version that explicitly adds these\ncapabilities, but there are nuances to index formats 2, 3, and 4 that\nare still valuable to select as options. Until we add index format\nversion 5, create a repo extension, \"extensions.sparseIndex\", that\nspecifies that the tool reading this repository must understand sparse\ndirectory entries.\n\nThis change only encodes the extension and enables it when\nGIT_TEST_SPARSE_INDEX=1. Later, we will add a more user-friendly CLI\nmechanism.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/extensions.txt |  8 ++++++\n cache.h                             |  1 +\n repo-settings.c                     |  7 ++++++\n repository.h                        |  3 ++-\n setup.c                             |  3 +++\n sparse-index.c                      | 38 +++++++++++++++++++++++++----\n 6 files changed, 54 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config/extensions.txt b/Documentation/config/extensions.txt\nindex 4e23d73cdcad..c02e09af0046 100644\n--- a/Documentation/config/extensions.txt\n+++ b/Documentation/config/extensions.txt\n@@ -6,3 +6,11 @@ extensions.objectFormat::\n Note that this setting should only be set by linkgit:git-init[1] or\n linkgit:git-clone[1].  Trying to change it after initialization will not\n work and will produce hard-to-diagnose issues.\n+\n+extensions.sparseIndex::\n+\tWhen combined with `core.sparseCheckout=true` and\n+\t`core.sparseCheckoutCone=true`, the index may contain entries\n+\tcorresponding to directories outside of the sparse-checkout\n+\tdefinition in lieu of containing each path under such directories.\n+\tVersions of Git that do not understand this extension do not\n+\texpect directory entries in the index.\ndiff --git a/cache.h b/cache.h\nindex 9217d405b9b8..03f931c5f34d 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1059,6 +1059,7 @@ struct repository_format {\n \tint worktree_config;\n \tint is_bare;\n \tint hash_algo;\n+\tint sparse_index;\n \tchar *work_tree;\n \tstruct string_list unknown_extensions;\n \tstruct string_list v1_only_extensions;\ndiff --git a/repo-settings.c b/repo-settings.c\nindex d63569e4041e..9677d50f9238 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -85,4 +85,11 @@ void prepare_repo_settings(struct repository *r)\n \t * removed.\n \t */\n \tr->settings.command_requires_full_index = 1;\n+\n+\t/*\n+\t * Initialize this as off.\n+\t */\n+\tr->settings.sparse_index = 0;\n+\tif (!repo_config_get_bool(r, \"extensions.sparseindex\", &value) && value)\n+\t\tr->settings.sparse_index = 1;\n }\ndiff --git a/repository.h b/repository.h\nindex e06a23015697..a45f7520fd9e 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -42,7 +42,8 @@ struct repo_settings {\n \n \tint core_multi_pack_index;\n \n-\tunsigned command_requires_full_index:1;\n+\tunsigned command_requires_full_index:1,\n+\t\t sparse_index:1;\n };\n \n struct repository {\ndiff --git a/setup.c b/setup.c\nindex c04cd25a30df..cd8394564613 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -500,6 +500,9 @@ static enum extension_result handle_extension(const char *var,\n \t\t\treturn error(\"invalid value for 'extensions.objectformat'\");\n \t\tdata->hash_algo = format;\n \t\treturn EXTENSION_OK;\n+\t} else if (!strcmp(ext, \"sparseindex\")) {\n+\t\tdata->sparse_index = 1;\n+\t\treturn EXTENSION_OK;\n \t}\n \treturn EXTENSION_UNKNOWN;\n }\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 36b4dde7eeda..b9c14ef7ab50 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -102,19 +102,47 @@ static int convert_to_sparse_rec(struct index_state *istate,\n \treturn num_converted - start_converted;\n }\n \n+static int enable_sparse_index(struct repository *repo)\n+{\n+\tconst char *config_path = repo_git_path(repo, \"config.worktree\");\n+\n+\tif (upgrade_repository_format(1) < 0) {\n+\t\twarning(_(\"unable to upgrade repository format to enable sparse-index\"));\n+\t\treturn -1;\n+\t}\n+\tgit_config_set_in_file_gently(config_path,\n+\t\t\t\t      \"extensions.sparseIndex\",\n+\t\t\t\t      \"true\");\n+\n+\tprepare_repo_settings(repo);\n+\trepo->settings.sparse_index = 1;\n+\treturn 0;\n+}\n+\n int convert_to_sparse(struct index_state *istate)\n {\n \tif (istate->split_index || istate->sparse_index ||\n \t    !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n \t\treturn 0;\n \n+\tif (!istate->repo)\n+\t\tistate->repo = the_repository;\n+\n+\t/*\n+\t * The GIT_TEST_SPARSE_INDEX environment variable triggers the\n+\t * extensions.sparseIndex config variable to be on.\n+\t */\n+\tif (git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0)) {\n+\t\tint err = enable_sparse_index(istate->repo);\n+\t\tif (err < 0)\n+\t\t\treturn err;\n+\t}\n+\n \t/*\n-\t * For now, only create a sparse index with the\n-\t * GIT_TEST_SPARSE_INDEX environment variable. We will relax\n-\t * this once we have a proper way to opt-in (and later still,\n-\t * opt-out).\n+\t * Only convert to sparse if extensions.sparseIndex is set.\n \t */\n-\tif (!git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0))\n+\tprepare_repo_settings(istate->repo);\n+\tif (!istate->repo->settings.sparse_index)\n \t\treturn 0;\n \n \tif (!istate->sparse_checkout_patterns) {\n-- \ngitgitgadget\n\n"},{"id":"418717","messageId":"07f34e80609a39f7cf52d21cc8fe0d83ee728fb0.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 19/20] sparse-index: loose integration with cache_tree_verify()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:31:02Z","receivedAt":"2021-03-10T19:32: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\nThe cache_tree_verify() method is run when GIT_TEST_CHECK_CACHE_TREE\nis enabled, which it is by default in the test suite. The logic must\nbe adjusted for the presence of these directory entries.\n\nFor now, leave the test as a simple check for whether the directory\nentry is sparse. Do not go any further until needed.\n\nThis allows us to re-enable GIT_TEST_CHECK_CACHE_TREE in\nt1092-sparse-checkout-compatibility.sh. Further,\np2000-sparse-operations.sh uses the test suite and hence this is enabled\nfor all tests. We need to integrate with it before we run our\nperformance tests with a sparse-index.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache-tree.c                             | 19 +++++++++++++++++++\n t/t1092-sparse-checkout-compatibility.sh |  1 -\n 2 files changed, 19 insertions(+), 1 deletion(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 950a9615db8f..11bf1fcae6e1 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -808,6 +808,19 @@ int cache_tree_matches_traversal(struct cache_tree *root,\n \treturn 0;\n }\n \n+static void verify_one_sparse(struct repository *r,\n+\t\t\t      struct index_state *istate,\n+\t\t\t      struct cache_tree *it,\n+\t\t\t      struct strbuf *path,\n+\t\t\t      int pos)\n+{\n+\tstruct cache_entry *ce = istate->cache[pos];\n+\n+\tif (!S_ISSPARSEDIR(ce->ce_mode))\n+\t\tBUG(\"directory '%s' is present in index, but not sparse\",\n+\t\t    path->buf);\n+}\n+\n static void verify_one(struct repository *r,\n \t\t       struct index_state *istate,\n \t\t       struct cache_tree *it,\n@@ -830,6 +843,12 @@ static void verify_one(struct repository *r,\n \n \tif (path->len) {\n \t\tpos = index_name_pos(istate, path->buf, path->len);\n+\n+\t\tif (pos >= 0) {\n+\t\t\tverify_one_sparse(r, istate, it, path, pos);\n+\t\t\treturn;\n+\t\t}\n+\n \t\tpos = -pos - 1;\n \t} else {\n \t\tpos = 0;\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 9c2bc4d25f66..c2624176c2e0 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -2,7 +2,6 @@\n \n test_description='compare full workdir to sparse workdir'\n \n-GIT_TEST_CHECK_CACHE_TREE=0\n GIT_TEST_SPLIT_INDEX=0\n GIT_TEST_SPARSE_INDEX=\n \n-- \ngitgitgadget\n\n"},{"id":"418719","messageId":"83aac8b7a1ec18d018205117dc2e98a5bb99d4c6.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 10/20] sparse-checkout: hold pattern list in index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:30:53Z","receivedAt":"2021-03-10T19:32: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\nAs we modify the sparse-checkout definition, we perform index operations\non a pattern_list that only exists in-memory. This allows easy backing\nout in case the index update fails.\n\nHowever, if the index write itself cares about the sparse-checkout\npattern set, we need access to that in-memory copy. Place a pointer to\na 'struct pattern_list' in the index so we can access this on-demand.\nThis will be used in the next change which uses the sparse-checkout\ndefinition to filter out directories that are outside the sparse cone.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/sparse-checkout.c | 17 ++++++++++-------\n cache.h                   |  2 ++\n 2 files changed, 12 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex 2306a9ad98e0..e00b82af727b 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -110,6 +110,8 @@ static int update_working_directory(struct pattern_list *pl)\n \tif (is_index_unborn(r->index))\n \t\treturn UPDATE_SPARSITY_SUCCESS;\n \n+\tr->index->sparse_checkout_patterns = pl;\n+\n \tmemset(&o, 0, sizeof(o));\n \to.verbose_update = isatty(2);\n \to.update = 1;\n@@ -138,6 +140,7 @@ static int update_working_directory(struct pattern_list *pl)\n \telse\n \t\trollback_lock_file(&lock_file);\n \n+\tr->index->sparse_checkout_patterns = NULL;\n \treturn result;\n }\n \n@@ -517,19 +520,18 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)\n {\n \tint result;\n \tint changed_config = 0;\n-\tstruct pattern_list pl;\n-\tmemset(&pl, 0, sizeof(pl));\n+\tstruct pattern_list *pl = xcalloc(1, sizeof(*pl));\n \n \tswitch (m) {\n \tcase ADD:\n \t\tif (core_sparse_checkout_cone)\n-\t\t\tadd_patterns_cone_mode(argc, argv, &pl);\n+\t\t\tadd_patterns_cone_mode(argc, argv, pl);\n \t\telse\n-\t\t\tadd_patterns_literal(argc, argv, &pl);\n+\t\t\tadd_patterns_literal(argc, argv, pl);\n \t\tbreak;\n \n \tcase REPLACE:\n-\t\tadd_patterns_from_input(&pl, argc, argv);\n+\t\tadd_patterns_from_input(pl, argc, argv);\n \t\tbreak;\n \t}\n \n@@ -539,12 +541,13 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)\n \t\tchanged_config = 1;\n \t}\n \n-\tresult = write_patterns_and_update(&pl);\n+\tresult = write_patterns_and_update(pl);\n \n \tif (result && changed_config)\n \t\tset_config(MODE_NO_PATTERNS);\n \n-\tclear_pattern_list(&pl);\n+\tclear_pattern_list(pl);\n+\tfree(pl);\n \treturn result;\n }\n \ndiff --git a/cache.h b/cache.h\nindex 1f0b42264606..303411726e10 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -307,6 +307,7 @@ static inline unsigned int canon_mode(unsigned int mode)\n struct split_index;\n struct untracked_cache;\n struct progress;\n+struct pattern_list;\n \n struct index_state {\n \tstruct cache_entry **cache;\n@@ -338,6 +339,7 @@ struct index_state {\n \tstruct mem_pool *ce_mem_pool;\n \tstruct progress *progress;\n \tstruct repository *repo;\n+\tstruct pattern_list *sparse_checkout_patterns;\n };\n \n /* Name hashing */\n-- \ngitgitgadget\n\n"},{"id":"418720","messageId":"42d0da9c5def853dcec0855d586fe3c78de4c9d5.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 17/20] sparse-checkout: disable sparse-index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:31:00Z","receivedAt":"2021-03-10T19:32: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\nWe use 'git sparse-checkout init --cone --sparse-index' to toggle the\nsparse-index feature. It makes sense to also disable it when running\n'git sparse-checkout disable'. This is particularly important because it\nremoves the extensions.sparseIndex config option, allowing other tools\nto use this Git repository again.\n\nThis does mean that 'git sparse-checkout init' will not re-enable the\nsparse-index feature, even if it was previously enabled.\n\nWhile testing this feature, I noticed that the sparse-index was not\nbeing written on the first run, but by a second. This was caught by the\ncall to 'test-tool read-cache --table'. This requires adjusting some\nassignments to core_apply_sparse_checkout and pl.use_cone_patterns in\nthe sparse_checkout_init() logic.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/sparse-checkout.c          | 10 +++++++++-\n t/t1091-sparse-checkout-builtin.sh | 13 +++++++++++++\n 2 files changed, 22 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex ca63e2c64e95..585343fa1972 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -280,6 +280,9 @@ static int set_config(enum sparse_checkout_mode mode)\n \t\t\t\t      \"core.sparseCheckoutCone\",\n \t\t\t\t      mode == MODE_CONE_PATTERNS ? \"true\" : NULL);\n \n+\tif (mode == MODE_NO_PATTERNS)\n+\t\tset_sparse_index_config(the_repository, 0);\n+\n \treturn 0;\n }\n \n@@ -341,10 +344,11 @@ static int sparse_checkout_init(int argc, const char **argv)\n \t\tthe_repository->index->updated_workdir = 1;\n \t}\n \n+\tcore_apply_sparse_checkout = 1;\n+\n \t/* If we already have a sparse-checkout file, use it. */\n \tif (res >= 0) {\n \t\tfree(sparse_filename);\n-\t\tcore_apply_sparse_checkout = 1;\n \t\treturn update_working_directory(NULL);\n \t}\n \n@@ -366,6 +370,7 @@ static int sparse_checkout_init(int argc, const char **argv)\n \tadd_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);\n \tstrbuf_addstr(&pattern, \"!/*/\");\n \tadd_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);\n+\tpl.use_cone_patterns = init_opts.cone_mode;\n \n \treturn write_patterns_and_update(&pl);\n }\n@@ -632,6 +637,9 @@ static int sparse_checkout_disable(int argc, const char **argv)\n \tstrbuf_addstr(&match_all, \"/*\");\n \tadd_pattern(strbuf_detach(&match_all, NULL), empty_base, 0, &pl, 0);\n \n+\tprepare_repo_settings(the_repository);\n+\tthe_repository->settings.sparse_index = 0;\n+\n \tif (update_working_directory(&pl))\n \t\tdie(_(\"error while refreshing working directory\"));\n \ndiff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh\nindex fc64e9ed99f4..ff1ad570a255 100755\n--- a/t/t1091-sparse-checkout-builtin.sh\n+++ b/t/t1091-sparse-checkout-builtin.sh\n@@ -205,6 +205,19 @@ test_expect_success 'sparse-checkout disable' '\n \tcheck_files repo a deep folder1 folder2\n '\n \n+test_expect_success 'sparse-index enabled and disabled' '\n+\tgit -C repo sparse-checkout init --cone --sparse-index &&\n+\ttest_cmp_config -C repo true extensions.sparseIndex &&\n+\ttest-tool -C repo read-cache --table >cache &&\n+\tgrep \" tree \" cache &&\n+\n+\tgit -C repo sparse-checkout disable &&\n+\ttest-tool -C repo read-cache --table >cache &&\n+\t! grep \" tree \" cache &&\n+\tgit -C repo config --list >config &&\n+\t! grep extensions.sparseindex config\n+'\n+\n test_expect_success 'cone mode: init and set' '\n \tgit -C repo sparse-checkout init --cone &&\n \tgit -C repo config --list >config &&\n-- \ngitgitgadget\n\n"},{"id":"418721","messageId":"6bb0976a6295e4a98d050ab26a0545af8c9b5ff1.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 18/20] cache-tree: integrate with sparse directory entries","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:31:01Z","receivedAt":"2021-03-10T19:32: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\nThe cache-tree extension was previously disabled with sparse indexes.\nHowever, the cache-tree is an important performance feature for commands\nlike 'git status' and 'git add'. Integrate it with sparse directory\nentries.\n\nWhen writing a sparse index, completely clear and recalculate the cache\ntree. By starting from scratch, the only integration necessary is to\ncheck if we hit a sparse directory entry and create a leaf of the\ncache-tree that has an entry_count of one and no subtrees.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache-tree.c   | 18 ++++++++++++++++++\n sparse-index.c | 10 +++++++++-\n 2 files changed, 27 insertions(+), 1 deletion(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 5f07a39e501e..950a9615db8f 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -256,6 +256,24 @@ static int update_one(struct cache_tree *it,\n \n \t*skip_count = 0;\n \n+\t/*\n+\t * If the first entry of this region is a sparse directory\n+\t * entry corresponding exactly to 'base', then this cache_tree\n+\t * struct is a \"leaf\" in the data structure, pointing to the\n+\t * tree OID specified in the entry.\n+\t */\n+\tif (entries > 0) {\n+\t\tconst struct cache_entry *ce = cache[0];\n+\n+\t\tif (S_ISSPARSEDIR(ce->ce_mode) &&\n+\t\t    ce->ce_namelen == baselen &&\n+\t\t    !strncmp(ce->name, base, baselen)) {\n+\t\t\tit->entry_count = 1;\n+\t\t\toidcpy(&it->oid, &ce->oid);\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n \tif (0 <= it->entry_count && has_object_file(&it->oid))\n \t\treturn it->entry_count;\n \ndiff --git a/sparse-index.c b/sparse-index.c\nindex 1c84cac255bf..ea603201a323 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -180,7 +180,11 @@ int convert_to_sparse(struct index_state *istate)\n \tistate->cache_nr = convert_to_sparse_rec(istate,\n \t\t\t\t\t\t 0, 0, istate->cache_nr,\n \t\t\t\t\t\t \"\", 0, istate->cache_tree);\n-\tistate->drop_cache_tree = 1;\n+\n+\t/* Clear and recompute the cache-tree */\n+\tcache_tree_free(&istate->cache_tree);\n+\tcache_tree_update(istate, 0);\n+\n \tistate->sparse_index = 1;\n \ttrace2_region_leave(\"index\", \"convert_to_sparse\", istate->repo);\n \treturn 0;\n@@ -278,5 +282,9 @@ void ensure_full_index(struct index_state *istate)\n \n \tfree(full);\n \n+\t/* Clear and recompute the cache-tree */\n+\tcache_tree_free(&istate->cache_tree);\n+\tcache_tree_update(istate, 0);\n+\n \ttrace2_region_leave(\"index\", \"ensure_full_index\", istate->repo);\n }\n-- \ngitgitgadget\n\n"},{"id":"418722","messageId":"41e3b56b9c17d3c30b7a7fe79abfc43e9c45ecee.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 20/20] p2000: add sparse-index repos","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:31:03Z","receivedAt":"2021-03-10T19:32: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\np2000-sparse-operations.sh compares different Git commands in\nrepositories with many files at HEAD but using sparse-checkout to focus\non a small portion of those files.\n\nAdd extra copies of the repository that use the sparse-index format so\nwe can track how that affects the performance of different commands.\n\nAt this point in time, the sparse-index is 100% overhead from the CPU\nfront, and this is measurable in these tests:\n\nTest\n---------------------------------------------------------------\n2000.2: git status (full-index-v3)              0.59(0.51+0.12)\n2000.3: git status (full-index-v4)              0.59(0.52+0.11)\n2000.4: git status (sparse-index-v3)            1.40(1.32+0.12)\n2000.5: git status (sparse-index-v4)            1.41(1.36+0.08)\n2000.6: git add -A (full-index-v3)              2.32(1.97+0.19)\n2000.7: git add -A (full-index-v4)              2.17(1.92+0.14)\n2000.8: git add -A (sparse-index-v3)            2.31(2.21+0.15)\n2000.9: git add -A (sparse-index-v4)            2.30(2.20+0.13)\n2000.10: git add . (full-index-v3)              2.39(2.02+0.20)\n2000.11: git add . (full-index-v4)              2.20(1.94+0.16)\n2000.12: git add . (sparse-index-v3)            2.36(2.27+0.12)\n2000.13: git add . (sparse-index-v4)            2.33(2.21+0.16)\n2000.14: git commit -a -m A (full-index-v3)     2.47(2.12+0.20)\n2000.15: git commit -a -m A (full-index-v4)     2.26(2.00+0.17)\n2000.16: git commit -a -m A (sparse-index-v3)   3.01(2.92+0.16)\n2000.17: git commit -a -m A (sparse-index-v4)   3.01(2.94+0.15)\n\nNote that there is very little difference between the v3 and v4 index\nformats when the sparse-index is enabled. This is primarily due to the\nfact that the relative file sizes are the same, and the command time is\nmostly taken up by parsing tree objects to expand the sparse index into\na full one.\n\nWith the current file layout, the index file sizes are given by this\ntable:\n\n       |  full index | sparse index |\n       +-------------+--------------+\n    v3 |     108 MiB |      1.6 MiB |\n    v4 |      80 MiB |      1.2 MiB |\n\nFuture updates will improve the performance of Git commands when the\nindex is sparse.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/perf/p2000-sparse-operations.sh | 19 ++++++++++++++++++-\n 1 file changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh\nindex 2fbc81b22119..e527316e66d6 100755\n--- a/t/perf/p2000-sparse-operations.sh\n+++ b/t/perf/p2000-sparse-operations.sh\n@@ -60,12 +60,29 @@ test_expect_success 'setup repo and indexes' '\n \t\tgit sparse-checkout set $SPARSE_CONE &&\n \t\tgit config index.version 4 &&\n \t\tgit update-index --index-version=4\n+\t) &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v3 &&\n+\t(\n+\t\tcd sparse-index-v3 &&\n+\t\tgit sparse-checkout init --cone --sparse-index &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 3 &&\n+\t\tgit update-index --index-version=3\n+\t) &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v4 &&\n+\t(\n+\t\tcd sparse-index-v4 &&\n+\t\tgit sparse-checkout init --cone --sparse-index &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 4 &&\n+\t\tgit update-index --index-version=4\n \t)\n '\n \n test_perf_on_all () {\n \tcommand=\"$@\"\n-\tfor repo in full-index-v3 full-index-v4\n+\tfor repo in full-index-v3 full-index-v4 \\\n+\t\t    sparse-index-v3 sparse-index-v4\n \tdo\n \t\ttest_perf \"$command ($repo)\" \"\n \t\t\t(\n-- \ngitgitgadget\n"},{"id":"418724","messageId":"e530ca5f668d2b3d3583133ba3b539a3ef69181d.1615404665.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v2 16/20] sparse-checkout: toggle sparse index from builtin","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-10T19:30:59Z","receivedAt":"2021-03-10T19:32: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\nThe sparse index extension is used to signal that index writes should be\nin sparse mode. This was only updated using GIT_TEST_SPARSE_INDEX=1.\n\nAdd a '--[no-]sparse-index' option to 'git sparse-checkout init' that\nspecifies if the sparse index should be used. It also updates the index\nto use the correct format, either way. Add a warning in the\ndocumentation that the use of a repository extension might reduce\ncompatibility with third-party tools. 'git sparse-checkout init' already\nsets extension.worktreeConfig, which places most sparse-checkout users\noutside of the scope of most third-party tools.\n\nUpdate t1092-sparse-checkout-compatibility.sh to use this CLI instead of\nGIT_TEST_SPARSE_INDEX=1.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-sparse-checkout.txt    | 14 +++++++++\n builtin/sparse-checkout.c                | 17 ++++++++++-\n sparse-index.c                           | 37 +++++++++++++++--------\n sparse-index.h                           |  3 ++\n t/t1092-sparse-checkout-compatibility.sh | 38 +++++++++++-------------\n 5 files changed, 76 insertions(+), 33 deletions(-)\n\ndiff --git a/Documentation/git-sparse-checkout.txt b/Documentation/git-sparse-checkout.txt\nindex a0eeaeb02ee3..4a8343cf7fa4 100644\n--- a/Documentation/git-sparse-checkout.txt\n+++ b/Documentation/git-sparse-checkout.txt\n@@ -45,6 +45,20 @@ To avoid interfering with other worktrees, it first enables the\n When `--cone` is provided, the `core.sparseCheckoutCone` setting is\n also set, allowing for better performance with a limited set of\n patterns (see 'CONE PATTERN SET' below).\n++\n+Use the `--[no-]sparse-index` option to toggle the use of the sparse\n+index format. This reduces the size of the index to be more closely\n+aligned with your sparse-checkout definition. This can have significant\n+performance advantages for commands such as `git status` or `git add`.\n+This feature is still experimental. Some commands might be slower with\n+a sparse index until they are properly integrated with the feature.\n++\n+**WARNING:** Using a sparse index requires modifying the index in a way\n+that is not completely understood by external tools. If you have trouble\n+with this compatibility, then run `git sparse-checkout sparse-index disable`\n+to rewrite your index to not be sparse. Older versions of Git will not\n+understand the `sparseIndex` repository extension and may fail to interact\n+with your repository until it is disabled.\n \n 'set'::\n \tWrite a set of patterns to the sparse-checkout file, as given as\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex e00b82af727b..ca63e2c64e95 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -14,6 +14,7 @@\n #include \"unpack-trees.h\"\n #include \"wt-status.h\"\n #include \"quote.h\"\n+#include \"sparse-index.h\"\n \n static const char *empty_base = \"\";\n \n@@ -283,12 +284,13 @@ static int set_config(enum sparse_checkout_mode mode)\n }\n \n static char const * const builtin_sparse_checkout_init_usage[] = {\n-\tN_(\"git sparse-checkout init [--cone]\"),\n+\tN_(\"git sparse-checkout init [--cone] [--[no-]sparse-index]\"),\n \tNULL\n };\n \n static struct sparse_checkout_init_opts {\n \tint cone_mode;\n+\tint sparse_index;\n } init_opts;\n \n static int sparse_checkout_init(int argc, const char **argv)\n@@ -303,11 +305,15 @@ static int sparse_checkout_init(int argc, const char **argv)\n \tstatic struct option builtin_sparse_checkout_init_options[] = {\n \t\tOPT_BOOL(0, \"cone\", &init_opts.cone_mode,\n \t\t\t N_(\"initialize the sparse-checkout in cone mode\")),\n+\t\tOPT_BOOL(0, \"sparse-index\", &init_opts.sparse_index,\n+\t\t\t N_(\"toggle the use of a sparse index\")),\n \t\tOPT_END(),\n \t};\n \n \trepo_read_index(the_repository);\n \n+\tinit_opts.sparse_index = -1;\n+\n \targc = parse_options(argc, argv, NULL,\n \t\t\t     builtin_sparse_checkout_init_options,\n \t\t\t     builtin_sparse_checkout_init_usage, 0);\n@@ -326,6 +332,15 @@ static int sparse_checkout_init(int argc, const char **argv)\n \tsparse_filename = get_sparse_checkout_filename();\n \tres = add_patterns_from_file_to_list(sparse_filename, \"\", 0, &pl, NULL);\n \n+\tif (init_opts.sparse_index >= 0) {\n+\t\tif (set_sparse_index_config(the_repository, init_opts.sparse_index) < 0)\n+\t\t\tdie(_(\"failed to modify sparse-index config\"));\n+\n+\t\t/* force an index rewrite */\n+\t\trepo_read_index(the_repository);\n+\t\tthe_repository->index->updated_workdir = 1;\n+\t}\n+\n \t/* If we already have a sparse-checkout file, use it. */\n \tif (res >= 0) {\n \t\tfree(sparse_filename);\ndiff --git a/sparse-index.c b/sparse-index.c\nindex b9c14ef7ab50..1c84cac255bf 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -104,23 +104,37 @@ static int convert_to_sparse_rec(struct index_state *istate,\n \n static int enable_sparse_index(struct repository *repo)\n {\n-\tconst char *config_path = repo_git_path(repo, \"config.worktree\");\n+\tint res;\n \n \tif (upgrade_repository_format(1) < 0) {\n \t\twarning(_(\"unable to upgrade repository format to enable sparse-index\"));\n \t\treturn -1;\n \t}\n-\tgit_config_set_in_file_gently(config_path,\n-\t\t\t\t      \"extensions.sparseIndex\",\n-\t\t\t\t      \"true\");\n+\tres = git_config_set_gently(\"extensions.sparseindex\", \"true\");\n \n \tprepare_repo_settings(repo);\n \trepo->settings.sparse_index = 1;\n-\treturn 0;\n+\treturn res;\n+}\n+\n+int set_sparse_index_config(struct repository *repo, int enable)\n+{\n+\tint res;\n+\n+\tif (enable)\n+\t\treturn enable_sparse_index(repo);\n+\n+\t/* Don't downgrade repository format, just remove the extension. */\n+\tres = git_config_set_gently(\"extensions.sparseindex\", NULL);\n+\n+\tprepare_repo_settings(repo);\n+\trepo->settings.sparse_index = 0;\n+\treturn res;\n }\n \n int convert_to_sparse(struct index_state *istate)\n {\n+\tint test_env;\n \tif (istate->split_index || istate->sparse_index ||\n \t    !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n \t\treturn 0;\n@@ -129,14 +143,13 @@ int convert_to_sparse(struct index_state *istate)\n \t\tistate->repo = the_repository;\n \n \t/*\n-\t * The GIT_TEST_SPARSE_INDEX environment variable triggers the\n-\t * extensions.sparseIndex config variable to be on.\n+\t * If GIT_TEST_SPARSE_INDEX=1, then trigger extensions.sparseIndex\n+\t * to be fully enabled. If GIT_TEST_SPARSE_INDEX=0 (set explicitly),\n+\t * then purposefully disable the setting.\n \t */\n-\tif (git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0)) {\n-\t\tint err = enable_sparse_index(istate->repo);\n-\t\tif (err < 0)\n-\t\t\treturn err;\n-\t}\n+\ttest_env = git_env_bool(\"GIT_TEST_SPARSE_INDEX\", -1);\n+\tif (test_env >= 0)\n+\t\tset_sparse_index_config(istate->repo, test_env);\n \n \t/*\n \t * Only convert to sparse if extensions.sparseIndex is set.\ndiff --git a/sparse-index.h b/sparse-index.h\nindex 64380e121d80..39dcc859735e 100644\n--- a/sparse-index.h\n+++ b/sparse-index.h\n@@ -5,4 +5,7 @@ struct index_state;\n void ensure_full_index(struct index_state *istate);\n int convert_to_sparse(struct index_state *istate);\n \n+struct repository;\n+int set_sparse_index_config(struct repository *repo, int enable);\n+\n #endif\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex bfc9e28ef0e1..9c2bc4d25f66 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -4,6 +4,7 @@ test_description='compare full workdir to sparse workdir'\n \n GIT_TEST_CHECK_CACHE_TREE=0\n GIT_TEST_SPLIT_INDEX=0\n+GIT_TEST_SPARSE_INDEX=\n \n . ./test-lib.sh\n \n@@ -98,25 +99,26 @@ init_repos () {\n \t# initialize sparse-checkout definitions\n \tgit -C sparse-checkout sparse-checkout init --cone &&\n \tgit -C sparse-checkout sparse-checkout set deep &&\n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout init --cone &&\n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep\n+\tgit -C sparse-index sparse-checkout init --cone --sparse-index &&\n+\ttest_cmp_config -C sparse-index true extensions.sparseindex &&\n+\tgit -C sparse-index sparse-checkout set deep\n }\n \n run_on_sparse () {\n \t(\n \t\tcd sparse-checkout &&\n-\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n+\t\t\"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n \t) &&\n \t(\n \t\tcd sparse-index &&\n-\t\tGIT_TEST_SPARSE_INDEX=1 \"$@\" >../sparse-index-out 2>../sparse-index-err\n+\t\t\"$@\" >../sparse-index-out 2>../sparse-index-err\n \t)\n }\n \n run_on_all () {\n \t(\n \t\tcd full-checkout &&\n-\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../full-checkout-out 2>../full-checkout-err\n+\t\t\"$@\" >../full-checkout-out 2>../full-checkout-err\n \t) &&\n \trun_on_sparse \"$@\"\n }\n@@ -146,7 +148,7 @@ test_expect_success 'sparse-index contents' '\n \t\t\t|| return 1\n \tdone &&\n \n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set folder1 &&\n+\tgit -C sparse-index sparse-checkout set folder1 &&\n \n \ttest-tool -C sparse-index read-cache --table >cache &&\n \tfor dir in deep folder2 x\n@@ -156,7 +158,7 @@ test_expect_success 'sparse-index contents' '\n \t\t\t|| return 1\n \tdone &&\n \n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep/deeper1 &&\n+\tgit -C sparse-index sparse-checkout set deep/deeper1 &&\n \n \ttest-tool -C sparse-index read-cache --table >cache &&\n \tfor dir in deep/deeper2 folder1 folder2 x\n@@ -394,19 +396,15 @@ test_expect_success 'submodule handling' '\n test_expect_success 'sparse-index is expanded and converted back' '\n \tinit_repos &&\n \n-\t(\n-\t\tGIT_TEST_SPARSE_INDEX=1 &&\n-\t\texport GIT_TEST_SPARSE_INDEX &&\n-\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n-\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" reset --hard &&\n-\t\ttest_region index convert_to_sparse trace2.txt &&\n-\t\ttest_region index ensure_full_index trace2.txt &&\n-\n-\t\trm trace2.txt &&\n-\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n-\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" status -uno &&\n-\t\ttest_region index ensure_full_index trace2.txt\n-\t)\n+\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\tgit -C sparse-index -c core.fsmonitor=\"\" reset --hard &&\n+\ttest_region index convert_to_sparse trace2.txt &&\n+\ttest_region index ensure_full_index trace2.txt &&\n+\n+\trm trace2.txt &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\tgit -C sparse-index -c core.fsmonitor=\"\" status -uno &&\n+\ttest_region index ensure_full_index trace2.txt\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"418744","messageId":"CABPp-BGwyyOHJbPtJ2qzsCbOyag5aj1UfRfeDeDj7z3W6zwkow@mail.gmail.com","threadId":"55195","inReplyTo":"2fe413fdac808807e2058caeee6ce86c54a678c0.1615404664.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 01/20] sparse-index: design doc and format update","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-03-10T22:19:34Z","receivedAt":"2021-03-10T22:20:24Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Mar 10, 2021 at 11:31 AM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> This begins a long effort to update the index format to allow sparse\n> directory entries. This should result in a significant improvement to\n> Git commands when HEAD contains millions of files, but the user has\n> selected many fewer files to keep in their sparse-checkout definition.\n>\n> Currently, the index format is only updated in the presence of\n> extensions.sparseIndex instead of increasing a file format version\n> number. This is temporary, and index v5 is part of the plan for future\n> work in this area.\n>\n> The design document details many of the reasons for embarking on this\n> work, and also the plan for completing it safely.\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  Documentation/technical/index-format.txt |   7 +\n>  Documentation/technical/sparse-index.txt | 173 +++++++++++++++++++++++\n>  2 files changed, 180 insertions(+)\n>  create mode 100644 Documentation/technical/sparse-index.txt\n>\n> diff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt\n> index b633482b1bdf..387126582556 100644\n> --- a/Documentation/technical/index-format.txt\n> +++ b/Documentation/technical/index-format.txt\n> @@ -44,6 +44,13 @@ Git index format\n>    localization, no special casing of directory separator '/'). Entries\n>    with the same name are sorted by their stage field.\n>\n> +  An index entry typically represents a file. However, if sparse-checkout\n> +  is enabled in cone mode (`core.sparseCheckoutCone` is enabled) and the\n> +  `extensions.sparseIndex` extension is enabled, then the index may\n> +  contain entries for directories outside of the sparse-checkout definition.\n> +  These entries have mode `0040000`, include the `SKIP_WORKTREE` bit, and\n> +  the path ends in a directory separator.\n> +\n>    32-bit ctime seconds, the last time a file's metadata changed\n>      this is stat(2) data\n>\n> diff --git a/Documentation/technical/sparse-index.txt b/Documentation/technical/sparse-index.txt\n> new file mode 100644\n> index 000000000000..787a2a0b3b81\n> --- /dev/null\n> +++ b/Documentation/technical/sparse-index.txt\n> @@ -0,0 +1,173 @@\n> +Git Sparse-Index Design Document\n> +================================\n> +\n> +The sparse-checkout feature allows users to focus a working directory on\n> +a subset of the files at HEAD. The cone mode patterns, enabled by\n> +`core.sparseCheckoutCone`, allow for very fast pattern matching to\n> +discover which files at HEAD belong in the sparse-checkout cone.\n> +\n> +Three important scale dimensions for a Git worktree are:\n> +\n> +* `HEAD`: How many files are present at `HEAD`?\n> +\n> +* Populated: How many files are within the sparse-checkout cone.\n> +\n> +* Modified: How many files has the user modified in the working directory?\n> +\n> +We will use big-O notation -- O(X) -- to denote how expensive certain\n> +operations are in terms of these dimensions.\n> +\n> +These dimensions are ordered by their magnitude: users (typically) modify\n> +fewer files than are populated, and we can only populate files at `HEAD`.\n> +These dimensions are also ordered by how expensive they are per item: it\n> +is expensive to detect a modified file than it is to write one that we\n> +know must be populated; changing `HEAD` only really requires updating the\n> +index.\n> +\n> +Problems occur if there is an extreme imbalance in these dimensions. For\n> +example, if `HEAD` contains millions of paths but the populated set has\n> +only tens of thousands, then commands like `git status` and `git add` can\n> +be dominated by operations that require O(`HEAD`) operations instead of\n> +O(Populated). Primarily, the cost is in parsing and rewriting the index,\n> +which is filled primarily with files at `HEAD` that are marked with the\n> +`SKIP_WORKTREE` bit.\n> +\n> +The sparse-index intends to take these commands that read and modify the\n> +index from O(`HEAD`) to O(Populated). To do this, we need to modify the\n> +index format in a significant way: add \"sparse directory\" entries.\n> +\n> +With cone mode patterns, it is possible to detect when an entire\n> +directory will have its contents outside of the sparse-checkout definition.\n> +Instead of listing all of the files it contains as individual entries, a\n> +sparse-index contains an entry with the directory name, referencing the\n> +object ID of the tree at `HEAD` and marked with the `SKIP_WORKTREE` bit.\n> +If we need to discover the details for paths within that directory, we\n> +can parse trees to find that list.\n> +\n> +At time of writing, sparse-directory entries violate expectations about the\n> +index format and its in-memory data structure. There are many consumers in\n> +the codebase that expect to iterate through all of the index entries and\n> +see only files. In addition, they expect to see all files at `HEAD`. One\n> +way to handle this is to parse trees to replace a sparse-directory entry\n> +with all of the files within that tree as the index is loaded. However,\n> +parsing trees is slower than parsing the index format, so that is a slower\n> +operation than if we left the index alone.\n> +\n> +The implementation plan below follows four phases to slowly integrate with\n> +the sparse-index. The intention is to incrementally update Git commands to\n> +interact safely with the sparse-index without significant slowdowns. This\n> +may not always be possible, but the hope is that the primary commands that\n> +users need in their daily work are dramatically improved.\n> +\n> +Phase I: Format and initial speedups\n> +------------------------------------\n> +\n> +During this phase, Git learns to enable the sparse-index and safely parse\n> +one. Protections are put in place so that every consumer of the in-memory\n> +data structure can operate with its current assumption of every file at\n> +`HEAD`.\n> +\n> +At first, every index parse will expand the sparse-directory entries into\n> +the full list of paths at `HEAD`. This will be slower in all cases. The\n> +only noticable change in behavior will be that the serialized index file\n> +contains sparse-directory entries.\n> +\n> +To start, we use a new repository extension, `extensions.sparseIndex`, to\n> +allow inserting sparse-directory entries into indexes with file format\n> +versions 2, 3, and 4. This prevents Git versions that do not understand\n> +the sparse-index from operating on one, but it also prevents other\n> +operations that do not use the index at all. A new format, index v5, will\n> +be introduced that includes sparse-directory entries by default. It might\n> +also introduce other features that have been considered for improving the\n> +index, as well.\n> +\n> +Next, consumers of the index will be guarded against operating on a\n> +sparse-index by inserting calls to `ensure_full_index()` or\n> +`expand_index_to_path()`. After these guards are in place, we can begin\n> +leaving sparse-directory entries in the in-memory index structure.\n> +\n> +Even after inserting these guards, we will keep expanding sparse-indexes\n> +for most Git commands using the `command_requires_full_index` repository\n> +setting. This setting will be on by default and disabled one builtin at a\n> +time until we have sufficient confidence that all of the index operations\n> +are properly guarded.\n> +\n> +To complete this phase, the commands `git status` and `git add` will be\n> +integrated with the sparse-index so that they operate with O(Populated)\n> +performance. They will be carefully tested for operations within and\n> +outside the sparse-checkout definition.\n> +\n> +Phase II: Careful integrations\n> +------------------------------\n> +\n> +This phase focuses on ensuring that all index extensions and APIs work\n> +well with a sparse-index. This requires significant increases to our test\n> +coverage, especially for operations that interact with the working\n> +directory outside of the sparse-checkout definition. Some of these\n> +behaviors may not be the desirable ones, such as some tests already\n> +marked for failure in `t1092-sparse-checkout-compatibility.sh`.\n> +\n> +The index extensions that may require special integrations are:\n> +\n> +* FS Monitor\n> +* Untracked cache\n> +\n> +While integrating with these features, we should look for patterns that\n> +might lead to better APIs for interacting with the index. Coalescing\n> +common usage patterns into an API call can reduce the number of places\n> +where sparse-directories need to be handled carefully.\n> +\n> +Phase III: Important command speedups\n> +-------------------------------------\n> +\n> +At this point, the patterns for testing and implementing sparse-directory\n> +logic should be relatively stable. This phase focuses on updating some of\n> +the most common builtins that use the index to operate as O(Populated).\n> +Here is a potential list of commands that could be valuable to integrate\n> +at this point:\n> +\n> +* `git commit`\n> +* `git checkout`\n> +* `git merge`\n> +* `git rebase`\n> +\n> +Hopefully, commands such as `git merge` and `git rebase` can benefit\n> +instead from merge algorithms that do not use the index as a data\n> +structure, such as the merge-ORT strategy. As these topics mature, we\n> +may enalbe the ORT strategy by default for repositories using the\n\ns/enalbe/enable/\n\n> +sparse-index feature.\n> +\n> +Along with `git status` and `git add`, these commands cover the majority\n> +of users' interactions with the working directory. In addition, we can\n> +integrate with these commands:\n> +\n> +* `git grep`\n> +* `git rm`\n> +\n> +These have been proposed as some whose behavior could change when in a\n> +repo with a sparse-checkout definition. It would be good to include this\n> +behavior automatically when using a sparse-index. Some clarity is needed\n> +to make the behavior switch clear to the user.\n> +\n> +This phase is the first where parallel work might be possible without too\n> +much conflicts between topics.\n> +\n> +Phase IV: The long tail\n> +-----------------------\n> +\n> +This last phase is less a \"phase\" and more \"the new normal\" after all of\n> +the previous work.\n> +\n> +To start, the `command_requires_full_index` option could be removed in\n> +favor of expanding only when hitting an API guard.\n> +\n> +There are many Git commands that could use special attention to operate as\n> +O(Populated), while some might be so rare that it is acceptable to leave\n> +them with additional overhead when a sparse-index is present.\n> +\n> +Here are some commands that might be useful to update:\n> +\n> +* `git sparse-checkout set`\n> +* `git am`\n> +* `git clean`\n> +* `git stash`\n> --\n> gitgitgadget\n>\n"},{"id":"418749","messageId":"CABPp-BG6eSpjHVU5XShi17MOuPku=U388rL1=JXXF88M4ymFHA@mail.gmail.com","threadId":"55195","inReplyTo":"eac2db5efc2297cffa9aba227e964bcc952d04ff.1615404665.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 06/20] t1092: compare sparse-checkout to sparse-index","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-03-10T23:04:57Z","receivedAt":"2021-03-10T23:06:09Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Mar 10, 2021 at 11:31 AM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> Add a new 'sparse-index' repo alongside the 'full-checkout' and\n> 'sparse-checkout' repos in t1092-sparse-checkout-compatibility.sh. Also\n> add run_on_sparse and test_sparse_match helpers. These helpers will be\n> used when the sparse index is implemented.\n>\n> Add GIT_TEST_SPARSE_INDEX environment variable to enable the\n> sparse-index by default. This will be intended to use across the entire\n> test suite, except that it will only affect cases where the\n> sparse-checkout feature is enabled.\n\nThis last sentence was a bit awkward to read.  \"will be intended to\nuse\" -> \"is intended to be used\"?\n\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  t/README                                 |  3 +++\n>  t/t1092-sparse-checkout-compatibility.sh | 24 ++++++++++++++++++++----\n>  2 files changed, 23 insertions(+), 4 deletions(-)\n>\n> diff --git a/t/README b/t/README\n> index 593d4a4e270c..b98bc563aab5 100644\n> --- a/t/README\n> +++ b/t/README\n> @@ -439,6 +439,9 @@ and \"sha256\".\n>  GIT_TEST_WRITE_REV_INDEX=<boolean>, when true enables the\n>  'pack.writeReverseIndex' setting.\n>\n> +GIT_TEST_SPARSE_INDEX=<boolean>, when true enables index writes to use the\n> +sparse-index format by default.\n> +\n>  Naming Tests\n>  ------------\n>\n> diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\n> index 3725d3997e70..71d6f9e4c014 100755\n> --- a/t/t1092-sparse-checkout-compatibility.sh\n> +++ b/t/t1092-sparse-checkout-compatibility.sh\n> @@ -7,6 +7,7 @@ test_description='compare full workdir to sparse workdir'\n>  test_expect_success 'setup' '\n>         git init initial-repo &&\n>         (\n> +               GIT_TEST_SPARSE_INDEX=0 &&\n>                 cd initial-repo &&\n>                 echo a >a &&\n>                 echo \"after deep\" >e &&\n> @@ -87,23 +88,32 @@ init_repos () {\n>\n>         cp -r initial-repo sparse-checkout &&\n>         git -C sparse-checkout reset --hard &&\n> -       git -C sparse-checkout sparse-checkout init --cone &&\n> +\n> +       cp -r initial-repo sparse-index &&\n> +       git -C sparse-index reset --hard &&\n>\n>         # initialize sparse-checkout definitions\n> -       git -C sparse-checkout sparse-checkout set deep\n> +       git -C sparse-checkout sparse-checkout init --cone &&\n> +       git -C sparse-checkout sparse-checkout set deep &&\n> +       GIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout init --cone &&\n> +       GIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep\n>  }\n>\n>  run_on_sparse () {\n>         (\n>                 cd sparse-checkout &&\n> -               \"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n> +               GIT_TEST_SPARSE_INDEX=0 \"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n> +       ) &&\n> +       (\n> +               cd sparse-index &&\n> +               GIT_TEST_SPARSE_INDEX=1 \"$@\" >../sparse-index-out 2>../sparse-index-err\n>         )\n>  }\n>\n>  run_on_all () {\n>         (\n>                 cd full-checkout &&\n> -               \"$@\" >../full-checkout-out 2>../full-checkout-err\n> +               GIT_TEST_SPARSE_INDEX=0 \"$@\" >../full-checkout-out 2>../full-checkout-err\n>         ) &&\n>         run_on_sparse \"$@\"\n>  }\n> @@ -114,6 +124,12 @@ test_all_match () {\n>         test_cmp full-checkout-err sparse-checkout-err\n>  }\n>\n> +test_sparse_match () {\n> +       run_on_sparse $* &&\n\nShould this be\n   run_on_sparse \"$@\"\nin order to allow arguments with spaces?\n\n> +       test_cmp sparse-checkout-out sparse-index-out &&\n> +       test_cmp sparse-checkout-err sparse-index-err\n> +}\n> +\n>  test_expect_success 'status with options' '\n>         init_repos &&\n>         test_all_match git status --porcelain=v2 &&\n> --\n> gitgitgadget\n>\n"},{"id":"418754","messageId":"CABPp-BGUqVC7JV7nnNDTt++rHCxUF+98T0OLq5dFf0mOKkp8fw@mail.gmail.com","threadId":"55195","inReplyTo":"f6db0c27a2854b16a3402e4133629a1f479e113d.1615404665.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 11/20] sparse-index: convert from full to sparse","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-03-10T23:44:13Z","receivedAt":"2021-03-10T23:45:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Mar 10, 2021 at 11:31 AM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> If we have a full index, then we can convert it to a sparse index by\n> replacing directories outside of the sparse cone with sparse directory\n> entries. The convert_to_sparse() method does this, when the situation is\n> appropriate.\n>\n> For now, we avoid converting the index to a sparse index if:\n>\n>  1. the index is split.\n>  2. the index is already sparse.\n>  3. sparse-checkout is disabled.\n>  4. sparse-checkout does not use cone mode.\n>\n> Finally, we currently limit the conversion to when the\n> GIT_TEST_SPARSE_INDEX environment variable is enabled. A mode using Git\n> config will be added in a later change.\n>\n> The trickiest thing about this conversion is that we might not be able\n> to mark a directory as a sparse directory just because it is outside the\n> sparse cone. There might be unmerged files within that directory, so we\n> need to look for those. Also, if there is some strange reason why a file\n> is not marked with CE_SKIP_WORKTREE, then we should give up on\n> converting that directory. There is still hope that some of its\n> subdirectories might be able to convert to sparse, so we keep looking\n> deeper.\n>\n> The conversion process is assisted by the cache-tree extension. This is\n> calculated from the full index if it does not already exist. We then\n> abandon the cache-tree as it no longer applies to the newly-sparse\n> index. Thus, this cache-tree will be recalculated in every\n> sparse-full-sparse round-trip until we integrate the cache-tree\n> extension with the sparse index.\n>\n> Some Git commands use the index after writing it. For example, 'git add'\n> will update the index, then write it to disk, then read its entries to\n> report information. To keep the in-memory index in a full state after\n> writing, we re-expand it to a full one after the write. This is wasteful\n> for commands that only write the index and do not read from it again,\n> but that is only the case until we make those commands \"sparse aware.\"\n>\n> We can compare the behavior of the sparse-index in\n> t1092-sparse-checkout-compability.sh by using GIT_TEST_SPARSE_INDEX=1\n> when operating on the 'sparse-index' repo. We can also compare the two\n> sparse repos directly, such as comparing their indexes (when expanded to\n> full in the case of the 'sparse-index' repo). We also verify that the\n> index is actually populated with sparse directory entries.\n>\n> The 'checkout and reset (mixed)' test is marked for failure when\n> comparing a sparse repo to a full repo, but we can compare the two\n> sparse-checkout cases directly to ensure that we are not changing the\n> behavior when using a sparse index.\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  cache-tree.c                             |   3 +\n>  cache.h                                  |   2 +\n>  read-cache.c                             |  26 ++++-\n>  sparse-index.c                           | 139 +++++++++++++++++++++++\n>  sparse-index.h                           |   1 +\n>  t/t1092-sparse-checkout-compatibility.sh |  61 +++++++++-\n>  6 files changed, 227 insertions(+), 5 deletions(-)\n>\n> diff --git a/cache-tree.c b/cache-tree.c\n> index 2fb483d3c083..5f07a39e501e 100644\n> --- a/cache-tree.c\n> +++ b/cache-tree.c\n> @@ -6,6 +6,7 @@\n>  #include \"object-store.h\"\n>  #include \"replace-object.h\"\n>  #include \"promisor-remote.h\"\n> +#include \"sparse-index.h\"\n>\n>  #ifndef DEBUG_CACHE_TREE\n>  #define DEBUG_CACHE_TREE 0\n> @@ -442,6 +443,8 @@ int cache_tree_update(struct index_state *istate, int flags)\n>         if (i)\n>                 return i;\n>\n> +       ensure_full_index(istate);\n> +\n>         if (!istate->cache_tree)\n>                 istate->cache_tree = cache_tree();\n>\n> diff --git a/cache.h b/cache.h\n> index 303411726e10..9217d405b9b8 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -251,6 +251,8 @@ static inline unsigned int create_ce_mode(unsigned int mode)\n>  {\n>         if (S_ISLNK(mode))\n>                 return S_IFLNK;\n> +       if (mode == S_IFDIR)\n> +               return S_IFDIR;\n>         if (S_ISDIR(mode) || S_ISGITLINK(mode))\n>                 return S_IFGITLINK;\n>         return S_IFREG | ce_permissions(mode);\n> diff --git a/read-cache.c b/read-cache.c\n> index 97dbf2434f30..92126b9d23c9 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 \"sparse-index.h\"\n>\n>  /* Mask for the name length in ce_flags in the on-disk index */\n>\n> @@ -1002,8 +1003,14 @@ int verify_path(const char *path, unsigned mode)\n>\n>                         c = *path++;\n>                         if ((c == '.' && !verify_dotfile(path, mode)) ||\n> -                           is_dir_sep(c) || c == '\\0')\n> +                           is_dir_sep(c))\n>                                 return 0;\n> +                       /*\n> +                        * allow terminating directory separators for\n> +                        * sparse directory entries.\n> +                        */\n> +                       if (c == '\\0')\n> +                               return S_ISDIR(mode);\n>                 } else if (c == '\\\\' && protect_ntfs) {\n>                         if (is_ntfs_dotgit(path))\n>                                 return 0;\n> @@ -3061,6 +3068,14 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n>                                  unsigned flags)\n>  {\n>         int ret;\n> +       int was_full = !istate->sparse_index;\n> +\n> +       ret = convert_to_sparse(istate);\n> +\n> +       if (ret) {\n> +               warning(_(\"failed to convert to a sparse-index\"));\n> +               return ret;\n> +       }\n>\n>         /*\n>          * TODO trace2: replace \"the_repository\" with the actual repo instance\n> @@ -3072,6 +3087,9 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n>         trace2_region_leave_printf(\"index\", \"do_write_index\", the_repository,\n>                                    \"%s\", get_lock_file_path(lock));\n>\n> +       if (was_full)\n> +               ensure_full_index(istate);\n> +\n>         if (ret)\n>                 return ret;\n>         if (flags & COMMIT_LOCK)\n> @@ -3162,9 +3180,10 @@ static int write_shared_index(struct index_state *istate,\n>                               struct tempfile **temp)\n>  {\n>         struct split_index *si = istate->split_index;\n> -       int ret;\n> +       int ret, was_full = !istate->sparse_index;\n>\n>         move_cache_to_base_index(istate);\n> +       convert_to_sparse(istate);\n>\n>         trace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n>                                    the_repository, \"%s\", get_tempfile_path(*temp));\n> @@ -3172,6 +3191,9 @@ static int write_shared_index(struct index_state *istate,\n>         trace2_region_leave_printf(\"index\", \"shared/do_write_index\",\n>                                    the_repository, \"%s\", get_tempfile_path(*temp));\n>\n> +       if (was_full)\n> +               ensure_full_index(istate);\n> +\n>         if (ret)\n>                 return ret;\n>         ret = adjust_shared_perm(get_tempfile_path(*temp));\n> diff --git a/sparse-index.c b/sparse-index.c\n> index 316cb949b74b..5eb561259bb1 100644\n> --- a/sparse-index.c\n> +++ b/sparse-index.c\n> @@ -4,6 +4,145 @@\n>  #include \"tree.h\"\n>  #include \"pathspec.h\"\n>  #include \"trace2.h\"\n> +#include \"cache-tree.h\"\n> +#include \"config.h\"\n> +#include \"dir.h\"\n> +#include \"fsmonitor.h\"\n> +\n> +static struct cache_entry *construct_sparse_dir_entry(\n> +                               struct index_state *istate,\n> +                               const char *sparse_dir,\n> +                               struct cache_tree *tree)\n> +{\n> +       struct cache_entry *de;\n> +\n> +       de = make_cache_entry(istate, S_IFDIR, &tree->oid, sparse_dir, 0, 0);\n> +\n> +       de->ce_flags |= CE_SKIP_WORKTREE;\n> +       return de;\n> +}\n> +\n> +/*\n> + * Returns the number of entries \"inserted\" into the index.\n> + */\n> +static int convert_to_sparse_rec(struct index_state *istate,\n> +                                int num_converted,\n> +                                int start, int end,\n> +                                const char *ct_path, size_t ct_pathlen,\n> +                                struct cache_tree *ct)\n> +{\n> +       int i, can_convert = 1;\n> +       int start_converted = num_converted;\n> +       enum pattern_match_result match;\n> +       int dtype;\n> +       struct strbuf child_path = STRBUF_INIT;\n> +       struct pattern_list *pl = istate->sparse_checkout_patterns;\n> +\n> +       /*\n> +        * Is the current path outside of the sparse cone?\n> +        * Then check if the region can be replaced by a sparse\n> +        * directory entry (everything is sparse and merged).\n> +        */\n> +       match = path_matches_pattern_list(ct_path, ct_pathlen,\n> +                                         NULL, &dtype, pl, istate);\n> +       if (match != NOT_MATCHED)\n> +               can_convert = 0;\n> +\n> +       for (i = start; can_convert && i < end; i++) {\n> +               struct cache_entry *ce = istate->cache[i];\n> +\n> +               if (ce_stage(ce) ||\n> +                   !(ce->ce_flags & CE_SKIP_WORKTREE))\n> +                       can_convert = 0;\n> +       }\n> +\n> +       if (can_convert) {\n> +               struct cache_entry *se;\n> +               se = construct_sparse_dir_entry(istate, ct_path, ct);\n> +\n> +               istate->cache[num_converted++] = se;\n> +               return 1;\n> +       }\n> +\n> +       for (i = start; i < end; ) {\n> +               int count, span, pos = -1;\n> +               const char *base, *slash;\n> +               struct cache_entry *ce = istate->cache[i];\n> +\n> +               /*\n> +                * Detect if this is a normal entry outside of any subtree\n> +                * entry.\n> +                */\n> +               base = ce->name + ct_pathlen;\n> +               slash = strchr(base, '/');\n> +\n> +               if (slash)\n> +                       pos = cache_tree_subtree_pos(ct, base, slash - base);\n> +\n> +               if (pos < 0) {\n> +                       istate->cache[num_converted++] = ce;\n> +                       i++;\n> +                       continue;\n> +               }\n> +\n> +               strbuf_setlen(&child_path, 0);\n> +               strbuf_add(&child_path, ce->name, slash - ce->name + 1);\n> +\n> +               span = ct->down[pos]->cache_tree->entry_count;\n> +               count = convert_to_sparse_rec(istate,\n> +                                             num_converted, i, i + span,\n> +                                             child_path.buf, child_path.len,\n> +                                             ct->down[pos]->cache_tree);\n> +               num_converted += count;\n> +               i += span;\n> +       }\n> +\n> +       strbuf_release(&child_path);\n> +       return num_converted - start_converted;\n> +}\n> +\n> +int convert_to_sparse(struct index_state *istate)\n> +{\n> +       if (istate->split_index || istate->sparse_index ||\n> +           !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n> +               return 0;\n> +\n> +       /*\n> +        * For now, only create a sparse index with the\n> +        * GIT_TEST_SPARSE_INDEX environment variable. We will relax\n> +        * this once we have a proper way to opt-in (and later still,\n> +        * opt-out).\n> +        */\n> +       if (!git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0))\n> +               return 0;\n> +\n> +       if (!istate->sparse_checkout_patterns) {\n> +               istate->sparse_checkout_patterns = xcalloc(1, sizeof(struct pattern_list));\n> +               if (get_sparse_checkout_patterns(istate->sparse_checkout_patterns) < 0)\n> +                       return 0;\n> +       }\n> +\n> +       if (!istate->sparse_checkout_patterns->use_cone_patterns) {\n> +               warning(_(\"attempting to use sparse-index without cone mode\"));\n> +               return -1;\n> +       }\n> +\n> +       if (cache_tree_update(istate, 0)) {\n> +               warning(_(\"unable to update cache-tree, staying full\"));\n> +               return -1;\n> +       }\n> +\n> +       remove_fsmonitor(istate);\n> +\n> +       trace2_region_enter(\"index\", \"convert_to_sparse\", istate->repo);\n> +       istate->cache_nr = convert_to_sparse_rec(istate,\n> +                                                0, 0, istate->cache_nr,\n> +                                                \"\", 0, istate->cache_tree);\n> +       istate->drop_cache_tree = 1;\n> +       istate->sparse_index = 1;\n> +       trace2_region_leave(\"index\", \"convert_to_sparse\", istate->repo);\n> +       return 0;\n> +}\n>\n>  static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n>  {\n> diff --git a/sparse-index.h b/sparse-index.h\n> index 09a20d036c46..64380e121d80 100644\n> --- a/sparse-index.h\n> +++ b/sparse-index.h\n> @@ -3,5 +3,6 @@\n>\n>  struct index_state;\n>  void ensure_full_index(struct index_state *istate);\n> +int convert_to_sparse(struct index_state *istate);\n>\n>  #endif\n> diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\n> index 4d789fe86b9d..ca87033d30b0 100755\n> --- a/t/t1092-sparse-checkout-compatibility.sh\n> +++ b/t/t1092-sparse-checkout-compatibility.sh\n> @@ -2,6 +2,9 @@\n>\n>  test_description='compare full workdir to sparse workdir'\n>\n> +GIT_TEST_CHECK_CACHE_TREE=0\n\nI still think it'd be nice to get a comment, either in the code or the\ncommit message, explaining why your series needs to set\nGIT_TEST_CHECK_CACHE_TREE to 0.  I feel like I should almost know the\nanswer (was this just a preliminary step and it'll later be turned on?\ndid the cache-tree checking do stuff that assumes no sparse directory\nentries? is it really slow?), but I don't.\n\n> +GIT_TEST_SPLIT_INDEX=0\n> +\n>  . ./test-lib.sh\n>\n>  test_expect_success 'setup' '\n> @@ -121,15 +124,49 @@ run_on_all () {\n>  test_all_match () {\n>         run_on_all \"$@\" &&\n>         test_cmp full-checkout-out sparse-checkout-out &&\n> -       test_cmp full-checkout-err sparse-checkout-err\n> +       test_cmp full-checkout-out sparse-index-out &&\n> +       test_cmp full-checkout-err sparse-checkout-err &&\n> +       test_cmp full-checkout-err sparse-index-err\n>  }\n>\n>  test_sparse_match () {\n> -       run_on_sparse $* &&\n> +       run_on_sparse \"$@\" &&\n>         test_cmp sparse-checkout-out sparse-index-out &&\n>         test_cmp sparse-checkout-err sparse-index-err\n>  }\n>\n> +test_expect_success 'sparse-index contents' '\n> +       init_repos &&\n> +\n> +       test-tool -C sparse-index read-cache --table >cache &&\n> +       for dir in folder1 folder2 x\n> +       do\n> +               TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n> +               grep \"040000 tree $TREE $dir/\" cache \\\n> +                       || return 1\n> +       done &&\n> +\n> +       GIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set folder1 &&\n> +\n> +       test-tool -C sparse-index read-cache --table >cache &&\n> +       for dir in deep folder2 x\n> +       do\n> +               TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n> +               grep \"040000 tree $TREE $dir/\" cache \\\n> +                       || return 1\n> +       done &&\n> +\n> +       GIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep/deeper1 &&\n> +\n> +       test-tool -C sparse-index read-cache --table >cache &&\n> +       for dir in deep/deeper2 folder1 folder2 x\n> +       do\n> +               TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n> +               grep \"040000 tree $TREE $dir/\" cache \\\n> +                       || return 1\n> +       done\n> +'\n> +\n>  test_expect_success 'expanded in-memory index matches full index' '\n>         init_repos &&\n>         test_sparse_match test-tool read-cache --expand --table\n> @@ -137,6 +174,7 @@ test_expect_success 'expanded in-memory index matches full index' '\n>\n>  test_expect_success 'status with options' '\n>         init_repos &&\n> +       test_sparse_match ls &&\n>         test_all_match git status --porcelain=v2 &&\n>         test_all_match git status --porcelain=v2 -z -u &&\n>         test_all_match git status --porcelain=v2 -uno &&\n> @@ -273,6 +311,17 @@ test_expect_failure 'checkout and reset (mixed)' '\n>         test_all_match git reset update-folder2\n>  '\n>\n> +# Ensure that sparse-index behaves identically to\n> +# sparse-checkout with a full index.\n> +test_expect_success 'checkout and reset (mixed) [sparse]' '\n> +       init_repos &&\n> +\n> +       test_sparse_match git checkout -b reset-test update-deep &&\n> +       test_sparse_match git reset deepest &&\n> +       test_sparse_match git reset update-folder1 &&\n> +       test_sparse_match git reset update-folder2\n> +'\n> +\n>  test_expect_success 'merge' '\n>         init_repos &&\n>\n> @@ -309,14 +358,20 @@ test_expect_success 'clean' '\n>         test_all_match git status --porcelain=v2 &&\n>         test_all_match git clean -f &&\n>         test_all_match git status --porcelain=v2 &&\n> +       test_sparse_match ls &&\n> +       test_sparse_match ls folder1 &&\n>\n>         test_all_match git clean -xf &&\n>         test_all_match git status --porcelain=v2 &&\n> +       test_sparse_match ls &&\n> +       test_sparse_match ls folder1 &&\n>\n>         test_all_match git clean -xdf &&\n>         test_all_match git status --porcelain=v2 &&\n> +       test_sparse_match ls &&\n> +       test_sparse_match ls folder1 &&\n>\n> -       test_path_is_dir sparse-checkout/folder1\n> +       test_sparse_match test_path_is_dir folder1\n>  '\n>\n>  test_done\n> --\n> gitgitgadget\n>\n"},{"id":"418755","messageId":"CABPp-BGkr5D8bBgeeFob=j3p01_nJzUaQXDD=EGriUJ6XNX+Rw@mail.gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 00/20] Sparse Index: Design, Format, Tests","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-03-11T00:07:34Z","receivedAt":"2021-03-11T00:08:37Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Mar 10, 2021 at 11:31 AM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> Here is the first full patch series submission coming out of the\n> sparse-index RFC [1].\n>\n> [1]\n> https://lore.kernel.org/git/pull.847.git.1611596533.gitgitgadget@gmail.com/\n>\n> I won't waste too much space here, because PATCH 1 includes a sizeable\n> design document that describes the feature, the reasoning behind it, and my\n> plan for getting this implemented widely throughout the codebase.\n>\n> There are some new things here that were not in the RFC:\n>\n>  * Design doc and format updates. (Patch 1)\n>  * Performance test script. (Patches 2 and 20)\n>\n> Notably missing in this series from the RFC:\n>\n>  * The mega-patch inserting ensure_full_index() throughout the codebase.\n>    That will be a follow-up series to this one.\n>  * The integrations with git status and git add to demonstrate the improved\n>    performance. Those will also appear in their own series later.\n>\n> I plan to keep my latest work in this area in my 'sparse-index/wip' branch\n> [2]. It includes all of the work from the RFC right now, updated with the\n> work from this series.\n>\n> [2] https://github.com/derrickstolee/git/tree/sparse-index/wip\n>\n>\n> Updates in V2\n> =============\n>\n>  * Various typos and awkward grammar is fixed.\n>  * Cleaned up unnecessary commands in p2000-sparse-operations.sh\n>  * Added a comment to the sparse_index member of struct index_state.\n>  * Used tree_type, commit_type, and blob_type in test-read-cache.c.\n\nI read through the range-diff and comments from the previous series.\nThere's only a few things left (as I noted in comments), but they're\nall pretty trivial so this one is:\n\nReviewed-by: Elijah Newren <newren@gmail.com>\n\n>\n> Thanks, -Stolee\n>\n> Derrick Stolee (20):\n>   sparse-index: design doc and format update\n>   t/perf: add performance test for sparse operations\n>   t1092: clean up script quoting\n>   sparse-index: add guard to ensure full index\n>   sparse-index: implement ensure_full_index()\n>   t1092: compare sparse-checkout to sparse-index\n>   test-read-cache: print cache entries with --table\n>   test-tool: don't force full index\n>   unpack-trees: ensure full index\n>   sparse-checkout: hold pattern list in index\n>   sparse-index: convert from full to sparse\n>   submodule: sparse-index should not collapse links\n>   unpack-trees: allow sparse directories\n>   sparse-index: check index conversion happens\n>   sparse-index: create extension for compatibility\n>   sparse-checkout: toggle sparse index from builtin\n>   sparse-checkout: disable sparse-index\n>   cache-tree: integrate with sparse directory entries\n>   sparse-index: loose integration with cache_tree_verify()\n>   p2000: add sparse-index repos\n>\n>  Documentation/config/extensions.txt      |   8 +\n>  Documentation/git-sparse-checkout.txt    |  14 ++\n>  Documentation/technical/index-format.txt |   7 +\n>  Documentation/technical/sparse-index.txt | 173 ++++++++++++++\n>  Makefile                                 |   1 +\n>  builtin/sparse-checkout.c                |  44 +++-\n>  cache-tree.c                             |  40 ++++\n>  cache.h                                  |  18 +-\n>  read-cache.c                             |  35 ++-\n>  repo-settings.c                          |  15 ++\n>  repository.c                             |  11 +-\n>  repository.h                             |   3 +\n>  setup.c                                  |   3 +\n>  sparse-index.c                           | 290 +++++++++++++++++++++++\n>  sparse-index.h                           |  11 +\n>  t/README                                 |   3 +\n>  t/helper/test-read-cache.c               |  66 +++++-\n>  t/perf/p2000-sparse-operations.sh        | 102 ++++++++\n>  t/t1091-sparse-checkout-builtin.sh       |  13 +\n>  t/t1092-sparse-checkout-compatibility.sh | 136 +++++++++--\n>  unpack-trees.c                           |  16 +-\n>  21 files changed, 969 insertions(+), 40 deletions(-)\n>  create mode 100644 Documentation/technical/sparse-index.txt\n>  create mode 100644 sparse-index.c\n>  create mode 100644 sparse-index.h\n>  create mode 100755 t/perf/p2000-sparse-operations.sh\n>\n>\n> base-commit: 966e671106b2fd38301e7c344c754fd118d0bb07\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-883%2Fderrickstolee%2Fsparse-index%2Fformat-v2\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-883/derrickstolee/sparse-index/format-v2\n> Pull-Request: https://github.com/gitgitgadget/git/pull/883\n>\n> Range-diff vs v1:\n>\n>   1:  daa9a6bcefbc !  1:  2fe413fdac80 sparse-index: design doc and format update\n>      @@ Documentation/technical/sparse-index.txt (new)\n>       +If we need to discover the details for paths within that directory, we\n>       +can parse trees to find that list.\n>       +\n>      -+This addition of sparse-directory entries violates expectations about the\n>      ++At time of writing, sparse-directory entries violate expectations about the\n>       +index format and its in-memory data structure. There are many consumers in\n>       +the codebase that expect to iterate through all of the index entries and\n>       +see only files. In addition, they expect to see all files at `HEAD`. One\n>      @@ Documentation/technical/sparse-index.txt (new)\n>       +* `git merge`\n>       +* `git rebase`\n>       +\n>      ++Hopefully, commands such as `git merge` and `git rebase` can benefit\n>      ++instead from merge algorithms that do not use the index as a data\n>      ++structure, such as the merge-ORT strategy. As these topics mature, we\n>      ++may enalbe the ORT strategy by default for repositories using the\n>      ++sparse-index feature.\n>      ++\n>       +Along with `git status` and `git add`, these commands cover the majority\n>       +of users' interactions with the working directory. In addition, we can\n>       +integrate with these commands:\n>   2:  a8c6322a3dbe !  2:  540ab5495065 t/perf: add performance test for sparse operations\n>      @@ t/perf/p2000-sparse-operations.sh (new)\n>       + # Remove submodules from the example repo, because our\n>       + # duplication of the entire repo creates an unlikly data shape.\n>       + git config --file .gitmodules --get-regexp \"submodule.*.path\" >modules &&\n>      -+ rm -f .gitmodules &&\n>      -+ git add .gitmodules &&\n>      ++ git rm -f .gitmodules &&\n>       + for module in $(awk \"{print \\$2}\" modules)\n>       + do\n>       +         git rm $module || return 1\n>       + done &&\n>      -+ git add . &&\n>       + git commit -m \"remove submodules\" &&\n>       +\n>       + echo bogus >a &&\n>   3:  6e783c88821e =  3:  5cbedb377b37 t1092: clean up script quoting\n>   4:  01da4c48a1fa =  4:  6e21f776e883 sparse-index: add guard to ensure full index\n>   5:  2b83989fbcd3 !  5:  399ddb0bad56 sparse-index: implement ensure_full_index()\n>      @@ cache.h: struct index_state {\n>                  updated_skipworktree : 1,\n>       -          fsmonitor_has_run_once : 1;\n>       +          fsmonitor_has_run_once : 1,\n>      ++\n>      ++          /*\n>      ++           * sparse_index == 1 when sparse-directory\n>      ++           * entries exist. Requires sparse-checkout\n>      ++           * in cone mode.\n>      ++           */\n>       +          sparse_index : 1;\n>         struct hashmap name_hash;\n>         struct hashmap dir_hash;\n>   6:  c9910a37579c =  6:  eac2db5efc22 t1092: compare sparse-checkout to sparse-index\n>   7:  3d92df7a0cf9 !  7:  e9c82d2eda82 test-read-cache: print cache entries with --table\n>      @@ Commit message\n>\n>        ## t/helper/test-read-cache.c ##\n>       @@\n>      + #include \"test-tool.h\"\n>        #include \"cache.h\"\n>        #include \"config.h\"\n>      -\n>      ++#include \"blob.h\"\n>      ++#include \"commit.h\"\n>      ++#include \"tree.h\"\n>      ++\n>       +static void print_cache_entry(struct cache_entry *ce)\n>       +{\n>      -+ printf(\"%06o \", ce->ce_mode & 0777777);\n>      ++ const char *type;\n>      ++ printf(\"%06o \", ce->ce_mode & 0177777);\n>       +\n>       + if (S_ISSPARSEDIR(ce->ce_mode))\n>      -+         printf(\"tree \");\n>      ++         type = tree_type;\n>       + else if (S_ISGITLINK(ce->ce_mode))\n>      -+         printf(\"commit \");\n>      ++         type = commit_type;\n>       + else\n>      -+         printf(\"blob \");\n>      ++         type = blob_type;\n>       +\n>      -+ printf(\"%s\\t%s\\n\",\n>      ++ printf(\"%s %s\\t%s\\n\",\n>      ++        type,\n>       +        oid_to_hex(&ce->oid),\n>       +        ce->name);\n>       +}\n>       +\n>      -+static void print_cache(struct index_state *cache)\n>      ++static void print_cache(struct index_state *istate)\n>       +{\n>       + int i;\n>      -+ for (i = 0; i < the_index.cache_nr; i++)\n>      -+         print_cache_entry(the_index.cache[i]);\n>      ++ for (i = 0; i < istate->cache_nr; i++)\n>      ++         print_cache_entry(istate->cache[i]);\n>       +}\n>      -+\n>      +\n>        int cmd__read_cache(int argc, const char **argv)\n>        {\n>       + struct repository *r = the_repository;\n>   8:  94373e2bfbbc !  8:  243541fc5820 test-tool: don't force full index\n>      @@ Commit message\n>\n>        ## t/helper/test-read-cache.c ##\n>       @@\n>      - #include \"test-tool.h\"\n>      - #include \"cache.h\"\n>      - #include \"config.h\"\n>      + #include \"blob.h\"\n>      + #include \"commit.h\"\n>      + #include \"tree.h\"\n>       +#include \"sparse-index.h\"\n>\n>        static void print_cache_entry(struct cache_entry *ce)\n>   9:  e71f033c2871 =  9:  48f65093b3da unpack-trees: ensure full index\n>  10:  f86d3dc154d1 ! 10:  83aac8b7a1ec sparse-checkout: hold pattern list in index\n>      @@ Commit message\n>           pattern set, we need access to that in-memory copy. Place a pointer to\n>           a 'struct pattern_list' in the index so we can access this on-demand.\n>           This will be used in the next change which uses the sparse-checkout\n>      -    definition to filter out directories that are outsie the sparse cone.\n>      +    definition to filter out directories that are outside the sparse cone.\n>\n>           Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n>\n>  11:  a2d77c23a0cb ! 11:  f6db0c27a285 sparse-index: convert from full to sparse\n>      @@ read-cache.c: int verify_path(const char *path, unsigned mode)\n>                                 return 0;\n>       +                 /*\n>       +                  * allow terminating directory separators for\n>      -+                  * sparse directory enries.\n>      ++                  * sparse directory entries.\n>       +                  */\n>       +                 if (c == '\\0')\n>       +                         return S_ISDIR(mode);\n>      @@ sparse-index.c\n>       +         struct cache_entry *ce = istate->cache[i];\n>       +\n>       +         /*\n>      -+          * Detect if this is a normal entry oustide of any subtree\n>      ++          * Detect if this is a normal entry outside of any subtree\n>       +          * entry.\n>       +          */\n>       +         base = ce->name + ct_pathlen;\n>  12:  4405a9115c3b = 12:  f2a3e7298798 submodule: sparse-index should not collapse links\n>  13:  fda23f07e6a2 ! 13:  6f1ebe6ccc08 unpack-trees: allow sparse directories\n>      @@ Commit message\n>           is possible to have a directory in a sparse index as long as that entry\n>           is itself marked with the skip-worktree bit.\n>\n>      -    The negation of the 'pos' variable must be conditioned to only when it\n>      -    starts as negative. This is identical behavior as before when the index\n>      -    is full.\n>      +    The 'pos' variable is assigned a negative value if an exact match is not\n>      +    found. Since a directory name can be an exact match, it is no longer an\n>      +    error to have a nonnegative 'pos' value.\n>\n>           Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n>\n>  14:  7d4627574bb8 = 14:  3fa684b315fb sparse-index: check index conversion happens\n>  15:  564503f78784 ! 15:  d74576d677f6 sparse-index: create extension for compatibility\n>      @@ Commit message\n>\n>           We _could_ add a new index version that explicitly adds these\n>           capabilities, but there are nuances to index formats 2, 3, and 4 that\n>      -    are still valuable to select as options. For now, create a repo\n>      -    extension, \"extensions.sparseIndex\", that specifies that the tool\n>      -    reading this repository must understand sparse directory entries.\n>      +    are still valuable to select as options. Until we add index format\n>      +    version 5, create a repo extension, \"extensions.sparseIndex\", that\n>      +    specifies that the tool reading this repository must understand sparse\n>      +    directory entries.\n>\n>           This change only encodes the extension and enables it when\n>           GIT_TEST_SPARSE_INDEX=1. Later, we will add a more user-friendly CLI\n>      @@ Documentation/config/extensions.txt: extensions.objectFormat::\n>       + When combined with `core.sparseCheckout=true` and\n>       + `core.sparseCheckoutCone=true`, the index may contain entries\n>       + corresponding to directories outside of the sparse-checkout\n>      -+ definition. Versions of Git that do not understand this extension\n>      -+ do not expect directory entries in the index.\n>      ++ definition in lieu of containing each path under such directories.\n>      ++ Versions of Git that do not understand this extension do not\n>      ++ expect directory entries in the index.\n>\n>        ## cache.h ##\n>       @@ cache.h: struct repository_format {\n>  16:  6d6b230e3318 ! 16:  e530ca5f668d sparse-checkout: toggle sparse index from builtin\n>      @@ Documentation/git-sparse-checkout.txt: To avoid interfering with other worktrees\n>       +a sparse index until they are properly integrated with the feature.\n>       ++\n>       +**WARNING:** Using a sparse index requires modifying the index in a way\n>      -+that is not completely understood by other tools. Enabling sparse index\n>      -+enables the `extensions.spareseIndex` config value, which might cause\n>      -+other tools to stop working with your repository. If you have trouble with\n>      -+this compatibility, then run `git sparse-checkout sparse-index disable` to\n>      -+remove this config and rewrite your index to not be sparse.\n>      ++that is not completely understood by external tools. If you have trouble\n>      ++with this compatibility, then run `git sparse-checkout sparse-index disable`\n>      ++to rewrite your index to not be sparse. Older versions of Git will not\n>      ++understand the `sparseIndex` repository extension and may fail to interact\n>      ++with your repository until it is disabled.\n>\n>        'set'::\n>         Write a set of patterns to the sparse-checkout file, as given as\n>  17:  bcf960ef2362 = 17:  42d0da9c5def sparse-checkout: disable sparse-index\n>  18:  e6afec58674e = 18:  6bb0976a6295 cache-tree: integrate with sparse directory entries\n>  19:  2be4981fe698 = 19:  07f34e80609a sparse-index: loose integration with cache_tree_verify()\n>  20:  a738b0ba8ab4 = 20:  41e3b56b9c17 p2000: add sparse-index repos\n>\n> --\n> gitgitgadget\n"},{"id":"418846","messageId":"1241c484-62ce-5acb-794e-444ba2cdc4c2@gmail.com","threadId":"55195","inReplyTo":"CABPp-BGUqVC7JV7nnNDTt++rHCxUF+98T0OLq5dFf0mOKkp8fw@mail.gmail.com","subject":"Re: [PATCH v2 11/20] sparse-index: convert from full to sparse","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-11T14:13:32Z","receivedAt":"2021-03-11T14:14:18Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/10/2021 6:44 PM, Elijah Newren wrote:\n> On Wed, Mar 10, 2021 at 11:31 AM Derrick Stolee via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>> +GIT_TEST_CHECK_CACHE_TREE=0\n> \n> I still think it'd be nice to get a comment, either in the code or the\n> commit message, explaining why your series needs to set\n> GIT_TEST_CHECK_CACHE_TREE to 0.  I feel like I should almost know the\n> answer (was this just a preliminary step and it'll later be turned on?\n> did the cache-tree checking do stuff that assumes no sparse directory\n> entries? is it really slow?), but I don't.\n\nSorry I missed commenting on this earlier.\n\nThe GIT_TEST_CHECK_CACHE_TREE environment is enabled by the test suite\nby default and it does extra validation to see that the cache-tree\nextension exists and matches the index contents. Since at this point\nwe don't have the cache-tree extension enabled with sparse-index, we\nwould start getting failures by those tests.\n\nThis is re-enabled in \"sparse-index: loose integration with\ncache_tree_verify()\" so everything is being verified at the end of the\nseries.\n\nThanks,\n-Stolee\n"},{"id":"418847","messageId":"1efcb012-53d7-9939-7fa3-ab45cdc1a6fa@gmail.com","threadId":"55195","inReplyTo":"CABPp-BG6eSpjHVU5XShi17MOuPku=U388rL1=JXXF88M4ymFHA@mail.gmail.com","subject":"Re: [PATCH v2 06/20] t1092: compare sparse-checkout to sparse-index","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-11T14:17:58Z","receivedAt":"2021-03-11T14:19:06Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/10/2021 6:04 PM, Elijah Newren wrote:\n> On Wed, Mar 10, 2021 at 11:31 AM Derrick Stolee via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>> Add GIT_TEST_SPARSE_INDEX environment variable to enable the\n>> sparse-index by default. This will be intended to use across the entire\n>> test suite, except that it will only affect cases where the\n>> sparse-checkout feature is enabled.\n> \n> This last sentence was a bit awkward to read.  \"will be intended to\n> use\" -> \"is intended to be used\"?\n\nFixed locally to:\n\n    Add the GIT_TEST_SPARSE_INDEX environment variable to enable the\n    sparse-index by default. This can be enabled across all tests, but that\n    will only affect cases where the sparse-checkout feature is enabled.\n \n>> +test_sparse_match () {\n>> +       run_on_sparse $* &&\n> \n> Should this be\n>    run_on_sparse \"$@\"\n> in order to allow arguments with spaces?\n\nSorry I missed this one. It was fixed to the right use in\n\"sparse-index: convert from full to sparse\" so I thought I\nhad already covered this one when looking at the tip of my\nbranch.\n \nThanks,\n-Stolee\n"},{"id":"418933","messageId":"xmqq1rckc1cb.fsf@gitster.g","threadId":"55195","inReplyTo":"399ddb0bad56c69ff9d9591f5e8eacf52cf50a15.1615404665.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 05/20] sparse-index: implement ensure_full_index()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-12T06:50:28Z","receivedAt":"2021-03-12T06:51:34Z","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>  void ensure_full_index(struct index_state *istate)\n>  {\n> ...\n> +\tint i;\n> +\t\ttree = lookup_tree(istate->repo, &ce->oid);\n> +\n> +\t\tmemset(&ps, 0, sizeof(ps));\n> +\t\tps.recursive = 1;\n> +\t\tps.has_wildcard = 1;\n> +\t\tps.max_depth = -1;\n> +\n> +\t\tread_tree_recursive(istate->repo, tree,\n> +\t\t\t\t    ce->name, strlen(ce->name),\n> +\t\t\t\t    0, &ps,\n> +\t\t\t\t    add_path_to_index, full);\n\nÆvar, the assumption that led to your e68237bb (tree.h API: remove\nsupport for starting at prefix != \"\", 2021-03-08) closes the door\nfor this code rather badly.  Please work with Derrick to figure out\nwhat the best course of action would be.\n\nThanks.\n\n> +\t\t/* free directory entries. full entries are re-used */\n> +\t\tdiscard_cache_entry(ce);\n> +\t}\n> +\n> +\t/* Copy back into original index. */\n> +\tmemcpy(&istate->name_hash, &full->name_hash, sizeof(full->name_hash));\n> +\tistate->sparse_index = 0;\n> +\tfree(istate->cache);\n> +\tistate->cache = full->cache;\n> +\tistate->cache_nr = full->cache_nr;\n> +\tistate->cache_alloc = full->cache_alloc;\n> +\n> +\tfree(full);\n> +\n> +\ttrace2_region_leave(\"index\", \"ensure_full_index\", istate->repo);\n>  }\n"},{"id":"418970","messageId":"c5e42675-5f03-728a-60ec-880da368085b@gmail.com","threadId":"55195","inReplyTo":"xmqq1rckc1cb.fsf@gitster.g","subject":"Re: [PATCH v2 05/20] sparse-index: implement ensure_full_index()","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-12T13:56:28Z","receivedAt":"2021-03-12T13:57:20Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/12/2021 1:50 AM, Junio C Hamano wrote:\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>>  void ensure_full_index(struct index_state *istate)\n>>  {\n>> ...\n>> +\tint i;\n>> +\t\ttree = lookup_tree(istate->repo, &ce->oid);\n>> +\n>> +\t\tmemset(&ps, 0, sizeof(ps));\n>> +\t\tps.recursive = 1;\n>> +\t\tps.has_wildcard = 1;\n>> +\t\tps.max_depth = -1;\n>> +\n>> +\t\tread_tree_recursive(istate->repo, tree,\n>> +\t\t\t\t    ce->name, strlen(ce->name),\n>> +\t\t\t\t    0, &ps,\n>> +\t\t\t\t    add_path_to_index, full);\n> \n> Ævar, the assumption that led to your e68237bb (tree.h API: remove\n> support for starting at prefix != \"\", 2021-03-08) closes the door\n> for this code rather badly.  Please work with Derrick to figure out\n> what the best course of action would be.\n\nThanks for pointing this out, Junio.\n\nMy preference would be to drop \"tree.h API: remove support for\nstarting at prefix != \"\"\", but it should be OK to keep \"tree.h API:\nremove \"stage\" parameter from read_tree_recursive()\" (currently\nb3a078863f6), even though it introduces a semantic conflict here.\n\nSince I haven't seen my sparse-index topic get picked up by a\ntracking branch, I'd be happy to rebase on top of Ævar's topic if\nI can still set a non-root prefix.\n\nThanks,\n-Stolee\n"},{"id":"418979","messageId":"xmqqft106sok.fsf@gitster.g","threadId":"55195","inReplyTo":"c5e42675-5f03-728a-60ec-880da368085b@gmail.com","subject":"Re: [PATCH v2 05/20] sparse-index: implement ensure_full_index()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-12T20:08:43Z","receivedAt":"2021-03-12T20:09:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <stolee@gmail.com> writes:\n\n>> Ævar, the assumption that led to your e68237bb (tree.h API: remove\n>> support for starting at prefix != \"\", 2021-03-08) closes the door\n>> for this code rather badly.  Please work with Derrick to figure out\n>> what the best course of action would be.\n>\n> Thanks for pointing this out, Junio.\n>\n> My preference would be to drop \"tree.h API: remove support for\n> starting at prefix != \"\"\", but it should be OK to keep \"tree.h API:\n> remove \"stage\" parameter from read_tree_recursive()\" (currently\n> b3a078863f6), even though it introduces a semantic conflict here.\n>\n> Since I haven't seen my sparse-index topic get picked up by a\n> tracking branch, I'd be happy to rebase on top of Ævar's topic if\n> I can still set a non-root prefix.\n\nI did try to have both in 'seen' (after all, that is the primary way\nI find out these conflicts early---no one can keep all the details\nof all the topics in flight in one's head), and saw that we now have\na need for non-empty prefix that we thought we no longer have in the\nother topic --- I think we should probably keep support of non-empty\nprefix (as the primary reason why that patch exists is because we\nsaw no in-tree users---now if your 05/20 proves to be a good use of\nthe feature, there is one fewer reasons to remove the support) in\nsome form, so discarding e68237bb certainly is an option.\n\n\nIf we were to base the sparse-index topic on top of ab/read-tree, we\nmay be able to gain further simplification and clean-up of the API.\n\nI think all the clean-up value e68237bb has are on the calling side\n(they no longer have to pass constant (\"\", 0) to the function), and\nwe could rewrite e68237bb by\n\n - renaming \"read_tree_recursive()\" to \"read_tree_at()\", with the\n   non-empty prefix support.\n\n - creating a new function \"read_tree()\", which lacks the support\n   for prefix, as a thin-wrapper around \"read_tree_at()\".\n\n - modifying the callers of \"read_tree_recursive()\" changed by\n   e68237bb to instead call \"read_tree()\" (without prefix).\n\nto simplify majority of calling sites without losing functionality.\n\nThen your [05/20] can use the read_tree_at() to read with a prefix.\n\n\nBut that kind of details, I'd want to see you two figure out\nyourselves.\n\nThanks.\n\n"},{"id":"418980","messageId":"c730cb8d-06ff-6f86-4eae-812472157327@gmail.com","threadId":"55195","inReplyTo":"xmqqft106sok.fsf@gitster.g","subject":"Re: [PATCH v2 05/20] sparse-index: implement ensure_full_index()","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-12T20:11:06Z","receivedAt":"2021-03-12T20:12:13Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/12/2021 3:08 PM, Junio C Hamano wrote:\n> Derrick Stolee <stolee@gmail.com> writes:\n> \n>>> Ævar, the assumption that led to your e68237bb (tree.h API: remove\n>>> support for starting at prefix != \"\", 2021-03-08) closes the door\n>>> for this code rather badly.  Please work with Derrick to figure out\n>>> what the best course of action would be.\n>>\n>> Thanks for pointing this out, Junio.\n>>\n>> My preference would be to drop \"tree.h API: remove support for\n>> starting at prefix != \"\"\", but it should be OK to keep \"tree.h API:\n>> remove \"stage\" parameter from read_tree_recursive()\" (currently\n>> b3a078863f6), even though it introduces a semantic conflict here.\n>>\n>> Since I haven't seen my sparse-index topic get picked up by a\n>> tracking branch, I'd be happy to rebase on top of Ævar's topic if\n>> I can still set a non-root prefix.\n> I think all the clean-up value e68237bb has are on the calling side\n> (they no longer have to pass constant (\"\", 0) to the function), and\n> we could rewrite e68237bb by\n> \n>  - renaming \"read_tree_recursive()\" to \"read_tree_at()\", with the\n>    non-empty prefix support.\n> \n>  - creating a new function \"read_tree()\", which lacks the support\n>    for prefix, as a thin-wrapper around \"read_tree_at()\".\n> \n>  - modifying the callers of \"read_tree_recursive()\" changed by\n>    e68237bb to instead call \"read_tree()\" (without prefix).\n> \n> to simplify majority of calling sites without losing functionality.\n> \n> Then your [05/20] can use the read_tree_at() to read with a prefix.\n> \n> \n> But that kind of details, I'd want to see you two figure out\n> yourselves.\n\nYou've given us a great proposal. I'll wait for Ævar to chime in\n(and probably update his topic) before I submit a new version.\n\nThanks,\n-Stolee\n"},{"id":"419102","messageId":"CAN0heSpu7mWCXTu3qUKN1VEiH3rqHpstEU_k1LktNhV-z8b2pQ@mail.gmail.com","threadId":"55195","inReplyTo":"466cb063-0481-cfa6-cc3e-8ca26d73c8fb@gmail.com","subject":"Re: [PATCH 16/20] sparse-checkout: toggle sparse index from builtin","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2021-03-14T20:08:07Z","receivedAt":"2021-03-14T20:09:03Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"On Tue, 9 Mar 2021 at 21:52, Derrick Stolee <stolee@gmail.com> wrote:\n>\n> I agree that the layers are confusing. We could rearrange and have\n> a similar flow to what you recommend by mentioning the extension at\n> the end:\n>\n> **WARNING:** Using a sparse index requires modifying the index in a way\n> that is not completely understood by other tools. If you have trouble with\n> this compatibility, then run `git sparse-checkout sparse-index disable` to\n> rewrite your index to not be sparse. Older versions of Git will not\n> understand the `sparseIndex` repository extension and may fail to interact\n> with your repository until it is disabled.\n\nI like it. I find this easier to read than the previous version. That\nsaid, is `git sparse-index sparse-checkout disable` really the way to do\nthis? I don't see a \"sparse-index\" subcommand of git-sparse-checkout.\n... Hmm, no, after building and installing your patches, I get\n\n  $ git sparse-checkout sparse-index disable\n  usage: git sparse-checkout (init|list|set|add|reapply|disable) <options>\n\nShould that be `git sparse-checkout init --no-sparse-index`? I just\ntried that on a fresh, empty repo. It seems to work in the sense that it\ndrops the config item. I'm guessing re-initing a sparse checkout is a\nsafe and sane thing to do?\n\nI don't find any tests for this. If re-initing should be ok and in\nparticular if it should allow toggling the use of sparse index, it might\nbe good having a test. At a minimum to see that the command passes and\nthat the config item goes away? And check that the actual index is\nrewritten back to the \"old\" format? (Sorry if you have that already and\nI'm just bad at finding it.)\n\nMartin\n"},{"id":"419160","messageId":"03e4b93d-cba7-1ee4-e22e-d3c5a5d56175@gmail.com","threadId":"55195","inReplyTo":"CAN0heSpu7mWCXTu3qUKN1VEiH3rqHpstEU_k1LktNhV-z8b2pQ@mail.gmail.com","subject":"Re: [PATCH 16/20] sparse-checkout: toggle sparse index from builtin","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-15T13:36:50Z","receivedAt":"2021-03-15T13:37:53Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/14/2021 4:08 PM, Martin Ågren wrote:\n> On Tue, 9 Mar 2021 at 21:52, Derrick Stolee <stolee@gmail.com> wrote:\n>>\n>> I agree that the layers are confusing. We could rearrange and have\n>> a similar flow to what you recommend by mentioning the extension at\n>> the end:\n>>\n>> **WARNING:** Using a sparse index requires modifying the index in a way\n>> that is not completely understood by other tools. If you have trouble with\n>> this compatibility, then run `git sparse-checkout sparse-index disable` to\n>> rewrite your index to not be sparse. Older versions of Git will not\n>> understand the `sparseIndex` repository extension and may fail to interact\n>> with your repository until it is disabled.\n> \n> I like it. I find this easier to read than the previous version. That\n> said, is `git sparse-index sparse-checkout disable` really the way to do\n> this? I don't see a \"sparse-index\" subcommand of git-sparse-checkout.\n> ... Hmm, no, after building and installing your patches, I get\n> \n>   $ git sparse-checkout sparse-index disable\n>   usage: git sparse-checkout (init|list|set|add|reapply|disable) <options>\n> \n> Should that be `git sparse-checkout init --no-sparse-index`? I just\n> tried that on a fresh, empty repo. It seems to work in the sense that it\n> drops the config item. I'm guessing re-initing a sparse checkout is a\n> safe and sane thing to do?\n\nYes! Sorry I missed updating this instance when changing the\ndesign. Your suggestion is indeed the proper way to disable the\nsparse-index.\n \n> I don't find any tests for this. If re-initing should be ok and in\n> particular if it should allow toggling the use of sparse index, it might\n> be good having a test. At a minimum to see that the command passes and\n> that the config item goes away? And check that the actual index is\n> rewritten back to the \"old\" format? (Sorry if you have that already and\n> I'm just bad at finding it.)\n\nWe have tests already that 'git sparse-checkout init' will preserve\nexisting sparse-checkout patterns.\n\nI should definitely have a test to ensure that '--no-sparse-index'\nrewrites the index to be a full one. Thanks!\n\n-Stolee\n"},{"id":"419215","messageId":"87eeggf00n.fsf@evledraar.gmail.com","threadId":"55195","inReplyTo":"c730cb8d-06ff-6f86-4eae-812472157327@gmail.com","subject":"Re: [PATCH v2 05/20] sparse-index: implement ensure_full_index()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-15T23:52:08Z","receivedAt":"2021-03-15T23:53:11Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Mar 12 2021, Derrick Stolee wrote:\n\n> On 3/12/2021 3:08 PM, Junio C Hamano wrote:\n>> Derrick Stolee <stolee@gmail.com> writes:\n>> \n>>>> Ævar, the assumption that led to your e68237bb (tree.h API: remove\n>>>> support for starting at prefix != \"\", 2021-03-08) closes the door\n>>>> for this code rather badly.  Please work with Derrick to figure out\n>>>> what the best course of action would be.\n>>>\n>>> Thanks for pointing this out, Junio.\n>>>\n>>> My preference would be to drop \"tree.h API: remove support for\n>>> starting at prefix != \"\"\", but it should be OK to keep \"tree.h API:\n>>> remove \"stage\" parameter from read_tree_recursive()\" (currently\n>>> b3a078863f6), even though it introduces a semantic conflict here.\n>>>\n>>> Since I haven't seen my sparse-index topic get picked up by a\n>>> tracking branch, I'd be happy to rebase on top of Ævar's topic if\n>>> I can still set a non-root prefix.\n>> I think all the clean-up value e68237bb has are on the calling side\n>> (they no longer have to pass constant (\"\", 0) to the function), and\n>> we could rewrite e68237bb by\n>> \n>>  - renaming \"read_tree_recursive()\" to \"read_tree_at()\", with the\n>>    non-empty prefix support.\n>> \n>>  - creating a new function \"read_tree()\", which lacks the support\n>>    for prefix, as a thin-wrapper around \"read_tree_at()\".\n>> \n>>  - modifying the callers of \"read_tree_recursive()\" changed by\n>>    e68237bb to instead call \"read_tree()\" (without prefix).\n>> \n>> to simplify majority of calling sites without losing functionality.\n>> \n>> Then your [05/20] can use the read_tree_at() to read with a prefix.\n>> \n>> \n>> But that kind of details, I'd want to see you two figure out\n>> yourselves.\n>\n> You've given us a great proposal. I'll wait for Ævar to chime in\n> (and probably update his topic) before I submit a new version.\n\nI've re-rolled my series just now at\nhttps://lore.kernel.org/git/20210315234344.28427-1-avarab@gmail.com/\nsorry for the delay.\n\nYou should be able to rebase easily on top of it, although note that the\nnew read_tree_at() uses a strbuf, but is otherwise the same as the old\nread_tree_recursive().\n\nNote that the pathspec can also be used to get to where\nread_tree_recursive() would have brought you. I haven't looked at\nwhether there's reasons to convert in-tree (or this) code to pathspec\nuse, or vice-versa convert some things that use pathspecs now\n(e.g. ls-tree with a path) to providing a prefix via the strbuf.\n"},{"id":"419374","messageId":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v2.git.1615404664.gitgitgadget@gmail.com","subject":"[PATCH v3 00/20] Sparse Index: Design, Format, Tests","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:42:43Z","receivedAt":"2021-03-16T16:44:07Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"Here is the first full patch series submission coming out of the\nsparse-index RFC [1].\n\n[1]\nhttps://lore.kernel.org/git/pull.847.git.1611596533.gitgitgadget@gmail.com/\n\nI won't waste too much space here, because PATCH 1 includes a sizeable\ndesign document that describes the feature, the reasoning behind it, and my\nplan for getting this implemented widely throughout the codebase.\n\nThere are some new things here that were not in the RFC:\n\n * Design doc and format updates. (Patch 1)\n * Performance test script. (Patches 2 and 20)\n\nNotably missing in this series from the RFC:\n\n * The mega-patch inserting ensure_full_index() throughout the codebase.\n   That will be a follow-up series to this one.\n * The integrations with git status and git add to demonstrate the improved\n   performance. Those will also appear in their own series later.\n\nI plan to keep my latest work in this area in my 'sparse-index/wip' branch\n[2]. It includes all of the work from the RFC right now, updated with the\nwork from this series.\n\n[2] https://github.com/derrickstolee/git/tree/sparse-index/wip\n\n\nUpdates in V3\n=============\n\nFor this version, I took Ævar's latest patches and applied them to v2.31.0\nand rebased this series on top. It uses his new \"read_tree_at()\" helper and\nthe associated changes to the function pointer type.\n\n * Fixed more typos. Thanks Martin and Elijah!\n * Updated the test_sparse_match() macro to use \"$@\" instead of $*\n * Added a test that git sparse-checkout init --no-sparse-index rewrites the\n   index to be full.\n\n\nUpdates in V2\n=============\n\n * Various typos and awkward grammar is fixed.\n * Cleaned up unnecessary commands in p2000-sparse-operations.sh\n * Added a comment to the sparse_index member of struct index_state.\n * Used tree_type, commit_type, and blob_type in test-read-cache.c.\n\nThanks, -Stolee\n\nDerrick Stolee (20):\n  sparse-index: design doc and format update\n  t/perf: add performance test for sparse operations\n  t1092: clean up script quoting\n  sparse-index: add guard to ensure full index\n  sparse-index: implement ensure_full_index()\n  t1092: compare sparse-checkout to sparse-index\n  test-read-cache: print cache entries with --table\n  test-tool: don't force full index\n  unpack-trees: ensure full index\n  sparse-checkout: hold pattern list in index\n  sparse-index: convert from full to sparse\n  submodule: sparse-index should not collapse links\n  unpack-trees: allow sparse directories\n  sparse-index: check index conversion happens\n  sparse-index: create extension for compatibility\n  sparse-checkout: toggle sparse index from builtin\n  sparse-checkout: disable sparse-index\n  cache-tree: integrate with sparse directory entries\n  sparse-index: loose integration with cache_tree_verify()\n  p2000: add sparse-index repos\n\n Documentation/config/extensions.txt      |   8 +\n Documentation/git-sparse-checkout.txt    |  14 ++\n Documentation/technical/index-format.txt |   7 +\n Documentation/technical/sparse-index.txt | 173 +++++++++++++\n Makefile                                 |   1 +\n builtin/sparse-checkout.c                |  44 +++-\n cache-tree.c                             |  40 ++++\n cache.h                                  |  18 +-\n read-cache.c                             |  35 ++-\n repo-settings.c                          |  15 ++\n repository.c                             |  11 +-\n repository.h                             |   3 +\n setup.c                                  |   3 +\n sparse-index.c                           | 293 +++++++++++++++++++++++\n sparse-index.h                           |  11 +\n t/README                                 |   3 +\n t/helper/test-read-cache.c               |  66 ++++-\n t/perf/p2000-sparse-operations.sh        | 102 ++++++++\n t/t1091-sparse-checkout-builtin.sh       |  13 +\n t/t1092-sparse-checkout-compatibility.sh | 143 +++++++++--\n unpack-trees.c                           |  16 +-\n 21 files changed, 979 insertions(+), 40 deletions(-)\n create mode 100644 Documentation/technical/sparse-index.txt\n create mode 100644 sparse-index.c\n create mode 100644 sparse-index.h\n create mode 100755 t/perf/p2000-sparse-operations.sh\n\n\nbase-commit: 9c34e7ffd7b544199d889e2f3f7d9ba663c4357d\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-883%2Fderrickstolee%2Fsparse-index%2Fformat-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-883/derrickstolee/sparse-index/format-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/883\n\nRange-diff vs v2:\n\n  1:  2fe413fdac80 !  1:  62ac13945bec sparse-index: design doc and format update\n     @@ Documentation/technical/sparse-index.txt (new)\n      +Hopefully, commands such as `git merge` and `git rebase` can benefit\n      +instead from merge algorithms that do not use the index as a data\n      +structure, such as the merge-ORT strategy. As these topics mature, we\n     -+may enalbe the ORT strategy by default for repositories using the\n     ++may enable the ORT strategy by default for repositories using the\n      +sparse-index feature.\n      +\n      +Along with `git status` and `git add`, these commands cover the majority\n  2:  540ab5495065 =  2:  d2197e895e4d t/perf: add performance test for sparse operations\n  3:  5cbedb377b37 =  3:  d3cfd34b8418 t1092: clean up script quoting\n  4:  6e21f776e883 =  4:  4472118cf903 sparse-index: add guard to ensure full index\n  5:  399ddb0bad56 !  5:  99292cdbaae4 sparse-index: implement ensure_full_index()\n     @@ sparse-index.c\n      +}\n      +\n      +static int add_path_to_index(const struct object_id *oid,\n     -+\t\t\t\tstruct strbuf *base, const char *path,\n     -+\t\t\t\tunsigned int mode, int stage, void *context)\n     ++\t\t\t     struct strbuf *base, const char *path,\n     ++\t\t\t     unsigned int mode, void *context)\n      +{\n      +\tstruct index_state *istate = (struct index_state *)context;\n      +\tstruct cache_entry *ce;\n     @@ sparse-index.c\n      -\t/* intentionally left blank */\n      +\tint i;\n      +\tstruct index_state *full;\n     ++\tstruct strbuf base = STRBUF_INIT;\n      +\n      +\tif (!istate || !istate->sparse_index)\n      +\t\treturn;\n     @@ sparse-index.c\n      +\t\tps.has_wildcard = 1;\n      +\t\tps.max_depth = -1;\n      +\n     -+\t\tread_tree_recursive(istate->repo, tree,\n     -+\t\t\t\t    ce->name, strlen(ce->name),\n     -+\t\t\t\t    0, &ps,\n     -+\t\t\t\t    add_path_to_index, full);\n     ++\t\tstrbuf_setlen(&base, 0);\n     ++\t\tstrbuf_add(&base, ce->name, strlen(ce->name));\n     ++\n     ++\t\tread_tree_at(istate->repo, tree, &base, &ps,\n     ++\t\t\t     add_path_to_index, full);\n      +\n      +\t\t/* free directory entries. full entries are re-used */\n      +\t\tdiscard_cache_entry(ce);\n     @@ sparse-index.c\n      +\tistate->cache_nr = full->cache_nr;\n      +\tistate->cache_alloc = full->cache_alloc;\n      +\n     ++\tstrbuf_release(&base);\n      +\tfree(full);\n      +\n      +\ttrace2_region_leave(\"index\", \"ensure_full_index\", istate->repo);\n  6:  eac2db5efc22 !  6:  fae5663a17bb t1092: compare sparse-checkout to sparse-index\n     @@ Commit message\n          add run_on_sparse and test_sparse_match helpers. These helpers will be\n          used when the sparse index is implemented.\n      \n     -    Add GIT_TEST_SPARSE_INDEX environment variable to enable the\n     -    sparse-index by default. This will be intended to use across the entire\n     -    test suite, except that it will only affect cases where the\n     -    sparse-checkout feature is enabled.\n     +    Add the GIT_TEST_SPARSE_INDEX environment variable to enable the\n     +    sparse-index by default. This can be enabled across all tests, but that\n     +    will only affect cases where the sparse-checkout feature is enabled.\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n      \n     @@ t/t1092-sparse-checkout-compatibility.sh: test_all_match () {\n       }\n       \n      +test_sparse_match () {\n     -+\trun_on_sparse $* &&\n     ++\trun_on_sparse \"$@\" &&\n      +\ttest_cmp sparse-checkout-out sparse-index-out &&\n      +\ttest_cmp sparse-checkout-err sparse-index-err\n      +}\n  7:  e9c82d2eda82 =  7:  dffe8821fde2 test-read-cache: print cache entries with --table\n  8:  243541fc5820 =  8:  f4ad081f25bb test-tool: don't force full index\n  9:  48f65093b3da =  9:  4780076a50df unpack-trees: ensure full index\n 10:  83aac8b7a1ec = 10:  33fdba2b8cfd sparse-checkout: hold pattern list in index\n 11:  f6db0c27a285 ! 11:  e41b14e03ebb sparse-index: convert from full to sparse\n     @@ t/t1092-sparse-checkout-compatibility.sh\n       \n       test_description='compare full workdir to sparse workdir'\n       \n     ++# The verify_cache_tree() check is not sparse-aware (yet).\n     ++# So, disable the check until that integration is complete.\n      +GIT_TEST_CHECK_CACHE_TREE=0\n      +GIT_TEST_SPLIT_INDEX=0\n      +\n     @@ t/t1092-sparse-checkout-compatibility.sh: run_on_all () {\n       }\n       \n       test_sparse_match () {\n     --\trun_on_sparse $* &&\n     -+\trun_on_sparse \"$@\" &&\n     - \ttest_cmp sparse-checkout-out sparse-index-out &&\n     +@@ t/t1092-sparse-checkout-compatibility.sh: test_sparse_match () {\n       \ttest_cmp sparse-checkout-err sparse-index-err\n       }\n       \n 12:  f2a3e7298798 = 12:  b77cd6b02265 submodule: sparse-index should not collapse links\n 13:  6f1ebe6ccc08 = 13:  4000c5cdd4cf unpack-trees: allow sparse directories\n 14:  3fa684b315fb = 14:  1a2be38b2ca7 sparse-index: check index conversion happens\n 15:  d74576d677f6 = 15:  f89891b0ae4e sparse-index: create extension for compatibility\n 16:  e530ca5f668d ! 16:  bd703c76c859 sparse-checkout: toggle sparse index from builtin\n     @@ Documentation/git-sparse-checkout.txt: To avoid interfering with other worktrees\n      ++\n      +**WARNING:** Using a sparse index requires modifying the index in a way\n      +that is not completely understood by external tools. If you have trouble\n     -+with this compatibility, then run `git sparse-checkout sparse-index disable`\n     ++with this compatibility, then run `git sparse-checkout init --no-sparse-index`\n      +to rewrite your index to not be sparse. Older versions of Git will not\n      +understand the `sparseIndex` repository extension and may fail to interact\n      +with your repository until it is disabled.\n     @@ sparse-index.h: struct index_state;\n      \n       ## t/t1092-sparse-checkout-compatibility.sh ##\n      @@ t/t1092-sparse-checkout-compatibility.sh: test_description='compare full workdir to sparse workdir'\n     - \n     + # So, disable the check until that integration is complete.\n       GIT_TEST_CHECK_CACHE_TREE=0\n       GIT_TEST_SPLIT_INDEX=0\n      +GIT_TEST_SPARSE_INDEX=\n     @@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'sparse-index cont\n       \n       \ttest-tool -C sparse-index read-cache --table >cache &&\n       \tfor dir in deep/deeper2 folder1 folder2 x\n     +@@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'sparse-index contents' '\n     + \t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n     + \t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n     + \t\t\t|| return 1\n     +-\tdone\n     ++\tdone &&\n     ++\n     ++\t# Disabling the sparse-index removes tree entries with full ones\n     ++\tgit -C sparse-index sparse-checkout init --no-sparse-index &&\n     ++\n     ++\ttest-tool -C sparse-index read-cache --table >cache &&\n     ++\t! grep \"040000 tree\" cache &&\n     ++\ttest_sparse_match test-tool read-cache --table\n     + '\n     + \n     + test_expect_success 'expanded in-memory index matches full index' '\n      @@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'submodule handling' '\n       test_expect_success 'sparse-index is expanded and converted back' '\n       \tinit_repos &&\n 17:  42d0da9c5def = 17:  598557f90a2a sparse-checkout: disable sparse-index\n 18:  6bb0976a6295 ! 18:  c2d0c17db31a cache-tree: integrate with sparse directory entries\n     @@ sparse-index.c: int convert_to_sparse(struct index_state *istate)\n       \ttrace2_region_leave(\"index\", \"convert_to_sparse\", istate->repo);\n       \treturn 0;\n      @@ sparse-index.c: void ensure_full_index(struct index_state *istate)\n     - \n     + \tstrbuf_release(&base);\n       \tfree(full);\n       \n      +\t/* Clear and recompute the cache-tree */\n 19:  07f34e80609a ! 19:  6fdd9323c14e sparse-index: loose integration with cache_tree_verify()\n     @@ t/t1092-sparse-checkout-compatibility.sh\n       \n       test_description='compare full workdir to sparse workdir'\n       \n     +-# The verify_cache_tree() check is not sparse-aware (yet).\n     +-# So, disable the check until that integration is complete.\n      -GIT_TEST_CHECK_CACHE_TREE=0\n       GIT_TEST_SPLIT_INDEX=0\n       GIT_TEST_SPARSE_INDEX=\n 20:  41e3b56b9c17 = 20:  3db06ac46dd5 p2000: add sparse-index repos\n\n-- \ngitgitgadget\n"},{"id":"419375","messageId":"62ac13945bec13270e0898126756c3f947ae264b.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 01/20] sparse-index: design doc and format update","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:42:44Z","receivedAt":"2021-03-16T16:44:08Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThis begins a long effort to update the index format to allow sparse\ndirectory entries. This should result in a significant improvement to\nGit commands when HEAD contains millions of files, but the user has\nselected many fewer files to keep in their sparse-checkout definition.\n\nCurrently, the index format is only updated in the presence of\nextensions.sparseIndex instead of increasing a file format version\nnumber. This is temporary, and index v5 is part of the plan for future\nwork in this area.\n\nThe design document details many of the reasons for embarking on this\nwork, and also the plan for completing it safely.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/technical/index-format.txt |   7 +\n Documentation/technical/sparse-index.txt | 173 +++++++++++++++++++++++\n 2 files changed, 180 insertions(+)\n create mode 100644 Documentation/technical/sparse-index.txt\n\ndiff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt\nindex d363a71c37ec..cc548eaa0e97 100644\n--- a/Documentation/technical/index-format.txt\n+++ b/Documentation/technical/index-format.txt\n@@ -44,6 +44,13 @@ Git index format\n   localization, no special casing of directory separator '/'). Entries\n   with the same name are sorted by their stage field.\n \n+  An index entry typically represents a file. However, if sparse-checkout\n+  is enabled in cone mode (`core.sparseCheckoutCone` is enabled) and the\n+  `extensions.sparseIndex` extension is enabled, then the index may\n+  contain entries for directories outside of the sparse-checkout definition.\n+  These entries have mode `0040000`, include the `SKIP_WORKTREE` bit, and\n+  the path ends in a directory separator.\n+\n   32-bit ctime seconds, the last time a file's metadata changed\n     this is stat(2) data\n \ndiff --git a/Documentation/technical/sparse-index.txt b/Documentation/technical/sparse-index.txt\nnew file mode 100644\nindex 000000000000..aa116406a016\n--- /dev/null\n+++ b/Documentation/technical/sparse-index.txt\n@@ -0,0 +1,173 @@\n+Git Sparse-Index Design Document\n+================================\n+\n+The sparse-checkout feature allows users to focus a working directory on\n+a subset of the files at HEAD. The cone mode patterns, enabled by\n+`core.sparseCheckoutCone`, allow for very fast pattern matching to\n+discover which files at HEAD belong in the sparse-checkout cone.\n+\n+Three important scale dimensions for a Git worktree are:\n+\n+* `HEAD`: How many files are present at `HEAD`?\n+\n+* Populated: How many files are within the sparse-checkout cone.\n+\n+* Modified: How many files has the user modified in the working directory?\n+\n+We will use big-O notation -- O(X) -- to denote how expensive certain\n+operations are in terms of these dimensions.\n+\n+These dimensions are ordered by their magnitude: users (typically) modify\n+fewer files than are populated, and we can only populate files at `HEAD`.\n+These dimensions are also ordered by how expensive they are per item: it\n+is expensive to detect a modified file than it is to write one that we\n+know must be populated; changing `HEAD` only really requires updating the\n+index.\n+\n+Problems occur if there is an extreme imbalance in these dimensions. For\n+example, if `HEAD` contains millions of paths but the populated set has\n+only tens of thousands, then commands like `git status` and `git add` can\n+be dominated by operations that require O(`HEAD`) operations instead of\n+O(Populated). Primarily, the cost is in parsing and rewriting the index,\n+which is filled primarily with files at `HEAD` that are marked with the\n+`SKIP_WORKTREE` bit.\n+\n+The sparse-index intends to take these commands that read and modify the\n+index from O(`HEAD`) to O(Populated). To do this, we need to modify the\n+index format in a significant way: add \"sparse directory\" entries.\n+\n+With cone mode patterns, it is possible to detect when an entire\n+directory will have its contents outside of the sparse-checkout definition.\n+Instead of listing all of the files it contains as individual entries, a\n+sparse-index contains an entry with the directory name, referencing the\n+object ID of the tree at `HEAD` and marked with the `SKIP_WORKTREE` bit.\n+If we need to discover the details for paths within that directory, we\n+can parse trees to find that list.\n+\n+At time of writing, sparse-directory entries violate expectations about the\n+index format and its in-memory data structure. There are many consumers in\n+the codebase that expect to iterate through all of the index entries and\n+see only files. In addition, they expect to see all files at `HEAD`. One\n+way to handle this is to parse trees to replace a sparse-directory entry\n+with all of the files within that tree as the index is loaded. However,\n+parsing trees is slower than parsing the index format, so that is a slower\n+operation than if we left the index alone.\n+\n+The implementation plan below follows four phases to slowly integrate with\n+the sparse-index. The intention is to incrementally update Git commands to\n+interact safely with the sparse-index without significant slowdowns. This\n+may not always be possible, but the hope is that the primary commands that\n+users need in their daily work are dramatically improved.\n+\n+Phase I: Format and initial speedups\n+------------------------------------\n+\n+During this phase, Git learns to enable the sparse-index and safely parse\n+one. Protections are put in place so that every consumer of the in-memory\n+data structure can operate with its current assumption of every file at\n+`HEAD`.\n+\n+At first, every index parse will expand the sparse-directory entries into\n+the full list of paths at `HEAD`. This will be slower in all cases. The\n+only noticable change in behavior will be that the serialized index file\n+contains sparse-directory entries.\n+\n+To start, we use a new repository extension, `extensions.sparseIndex`, to\n+allow inserting sparse-directory entries into indexes with file format\n+versions 2, 3, and 4. This prevents Git versions that do not understand\n+the sparse-index from operating on one, but it also prevents other\n+operations that do not use the index at all. A new format, index v5, will\n+be introduced that includes sparse-directory entries by default. It might\n+also introduce other features that have been considered for improving the\n+index, as well.\n+\n+Next, consumers of the index will be guarded against operating on a\n+sparse-index by inserting calls to `ensure_full_index()` or\n+`expand_index_to_path()`. After these guards are in place, we can begin\n+leaving sparse-directory entries in the in-memory index structure.\n+\n+Even after inserting these guards, we will keep expanding sparse-indexes\n+for most Git commands using the `command_requires_full_index` repository\n+setting. This setting will be on by default and disabled one builtin at a\n+time until we have sufficient confidence that all of the index operations\n+are properly guarded.\n+\n+To complete this phase, the commands `git status` and `git add` will be\n+integrated with the sparse-index so that they operate with O(Populated)\n+performance. They will be carefully tested for operations within and\n+outside the sparse-checkout definition.\n+\n+Phase II: Careful integrations\n+------------------------------\n+\n+This phase focuses on ensuring that all index extensions and APIs work\n+well with a sparse-index. This requires significant increases to our test\n+coverage, especially for operations that interact with the working\n+directory outside of the sparse-checkout definition. Some of these\n+behaviors may not be the desirable ones, such as some tests already\n+marked for failure in `t1092-sparse-checkout-compatibility.sh`.\n+\n+The index extensions that may require special integrations are:\n+\n+* FS Monitor\n+* Untracked cache\n+\n+While integrating with these features, we should look for patterns that\n+might lead to better APIs for interacting with the index. Coalescing\n+common usage patterns into an API call can reduce the number of places\n+where sparse-directories need to be handled carefully.\n+\n+Phase III: Important command speedups\n+-------------------------------------\n+\n+At this point, the patterns for testing and implementing sparse-directory\n+logic should be relatively stable. This phase focuses on updating some of\n+the most common builtins that use the index to operate as O(Populated).\n+Here is a potential list of commands that could be valuable to integrate\n+at this point:\n+\n+* `git commit`\n+* `git checkout`\n+* `git merge`\n+* `git rebase`\n+\n+Hopefully, commands such as `git merge` and `git rebase` can benefit\n+instead from merge algorithms that do not use the index as a data\n+structure, such as the merge-ORT strategy. As these topics mature, we\n+may enable the ORT strategy by default for repositories using the\n+sparse-index feature.\n+\n+Along with `git status` and `git add`, these commands cover the majority\n+of users' interactions with the working directory. In addition, we can\n+integrate with these commands:\n+\n+* `git grep`\n+* `git rm`\n+\n+These have been proposed as some whose behavior could change when in a\n+repo with a sparse-checkout definition. It would be good to include this\n+behavior automatically when using a sparse-index. Some clarity is needed\n+to make the behavior switch clear to the user.\n+\n+This phase is the first where parallel work might be possible without too\n+much conflicts between topics.\n+\n+Phase IV: The long tail\n+-----------------------\n+\n+This last phase is less a \"phase\" and more \"the new normal\" after all of\n+the previous work.\n+\n+To start, the `command_requires_full_index` option could be removed in\n+favor of expanding only when hitting an API guard.\n+\n+There are many Git commands that could use special attention to operate as\n+O(Populated), while some might be so rare that it is acceptable to leave\n+them with additional overhead when a sparse-index is present.\n+\n+Here are some commands that might be useful to update:\n+\n+* `git sparse-checkout set`\n+* `git am`\n+* `git clean`\n+* `git stash`\n-- \ngitgitgadget\n\n"},{"id":"419376","messageId":"d3cfd34b84184bef42fe0892790d80091c9ca01b.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 03/20] t1092: clean up script quoting","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:42:46Z","receivedAt":"2021-03-16T16:44:08Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThis test was introduced in 19a0acc83e4 (t1092: test interesting\nsparse-checkout scenarios, 2021-01-23), but these issues with quoting\nwere not noticed until starting this follow-up series. The old mechanism\nwould drop quoting such as in\n\n   test_all_match git commit -m \"touch README.md\"\n\nThe above happened to work because README.md is a file in the\nrepository, so 'git commit -m touch REAMDE.md' would succeed by\naccident.\n\nOther cases included quoting for no good reason, so clean that up now.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/t1092-sparse-checkout-compatibility.sh | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 8cd3e5a8d227..3725d3997e70 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -96,20 +96,20 @@ init_repos () {\n run_on_sparse () {\n \t(\n \t\tcd sparse-checkout &&\n-\t\t$* >../sparse-checkout-out 2>../sparse-checkout-err\n+\t\t\"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n \t)\n }\n \n run_on_all () {\n \t(\n \t\tcd full-checkout &&\n-\t\t$* >../full-checkout-out 2>../full-checkout-err\n+\t\t\"$@\" >../full-checkout-out 2>../full-checkout-err\n \t) &&\n-\trun_on_sparse $*\n+\trun_on_sparse \"$@\"\n }\n \n test_all_match () {\n-\trun_on_all $* &&\n+\trun_on_all \"$@\" &&\n \ttest_cmp full-checkout-out sparse-checkout-out &&\n \ttest_cmp full-checkout-err sparse-checkout-err\n }\n@@ -119,7 +119,7 @@ test_expect_success 'status with options' '\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git status --porcelain=v2 -z -u &&\n \ttest_all_match git status --porcelain=v2 -uno &&\n-\trun_on_all \"touch README.md\" &&\n+\trun_on_all touch README.md &&\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git status --porcelain=v2 -z -u &&\n \ttest_all_match git status --porcelain=v2 -uno &&\n@@ -135,7 +135,7 @@ test_expect_success 'add, commit, checkout' '\n \twrite_script edit-contents <<-\\EOF &&\n \techo text >>$1\n \tEOF\n-\trun_on_all \"../edit-contents README.md\" &&\n+\trun_on_all ../edit-contents README.md &&\n \n \ttest_all_match git add README.md &&\n \ttest_all_match git status --porcelain=v2 &&\n@@ -144,7 +144,7 @@ test_expect_success 'add, commit, checkout' '\n \ttest_all_match git checkout HEAD~1 &&\n \ttest_all_match git checkout - &&\n \n-\trun_on_all \"../edit-contents README.md\" &&\n+\trun_on_all ../edit-contents README.md &&\n \n \ttest_all_match git add -A &&\n \ttest_all_match git status --porcelain=v2 &&\n@@ -153,7 +153,7 @@ test_expect_success 'add, commit, checkout' '\n \ttest_all_match git checkout HEAD~1 &&\n \ttest_all_match git checkout - &&\n \n-\trun_on_all \"../edit-contents deep/newfile\" &&\n+\trun_on_all ../edit-contents deep/newfile &&\n \n \ttest_all_match git status --porcelain=v2 -uno &&\n \ttest_all_match git status --porcelain=v2 &&\n@@ -186,7 +186,7 @@ test_expect_success 'diff --staged' '\n \twrite_script edit-contents <<-\\EOF &&\n \techo text >>README.md\n \tEOF\n-\trun_on_all \"../edit-contents\" &&\n+\trun_on_all ../edit-contents &&\n \n \ttest_all_match git diff &&\n \ttest_all_match git diff --staged &&\n@@ -280,7 +280,7 @@ test_expect_success 'clean' '\n \techo bogus >>.gitignore &&\n \trun_on_all cp ../.gitignore . &&\n \ttest_all_match git add .gitignore &&\n-\ttest_all_match git commit -m ignore-bogus-files &&\n+\ttest_all_match git commit -m \"ignore bogus files\" &&\n \n \trun_on_sparse mkdir folder1 &&\n \trun_on_all touch folder1/bogus &&\n-- \ngitgitgadget\n\n"},{"id":"419377","messageId":"d2197e895e4d4160fa369e2ba7d82e2e5a7fbc01.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 02/20] t/perf: add performance test for sparse operations","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:42:45Z","receivedAt":"2021-03-16T16:44:08Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nCreate a test script that takes the default performance test (the Git\ncodebase) and multiplies it by 256 using four layers of duplicated\ntrees of width four. This results in nearly one million blob entries in\nthe index. Then, we can clone this repository with sparse-checkout\npatterns that demonstrate four copies of the initial repository. Each\nclone will use a different index format or mode so peformance can be\ntested across the different options.\n\nNote that the initial repo is stripped of submodules before doing the\ncopies. This preserves the expected data shape of the sparse index,\nbecause directories containing submodules are not collapsed to a sparse\ndirectory entry.\n\nRun a few Git commands on these clones, especially those that use the\nindex (status, add, commit).\n\nHere are the results on my Linux machine:\n\nTest\n--------------------------------------------------------------\n2000.2: git status (full-index-v3)             0.37(0.30+0.09)\n2000.3: git status (full-index-v4)             0.39(0.32+0.10)\n2000.4: git add -A (full-index-v3)             1.42(1.06+0.20)\n2000.5: git add -A (full-index-v4)             1.26(0.98+0.16)\n2000.6: git add . (full-index-v3)              1.40(1.04+0.18)\n2000.7: git add . (full-index-v4)              1.26(0.98+0.17)\n2000.8: git commit -a -m A (full-index-v3)     1.42(1.11+0.16)\n2000.9: git commit -a -m A (full-index-v4)     1.33(1.08+0.16)\n\nIt is perhaps noteworthy that there is an improvement when using index\nversion 4. This is because the v3 index uses 108 MiB while the v4\nindex uses 80 MiB. Since the repeated portions of the directories are\nvery short (f3/f1/f2, for example) this ratio is less pronounced than in\nsimilarly-sized real repositories.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/perf/p2000-sparse-operations.sh | 85 +++++++++++++++++++++++++++++++\n 1 file changed, 85 insertions(+)\n create mode 100755 t/perf/p2000-sparse-operations.sh\n\ndiff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh\nnew file mode 100755\nindex 000000000000..2fbc81b22119\n--- /dev/null\n+++ b/t/perf/p2000-sparse-operations.sh\n@@ -0,0 +1,85 @@\n+#!/bin/sh\n+\n+test_description=\"test performance of Git operations using the index\"\n+\n+. ./perf-lib.sh\n+\n+test_perf_default_repo\n+\n+SPARSE_CONE=f2/f4/f1\n+\n+test_expect_success 'setup repo and indexes' '\n+\tgit reset --hard HEAD &&\n+\t# Remove submodules from the example repo, because our\n+\t# duplication of the entire repo creates an unlikly data shape.\n+\tgit config --file .gitmodules --get-regexp \"submodule.*.path\" >modules &&\n+\tgit rm -f .gitmodules &&\n+\tfor module in $(awk \"{print \\$2}\" modules)\n+\tdo\n+\t\tgit rm $module || return 1\n+\tdone &&\n+\tgit commit -m \"remove submodules\" &&\n+\n+\techo bogus >a &&\n+\tcp a b &&\n+\tgit add a b &&\n+\tgit commit -m \"level 0\" &&\n+\tBLOB=$(git rev-parse HEAD:a) &&\n+\tOLD_COMMIT=$(git rev-parse HEAD) &&\n+\tOLD_TREE=$(git rev-parse HEAD^{tree}) &&\n+\n+\tfor i in $(test_seq 1 4)\n+\tdo\n+\t\tcat >in <<-EOF &&\n+\t\t\t100755 blob $BLOB\ta\n+\t\t\t040000 tree $OLD_TREE\tf1\n+\t\t\t040000 tree $OLD_TREE\tf2\n+\t\t\t040000 tree $OLD_TREE\tf3\n+\t\t\t040000 tree $OLD_TREE\tf4\n+\t\tEOF\n+\t\tNEW_TREE=$(git mktree <in) &&\n+\t\tNEW_COMMIT=$(git commit-tree $NEW_TREE -p $OLD_COMMIT -m \"level $i\") &&\n+\t\tOLD_TREE=$NEW_TREE &&\n+\t\tOLD_COMMIT=$NEW_COMMIT || return 1\n+\tdone &&\n+\n+\tgit sparse-checkout init --cone &&\n+\tgit branch -f wide $OLD_COMMIT &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v3 &&\n+\t(\n+\t\tcd full-index-v3 &&\n+\t\tgit sparse-checkout init --cone &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 3 &&\n+\t\tgit update-index --index-version=3\n+\t) &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v4 &&\n+\t(\n+\t\tcd full-index-v4 &&\n+\t\tgit sparse-checkout init --cone &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 4 &&\n+\t\tgit update-index --index-version=4\n+\t)\n+'\n+\n+test_perf_on_all () {\n+\tcommand=\"$@\"\n+\tfor repo in full-index-v3 full-index-v4\n+\tdo\n+\t\ttest_perf \"$command ($repo)\" \"\n+\t\t\t(\n+\t\t\t\tcd $repo &&\n+\t\t\t\techo >>$SPARSE_CONE/a &&\n+\t\t\t\t$command\n+\t\t\t)\n+\t\t\"\n+\tdone\n+}\n+\n+test_perf_on_all git status\n+test_perf_on_all git add -A\n+test_perf_on_all git add .\n+test_perf_on_all git commit -a -m A\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"419378","messageId":"f4ad081f25bb2760937e3f64aba80aa1455a8e17.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 08/20] test-tool: don't force full index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:42:51Z","receivedAt":"2021-03-16T16:44:08Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWe will use 'test-tool read-cache --table' to check that a sparse\nindex is written as part of init_repos. Since we will no longer always\nexpand a sparse index into a full index, add an '--expand' parameter\nthat adds a call to ensure_full_index() so we can compare a sparse index\ndirectly against a full index, or at least what the in-memory index\nlooks like when expanded in this way.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/helper/test-read-cache.c               | 13 ++++++++++++-\n t/t1092-sparse-checkout-compatibility.sh |  5 +++++\n 2 files changed, 17 insertions(+), 1 deletion(-)\n\ndiff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c\nindex 6cfd8f2de71c..b52c174acc7a 100644\n--- a/t/helper/test-read-cache.c\n+++ b/t/helper/test-read-cache.c\n@@ -4,6 +4,7 @@\n #include \"blob.h\"\n #include \"commit.h\"\n #include \"tree.h\"\n+#include \"sparse-index.h\"\n \n static void print_cache_entry(struct cache_entry *ce)\n {\n@@ -35,13 +36,19 @@ int cmd__read_cache(int argc, const char **argv)\n \tstruct repository *r = the_repository;\n \tint i, cnt = 1;\n \tconst char *name = NULL;\n-\tint table = 0;\n+\tint table = 0, expand = 0;\n+\n+\tinitialize_the_repository();\n+\tprepare_repo_settings(r);\n+\tr->settings.command_requires_full_index = 0;\n \n \tfor (++argv, --argc; *argv && starts_with(*argv, \"--\"); ++argv, --argc) {\n \t\tif (skip_prefix(*argv, \"--print-and-refresh=\", &name))\n \t\t\tcontinue;\n \t\tif (!strcmp(*argv, \"--table\"))\n \t\t\ttable = 1;\n+\t\telse if (!strcmp(*argv, \"--expand\"))\n+\t\t\texpand = 1;\n \t}\n \n \tif (argc == 1)\n@@ -51,6 +58,10 @@ int cmd__read_cache(int argc, const char **argv)\n \n \tfor (i = 0; i < cnt; i++) {\n \t\trepo_read_index(r);\n+\n+\t\tif (expand)\n+\t\t\tensure_full_index(r->index);\n+\n \t\tif (name) {\n \t\t\tint pos;\n \ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex de5d8461c993..a1aea141c62c 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -130,6 +130,11 @@ test_sparse_match () {\n \ttest_cmp sparse-checkout-err sparse-index-err\n }\n \n+test_expect_success 'expanded in-memory index matches full index' '\n+\tinit_repos &&\n+\ttest_sparse_match test-tool read-cache --expand --table\n+'\n+\n test_expect_success 'status with options' '\n \tinit_repos &&\n \ttest_all_match git status --porcelain=v2 &&\n-- \ngitgitgadget\n\n"},{"id":"419379","messageId":"fae5663a17bb565619a7ac1a2172063a80a86cde.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 06/20] t1092: compare sparse-checkout to sparse-index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:42:49Z","receivedAt":"2021-03-16T16:44:08Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nAdd a new 'sparse-index' repo alongside the 'full-checkout' and\n'sparse-checkout' repos in t1092-sparse-checkout-compatibility.sh. Also\nadd run_on_sparse and test_sparse_match helpers. These helpers will be\nused when the sparse index is implemented.\n\nAdd the GIT_TEST_SPARSE_INDEX environment variable to enable the\nsparse-index by default. This can be enabled across all tests, but that\nwill only affect cases where the sparse-checkout feature is enabled.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/README                                 |  3 +++\n t/t1092-sparse-checkout-compatibility.sh | 24 ++++++++++++++++++++----\n 2 files changed, 23 insertions(+), 4 deletions(-)\n\ndiff --git a/t/README b/t/README\nindex 593d4a4e270c..b98bc563aab5 100644\n--- a/t/README\n+++ b/t/README\n@@ -439,6 +439,9 @@ and \"sha256\".\n GIT_TEST_WRITE_REV_INDEX=<boolean>, when true enables the\n 'pack.writeReverseIndex' setting.\n \n+GIT_TEST_SPARSE_INDEX=<boolean>, when true enables index writes to use the\n+sparse-index format by default.\n+\n Naming Tests\n ------------\n \ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 3725d3997e70..de5d8461c993 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -7,6 +7,7 @@ test_description='compare full workdir to sparse workdir'\n test_expect_success 'setup' '\n \tgit init initial-repo &&\n \t(\n+\t\tGIT_TEST_SPARSE_INDEX=0 &&\n \t\tcd initial-repo &&\n \t\techo a >a &&\n \t\techo \"after deep\" >e &&\n@@ -87,23 +88,32 @@ init_repos () {\n \n \tcp -r initial-repo sparse-checkout &&\n \tgit -C sparse-checkout reset --hard &&\n-\tgit -C sparse-checkout sparse-checkout init --cone &&\n+\n+\tcp -r initial-repo sparse-index &&\n+\tgit -C sparse-index reset --hard &&\n \n \t# initialize sparse-checkout definitions\n-\tgit -C sparse-checkout sparse-checkout set deep\n+\tgit -C sparse-checkout sparse-checkout init --cone &&\n+\tgit -C sparse-checkout sparse-checkout set deep &&\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout init --cone &&\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep\n }\n \n run_on_sparse () {\n \t(\n \t\tcd sparse-checkout &&\n-\t\t\"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n+\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n+\t) &&\n+\t(\n+\t\tcd sparse-index &&\n+\t\tGIT_TEST_SPARSE_INDEX=1 \"$@\" >../sparse-index-out 2>../sparse-index-err\n \t)\n }\n \n run_on_all () {\n \t(\n \t\tcd full-checkout &&\n-\t\t\"$@\" >../full-checkout-out 2>../full-checkout-err\n+\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../full-checkout-out 2>../full-checkout-err\n \t) &&\n \trun_on_sparse \"$@\"\n }\n@@ -114,6 +124,12 @@ test_all_match () {\n \ttest_cmp full-checkout-err sparse-checkout-err\n }\n \n+test_sparse_match () {\n+\trun_on_sparse \"$@\" &&\n+\ttest_cmp sparse-checkout-out sparse-index-out &&\n+\ttest_cmp sparse-checkout-err sparse-index-err\n+}\n+\n test_expect_success 'status with options' '\n \tinit_repos &&\n \ttest_all_match git status --porcelain=v2 &&\n-- \ngitgitgadget\n\n"},{"id":"419380","messageId":"4472118cf903d8dbcfdefe68af9166711d4ad6c6.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 04/20] sparse-index: add guard to ensure full index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:42:47Z","receivedAt":"2021-03-16T16:44:08Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nUpcoming changes will introduce modifications to the index format that\nallow sparse directories. It will be useful to have a mechanism for\nconverting those sparse index files into full indexes by walking the\ntree at those sparse directories. Name this method ensure_full_index()\nas it will guarantee that the index is fully expanded.\n\nThis method is not implemented yet, and instead we focus on the\nscaffolding to declare it and call it at the appropriate time.\n\nAdd a 'command_requires_full_index' member to struct repo_settings. This\nwill be an indicator that we need the index in full mode to do certain\nindex operations. This starts as being true for every command, then we\nwill set it to false as some commands integrate with sparse indexes.\n\nIf 'command_requires_full_index' is true, then we will immediately\nexpand a sparse index to a full one upon reading from disk. This\nsuffices for now, but we will want to add more callers to\nensure_full_index() later.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Makefile        |  1 +\n repo-settings.c |  8 ++++++++\n repository.c    | 11 ++++++++++-\n repository.h    |  2 ++\n sparse-index.c  |  8 ++++++++\n sparse-index.h  |  7 +++++++\n 6 files changed, 36 insertions(+), 1 deletion(-)\n create mode 100644 sparse-index.c\n create mode 100644 sparse-index.h\n\ndiff --git a/Makefile b/Makefile\nindex dfb0f1000fa3..89b1d5374107 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -985,6 +985,7 @@ LIB_OBJS += setup.o\n LIB_OBJS += shallow.o\n LIB_OBJS += sideband.o\n LIB_OBJS += sigchain.o\n+LIB_OBJS += sparse-index.o\n LIB_OBJS += split-index.o\n LIB_OBJS += stable-qsort.o\n LIB_OBJS += strbuf.o\ndiff --git a/repo-settings.c b/repo-settings.c\nindex f7fff0f5ab83..d63569e4041e 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -77,4 +77,12 @@ void prepare_repo_settings(struct repository *r)\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+\t/*\n+\t * This setting guards all index reads to require a full index\n+\t * over a sparse index. After suitable guards are placed in the\n+\t * codebase around uses of the index, this setting will be\n+\t * removed.\n+\t */\n+\tr->settings.command_requires_full_index = 1;\n }\ndiff --git a/repository.c b/repository.c\nindex c98298acd017..a8acae002f71 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -10,6 +10,7 @@\n #include \"object.h\"\n #include \"lockfile.h\"\n #include \"submodule-config.h\"\n+#include \"sparse-index.h\"\n \n /* The main repository */\n static struct repository the_repo;\n@@ -261,6 +262,8 @@ void repo_clear(struct repository *repo)\n \n int repo_read_index(struct repository *repo)\n {\n+\tint res;\n+\n \tif (!repo->index)\n \t\trepo->index = xcalloc(1, sizeof(*repo->index));\n \n@@ -270,7 +273,13 @@ int repo_read_index(struct repository *repo)\n \telse if (repo->index->repo != repo)\n \t\tBUG(\"repo's index should point back at itself\");\n \n-\treturn read_index_from(repo->index, repo->index_file, repo->gitdir);\n+\tres = read_index_from(repo->index, repo->index_file, repo->gitdir);\n+\n+\tprepare_repo_settings(repo);\n+\tif (repo->settings.command_requires_full_index)\n+\t\tensure_full_index(repo->index);\n+\n+\treturn res;\n }\n \n int repo_hold_locked_index(struct repository *repo,\ndiff --git a/repository.h b/repository.h\nindex b385ca3c94b6..e06a23015697 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -41,6 +41,8 @@ struct repo_settings {\n \tenum fetch_negotiation_setting fetch_negotiation_algorithm;\n \n \tint core_multi_pack_index;\n+\n+\tunsigned command_requires_full_index:1;\n };\n \n struct repository {\ndiff --git a/sparse-index.c b/sparse-index.c\nnew file mode 100644\nindex 000000000000..82183ead563b\n--- /dev/null\n+++ b/sparse-index.c\n@@ -0,0 +1,8 @@\n+#include \"cache.h\"\n+#include \"repository.h\"\n+#include \"sparse-index.h\"\n+\n+void ensure_full_index(struct index_state *istate)\n+{\n+\t/* intentionally left blank */\n+}\ndiff --git a/sparse-index.h b/sparse-index.h\nnew file mode 100644\nindex 000000000000..09a20d036c46\n--- /dev/null\n+++ b/sparse-index.h\n@@ -0,0 +1,7 @@\n+#ifndef SPARSE_INDEX_H__\n+#define SPARSE_INDEX_H__\n+\n+struct index_state;\n+void ensure_full_index(struct index_state *istate);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"419381","messageId":"4780076a50df8c4db73c04baa95d8654fd04f38b.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 09/20] unpack-trees: ensure full index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:42:52Z","receivedAt":"2021-03-16T16:44:08Z","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 next change will translate full indexes into sparse indexes at write\ntime. The existing logic provides a way for every sparse index to be\nexpanded to a full index at read time. However, there are cases where an\nindex is written and then continues to be used in-memory to perform\nfurther updates.\n\nunpack_trees() is frequently called after such a write. In particular,\ncommands like 'git reset' do this double-update of the index.\n\nEnsure that we have a full index when entering unpack_trees(), but only\nwhen command_requires_full_index is true. This is always true at the\nmoment, but we will later relax that after unpack_trees() is updated to\nhandle sparse directory entries.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n unpack-trees.c | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex eb8fcda31ba7..2da3e5ec77a1 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -1570,6 +1570,7 @@ static int verify_absent(const struct cache_entry *,\n  */\n int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)\n {\n+\tstruct repository *repo = the_repository;\n \tint i, ret;\n \tstatic struct cache_entry *dfc;\n \tstruct pattern_list pl;\n@@ -1581,6 +1582,12 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n \ttrace_performance_enter();\n \ttrace2_region_enter(\"unpack_trees\", \"unpack_trees\", the_repository);\n \n+\tprepare_repo_settings(repo);\n+\tif (repo->settings.command_requires_full_index) {\n+\t\tensure_full_index(o->src_index);\n+\t\tensure_full_index(o->dst_index);\n+\t}\n+\n \tif (!core_apply_sparse_checkout || !o->update)\n \t\to->skip_sparse_checkout = 1;\n \tif (!o->skip_sparse_checkout && !o->pl) {\n-- \ngitgitgadget\n\n"},{"id":"419382","messageId":"dffe8821fde290a1e19b2968098b007b9ac213e6.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 07/20] test-read-cache: print cache entries with --table","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:42:50Z","receivedAt":"2021-03-16T16:44:08Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThis table is helpful for discovering data in the index to ensure it is\nbeing written correctly, especially as we build and test the\nsparse-index. This table includes an output format similar to 'git\nls-tree', but should not be compared to that directly. The biggest\nreasons are that 'git ls-tree' includes a tree entry for every\nsubdirectory, even those that would not appear as a sparse directory in\na sparse-index. Further, 'git ls-tree' does not use a trailing directory\nseparator for its tree rows.\n\nThis does not print the stat() information for the blobs. That could be\nadded in a future change with another option. The tests that are added\nin the next few changes care only about the object types and IDs.\n\nTo make the option parsing slightly more robust, wrap the string\ncomparisons in a loop adapted from test-dir-iterator.c.\n\nCare must be taken with the final check for the 'cnt' variable. We\ncontinue the expectation that the numerical value is the final argument.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/helper/test-read-cache.c | 55 +++++++++++++++++++++++++++++++-------\n 1 file changed, 45 insertions(+), 10 deletions(-)\n\ndiff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c\nindex 244977a29bdf..6cfd8f2de71c 100644\n--- a/t/helper/test-read-cache.c\n+++ b/t/helper/test-read-cache.c\n@@ -1,36 +1,71 @@\n #include \"test-tool.h\"\n #include \"cache.h\"\n #include \"config.h\"\n+#include \"blob.h\"\n+#include \"commit.h\"\n+#include \"tree.h\"\n+\n+static void print_cache_entry(struct cache_entry *ce)\n+{\n+\tconst char *type;\n+\tprintf(\"%06o \", ce->ce_mode & 0177777);\n+\n+\tif (S_ISSPARSEDIR(ce->ce_mode))\n+\t\ttype = tree_type;\n+\telse if (S_ISGITLINK(ce->ce_mode))\n+\t\ttype = commit_type;\n+\telse\n+\t\ttype = blob_type;\n+\n+\tprintf(\"%s %s\\t%s\\n\",\n+\t       type,\n+\t       oid_to_hex(&ce->oid),\n+\t       ce->name);\n+}\n+\n+static void print_cache(struct index_state *istate)\n+{\n+\tint i;\n+\tfor (i = 0; i < istate->cache_nr; i++)\n+\t\tprint_cache_entry(istate->cache[i]);\n+}\n \n int cmd__read_cache(int argc, const char **argv)\n {\n+\tstruct repository *r = the_repository;\n \tint i, cnt = 1;\n \tconst char *name = NULL;\n+\tint table = 0;\n \n-\tif (argc > 1 && skip_prefix(argv[1], \"--print-and-refresh=\", &name)) {\n-\t\targc--;\n-\t\targv++;\n+\tfor (++argv, --argc; *argv && starts_with(*argv, \"--\"); ++argv, --argc) {\n+\t\tif (skip_prefix(*argv, \"--print-and-refresh=\", &name))\n+\t\t\tcontinue;\n+\t\tif (!strcmp(*argv, \"--table\"))\n+\t\t\ttable = 1;\n \t}\n \n-\tif (argc == 2)\n-\t\tcnt = strtol(argv[1], NULL, 0);\n+\tif (argc == 1)\n+\t\tcnt = strtol(argv[0], NULL, 0);\n \tsetup_git_directory();\n \tgit_config(git_default_config, NULL);\n+\n \tfor (i = 0; i < cnt; i++) {\n-\t\tread_cache();\n+\t\trepo_read_index(r);\n \t\tif (name) {\n \t\t\tint pos;\n \n-\t\t\trefresh_index(&the_index, REFRESH_QUIET,\n+\t\t\trefresh_index(r->index, REFRESH_QUIET,\n \t\t\t\t      NULL, NULL, NULL);\n-\t\t\tpos = index_name_pos(&the_index, name, strlen(name));\n+\t\t\tpos = index_name_pos(r->index, name, strlen(name));\n \t\t\tif (pos < 0)\n \t\t\t\tdie(\"%s not in index\", name);\n \t\t\tprintf(\"%s is%s up to date\\n\", name,\n-\t\t\t       ce_uptodate(the_index.cache[pos]) ? \"\" : \" not\");\n+\t\t\t       ce_uptodate(r->index->cache[pos]) ? \"\" : \" not\");\n \t\t\twrite_file(name, \"%d\\n\", i);\n \t\t}\n-\t\tdiscard_cache();\n+\t\tif (table)\n+\t\t\tprint_cache(r->index);\n+\t\tdiscard_index(r->index);\n \t}\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"419383","messageId":"99292cdbaae488101d1c247ab94dc4b3b04d0311.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 05/20] sparse-index: implement ensure_full_index()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:42:48Z","receivedAt":"2021-03-16T16:44:08Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWe will mark an in-memory index_state as having sparse directory entries\nwith the sparse_index bit. These currently cannot exist, but we will add\na mechanism for collapsing a full index to a sparse one in a later\nchange. That will happen at write time, so we must first allow parsing\nthe format before writing it.\n\nCommands or methods that require a full index in order to operate can\ncall ensure_full_index() to expand that index in-memory. This requires\nparsing trees using that index's repository.\n\nSparse directory entries have a specific 'ce_mode' value. The macro\nS_ISSPARSEDIR(ce->ce_mode) can check if a cache_entry 'ce' has this type.\nThis ce_mode is not possible with the existing index formats, so we don't\nalso verify all properties of a sparse-directory entry, which are:\n\n 1. ce->ce_mode == 0040000\n 2. ce->flags & CE_SKIP_WORKTREE is true\n 3. ce->name[ce->namelen - 1] == '/' (ends in dir separator)\n 4. ce->oid references a tree object.\n\nThese are all semi-enforced in ensure_full_index() to some extent. Any\ndeviation will cause a warning at minimum or a failure in the worst\ncase.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache.h        | 13 ++++++-\n read-cache.c   |  9 +++++\n sparse-index.c | 98 +++++++++++++++++++++++++++++++++++++++++++++++++-\n 3 files changed, 118 insertions(+), 2 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex c2f8a8eadf67..abb00a068e5d 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -204,6 +204,8 @@ struct cache_entry {\n #error \"CE_EXTENDED_FLAGS out of range\"\n #endif\n \n+#define S_ISSPARSEDIR(m) ((m) == S_IFDIR)\n+\n /* Forward structure decls */\n struct pathspec;\n struct child_process;\n@@ -319,7 +321,14 @@ struct index_state {\n \t\t drop_cache_tree : 1,\n \t\t updated_workdir : 1,\n \t\t updated_skipworktree : 1,\n-\t\t fsmonitor_has_run_once : 1;\n+\t\t fsmonitor_has_run_once : 1,\n+\n+\t\t /*\n+\t\t  * sparse_index == 1 when sparse-directory\n+\t\t  * entries exist. Requires sparse-checkout\n+\t\t  * in cone mode.\n+\t\t  */\n+\t\t sparse_index : 1;\n \tstruct hashmap name_hash;\n \tstruct hashmap dir_hash;\n \tstruct object_id oid;\n@@ -722,6 +731,8 @@ int read_index_from(struct index_state *, const char *path,\n \t\t    const char *gitdir);\n int is_index_unborn(struct index_state *);\n \n+void ensure_full_index(struct index_state *istate);\n+\n /* For use with `write_locked_index()`. */\n #define COMMIT_LOCK\t\t(1 << 0)\n #define SKIP_IF_UNCHANGED\t(1 << 1)\ndiff --git a/read-cache.c b/read-cache.c\nindex 1e9a50c6c734..dd3980c12b53 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -101,6 +101,9 @@ static const char *alternate_index_output;\n \n static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n {\n+\tif (S_ISSPARSEDIR(ce->ce_mode))\n+\t\tistate->sparse_index = 1;\n+\n \tistate->cache[nr] = ce;\n \tadd_name_hash(istate, ce);\n }\n@@ -2273,6 +2276,12 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)\n \ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\",\n \t\t\t   istate->cache_nr);\n \n+\tif (!istate->repo)\n+\t\tistate->repo = the_repository;\n+\tprepare_repo_settings(istate->repo);\n+\tif (istate->repo->settings.command_requires_full_index)\n+\t\tensure_full_index(istate);\n+\n \treturn istate->cache_nr;\n \n unmap:\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 82183ead563b..7095378a1b28 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -1,8 +1,104 @@\n #include \"cache.h\"\n #include \"repository.h\"\n #include \"sparse-index.h\"\n+#include \"tree.h\"\n+#include \"pathspec.h\"\n+#include \"trace2.h\"\n+\n+static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n+{\n+\tALLOC_GROW(istate->cache, nr + 1, istate->cache_alloc);\n+\n+\tistate->cache[nr] = ce;\n+\tadd_name_hash(istate, ce);\n+}\n+\n+static int add_path_to_index(const struct object_id *oid,\n+\t\t\t     struct strbuf *base, const char *path,\n+\t\t\t     unsigned int mode, void *context)\n+{\n+\tstruct index_state *istate = (struct index_state *)context;\n+\tstruct cache_entry *ce;\n+\tsize_t len = base->len;\n+\n+\tif (S_ISDIR(mode))\n+\t\treturn READ_TREE_RECURSIVE;\n+\n+\tstrbuf_addstr(base, path);\n+\n+\tce = make_cache_entry(istate, mode, oid, base->buf, 0, 0);\n+\tce->ce_flags |= CE_SKIP_WORKTREE;\n+\tset_index_entry(istate, istate->cache_nr++, ce);\n+\n+\tstrbuf_setlen(base, len);\n+\treturn 0;\n+}\n \n void ensure_full_index(struct index_state *istate)\n {\n-\t/* intentionally left blank */\n+\tint i;\n+\tstruct index_state *full;\n+\tstruct strbuf base = STRBUF_INIT;\n+\n+\tif (!istate || !istate->sparse_index)\n+\t\treturn;\n+\n+\tif (!istate->repo)\n+\t\tistate->repo = the_repository;\n+\n+\ttrace2_region_enter(\"index\", \"ensure_full_index\", istate->repo);\n+\n+\t/* initialize basics of new index */\n+\tfull = xcalloc(1, sizeof(struct index_state));\n+\tmemcpy(full, istate, sizeof(struct index_state));\n+\n+\t/* then change the necessary things */\n+\tfull->sparse_index = 0;\n+\tfull->cache_alloc = (3 * istate->cache_alloc) / 2;\n+\tfull->cache_nr = 0;\n+\tALLOC_ARRAY(full->cache, full->cache_alloc);\n+\n+\tfor (i = 0; i < istate->cache_nr; i++) {\n+\t\tstruct cache_entry *ce = istate->cache[i];\n+\t\tstruct tree *tree;\n+\t\tstruct pathspec ps;\n+\n+\t\tif (!S_ISSPARSEDIR(ce->ce_mode)) {\n+\t\t\tset_index_entry(full, full->cache_nr++, ce);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!(ce->ce_flags & CE_SKIP_WORKTREE))\n+\t\t\twarning(_(\"index entry is a directory, but not sparse (%08x)\"),\n+\t\t\t\tce->ce_flags);\n+\n+\t\t/* recursively walk into cd->name */\n+\t\ttree = lookup_tree(istate->repo, &ce->oid);\n+\n+\t\tmemset(&ps, 0, sizeof(ps));\n+\t\tps.recursive = 1;\n+\t\tps.has_wildcard = 1;\n+\t\tps.max_depth = -1;\n+\n+\t\tstrbuf_setlen(&base, 0);\n+\t\tstrbuf_add(&base, ce->name, strlen(ce->name));\n+\n+\t\tread_tree_at(istate->repo, tree, &base, &ps,\n+\t\t\t     add_path_to_index, full);\n+\n+\t\t/* free directory entries. full entries are re-used */\n+\t\tdiscard_cache_entry(ce);\n+\t}\n+\n+\t/* Copy back into original index. */\n+\tmemcpy(&istate->name_hash, &full->name_hash, sizeof(full->name_hash));\n+\tistate->sparse_index = 0;\n+\tfree(istate->cache);\n+\tistate->cache = full->cache;\n+\tistate->cache_nr = full->cache_nr;\n+\tistate->cache_alloc = full->cache_alloc;\n+\n+\tstrbuf_release(&base);\n+\tfree(full);\n+\n+\ttrace2_region_leave(\"index\", \"ensure_full_index\", istate->repo);\n }\n-- \ngitgitgadget\n\n"},{"id":"419384","messageId":"4000c5cdd4cf6008358a02d1b0244b24e61b3e3e.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 13/20] unpack-trees: allow sparse directories","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:42:56Z","receivedAt":"2021-03-16T16:44:09Z","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 index_pos_by_traverse_info() currently throws a BUG() when a\ndirectory entry exists exactly in the index. We need to consider that it\nis possible to have a directory in a sparse index as long as that entry\nis itself marked with the skip-worktree bit.\n\nThe 'pos' variable is assigned a negative value if an exact match is not\nfound. Since a directory name can be an exact match, it is no longer an\nerror to have a nonnegative 'pos' value.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n unpack-trees.c | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 2da3e5ec77a1..e81d82d72d89 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -749,9 +749,12 @@ static int index_pos_by_traverse_info(struct name_entry *names,\n \tstrbuf_make_traverse_path(&name, info, names->path, names->pathlen);\n \tstrbuf_addch(&name, '/');\n \tpos = index_name_pos(o->src_index, name.buf, name.len);\n-\tif (pos >= 0)\n-\t\tBUG(\"This is a directory and should not exist in index\");\n-\tpos = -pos - 1;\n+\tif (pos >= 0) {\n+\t\tif (!o->src_index->sparse_index ||\n+\t\t    !(o->src_index->cache[pos]->ce_flags & CE_SKIP_WORKTREE))\n+\t\t\tBUG(\"This is a directory and should not exist in index\");\n+\t} else\n+\t\tpos = -pos - 1;\n \tif (pos >= o->src_index->cache_nr ||\n \t    !starts_with(o->src_index->cache[pos]->name, name.buf) ||\n \t    (pos > 0 && starts_with(o->src_index->cache[pos-1]->name, name.buf)))\n-- \ngitgitgadget\n\n"},{"id":"419385","messageId":"b77cd6b02265cf0eab0f9936d19ad99f0ffea3b9.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 12/20] submodule: sparse-index should not collapse links","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:42:55Z","receivedAt":"2021-03-16T16:44:09Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nA submodule is stored as a \"Git link\" that actually points to a commit\nwithin a submodule. Submodules are populated or not depending on\nsubmodule configuration, not sparse-checkout. To ensure that the\nsparse-index feature integrates correctly with submodules, we should not\ncollapse a directory if there is a Git link within its range.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n sparse-index.c                           |  1 +\n t/t1092-sparse-checkout-compatibility.sh | 17 +++++++++++++++++\n 2 files changed, 18 insertions(+)\n\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 619ff7c2e217..7631f7bd00b7 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -52,6 +52,7 @@ static int convert_to_sparse_rec(struct index_state *istate,\n \t\tstruct cache_entry *ce = istate->cache[i];\n \n \t\tif (ce_stage(ce) ||\n+\t\t    S_ISGITLINK(ce->ce_mode) ||\n \t\t    !(ce->ce_flags & CE_SKIP_WORKTREE))\n \t\t\tcan_convert = 0;\n \t}\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 1e888d195122..cba5f89b1e96 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -376,4 +376,21 @@ test_expect_success 'clean' '\n \ttest_sparse_match test_path_is_dir folder1\n '\n \n+test_expect_success 'submodule handling' '\n+\tinit_repos &&\n+\n+\ttest_all_match mkdir modules &&\n+\ttest_all_match touch modules/a &&\n+\ttest_all_match git add modules &&\n+\ttest_all_match git commit -m \"add modules directory\" &&\n+\n+\trun_on_all git submodule add \"$(pwd)/initial-repo\" modules/sub &&\n+\ttest_all_match git commit -m \"add submodule\" &&\n+\n+\t# having a submodule prevents \"modules\" from collapse\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tgrep \"100644 blob .*\tmodules/a\" cache &&\n+\tgrep \"160000 commit $(git -C initial-repo rev-parse HEAD)\tmodules/sub\" cache\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"419386","messageId":"33fdba2b8cfdf3b7d003989a0ba1264ae8f9bb99.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 10/20] sparse-checkout: hold pattern list in index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:42:53Z","receivedAt":"2021-03-16T16:44:09Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nAs we modify the sparse-checkout definition, we perform index operations\non a pattern_list that only exists in-memory. This allows easy backing\nout in case the index update fails.\n\nHowever, if the index write itself cares about the sparse-checkout\npattern set, we need access to that in-memory copy. Place a pointer to\na 'struct pattern_list' in the index so we can access this on-demand.\nThis will be used in the next change which uses the sparse-checkout\ndefinition to filter out directories that are outside the sparse cone.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/sparse-checkout.c | 17 ++++++++++-------\n cache.h                   |  2 ++\n 2 files changed, 12 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex 2306a9ad98e0..e00b82af727b 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -110,6 +110,8 @@ static int update_working_directory(struct pattern_list *pl)\n \tif (is_index_unborn(r->index))\n \t\treturn UPDATE_SPARSITY_SUCCESS;\n \n+\tr->index->sparse_checkout_patterns = pl;\n+\n \tmemset(&o, 0, sizeof(o));\n \to.verbose_update = isatty(2);\n \to.update = 1;\n@@ -138,6 +140,7 @@ static int update_working_directory(struct pattern_list *pl)\n \telse\n \t\trollback_lock_file(&lock_file);\n \n+\tr->index->sparse_checkout_patterns = NULL;\n \treturn result;\n }\n \n@@ -517,19 +520,18 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)\n {\n \tint result;\n \tint changed_config = 0;\n-\tstruct pattern_list pl;\n-\tmemset(&pl, 0, sizeof(pl));\n+\tstruct pattern_list *pl = xcalloc(1, sizeof(*pl));\n \n \tswitch (m) {\n \tcase ADD:\n \t\tif (core_sparse_checkout_cone)\n-\t\t\tadd_patterns_cone_mode(argc, argv, &pl);\n+\t\t\tadd_patterns_cone_mode(argc, argv, pl);\n \t\telse\n-\t\t\tadd_patterns_literal(argc, argv, &pl);\n+\t\t\tadd_patterns_literal(argc, argv, pl);\n \t\tbreak;\n \n \tcase REPLACE:\n-\t\tadd_patterns_from_input(&pl, argc, argv);\n+\t\tadd_patterns_from_input(pl, argc, argv);\n \t\tbreak;\n \t}\n \n@@ -539,12 +541,13 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)\n \t\tchanged_config = 1;\n \t}\n \n-\tresult = write_patterns_and_update(&pl);\n+\tresult = write_patterns_and_update(pl);\n \n \tif (result && changed_config)\n \t\tset_config(MODE_NO_PATTERNS);\n \n-\tclear_pattern_list(&pl);\n+\tclear_pattern_list(pl);\n+\tfree(pl);\n \treturn result;\n }\n \ndiff --git a/cache.h b/cache.h\nindex abb00a068e5d..759ca92e2ecc 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -307,6 +307,7 @@ static inline unsigned int canon_mode(unsigned int mode)\n struct split_index;\n struct untracked_cache;\n struct progress;\n+struct pattern_list;\n \n struct index_state {\n \tstruct cache_entry **cache;\n@@ -338,6 +339,7 @@ struct index_state {\n \tstruct mem_pool *ce_mem_pool;\n \tstruct progress *progress;\n \tstruct repository *repo;\n+\tstruct pattern_list *sparse_checkout_patterns;\n };\n \n /* Name hashing */\n-- \ngitgitgadget\n\n"},{"id":"419387","messageId":"e41b14e03ebb143c874cc276adcbc36059f0c64e.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 11/20] sparse-index: convert from full to sparse","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:42:54Z","receivedAt":"2021-03-16T16:44:09Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nIf we have a full index, then we can convert it to a sparse index by\nreplacing directories outside of the sparse cone with sparse directory\nentries. The convert_to_sparse() method does this, when the situation is\nappropriate.\n\nFor now, we avoid converting the index to a sparse index if:\n\n 1. the index is split.\n 2. the index is already sparse.\n 3. sparse-checkout is disabled.\n 4. sparse-checkout does not use cone mode.\n\nFinally, we currently limit the conversion to when the\nGIT_TEST_SPARSE_INDEX environment variable is enabled. A mode using Git\nconfig will be added in a later change.\n\nThe trickiest thing about this conversion is that we might not be able\nto mark a directory as a sparse directory just because it is outside the\nsparse cone. There might be unmerged files within that directory, so we\nneed to look for those. Also, if there is some strange reason why a file\nis not marked with CE_SKIP_WORKTREE, then we should give up on\nconverting that directory. There is still hope that some of its\nsubdirectories might be able to convert to sparse, so we keep looking\ndeeper.\n\nThe conversion process is assisted by the cache-tree extension. This is\ncalculated from the full index if it does not already exist. We then\nabandon the cache-tree as it no longer applies to the newly-sparse\nindex. Thus, this cache-tree will be recalculated in every\nsparse-full-sparse round-trip until we integrate the cache-tree\nextension with the sparse index.\n\nSome Git commands use the index after writing it. For example, 'git add'\nwill update the index, then write it to disk, then read its entries to\nreport information. To keep the in-memory index in a full state after\nwriting, we re-expand it to a full one after the write. This is wasteful\nfor commands that only write the index and do not read from it again,\nbut that is only the case until we make those commands \"sparse aware.\"\n\nWe can compare the behavior of the sparse-index in\nt1092-sparse-checkout-compability.sh by using GIT_TEST_SPARSE_INDEX=1\nwhen operating on the 'sparse-index' repo. We can also compare the two\nsparse repos directly, such as comparing their indexes (when expanded to\nfull in the case of the 'sparse-index' repo). We also verify that the\nindex is actually populated with sparse directory entries.\n\nThe 'checkout and reset (mixed)' test is marked for failure when\ncomparing a sparse repo to a full repo, but we can compare the two\nsparse-checkout cases directly to ensure that we are not changing the\nbehavior when using a sparse index.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache-tree.c                             |   3 +\n cache.h                                  |   2 +\n read-cache.c                             |  26 ++++-\n sparse-index.c                           | 139 +++++++++++++++++++++++\n sparse-index.h                           |   1 +\n t/t1092-sparse-checkout-compatibility.sh |  61 +++++++++-\n 6 files changed, 228 insertions(+), 4 deletions(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 2fb483d3c083..5f07a39e501e 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -6,6 +6,7 @@\n #include \"object-store.h\"\n #include \"replace-object.h\"\n #include \"promisor-remote.h\"\n+#include \"sparse-index.h\"\n \n #ifndef DEBUG_CACHE_TREE\n #define DEBUG_CACHE_TREE 0\n@@ -442,6 +443,8 @@ int cache_tree_update(struct index_state *istate, int flags)\n \tif (i)\n \t\treturn i;\n \n+\tensure_full_index(istate);\n+\n \tif (!istate->cache_tree)\n \t\tistate->cache_tree = cache_tree();\n \ndiff --git a/cache.h b/cache.h\nindex 759ca92e2ecc..69a32146cd77 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -251,6 +251,8 @@ static inline unsigned int create_ce_mode(unsigned int mode)\n {\n \tif (S_ISLNK(mode))\n \t\treturn S_IFLNK;\n+\tif (mode == S_IFDIR)\n+\t\treturn S_IFDIR;\n \tif (S_ISDIR(mode) || S_ISGITLINK(mode))\n \t\treturn S_IFGITLINK;\n \treturn S_IFREG | ce_permissions(mode);\ndiff --git a/read-cache.c b/read-cache.c\nindex dd3980c12b53..b9c08773466c 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 \"sparse-index.h\"\n \n /* Mask for the name length in ce_flags in the on-disk index */\n \n@@ -1002,8 +1003,14 @@ int verify_path(const char *path, unsigned mode)\n \n \t\t\tc = *path++;\n \t\t\tif ((c == '.' && !verify_dotfile(path, mode)) ||\n-\t\t\t    is_dir_sep(c) || c == '\\0')\n+\t\t\t    is_dir_sep(c))\n \t\t\t\treturn 0;\n+\t\t\t/*\n+\t\t\t * allow terminating directory separators for\n+\t\t\t * sparse directory entries.\n+\t\t\t */\n+\t\t\tif (c == '\\0')\n+\t\t\t\treturn S_ISDIR(mode);\n \t\t} else if (c == '\\\\' && protect_ntfs) {\n \t\t\tif (is_ntfs_dotgit(path))\n \t\t\t\treturn 0;\n@@ -3079,6 +3086,14 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n \t\t\t\t unsigned flags)\n {\n \tint ret;\n+\tint was_full = !istate->sparse_index;\n+\n+\tret = convert_to_sparse(istate);\n+\n+\tif (ret) {\n+\t\twarning(_(\"failed to convert to a sparse-index\"));\n+\t\treturn ret;\n+\t}\n \n \t/*\n \t * TODO trace2: replace \"the_repository\" with the actual repo instance\n@@ -3090,6 +3105,9 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n \ttrace2_region_leave_printf(\"index\", \"do_write_index\", the_repository,\n \t\t\t\t   \"%s\", get_lock_file_path(lock));\n \n+\tif (was_full)\n+\t\tensure_full_index(istate);\n+\n \tif (ret)\n \t\treturn ret;\n \tif (flags & COMMIT_LOCK)\n@@ -3180,9 +3198,10 @@ static int write_shared_index(struct index_state *istate,\n \t\t\t      struct tempfile **temp)\n {\n \tstruct split_index *si = istate->split_index;\n-\tint ret;\n+\tint ret, was_full = !istate->sparse_index;\n \n \tmove_cache_to_base_index(istate);\n+\tconvert_to_sparse(istate);\n \n \ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n \t\t\t\t   the_repository, \"%s\", get_tempfile_path(*temp));\n@@ -3190,6 +3209,9 @@ static int write_shared_index(struct index_state *istate,\n \ttrace2_region_leave_printf(\"index\", \"shared/do_write_index\",\n \t\t\t\t   the_repository, \"%s\", get_tempfile_path(*temp));\n \n+\tif (was_full)\n+\t\tensure_full_index(istate);\n+\n \tif (ret)\n \t\treturn ret;\n \tret = adjust_shared_perm(get_tempfile_path(*temp));\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 7095378a1b28..619ff7c2e217 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -4,6 +4,145 @@\n #include \"tree.h\"\n #include \"pathspec.h\"\n #include \"trace2.h\"\n+#include \"cache-tree.h\"\n+#include \"config.h\"\n+#include \"dir.h\"\n+#include \"fsmonitor.h\"\n+\n+static struct cache_entry *construct_sparse_dir_entry(\n+\t\t\t\tstruct index_state *istate,\n+\t\t\t\tconst char *sparse_dir,\n+\t\t\t\tstruct cache_tree *tree)\n+{\n+\tstruct cache_entry *de;\n+\n+\tde = make_cache_entry(istate, S_IFDIR, &tree->oid, sparse_dir, 0, 0);\n+\n+\tde->ce_flags |= CE_SKIP_WORKTREE;\n+\treturn de;\n+}\n+\n+/*\n+ * Returns the number of entries \"inserted\" into the index.\n+ */\n+static int convert_to_sparse_rec(struct index_state *istate,\n+\t\t\t\t int num_converted,\n+\t\t\t\t int start, int end,\n+\t\t\t\t const char *ct_path, size_t ct_pathlen,\n+\t\t\t\t struct cache_tree *ct)\n+{\n+\tint i, can_convert = 1;\n+\tint start_converted = num_converted;\n+\tenum pattern_match_result match;\n+\tint dtype;\n+\tstruct strbuf child_path = STRBUF_INIT;\n+\tstruct pattern_list *pl = istate->sparse_checkout_patterns;\n+\n+\t/*\n+\t * Is the current path outside of the sparse cone?\n+\t * Then check if the region can be replaced by a sparse\n+\t * directory entry (everything is sparse and merged).\n+\t */\n+\tmatch = path_matches_pattern_list(ct_path, ct_pathlen,\n+\t\t\t\t\t  NULL, &dtype, pl, istate);\n+\tif (match != NOT_MATCHED)\n+\t\tcan_convert = 0;\n+\n+\tfor (i = start; can_convert && i < end; i++) {\n+\t\tstruct cache_entry *ce = istate->cache[i];\n+\n+\t\tif (ce_stage(ce) ||\n+\t\t    !(ce->ce_flags & CE_SKIP_WORKTREE))\n+\t\t\tcan_convert = 0;\n+\t}\n+\n+\tif (can_convert) {\n+\t\tstruct cache_entry *se;\n+\t\tse = construct_sparse_dir_entry(istate, ct_path, ct);\n+\n+\t\tistate->cache[num_converted++] = se;\n+\t\treturn 1;\n+\t}\n+\n+\tfor (i = start; i < end; ) {\n+\t\tint count, span, pos = -1;\n+\t\tconst char *base, *slash;\n+\t\tstruct cache_entry *ce = istate->cache[i];\n+\n+\t\t/*\n+\t\t * Detect if this is a normal entry outside of any subtree\n+\t\t * entry.\n+\t\t */\n+\t\tbase = ce->name + ct_pathlen;\n+\t\tslash = strchr(base, '/');\n+\n+\t\tif (slash)\n+\t\t\tpos = cache_tree_subtree_pos(ct, base, slash - base);\n+\n+\t\tif (pos < 0) {\n+\t\t\tistate->cache[num_converted++] = ce;\n+\t\t\ti++;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tstrbuf_setlen(&child_path, 0);\n+\t\tstrbuf_add(&child_path, ce->name, slash - ce->name + 1);\n+\n+\t\tspan = ct->down[pos]->cache_tree->entry_count;\n+\t\tcount = convert_to_sparse_rec(istate,\n+\t\t\t\t\t      num_converted, i, i + span,\n+\t\t\t\t\t      child_path.buf, child_path.len,\n+\t\t\t\t\t      ct->down[pos]->cache_tree);\n+\t\tnum_converted += count;\n+\t\ti += span;\n+\t}\n+\n+\tstrbuf_release(&child_path);\n+\treturn num_converted - start_converted;\n+}\n+\n+int convert_to_sparse(struct index_state *istate)\n+{\n+\tif (istate->split_index || istate->sparse_index ||\n+\t    !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n+\t\treturn 0;\n+\n+\t/*\n+\t * For now, only create a sparse index with the\n+\t * GIT_TEST_SPARSE_INDEX environment variable. We will relax\n+\t * this once we have a proper way to opt-in (and later still,\n+\t * opt-out).\n+\t */\n+\tif (!git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0))\n+\t\treturn 0;\n+\n+\tif (!istate->sparse_checkout_patterns) {\n+\t\tistate->sparse_checkout_patterns = xcalloc(1, sizeof(struct pattern_list));\n+\t\tif (get_sparse_checkout_patterns(istate->sparse_checkout_patterns) < 0)\n+\t\t\treturn 0;\n+\t}\n+\n+\tif (!istate->sparse_checkout_patterns->use_cone_patterns) {\n+\t\twarning(_(\"attempting to use sparse-index without cone mode\"));\n+\t\treturn -1;\n+\t}\n+\n+\tif (cache_tree_update(istate, 0)) {\n+\t\twarning(_(\"unable to update cache-tree, staying full\"));\n+\t\treturn -1;\n+\t}\n+\n+\tremove_fsmonitor(istate);\n+\n+\ttrace2_region_enter(\"index\", \"convert_to_sparse\", istate->repo);\n+\tistate->cache_nr = convert_to_sparse_rec(istate,\n+\t\t\t\t\t\t 0, 0, istate->cache_nr,\n+\t\t\t\t\t\t \"\", 0, istate->cache_tree);\n+\tistate->drop_cache_tree = 1;\n+\tistate->sparse_index = 1;\n+\ttrace2_region_leave(\"index\", \"convert_to_sparse\", istate->repo);\n+\treturn 0;\n+}\n \n static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n {\ndiff --git a/sparse-index.h b/sparse-index.h\nindex 09a20d036c46..64380e121d80 100644\n--- a/sparse-index.h\n+++ b/sparse-index.h\n@@ -3,5 +3,6 @@\n \n struct index_state;\n void ensure_full_index(struct index_state *istate);\n+int convert_to_sparse(struct index_state *istate);\n \n #endif\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex a1aea141c62c..1e888d195122 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -2,6 +2,11 @@\n \n test_description='compare full workdir to sparse workdir'\n \n+# The verify_cache_tree() check is not sparse-aware (yet).\n+# So, disable the check until that integration is complete.\n+GIT_TEST_CHECK_CACHE_TREE=0\n+GIT_TEST_SPLIT_INDEX=0\n+\n . ./test-lib.sh\n \n test_expect_success 'setup' '\n@@ -121,7 +126,9 @@ run_on_all () {\n test_all_match () {\n \trun_on_all \"$@\" &&\n \ttest_cmp full-checkout-out sparse-checkout-out &&\n-\ttest_cmp full-checkout-err sparse-checkout-err\n+\ttest_cmp full-checkout-out sparse-index-out &&\n+\ttest_cmp full-checkout-err sparse-checkout-err &&\n+\ttest_cmp full-checkout-err sparse-index-err\n }\n \n test_sparse_match () {\n@@ -130,6 +137,38 @@ test_sparse_match () {\n \ttest_cmp sparse-checkout-err sparse-index-err\n }\n \n+test_expect_success 'sparse-index contents' '\n+\tinit_repos &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tfor dir in folder1 folder2 x\n+\tdo\n+\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n+\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n+\t\t\t|| return 1\n+\tdone &&\n+\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set folder1 &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tfor dir in deep folder2 x\n+\tdo\n+\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n+\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n+\t\t\t|| return 1\n+\tdone &&\n+\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep/deeper1 &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tfor dir in deep/deeper2 folder1 folder2 x\n+\tdo\n+\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n+\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n+\t\t\t|| return 1\n+\tdone\n+'\n+\n test_expect_success 'expanded in-memory index matches full index' '\n \tinit_repos &&\n \ttest_sparse_match test-tool read-cache --expand --table\n@@ -137,6 +176,7 @@ test_expect_success 'expanded in-memory index matches full index' '\n \n test_expect_success 'status with options' '\n \tinit_repos &&\n+\ttest_sparse_match ls &&\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git status --porcelain=v2 -z -u &&\n \ttest_all_match git status --porcelain=v2 -uno &&\n@@ -273,6 +313,17 @@ test_expect_failure 'checkout and reset (mixed)' '\n \ttest_all_match git reset update-folder2\n '\n \n+# Ensure that sparse-index behaves identically to\n+# sparse-checkout with a full index.\n+test_expect_success 'checkout and reset (mixed) [sparse]' '\n+\tinit_repos &&\n+\n+\ttest_sparse_match git checkout -b reset-test update-deep &&\n+\ttest_sparse_match git reset deepest &&\n+\ttest_sparse_match git reset update-folder1 &&\n+\ttest_sparse_match git reset update-folder2\n+'\n+\n test_expect_success 'merge' '\n \tinit_repos &&\n \n@@ -309,14 +360,20 @@ test_expect_success 'clean' '\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git clean -f &&\n \ttest_all_match git status --porcelain=v2 &&\n+\ttest_sparse_match ls &&\n+\ttest_sparse_match ls folder1 &&\n \n \ttest_all_match git clean -xf &&\n \ttest_all_match git status --porcelain=v2 &&\n+\ttest_sparse_match ls &&\n+\ttest_sparse_match ls folder1 &&\n \n \ttest_all_match git clean -xdf &&\n \ttest_all_match git status --porcelain=v2 &&\n+\ttest_sparse_match ls &&\n+\ttest_sparse_match ls folder1 &&\n \n-\ttest_path_is_dir sparse-checkout/folder1\n+\ttest_sparse_match test_path_is_dir folder1\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"419388","messageId":"1a2be38b2ca71974d19f78d4391f16145d5a9ba2.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 14/20] sparse-index: check index conversion happens","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:42:57Z","receivedAt":"2021-03-16T16:44:39Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nAdd a test case that uses test_region to ensure that we are truly\nexpanding a sparse index to a full one, then converting back to sparse\nwhen writing the index. As we integrate more Git commands with the\nsparse index, we will convert these commands to check that we do _not_\nconvert the sparse index to a full index and instead stay sparse the\nentire time.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/t1092-sparse-checkout-compatibility.sh | 18 ++++++++++++++++++\n 1 file changed, 18 insertions(+)\n\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex cba5f89b1e96..47f983217852 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -393,4 +393,22 @@ test_expect_success 'submodule handling' '\n \tgrep \"160000 commit $(git -C initial-repo rev-parse HEAD)\tmodules/sub\" cache\n '\n \n+test_expect_success 'sparse-index is expanded and converted back' '\n+\tinit_repos &&\n+\n+\t(\n+\t\tGIT_TEST_SPARSE_INDEX=1 &&\n+\t\texport GIT_TEST_SPARSE_INDEX &&\n+\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" reset --hard &&\n+\t\ttest_region index convert_to_sparse trace2.txt &&\n+\t\ttest_region index ensure_full_index trace2.txt &&\n+\n+\t\trm trace2.txt &&\n+\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" status -uno &&\n+\t\ttest_region index ensure_full_index trace2.txt\n+\t)\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"419389","messageId":"f89891b0ae4e5e8f7b53da41582ca56772de0d16.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 15/20] sparse-index: create extension for compatibility","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:42:58Z","receivedAt":"2021-03-16T16:44:39Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nPreviously, we enabled the sparse index format only using\nGIT_TEST_SPARSE_INDEX=1. This is not a feasible direction for users to\nactually select this mode. Further, sparse directory entries are not\nunderstood by the index formats as advertised.\n\nWe _could_ add a new index version that explicitly adds these\ncapabilities, but there are nuances to index formats 2, 3, and 4 that\nare still valuable to select as options. Until we add index format\nversion 5, create a repo extension, \"extensions.sparseIndex\", that\nspecifies that the tool reading this repository must understand sparse\ndirectory entries.\n\nThis change only encodes the extension and enables it when\nGIT_TEST_SPARSE_INDEX=1. Later, we will add a more user-friendly CLI\nmechanism.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/extensions.txt |  8 ++++++\n cache.h                             |  1 +\n repo-settings.c                     |  7 ++++++\n repository.h                        |  3 ++-\n setup.c                             |  3 +++\n sparse-index.c                      | 38 +++++++++++++++++++++++++----\n 6 files changed, 54 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config/extensions.txt b/Documentation/config/extensions.txt\nindex 4e23d73cdcad..c02e09af0046 100644\n--- a/Documentation/config/extensions.txt\n+++ b/Documentation/config/extensions.txt\n@@ -6,3 +6,11 @@ extensions.objectFormat::\n Note that this setting should only be set by linkgit:git-init[1] or\n linkgit:git-clone[1].  Trying to change it after initialization will not\n work and will produce hard-to-diagnose issues.\n+\n+extensions.sparseIndex::\n+\tWhen combined with `core.sparseCheckout=true` and\n+\t`core.sparseCheckoutCone=true`, the index may contain entries\n+\tcorresponding to directories outside of the sparse-checkout\n+\tdefinition in lieu of containing each path under such directories.\n+\tVersions of Git that do not understand this extension do not\n+\texpect directory entries in the index.\ndiff --git a/cache.h b/cache.h\nindex 69a32146cd77..4ca6cd7f782c 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1059,6 +1059,7 @@ struct repository_format {\n \tint worktree_config;\n \tint is_bare;\n \tint hash_algo;\n+\tint sparse_index;\n \tchar *work_tree;\n \tstruct string_list unknown_extensions;\n \tstruct string_list v1_only_extensions;\ndiff --git a/repo-settings.c b/repo-settings.c\nindex d63569e4041e..9677d50f9238 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -85,4 +85,11 @@ void prepare_repo_settings(struct repository *r)\n \t * removed.\n \t */\n \tr->settings.command_requires_full_index = 1;\n+\n+\t/*\n+\t * Initialize this as off.\n+\t */\n+\tr->settings.sparse_index = 0;\n+\tif (!repo_config_get_bool(r, \"extensions.sparseindex\", &value) && value)\n+\t\tr->settings.sparse_index = 1;\n }\ndiff --git a/repository.h b/repository.h\nindex e06a23015697..a45f7520fd9e 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -42,7 +42,8 @@ struct repo_settings {\n \n \tint core_multi_pack_index;\n \n-\tunsigned command_requires_full_index:1;\n+\tunsigned command_requires_full_index:1,\n+\t\t sparse_index:1;\n };\n \n struct repository {\ndiff --git a/setup.c b/setup.c\nindex c04cd25a30df..cd8394564613 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -500,6 +500,9 @@ static enum extension_result handle_extension(const char *var,\n \t\t\treturn error(\"invalid value for 'extensions.objectformat'\");\n \t\tdata->hash_algo = format;\n \t\treturn EXTENSION_OK;\n+\t} else if (!strcmp(ext, \"sparseindex\")) {\n+\t\tdata->sparse_index = 1;\n+\t\treturn EXTENSION_OK;\n \t}\n \treturn EXTENSION_UNKNOWN;\n }\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 7631f7bd00b7..3a6df66faeab 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -102,19 +102,47 @@ static int convert_to_sparse_rec(struct index_state *istate,\n \treturn num_converted - start_converted;\n }\n \n+static int enable_sparse_index(struct repository *repo)\n+{\n+\tconst char *config_path = repo_git_path(repo, \"config.worktree\");\n+\n+\tif (upgrade_repository_format(1) < 0) {\n+\t\twarning(_(\"unable to upgrade repository format to enable sparse-index\"));\n+\t\treturn -1;\n+\t}\n+\tgit_config_set_in_file_gently(config_path,\n+\t\t\t\t      \"extensions.sparseIndex\",\n+\t\t\t\t      \"true\");\n+\n+\tprepare_repo_settings(repo);\n+\trepo->settings.sparse_index = 1;\n+\treturn 0;\n+}\n+\n int convert_to_sparse(struct index_state *istate)\n {\n \tif (istate->split_index || istate->sparse_index ||\n \t    !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n \t\treturn 0;\n \n+\tif (!istate->repo)\n+\t\tistate->repo = the_repository;\n+\n+\t/*\n+\t * The GIT_TEST_SPARSE_INDEX environment variable triggers the\n+\t * extensions.sparseIndex config variable to be on.\n+\t */\n+\tif (git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0)) {\n+\t\tint err = enable_sparse_index(istate->repo);\n+\t\tif (err < 0)\n+\t\t\treturn err;\n+\t}\n+\n \t/*\n-\t * For now, only create a sparse index with the\n-\t * GIT_TEST_SPARSE_INDEX environment variable. We will relax\n-\t * this once we have a proper way to opt-in (and later still,\n-\t * opt-out).\n+\t * Only convert to sparse if extensions.sparseIndex is set.\n \t */\n-\tif (!git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0))\n+\tprepare_repo_settings(istate->repo);\n+\tif (!istate->repo->settings.sparse_index)\n \t\treturn 0;\n \n \tif (!istate->sparse_checkout_patterns) {\n-- \ngitgitgadget\n\n"},{"id":"419390","messageId":"598557f90a2a6d2a8656ff60179adcd260074771.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 17/20] sparse-checkout: disable sparse-index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:43:00Z","receivedAt":"2021-03-16T16:44:39Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWe use 'git sparse-checkout init --cone --sparse-index' to toggle the\nsparse-index feature. It makes sense to also disable it when running\n'git sparse-checkout disable'. This is particularly important because it\nremoves the extensions.sparseIndex config option, allowing other tools\nto use this Git repository again.\n\nThis does mean that 'git sparse-checkout init' will not re-enable the\nsparse-index feature, even if it was previously enabled.\n\nWhile testing this feature, I noticed that the sparse-index was not\nbeing written on the first run, but by a second. This was caught by the\ncall to 'test-tool read-cache --table'. This requires adjusting some\nassignments to core_apply_sparse_checkout and pl.use_cone_patterns in\nthe sparse_checkout_init() logic.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/sparse-checkout.c          | 10 +++++++++-\n t/t1091-sparse-checkout-builtin.sh | 13 +++++++++++++\n 2 files changed, 22 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex ca63e2c64e95..585343fa1972 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -280,6 +280,9 @@ static int set_config(enum sparse_checkout_mode mode)\n \t\t\t\t      \"core.sparseCheckoutCone\",\n \t\t\t\t      mode == MODE_CONE_PATTERNS ? \"true\" : NULL);\n \n+\tif (mode == MODE_NO_PATTERNS)\n+\t\tset_sparse_index_config(the_repository, 0);\n+\n \treturn 0;\n }\n \n@@ -341,10 +344,11 @@ static int sparse_checkout_init(int argc, const char **argv)\n \t\tthe_repository->index->updated_workdir = 1;\n \t}\n \n+\tcore_apply_sparse_checkout = 1;\n+\n \t/* If we already have a sparse-checkout file, use it. */\n \tif (res >= 0) {\n \t\tfree(sparse_filename);\n-\t\tcore_apply_sparse_checkout = 1;\n \t\treturn update_working_directory(NULL);\n \t}\n \n@@ -366,6 +370,7 @@ static int sparse_checkout_init(int argc, const char **argv)\n \tadd_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);\n \tstrbuf_addstr(&pattern, \"!/*/\");\n \tadd_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);\n+\tpl.use_cone_patterns = init_opts.cone_mode;\n \n \treturn write_patterns_and_update(&pl);\n }\n@@ -632,6 +637,9 @@ static int sparse_checkout_disable(int argc, const char **argv)\n \tstrbuf_addstr(&match_all, \"/*\");\n \tadd_pattern(strbuf_detach(&match_all, NULL), empty_base, 0, &pl, 0);\n \n+\tprepare_repo_settings(the_repository);\n+\tthe_repository->settings.sparse_index = 0;\n+\n \tif (update_working_directory(&pl))\n \t\tdie(_(\"error while refreshing working directory\"));\n \ndiff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh\nindex fc64e9ed99f4..ff1ad570a255 100755\n--- a/t/t1091-sparse-checkout-builtin.sh\n+++ b/t/t1091-sparse-checkout-builtin.sh\n@@ -205,6 +205,19 @@ test_expect_success 'sparse-checkout disable' '\n \tcheck_files repo a deep folder1 folder2\n '\n \n+test_expect_success 'sparse-index enabled and disabled' '\n+\tgit -C repo sparse-checkout init --cone --sparse-index &&\n+\ttest_cmp_config -C repo true extensions.sparseIndex &&\n+\ttest-tool -C repo read-cache --table >cache &&\n+\tgrep \" tree \" cache &&\n+\n+\tgit -C repo sparse-checkout disable &&\n+\ttest-tool -C repo read-cache --table >cache &&\n+\t! grep \" tree \" cache &&\n+\tgit -C repo config --list >config &&\n+\t! grep extensions.sparseindex config\n+'\n+\n test_expect_success 'cone mode: init and set' '\n \tgit -C repo sparse-checkout init --cone &&\n \tgit -C repo config --list >config &&\n-- \ngitgitgadget\n\n"},{"id":"419391","messageId":"bd703c76c85946416e5d9fa48d3880c9ec455d49.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 16/20] sparse-checkout: toggle sparse index from builtin","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:42:59Z","receivedAt":"2021-03-16T16:44:39Z","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 sparse index extension is used to signal that index writes should be\nin sparse mode. This was only updated using GIT_TEST_SPARSE_INDEX=1.\n\nAdd a '--[no-]sparse-index' option to 'git sparse-checkout init' that\nspecifies if the sparse index should be used. It also updates the index\nto use the correct format, either way. Add a warning in the\ndocumentation that the use of a repository extension might reduce\ncompatibility with third-party tools. 'git sparse-checkout init' already\nsets extension.worktreeConfig, which places most sparse-checkout users\noutside of the scope of most third-party tools.\n\nUpdate t1092-sparse-checkout-compatibility.sh to use this CLI instead of\nGIT_TEST_SPARSE_INDEX=1.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-sparse-checkout.txt    | 14 +++++++\n builtin/sparse-checkout.c                | 17 ++++++++-\n sparse-index.c                           | 37 +++++++++++++------\n sparse-index.h                           |  3 ++\n t/t1092-sparse-checkout-compatibility.sh | 47 +++++++++++++-----------\n 5 files changed, 84 insertions(+), 34 deletions(-)\n\ndiff --git a/Documentation/git-sparse-checkout.txt b/Documentation/git-sparse-checkout.txt\nindex a0eeaeb02ee3..2ff66c5a4e41 100644\n--- a/Documentation/git-sparse-checkout.txt\n+++ b/Documentation/git-sparse-checkout.txt\n@@ -45,6 +45,20 @@ To avoid interfering with other worktrees, it first enables the\n When `--cone` is provided, the `core.sparseCheckoutCone` setting is\n also set, allowing for better performance with a limited set of\n patterns (see 'CONE PATTERN SET' below).\n++\n+Use the `--[no-]sparse-index` option to toggle the use of the sparse\n+index format. This reduces the size of the index to be more closely\n+aligned with your sparse-checkout definition. This can have significant\n+performance advantages for commands such as `git status` or `git add`.\n+This feature is still experimental. Some commands might be slower with\n+a sparse index until they are properly integrated with the feature.\n++\n+**WARNING:** Using a sparse index requires modifying the index in a way\n+that is not completely understood by external tools. If you have trouble\n+with this compatibility, then run `git sparse-checkout init --no-sparse-index`\n+to rewrite your index to not be sparse. Older versions of Git will not\n+understand the `sparseIndex` repository extension and may fail to interact\n+with your repository until it is disabled.\n \n 'set'::\n \tWrite a set of patterns to the sparse-checkout file, as given as\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex e00b82af727b..ca63e2c64e95 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -14,6 +14,7 @@\n #include \"unpack-trees.h\"\n #include \"wt-status.h\"\n #include \"quote.h\"\n+#include \"sparse-index.h\"\n \n static const char *empty_base = \"\";\n \n@@ -283,12 +284,13 @@ static int set_config(enum sparse_checkout_mode mode)\n }\n \n static char const * const builtin_sparse_checkout_init_usage[] = {\n-\tN_(\"git sparse-checkout init [--cone]\"),\n+\tN_(\"git sparse-checkout init [--cone] [--[no-]sparse-index]\"),\n \tNULL\n };\n \n static struct sparse_checkout_init_opts {\n \tint cone_mode;\n+\tint sparse_index;\n } init_opts;\n \n static int sparse_checkout_init(int argc, const char **argv)\n@@ -303,11 +305,15 @@ static int sparse_checkout_init(int argc, const char **argv)\n \tstatic struct option builtin_sparse_checkout_init_options[] = {\n \t\tOPT_BOOL(0, \"cone\", &init_opts.cone_mode,\n \t\t\t N_(\"initialize the sparse-checkout in cone mode\")),\n+\t\tOPT_BOOL(0, \"sparse-index\", &init_opts.sparse_index,\n+\t\t\t N_(\"toggle the use of a sparse index\")),\n \t\tOPT_END(),\n \t};\n \n \trepo_read_index(the_repository);\n \n+\tinit_opts.sparse_index = -1;\n+\n \targc = parse_options(argc, argv, NULL,\n \t\t\t     builtin_sparse_checkout_init_options,\n \t\t\t     builtin_sparse_checkout_init_usage, 0);\n@@ -326,6 +332,15 @@ static int sparse_checkout_init(int argc, const char **argv)\n \tsparse_filename = get_sparse_checkout_filename();\n \tres = add_patterns_from_file_to_list(sparse_filename, \"\", 0, &pl, NULL);\n \n+\tif (init_opts.sparse_index >= 0) {\n+\t\tif (set_sparse_index_config(the_repository, init_opts.sparse_index) < 0)\n+\t\t\tdie(_(\"failed to modify sparse-index config\"));\n+\n+\t\t/* force an index rewrite */\n+\t\trepo_read_index(the_repository);\n+\t\tthe_repository->index->updated_workdir = 1;\n+\t}\n+\n \t/* If we already have a sparse-checkout file, use it. */\n \tif (res >= 0) {\n \t\tfree(sparse_filename);\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 3a6df66faeab..30c1a11fd62d 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -104,23 +104,37 @@ static int convert_to_sparse_rec(struct index_state *istate,\n \n static int enable_sparse_index(struct repository *repo)\n {\n-\tconst char *config_path = repo_git_path(repo, \"config.worktree\");\n+\tint res;\n \n \tif (upgrade_repository_format(1) < 0) {\n \t\twarning(_(\"unable to upgrade repository format to enable sparse-index\"));\n \t\treturn -1;\n \t}\n-\tgit_config_set_in_file_gently(config_path,\n-\t\t\t\t      \"extensions.sparseIndex\",\n-\t\t\t\t      \"true\");\n+\tres = git_config_set_gently(\"extensions.sparseindex\", \"true\");\n \n \tprepare_repo_settings(repo);\n \trepo->settings.sparse_index = 1;\n-\treturn 0;\n+\treturn res;\n+}\n+\n+int set_sparse_index_config(struct repository *repo, int enable)\n+{\n+\tint res;\n+\n+\tif (enable)\n+\t\treturn enable_sparse_index(repo);\n+\n+\t/* Don't downgrade repository format, just remove the extension. */\n+\tres = git_config_set_gently(\"extensions.sparseindex\", NULL);\n+\n+\tprepare_repo_settings(repo);\n+\trepo->settings.sparse_index = 0;\n+\treturn res;\n }\n \n int convert_to_sparse(struct index_state *istate)\n {\n+\tint test_env;\n \tif (istate->split_index || istate->sparse_index ||\n \t    !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n \t\treturn 0;\n@@ -129,14 +143,13 @@ int convert_to_sparse(struct index_state *istate)\n \t\tistate->repo = the_repository;\n \n \t/*\n-\t * The GIT_TEST_SPARSE_INDEX environment variable triggers the\n-\t * extensions.sparseIndex config variable to be on.\n+\t * If GIT_TEST_SPARSE_INDEX=1, then trigger extensions.sparseIndex\n+\t * to be fully enabled. If GIT_TEST_SPARSE_INDEX=0 (set explicitly),\n+\t * then purposefully disable the setting.\n \t */\n-\tif (git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0)) {\n-\t\tint err = enable_sparse_index(istate->repo);\n-\t\tif (err < 0)\n-\t\t\treturn err;\n-\t}\n+\ttest_env = git_env_bool(\"GIT_TEST_SPARSE_INDEX\", -1);\n+\tif (test_env >= 0)\n+\t\tset_sparse_index_config(istate->repo, test_env);\n \n \t/*\n \t * Only convert to sparse if extensions.sparseIndex is set.\ndiff --git a/sparse-index.h b/sparse-index.h\nindex 64380e121d80..39dcc859735e 100644\n--- a/sparse-index.h\n+++ b/sparse-index.h\n@@ -5,4 +5,7 @@ struct index_state;\n void ensure_full_index(struct index_state *istate);\n int convert_to_sparse(struct index_state *istate);\n \n+struct repository;\n+int set_sparse_index_config(struct repository *repo, int enable);\n+\n #endif\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 47f983217852..f14dc48924d2 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -6,6 +6,7 @@ test_description='compare full workdir to sparse workdir'\n # So, disable the check until that integration is complete.\n GIT_TEST_CHECK_CACHE_TREE=0\n GIT_TEST_SPLIT_INDEX=0\n+GIT_TEST_SPARSE_INDEX=\n \n . ./test-lib.sh\n \n@@ -100,25 +101,26 @@ init_repos () {\n \t# initialize sparse-checkout definitions\n \tgit -C sparse-checkout sparse-checkout init --cone &&\n \tgit -C sparse-checkout sparse-checkout set deep &&\n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout init --cone &&\n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep\n+\tgit -C sparse-index sparse-checkout init --cone --sparse-index &&\n+\ttest_cmp_config -C sparse-index true extensions.sparseindex &&\n+\tgit -C sparse-index sparse-checkout set deep\n }\n \n run_on_sparse () {\n \t(\n \t\tcd sparse-checkout &&\n-\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n+\t\t\"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n \t) &&\n \t(\n \t\tcd sparse-index &&\n-\t\tGIT_TEST_SPARSE_INDEX=1 \"$@\" >../sparse-index-out 2>../sparse-index-err\n+\t\t\"$@\" >../sparse-index-out 2>../sparse-index-err\n \t)\n }\n \n run_on_all () {\n \t(\n \t\tcd full-checkout &&\n-\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../full-checkout-out 2>../full-checkout-err\n+\t\t\"$@\" >../full-checkout-out 2>../full-checkout-err\n \t) &&\n \trun_on_sparse \"$@\"\n }\n@@ -148,7 +150,7 @@ test_expect_success 'sparse-index contents' '\n \t\t\t|| return 1\n \tdone &&\n \n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set folder1 &&\n+\tgit -C sparse-index sparse-checkout set folder1 &&\n \n \ttest-tool -C sparse-index read-cache --table >cache &&\n \tfor dir in deep folder2 x\n@@ -158,7 +160,7 @@ test_expect_success 'sparse-index contents' '\n \t\t\t|| return 1\n \tdone &&\n \n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep/deeper1 &&\n+\tgit -C sparse-index sparse-checkout set deep/deeper1 &&\n \n \ttest-tool -C sparse-index read-cache --table >cache &&\n \tfor dir in deep/deeper2 folder1 folder2 x\n@@ -166,7 +168,14 @@ test_expect_success 'sparse-index contents' '\n \t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n \t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n \t\t\t|| return 1\n-\tdone\n+\tdone &&\n+\n+\t# Disabling the sparse-index removes tree entries with full ones\n+\tgit -C sparse-index sparse-checkout init --no-sparse-index &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\t! grep \"040000 tree\" cache &&\n+\ttest_sparse_match test-tool read-cache --table\n '\n \n test_expect_success 'expanded in-memory index matches full index' '\n@@ -396,19 +405,15 @@ test_expect_success 'submodule handling' '\n test_expect_success 'sparse-index is expanded and converted back' '\n \tinit_repos &&\n \n-\t(\n-\t\tGIT_TEST_SPARSE_INDEX=1 &&\n-\t\texport GIT_TEST_SPARSE_INDEX &&\n-\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n-\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" reset --hard &&\n-\t\ttest_region index convert_to_sparse trace2.txt &&\n-\t\ttest_region index ensure_full_index trace2.txt &&\n-\n-\t\trm trace2.txt &&\n-\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n-\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" status -uno &&\n-\t\ttest_region index ensure_full_index trace2.txt\n-\t)\n+\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\tgit -C sparse-index -c core.fsmonitor=\"\" reset --hard &&\n+\ttest_region index convert_to_sparse trace2.txt &&\n+\ttest_region index ensure_full_index trace2.txt &&\n+\n+\trm trace2.txt &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\tgit -C sparse-index -c core.fsmonitor=\"\" status -uno &&\n+\ttest_region index ensure_full_index trace2.txt\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"419392","messageId":"c2d0c17db31a283ff9f9a137c4d497d878f13458.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 18/20] cache-tree: integrate with sparse directory entries","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:43:01Z","receivedAt":"2021-03-16T16:44:39Z","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 cache-tree extension was previously disabled with sparse indexes.\nHowever, the cache-tree is an important performance feature for commands\nlike 'git status' and 'git add'. Integrate it with sparse directory\nentries.\n\nWhen writing a sparse index, completely clear and recalculate the cache\ntree. By starting from scratch, the only integration necessary is to\ncheck if we hit a sparse directory entry and create a leaf of the\ncache-tree that has an entry_count of one and no subtrees.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache-tree.c   | 18 ++++++++++++++++++\n sparse-index.c | 10 +++++++++-\n 2 files changed, 27 insertions(+), 1 deletion(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 5f07a39e501e..950a9615db8f 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -256,6 +256,24 @@ static int update_one(struct cache_tree *it,\n \n \t*skip_count = 0;\n \n+\t/*\n+\t * If the first entry of this region is a sparse directory\n+\t * entry corresponding exactly to 'base', then this cache_tree\n+\t * struct is a \"leaf\" in the data structure, pointing to the\n+\t * tree OID specified in the entry.\n+\t */\n+\tif (entries > 0) {\n+\t\tconst struct cache_entry *ce = cache[0];\n+\n+\t\tif (S_ISSPARSEDIR(ce->ce_mode) &&\n+\t\t    ce->ce_namelen == baselen &&\n+\t\t    !strncmp(ce->name, base, baselen)) {\n+\t\t\tit->entry_count = 1;\n+\t\t\toidcpy(&it->oid, &ce->oid);\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n \tif (0 <= it->entry_count && has_object_file(&it->oid))\n \t\treturn it->entry_count;\n \ndiff --git a/sparse-index.c b/sparse-index.c\nindex 30c1a11fd62d..56313e805d9d 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -180,7 +180,11 @@ int convert_to_sparse(struct index_state *istate)\n \tistate->cache_nr = convert_to_sparse_rec(istate,\n \t\t\t\t\t\t 0, 0, istate->cache_nr,\n \t\t\t\t\t\t \"\", 0, istate->cache_tree);\n-\tistate->drop_cache_tree = 1;\n+\n+\t/* Clear and recompute the cache-tree */\n+\tcache_tree_free(&istate->cache_tree);\n+\tcache_tree_update(istate, 0);\n+\n \tistate->sparse_index = 1;\n \ttrace2_region_leave(\"index\", \"convert_to_sparse\", istate->repo);\n \treturn 0;\n@@ -281,5 +285,9 @@ void ensure_full_index(struct index_state *istate)\n \tstrbuf_release(&base);\n \tfree(full);\n \n+\t/* Clear and recompute the cache-tree */\n+\tcache_tree_free(&istate->cache_tree);\n+\tcache_tree_update(istate, 0);\n+\n \ttrace2_region_leave(\"index\", \"ensure_full_index\", istate->repo);\n }\n-- \ngitgitgadget\n\n"},{"id":"419393","messageId":"6fdd9323c14ea42f805eaa1ace525d268fc3438c.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 19/20] sparse-index: loose integration with cache_tree_verify()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:43:02Z","receivedAt":"2021-03-16T16:44:39Z","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 cache_tree_verify() method is run when GIT_TEST_CHECK_CACHE_TREE\nis enabled, which it is by default in the test suite. The logic must\nbe adjusted for the presence of these directory entries.\n\nFor now, leave the test as a simple check for whether the directory\nentry is sparse. Do not go any further until needed.\n\nThis allows us to re-enable GIT_TEST_CHECK_CACHE_TREE in\nt1092-sparse-checkout-compatibility.sh. Further,\np2000-sparse-operations.sh uses the test suite and hence this is enabled\nfor all tests. We need to integrate with it before we run our\nperformance tests with a sparse-index.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache-tree.c                             | 19 +++++++++++++++++++\n t/t1092-sparse-checkout-compatibility.sh |  3 ---\n 2 files changed, 19 insertions(+), 3 deletions(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 950a9615db8f..11bf1fcae6e1 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -808,6 +808,19 @@ int cache_tree_matches_traversal(struct cache_tree *root,\n \treturn 0;\n }\n \n+static void verify_one_sparse(struct repository *r,\n+\t\t\t      struct index_state *istate,\n+\t\t\t      struct cache_tree *it,\n+\t\t\t      struct strbuf *path,\n+\t\t\t      int pos)\n+{\n+\tstruct cache_entry *ce = istate->cache[pos];\n+\n+\tif (!S_ISSPARSEDIR(ce->ce_mode))\n+\t\tBUG(\"directory '%s' is present in index, but not sparse\",\n+\t\t    path->buf);\n+}\n+\n static void verify_one(struct repository *r,\n \t\t       struct index_state *istate,\n \t\t       struct cache_tree *it,\n@@ -830,6 +843,12 @@ static void verify_one(struct repository *r,\n \n \tif (path->len) {\n \t\tpos = index_name_pos(istate, path->buf, path->len);\n+\n+\t\tif (pos >= 0) {\n+\t\t\tverify_one_sparse(r, istate, it, path, pos);\n+\t\t\treturn;\n+\t\t}\n+\n \t\tpos = -pos - 1;\n \t} else {\n \t\tpos = 0;\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex f14dc48924d2..d97bf9b64527 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -2,9 +2,6 @@\n \n test_description='compare full workdir to sparse workdir'\n \n-# The verify_cache_tree() check is not sparse-aware (yet).\n-# So, disable the check until that integration is complete.\n-GIT_TEST_CHECK_CACHE_TREE=0\n GIT_TEST_SPLIT_INDEX=0\n GIT_TEST_SPARSE_INDEX=\n \n-- \ngitgitgadget\n\n"},{"id":"419394","messageId":"3db06ac46dd5c61e83d7fc4747615d616fdbbdda.1615912983.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v3 20/20] p2000: add sparse-index repos","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T16:43:03Z","receivedAt":"2021-03-16T16:44:40Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\np2000-sparse-operations.sh compares different Git commands in\nrepositories with many files at HEAD but using sparse-checkout to focus\non a small portion of those files.\n\nAdd extra copies of the repository that use the sparse-index format so\nwe can track how that affects the performance of different commands.\n\nAt this point in time, the sparse-index is 100% overhead from the CPU\nfront, and this is measurable in these tests:\n\nTest\n---------------------------------------------------------------\n2000.2: git status (full-index-v3)              0.59(0.51+0.12)\n2000.3: git status (full-index-v4)              0.59(0.52+0.11)\n2000.4: git status (sparse-index-v3)            1.40(1.32+0.12)\n2000.5: git status (sparse-index-v4)            1.41(1.36+0.08)\n2000.6: git add -A (full-index-v3)              2.32(1.97+0.19)\n2000.7: git add -A (full-index-v4)              2.17(1.92+0.14)\n2000.8: git add -A (sparse-index-v3)            2.31(2.21+0.15)\n2000.9: git add -A (sparse-index-v4)            2.30(2.20+0.13)\n2000.10: git add . (full-index-v3)              2.39(2.02+0.20)\n2000.11: git add . (full-index-v4)              2.20(1.94+0.16)\n2000.12: git add . (sparse-index-v3)            2.36(2.27+0.12)\n2000.13: git add . (sparse-index-v4)            2.33(2.21+0.16)\n2000.14: git commit -a -m A (full-index-v3)     2.47(2.12+0.20)\n2000.15: git commit -a -m A (full-index-v4)     2.26(2.00+0.17)\n2000.16: git commit -a -m A (sparse-index-v3)   3.01(2.92+0.16)\n2000.17: git commit -a -m A (sparse-index-v4)   3.01(2.94+0.15)\n\nNote that there is very little difference between the v3 and v4 index\nformats when the sparse-index is enabled. This is primarily due to the\nfact that the relative file sizes are the same, and the command time is\nmostly taken up by parsing tree objects to expand the sparse index into\na full one.\n\nWith the current file layout, the index file sizes are given by this\ntable:\n\n       |  full index | sparse index |\n       +-------------+--------------+\n    v3 |     108 MiB |      1.6 MiB |\n    v4 |      80 MiB |      1.2 MiB |\n\nFuture updates will improve the performance of Git commands when the\nindex is sparse.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/perf/p2000-sparse-operations.sh | 19 ++++++++++++++++++-\n 1 file changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh\nindex 2fbc81b22119..e527316e66d6 100755\n--- a/t/perf/p2000-sparse-operations.sh\n+++ b/t/perf/p2000-sparse-operations.sh\n@@ -60,12 +60,29 @@ test_expect_success 'setup repo and indexes' '\n \t\tgit sparse-checkout set $SPARSE_CONE &&\n \t\tgit config index.version 4 &&\n \t\tgit update-index --index-version=4\n+\t) &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v3 &&\n+\t(\n+\t\tcd sparse-index-v3 &&\n+\t\tgit sparse-checkout init --cone --sparse-index &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 3 &&\n+\t\tgit update-index --index-version=3\n+\t) &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v4 &&\n+\t(\n+\t\tcd sparse-index-v4 &&\n+\t\tgit sparse-checkout init --cone --sparse-index &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 4 &&\n+\t\tgit update-index --index-version=4\n \t)\n '\n \n test_perf_on_all () {\n \tcommand=\"$@\"\n-\tfor repo in full-index-v3 full-index-v4\n+\tfor repo in full-index-v3 full-index-v4 \\\n+\t\t    sparse-index-v3 sparse-index-v4\n \tdo\n \t\ttest_perf \"$command ($repo)\" \"\n \t\t\t(\n-- \ngitgitgadget\n"},{"id":"419395","messageId":"1ac21589-24c8-d24b-bee3-6682aa76e434@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 00/20] Sparse Index: Design, Format, Tests","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-16T16:59:12Z","receivedAt":"2021-03-16T16:59:55Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/16/2021 12:42 PM, Derrick Stolee via GitGitGadget wrote:> Updates in V3\n> =============\n> \n> For this version, I took Ævar's latest patches and applied them to v2.31.0\n> and rebased this series on top. It uses his new \"read_tree_at()\" helper and\n> the associated changes to the function pointer type.\n\nJunio, I wanted to call your attention to this change in base.\n\nHere is the relevant part of the range-diff:\n\n>   5:  399ddb0bad56 !  5:  99292cdbaae4 sparse-index: implement ensure_full_index()\n>      @@ sparse-index.c\n>       +}\n>       +\n>       +static int add_path_to_index(const struct object_id *oid,\n>      -+\t\t\t\tstruct strbuf *base, const char *path,\n>      -+\t\t\t\tunsigned int mode, int stage, void *context)\n>      ++\t\t\t     struct strbuf *base, const char *path,\n>      ++\t\t\t     unsigned int mode, void *context)\n>       +{\n>       +\tstruct index_state *istate = (struct index_state *)context;\n>       +\tstruct cache_entry *ce;\n>      @@ sparse-index.c\n>       -\t/* intentionally left blank */\n>       +\tint i;\n>       +\tstruct index_state *full;\n>      ++\tstruct strbuf base = STRBUF_INIT;\n>       +\n>       +\tif (!istate || !istate->sparse_index)\n>       +\t\treturn;\n>      @@ sparse-index.c\n>       +\t\tps.has_wildcard = 1;\n>       +\t\tps.max_depth = -1;\n>       +\n>      -+\t\tread_tree_recursive(istate->repo, tree,\n>      -+\t\t\t\t    ce->name, strlen(ce->name),\n>      -+\t\t\t\t    0, &ps,\n>      -+\t\t\t\t    add_path_to_index, full);\n>      ++\t\tstrbuf_setlen(&base, 0);\n>      ++\t\tstrbuf_add(&base, ce->name, strlen(ce->name));\n>      ++\n>      ++\t\tread_tree_at(istate->repo, tree, &base, &ps,\n>      ++\t\t\t     add_path_to_index, full);\n>       +\n>       +\t\t/* free directory entries. full entries are re-used */\n>       +\t\tdiscard_cache_entry(ce);\n>      @@ sparse-index.c\n>       +\tistate->cache_nr = full->cache_nr;\n>       +\tistate->cache_alloc = full->cache_alloc;\n>       +\n>      ++\tstrbuf_release(&base);\n>       +\tfree(full);\n>       +\n>       +\ttrace2_region_leave(\"index\", \"ensure_full_index\", istate->repo);\n\nThanks,\n-Stolee\n"},{"id":"419436","messageId":"CABPp-BGpDaj6GqK9zRw-_GUvGZewk7gsVxdjckfJXbkqbTQaeQ@mail.gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 00/20] Sparse Index: Design, Format, Tests","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-03-16T21:18:58Z","receivedAt":"2021-03-16T21:20:02Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Mar 16, 2021 at 9:43 AM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> Here is the first full patch series submission coming out of the\n> sparse-index RFC [1].\n>\n> [1]\n> https://lore.kernel.org/git/pull.847.git.1611596533.gitgitgadget@gmail.com/\n>\n> I won't waste too much space here, because PATCH 1 includes a sizeable\n> design document that describes the feature, the reasoning behind it, and my\n> plan for getting this implemented widely throughout the codebase.\n>\n> There are some new things here that were not in the RFC:\n>\n>  * Design doc and format updates. (Patch 1)\n>  * Performance test script. (Patches 2 and 20)\n>\n> Notably missing in this series from the RFC:\n>\n>  * The mega-patch inserting ensure_full_index() throughout the codebase.\n>    That will be a follow-up series to this one.\n>  * The integrations with git status and git add to demonstrate the improved\n>    performance. Those will also appear in their own series later.\n>\n> I plan to keep my latest work in this area in my 'sparse-index/wip' branch\n> [2]. It includes all of the work from the RFC right now, updated with the\n> work from this series.\n>\n> [2] https://github.com/derrickstolee/git/tree/sparse-index/wip\n>\n>\n> Updates in V3\n> =============\n>\n> For this version, I took Ævar's latest patches and applied them to v2.31.0\n> and rebased this series on top. It uses his new \"read_tree_at()\" helper and\n> the associated changes to the function pointer type.\n>\n>  * Fixed more typos. Thanks Martin and Elijah!\n>  * Updated the test_sparse_match() macro to use \"$@\" instead of $*\n>  * Added a test that git sparse-checkout init --no-sparse-index rewrites the\n>    index to be full.\n\nI've read through the range-diff.  Sorry for not spotting the conflict\nwith Ævar's series (that I also reviewed).  Anyway, my Reviewed-by\nfrom the last series still holds.\n"},{"id":"419450","messageId":"87tupadve8.fsf@evledraar.gmail.com","threadId":"55195","inReplyTo":"d2197e895e4d4160fa369e2ba7d82e2e5a7fbc01.1615912983.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 02/20] t/perf: add performance test for sparse operations","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-17T08:41:51Z","receivedAt":"2021-03-17T08:42:41Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Mar 16 2021, Derrick Stolee via GitGitGadget wrote:\n\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> Create a test script that takes the default performance test (the Git\n> codebase) and multiplies it by 256 using four layers of duplicated\n> trees of width four. This results in nearly one million blob entries in\n> the index. Then, we can clone this repository with sparse-checkout\n> patterns that demonstrate four copies of the initial repository. Each\n> clone will use a different index format or mode so peformance can be\n> tested across the different options.\n>\n> Note that the initial repo is stripped of submodules before doing the\n> copies. This preserves the expected data shape of the sparse index,\n> because directories containing submodules are not collapsed to a sparse\n> directory entry.\n>\n> Run a few Git commands on these clones, especially those that use the\n> index (status, add, commit).\n>\n> Here are the results on my Linux machine:\n>\n> Test\n> --------------------------------------------------------------\n> 2000.2: git status (full-index-v3)             0.37(0.30+0.09)\n> 2000.3: git status (full-index-v4)             0.39(0.32+0.10)\n> 2000.4: git add -A (full-index-v3)             1.42(1.06+0.20)\n> 2000.5: git add -A (full-index-v4)             1.26(0.98+0.16)\n> 2000.6: git add . (full-index-v3)              1.40(1.04+0.18)\n> 2000.7: git add . (full-index-v4)              1.26(0.98+0.17)\n> 2000.8: git commit -a -m A (full-index-v3)     1.42(1.11+0.16)\n> 2000.9: git commit -a -m A (full-index-v4)     1.33(1.08+0.16)\n>\n> It is perhaps noteworthy that there is an improvement when using index\n> version 4. This is because the v3 index uses 108 MiB while the v4\n> index uses 80 MiB. Since the repeated portions of the directories are\n> very short (f3/f1/f2, for example) this ratio is less pronounced than in\n> similarly-sized real repositories.\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  t/perf/p2000-sparse-operations.sh | 85 +++++++++++++++++++++++++++++++\n>  1 file changed, 85 insertions(+)\n>  create mode 100755 t/perf/p2000-sparse-operations.sh\n>\n> diff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh\n> new file mode 100755\n> index 000000000000..2fbc81b22119\n> --- /dev/null\n> +++ b/t/perf/p2000-sparse-operations.sh\n> @@ -0,0 +1,85 @@\n> +#!/bin/sh\n> +\n> +test_description=\"test performance of Git operations using the index\"\n> +\n> +. ./perf-lib.sh\n> +\n> +test_perf_default_repo\n> +\n> +SPARSE_CONE=f2/f4/f1\n> +\n> +test_expect_success 'setup repo and indexes' '\n> +\tgit reset --hard HEAD &&\n> +\t# Remove submodules from the example repo, because our\n> +\t# duplication of the entire repo creates an unlikly data shape.\n> +\tgit config --file .gitmodules --get-regexp \"submodule.*.path\" >modules &&\n> +\tgit rm -f .gitmodules &&\n> +\tfor module in $(awk \"{print \\$2}\" modules)\n> +\tdo\n> +\t\tgit rm $module || return 1\n> +\tdone &&\n> +\tgit commit -m \"remove submodules\" &&\n\nParadoxically with this you can no longer use a repo that's not git.git\nor another repo that has submodules, since we'll die in trying to remove\nthem.\n\nAlso you don't have to \"git rm .gitmodules\", the \"git rm\" command\nremoves submodule entries.\n\nPerhaps just:\n\n    for module in $(git ls-files --stage | grep ^160000 | awk -F '\\t' '{ print $2 }')\n    do\n        git rm \"$module\"\n    done\n\nOr another way of guarding against rm getting the empty list && commit?\n\nBut it seems odd to be doing this at all, the point of the perf\nframework is that you can point it at any repo, and some repos you want\nto test will have submodules.\n\nSeems like something like the WIP patch at the end on top would be\nbetter.\n\n> +\techo bogus >a &&\n> +\tcp a b &&\n> +\tgit add a b &&\n> +\tgit commit -m \"level 0\" &&\n> +\tBLOB=$(git rev-parse HEAD:a) &&\n\nIsn't the way we're getting this $BLOB equivalent to just 'echo bogus |\ngit hash-object --stdin -w' why commit it?\n\n> +\tOLD_COMMIT=$(git rev-parse HEAD) &&\n> +\tOLD_TREE=$(git rev-parse HEAD^{tree}) &&\n> +\n> +\tfor i in $(test_seq 1 4)\n> +\tdo\n> +\t\tcat >in <<-EOF &&\n> +\t\t\t100755 blob $BLOB\ta\n> +\t\t\t040000 tree $OLD_TREE\tf1\n> +\t\t\t040000 tree $OLD_TREE\tf2\n> +\t\t\t040000 tree $OLD_TREE\tf3\n> +\t\t\t040000 tree $OLD_TREE\tf4\n> +\t\tEOF\n> +\t\tNEW_TREE=$(git mktree <in) &&\n> +\t\tNEW_COMMIT=$(git commit-tree $NEW_TREE -p $OLD_COMMIT -m \"level $i\") &&\n> +\t\tOLD_TREE=$NEW_TREE &&\n> +\t\tOLD_COMMIT=$NEW_COMMIT || return 1\n> +\tdone &&\n> +\n> +\tgit sparse-checkout init --cone &&\n> +\tgit branch -f wide $OLD_COMMIT &&\n> +\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v3 &&\n> +\t(\n> +\t\tcd full-index-v3 &&\n> +\t\tgit sparse-checkout init --cone &&\n> +\t\tgit sparse-checkout set $SPARSE_CONE &&\n> +\t\tgit config index.version 3 &&\n> +\t\tgit update-index --index-version=3\n> +\t) &&\n> +\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v4 &&\n> +\t(\n> +\t\tcd full-index-v4 &&\n> +\t\tgit sparse-checkout init --cone &&\n> +\t\tgit sparse-checkout set $SPARSE_CONE &&\n> +\t\tgit config index.version 4 &&\n> +\t\tgit update-index --index-version=4\n> +\t)\n> +'\n\nThis whole thing makes me think you just wanted a test_perf_fresh_repo\nall along, but I think this would be much more useful if you took the\ndefault repo and multiplied the size in its tree by some multiple.\n\nE.g. take the files we have in git.git, write a copy at prefix-1/,\nprefix-2/ etc.\n\nThe whole point of test_perf_{default,large}_repo is being able to point\nthem at a local repo you're testing for performance and get numbers\nrepresentative of that repo.\n\nSo maybe that's not what's wanted here at all, but that brings us back\nto test_perf_fresh_repo...\n\n> +test_perf_on_all () {\n> +\tcommand=\"$@\"\n> +\tfor repo in full-index-v3 full-index-v4\n> +\tdo\n> +\t\ttest_perf \"$command ($repo)\" \"\n> +\t\t\t(\n> +\t\t\t\tcd $repo &&\n> +\t\t\t\techo >>$SPARSE_CONE/a &&\n> +\t\t\t\t$command\n> +\t\t\t)\n> +\t\t\"\n> +\tdone\n> +}\n> +\n> +test_perf_on_all git status\n> +test_perf_on_all git add -A\n> +test_perf_on_all git add .\n> +test_perf_on_all git commit -a -m A\n> +\n> +test_done\n\ndiff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh\nindex e527316e66..2c07b04159 100755\n--- a/t/perf/p2000-sparse-operations.sh\n+++ b/t/perf/p2000-sparse-operations.sh\n@@ -4,22 +4,11 @@ test_description=\"test performance of Git operations using the index\"\n \n . ./perf-lib.sh\n \n-test_perf_default_repo\n+test_perf_nosubodules_repo\n \n SPARSE_CONE=f2/f4/f1\n \n test_expect_success 'setup repo and indexes' '\n-\tgit reset --hard HEAD &&\n-\t# Remove submodules from the example repo, because our\n-\t# duplication of the entire repo creates an unlikly data shape.\n-\tgit config --file .gitmodules --get-regexp \"submodule.*.path\" >modules &&\n-\tgit rm -f .gitmodules &&\n-\tfor module in $(awk \"{print \\$2}\" modules)\n-\tdo\n-\t\tgit rm $module || return 1\n-\tdone &&\n-\tgit commit -m \"remove submodules\" &&\n-\n \techo bogus >a &&\n \tcp a b &&\n \tgit add a b &&\ndiff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh\nindex e385c6896f..86b716ce8f 100644\n--- a/t/perf/perf-lib.sh\n+++ b/t/perf/perf-lib.sh\n@@ -128,6 +128,15 @@ test_perf_large_repo () {\n \tfi\n \ttest_perf_create_repo_from \"${1:-$TRASH_DIRECTORY}\" \"$GIT_PERF_LARGE_REPO\"\n }\n+test_perf_nosubodules_repo () {\n+\tif test \"$GIT_PERF_NOSUBMODULES_REPO\" = \"$GIT_BUILD_DIR\"; then\n+\t\techo \"warning: \\$GIT_PERF_NOSUBMODULES_REPO is \\$GIT_BUILD_DIR.\" >&2\n+\t\techo \"warning: This will probably work, but it has a submodule!\" >&2\n+\t\techo \"warning: point to another repo for representative measurements.\" >&2\n+\t\t# git rm dance here? optionally?\n+\tfi\n+\ttest_perf_create_repo_from \"${1:-$TRASH_DIRECTORY}\" \"$GIT_PERF_NOSUBMODULES_REPO\"\n+}\n test_checkout_worktree () {\n \tgit checkout-index -u -a ||\n \terror \"git checkout-index failed\"\n@@ -196,7 +205,7 @@ test_perf_ () {\n \telse\n \t\techo \"perf $test_count - $1:\"\n \tfi\n-\tfor i in $(test_seq 1 $GIT_PERF_REPEAT_COUNT); do\n+\tfor i in $(test_seq 1 $GIT_PERF_REP\n \t\tsay >&3 \"running: $2\"\n \t\tif test_run_perf_ \"$2\"\n \t\tthen\n"},{"id":"419451","messageId":"87r1kedv5b.fsf@evledraar.gmail.com","threadId":"55195","inReplyTo":"d3cfd34b84184bef42fe0892790d80091c9ca01b.1615912983.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 03/20] t1092: clean up script quoting","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-17T08:47:12Z","receivedAt":"2021-03-17T08:48:04Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Mar 16 2021, Derrick Stolee via GitGitGadget wrote:\n\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> This test was introduced in 19a0acc83e4 (t1092: test interesting\n> sparse-checkout scenarios, 2021-01-23), but these issues with quoting\n> were not noticed until starting this follow-up series. The old mechanism\n> would drop quoting such as in\n\nthe \"but these issues\" follows a partial sentence where we haven't\nintroduces \"what issues?\".\n\nPerhaps leading with some summary about $@ v.s. $*:\n\n    Fix a bug in the sparse checkout tests of \"$@\" being conflated with\n    \"$*\". The bug was introduced in 19a0acc83e4 ([...]), but had no\n    effect until now because XYZ ...\n\n\n>    test_all_match git commit -m \"touch README.md\"\n>\n> The above happened to work because README.md is a file in the\n> repository, so 'git commit -m touch REAMDE.md' would succeed by\n> accident.\n>\n> Other cases included quoting for no good reason, so clean that up now.\n\nMaybe just my taste, per your comment on another series of mine we might\nnot have the same sense of splitting up commits, but...\n\nI think in this case it's clearer to have these be two commits. We have\n3 hunks fixing the bug, and 6 on an unrelated cleanup. It's a lot easier\nfor eyeballing a fix to be able to glance just at the 3, especially with\nsomething like $@ v.s. $*.\n\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  t/t1092-sparse-checkout-compatibility.sh | 20 ++++++++++----------\n>  1 file changed, 10 insertions(+), 10 deletions(-)\n>\n> diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\n> index 8cd3e5a8d227..3725d3997e70 100755\n> --- a/t/t1092-sparse-checkout-compatibility.sh\n> +++ b/t/t1092-sparse-checkout-compatibility.sh\n> @@ -96,20 +96,20 @@ init_repos () {\n>  run_on_sparse () {\n>  \t(\n>  \t\tcd sparse-checkout &&\n> -\t\t$* >../sparse-checkout-out 2>../sparse-checkout-err\n> +\t\t\"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n>  \t)\n>  }\n>  \n>  run_on_all () {\n>  \t(\n>  \t\tcd full-checkout &&\n> -\t\t$* >../full-checkout-out 2>../full-checkout-err\n> +\t\t\"$@\" >../full-checkout-out 2>../full-checkout-err\n>  \t) &&\n> -\trun_on_sparse $*\n> +\trun_on_sparse \"$@\"\n>  }\n>  \n>  test_all_match () {\n> -\trun_on_all $* &&\n> +\trun_on_all \"$@\" &&\n>  \ttest_cmp full-checkout-out sparse-checkout-out &&\n>  \ttest_cmp full-checkout-err sparse-checkout-err\n>  }\n> @@ -119,7 +119,7 @@ test_expect_success 'status with options' '\n>  \ttest_all_match git status --porcelain=v2 &&\n>  \ttest_all_match git status --porcelain=v2 -z -u &&\n>  \ttest_all_match git status --porcelain=v2 -uno &&\n> -\trun_on_all \"touch README.md\" &&\n> +\trun_on_all touch README.md &&\n>  \ttest_all_match git status --porcelain=v2 &&\n>  \ttest_all_match git status --porcelain=v2 -z -u &&\n>  \ttest_all_match git status --porcelain=v2 -uno &&\n> @@ -135,7 +135,7 @@ test_expect_success 'add, commit, checkout' '\n>  \twrite_script edit-contents <<-\\EOF &&\n>  \techo text >>$1\n>  \tEOF\n> -\trun_on_all \"../edit-contents README.md\" &&\n> +\trun_on_all ../edit-contents README.md &&\n>  \n>  \ttest_all_match git add README.md &&\n>  \ttest_all_match git status --porcelain=v2 &&\n> @@ -144,7 +144,7 @@ test_expect_success 'add, commit, checkout' '\n>  \ttest_all_match git checkout HEAD~1 &&\n>  \ttest_all_match git checkout - &&\n>  \n> -\trun_on_all \"../edit-contents README.md\" &&\n> +\trun_on_all ../edit-contents README.md &&\n>  \n>  \ttest_all_match git add -A &&\n>  \ttest_all_match git status --porcelain=v2 &&\n> @@ -153,7 +153,7 @@ test_expect_success 'add, commit, checkout' '\n>  \ttest_all_match git checkout HEAD~1 &&\n>  \ttest_all_match git checkout - &&\n>  \n> -\trun_on_all \"../edit-contents deep/newfile\" &&\n> +\trun_on_all ../edit-contents deep/newfile &&\n>  \n>  \ttest_all_match git status --porcelain=v2 -uno &&\n>  \ttest_all_match git status --porcelain=v2 &&\n> @@ -186,7 +186,7 @@ test_expect_success 'diff --staged' '\n>  \twrite_script edit-contents <<-\\EOF &&\n>  \techo text >>README.md\n>  \tEOF\n> -\trun_on_all \"../edit-contents\" &&\n> +\trun_on_all ../edit-contents &&\n>  \n>  \ttest_all_match git diff &&\n>  \ttest_all_match git diff --staged &&\n> @@ -280,7 +280,7 @@ test_expect_success 'clean' '\n>  \techo bogus >>.gitignore &&\n>  \trun_on_all cp ../.gitignore . &&\n>  \ttest_all_match git add .gitignore &&\n> -\ttest_all_match git commit -m ignore-bogus-files &&\n> +\ttest_all_match git commit -m \"ignore bogus files\" &&\n>  \n>  \trun_on_sparse mkdir folder1 &&\n>  \trun_on_all touch folder1/bogus &&\n\n"},{"id":"419457","messageId":"87k0q6dj9q.fsf@evledraar.gmail.com","threadId":"55195","inReplyTo":"99292cdbaae488101d1c247ab94dc4b3b04d0311.1615912983.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 05/20] sparse-index: implement ensure_full_index()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-17T13:03:45Z","receivedAt":"2021-03-17T13:04:43Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Mar 16 2021, Derrick Stolee via GitGitGadget wrote:\n\n> From: Derrick Stolee <dstolee@microsoft.com>\n> [...]\n> +static int add_path_to_index(const struct object_id *oid,\n> +\t\t\t     struct strbuf *base, const char *path,\n> +\t\t\t     unsigned int mode, void *context)\n> +{\n> +\tstruct index_state *istate = (struct index_state *)context;\n> +\tstruct cache_entry *ce;\n> +\tsize_t len = base->len;\n> +\n> +\tif (S_ISDIR(mode))\n> +\t\treturn READ_TREE_RECURSIVE;\n> +\n> +\tstrbuf_addstr(base, path);\n> +\n> +\tce = make_cache_entry(istate, mode, oid, base->buf, 0, 0);\n> +\tce->ce_flags |= CE_SKIP_WORKTREE;\n> +\tset_index_entry(istate, istate->cache_nr++, ce);\n> +\n> +\tstrbuf_setlen(base, len);\n> +\treturn 0;\n> +}\n>  \n>  void ensure_full_index(struct index_state *istate)\n>  {\n> -\t/* intentionally left blank */\n> +\tint i;\n> +\tstruct index_state *full;\n> +\tstruct strbuf base = STRBUF_INIT;\n> +\n> +\tif (!istate || !istate->sparse_index)\n> +\t\treturn;\n> +\n> +\tif (!istate->repo)\n> +\t\tistate->repo = the_repository;\n> +\n> +\ttrace2_region_enter(\"index\", \"ensure_full_index\", istate->repo);\n> +\n> +\t/* initialize basics of new index */\n> +\tfull = xcalloc(1, sizeof(struct index_state));\n> +\tmemcpy(full, istate, sizeof(struct index_state));\n> +\n> +\t/* then change the necessary things */\n> +\tfull->sparse_index = 0;\n> +\tfull->cache_alloc = (3 * istate->cache_alloc) / 2;\n> +\tfull->cache_nr = 0;\n> +\tALLOC_ARRAY(full->cache, full->cache_alloc);\n> +\n> +\tfor (i = 0; i < istate->cache_nr; i++) {\n> +\t\tstruct cache_entry *ce = istate->cache[i];\n> +\t\tstruct tree *tree;\n> +\t\tstruct pathspec ps;\n> +\n> +\t\tif (!S_ISSPARSEDIR(ce->ce_mode)) {\n> +\t\t\tset_index_entry(full, full->cache_nr++, ce);\n> +\t\t\tcontinue;\n> +\t\t}\n> +\t\tif (!(ce->ce_flags & CE_SKIP_WORKTREE))\n> +\t\t\twarning(_(\"index entry is a directory, but not sparse (%08x)\"),\n> +\t\t\t\tce->ce_flags);\n> +\n> +\t\t/* recursively walk into cd->name */\n> +\t\ttree = lookup_tree(istate->repo, &ce->oid);\n> +\n> +\t\tmemset(&ps, 0, sizeof(ps));\n> +\t\tps.recursive = 1;\n> +\t\tps.has_wildcard = 1;\n> +\t\tps.max_depth = -1;\n> +\n> +\t\tstrbuf_setlen(&base, 0);\n> +\t\tstrbuf_add(&base, ce->name, strlen(ce->name));\n> +\n> +\t\tread_tree_at(istate->repo, tree, &base, &ps,\n> +\t\t\t     add_path_to_index, full);\n> +\n> +\t\t/* free directory entries. full entries are re-used */\n> +\t\tdiscard_cache_entry(ce);\n> +\t}\n> +\n> +\t/* Copy back into original index. */\n> +\tmemcpy(&istate->name_hash, &full->name_hash, sizeof(full->name_hash));\n> +\tistate->sparse_index = 0;\n> +\tfree(istate->cache);\n> +\tistate->cache = full->cache;\n> +\tistate->cache_nr = full->cache_nr;\n> +\tistate->cache_alloc = full->cache_alloc;\n> +\n> +\tstrbuf_release(&base);\n> +\tfree(full);\n> +\n> +\ttrace2_region_leave(\"index\", \"ensure_full_index\", istate->repo);\n>  }\n\nNot that I mind having added the read_tree_at() again, but just thinking\naloud here.\n\nSo we need this loop here because there's nothing like a read_tree_at()\nthat knows how to start at the non-tree root of the index, and then for\neach directory there we're going to perform the equivalent of a\nread_tree() there, but we need to set the base for add_path_to_index()\nsince we started at subdirs, not the root.\n\nThat's fine, but grepping around a bit I wonder if we shouldn't\neventually have some slightly fancier API that just works like\nread_tree() but takes an optional \"start at the index's root\" instead.\n\nWell, things that want that usually care about the index-specific bits,\nwhereas this \"I just care about the tree for these\" is more of a special\ncase I guess.\n"},{"id":"419458","messageId":"792a1249-166b-56f4-1e7d-0c5d7a1ba0e5@gmail.com","threadId":"55195","inReplyTo":"87tupadve8.fsf@evledraar.gmail.com","subject":"Re: [PATCH v3 02/20] t/perf: add performance test for sparse operations","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-17T13:05:49Z","receivedAt":"2021-03-17T13:06:54Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/17/2021 4:41 AM, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Tue, Mar 16 2021, Derrick Stolee via GitGitGadget wrote:\n>> +test_expect_success 'setup repo and indexes' '\n>> +\tgit reset --hard HEAD &&\n>> +\t# Remove submodules from the example repo, because our\n>> +\t# duplication of the entire repo creates an unlikly data shape.\n>> +\tgit config --file .gitmodules --get-regexp \"submodule.*.path\" >modules &&\n>> +\tgit rm -f .gitmodules &&\n>> +\tfor module in $(awk \"{print \\$2}\" modules)\n>> +\tdo\n>> +\t\tgit rm $module || return 1\n>> +\tdone &&\n>> +\tgit commit -m \"remove submodules\" &&\n> \n> Paradoxically with this you can no longer use a repo that's not git.git\n> or another repo that has submodules, since we'll die in trying to remove\n> them.\n\nGood point.\n\n> Also you don't have to \"git rm .gitmodules\", the \"git rm\" command\n> removes submodule entries.\n\nSure.\n\n> Perhaps just:\n> \n>     for module in $(git ls-files --stage | grep ^160000 | awk -F '\\t' '{ print $2 }')\n>     do\n>         git rm \"$module\"\n>     done\n> \n> Or another way of guarding against rm getting the empty list && commit?\n> \n> But it seems odd to be doing this at all, the point of the perf\n> framework is that you can point it at any repo, and some repos you want\n> to test will have submodules.\n\nYou're right that it should handle all repos. However, the point of\nthe test is to have many copies of the repo, but most of them are\nexcluded by sparse-directory entries. We don't collapse sparse-directory\nentries if there is a submodule inside, so the data shape is wrong after\nmaking all the copies.\n\nSo, I disagree with your approach in your suggested diff, and instead\noffer this one. I've tested this with git.git and another local repo\nwithout submodules and checked that everything works as expected.\n\ndiff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh\nindex e527316e66d..5c0d78eeeea 100755\n--- a/t/perf/p2000-sparse-operations.sh\n+++ b/t/perf/p2000-sparse-operations.sh\n@@ -10,15 +10,17 @@ SPARSE_CONE=f2/f4/f1\n \n test_expect_success 'setup repo and indexes' '\n \tgit reset --hard HEAD &&\n+\n \t# Remove submodules from the example repo, because our\n-\t# duplication of the entire repo creates an unlikly data shape.\n-\tgit config --file .gitmodules --get-regexp \"submodule.*.path\" >modules &&\n-\tgit rm -f .gitmodules &&\n-\tfor module in $(awk \"{print \\$2}\" modules)\n-\tdo\n-\t\tgit rm $module || return 1\n-\tdone &&\n-\tgit commit -m \"remove submodules\" &&\n+\t# duplication of the entire repo creates an unlikely data shape.\n+\tif (git config --file .gitmodules --get-regexp \"submodule.*.path\" >modules)\n+\tthen\n+\t\tfor module in $(awk \"{print \\$2}\" modules)\n+\t\tdo\n+\t\t\tgit rm $module || return 1\n+\t\tdone &&\n+\t\tgit commit -m \"remove submodules\" || return 1\n+\tfi &&\n \n \techo bogus >a &&\n \tcp a b &&\n\n> Seems like something like the WIP patch at the end on top would be\n> better.\n> \n>> +\techo bogus >a &&\n>> +\tcp a b &&\n>> +\tgit add a b &&\n>> +\tgit commit -m \"level 0\" &&\n>> +\tBLOB=$(git rev-parse HEAD:a) &&\n> \n> Isn't the way we're getting this $BLOB equivalent to just 'echo bogus |\n> git hash-object --stdin -w' why commit it?\n\nWe are committing it so we can add commits that deepen the copies,\nbut within those copies we have these known file paths.\n\n> This whole thing makes me think you just wanted a test_perf_fresh_repo\n> all along, but I think this would be much more useful if you took the\n> default repo and multiplied the size in its tree by some multiple.\n> \n> E.g. take the files we have in git.git, write a copy at prefix-1/,\n> prefix-2/ etc.\n\nThat is essentially what is happening here, but using multiple levels\nof directories. Using these multiple levels presents extra tree\nlookups and parsing in the event of expanding a sparse index to a\nfull one.\n\nThanks,\n-Stolee\n"},{"id":"419459","messageId":"875z1p29wd.fsf@evledraar.gmail.com","threadId":"55195","inReplyTo":"792a1249-166b-56f4-1e7d-0c5d7a1ba0e5@gmail.com","subject":"Re: [PATCH v3 02/20] t/perf: add performance test for sparse operations","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-17T13:21:38Z","receivedAt":"2021-03-17T13:22:44Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Mar 17 2021, Derrick Stolee wrote:\n\n> On 3/17/2021 4:41 AM, Ævar Arnfjörð Bjarmason wrote:\n>> \n>> On Tue, Mar 16 2021, Derrick Stolee via GitGitGadget wrote:\n>>> +test_expect_success 'setup repo and indexes' '\n>>> +\tgit reset --hard HEAD &&\n>>> +\t# Remove submodules from the example repo, because our\n>>> +\t# duplication of the entire repo creates an unlikly data shape.\n>>> +\tgit config --file .gitmodules --get-regexp \"submodule.*.path\" >modules &&\n>>> +\tgit rm -f .gitmodules &&\n>>> +\tfor module in $(awk \"{print \\$2}\" modules)\n>>> +\tdo\n>>> +\t\tgit rm $module || return 1\n>>> +\tdone &&\n>>> +\tgit commit -m \"remove submodules\" &&\n>> \n>> Paradoxically with this you can no longer use a repo that's not git.git\n>> or another repo that has submodules, since we'll die in trying to remove\n>> them.\n>\n> Good point.\n>\n>> Also you don't have to \"git rm .gitmodules\", the \"git rm\" command\n>> removes submodule entries.\n>\n> Sure.\n>\n>> Perhaps just:\n>> \n>>     for module in $(git ls-files --stage | grep ^160000 | awk -F '\\t' '{ print $2 }')\n>>     do\n>>         git rm \"$module\"\n>>     done\n>> \n>> Or another way of guarding against rm getting the empty list && commit?\n>> \n>> But it seems odd to be doing this at all, the point of the perf\n>> framework is that you can point it at any repo, and some repos you want\n>> to test will have submodules.\n>\n> You're right that it should handle all repos. However, the point of\n> the test is to have many copies of the repo, but most of them are\n> excluded by sparse-directory entries. We don't collapse sparse-directory\n> entries if there is a submodule inside, so the data shape is wrong after\n> making all the copies.\n>\n> So, I disagree with your approach in your suggested diff, and instead\n> offer this one. I've tested this with git.git and another local repo\n> without submodules and checked that everything works as expected.\n\nWhat's got me confused here is that there's two uses for the perf\nframework in this context.\n\nIt's to use an empty/git.git as a test repo to demonstrate something,\nbut then also that you can run it in your arbitrary repo, and e.g. see\nhow much a given feature might benefit you.\n\nHence suggesting that maybe test_perf_fresh_repois better here, because\nby using test_perf_default_repo you're creating the expectation that you\ncan run the perf test, observe an %X difference, and that'll be\ngive-or-take what you'll get for that use case if you enable the feature.\n\nExcept it won't because the repo has submodules, which we deleted for\nthe perf test...\n\n> diff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh\n> index e527316e66d..5c0d78eeeea 100755\n> --- a/t/perf/p2000-sparse-operations.sh\n> +++ b/t/perf/p2000-sparse-operations.sh\n> @@ -10,15 +10,17 @@ SPARSE_CONE=f2/f4/f1\n>  \n>  test_expect_success 'setup repo and indexes' '\n>  \tgit reset --hard HEAD &&\n> +\n>  \t# Remove submodules from the example repo, because our\n> -\t# duplication of the entire repo creates an unlikly data shape.\n> -\tgit config --file .gitmodules --get-regexp \"submodule.*.path\" >modules &&\n> -\tgit rm -f .gitmodules &&\n> -\tfor module in $(awk \"{print \\$2}\" modules)\n> -\tdo\n> -\t\tgit rm $module || return 1\n> -\tdone &&\n> -\tgit commit -m \"remove submodules\" &&\n> +\t# duplication of the entire repo creates an unlikely data shape.\n> +\tif (git config --file .gitmodules --get-regexp \"submodule.*.path\" >modules)\n\nA subshell isn't needed here.\n\nFWIW the reason I got this out of ls-files is because you can have\nsubmodules without .gitmodules entries, rare and broken, but seemed more\ndirect to grep the mode bits.\n\n> +\tthen\n> +\t\tfor module in $(awk \"{print \\$2}\" modules)\n> +\t\tdo\n> +\t\t\tgit rm $module || return 1\n> +\t\tdone &&\n\nOnce we know we have submodules we can just do this without the loop.\n\n    git rm $(awk \"{print \\$2}\" modules)\n\n\n\n> +\t\tgit commit -m \"remove submodules\" || return 1\n> +\tfi &&\n>  \n>  \techo bogus >a &&\n>  \tcp a b &&\n>\n>> Seems like something like the WIP patch at the end on top would be\n>> better.\n>> \n>>> +\techo bogus >a &&\n>>> +\tcp a b &&\n>>> +\tgit add a b &&\n>>> +\tgit commit -m \"level 0\" &&\n>>> +\tBLOB=$(git rev-parse HEAD:a) &&\n>> \n>> Isn't the way we're getting this $BLOB equivalent to just 'echo bogus |\n>> git hash-object --stdin -w' why commit it?\n>\n> We are committing it so we can add commits that deepen the copies,\n> but within those copies we have these known file paths.\n>\n>> This whole thing makes me think you just wanted a test_perf_fresh_repo\n>> all along, but I think this would be much more useful if you took the\n>> default repo and multiplied the size in its tree by some multiple.\n>> \n>> E.g. take the files we have in git.git, write a copy at prefix-1/,\n>> prefix-2/ etc.\n>\n> That is essentially what is happening here, but using multiple levels\n> of directories. Using these multiple levels presents extra tree\n> lookups and parsing in the event of expanding a sparse index to a\n> full one.\n\n*nod*\n\nAnyway, this thread's a bit of a bikeshed on my part, I was just\nwondering if & what part of the test relied on the existing repo if it\nwas mostly setting up its own test data.\n"},{"id":"419460","messageId":"20210317132814.30175-2-avarab@gmail.com","threadId":"55195","inReplyTo":"dffe8821fde290a1e19b2968098b007b9ac213e6.1615912983.git.gitgitgadget@gmail.com","subject":"[RFC/PATCH 1/5] ls-files: defer read_index() after parse_options() etc.","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-17T13:28:10Z","receivedAt":"2021-03-17T13:29:24Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Move the reading of the index below the parsing of options. We'll need\nto setup some index options in the next commit after option parsing,\nbut in any case it makes sense to give parse_options() handling a\nchance to die early before we perform the more expensive operation of\nreading the index.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/ls-files.c | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 13bcc2d847..eb72d16493 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -681,9 +681,6 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\tprefix_len = strlen(prefix);\n \tgit_config(git_default_config, NULL);\n \n-\tif (repo_read_index(the_repository) < 0)\n-\t\tdie(\"index file corrupt\");\n-\n \targc = parse_options(argc, argv, prefix, builtin_ls_files_options,\n \t\t\tls_files_usage, 0);\n \tpl = add_pattern_list(&dir, EXC_CMDL, \"--exclude option\");\n@@ -743,6 +740,12 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\tmax_prefix = common_prefix(&pathspec);\n \tmax_prefix_len = get_common_prefix_len(max_prefix);\n \n+\t/*\n+\t * Read the index after parse options etc. have had a chance\n+\t * to die early.\n+\t */\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(\"index file corrupt\");\n \tprune_index(the_repository->index, max_prefix, max_prefix_len);\n \n \t/* Treat unmatching pathspec elements as errors */\n-- \n2.31.0.260.g719c683c1d\n\n"},{"id":"419461","messageId":"20210317132814.30175-3-avarab@gmail.com","threadId":"55195","inReplyTo":"dffe8821fde290a1e19b2968098b007b9ac213e6.1615912983.git.gitgitgadget@gmail.com","subject":"[RFC/PATCH 2/5] ls-files: make \"mode\" in show_ce() loop a variable","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-17T13:28:11Z","receivedAt":"2021-03-17T13:29:24Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"In a subsequent commit I'll optionally change the mode in a new sparse\nmode, let's do this first to make that change smaller.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/ls-files.c | 10 +++++++++-\n 1 file changed, 9 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex eb72d16493..4db75351f2 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -242,9 +242,17 @@ static void show_ce(struct repository *repo, struct dir_struct *dir,\n \t\tif (!show_stage) {\n \t\t\tfputs(tag, stdout);\n \t\t} else {\n+\t\t\tunsigned int mode = ce->ce_mode;\n+\t\t\tif (show_sparse && S_ISSPARSEDIR(mode))\n+\t\t\t\t/*\n+\t\t\t\t * We could just do & 0177777 all the\n+\t\t\t\t * time, just make it clear this is\n+\t\t\t\t * for --stage-sparse.\n+\t\t\t\t */\n+\t\t\t\tmode &= 0177777;\n \t\t\tprintf(\"%s%06o %s %d\\t\",\n \t\t\t       tag,\n-\t\t\t       ce->ce_mode,\n+\t\t\t       mode,\n \t\t\t       find_unique_abbrev(&ce->oid, abbrev),\n \t\t\t       ce_stage(ce));\n \t\t}\n-- \n2.31.0.260.g719c683c1d\n\n"},{"id":"419462","messageId":"20210317132814.30175-4-avarab@gmail.com","threadId":"55195","inReplyTo":"dffe8821fde290a1e19b2968098b007b9ac213e6.1615912983.git.gitgitgadget@gmail.com","subject":"[RFC/PATCH 3/5] ls-files: add and use a new --sparse option","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-17T13:28:12Z","receivedAt":"2021-03-17T13:29:25Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/git-ls-files.txt           |  4 ++\n builtin/ls-files.c                       | 10 ++++-\n t/t1091-sparse-checkout-builtin.sh       |  9 ++--\n t/t1092-sparse-checkout-compatibility.sh | 57 ++++++++++++++++--------\n 4 files changed, 56 insertions(+), 24 deletions(-)\n\ndiff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt\nindex 6d11ab506b..1145e960a4 100644\n--- a/Documentation/git-ls-files.txt\n+++ b/Documentation/git-ls-files.txt\n@@ -71,6 +71,10 @@ OPTIONS\n --unmerged::\n \tShow unmerged files in the output (forces --stage)\n \n+--sparse::\n+\tShow sparse directories in the output instead of expanding\n+\tthem (forces --stage)\n+\n -k::\n --killed::\n \tShow files on the filesystem that need to be removed due\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 4db75351f2..1ebbb63c10 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -26,6 +26,7 @@ static int show_deleted;\n static int show_cached;\n static int show_others;\n static int show_stage;\n+static int show_sparse;\n static int show_unmerged;\n static int show_resolve_undo;\n static int show_modified;\n@@ -639,6 +640,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t\tDIR_SHOW_IGNORED),\n \t\tOPT_BOOL('s', \"stage\", &show_stage,\n \t\t\tN_(\"show staged contents' object name in the output\")),\n+\t\tOPT_BOOL(0, \"sparse\", &show_sparse,\n+\t\t\tN_(\"show unexpanded sparse directories in the output\")),\n \t\tOPT_BOOL('k', \"killed\", &show_killed,\n \t\t\tN_(\"show files on the filesystem that need to be removed\")),\n \t\tOPT_BIT(0, \"directory\", &dir.flags,\n@@ -705,12 +708,17 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\ttag_skip_worktree = \"S \";\n \t\ttag_resolve_undo = \"U \";\n \t}\n+\tif (show_sparse) {\n+\t\tprepare_repo_settings(the_repository);\n+\t\tthe_repository->settings.command_requires_full_index = 0;\n+\t}\n \tif (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)\n \t\trequire_work_tree = 1;\n-\tif (show_unmerged)\n+\tif (show_unmerged || show_sparse)\n \t\t/*\n \t\t * There's no point in showing unmerged unless\n \t\t * you also show the stage information.\n+\t\t * The same goes for the --sparse option.\n \t\t */\n \t\tshow_stage = 1;\n \tif (show_tag || show_stage)\ndiff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh\nindex ff1ad570a2..c823df423c 100755\n--- a/t/t1091-sparse-checkout-builtin.sh\n+++ b/t/t1091-sparse-checkout-builtin.sh\n@@ -208,12 +208,13 @@ test_expect_success 'sparse-checkout disable' '\n test_expect_success 'sparse-index enabled and disabled' '\n \tgit -C repo sparse-checkout init --cone --sparse-index &&\n \ttest_cmp_config -C repo true extensions.sparseIndex &&\n-\ttest-tool -C repo read-cache --table >cache &&\n-\tgrep \" tree \" cache &&\n+\tgit -C repo ls-files --sparse >cache &&\n+\tgrep \"^040000 \" cache >lines &&\n+\ttest_line_count = 3 lines &&\n \n \tgit -C repo sparse-checkout disable &&\n-\ttest-tool -C repo read-cache --table >cache &&\n-\t! grep \" tree \" cache &&\n+\tgit -C repo ls-files --sparse >cache &&\n+\t! grep \"^040000 \" cache &&\n \tgit -C repo config --list >config &&\n \t! grep extensions.sparseindex config\n '\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex d97bf9b645..48d3920490 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -136,48 +136,67 @@ test_sparse_match () {\n \ttest_cmp sparse-checkout-err sparse-index-err\n }\n \n+test_index_entry_like () {\n+\tdir=$1\n+\tshift\n+\tfmt=$1\n+\tshift\n+\trev=$1\n+\tshift\n+\tentry=$1\n+\tshift\n+\tfile=$1\n+\tshift\n+\thash=$(git -C \"$dir\" rev-parse \"$rev\") &&\n+\tprintf \"$fmt\\n\" \"$hash\" \"$entry\" >expected &&\n+\tif grep \"$entry\" \"$file\" >line\n+\tthen\n+\t\ttest_cmp expected line\n+\telse\n+\t\tcat cache &&\n+\t\tfalse\n+\tfi\n+}\n+\n test_expect_success 'sparse-index contents' '\n \tinit_repos &&\n \n-\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tgit -C sparse-index ls-files --sparse >cache &&\n \tfor dir in folder1 folder2 x\n \tdo\n-\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n-\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n-\t\t\t|| return 1\n+\t\ttest_index_entry_like sparse-index \"040000 %s 0\\t%s\" \"HEAD:$dir\" \"$dir/\" cache || return 1\n \tdone &&\n \n \tgit -C sparse-index sparse-checkout set folder1 &&\n \n-\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tgit -C sparse-index ls-files --sparse >cache &&\n \tfor dir in deep folder2 x\n \tdo\n-\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n-\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n-\t\t\t|| return 1\n+\t\ttest_index_entry_like sparse-index \"040000 %s 0\\t%s\" \"HEAD:$dir\" \"$dir/\" cache || return 1\n \tdone &&\n \n \tgit -C sparse-index sparse-checkout set deep/deeper1 &&\n \n-\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tgit -C sparse-index ls-files --sparse >cache &&\n \tfor dir in deep/deeper2 folder1 folder2 x\n \tdo\n-\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n-\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n-\t\t\t|| return 1\n+\t\ttest_index_entry_like sparse-index \"040000 %s 0\\t%s\" \"HEAD:$dir\" \"$dir/\" cache || return 1\n \tdone &&\n \n+\tgrep 040000 cache >lines &&\n+\ttest_line_count = 4 lines &&\n+\n \t# Disabling the sparse-index removes tree entries with full ones\n \tgit -C sparse-index sparse-checkout init --no-sparse-index &&\n \n-\ttest-tool -C sparse-index read-cache --table >cache &&\n-\t! grep \"040000 tree\" cache &&\n-\ttest_sparse_match test-tool read-cache --table\n+\tgit -C sparse-index ls-files --sparse >cache &&\n+\t! grep \"^040000 \" cache >lines &&\n+\ttest_sparse_match git ls-tree -r HEAD\n '\n \n test_expect_success 'expanded in-memory index matches full index' '\n \tinit_repos &&\n-\ttest_sparse_match test-tool read-cache --expand --table\n+\ttest_sparse_match git ls-tree -r HEAD\n '\n \n test_expect_success 'status with options' '\n@@ -394,9 +413,9 @@ test_expect_success 'submodule handling' '\n \ttest_all_match git commit -m \"add submodule\" &&\n \n \t# having a submodule prevents \"modules\" from collapse\n-\ttest-tool -C sparse-index read-cache --table >cache &&\n-\tgrep \"100644 blob .*\tmodules/a\" cache &&\n-\tgrep \"160000 commit $(git -C initial-repo rev-parse HEAD)\tmodules/sub\" cache\n+\tgit -C sparse-index ls-files --sparse >cache &&\n+\ttest_index_entry_like sparse-index \"100644 %s 0\\t%s\" \"HEAD:modules/a\" \"modules/a\" cache &&\n+\ttest_index_entry_like sparse-index \"160000 %s 0\\t%s\" \"HEAD:modules/sub\" \"modules/sub\" cache\n '\n \n test_expect_success 'sparse-index is expanded and converted back' '\n-- \n2.31.0.260.g719c683c1d\n\n"},{"id":"419463","messageId":"20210317132814.30175-5-avarab@gmail.com","threadId":"55195","inReplyTo":"dffe8821fde290a1e19b2968098b007b9ac213e6.1615912983.git.gitgitgadget@gmail.com","subject":"[RFC/PATCH 4/5] test-tool read-cache: --table is redundant to ls-files","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-17T13:28:13Z","receivedAt":"2021-03-17T13:29:25Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n t/helper/test-read-cache.c | 43 --------------------------------------\n 1 file changed, 43 deletions(-)\n\ndiff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c\nindex b52c174acc..2499999af3 100644\n--- a/t/helper/test-read-cache.c\n+++ b/t/helper/test-read-cache.c\n@@ -1,54 +1,16 @@\n #include \"test-tool.h\"\n #include \"cache.h\"\n #include \"config.h\"\n-#include \"blob.h\"\n-#include \"commit.h\"\n-#include \"tree.h\"\n-#include \"sparse-index.h\"\n-\n-static void print_cache_entry(struct cache_entry *ce)\n-{\n-\tconst char *type;\n-\tprintf(\"%06o \", ce->ce_mode & 0177777);\n-\n-\tif (S_ISSPARSEDIR(ce->ce_mode))\n-\t\ttype = tree_type;\n-\telse if (S_ISGITLINK(ce->ce_mode))\n-\t\ttype = commit_type;\n-\telse\n-\t\ttype = blob_type;\n-\n-\tprintf(\"%s %s\\t%s\\n\",\n-\t       type,\n-\t       oid_to_hex(&ce->oid),\n-\t       ce->name);\n-}\n-\n-static void print_cache(struct index_state *istate)\n-{\n-\tint i;\n-\tfor (i = 0; i < istate->cache_nr; i++)\n-\t\tprint_cache_entry(istate->cache[i]);\n-}\n \n int cmd__read_cache(int argc, const char **argv)\n {\n \tstruct repository *r = the_repository;\n \tint i, cnt = 1;\n \tconst char *name = NULL;\n-\tint table = 0, expand = 0;\n-\n-\tinitialize_the_repository();\n-\tprepare_repo_settings(r);\n-\tr->settings.command_requires_full_index = 0;\n \n \tfor (++argv, --argc; *argv && starts_with(*argv, \"--\"); ++argv, --argc) {\n \t\tif (skip_prefix(*argv, \"--print-and-refresh=\", &name))\n \t\t\tcontinue;\n-\t\tif (!strcmp(*argv, \"--table\"))\n-\t\t\ttable = 1;\n-\t\telse if (!strcmp(*argv, \"--expand\"))\n-\t\t\texpand = 1;\n \t}\n \n \tif (argc == 1)\n@@ -59,9 +21,6 @@ int cmd__read_cache(int argc, const char **argv)\n \tfor (i = 0; i < cnt; i++) {\n \t\trepo_read_index(r);\n \n-\t\tif (expand)\n-\t\t\tensure_full_index(r->index);\n-\n \t\tif (name) {\n \t\t\tint pos;\n \n@@ -74,8 +33,6 @@ int cmd__read_cache(int argc, const char **argv)\n \t\t\t       ce_uptodate(r->index->cache[pos]) ? \"\" : \" not\");\n \t\t\twrite_file(name, \"%d\\n\", i);\n \t\t}\n-\t\tif (table)\n-\t\t\tprint_cache(r->index);\n \t\tdiscard_index(r->index);\n \t}\n \treturn 0;\n-- \n2.31.0.260.g719c683c1d\n\n"},{"id":"419464","messageId":"20210317132814.30175-1-avarab@gmail.com","threadId":"55195","inReplyTo":"dffe8821fde290a1e19b2968098b007b9ac213e6.1615912983.git.gitgitgadget@gmail.com","subject":"[RFC/PATCH 0/5] Re: [PATCH v3 07/20] test-read-cache: print cache entries with --table","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-17T13:28:09Z","receivedAt":"2021-03-17T13:29:25Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> This table is helpful for discovering data in the index to ensure it is\n> being written correctly, especially as we build and test the\n> sparse-index. This table includes an output format similar to 'git\n> ls-tree', but should not be compared to that directly. The biggest\n> reasons are that 'git ls-tree' includes a tree entry for every\n> subdirectory, even those that would not appear as a sparse directory in\n> a sparse-index. Further, 'git ls-tree' does not use a trailing directory\n> separator for its tree rows.\n>\n> This does not print the stat() information for the blobs. That could be\n> added in a future change with another option. The tests that are added\n> in the next few changes care only about the object types and IDs.\n>\n> To make the option parsing slightly more robust, wrap the string\n> comparisons in a loop adapted from test-dir-iterator.c.\n>\n> Care must be taken with the final check for the 'cnt' variable. We\n> continue the expectation that the numerical value is the final argument.\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  t/helper/test-read-cache.c | 55 +++++++++++++++++++++++++++++++-------\n>  1 file changed, 45 insertions(+), 10 deletions(-)\n>\n> diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c\n> index 244977a29bdf..6cfd8f2de71c 100644\n> --- a/t/helper/test-read-cache.c\n> +++ b/t/helper/test-read-cache.c\n> @@ -1,36 +1,71 @@\n>  #include \"test-tool.h\"\n>  #include \"cache.h\"\n>  #include \"config.h\"\n> +#include \"blob.h\"\n> +#include \"commit.h\"\n> +#include \"tree.h\"\n> +\n> +static void print_cache_entry(struct cache_entry *ce)\n> +{\n> +\tconst char *type;\n> +\tprintf(\"%06o \", ce->ce_mode & 0177777);\n> +\n> +\tif (S_ISSPARSEDIR(ce->ce_mode))\n> +\t\ttype = tree_type;\n> +\telse if (S_ISGITLINK(ce->ce_mode))\n> +\t\ttype = commit_type;\n> +\telse\n> +\t\ttype = blob_type;\n> +\n> +\tprintf(\"%s %s\\t%s\\n\",\n> +\t       type,\n> +\t       oid_to_hex(&ce->oid),\n> +\t       ce->name);\n> +}\n> +\n\nSo we have a test tool that's mostly ls-files but mocks the output\nls-tree would emit, won't these tests eventually care about what stage\nthings are in?\n\nWhat follows is an RFC series on top that's the result of me wondering\nwhy if we're adding new index constructs we aren't updating our\nplumbing to emit that data, can we just add this to ls-files and drop\nthis test helper?\n\nTurns out: Yes we can.\n\nÆvar Arnfjörð Bjarmason (5):\n  ls-files: defer read_index() after parse_options() etc.\n  ls-files: make \"mode\" in show_ce() loop a variable\n  ls-files: add and use a new --sparse option\n  test-tool read-cache: --table is redundant to ls-files\n  test-tool: split up test-tool read-cache\n\n Documentation/git-ls-files.txt           |  4 ++\n Makefile                                 |  3 +-\n builtin/ls-files.c                       | 29 +++++++--\n t/helper/test-read-cache-again.c         | 31 +++++++++\n t/helper/test-read-cache-perf.c          | 21 ++++++\n t/helper/test-read-cache.c               | 82 ------------------------\n t/helper/test-tool.c                     |  3 +-\n t/helper/test-tool.h                     |  3 +-\n t/perf/p0002-read-cache.sh               |  2 +-\n t/t1091-sparse-checkout-builtin.sh       |  9 +--\n t/t1092-sparse-checkout-compatibility.sh | 57 ++++++++++------\n t/t7519-status-fsmonitor.sh              |  2 +-\n 12 files changed, 131 insertions(+), 115 deletions(-)\n create mode 100644 t/helper/test-read-cache-again.c\n create mode 100644 t/helper/test-read-cache-perf.c\n delete mode 100644 t/helper/test-read-cache.c\n\n-- \n2.31.0.260.g719c683c1d\n\n"},{"id":"419465","messageId":"20210317132814.30175-6-avarab@gmail.com","threadId":"55195","inReplyTo":"dffe8821fde290a1e19b2968098b007b9ac213e6.1615912983.git.gitgitgadget@gmail.com","subject":"[RFC/PATCH 5/5] test-tool: split up test-tool read-cache","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-17T13:28:14Z","receivedAt":"2021-03-17T13:29:25Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Since the \"test-tool read-cache\" was originally added back in\n1ecb5ff141 (read-cache: add simple performance test, 2013-06-09) it's\nbeen growing all sorts of bells and whistles that aren't very\nconducive to performance testing the index, e.g. it learned how to\nread config.\n\nLet's split what remains of the \"test-tool read-cache\" into the two\nnarrow use-cases it's used for.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Makefile                         |  3 ++-\n t/helper/test-read-cache-again.c | 31 +++++++++++++++++++++++++\n t/helper/test-read-cache-perf.c  | 21 +++++++++++++++++\n t/helper/test-read-cache.c       | 39 --------------------------------\n t/helper/test-tool.c             |  3 ++-\n t/helper/test-tool.h             |  3 ++-\n t/perf/p0002-read-cache.sh       |  2 +-\n t/t7519-status-fsmonitor.sh      |  2 +-\n 8 files changed, 60 insertions(+), 44 deletions(-)\n create mode 100644 t/helper/test-read-cache-again.c\n create mode 100644 t/helper/test-read-cache-perf.c\n delete mode 100644 t/helper/test-read-cache.c\n\ndiff --git a/Makefile b/Makefile\nindex 89b1d53741..a1bbb818d9 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -724,7 +724,8 @@ TEST_BUILTINS_OBJS += test-prio-queue.o\n TEST_BUILTINS_OBJS += test-proc-receive.o\n TEST_BUILTINS_OBJS += test-progress.o\n TEST_BUILTINS_OBJS += test-reach.o\n-TEST_BUILTINS_OBJS += test-read-cache.o\n+TEST_BUILTINS_OBJS += test-read-cache-again.o\n+TEST_BUILTINS_OBJS += test-read-cache-perf.o\n TEST_BUILTINS_OBJS += test-read-graph.o\n TEST_BUILTINS_OBJS += test-read-midx.o\n TEST_BUILTINS_OBJS += test-ref-store.o\ndiff --git a/t/helper/test-read-cache-again.c b/t/helper/test-read-cache-again.c\nnew file mode 100644\nindex 0000000000..5e20ca1c8f\n--- /dev/null\n+++ b/t/helper/test-read-cache-again.c\n@@ -0,0 +1,31 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+\n+int cmd__read_cache_again(int argc, const char **argv)\n+{\n+\tstruct repository *r = the_repository;\n+\tint cnt;\n+\tconst char *name;\n+\n+\tif (argc != 2)\n+\t\tdie(\"usage: test-tool read-cache-again <count> <file>\");\n+\n+\tcnt = strtol(argv[0], NULL, 0);\n+\tname = argv[2];\n+\n+\tsetup_git_directory();\n+\twhile (cnt--) {\n+\t\tint pos;\n+\t\trepo_read_index(r);\n+\t\trefresh_index(r->index, REFRESH_QUIET,\n+\t\t\t      NULL, NULL, NULL);\n+\t\tpos = index_name_pos(r->index, name, strlen(name));\n+\t\tif (pos < 0)\n+\t\t\tdie(\"%s not in index\", name);\n+\t\tprintf(\"%s is%s up to date\\n\", name,\n+\t\t       ce_uptodate(r->index->cache[pos]) ? \"\" : \" not\");\n+\t\twrite_file(name, \"%d\\n\", cnt);\n+\t\tdiscard_index(r->index);\n+\t}\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-read-cache-perf.c b/t/helper/test-read-cache-perf.c\nnew file mode 100644\nindex 0000000000..ac9c297efa\n--- /dev/null\n+++ b/t/helper/test-read-cache-perf.c\n@@ -0,0 +1,21 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+\n+int cmd__read_cache_perf(int argc, const char **argv)\n+{\n+\tstruct repository *r = the_repository;\n+\tint cnt = 1000;\n+\n+\tif (argc == 1)\n+\t\tcnt = strtol(argv[0], NULL, 0);\n+\telse if (argc)\n+\t\tdie(\"usage: test-tool read-cache-perf [<count>]\");\n+\n+\tsetup_git_directory();\n+\twhile (cnt--) {\n+\t\trepo_read_index(r);\n+\t\tdiscard_index(r->index);\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c\ndeleted file mode 100644\nindex 2499999af3..0000000000\n--- a/t/helper/test-read-cache.c\n+++ /dev/null\n@@ -1,39 +0,0 @@\n-#include \"test-tool.h\"\n-#include \"cache.h\"\n-#include \"config.h\"\n-\n-int cmd__read_cache(int argc, const char **argv)\n-{\n-\tstruct repository *r = the_repository;\n-\tint i, cnt = 1;\n-\tconst char *name = NULL;\n-\n-\tfor (++argv, --argc; *argv && starts_with(*argv, \"--\"); ++argv, --argc) {\n-\t\tif (skip_prefix(*argv, \"--print-and-refresh=\", &name))\n-\t\t\tcontinue;\n-\t}\n-\n-\tif (argc == 1)\n-\t\tcnt = strtol(argv[0], NULL, 0);\n-\tsetup_git_directory();\n-\tgit_config(git_default_config, NULL);\n-\n-\tfor (i = 0; i < cnt; i++) {\n-\t\trepo_read_index(r);\n-\n-\t\tif (name) {\n-\t\t\tint pos;\n-\n-\t\t\trefresh_index(r->index, REFRESH_QUIET,\n-\t\t\t\t      NULL, NULL, NULL);\n-\t\t\tpos = index_name_pos(r->index, name, strlen(name));\n-\t\t\tif (pos < 0)\n-\t\t\t\tdie(\"%s not in index\", name);\n-\t\t\tprintf(\"%s is%s up to date\\n\", name,\n-\t\t\t       ce_uptodate(r->index->cache[pos]) ? \"\" : \" not\");\n-\t\t\twrite_file(name, \"%d\\n\", i);\n-\t\t}\n-\t\tdiscard_index(r->index);\n-\t}\n-\treturn 0;\n-}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f97cd9f48a..1334fa25ba 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -52,7 +52,8 @@ static struct test_cmd cmds[] = {\n \t{ \"proc-receive\", cmd__proc_receive},\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n-\t{ \"read-cache\", cmd__read_cache },\n+\t{ \"read-cache-again\", cmd__read_cache_again },\n+\t{ \"read-cache-perf\", cmd__read_cache_perf },\n \t{ \"read-graph\", cmd__read_graph },\n \t{ \"read-midx\", cmd__read_midx },\n \t{ \"ref-store\", cmd__ref_store },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 28072c0ad5..d70cde8574 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -41,7 +41,8 @@ int cmd__prio_queue(int argc, const char **argv);\n int cmd__proc_receive(int argc, const char **argv);\n int cmd__progress(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n-int cmd__read_cache(int argc, const char **argv);\n+int cmd__read_cache_again(int argc, const char **argv);\n+int cmd__read_cache_perf(int argc, const char **argv);\n int cmd__read_graph(int argc, const char **argv);\n int cmd__read_midx(int argc, const char **argv);\n int cmd__ref_store(int argc, const char **argv);\ndiff --git a/t/perf/p0002-read-cache.sh b/t/perf/p0002-read-cache.sh\nindex cdd105a594..d0ba5173fb 100755\n--- a/t/perf/p0002-read-cache.sh\n+++ b/t/perf/p0002-read-cache.sh\n@@ -8,7 +8,7 @@ test_perf_default_repo\n \n count=1000\n test_perf \"read_cache/discard_cache $count times\" \"\n-\ttest-tool read-cache $count\n+\ttest-tool read-cache-perf $count\n \"\n \n test_done\ndiff --git a/t/t7519-status-fsmonitor.sh b/t/t7519-status-fsmonitor.sh\nindex 45d025f960..3761a8781d 100755\n--- a/t/t7519-status-fsmonitor.sh\n+++ b/t/t7519-status-fsmonitor.sh\n@@ -359,7 +359,7 @@ test_expect_success UNTRACKED_CACHE 'ignore .git changes when invalidating UNTR'\n test_expect_success 'discard_index() also discards fsmonitor info' '\n \ttest_config core.fsmonitor \"$TEST_DIRECTORY/t7519/fsmonitor-all\" &&\n \ttest_might_fail git update-index --refresh &&\n-\ttest-tool read-cache --print-and-refresh=tracked 2 >actual &&\n+\ttest-tool read-cache-again 2 tracked >actual &&\n \tprintf \"tracked is%s up to date\\n\" \"\" \" not\" >expect &&\n \ttest_cmp expect actual\n '\n-- \n2.31.0.260.g719c683c1d\n\n"},{"id":"419466","messageId":"8735wt29dk.fsf@evledraar.gmail.com","threadId":"55195","inReplyTo":"20210317132814.30175-6-avarab@gmail.com","subject":"Re: [RFC/PATCH 5/5] test-tool: split up test-tool read-cache","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-17T13:32:55Z","receivedAt":"2021-03-17T13:33:43Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Mar 17 2021, Ævar Arnfjörð Bjarmason wrote:\n\n> +\tif (argc != 2)\n> +\t\tdie(\"usage: test-tool read-cache-again <count> <file>\");\n> +\n> +\tcnt = strtol(argv[0], NULL, 0);\n> +\tname = argv[2];\n\nThis is needed on top, the perils of sending out ad-hoc RFC patches from\nthe working tree..:\n\ndiff --git a/t/helper/test-read-cache-again.c b/t/helper/test-read-cache-again.c\nindex 5e20ca1c8f..aa97b3aaf3 100644\n--- a/t/helper/test-read-cache-again.c\n+++ b/t/helper/test-read-cache-again.c\n@@ -7,10 +7,9 @@ int cmd__read_cache_again(int argc, const char **argv)\n \tint cnt;\n \tconst char *name;\n \n-\tif (argc != 2)\n+\tif (argc != 3)\n \t\tdie(\"usage: test-tool read-cache-again <count> <file>\");\n-\n-\tcnt = strtol(argv[0], NULL, 0);\n+\tcnt = strtol(argv[1], NULL, 0);\n \tname = argv[2];\n \n \tsetup_git_directory();\n"},{"id":"419467","messageId":"87zgz1zywk.fsf@evledraar.gmail.com","threadId":"55195","inReplyTo":"4000c5cdd4cf6008358a02d1b0244b24e61b3e3e.1615912983.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 13/20] unpack-trees: allow sparse directories","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-17T13:35:07Z","receivedAt":"2021-03-17T13:35:52Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Mar 16 2021, Derrick Stolee via GitGitGadget wrote:\n\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> The index_pos_by_traverse_info() currently throws a BUG() when a\n> directory entry exists exactly in the index. We need to consider that it\n> is possible to have a directory in a sparse index as long as that entry\n> is itself marked with the skip-worktree bit.\n>\n> The 'pos' variable is assigned a negative value if an exact match is not\n> found. Since a directory name can be an exact match, it is no longer an\n> error to have a nonnegative 'pos' value.\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  unpack-trees.c | 9 ++++++---\n>  1 file changed, 6 insertions(+), 3 deletions(-)\n>\n> diff --git a/unpack-trees.c b/unpack-trees.c\n> index 2da3e5ec77a1..e81d82d72d89 100644\n> --- a/unpack-trees.c\n> +++ b/unpack-trees.c\n> @@ -749,9 +749,12 @@ static int index_pos_by_traverse_info(struct name_entry *names,\n>  \tstrbuf_make_traverse_path(&name, info, names->path, names->pathlen);\n>  \tstrbuf_addch(&name, '/');\n>  \tpos = index_name_pos(o->src_index, name.buf, name.len);\n> -\tif (pos >= 0)\n> -\t\tBUG(\"This is a directory and should not exist in index\");\n> -\tpos = -pos - 1;\n> +\tif (pos >= 0) {\n> +\t\tif (!o->src_index->sparse_index ||\n> +\t\t    !(o->src_index->cache[pos]->ce_flags & CE_SKIP_WORKTREE))\n> +\t\t\tBUG(\"This is a directory and should not exist in index\");\n> +\t} else\n> +\t\tpos = -pos - 1;\n\nStyle nit: add {}'s to the \"else\" once the \"if\" gets one.\n\n>  \tif (pos >= o->src_index->cache_nr ||\n>  \t    !starts_with(o->src_index->cache[pos]->name, name.buf) ||\n>  \t    (pos > 0 && starts_with(o->src_index->cache[pos-1]->name, name.buf)))\n\n"},{"id":"419469","messageId":"87wnu5zyif.fsf@evledraar.gmail.com","threadId":"55195","inReplyTo":"e41b14e03ebb143c874cc276adcbc36059f0c64e.1615912983.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 11/20] sparse-index: convert from full to sparse","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-17T13:43:36Z","receivedAt":"2021-03-17T13:44:40Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Mar 16 2021, Derrick Stolee via GitGitGadget wrote:\n\n> diff --git a/cache-tree.c b/cache-tree.c\n> index 2fb483d3c083..5f07a39e501e 100644\n> --- a/cache-tree.c\n> +++ b/cache-tree.c\n> @@ -6,6 +6,7 @@\n>  #include \"object-store.h\"\n>  #include \"replace-object.h\"\n>  #include \"promisor-remote.h\"\n> +#include \"sparse-index.h\"\n>  \n>  #ifndef DEBUG_CACHE_TREE\n>  #define DEBUG_CACHE_TREE 0\n> @@ -442,6 +443,8 @@ int cache_tree_update(struct index_state *istate, int flags)\n>  \tif (i)\n>  \t\treturn i;\n>  \n> +\tensure_full_index(istate);\n> +\n>  \tif (!istate->cache_tree)\n>  \t\tistate->cache_tree = cache_tree();\n>  \n> diff --git a/cache.h b/cache.h\n> index 759ca92e2ecc..69a32146cd77 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -251,6 +251,8 @@ static inline unsigned int create_ce_mode(unsigned int mode)\n>  {\n>  \tif (S_ISLNK(mode))\n>  \t\treturn S_IFLNK;\n> +\tif (mode == S_IFDIR)\n> +\t\treturn S_IFDIR;\n\nDoes this actually need to be mode == S_IFDIR v.s. S_ISDIR(mode)? Those\naren't the same thing...\n\n>  \tif (S_ISDIR(mode) || S_ISGITLINK(mode))\n>  \t\treturn S_IFGITLINK;\n\n...and if it can be S_ISDIR(mode) then this becomes just\nS_ISGITLINK(mode), but losing the \"if\" there makes me suspect that some\ndir == submodule heuristic is being broken somewhere..\n\n"},{"id":"419494","messageId":"42e0b111-14d2-431c-00cc-d87784b70581@gmail.com","threadId":"55195","inReplyTo":"875z1p29wd.fsf@evledraar.gmail.com","subject":"Re: [PATCH v3 02/20] t/perf: add performance test for sparse operations","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-17T18:02:43Z","receivedAt":"2021-03-17T18:03:50Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/17/2021 9:21 AM, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Wed, Mar 17 2021, Derrick Stolee wrote:\n> \n>> On 3/17/2021 4:41 AM, Ævar Arnfjörð Bjarmason wrote:\n>>> But it seems odd to be doing this at all, the point of the perf\n>>> framework is that you can point it at any repo, and some repos you want\n>>> to test will have submodules.\n>>\n>> You're right that it should handle all repos. However, the point of\n>> the test is to have many copies of the repo, but most of them are\n>> excluded by sparse-directory entries. We don't collapse sparse-directory\n>> entries if there is a submodule inside, so the data shape is wrong after\n>> making all the copies.\n>>\n>> So, I disagree with your approach in your suggested diff, and instead\n>> offer this one. I've tested this with git.git and another local repo\n>> without submodules and checked that everything works as expected.\n> \n> What's got me confused here is that there's two uses for the perf\n> framework in this context.\n> \n> It's to use an empty/git.git as a test repo to demonstrate something,\n> but then also that you can run it in your arbitrary repo, and e.g. see\n> how much a given feature might benefit you.\n> \n> Hence suggesting that maybe test_perf_fresh_repois better here, because\n> by using test_perf_default_repo you're creating the expectation that you\n> can run the perf test, observe an %X difference, and that'll be\n> give-or-take what you'll get for that use case if you enable the feature.\n> \n> Except it won't because the repo has submodules, which we deleted for\n> the perf test...\n\nI'm also dramatically changing the repository shape to expose index\nreads and writes as a bottleneck. The benefit of using other repos\n(like git.git or optionally choosing the Linux kernel repo) is to\nchange how much of the time is spent crawling the populated set.\n\n>> diff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh\n>> index e527316e66d..5c0d78eeeea 100755\n>> --- a/t/perf/p2000-sparse-operations.sh\n>> +++ b/t/perf/p2000-sparse-operations.sh\n>> @@ -10,15 +10,17 @@ SPARSE_CONE=f2/f4/f1\n>>  \n>>  test_expect_success 'setup repo and indexes' '\n>>  \tgit reset --hard HEAD &&\n>> +\n>>  \t# Remove submodules from the example repo, because our\n>> -\t# duplication of the entire repo creates an unlikly data shape.\n>> -\tgit config --file .gitmodules --get-regexp \"submodule.*.path\" >modules &&\n>> -\tgit rm -f .gitmodules &&\n>> -\tfor module in $(awk \"{print \\$2}\" modules)\n>> -\tdo\n>> -\t\tgit rm $module || return 1\n>> -\tdone &&\n>> -\tgit commit -m \"remove submodules\" &&\n>> +\t# duplication of the entire repo creates an unlikely data shape.\n>> +\tif (git config --file .gitmodules --get-regexp \"submodule.*.path\" >modules)\n> \n> A subshell isn't needed here.\n> \n> FWIW the reason I got this out of ls-files is because you can have\n> submodules without .gitmodules entries, rare and broken, but seemed more\n> direct to grep the mode bits.\n\nI'd prefer to do something (textually) simpler, expecting the input\nrepos to have correct data.\n\n>> +\tthen\n>> +\t\tfor module in $(awk \"{print \\$2}\" modules)\n>> +\t\tdo\n>> +\t\t\tgit rm $module || return 1\n>> +\t\tdone &&\n> \n> Once we know we have submodules we can just do this without the loop.\n> \n>     git rm $(awk \"{print \\$2}\" modules)\n\nOk. That works for me.\n>>> Seems like something like the WIP patch at the end on top would be\n>>> better.\n>>>\n>>>> +\techo bogus >a &&\n>>>> +\tcp a b &&\n>>>> +\tgit add a b &&\n>>>> +\tgit commit -m \"level 0\" &&\n>>>> +\tBLOB=$(git rev-parse HEAD:a) &&\n>>>\n>>> Isn't the way we're getting this $BLOB equivalent to just 'echo bogus |\n>>> git hash-object --stdin -w' why commit it?\n>>\n>> We are committing it so we can add commits that deepen the copies,\n>> but within those copies we have these known file paths.\n>>\n>>> This whole thing makes me think you just wanted a test_perf_fresh_repo\n>>> all along, but I think this would be much more useful if you took the\n>>> default repo and multiplied the size in its tree by some multiple.\n>>>\n>>> E.g. take the files we have in git.git, write a copy at prefix-1/,\n>>> prefix-2/ etc.\n>>\n>> That is essentially what is happening here, but using multiple levels\n>> of directories. Using these multiple levels presents extra tree\n>> lookups and parsing in the event of expanding a sparse index to a\n>> full one.\n> \n> *nod*\n> \n> Anyway, this thread's a bit of a bikeshed on my part, I was just\n> wondering if & what part of the test relied on the existing repo if it\n> was mostly setting up its own test data.\n\nAgain, the benefit is to depend on the repo shape in some aspects,\nwhile exaggerating the data shape to make the non-populated set\nextremely large.\n\nThis presents different aspects that are worth examining, such as\ngit.git is much smaller than linux.git, and that is noticable with\nthese different performance numbers (taken at the end of this\nseries):\n\ngit.git\nTest                                            this tree      \n---------------------------------------------------------------\n2000.2: git status (full-index-v3)              0.39(0.35+0.08)\n2000.3: git status (full-index-v4)              0.39(0.34+0.09)\n2000.4: git status (sparse-index-v3)            2.46(2.33+0.16)\n2000.5: git status (sparse-index-v4)            2.42(2.31+0.15)\n2000.6: git add -A (full-index-v3)              1.35(0.98+0.20)\n2000.7: git add -A (full-index-v4)              1.25(0.96+0.18)\n2000.8: git add -A (sparse-index-v3)            2.39(2.26+0.17)\n2000.9: git add -A (sparse-index-v4)            2.35(2.29+0.11)\n2000.10: git add . (full-index-v3)              1.39(1.01+0.19)\n2000.11: git add . (full-index-v4)              1.31(1.00+0.19)\n2000.12: git add . (sparse-index-v3)            2.41(2.28+0.16)\n2000.13: git add . (sparse-index-v4)            2.45(2.32+0.16)\n2000.14: git commit -a -m A (full-index-v3)     1.44(1.08+0.21)\n2000.15: git commit -a -m A (full-index-v4)     1.31(1.04+0.19)\n2000.16: git commit -a -m A (sparse-index-v3)   2.44(2.35+0.16)\n2000.17: git commit -a -m A (sparse-index-v4)   2.44(2.36+0.16)\n\nlinux.git\nTest                                            this tree        \n-----------------------------------------------------------------\n2000.2: git status (full-index-v3)              7.14(6.06+1.79)  \n2000.3: git status (full-index-v4)              7.01(6.16+1.60)  \n2000.4: git status (sparse-index-v3)            58.50(56.86+2.34)\n2000.5: git status (sparse-index-v4)            57.52(55.80+2.45)\n2000.6: git add -A (full-index-v3)              25.52(18.70+3.18)\n2000.7: git add -A (full-index-v4)              22.26(17.52+2.72)\n2000.8: git add -A (sparse-index-v3)            56.65(55.00+2.35)\n2000.9: git add -A (sparse-index-v4)            56.56(54.98+2.29)\n2000.10: git add . (full-index-v3)              25.87(19.12+3.15)\n2000.11: git add . (full-index-v4)              22.56(17.85+2.71)\n2000.12: git add . (sparse-index-v3)            57.01(55.28+2.42)\n2000.13: git add . (sparse-index-v4)            56.84(55.38+2.19)\n2000.14: git commit -a -m A (full-index-v3)     26.83(20.69+3.24)\n2000.15: git commit -a -m A (full-index-v4)     24.04(19.86+2.65)\n2000.16: git commit -a -m A (sparse-index-v3)   60.23(58.99+2.44)\n2000.17: git commit -a -m A (sparse-index-v4)   60.52(59.09+2.74)\n\nThe intention is to make these numbers improve in the future\nso that the sparse-index is a better approach.\n\nThanks,\n-Stolee\n"},{"id":"419497","messageId":"CABPp-BFA+6--PkJfVfERF-ak9NGd6J0=Jhzro1nqqJ+X-LH3xg@mail.gmail.com","threadId":"55195","inReplyTo":"20210317132814.30175-3-avarab@gmail.com","subject":"Re: [RFC/PATCH 2/5] ls-files: make \"mode\" in show_ce() loop a variable","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-03-17T18:11:04Z","receivedAt":"2021-03-17T18:12:06Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Mar 17, 2021 at 6:28 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> In a subsequent commit I'll optionally change the mode in a new sparse\n> mode, let's do this first to make that change smaller.\n>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n>  builtin/ls-files.c | 10 +++++++++-\n>  1 file changed, 9 insertions(+), 1 deletion(-)\n>\n> diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n> index eb72d16493..4db75351f2 100644\n> --- a/builtin/ls-files.c\n> +++ b/builtin/ls-files.c\n> @@ -242,9 +242,17 @@ static void show_ce(struct repository *repo, struct dir_struct *dir,\n>                 if (!show_stage) {\n>                         fputs(tag, stdout);\n>                 } else {\n> +                       unsigned int mode = ce->ce_mode;\n> +                       if (show_sparse && S_ISSPARSEDIR(mode))\n> +                               /*\n> +                                * We could just do & 0177777 all the\n> +                                * time, just make it clear this is\n> +                                * for --stage-sparse.\n> +                                */\n> +                               mode &= 0177777;\n\nI could kind of see referencing the magic constant 0177777 in a test-*\nsource file, but it really needs an explanation when showing up in\nactual git source code.  At least reference something about how\ncache.h mentions these are the mode bits, or better yet #define this\nconstant somewhere in cache.h with an explanation.\n\nAlso, what is --stage-sparse?\n\n>                         printf(\"%s%06o %s %d\\t\",\n>                                tag,\n> -                              ce->ce_mode,\n> +                              mode,\n>                                find_unique_abbrev(&ce->oid, abbrev),\n>                                ce_stage(ce));\n>                 }\n> --\n> 2.31.0.260.g719c683c1d\n"},{"id":"419501","messageId":"CABPp-BEvKTw6tXmFe-BnOsA6g2vsk7vwE1mkpxXmkO__5tFchg@mail.gmail.com","threadId":"55195","inReplyTo":"20210317132814.30175-4-avarab@gmail.com","subject":"Re: [RFC/PATCH 3/5] ls-files: add and use a new --sparse option","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-03-17T18:19:21Z","receivedAt":"2021-03-17T18:20:18Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Mar 17, 2021 at 6:28 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n>  Documentation/git-ls-files.txt           |  4 ++\n>  builtin/ls-files.c                       | 10 ++++-\n>  t/t1091-sparse-checkout-builtin.sh       |  9 ++--\n>  t/t1092-sparse-checkout-compatibility.sh | 57 ++++++++++++++++--------\n>  4 files changed, 56 insertions(+), 24 deletions(-)\n>\n> diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt\n> index 6d11ab506b..1145e960a4 100644\n> --- a/Documentation/git-ls-files.txt\n> +++ b/Documentation/git-ls-files.txt\n> @@ -71,6 +71,10 @@ OPTIONS\n>  --unmerged::\n>         Show unmerged files in the output (forces --stage)\n>\n> +--sparse::\n> +       Show sparse directories in the output instead of expanding\n> +       them (forces --stage)\n> +\n>  -k::\n>  --killed::\n>         Show files on the filesystem that need to be removed due\n> diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n> index 4db75351f2..1ebbb63c10 100644\n> --- a/builtin/ls-files.c\n> +++ b/builtin/ls-files.c\n> @@ -26,6 +26,7 @@ static int show_deleted;\n>  static int show_cached;\n>  static int show_others;\n>  static int show_stage;\n> +static int show_sparse;\n>  static int show_unmerged;\n>  static int show_resolve_undo;\n>  static int show_modified;\n> @@ -639,6 +640,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n>                         DIR_SHOW_IGNORED),\n>                 OPT_BOOL('s', \"stage\", &show_stage,\n>                         N_(\"show staged contents' object name in the output\")),\n> +               OPT_BOOL(0, \"sparse\", &show_sparse,\n> +                       N_(\"show unexpanded sparse directories in the output\")),\n>                 OPT_BOOL('k', \"killed\", &show_killed,\n>                         N_(\"show files on the filesystem that need to be removed\")),\n>                 OPT_BIT(0, \"directory\", &dir.flags,\n> @@ -705,12 +708,17 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n>                 tag_skip_worktree = \"S \";\n>                 tag_resolve_undo = \"U \";\n>         }\n> +       if (show_sparse) {\n> +               prepare_repo_settings(the_repository);\n> +               the_repository->settings.command_requires_full_index = 0;\n> +       }\n>         if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)\n>                 require_work_tree = 1;\n> -       if (show_unmerged)\n> +       if (show_unmerged || show_sparse)\n>                 /*\n>                  * There's no point in showing unmerged unless\n>                  * you also show the stage information.\n> +                * The same goes for the --sparse option.\n\nYuck, haven't you just made --sparse an alias for --stage?  Why does\nit need an alias?\n\nWas the goal just to get a quick way to make the command run under\nrepo->settings.command_requires_full_index = 0 without auditing the\ncodepaths?  It seems to rely on them having been audited anyway, since\nit just falls back to the code used for --stage, so I don't see how it\nhelps.  It also suggests the command might do unexpected or weird\nthings if run without the --sparse option?  If people manually\nconfigure a sparse-checkout and cone mode AND a sparse-index (it's\nannoying how they have to specify all three instead of having to just\npass one flag somewhere), then now we also need to force them to\nremember to pass extra flags to random various commands for them to\noperate in a sane manner in their environment??\n\nI think this is a bad path to go down.\n\nHowever, if you want to write the necessary tests to make it so that\nls-files can operate with command_requires_full_index = 0, then I\nthink that's useful.  If you want to add a special flag so that folks\nin a sparse-checkout-with-cone-mode-with-sparse-index setup want to\noperate densely (i.e. to show what files would be in the index if it\nwere fully populated), then I think that's useful.  But having\nsparse-yes-with-cone-yes-very-sparse folks need to specify an extra\nflag to commands to get sparse behavior just seems wrong to me.\n\n>                  */\n>                 show_stage = 1;\n>         if (show_tag || show_stage)\n> diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh\n> index ff1ad570a2..c823df423c 100755\n> --- a/t/t1091-sparse-checkout-builtin.sh\n> +++ b/t/t1091-sparse-checkout-builtin.sh\n> @@ -208,12 +208,13 @@ test_expect_success 'sparse-checkout disable' '\n>  test_expect_success 'sparse-index enabled and disabled' '\n>         git -C repo sparse-checkout init --cone --sparse-index &&\n>         test_cmp_config -C repo true extensions.sparseIndex &&\n> -       test-tool -C repo read-cache --table >cache &&\n> -       grep \" tree \" cache &&\n> +       git -C repo ls-files --sparse >cache &&\n> +       grep \"^040000 \" cache >lines &&\n> +       test_line_count = 3 lines &&\n>\n>         git -C repo sparse-checkout disable &&\n> -       test-tool -C repo read-cache --table >cache &&\n> -       ! grep \" tree \" cache &&\n> +       git -C repo ls-files --sparse >cache &&\n> +       ! grep \"^040000 \" cache &&\n>         git -C repo config --list >config &&\n>         ! grep extensions.sparseindex config\n>  '\n> diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\n> index d97bf9b645..48d3920490 100755\n> --- a/t/t1092-sparse-checkout-compatibility.sh\n> +++ b/t/t1092-sparse-checkout-compatibility.sh\n> @@ -136,48 +136,67 @@ test_sparse_match () {\n>         test_cmp sparse-checkout-err sparse-index-err\n>  }\n>\n> +test_index_entry_like () {\n> +       dir=$1\n> +       shift\n> +       fmt=$1\n> +       shift\n> +       rev=$1\n> +       shift\n> +       entry=$1\n> +       shift\n> +       file=$1\n> +       shift\n> +       hash=$(git -C \"$dir\" rev-parse \"$rev\") &&\n> +       printf \"$fmt\\n\" \"$hash\" \"$entry\" >expected &&\n> +       if grep \"$entry\" \"$file\" >line\n> +       then\n> +               test_cmp expected line\n> +       else\n> +               cat cache &&\n> +               false\n> +       fi\n> +}\n> +\n>  test_expect_success 'sparse-index contents' '\n>         init_repos &&\n>\n> -       test-tool -C sparse-index read-cache --table >cache &&\n> +       git -C sparse-index ls-files --sparse >cache &&\n>         for dir in folder1 folder2 x\n>         do\n> -               TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n> -               grep \"040000 tree $TREE $dir/\" cache \\\n> -                       || return 1\n> +               test_index_entry_like sparse-index \"040000 %s 0\\t%s\" \"HEAD:$dir\" \"$dir/\" cache || return 1\n>         done &&\n>\n>         git -C sparse-index sparse-checkout set folder1 &&\n>\n> -       test-tool -C sparse-index read-cache --table >cache &&\n> +       git -C sparse-index ls-files --sparse >cache &&\n>         for dir in deep folder2 x\n>         do\n> -               TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n> -               grep \"040000 tree $TREE $dir/\" cache \\\n> -                       || return 1\n> +               test_index_entry_like sparse-index \"040000 %s 0\\t%s\" \"HEAD:$dir\" \"$dir/\" cache || return 1\n>         done &&\n>\n>         git -C sparse-index sparse-checkout set deep/deeper1 &&\n>\n> -       test-tool -C sparse-index read-cache --table >cache &&\n> +       git -C sparse-index ls-files --sparse >cache &&\n>         for dir in deep/deeper2 folder1 folder2 x\n>         do\n> -               TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n> -               grep \"040000 tree $TREE $dir/\" cache \\\n> -                       || return 1\n> +               test_index_entry_like sparse-index \"040000 %s 0\\t%s\" \"HEAD:$dir\" \"$dir/\" cache || return 1\n>         done &&\n>\n> +       grep 040000 cache >lines &&\n> +       test_line_count = 4 lines &&\n> +\n>         # Disabling the sparse-index removes tree entries with full ones\n>         git -C sparse-index sparse-checkout init --no-sparse-index &&\n>\n> -       test-tool -C sparse-index read-cache --table >cache &&\n> -       ! grep \"040000 tree\" cache &&\n> -       test_sparse_match test-tool read-cache --table\n> +       git -C sparse-index ls-files --sparse >cache &&\n> +       ! grep \"^040000 \" cache >lines &&\n> +       test_sparse_match git ls-tree -r HEAD\n>  '\n>\n>  test_expect_success 'expanded in-memory index matches full index' '\n>         init_repos &&\n> -       test_sparse_match test-tool read-cache --expand --table\n> +       test_sparse_match git ls-tree -r HEAD\n>  '\n>\n>  test_expect_success 'status with options' '\n> @@ -394,9 +413,9 @@ test_expect_success 'submodule handling' '\n>         test_all_match git commit -m \"add submodule\" &&\n>\n>         # having a submodule prevents \"modules\" from collapse\n> -       test-tool -C sparse-index read-cache --table >cache &&\n> -       grep \"100644 blob .*    modules/a\" cache &&\n> -       grep \"160000 commit $(git -C initial-repo rev-parse HEAD)       modules/sub\" cache\n> +       git -C sparse-index ls-files --sparse >cache &&\n> +       test_index_entry_like sparse-index \"100644 %s 0\\t%s\" \"HEAD:modules/a\" \"modules/a\" cache &&\n> +       test_index_entry_like sparse-index \"160000 %s 0\\t%s\" \"HEAD:modules/sub\" \"modules/sub\" cache\n>  '\n>\n>  test_expect_success 'sparse-index is expanded and converted back' '\n> --\n> 2.31.0.260.g719c683c1d\n\nI do like the tests and your idea that we can use ls-files to list\nwhatever entries are in the index, I just think the tests should use\n--stage to do that.\n"},{"id":"419523","messageId":"87lfalzldu.fsf@evledraar.gmail.com","threadId":"55195","inReplyTo":"CABPp-BEvKTw6tXmFe-BnOsA6g2vsk7vwE1mkpxXmkO__5tFchg@mail.gmail.com","subject":"Re: [RFC/PATCH 3/5] ls-files: add and use a new --sparse option","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-17T18:27:09Z","receivedAt":"2021-03-17T18:27:52Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Mar 17 2021, Elijah Newren wrote:\n\n> On Wed, Mar 17, 2021 at 6:28 AM Ævar Arnfjörð Bjarmason\n> <avarab@gmail.com> wrote:\n>>\n>> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n>> ---\n>>  Documentation/git-ls-files.txt           |  4 ++\n>>  builtin/ls-files.c                       | 10 ++++-\n>>  t/t1091-sparse-checkout-builtin.sh       |  9 ++--\n>>  t/t1092-sparse-checkout-compatibility.sh | 57 ++++++++++++++++--------\n>>  4 files changed, 56 insertions(+), 24 deletions(-)\n>>\n>> diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt\n>> index 6d11ab506b..1145e960a4 100644\n>> --- a/Documentation/git-ls-files.txt\n>> +++ b/Documentation/git-ls-files.txt\n>> @@ -71,6 +71,10 @@ OPTIONS\n>>  --unmerged::\n>>         Show unmerged files in the output (forces --stage)\n>>\n>> +--sparse::\n>> +       Show sparse directories in the output instead of expanding\n>> +       them (forces --stage)\n>> +\n>>  -k::\n>>  --killed::\n>>         Show files on the filesystem that need to be removed due\n>> diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n>> index 4db75351f2..1ebbb63c10 100644\n>> --- a/builtin/ls-files.c\n>> +++ b/builtin/ls-files.c\n>> @@ -26,6 +26,7 @@ static int show_deleted;\n>>  static int show_cached;\n>>  static int show_others;\n>>  static int show_stage;\n>> +static int show_sparse;\n>>  static int show_unmerged;\n>>  static int show_resolve_undo;\n>>  static int show_modified;\n>> @@ -639,6 +640,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n>>                         DIR_SHOW_IGNORED),\n>>                 OPT_BOOL('s', \"stage\", &show_stage,\n>>                         N_(\"show staged contents' object name in the output\")),\n>> +               OPT_BOOL(0, \"sparse\", &show_sparse,\n>> +                       N_(\"show unexpanded sparse directories in the output\")),\n>>                 OPT_BOOL('k', \"killed\", &show_killed,\n>>                         N_(\"show files on the filesystem that need to be removed\")),\n>>                 OPT_BIT(0, \"directory\", &dir.flags,\n>> @@ -705,12 +708,17 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n>>                 tag_skip_worktree = \"S \";\n>>                 tag_resolve_undo = \"U \";\n>>         }\n>> +       if (show_sparse) {\n>> +               prepare_repo_settings(the_repository);\n>> +               the_repository->settings.command_requires_full_index = 0;\n>> +       }\n>>         if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)\n>>                 require_work_tree = 1;\n>> -       if (show_unmerged)\n>> +       if (show_unmerged || show_sparse)\n>>                 /*\n>>                  * There's no point in showing unmerged unless\n>>                  * you also show the stage information.\n>> +                * The same goes for the --sparse option.\n>\n> Yuck, haven't you just made --sparse an alias for --stage?  Why does\n> it need an alias?\n\nIt doesn't, but --unmerged, the one other option which purely modifies\n--stage output implies --stage.\n\nSo it's in line with existing UI convention in the command, it's\nprobably better to keep following that than have new options behave\ndifferently.\n\nBut yeah, we could spell out --stage --sparse in the tests.\n\n> Was the goal just to get a quick way to make the command run under\n> repo->settings.command_requires_full_index = 0 without auditing the\n> codepaths?  It seems to rely on them having been audited anyway, since\n> it just falls back to the code used for --stage, so I don't see how it\n> helps.  It also suggests the command might do unexpected or weird\n> things if run without the --sparse option?  If people manually\n> configure a sparse-checkout and cone mode AND a sparse-index (it's\n> annoying how they have to specify all three instead of having to just\n> pass one flag somewhere), then now we also need to force them to\n> remember to pass extra flags to random various commands for them to\n> operate in a sane manner in their environment??\n>\n> I think this is a bad path to go down.\n\nThose are probably good points, I don't have enough overview of the\nwhole sparse thing yet to say.\n\nI just thought it didn't make sense to have a series changing the nature\nof the index without corresponding tooling changes to interrogate the\nstate of the index.\n\n> However, if you want to write the necessary tests to make it so that\n> ls-files can operate with command_requires_full_index = 0, then I\n> think that's useful.  If you want to add a special flag so that folks\n> in a sparse-checkout-with-cone-mode-with-sparse-index setup want to\n> operate densely (i.e. to show what files would be in the index if it\n> were fully populated), then I think that's useful.  But having\n> sparse-yes-with-cone-yes-very-sparse folks need to specify an extra\n> flag to commands to get sparse behavior just seems wrong to me.\n\nMaybe, but what else do you suggest for getting this information out of\nthe index?\n\n>>                  */\n>>                 show_stage = 1;\n>>         if (show_tag || show_stage)\n>> diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh\n>> index ff1ad570a2..c823df423c 100755\n>> --- a/t/t1091-sparse-checkout-builtin.sh\n>> +++ b/t/t1091-sparse-checkout-builtin.sh\n>> @@ -208,12 +208,13 @@ test_expect_success 'sparse-checkout disable' '\n>>  test_expect_success 'sparse-index enabled and disabled' '\n>>         git -C repo sparse-checkout init --cone --sparse-index &&\n>>         test_cmp_config -C repo true extensions.sparseIndex &&\n>> -       test-tool -C repo read-cache --table >cache &&\n>> -       grep \" tree \" cache &&\n>> +       git -C repo ls-files --sparse >cache &&\n>> +       grep \"^040000 \" cache >lines &&\n>> +       test_line_count = 3 lines &&\n>>\n>>         git -C repo sparse-checkout disable &&\n>> -       test-tool -C repo read-cache --table >cache &&\n>> -       ! grep \" tree \" cache &&\n>> +       git -C repo ls-files --sparse >cache &&\n>> +       ! grep \"^040000 \" cache &&\n>>         git -C repo config --list >config &&\n>>         ! grep extensions.sparseindex config\n>>  '\n>> diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\n>> index d97bf9b645..48d3920490 100755\n>> --- a/t/t1092-sparse-checkout-compatibility.sh\n>> +++ b/t/t1092-sparse-checkout-compatibility.sh\n>> @@ -136,48 +136,67 @@ test_sparse_match () {\n>>         test_cmp sparse-checkout-err sparse-index-err\n>>  }\n>>\n>> +test_index_entry_like () {\n>> +       dir=$1\n>> +       shift\n>> +       fmt=$1\n>> +       shift\n>> +       rev=$1\n>> +       shift\n>> +       entry=$1\n>> +       shift\n>> +       file=$1\n>> +       shift\n>> +       hash=$(git -C \"$dir\" rev-parse \"$rev\") &&\n>> +       printf \"$fmt\\n\" \"$hash\" \"$entry\" >expected &&\n>> +       if grep \"$entry\" \"$file\" >line\n>> +       then\n>> +               test_cmp expected line\n>> +       else\n>> +               cat cache &&\n>> +               false\n>> +       fi\n>> +}\n>> +\n>>  test_expect_success 'sparse-index contents' '\n>>         init_repos &&\n>>\n>> -       test-tool -C sparse-index read-cache --table >cache &&\n>> +       git -C sparse-index ls-files --sparse >cache &&\n>>         for dir in folder1 folder2 x\n>>         do\n>> -               TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n>> -               grep \"040000 tree $TREE $dir/\" cache \\\n>> -                       || return 1\n>> +               test_index_entry_like sparse-index \"040000 %s 0\\t%s\" \"HEAD:$dir\" \"$dir/\" cache || return 1\n>>         done &&\n>>\n>>         git -C sparse-index sparse-checkout set folder1 &&\n>>\n>> -       test-tool -C sparse-index read-cache --table >cache &&\n>> +       git -C sparse-index ls-files --sparse >cache &&\n>>         for dir in deep folder2 x\n>>         do\n>> -               TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n>> -               grep \"040000 tree $TREE $dir/\" cache \\\n>> -                       || return 1\n>> +               test_index_entry_like sparse-index \"040000 %s 0\\t%s\" \"HEAD:$dir\" \"$dir/\" cache || return 1\n>>         done &&\n>>\n>>         git -C sparse-index sparse-checkout set deep/deeper1 &&\n>>\n>> -       test-tool -C sparse-index read-cache --table >cache &&\n>> +       git -C sparse-index ls-files --sparse >cache &&\n>>         for dir in deep/deeper2 folder1 folder2 x\n>>         do\n>> -               TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n>> -               grep \"040000 tree $TREE $dir/\" cache \\\n>> -                       || return 1\n>> +               test_index_entry_like sparse-index \"040000 %s 0\\t%s\" \"HEAD:$dir\" \"$dir/\" cache || return 1\n>>         done &&\n>>\n>> +       grep 040000 cache >lines &&\n>> +       test_line_count = 4 lines &&\n>> +\n>>         # Disabling the sparse-index removes tree entries with full ones\n>>         git -C sparse-index sparse-checkout init --no-sparse-index &&\n>>\n>> -       test-tool -C sparse-index read-cache --table >cache &&\n>> -       ! grep \"040000 tree\" cache &&\n>> -       test_sparse_match test-tool read-cache --table\n>> +       git -C sparse-index ls-files --sparse >cache &&\n>> +       ! grep \"^040000 \" cache >lines &&\n>> +       test_sparse_match git ls-tree -r HEAD\n>>  '\n>>\n>>  test_expect_success 'expanded in-memory index matches full index' '\n>>         init_repos &&\n>> -       test_sparse_match test-tool read-cache --expand --table\n>> +       test_sparse_match git ls-tree -r HEAD\n>>  '\n>>\n>>  test_expect_success 'status with options' '\n>> @@ -394,9 +413,9 @@ test_expect_success 'submodule handling' '\n>>         test_all_match git commit -m \"add submodule\" &&\n>>\n>>         # having a submodule prevents \"modules\" from collapse\n>> -       test-tool -C sparse-index read-cache --table >cache &&\n>> -       grep \"100644 blob .*    modules/a\" cache &&\n>> -       grep \"160000 commit $(git -C initial-repo rev-parse HEAD)       modules/sub\" cache\n>> +       git -C sparse-index ls-files --sparse >cache &&\n>> +       test_index_entry_like sparse-index \"100644 %s 0\\t%s\" \"HEAD:modules/a\" \"modules/a\" cache &&\n>> +       test_index_entry_like sparse-index \"160000 %s 0\\t%s\" \"HEAD:modules/sub\" \"modules/sub\" cache\n>>  '\n>>\n>>  test_expect_success 'sparse-index is expanded and converted back' '\n>> --\n>> 2.31.0.260.g719c683c1d\n>\n> I do like the tests and your idea that we can use ls-files to list\n> whatever entries are in the index, I just think the tests should use\n> --stage to do that.\n\n"},{"id":"419524","messageId":"CABPp-BGS7BxbxiPvk7zoO1XdbZSkpyrfYLuyiNnR+d6orPn+rw@mail.gmail.com","threadId":"55195","inReplyTo":"20210317132814.30175-1-avarab@gmail.com","subject":"Re: [RFC/PATCH 0/5] Re: [PATCH v3 07/20] test-read-cache: print cache entries with --table","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-03-17T18:28:38Z","receivedAt":"2021-03-17T18:29:30Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Mar 17, 2021 at 6:28 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> > From: Derrick Stolee <dstolee@microsoft.com>\n> >\n> > This table is helpful for discovering data in the index to ensure it is\n> > being written correctly, especially as we build and test the\n> > sparse-index. This table includes an output format similar to 'git\n> > ls-tree', but should not be compared to that directly. The biggest\n> > reasons are that 'git ls-tree' includes a tree entry for every\n> > subdirectory, even those that would not appear as a sparse directory in\n> > a sparse-index. Further, 'git ls-tree' does not use a trailing directory\n> > separator for its tree rows.\n> >\n> > This does not print the stat() information for the blobs. That could be\n> > added in a future change with another option. The tests that are added\n> > in the next few changes care only about the object types and IDs.\n> >\n> > To make the option parsing slightly more robust, wrap the string\n> > comparisons in a loop adapted from test-dir-iterator.c.\n> >\n> > Care must be taken with the final check for the 'cnt' variable. We\n> > continue the expectation that the numerical value is the final argument.\n> >\n> > Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> > ---\n> >  t/helper/test-read-cache.c | 55 +++++++++++++++++++++++++++++++-------\n> >  1 file changed, 45 insertions(+), 10 deletions(-)\n> >\n> > diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c\n> > index 244977a29bdf..6cfd8f2de71c 100644\n> > --- a/t/helper/test-read-cache.c\n> > +++ b/t/helper/test-read-cache.c\n> > @@ -1,36 +1,71 @@\n> >  #include \"test-tool.h\"\n> >  #include \"cache.h\"\n> >  #include \"config.h\"\n> > +#include \"blob.h\"\n> > +#include \"commit.h\"\n> > +#include \"tree.h\"\n> > +\n> > +static void print_cache_entry(struct cache_entry *ce)\n> > +{\n> > +     const char *type;\n> > +     printf(\"%06o \", ce->ce_mode & 0177777);\n> > +\n> > +     if (S_ISSPARSEDIR(ce->ce_mode))\n> > +             type = tree_type;\n> > +     else if (S_ISGITLINK(ce->ce_mode))\n> > +             type = commit_type;\n> > +     else\n> > +             type = blob_type;\n> > +\n> > +     printf(\"%s %s\\t%s\\n\",\n> > +            type,\n> > +            oid_to_hex(&ce->oid),\n> > +            ce->name);\n> > +}\n> > +\n>\n> So we have a test tool that's mostly ls-files but mocks the output\n> ls-tree would emit, won't these tests eventually care about what stage\n> things are in?\n>\n> What follows is an RFC series on top that's the result of me wondering\n> why if we're adding new index constructs we aren't updating our\n> plumbing to emit that data, can we just add this to ls-files and drop\n> this test helper?\n>\n> Turns out: Yes we can.\n\nI like the idea of having ls-files be usable to show the entries that\nare in the index; that seems great to me.  I very much dislike the\n--sparse flag to ls-files, as noted on that commit.\n\nAlso, as a minor point, the first two patches seemed a bit confusing\nto me.  The first commit said that it was there solely to make \"the\nnext commit\" easier, and the second was worded as just making the next\npatch easier, which made me wonder if the wording in the first commit\nmessage was referring to 3/5 when it said \"the next commit\".  Both of\nthe first two commits were so tiny that if they are both prep for 3/5,\nmaybe it makes sense to combine them (together or both to 3/5)?  If\nnot, maybe the commit messages could be cleaned up or clarified a bit?\n\n> Ævar Arnfjörð Bjarmason (5):\n>   ls-files: defer read_index() after parse_options() etc.\n>   ls-files: make \"mode\" in show_ce() loop a variable\n>   ls-files: add and use a new --sparse option\n>   test-tool read-cache: --table is redundant to ls-files\n>   test-tool: split up test-tool read-cache\n>\n>  Documentation/git-ls-files.txt           |  4 ++\n>  Makefile                                 |  3 +-\n>  builtin/ls-files.c                       | 29 +++++++--\n>  t/helper/test-read-cache-again.c         | 31 +++++++++\n>  t/helper/test-read-cache-perf.c          | 21 ++++++\n>  t/helper/test-read-cache.c               | 82 ------------------------\n>  t/helper/test-tool.c                     |  3 +-\n>  t/helper/test-tool.h                     |  3 +-\n>  t/perf/p0002-read-cache.sh               |  2 +-\n>  t/t1091-sparse-checkout-builtin.sh       |  9 +--\n>  t/t1092-sparse-checkout-compatibility.sh | 57 ++++++++++------\n>  t/t7519-status-fsmonitor.sh              |  2 +-\n>  12 files changed, 131 insertions(+), 115 deletions(-)\n>  create mode 100644 t/helper/test-read-cache-again.c\n>  create mode 100644 t/helper/test-read-cache-perf.c\n>  delete mode 100644 t/helper/test-read-cache.c\n>\n> --\n> 2.31.0.260.g719c683c1d\n"},{"id":"419529","messageId":"CABPp-BGeQmkwX5_W2jXBnfYWd21B7VRNOwXvo1L=7U7j718yyg@mail.gmail.com","threadId":"55195","inReplyTo":"87lfalzldu.fsf@evledraar.gmail.com","subject":"Re: [RFC/PATCH 3/5] ls-files: add and use a new --sparse option","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-03-17T18:44:51Z","receivedAt":"2021-03-17T18:46:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Mar 17, 2021 at 11:27 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> On Wed, Mar 17 2021, Elijah Newren wrote:\n>\n> > On Wed, Mar 17, 2021 at 6:28 AM Ævar Arnfjörð Bjarmason\n> > <avarab@gmail.com> wrote:\n> >>\n> >> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> >> ---\n> >>  Documentation/git-ls-files.txt           |  4 ++\n> >>  builtin/ls-files.c                       | 10 ++++-\n> >>  t/t1091-sparse-checkout-builtin.sh       |  9 ++--\n> >>  t/t1092-sparse-checkout-compatibility.sh | 57 ++++++++++++++++--------\n> >>  4 files changed, 56 insertions(+), 24 deletions(-)\n> >>\n> >> diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt\n> >> index 6d11ab506b..1145e960a4 100644\n> >> --- a/Documentation/git-ls-files.txt\n> >> +++ b/Documentation/git-ls-files.txt\n> >> @@ -71,6 +71,10 @@ OPTIONS\n> >>  --unmerged::\n> >>         Show unmerged files in the output (forces --stage)\n> >>\n> >> +--sparse::\n> >> +       Show sparse directories in the output instead of expanding\n> >> +       them (forces --stage)\n> >> +\n> >>  -k::\n> >>  --killed::\n> >>         Show files on the filesystem that need to be removed due\n> >> diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n> >> index 4db75351f2..1ebbb63c10 100644\n> >> --- a/builtin/ls-files.c\n> >> +++ b/builtin/ls-files.c\n> >> @@ -26,6 +26,7 @@ static int show_deleted;\n> >>  static int show_cached;\n> >>  static int show_others;\n> >>  static int show_stage;\n> >> +static int show_sparse;\n> >>  static int show_unmerged;\n> >>  static int show_resolve_undo;\n> >>  static int show_modified;\n> >> @@ -639,6 +640,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n> >>                         DIR_SHOW_IGNORED),\n> >>                 OPT_BOOL('s', \"stage\", &show_stage,\n> >>                         N_(\"show staged contents' object name in the output\")),\n> >> +               OPT_BOOL(0, \"sparse\", &show_sparse,\n> >> +                       N_(\"show unexpanded sparse directories in the output\")),\n> >>                 OPT_BOOL('k', \"killed\", &show_killed,\n> >>                         N_(\"show files on the filesystem that need to be removed\")),\n> >>                 OPT_BIT(0, \"directory\", &dir.flags,\n> >> @@ -705,12 +708,17 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n> >>                 tag_skip_worktree = \"S \";\n> >>                 tag_resolve_undo = \"U \";\n> >>         }\n> >> +       if (show_sparse) {\n> >> +               prepare_repo_settings(the_repository);\n> >> +               the_repository->settings.command_requires_full_index = 0;\n> >> +       }\n> >>         if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)\n> >>                 require_work_tree = 1;\n> >> -       if (show_unmerged)\n> >> +       if (show_unmerged || show_sparse)\n> >>                 /*\n> >>                  * There's no point in showing unmerged unless\n> >>                  * you also show the stage information.\n> >> +                * The same goes for the --sparse option.\n> >\n> > Yuck, haven't you just made --sparse an alias for --stage?  Why does\n> > it need an alias?\n>\n> It doesn't, but --unmerged, the one other option which purely modifies\n> --stage output implies --stage.\n\n--unmerged modifies --stage output.  --sparse won't.  (Maybe it does\n_now_ because the command doesn't yet support sparse-indexes, but\nthat's a temporary artifact.  Long term, there should be no difference\nin the output.)\n\n> So it's in line with existing UI convention in the command, it's\n> probably better to keep following that than have new options behave\n> differently.\n>\n> But yeah, we could spell out --stage --sparse in the tests.\n\nThere should not be a --sparse option.  The index is _already_ sparse\nand users had to take multiple steps to make it so; users shouldn't\nhave to repeat themselves with each and every command they ever type\nwhen they've created a sparse index that they want sparse behavior.\n\nYou should just spell it \"--stage\".\n\n> > Was the goal just to get a quick way to make the command run under\n> > repo->settings.command_requires_full_index = 0 without auditing the\n> > codepaths?  It seems to rely on them having been audited anyway, since\n> > it just falls back to the code used for --stage, so I don't see how it\n> > helps.  It also suggests the command might do unexpected or weird\n> > things if run without the --sparse option?  If people manually\n> > configure a sparse-checkout and cone mode AND a sparse-index (it's\n> > annoying how they have to specify all three instead of having to just\n> > pass one flag somewhere), then now we also need to force them to\n> > remember to pass extra flags to random various commands for them to\n> > operate in a sane manner in their environment??\n> >\n> > I think this is a bad path to go down.\n>\n> Those are probably good points, I don't have enough overview of the\n> whole sparse thing yet to say.\n>\n> I just thought it didn't make sense to have a series changing the nature\n> of the index without corresponding tooling changes to interrogate the\n> state of the index.\n\nThat makes sense to me; I agree with you on that point.\n\n> > However, if you want to write the necessary tests to make it so that\n> > ls-files can operate with command_requires_full_index = 0, then I\n> > think that's useful.  If you want to add a special flag so that folks\n> > in a sparse-checkout-with-cone-mode-with-sparse-index setup want to\n> > operate densely (i.e. to show what files would be in the index if it\n> > were fully populated), then I think that's useful.  But having\n> > sparse-yes-with-cone-yes-very-sparse folks need to specify an extra\n> > flag to commands to get sparse behavior just seems wrong to me.\n>\n> Maybe, but what else do you suggest for getting this information out of\n> the index?\n\nUse git ls-files without new options...as I stated here:\n\n...\n> > I do like the tests and your idea that we can use ls-files to list\n> > whatever entries are in the index, I just think the tests should use\n> > --stage to do that.\n\nIn other words, I think making \"git ls-files\" the first, or at least\none of the first, commands to be modified to behave properly in a\nsparse-index world is what you should be aiming for, not some\nnew-option-shortcut that'll make no sense long term and persist\nindefinitely.\n\nList the entries in the index: `git ls-files`\nList the entries in the index with their hash, mode, and stage: `git\nls-files --stage`\n\nList all the entries that would be in the index if it weren't sparse:\n`git ls-files --$SOME_NEW_OPTION_NAME`\n\nYou don't need to implement the --$SOME_NEW_OPTION_NAME yet, of\ncourse.  We can just note that it's the plan to add it later.\n"},{"id":"419542","messageId":"d93300ad-9a3b-6de4-c497-acd56eff30dc@gmail.com","threadId":"55195","inReplyTo":"CABPp-BGS7BxbxiPvk7zoO1XdbZSkpyrfYLuyiNnR+d6orPn+rw@mail.gmail.com","subject":"Re: [RFC/PATCH 0/5] Re: [PATCH v3 07/20] test-read-cache: print cache entries with --table","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-17T19:46:39Z","receivedAt":"2021-03-17T19:47:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/17/2021 2:28 PM, Elijah Newren wrote:\n> On Wed, Mar 17, 2021 at 6:28 AM Ævar Arnfjörð Bjarmason\n> <avarab@gmail.com> wrote:\n>>\n>>> From: Derrick Stolee <dstolee@microsoft.com>\n\n>>\n>> So we have a test tool that's mostly ls-files but mocks the output\n>> ls-tree would emit, won't these tests eventually care about what stage\n>> things are in?\n>>\n>> What follows is an RFC series on top that's the result of me wondering\n>> why if we're adding new index constructs we aren't updating our\n>> plumbing to emit that data, can we just add this to ls-files and drop\n>> this test helper?\n>>\n>> Turns out: Yes we can.\n> \n> I like the idea of having ls-files be usable to show the entries that\n> are in the index; that seems great to me.  I very much dislike the\n> --sparse flag to ls-files, as noted on that commit.\n\nI don't like this idea. I don't think exposing internal structures\nlike this is something we want to do so quickly. Further, I intend\nto use this test tool in the future to _also_ show the stored stat()\ndata, which would be inappropriate here in ls-files.\n\nI would prefer to continue using the test helper here and leave\nfunctional changes to ls-files be considered independently.\n\nThanks,\n-Stolee\n"},{"id":"419543","messageId":"1de9bc8a-b0fd-596a-3ae2-2e055b37534b@gmail.com","threadId":"55195","inReplyTo":"87wnu5zyif.fsf@evledraar.gmail.com","subject":"Re: [PATCH v3 11/20] sparse-index: convert from full to sparse","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-17T19:55:17Z","receivedAt":"2021-03-17T19:56:21Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/17/2021 9:43 AM, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Tue, Mar 16 2021, Derrick Stolee via GitGitGadget wrote:\n>> @@ -251,6 +251,8 @@ static inline unsigned int create_ce_mode(unsigned int mode)\n>>  {\n>>  \tif (S_ISLNK(mode))\n>>  \t\treturn S_IFLNK;\n>> +\tif (mode == S_IFDIR)\n>> +\t\treturn S_IFDIR;\n> \n> Does this actually need to be mode == S_IFDIR v.s. S_ISDIR(mode)? Those\n> aren't the same thing...\n> \n>>  \tif (S_ISDIR(mode) || S_ISGITLINK(mode))\n>>  \t\treturn S_IFGITLINK;\n> \n> ...and if it can be S_ISDIR(mode) then this becomes just\n> S_ISGITLINK(mode), but losing the \"if\" there makes me suspect that some\n> dir == submodule heuristic is being broken somewhere..\n \nI have a vague recollection that I did that at one point, and\nit didn't work. However, using the simpler\n\n\tif (S_ISDIR(mode))\n\t\treturn S_IFDIR;\n\tif (S_ISGITLINK(mode))\n\t\treturn S_IFGITLINK;\n\npasses all of my tests.\n\nLooking at the history of create_ce_mode(), this \"||\"\ncondition was created in this commit:\n\ncommit 9eec4795d44439cd170fb52c73827c728252648d\nAuthor: Linus Torvalds <torvalds@linux-foundation.org>\nDate:   Mon Apr 9 21:14:58 2007 -0700\n\n    Add \"S_IFDIRLNK\" file mode infrastructure for git links\n    \n    This just adds the basic helper functions to recognize and work with git\n    tree entries that are links to other git repositories (\"subprojects\").\n    They still aren't actually connected up to any of the code-paths, but\n    now all the infrastructure is in place.\n    \n    The next commit will start actually adding actual subproject support.\n    \n    Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>\n    Signed-off-by: Junio C Hamano <junkio@cox.net>\n\nThere isn't any justification of why S_ISDIR() is there. Perhaps\nit was defensive programming? If that is the case, then this simpler\nlogic will work.\n\nThanks,\n-Stolee\n"},{"id":"419548","messageId":"CABPp-BHh_QgF8xej8=xUJh+mYfKTB_d-7Su+xwcfRyaHEPz1mA@mail.gmail.com","threadId":"55195","inReplyTo":"d93300ad-9a3b-6de4-c497-acd56eff30dc@gmail.com","subject":"Re: [RFC/PATCH 0/5] Re: [PATCH v3 07/20] test-read-cache: print cache entries with --table","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-03-17T20:26:29Z","receivedAt":"2021-03-17T20:27:29Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Mar 17, 2021 at 12:46 PM Derrick Stolee <stolee@gmail.com> wrote:\n>\n> On 3/17/2021 2:28 PM, Elijah Newren wrote:\n> > On Wed, Mar 17, 2021 at 6:28 AM Ævar Arnfjörð Bjarmason\n> > <avarab@gmail.com> wrote:\n> >>\n> >>> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> >>\n> >> So we have a test tool that's mostly ls-files but mocks the output\n> >> ls-tree would emit, won't these tests eventually care about what stage\n> >> things are in?\n> >>\n> >> What follows is an RFC series on top that's the result of me wondering\n> >> why if we're adding new index constructs we aren't updating our\n> >> plumbing to emit that data, can we just add this to ls-files and drop\n> >> this test helper?\n> >>\n> >> Turns out: Yes we can.\n> >\n> > I like the idea of having ls-files be usable to show the entries that\n> > are in the index; that seems great to me.  I very much dislike the\n> > --sparse flag to ls-files, as noted on that commit.\n>\n> I don't like this idea. I don't think exposing internal structures\n> like this is something we want to do so quickly.\n\nNot sure I follow; ls-files was already about exposing three bits of\ninternal structures for index entries: mode, hash, and stage number.\nThese are quantities that are well-defined for sparse directories too.\nIt would not be exposing any new or different internal structures, nor\nchanging the output format.  (Ævar changed the tests to not look for\n\"tree\" but to look for the \"040000\" mode number.)\n\n>  Further, I intend\n> to use this test tool in the future to _also_ show the stored stat()\n> data, which would be inappropriate here in ls-files.\n>\n> I would prefer to continue using the test helper here and leave\n> functional changes to ls-files be considered independently.\n\nWell, I was okay with it being in a test helper regardless of whether\nit could be done with ls-files, and then just circling back and fixing\nup ls-files later.  But perhaps it's worth calling out in the commit\nmessage about your plans to add stat() data and how that future piece\ncan't be done in ls-files (without functional changes of some sort)\njust to make it clearer why we're using a test helper instead of\nfront-loading the port of ls-files over to sparse-indexes?\n"},{"id":"419551","messageId":"0c799406-7369-336e-bbec-0d852ad19f3d@gmail.com","threadId":"55195","inReplyTo":"CABPp-BHh_QgF8xej8=xUJh+mYfKTB_d-7Su+xwcfRyaHEPz1mA@mail.gmail.com","subject":"Re: [RFC/PATCH 0/5] Re: [PATCH v3 07/20] test-read-cache: print cache entries with --table","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-17T20:34:48Z","receivedAt":"2021-03-17T20:35:52Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/17/2021 4:26 PM, Elijah Newren wrote:\n> On Wed, Mar 17, 2021 at 12:46 PM Derrick Stolee <stolee@gmail.com> wrote:\n>>\n>> On 3/17/2021 2:28 PM, Elijah Newren wrote:\n>>> On Wed, Mar 17, 2021 at 6:28 AM Ævar Arnfjörð Bjarmason\n>>> <avarab@gmail.com> wrote:\n>>>>\n>>>>> From: Derrick Stolee <dstolee@microsoft.com>\n>>\n>>>>\n>>>> So we have a test tool that's mostly ls-files but mocks the output\n>>>> ls-tree would emit, won't these tests eventually care about what stage\n>>>> things are in?\n>>>>\n>>>> What follows is an RFC series on top that's the result of me wondering\n>>>> why if we're adding new index constructs we aren't updating our\n>>>> plumbing to emit that data, can we just add this to ls-files and drop\n>>>> this test helper?\n>>>>\n>>>> Turns out: Yes we can.\n>>>\n>>> I like the idea of having ls-files be usable to show the entries that\n>>> are in the index; that seems great to me.  I very much dislike the\n>>> --sparse flag to ls-files, as noted on that commit.\n>>\n>> I don't like this idea. I don't think exposing internal structures\n>> like this is something we want to do so quickly.\n> \n> Not sure I follow; ls-files was already about exposing three bits of\n> internal structures for index entries: mode, hash, and stage number.\n> These are quantities that are well-defined for sparse directories too.\n> It would not be exposing any new or different internal structures, nor\n> changing the output format.  (Ævar changed the tests to not look for\n> \"tree\" but to look for the \"040000\" mode number.)\n\nTrue, that is some internal information already.\n\n>>  Further, I intend\n>> to use this test tool in the future to _also_ show the stored stat()\n>> data, which would be inappropriate here in ls-files.\n>>\n>> I would prefer to continue using the test helper here and leave\n>> functional changes to ls-files be considered independently.\n> \n> Well, I was okay with it being in a test helper regardless of whether\n> it could be done with ls-files, and then just circling back and fixing\n> up ls-files later.  But perhaps it's worth calling out in the commit\n> message about your plans to add stat() data and how that future piece\n> can't be done in ls-files (without functional changes of some sort)\n> just to make it clearer why we're using a test helper instead of\n> front-loading the port of ls-files over to sparse-indexes?\n\nAdding this justification to the commit message would definitely be\nhelpful, so I will do that.\n\nThanks,\n-Stolee\n"},{"id":"419553","messageId":"028fb838-f012-e8c7-eb8b-53c810802243@gmail.com","threadId":"55195","inReplyTo":"20210317132814.30175-4-avarab@gmail.com","subject":"Re: [RFC/PATCH 3/5] ls-files: add and use a new --sparse option","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-17T20:43:14Z","receivedAt":"2021-03-17T20:44:09Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/17/2021 9:28 AM, Ævar Arnfjörð Bjarmason wrote:\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>> diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\n\nI want to learn from your suggested changes to the test, here,\nso forgive my questions here:\n  \n> +test_index_entry_like () {\n> +\tdir=$1\n> +\tshift\n> +\tfmt=$1\n> +\tshift\n> +\trev=$1\n> +\tshift\n> +\tentry=$1\n> +\tshift\n> +\tfile=$1\n> +\tshift\n\nWhy all the shifts? Why not just use $1, $2, $3,...? My\nguess is that you want to be able to insert a new parameter\nin the middle in the future without changing the later\nnumbers, but that seems unlikely, and we could just add\nthe parameter at the end.\n\n> +\thash=$(git -C \"$dir\" rev-parse \"$rev\") &&\n> +\tprintf \"$fmt\\n\" \"$hash\" \"$entry\" >expected &&\n> +\tif grep \"$entry\" \"$file\" >line\n> +\tthen\n> +\t\ttest_cmp expected line\n> +\telse\n> +\t\tcat cache &&\n> +\t\tfalse\n> +\tfi\n> +}\n> +\n>  test_expect_success 'sparse-index contents' '\n>  \tinit_repos &&\n>  \n> -\ttest-tool -C sparse-index read-cache --table >cache &&\n> +\tgit -C sparse-index ls-files --sparse >cache &&\n>  \tfor dir in folder1 folder2 x\n>  \tdo\n> -\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n> -\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n> -\t\t\t|| return 1\n> +\t\ttest_index_entry_like sparse-index \"040000 %s 0\\t%s\" \"HEAD:$dir\" \"$dir/\" cache || return 1\n\nI see how this uses only one line, but it seems like the\ntest_index_entry_like is too generic to make it not a\ncomplicated mess of format strings that need to copy\nover and over again.\n\nPerhaps instead it could be a \"test_entry_is_tree\"\nand it only passes \"$dir\" and \"cache\"? Then we could drop the loop and\njust have\n\n\ttest_entry_is_tree cache folder1 &&\n\ttest_entry_is_tree cache folder2 &&\n\ttest_entry_is_tree cache x &&\n\nor we could still use the loop, especially when we test for four trees.\n\n> -\ttest-tool -C sparse-index read-cache --table >cache &&\n> +\tgit -C sparse-index ls-files --sparse >cache &&\n>  \tfor dir in deep/deeper2 folder1 folder2 x\n>  \tdo\n> -\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n> -\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n> -\t\t\t|| return 1\n> +\t\ttest_index_entry_like sparse-index \"040000 %s 0\\t%s\" \"HEAD:$dir\" \"$dir/\" cache || return 1\n>  \tdone &&\n>  \n> +\tgrep 040000 cache >lines &&\n> +\ttest_line_count = 4 lines &&\n> +\n\nThe point here is to check that no other entries are trees? We know\nthat this number will be _at least_ 4 based on the loop above.\n\nThanks,\n-Stolee\n"},{"id":"419635","messageId":"70fe8d2e-d648-a064-bd8a-9e0b813980a0@gmail.com","threadId":"55195","inReplyTo":"1de9bc8a-b0fd-596a-3ae2-2e055b37534b@gmail.com","subject":"Re: [PATCH v3 11/20] sparse-index: convert from full to sparse","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-18T13:38:47Z","receivedAt":"2021-03-18T13:39:38Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/17/2021 3:55 PM, Derrick Stolee wrote:\n> On 3/17/2021 9:43 AM, Ævar Arnfjörð Bjarmason wrote:\n>>\n>> On Tue, Mar 16 2021, Derrick Stolee via GitGitGadget wrote:\n>>> @@ -251,6 +251,8 @@ static inline unsigned int create_ce_mode(unsigned int mode)\n>>>  {\n>>>  \tif (S_ISLNK(mode))\n>>>  \t\treturn S_IFLNK;\n>>> +\tif (mode == S_IFDIR)\n>>> +\t\treturn S_IFDIR;\n>>\n>> Does this actually need to be mode == S_IFDIR v.s. S_ISDIR(mode)? Those\n>> aren't the same thing...\n>>\n>>>  \tif (S_ISDIR(mode) || S_ISGITLINK(mode))\n>>>  \t\treturn S_IFGITLINK;\n>>\n>> ...and if it can be S_ISDIR(mode) then this becomes just\n>> S_ISGITLINK(mode), but losing the \"if\" there makes me suspect that some\n>> dir == submodule heuristic is being broken somewhere..\n>  \n> I have a vague recollection that I did that at one point, and\n> it didn't work. However, using the simpler\n> \n> \tif (S_ISDIR(mode))\n> \t\treturn S_IFDIR;\n> \tif (S_ISGITLINK(mode))\n> \t\treturn S_IFGITLINK;\n> \n> passes all of my tests.\n\nI'm not sure why it was passing yesterday (maybe I was in the\nwrong worktree) but I _do_ get failures, such as this one in t2105:\n\nexpecting success of 2105.4 'add gitlink to relative .git file': \n        git update-index --add -- sub2\n\n+ git update-index --add -- sub2\nwarning: index entry is a directory, but not sparse (00000000)\nerror: Could not read 50e526bb426771f6036ad3a8b0c81d511d91fc2a\nBUG: read-cache.c:324: unsupported ce_mode: 40000\nAborted (core dumped)\nerror: last command exited with $?=134\nnot ok 4 - add gitlink to relative .git file\n#\n#               git update-index --add -- sub2\n#\n\nIn this case, the mode that is specified is equal to 040775,\nso we need to use the permission bits outside of __S_IFMT\n(0170000) to determine if this is a sparse directory or a\nsubmodule entry. Submodules will never be sparse, so\npermissions matter. Sparse directories never actually exist,\nso permissions don't matter.\n\nPlaying around with it, I still only see the exact equality\nas working for me.\n\nI can, however, use this format for these if statements:\n\n\tif (S_ISSPARSEDIR(mode))\n\t\treturn S_IFDIR;\n\tif (S_ISDIR(mode) || S_ISGITLINK(mode))\n\t\treturn S_IFGITLINK;\n\nThe S_ISSPARSEDIR macro expands to the exact equality.\n\nNow, if we intended to make this work differently, then a\nchange would be required to construct_sparse_dir_entry()\nin sparse-index.c:\n\nstatic struct cache_entry *construct_sparse_dir_entry(\n\t\t\t\tstruct index_state *istate,\n\t\t\t\tconst char *sparse_dir,\n\t\t\t\tstruct cache_tree *tree)\n{\n\tstruct cache_entry *de;\n\n\tde = make_cache_entry(istate, S_IFDIR, &tree->oid, sparse_dir, 0, 0);\n\n\tde->ce_flags |= CE_SKIP_WORKTREE;\n\treturn de;\n}\n\nFor instance, we could at this point assign de->ce_mode to\nbe S_IFDIR directly. It seems like the wrong place to do that\nto me, but I'm open to suggestions.\n\nThanks,\n-Stolee\n"},{"id":"419658","messageId":"xmqqzgz0qghd.fsf@gitster.g","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 00/20] Sparse Index: Design, Format, Tests","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-18T21:50:06Z","receivedAt":"2021-03-18T21:50:51Z","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> For this version, I took Ævar's latest patches and applied them to v2.31.0\n> and rebased this series on top. It uses his new \"read_tree_at()\" helper and\n> the associated changes to the function pointer type.\n>\n>  * Fixed more typos. Thanks Martin and Elijah!\n>  * Updated the test_sparse_match() macro to use \"$@\" instead of $*\n>  * Added a test that git sparse-checkout init --no-sparse-index rewrites the\n>    index to be full.\n\nThanks.  I expect ab/read-tree would be rerolled at least one more\ntime, if only to straighten out the \"oops #5 was screwy, let's patch\nit up on top with three more steps\", but I do not expect the end\nstate would be all that different, so tentatively I'll queue these\npatches on top of the latest iteration of the topic for now and\nhope that the other topic will be updated soonish.\n\n\n"},{"id":"419700","messageId":"bbb87aa6-5e81-2528-39bd-461c3537bb1a@gmail.com","threadId":"55195","inReplyTo":"xmqqzgz0qghd.fsf@gitster.g","subject":"Re: [PATCH v3 00/20] Sparse Index: Design, Format, Tests","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-19T13:00:11Z","receivedAt":"2021-03-19T13:01:06Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"\n\nOn 3/18/2021 5:50 PM, Junio C Hamano wrote:\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> For this version, I took Ævar's latest patches and applied them to v2.31.0\n>> and rebased this series on top. It uses his new \"read_tree_at()\" helper and\n>> the associated changes to the function pointer type.\n>>\n>>  * Fixed more typos. Thanks Martin and Elijah!\n>>  * Updated the test_sparse_match() macro to use \"$@\" instead of $*\n>>  * Added a test that git sparse-checkout init --no-sparse-index rewrites the\n>>    index to be full.\n> \n> Thanks.  I expect ab/read-tree would be rerolled at least one more\n> time, if only to straighten out the \"oops #5 was screwy, let's patch\n> it up on top with three more steps\", but I do not expect the end\n> state would be all that different, so tentatively I'll queue these\n> patches on top of the latest iteration of the topic for now and\n> hope that the other topic will be updated soonish.\n\nThanks. I'm grateful that it can spend some time in 'seen' if only\nto avoid these conflicts in the meantime.\n\nI'm waiting for that reroll of ab/read-tree before updating this\nversion with the feedback from v3.\n\nThanks,\n-Stolee\n"},{"id":"419742","messageId":"xmqqczvulnfn.fsf@gitster.g","threadId":"55195","inReplyTo":"62ac13945bec13270e0898126756c3f947ae264b.1615912983.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 01/20] sparse-index: design doc and format update","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-19T23:43:24Z","receivedAt":"2021-03-19T23:44:35Z","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> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> This begins a long effort to update the index format to allow sparse\n> directory entries. This should result in a significant improvement to\n> Git commands when HEAD contains millions of files, but the user has\n> selected many fewer files to keep in their sparse-checkout definition.\n\nThis compromise makes sense.\n\nIn the past, we often dreamed of recording trees in the index\n(instead of using a bolted on extension like cache-tree, treating\ntrees as first-class citizens) and lazily expanding it only when the\nuser starts modifying the paths within the subdirectory.\n\nBut such an optimization never materialized, as the dual and\nconflicting nature of the index to keep track of the contents for\nthe \"next\" commit (for which it is sufficient to just record trees\nfor parts that have not been modified) and to cache stat information\nto detect which working tree paths may possibly have modifications\n(for which, we used the one-entry-per-path nature of the cache\nentries so far) was never resolved.\n\nBut if we limit the use of trees-in-index for sparse/cone checkout\ncase, we do not even have to worry about having to cache the stat\ninformation for those paths that we are not going to populate in the\nworking tree at all.  It is a great simplification of the problem.\n\n> +  These entries have mode `0040000`, include the `SKIP_WORKTREE` bit, and\n> +  the path ends in a directory separator.\n> +\n\nWhy leading two 0's?  At the tree object level, we do not 0-pad blob\nmode word, and if you are writing for C programmers, you need only\none '0' prefix to signal that it is in octal (in the on-disk index\nfile, the blob mode word is stored in a be16 word).\n\n> diff --git a/Documentation/technical/sparse-index.txt b/Documentation/technical/sparse-index.txt\n> new file mode 100644\n> index 000000000000..aa116406a016\n> --- /dev/null\n> +++ b/Documentation/technical/sparse-index.txt\n> @@ -0,0 +1,173 @@\n> +Git Sparse-Index Design Document\n> +================================\n> +\n> +The sparse-checkout feature allows users to focus a working directory on\n> +a subset of the files at HEAD. The cone mode patterns, enabled by\n> +`core.sparseCheckoutCone`, allow for very fast pattern matching to\n> +discover which files at HEAD belong in the sparse-checkout cone.\n> +\n> +Three important scale dimensions for a Git worktree are:\n\ns/worktree/working tree/; The former is the thing the \"git worktree\"\ncommand deals with.  The latter is relevant even when \"git worktree\"\nis not used (the traditional \"git clone and you get a working tree\nto work in\").\n\n> +* `HEAD`: How many files are present at `HEAD`?\n> +\n> +* Populated: How many files are within the sparse-checkout cone.\n> +\n> +* Modified: How many files has the user modified in the working directory?\n> +\n> +We will use big-O notation -- O(X) -- to denote how expensive certain\n> +operations are in terms of these dimensions.\n> +\n> +These dimensions are ordered by their magnitude: users (typically) modify\n> +fewer files than are populated, and we can only populate files at `HEAD`.\n\nOK.\n\n> +These dimensions are also ordered by how expensive they are per item: it\n> +is expensive to detect a modified file than it is to write one that we\n> +know must be populated; changing `HEAD` only really requires updating the\n> +index.\n\nThis is a bit too dense to grok.  Among Populated, there are some\nModified but it takes lstat(2) per path or fsmonitor listening to\ninotify to know which ones are in the Modified set.  Is that the\n\"expensive\" you are referring to here?  I am not sure how you\ncompared the cost to know if a path is modified or merely populated\nwith the cost of \"write one that we know must be populated\" (which I\ntake as \"given a populated file, make modification to it\").  Also it\nis unclear what you mean by \"changing HEAD only require updating the\nindex\".  Certainly when \"git switch\" flips HEAD from one commit to\nanother, you'd update the index and update the files in the working\ntree (in the Populated part that is in the sparse-checkout cone) to\nmatch, no?\n\n> +Problems occur if there is an extreme imbalance in these dimensions. For\n> +example, if `HEAD` contains millions of paths but the populated set has\n> +only tens of thousands, then commands like `git status` and `git add` can\n> +be dominated by operations that require O(`HEAD`) operations instead of\n> +O(Populated). Primarily, the cost is in parsing and rewriting the index,\n> +which is filled primarily with files at `HEAD` that are marked with the\n> +`SKIP_WORKTREE` bit.\n> +\n> +The sparse-index intends to take these commands that read and modify the\n> +index from O(`HEAD`) to O(Populated). To do this, we need to modify the\n> +index format in a significant way: add \"sparse directory\" entries.\n\nOK.\n\n> +With cone mode patterns, it is possible to detect when an entire\n> +directory will have its contents outside of the sparse-checkout definition.\n> +Instead of listing all of the files it contains as individual entries, a\n> +sparse-index contains an entry with the directory name, referencing the\n> +object ID of the tree at `HEAD` and marked with the `SKIP_WORKTREE` bit.\n> +If we need to discover the details for paths within that directory, we\n> +can parse trees to find that list.\n\n;-)\n\n> +At time of writing, sparse-directory entries violate expectations about the\n> +index format and its in-memory data structure. There are many consumers in\n> +the codebase that expect to iterate through all of the index entries and\n> +see only files.\n\nTrue.\n\n> In addition, they expect to see all files at `HEAD`.\n\nIt is not clear to me what this means.  After \"git add\", \"git\nls-files\" would expect to see a file that may not even in HEAD.\nAfter \"git rm\", it would expect to see some file missing from the\nset of paths in HEAD.  While I do not think that is what you meant\nhere, it is hard to guess what you wanted to say.\n\n> One\n> +way to handle this is to parse trees to replace a sparse-directory entry\n> +with all of the files within that tree as the index is loaded. However,\n> +parsing trees is slower than parsing the index format, so that is a slower\n> +operation than if we left the index alone.\n\nBesides, that would leave in-core index fully populated, so I would\nsuspect that you'd lose a lot of benefit that comes from having to\nkeep much fewer entries in the in-core index than what is in HEAD.\nIt would be nice for \"git diff-index --cached\" (which is part of\n\"git status\") to be able to skip a single \"tree\" entry in the sparse\nindex as \"known to be untouched\", than skipping thousands of paths\nin that single subdirectory (in a mega monorepo project) as \"these\nare marked with SKIP_WORKTREE so ignore what is in the working tree\".\n\n> +The implementation plan below follows four phases to slowly integrate with\n> +the sparse-index. The intention is to incrementally update Git commands to\n> +interact safely with the sparse-index without significant slowdowns. This\n> +may not always be possible, but the hope is that the primary commands that\n> +users need in their daily work are dramatically improved.\n\nOK.\n\n> +Phase I: Format and initial speedups\n> +------------------------------------\n> +\n> +During this phase, Git learns to enable the sparse-index and safely parse\n> +one. Protections are put in place so that every consumer of the in-memory\n> +data structure can operate with its current assumption of every file at\n> +`HEAD`.\n\nIOW, before they iterate over the in-core index, tree entries are expanded\ninto bunch of individual entries with SKIP_WORKTREE bit?  Makes sense.\n\n> +At first, every index parse will expand the sparse-directory entries into\n> +the full list of paths at `HEAD`. This will be slower in all cases. The\n> +only noticable change in behavior will be that the serialized index file\n> +contains sparse-directory entries.\n\nHmph, do you mean that the expansion is done by not replacing each\n\"tree\" entry with blob entries for the contents of the directory,\nbut the original \"tree\" entry is still left in the in-core index?\nIt is not immediately clear what we are trying to gain by leaving it\nin, but let's read on.  Perhaps we can get rid of cache-tree\nextension and replace its use with these \"tree\" entries whose\ncontent paths are populated in the index?\n\n> +To start, we use a new repository extension, `extensions.sparseIndex`, to\n> +allow inserting sparse-directory entries into indexes with file format\n> +versions 2, 3, and 4. This prevents Git versions that do not understand\n> +the sparse-index from operating on one, but it also prevents other\n> +operations that do not use the index at all. A new format, index v5, will\n> +be introduced that includes sparse-directory entries by default. It might\n> +also introduce other features that have been considered for improving the\n> +index, as well.\n\nOK.\n\n> +Next, consumers of the index will be guarded against operating on a\n> +sparse-index by inserting calls to `ensure_full_index()` or\n> +`expand_index_to_path()`. After these guards are in place, we can begin\n> +leaving sparse-directory entries in the in-memory index structure.\n\nIt is unclear why \"we can begin leaving\"; an iterator that only\nexpects to see blobs would need to be updated to skip them, too, no?\nThey would probably be already skipping blob entries that are marked\nwith the SKIP_WORKTREE bit, so it may be just a matter of skipping\nmore things than the current code.\n\nOr did I misread the design presented earlier, and when a directory\nthat is outside the cone is expanded into the paths of blobs in the\ndirectory, the \"tree\" entry is removed from the in-core index?\n\n> +Even after inserting these guards, we will keep expanding sparse-indexes\n> +for most Git commands using the `command_requires_full_index` repository\n> +setting. This setting will be on by default and disabled one builtin at a\n> +time until we have sufficient confidence that all of the index operations\n> +are properly guarded.\n\nOK.\n\n> +To complete this phase, the commands `git status` and `git add` will be\n> +integrated with the sparse-index so that they operate with O(Populated)\n> +performance. They will be carefully tested for operations within and\n> +outside the sparse-checkout definition.\n\n;-)\n\n"},{"id":"419998","messageId":"97d57947-dd5f-bb06-cc40-37a23f1db5d9@gmail.com","threadId":"55195","inReplyTo":"xmqqczvulnfn.fsf@gitster.g","subject":"Re: [PATCH v3 01/20] sparse-index: design doc and format update","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-23T11:16:33Z","receivedAt":"2021-03-23T11:17:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/19/2021 7:43 PM, Junio C Hamano wrote:\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Derrick Stolee <dstolee@microsoft.com>\n>>\n>> This begins a long effort to update the index format to allow sparse\n>> directory entries. This should result in a significant improvement to\n>> Git commands when HEAD contains millions of files, but the user has\n>> selected many fewer files to keep in their sparse-checkout definition.\n> \n> This compromise makes sense.\n> \n> In the past, we often dreamed of recording trees in the index\n> (instead of using a bolted on extension like cache-tree, treating\n> trees as first-class citizens) and lazily expanding it only when the\n> user starts modifying the paths within the subdirectory.\n> \n> But such an optimization never materialized, as the dual and\n> conflicting nature of the index to keep track of the contents for\n> the \"next\" commit (for which it is sufficient to just record trees\n> for parts that have not been modified) and to cache stat information\n> to detect which working tree paths may possibly have modifications\n> (for which, we used the one-entry-per-path nature of the cache\n> entries so far) was never resolved.\n> \n> But if we limit the use of trees-in-index for sparse/cone checkout\n> case, we do not even have to worry about having to cache the stat\n> information for those paths that we are not going to populate in the\n> working tree at all.  It is a great simplification of the problem.\n\nThanks. I appreciate your input here.\n \n>> +  These entries have mode `0040000`, include the `SKIP_WORKTREE` bit, and\n>> +  the path ends in a directory separator.\n>> +\n> \n> Why leading two 0's?  At the tree object level, we do not 0-pad blob\n> mode word, and if you are writing for C programmers, you need only\n> one '0' prefix to signal that it is in octal (in the on-disk index\n> file, the blob mode word is stored in a be16 word).\n\nFixed.\n\n>> diff --git a/Documentation/technical/sparse-index.txt b/Documentation/technical/sparse-index.txt\n>> new file mode 100644\n>> index 000000000000..aa116406a016\n>> --- /dev/null\n>> +++ b/Documentation/technical/sparse-index.txt\n>> @@ -0,0 +1,173 @@\n>> +Git Sparse-Index Design Document\n>> +================================\n>> +\n>> +The sparse-checkout feature allows users to focus a working directory on\n>> +a subset of the files at HEAD. The cone mode patterns, enabled by\n>> +`core.sparseCheckoutCone`, allow for very fast pattern matching to\n>> +discover which files at HEAD belong in the sparse-checkout cone.\n>> +\n>> +Three important scale dimensions for a Git worktree are:\n> \n> s/worktree/working tree/; The former is the thing the \"git worktree\"\n> command deals with.  The latter is relevant even when \"git worktree\"\n> is not used (the traditional \"git clone and you get a working tree\n> to work in\").\n\nI guess I'm distracted by using SKIP_WORKTREE a lot, but \"working\ndirectory\" is more specific and hence better.\n\n>> +These dimensions are also ordered by how expensive they are per item: it\n>> +is expensive to detect a modified file than it is to write one that we\n>> +know must be populated; changing `HEAD` only really requires updating the\n>> +index.\n> \n> This is a bit too dense to grok.  Among Populated, there are some\n> Modified but it takes lstat(2) per path or fsmonitor listening to\n> inotify to know which ones are in the Modified set.  Is that the\n> \"expensive\" you are referring to here?  I am not sure how you\n> compared the cost to know if a path is modified or merely populated\n> with the cost of \"write one that we know must be populated\" (which I\n> take as \"given a populated file, make modification to it\"). \n\nI could rearrange things here. The important things to note are:\n\n1. Updating index entries is very fast, but adds up at large scale.\n\n2. It is faster to write a file to disk from Git's object database\n   than it is to compare a file on disk to the copy in the database,\n   which is frequently necessary when the mtime on disk doesn't match\n   the mtime in the index.\n\n> Also it\n> is unclear what you mean by \"changing HEAD only require updating the\n> index\".  Certainly when \"git switch\" flips HEAD from one commit to\n> another, you'd update the index and update the files in the working\n> tree (in the Populated part that is in the sparse-checkout cone) to\n> match, no?\n\nThis is unclear of me. I was thinking more on the lines of \"git reset\"\n(soft mode) which updates HEAD without changing the files on disk.\n\nAfter all of this postulating, I think that the offending sentences\nare better off deleted. They don't add clarity over what can be\ninferred by an interested reader.\n\n>> In addition, they expect to see all files at `HEAD`.\n> \n> It is not clear to me what this means.  After \"git add\", \"git\n> ls-files\" would expect to see a file that may not even in HEAD.\n> After \"git rm\", it would expect to see some file missing from the\n> set of paths in HEAD.  While I do not think that is what you meant\n> here, it is hard to guess what you wanted to say.\n\nI'm mixing terms incorrectly. I think what I really mean is\n\n  In fact, these loops expect to see a reference to every\n  staged file.\n\n>> One\n>> +way to handle this is to parse trees to replace a sparse-directory entry\n>> +with all of the files within that tree as the index is loaded. However,\n>> +parsing trees is slower than parsing the index format, so that is a slower\n>> +operation than if we left the index alone.\n> \n> Besides, that would leave in-core index fully populated, so I would\n> suspect that you'd lose a lot of benefit that comes from having to\n> keep much fewer entries in the in-core index than what is in HEAD.\n> It would be nice for \"git diff-index --cached\" (which is part of\n> \"git status\") to be able to skip a single \"tree\" entry in the sparse\n> index as \"known to be untouched\", than skipping thousands of paths\n> in that single subdirectory (in a mega monorepo project) as \"these\n> are marked with SKIP_WORKTREE so ignore what is in the working tree\".\n\nAbsolutely! I'm burying the lead here, so I should get to the real\npoint by adding this to the end:\n\n The plan is to make all of these integrations \"sparse aware\" so\n this expansion through tree parsing is unnecessary and they use\n fewer resources than when using a full index.\n\n>> +Phase I: Format and initial speedups\n>> +------------------------------------\n>> +\n>> +During this phase, Git learns to enable the sparse-index and safely parse\n>> +one. Protections are put in place so that every consumer of the in-memory\n>> +data structure can operate with its current assumption of every file at\n>> +`HEAD`.\n> \n> IOW, before they iterate over the in-core index, tree entries are expanded\n> into bunch of individual entries with SKIP_WORKTREE bit?  Makes sense.\n> \n>> +At first, every index parse will expand the sparse-directory entries into\n>> +the full list of paths at `HEAD`. This will be slower in all cases. The\n>> +only noticable change in behavior will be that the serialized index file\n>> +contains sparse-directory entries.\n> \n> Hmph, do you mean that the expansion is done by not replacing each\n> \"tree\" entry with blob entries for the contents of the directory,\n> but the original \"tree\" entry is still left in the in-core index?\n\nI meant by \"serialized index file\" is that the file written to disk has\nthe sparse directory entries, but the in-core copy will not (except for\na very brief moment in time, during do_read_index()).\n\nThe intention at this point in time is that all code behaves identically\nto the full index case, except that the index file itself is smaller due\nto these sparse directory entries.\n\n> It is not immediately clear what we are trying to gain by leaving it\n> in, but let's read on.  Perhaps we can get rid of cache-tree\n> extension and replace its use with these \"tree\" entries whose\n> content paths are populated in the index?\n\nThis is an interesting idea, but not one I plan to pursue with this work.\n\n>> +Next, consumers of the index will be guarded against operating on a\n>> +sparse-index by inserting calls to `ensure_full_index()` or\n>> +`expand_index_to_path()`. After these guards are in place, we can begin\n>> +leaving sparse-directory entries in the in-memory index structure.\n> \n> It is unclear why \"we can begin leaving\"; an iterator that only\n> expects to see blobs would need to be updated to skip them, too, no?\n> They would probably be already skipping blob entries that are marked\n> with the SKIP_WORKTREE bit, so it may be just a matter of skipping\n> more things than the current code.\n> \n> Or did I misread the design presented earlier, and when a directory\n> that is outside the cone is expanded into the paths of blobs in the\n> directory, the \"tree\" entry is removed from the in-core index?\n\nI will make this more explicit.\n \nThanks for your help improving this doc! Hopefully the plan is a\nlittle more clear, now.\n\n-Stolee\n"},{"id":"420005","messageId":"6426a5c60e53e30091360c00c61c9123803fe9c1.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 01/20] sparse-index: design doc and format update","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:09Z","receivedAt":"2021-03-23T13:45: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\nThis begins a long effort to update the index format to allow sparse\ndirectory entries. This should result in a significant improvement to\nGit commands when HEAD contains millions of files, but the user has\nselected many fewer files to keep in their sparse-checkout definition.\n\nCurrently, the index format is only updated in the presence of\nextensions.sparseIndex instead of increasing a file format version\nnumber. This is temporary, and index v5 is part of the plan for future\nwork in this area.\n\nThe design document details many of the reasons for embarking on this\nwork, and also the plan for completing it safely.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/technical/index-format.txt |   7 +\n Documentation/technical/sparse-index.txt | 174 +++++++++++++++++++++++\n 2 files changed, 181 insertions(+)\n create mode 100644 Documentation/technical/sparse-index.txt\n\ndiff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt\nindex d363a71c37ec..3b74c05647db 100644\n--- a/Documentation/technical/index-format.txt\n+++ b/Documentation/technical/index-format.txt\n@@ -44,6 +44,13 @@ Git index format\n   localization, no special casing of directory separator '/'). Entries\n   with the same name are sorted by their stage field.\n \n+  An index entry typically represents a file. However, if sparse-checkout\n+  is enabled in cone mode (`core.sparseCheckoutCone` is enabled) and the\n+  `extensions.sparseIndex` extension is enabled, then the index may\n+  contain entries for directories outside of the sparse-checkout definition.\n+  These entries have mode `040000`, include the `SKIP_WORKTREE` bit, and\n+  the path ends in a directory separator.\n+\n   32-bit ctime seconds, the last time a file's metadata changed\n     this is stat(2) data\n \ndiff --git a/Documentation/technical/sparse-index.txt b/Documentation/technical/sparse-index.txt\nnew file mode 100644\nindex 000000000000..62f6dc225a44\n--- /dev/null\n+++ b/Documentation/technical/sparse-index.txt\n@@ -0,0 +1,174 @@\n+Git Sparse-Index Design Document\n+================================\n+\n+The sparse-checkout feature allows users to focus a working directory on\n+a subset of the files at HEAD. The cone mode patterns, enabled by\n+`core.sparseCheckoutCone`, allow for very fast pattern matching to\n+discover which files at HEAD belong in the sparse-checkout cone.\n+\n+Three important scale dimensions for a Git working directory are:\n+\n+* `HEAD`: How many files are present at `HEAD`?\n+\n+* Populated: How many files are within the sparse-checkout cone.\n+\n+* Modified: How many files has the user modified in the working directory?\n+\n+We will use big-O notation -- O(X) -- to denote how expensive certain\n+operations are in terms of these dimensions.\n+\n+These dimensions are ordered by their magnitude: users (typically) modify\n+fewer files than are populated, and we can only populate files at `HEAD`.\n+\n+Problems occur if there is an extreme imbalance in these dimensions. For\n+example, if `HEAD` contains millions of paths but the populated set has\n+only tens of thousands, then commands like `git status` and `git add` can\n+be dominated by operations that require O(`HEAD`) operations instead of\n+O(Populated). Primarily, the cost is in parsing and rewriting the index,\n+which is filled primarily with files at `HEAD` that are marked with the\n+`SKIP_WORKTREE` bit.\n+\n+The sparse-index intends to take these commands that read and modify the\n+index from O(`HEAD`) to O(Populated). To do this, we need to modify the\n+index format in a significant way: add \"sparse directory\" entries.\n+\n+With cone mode patterns, it is possible to detect when an entire\n+directory will have its contents outside of the sparse-checkout definition.\n+Instead of listing all of the files it contains as individual entries, a\n+sparse-index contains an entry with the directory name, referencing the\n+object ID of the tree at `HEAD` and marked with the `SKIP_WORKTREE` bit.\n+If we need to discover the details for paths within that directory, we\n+can parse trees to find that list.\n+\n+At time of writing, sparse-directory entries violate expectations about the\n+index format and its in-memory data structure. There are many consumers in\n+the codebase that expect to iterate through all of the index entries and\n+see only files. In fact, these loops expect to see a reference to every\n+staged file. One way to handle this is to parse trees to replace a\n+sparse-directory entry with all of the files within that tree as the index\n+is loaded. However, parsing trees is slower than parsing the index format,\n+so that is a slower operation than if we left the index alone. The plan is\n+to make all of these integrations \"sparse aware\" so this expansion through\n+tree parsing is unnecessary and they use fewer resources than when using a\n+full index.\n+\n+The implementation plan below follows four phases to slowly integrate with\n+the sparse-index. The intention is to incrementally update Git commands to\n+interact safely with the sparse-index without significant slowdowns. This\n+may not always be possible, but the hope is that the primary commands that\n+users need in their daily work are dramatically improved.\n+\n+Phase I: Format and initial speedups\n+------------------------------------\n+\n+During this phase, Git learns to enable the sparse-index and safely parse\n+one. Protections are put in place so that every consumer of the in-memory\n+data structure can operate with its current assumption of every file at\n+`HEAD`.\n+\n+At first, every index parse will call a helper method,\n+`ensure_full_index()`, which scans the index for sparse-directory entries\n+(pointing to trees) and replaces them with the full list of paths (with\n+blob contents) by parsing tree objects. This will be slower in all cases.\n+The only noticeable change in behavior will be that the serialized index\n+file contains sparse-directory entries.\n+\n+To start, we use a new repository extension, `extensions.sparseIndex`, to\n+allow inserting sparse-directory entries into indexes with file format\n+versions 2, 3, and 4. This prevents Git versions that do not understand\n+the sparse-index from operating on one, but it also prevents other\n+operations that do not use the index at all. A new format, index v5, will\n+be introduced that includes sparse-directory entries by default. It might\n+also introduce other features that have been considered for improving the\n+index, as well.\n+\n+Next, consumers of the index will be guarded against operating on a\n+sparse-index by inserting calls to `ensure_full_index()` or\n+`expand_index_to_path()`. After these guards are in place, we can begin\n+leaving sparse-directory entries in the in-memory index structure.\n+\n+Even after inserting these guards, we will keep expanding sparse-indexes\n+for most Git commands using the `command_requires_full_index` repository\n+setting. This setting will be on by default and disabled one builtin at a\n+time until we have sufficient confidence that all of the index operations\n+are properly guarded.\n+\n+To complete this phase, the commands `git status` and `git add` will be\n+integrated with the sparse-index so that they operate with O(Populated)\n+performance. They will be carefully tested for operations within and\n+outside the sparse-checkout definition.\n+\n+Phase II: Careful integrations\n+------------------------------\n+\n+This phase focuses on ensuring that all index extensions and APIs work\n+well with a sparse-index. This requires significant increases to our test\n+coverage, especially for operations that interact with the working\n+directory outside of the sparse-checkout definition. Some of these\n+behaviors may not be the desirable ones, such as some tests already\n+marked for failure in `t1092-sparse-checkout-compatibility.sh`.\n+\n+The index extensions that may require special integrations are:\n+\n+* FS Monitor\n+* Untracked cache\n+\n+While integrating with these features, we should look for patterns that\n+might lead to better APIs for interacting with the index. Coalescing\n+common usage patterns into an API call can reduce the number of places\n+where sparse-directories need to be handled carefully.\n+\n+Phase III: Important command speedups\n+-------------------------------------\n+\n+At this point, the patterns for testing and implementing sparse-directory\n+logic should be relatively stable. This phase focuses on updating some of\n+the most common builtins that use the index to operate as O(Populated).\n+Here is a potential list of commands that could be valuable to integrate\n+at this point:\n+\n+* `git commit`\n+* `git checkout`\n+* `git merge`\n+* `git rebase`\n+\n+Hopefully, commands such as `git merge` and `git rebase` can benefit\n+instead from merge algorithms that do not use the index as a data\n+structure, such as the merge-ORT strategy. As these topics mature, we\n+may enable the ORT strategy by default for repositories using the\n+sparse-index feature.\n+\n+Along with `git status` and `git add`, these commands cover the majority\n+of users' interactions with the working directory. In addition, we can\n+integrate with these commands:\n+\n+* `git grep`\n+* `git rm`\n+\n+These have been proposed as some whose behavior could change when in a\n+repo with a sparse-checkout definition. It would be good to include this\n+behavior automatically when using a sparse-index. Some clarity is needed\n+to make the behavior switch clear to the user.\n+\n+This phase is the first where parallel work might be possible without too\n+much conflicts between topics.\n+\n+Phase IV: The long tail\n+-----------------------\n+\n+This last phase is less a \"phase\" and more \"the new normal\" after all of\n+the previous work.\n+\n+To start, the `command_requires_full_index` option could be removed in\n+favor of expanding only when hitting an API guard.\n+\n+There are many Git commands that could use special attention to operate as\n+O(Populated), while some might be so rare that it is acceptable to leave\n+them with additional overhead when a sparse-index is present.\n+\n+Here are some commands that might be useful to update:\n+\n+* `git sparse-checkout set`\n+* `git am`\n+* `git clean`\n+* `git stash`\n-- \ngitgitgadget\n\n"},{"id":"420006","messageId":"6b3b6d86385d7d8430644e6248996ee469041c3f.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 05/20] sparse-index: implement ensure_full_index()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:13Z","receivedAt":"2021-03-23T13:45:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWe will mark an in-memory index_state as having sparse directory entries\nwith the sparse_index bit. These currently cannot exist, but we will add\na mechanism for collapsing a full index to a sparse one in a later\nchange. That will happen at write time, so we must first allow parsing\nthe format before writing it.\n\nCommands or methods that require a full index in order to operate can\ncall ensure_full_index() to expand that index in-memory. This requires\nparsing trees using that index's repository.\n\nSparse directory entries have a specific 'ce_mode' value. The macro\nS_ISSPARSEDIR(ce->ce_mode) can check if a cache_entry 'ce' has this type.\nThis ce_mode is not possible with the existing index formats, so we don't\nalso verify all properties of a sparse-directory entry, which are:\n\n 1. ce->ce_mode == 0040000\n 2. ce->flags & CE_SKIP_WORKTREE is true\n 3. ce->name[ce->namelen - 1] == '/' (ends in dir separator)\n 4. ce->oid references a tree object.\n\nThese are all semi-enforced in ensure_full_index() to some extent. Any\ndeviation will cause a warning at minimum or a failure in the worst\ncase.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache.h        | 13 ++++++-\n read-cache.c   |  9 +++++\n sparse-index.c | 98 +++++++++++++++++++++++++++++++++++++++++++++++++-\n 3 files changed, 118 insertions(+), 2 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex bb317abc91fb..136dd496c95d 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -204,6 +204,8 @@ struct cache_entry {\n #error \"CE_EXTENDED_FLAGS out of range\"\n #endif\n \n+#define S_ISSPARSEDIR(m) ((m) == S_IFDIR)\n+\n /* Forward structure decls */\n struct pathspec;\n struct child_process;\n@@ -319,7 +321,14 @@ struct index_state {\n \t\t drop_cache_tree : 1,\n \t\t updated_workdir : 1,\n \t\t updated_skipworktree : 1,\n-\t\t fsmonitor_has_run_once : 1;\n+\t\t fsmonitor_has_run_once : 1,\n+\n+\t\t /*\n+\t\t  * sparse_index == 1 when sparse-directory\n+\t\t  * entries exist. Requires sparse-checkout\n+\t\t  * in cone mode.\n+\t\t  */\n+\t\t sparse_index : 1;\n \tstruct hashmap name_hash;\n \tstruct hashmap dir_hash;\n \tstruct object_id oid;\n@@ -722,6 +731,8 @@ int read_index_from(struct index_state *, const char *path,\n \t\t    const char *gitdir);\n int is_index_unborn(struct index_state *);\n \n+void ensure_full_index(struct index_state *istate);\n+\n /* For use with `write_locked_index()`. */\n #define COMMIT_LOCK\t\t(1 << 0)\n #define SKIP_IF_UNCHANGED\t(1 << 1)\ndiff --git a/read-cache.c b/read-cache.c\nindex 1e9a50c6c734..dd3980c12b53 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -101,6 +101,9 @@ static const char *alternate_index_output;\n \n static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n {\n+\tif (S_ISSPARSEDIR(ce->ce_mode))\n+\t\tistate->sparse_index = 1;\n+\n \tistate->cache[nr] = ce;\n \tadd_name_hash(istate, ce);\n }\n@@ -2273,6 +2276,12 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)\n \ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\",\n \t\t\t   istate->cache_nr);\n \n+\tif (!istate->repo)\n+\t\tistate->repo = the_repository;\n+\tprepare_repo_settings(istate->repo);\n+\tif (istate->repo->settings.command_requires_full_index)\n+\t\tensure_full_index(istate);\n+\n \treturn istate->cache_nr;\n \n unmap:\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 82183ead563b..7095378a1b28 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -1,8 +1,104 @@\n #include \"cache.h\"\n #include \"repository.h\"\n #include \"sparse-index.h\"\n+#include \"tree.h\"\n+#include \"pathspec.h\"\n+#include \"trace2.h\"\n+\n+static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n+{\n+\tALLOC_GROW(istate->cache, nr + 1, istate->cache_alloc);\n+\n+\tistate->cache[nr] = ce;\n+\tadd_name_hash(istate, ce);\n+}\n+\n+static int add_path_to_index(const struct object_id *oid,\n+\t\t\t     struct strbuf *base, const char *path,\n+\t\t\t     unsigned int mode, void *context)\n+{\n+\tstruct index_state *istate = (struct index_state *)context;\n+\tstruct cache_entry *ce;\n+\tsize_t len = base->len;\n+\n+\tif (S_ISDIR(mode))\n+\t\treturn READ_TREE_RECURSIVE;\n+\n+\tstrbuf_addstr(base, path);\n+\n+\tce = make_cache_entry(istate, mode, oid, base->buf, 0, 0);\n+\tce->ce_flags |= CE_SKIP_WORKTREE;\n+\tset_index_entry(istate, istate->cache_nr++, ce);\n+\n+\tstrbuf_setlen(base, len);\n+\treturn 0;\n+}\n \n void ensure_full_index(struct index_state *istate)\n {\n-\t/* intentionally left blank */\n+\tint i;\n+\tstruct index_state *full;\n+\tstruct strbuf base = STRBUF_INIT;\n+\n+\tif (!istate || !istate->sparse_index)\n+\t\treturn;\n+\n+\tif (!istate->repo)\n+\t\tistate->repo = the_repository;\n+\n+\ttrace2_region_enter(\"index\", \"ensure_full_index\", istate->repo);\n+\n+\t/* initialize basics of new index */\n+\tfull = xcalloc(1, sizeof(struct index_state));\n+\tmemcpy(full, istate, sizeof(struct index_state));\n+\n+\t/* then change the necessary things */\n+\tfull->sparse_index = 0;\n+\tfull->cache_alloc = (3 * istate->cache_alloc) / 2;\n+\tfull->cache_nr = 0;\n+\tALLOC_ARRAY(full->cache, full->cache_alloc);\n+\n+\tfor (i = 0; i < istate->cache_nr; i++) {\n+\t\tstruct cache_entry *ce = istate->cache[i];\n+\t\tstruct tree *tree;\n+\t\tstruct pathspec ps;\n+\n+\t\tif (!S_ISSPARSEDIR(ce->ce_mode)) {\n+\t\t\tset_index_entry(full, full->cache_nr++, ce);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!(ce->ce_flags & CE_SKIP_WORKTREE))\n+\t\t\twarning(_(\"index entry is a directory, but not sparse (%08x)\"),\n+\t\t\t\tce->ce_flags);\n+\n+\t\t/* recursively walk into cd->name */\n+\t\ttree = lookup_tree(istate->repo, &ce->oid);\n+\n+\t\tmemset(&ps, 0, sizeof(ps));\n+\t\tps.recursive = 1;\n+\t\tps.has_wildcard = 1;\n+\t\tps.max_depth = -1;\n+\n+\t\tstrbuf_setlen(&base, 0);\n+\t\tstrbuf_add(&base, ce->name, strlen(ce->name));\n+\n+\t\tread_tree_at(istate->repo, tree, &base, &ps,\n+\t\t\t     add_path_to_index, full);\n+\n+\t\t/* free directory entries. full entries are re-used */\n+\t\tdiscard_cache_entry(ce);\n+\t}\n+\n+\t/* Copy back into original index. */\n+\tmemcpy(&istate->name_hash, &full->name_hash, sizeof(full->name_hash));\n+\tistate->sparse_index = 0;\n+\tfree(istate->cache);\n+\tistate->cache = full->cache;\n+\tistate->cache_nr = full->cache_nr;\n+\tistate->cache_alloc = full->cache_alloc;\n+\n+\tstrbuf_release(&base);\n+\tfree(full);\n+\n+\ttrace2_region_leave(\"index\", \"ensure_full_index\", istate->repo);\n }\n-- \ngitgitgadget\n\n"},{"id":"420007","messageId":"03cdde7565630e43135028350092373917e6fa31.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 04/20] sparse-index: add guard to ensure full index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:12Z","receivedAt":"2021-03-23T13:45:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nUpcoming changes will introduce modifications to the index format that\nallow sparse directories. It will be useful to have a mechanism for\nconverting those sparse index files into full indexes by walking the\ntree at those sparse directories. Name this method ensure_full_index()\nas it will guarantee that the index is fully expanded.\n\nThis method is not implemented yet, and instead we focus on the\nscaffolding to declare it and call it at the appropriate time.\n\nAdd a 'command_requires_full_index' member to struct repo_settings. This\nwill be an indicator that we need the index in full mode to do certain\nindex operations. This starts as being true for every command, then we\nwill set it to false as some commands integrate with sparse indexes.\n\nIf 'command_requires_full_index' is true, then we will immediately\nexpand a sparse index to a full one upon reading from disk. This\nsuffices for now, but we will want to add more callers to\nensure_full_index() later.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Makefile        |  1 +\n repo-settings.c |  8 ++++++++\n repository.c    | 11 ++++++++++-\n repository.h    |  2 ++\n sparse-index.c  |  8 ++++++++\n sparse-index.h  |  7 +++++++\n 6 files changed, 36 insertions(+), 1 deletion(-)\n create mode 100644 sparse-index.c\n create mode 100644 sparse-index.h\n\ndiff --git a/Makefile b/Makefile\nindex dfb0f1000fa3..89b1d5374107 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -985,6 +985,7 @@ LIB_OBJS += setup.o\n LIB_OBJS += shallow.o\n LIB_OBJS += sideband.o\n LIB_OBJS += sigchain.o\n+LIB_OBJS += sparse-index.o\n LIB_OBJS += split-index.o\n LIB_OBJS += stable-qsort.o\n LIB_OBJS += strbuf.o\ndiff --git a/repo-settings.c b/repo-settings.c\nindex f7fff0f5ab83..d63569e4041e 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -77,4 +77,12 @@ void prepare_repo_settings(struct repository *r)\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+\t/*\n+\t * This setting guards all index reads to require a full index\n+\t * over a sparse index. After suitable guards are placed in the\n+\t * codebase around uses of the index, this setting will be\n+\t * removed.\n+\t */\n+\tr->settings.command_requires_full_index = 1;\n }\ndiff --git a/repository.c b/repository.c\nindex c98298acd017..a8acae002f71 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -10,6 +10,7 @@\n #include \"object.h\"\n #include \"lockfile.h\"\n #include \"submodule-config.h\"\n+#include \"sparse-index.h\"\n \n /* The main repository */\n static struct repository the_repo;\n@@ -261,6 +262,8 @@ void repo_clear(struct repository *repo)\n \n int repo_read_index(struct repository *repo)\n {\n+\tint res;\n+\n \tif (!repo->index)\n \t\trepo->index = xcalloc(1, sizeof(*repo->index));\n \n@@ -270,7 +273,13 @@ int repo_read_index(struct repository *repo)\n \telse if (repo->index->repo != repo)\n \t\tBUG(\"repo's index should point back at itself\");\n \n-\treturn read_index_from(repo->index, repo->index_file, repo->gitdir);\n+\tres = read_index_from(repo->index, repo->index_file, repo->gitdir);\n+\n+\tprepare_repo_settings(repo);\n+\tif (repo->settings.command_requires_full_index)\n+\t\tensure_full_index(repo->index);\n+\n+\treturn res;\n }\n \n int repo_hold_locked_index(struct repository *repo,\ndiff --git a/repository.h b/repository.h\nindex b385ca3c94b6..e06a23015697 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -41,6 +41,8 @@ struct repo_settings {\n \tenum fetch_negotiation_setting fetch_negotiation_algorithm;\n \n \tint core_multi_pack_index;\n+\n+\tunsigned command_requires_full_index:1;\n };\n \n struct repository {\ndiff --git a/sparse-index.c b/sparse-index.c\nnew file mode 100644\nindex 000000000000..82183ead563b\n--- /dev/null\n+++ b/sparse-index.c\n@@ -0,0 +1,8 @@\n+#include \"cache.h\"\n+#include \"repository.h\"\n+#include \"sparse-index.h\"\n+\n+void ensure_full_index(struct index_state *istate)\n+{\n+\t/* intentionally left blank */\n+}\ndiff --git a/sparse-index.h b/sparse-index.h\nnew file mode 100644\nindex 000000000000..09a20d036c46\n--- /dev/null\n+++ b/sparse-index.h\n@@ -0,0 +1,7 @@\n+#ifndef SPARSE_INDEX_H__\n+#define SPARSE_INDEX_H__\n+\n+struct index_state;\n+void ensure_full_index(struct index_state *istate);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"420008","messageId":"c9e21d78ecba46988e9e98ffecca96f1fcca6e79.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 03/20] t1092: clean up script quoting","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:11Z","receivedAt":"2021-03-23T13:45:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThis test was introduced in 19a0acc83e4 (t1092: test interesting\nsparse-checkout scenarios, 2021-01-23), but it contains issues with quoting\nthat were not noticed until starting this follow-up series. The old\nmechanism would drop quoting such as in\n\n   test_all_match git commit -m \"touch README.md\"\n\nThe above happened to work because README.md is a file in the\nrepository, so 'git commit -m touch REAMDE.md' would succeed by\naccident.\n\nOther cases included quoting for no good reason, so clean that up now.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/t1092-sparse-checkout-compatibility.sh | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 8cd3e5a8d227..3725d3997e70 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -96,20 +96,20 @@ init_repos () {\n run_on_sparse () {\n \t(\n \t\tcd sparse-checkout &&\n-\t\t$* >../sparse-checkout-out 2>../sparse-checkout-err\n+\t\t\"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n \t)\n }\n \n run_on_all () {\n \t(\n \t\tcd full-checkout &&\n-\t\t$* >../full-checkout-out 2>../full-checkout-err\n+\t\t\"$@\" >../full-checkout-out 2>../full-checkout-err\n \t) &&\n-\trun_on_sparse $*\n+\trun_on_sparse \"$@\"\n }\n \n test_all_match () {\n-\trun_on_all $* &&\n+\trun_on_all \"$@\" &&\n \ttest_cmp full-checkout-out sparse-checkout-out &&\n \ttest_cmp full-checkout-err sparse-checkout-err\n }\n@@ -119,7 +119,7 @@ test_expect_success 'status with options' '\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git status --porcelain=v2 -z -u &&\n \ttest_all_match git status --porcelain=v2 -uno &&\n-\trun_on_all \"touch README.md\" &&\n+\trun_on_all touch README.md &&\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git status --porcelain=v2 -z -u &&\n \ttest_all_match git status --porcelain=v2 -uno &&\n@@ -135,7 +135,7 @@ test_expect_success 'add, commit, checkout' '\n \twrite_script edit-contents <<-\\EOF &&\n \techo text >>$1\n \tEOF\n-\trun_on_all \"../edit-contents README.md\" &&\n+\trun_on_all ../edit-contents README.md &&\n \n \ttest_all_match git add README.md &&\n \ttest_all_match git status --porcelain=v2 &&\n@@ -144,7 +144,7 @@ test_expect_success 'add, commit, checkout' '\n \ttest_all_match git checkout HEAD~1 &&\n \ttest_all_match git checkout - &&\n \n-\trun_on_all \"../edit-contents README.md\" &&\n+\trun_on_all ../edit-contents README.md &&\n \n \ttest_all_match git add -A &&\n \ttest_all_match git status --porcelain=v2 &&\n@@ -153,7 +153,7 @@ test_expect_success 'add, commit, checkout' '\n \ttest_all_match git checkout HEAD~1 &&\n \ttest_all_match git checkout - &&\n \n-\trun_on_all \"../edit-contents deep/newfile\" &&\n+\trun_on_all ../edit-contents deep/newfile &&\n \n \ttest_all_match git status --porcelain=v2 -uno &&\n \ttest_all_match git status --porcelain=v2 &&\n@@ -186,7 +186,7 @@ test_expect_success 'diff --staged' '\n \twrite_script edit-contents <<-\\EOF &&\n \techo text >>README.md\n \tEOF\n-\trun_on_all \"../edit-contents\" &&\n+\trun_on_all ../edit-contents &&\n \n \ttest_all_match git diff &&\n \ttest_all_match git diff --staged &&\n@@ -280,7 +280,7 @@ test_expect_success 'clean' '\n \techo bogus >>.gitignore &&\n \trun_on_all cp ../.gitignore . &&\n \ttest_all_match git add .gitignore &&\n-\ttest_all_match git commit -m ignore-bogus-files &&\n+\ttest_all_match git commit -m \"ignore bogus files\" &&\n \n \trun_on_sparse mkdir folder1 &&\n \trun_on_all touch folder1/bogus &&\n-- \ngitgitgadget\n\n"},{"id":"420009","messageId":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v3.git.1615912983.gitgitgadget@gmail.com","subject":"[PATCH v4 00/20] Sparse Index: Design, Format, Tests","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:08Z","receivedAt":"2021-03-23T13:45:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"Here is the first full patch series submission coming out of the\nsparse-index RFC [1].\n\n[1]\nhttps://lore.kernel.org/git/pull.847.git.1611596533.gitgitgadget@gmail.com/\n\nI won't waste too much space here, because PATCH 1 includes a sizeable\ndesign document that describes the feature, the reasoning behind it, and my\nplan for getting this implemented widely throughout the codebase.\n\nThere are some new things here that were not in the RFC:\n\n * Design doc and format updates. (Patch 1)\n * Performance test script. (Patches 2 and 20)\n\nNotably missing in this series from the RFC:\n\n * The mega-patch inserting ensure_full_index() throughout the codebase.\n   That will be a follow-up series to this one.\n * The integrations with git status and git add to demonstrate the improved\n   performance. Those will also appear in their own series later.\n\nI plan to keep my latest work in this area in my 'sparse-index/wip' branch\n[2]. It includes all of the work from the RFC right now, updated with the\nwork from this series.\n\n[2] https://github.com/derrickstolee/git/tree/sparse-index/wip\n\n\nUpdates in V4\n=============\n\n * Rebased onto the latest copy of ab/read-tree.\n * Updated the design document as per Junio's comments.\n * Updated the submodule handling in the performance test.\n * Followed up on some other review from Ævar, mostly style or commit\n   message things.\n\n\nUpdates in V3\n=============\n\nFor this version, I took Ævar's latest patches and applied them to v2.31.0\nand rebased this series on top. It uses his new \"read_tree_at()\" helper and\nthe associated changes to the function pointer type.\n\n * Fixed more typos. Thanks Martin and Elijah!\n * Updated the test_sparse_match() macro to use \"$@\" instead of $*\n * Added a test that git sparse-checkout init --no-sparse-index rewrites the\n   index to be full.\n\n\nUpdates in V2\n=============\n\n * Various typos and awkward grammar is fixed.\n * Cleaned up unnecessary commands in p2000-sparse-operations.sh\n * Added a comment to the sparse_index member of struct index_state.\n * Used tree_type, commit_type, and blob_type in test-read-cache.c.\n\nThanks, -Stolee\n\nDerrick Stolee (20):\n  sparse-index: design doc and format update\n  t/perf: add performance test for sparse operations\n  t1092: clean up script quoting\n  sparse-index: add guard to ensure full index\n  sparse-index: implement ensure_full_index()\n  t1092: compare sparse-checkout to sparse-index\n  test-read-cache: print cache entries with --table\n  test-tool: don't force full index\n  unpack-trees: ensure full index\n  sparse-checkout: hold pattern list in index\n  sparse-index: convert from full to sparse\n  submodule: sparse-index should not collapse links\n  unpack-trees: allow sparse directories\n  sparse-index: check index conversion happens\n  sparse-index: create extension for compatibility\n  sparse-checkout: toggle sparse index from builtin\n  sparse-checkout: disable sparse-index\n  cache-tree: integrate with sparse directory entries\n  sparse-index: loose integration with cache_tree_verify()\n  p2000: add sparse-index repos\n\n Documentation/config/extensions.txt      |   8 +\n Documentation/git-sparse-checkout.txt    |  14 ++\n Documentation/technical/index-format.txt |   7 +\n Documentation/technical/sparse-index.txt | 174 ++++++++++++++\n Makefile                                 |   1 +\n builtin/sparse-checkout.c                |  44 +++-\n cache-tree.c                             |  40 ++++\n cache.h                                  |  18 +-\n read-cache.c                             |  35 ++-\n repo-settings.c                          |  15 ++\n repository.c                             |  11 +-\n repository.h                             |   3 +\n setup.c                                  |   3 +\n sparse-index.c                           | 293 +++++++++++++++++++++++\n sparse-index.h                           |  11 +\n t/README                                 |   3 +\n t/helper/test-read-cache.c               |  66 ++++-\n t/perf/p2000-sparse-operations.sh        | 101 ++++++++\n t/t1091-sparse-checkout-builtin.sh       |  13 +\n t/t1092-sparse-checkout-compatibility.sh | 143 +++++++++--\n unpack-trees.c                           |  17 +-\n 21 files changed, 980 insertions(+), 40 deletions(-)\n create mode 100644 Documentation/technical/sparse-index.txt\n create mode 100644 sparse-index.c\n create mode 100644 sparse-index.h\n create mode 100755 t/perf/p2000-sparse-operations.sh\n\n\nbase-commit: 47957485b3b731a7860e0554d2bd12c0dce1c75a\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-883%2Fderrickstolee%2Fsparse-index%2Fformat-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-883/derrickstolee/sparse-index/format-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/883\n\nRange-diff vs v3:\n\n  1:  62ac13945bec !  1:  6426a5c60e53 sparse-index: design doc and format update\n     @@ Documentation/technical/index-format.txt: Git index format\n      +  is enabled in cone mode (`core.sparseCheckoutCone` is enabled) and the\n      +  `extensions.sparseIndex` extension is enabled, then the index may\n      +  contain entries for directories outside of the sparse-checkout definition.\n     -+  These entries have mode `0040000`, include the `SKIP_WORKTREE` bit, and\n     ++  These entries have mode `040000`, include the `SKIP_WORKTREE` bit, and\n      +  the path ends in a directory separator.\n      +\n         32-bit ctime seconds, the last time a file's metadata changed\n     @@ Documentation/technical/sparse-index.txt (new)\n      +`core.sparseCheckoutCone`, allow for very fast pattern matching to\n      +discover which files at HEAD belong in the sparse-checkout cone.\n      +\n     -+Three important scale dimensions for a Git worktree are:\n     ++Three important scale dimensions for a Git working directory are:\n      +\n      +* `HEAD`: How many files are present at `HEAD`?\n      +\n     @@ Documentation/technical/sparse-index.txt (new)\n      +\n      +These dimensions are ordered by their magnitude: users (typically) modify\n      +fewer files than are populated, and we can only populate files at `HEAD`.\n     -+These dimensions are also ordered by how expensive they are per item: it\n     -+is expensive to detect a modified file than it is to write one that we\n     -+know must be populated; changing `HEAD` only really requires updating the\n     -+index.\n      +\n      +Problems occur if there is an extreme imbalance in these dimensions. For\n      +example, if `HEAD` contains millions of paths but the populated set has\n     @@ Documentation/technical/sparse-index.txt (new)\n      +At time of writing, sparse-directory entries violate expectations about the\n      +index format and its in-memory data structure. There are many consumers in\n      +the codebase that expect to iterate through all of the index entries and\n     -+see only files. In addition, they expect to see all files at `HEAD`. One\n     -+way to handle this is to parse trees to replace a sparse-directory entry\n     -+with all of the files within that tree as the index is loaded. However,\n     -+parsing trees is slower than parsing the index format, so that is a slower\n     -+operation than if we left the index alone.\n     ++see only files. In fact, these loops expect to see a reference to every\n     ++staged file. One way to handle this is to parse trees to replace a\n     ++sparse-directory entry with all of the files within that tree as the index\n     ++is loaded. However, parsing trees is slower than parsing the index format,\n     ++so that is a slower operation than if we left the index alone. The plan is\n     ++to make all of these integrations \"sparse aware\" so this expansion through\n     ++tree parsing is unnecessary and they use fewer resources than when using a\n     ++full index.\n      +\n      +The implementation plan below follows four phases to slowly integrate with\n      +the sparse-index. The intention is to incrementally update Git commands to\n     @@ Documentation/technical/sparse-index.txt (new)\n      +data structure can operate with its current assumption of every file at\n      +`HEAD`.\n      +\n     -+At first, every index parse will expand the sparse-directory entries into\n     -+the full list of paths at `HEAD`. This will be slower in all cases. The\n     -+only noticable change in behavior will be that the serialized index file\n     -+contains sparse-directory entries.\n     ++At first, every index parse will call a helper method,\n     ++`ensure_full_index()`, which scans the index for sparse-directory entries\n     ++(pointing to trees) and replaces them with the full list of paths (with\n     ++blob contents) by parsing tree objects. This will be slower in all cases.\n     ++The only noticeable change in behavior will be that the serialized index\n     ++file contains sparse-directory entries.\n      +\n      +To start, we use a new repository extension, `extensions.sparseIndex`, to\n      +allow inserting sparse-directory entries into indexes with file format\n  2:  d2197e895e4d !  2:  7eabc1d0586c t/perf: add performance test for sparse operations\n     @@ t/perf/p2000-sparse-operations.sh (new)\n      +\n      +test_expect_success 'setup repo and indexes' '\n      +\tgit reset --hard HEAD &&\n     ++\n      +\t# Remove submodules from the example repo, because our\n     -+\t# duplication of the entire repo creates an unlikly data shape.\n     -+\tgit config --file .gitmodules --get-regexp \"submodule.*.path\" >modules &&\n     -+\tgit rm -f .gitmodules &&\n     -+\tfor module in $(awk \"{print \\$2}\" modules)\n     -+\tdo\n     -+\t\tgit rm $module || return 1\n     -+\tdone &&\n     -+\tgit commit -m \"remove submodules\" &&\n     ++\t# duplication of the entire repo creates an unlikely data shape.\n     ++\tif git config --file .gitmodules --get-regexp \"submodule.*.path\" >modules\n     ++\tthen\n     ++\t\tgit rm $(awk \"{print \\$2}\" modules) &&\n     ++\t\tgit commit -m \"remove submodules\" || return 1\n     ++\tfi &&\n      +\n      +\techo bogus >a &&\n      +\tcp a b &&\n  3:  d3cfd34b8418 !  3:  c9e21d78ecba t1092: clean up script quoting\n     @@ Commit message\n          t1092: clean up script quoting\n      \n          This test was introduced in 19a0acc83e4 (t1092: test interesting\n     -    sparse-checkout scenarios, 2021-01-23), but these issues with quoting\n     -    were not noticed until starting this follow-up series. The old mechanism\n     -    would drop quoting such as in\n     +    sparse-checkout scenarios, 2021-01-23), but it contains issues with quoting\n     +    that were not noticed until starting this follow-up series. The old\n     +    mechanism would drop quoting such as in\n      \n             test_all_match git commit -m \"touch README.md\"\n      \n  4:  4472118cf903 =  4:  03cdde756563 sparse-index: add guard to ensure full index\n  5:  99292cdbaae4 =  5:  6b3b6d86385d sparse-index: implement ensure_full_index()\n  6:  fae5663a17bb =  6:  7f67adba0498 t1092: compare sparse-checkout to sparse-index\n  7:  dffe8821fde2 !  7:  7ebd9570b1ad test-read-cache: print cache entries with --table\n     @@ Commit message\n          a sparse-index. Further, 'git ls-tree' does not use a trailing directory\n          separator for its tree rows.\n      \n     -    This does not print the stat() information for the blobs. That could be\n     +    This does not print the stat() information for the blobs. That will be\n          added in a future change with another option. The tests that are added\n          in the next few changes care only about the object types and IDs.\n     +    However, this future need for full index information justifies the need\n     +    for this test helper over extending a user-facing feature, such as 'git\n     +    ls-files'.\n      \n          To make the option parsing slightly more robust, wrap the string\n          comparisons in a loop adapted from test-dir-iterator.c.\n  8:  f4ad081f25bb =  8:  db7bbd06dbcc test-tool: don't force full index\n  9:  4780076a50df =  9:  3ddd5e794b5e unpack-trees: ensure full index\n 10:  33fdba2b8cfd = 10:  7308c87697f1 sparse-checkout: hold pattern list in index\n 11:  e41b14e03ebb ! 11:  7c10d653ca6b sparse-index: convert from full to sparse\n     @@ cache.h: static inline unsigned int create_ce_mode(unsigned int mode)\n       {\n       \tif (S_ISLNK(mode))\n       \t\treturn S_IFLNK;\n     -+\tif (mode == S_IFDIR)\n     ++\tif (S_ISSPARSEDIR(mode))\n      +\t\treturn S_IFDIR;\n       \tif (S_ISDIR(mode) || S_ISGITLINK(mode))\n       \t\treturn S_IFGITLINK;\n 12:  b77cd6b02265 = 12:  6db36f33e960 submodule: sparse-index should not collapse links\n 13:  4000c5cdd4cf ! 13:  d24bd3348d98 unpack-trees: allow sparse directories\n     @@ unpack-trees.c: static int index_pos_by_traverse_info(struct name_entry *names,\n      +\t\tif (!o->src_index->sparse_index ||\n      +\t\t    !(o->src_index->cache[pos]->ce_flags & CE_SKIP_WORKTREE))\n      +\t\t\tBUG(\"This is a directory and should not exist in index\");\n     -+\t} else\n     ++\t} else {\n      +\t\tpos = -pos - 1;\n     ++\t}\n       \tif (pos >= o->src_index->cache_nr ||\n       \t    !starts_with(o->src_index->cache[pos]->name, name.buf) ||\n       \t    (pos > 0 && starts_with(o->src_index->cache[pos-1]->name, name.buf)))\n 14:  1a2be38b2ca7 = 14:  08d9f5f3c0d1 sparse-index: check index conversion happens\n 15:  f89891b0ae4e = 15:  6f38cef196b0 sparse-index: create extension for compatibility\n 16:  bd703c76c859 = 16:  923081e7e079 sparse-checkout: toggle sparse index from builtin\n 17:  598557f90a2a = 17:  6f1ad72c390d sparse-checkout: disable sparse-index\n 18:  c2d0c17db31a = 18:  bd94e6b7d089 cache-tree: integrate with sparse directory entries\n 19:  6fdd9323c14e = 19:  e7190376b806 sparse-index: loose integration with cache_tree_verify()\n 20:  3db06ac46dd5 = 20:  bcf0a58eb38c p2000: add sparse-index repos\n\n-- \ngitgitgadget\n"},{"id":"420010","messageId":"7eabc1d0586cfd2d6526b5b6e40b7a42f2495e06.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 02/20] t/perf: add performance test for sparse operations","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:10Z","receivedAt":"2021-03-23T13:45:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nCreate a test script that takes the default performance test (the Git\ncodebase) and multiplies it by 256 using four layers of duplicated\ntrees of width four. This results in nearly one million blob entries in\nthe index. Then, we can clone this repository with sparse-checkout\npatterns that demonstrate four copies of the initial repository. Each\nclone will use a different index format or mode so peformance can be\ntested across the different options.\n\nNote that the initial repo is stripped of submodules before doing the\ncopies. This preserves the expected data shape of the sparse index,\nbecause directories containing submodules are not collapsed to a sparse\ndirectory entry.\n\nRun a few Git commands on these clones, especially those that use the\nindex (status, add, commit).\n\nHere are the results on my Linux machine:\n\nTest\n--------------------------------------------------------------\n2000.2: git status (full-index-v3)             0.37(0.30+0.09)\n2000.3: git status (full-index-v4)             0.39(0.32+0.10)\n2000.4: git add -A (full-index-v3)             1.42(1.06+0.20)\n2000.5: git add -A (full-index-v4)             1.26(0.98+0.16)\n2000.6: git add . (full-index-v3)              1.40(1.04+0.18)\n2000.7: git add . (full-index-v4)              1.26(0.98+0.17)\n2000.8: git commit -a -m A (full-index-v3)     1.42(1.11+0.16)\n2000.9: git commit -a -m A (full-index-v4)     1.33(1.08+0.16)\n\nIt is perhaps noteworthy that there is an improvement when using index\nversion 4. This is because the v3 index uses 108 MiB while the v4\nindex uses 80 MiB. Since the repeated portions of the directories are\nvery short (f3/f1/f2, for example) this ratio is less pronounced than in\nsimilarly-sized real repositories.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/perf/p2000-sparse-operations.sh | 84 +++++++++++++++++++++++++++++++\n 1 file changed, 84 insertions(+)\n create mode 100755 t/perf/p2000-sparse-operations.sh\n\ndiff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh\nnew file mode 100755\nindex 000000000000..dddd527b6330\n--- /dev/null\n+++ b/t/perf/p2000-sparse-operations.sh\n@@ -0,0 +1,84 @@\n+#!/bin/sh\n+\n+test_description=\"test performance of Git operations using the index\"\n+\n+. ./perf-lib.sh\n+\n+test_perf_default_repo\n+\n+SPARSE_CONE=f2/f4/f1\n+\n+test_expect_success 'setup repo and indexes' '\n+\tgit reset --hard HEAD &&\n+\n+\t# Remove submodules from the example repo, because our\n+\t# duplication of the entire repo creates an unlikely data shape.\n+\tif git config --file .gitmodules --get-regexp \"submodule.*.path\" >modules\n+\tthen\n+\t\tgit rm $(awk \"{print \\$2}\" modules) &&\n+\t\tgit commit -m \"remove submodules\" || return 1\n+\tfi &&\n+\n+\techo bogus >a &&\n+\tcp a b &&\n+\tgit add a b &&\n+\tgit commit -m \"level 0\" &&\n+\tBLOB=$(git rev-parse HEAD:a) &&\n+\tOLD_COMMIT=$(git rev-parse HEAD) &&\n+\tOLD_TREE=$(git rev-parse HEAD^{tree}) &&\n+\n+\tfor i in $(test_seq 1 4)\n+\tdo\n+\t\tcat >in <<-EOF &&\n+\t\t\t100755 blob $BLOB\ta\n+\t\t\t040000 tree $OLD_TREE\tf1\n+\t\t\t040000 tree $OLD_TREE\tf2\n+\t\t\t040000 tree $OLD_TREE\tf3\n+\t\t\t040000 tree $OLD_TREE\tf4\n+\t\tEOF\n+\t\tNEW_TREE=$(git mktree <in) &&\n+\t\tNEW_COMMIT=$(git commit-tree $NEW_TREE -p $OLD_COMMIT -m \"level $i\") &&\n+\t\tOLD_TREE=$NEW_TREE &&\n+\t\tOLD_COMMIT=$NEW_COMMIT || return 1\n+\tdone &&\n+\n+\tgit sparse-checkout init --cone &&\n+\tgit branch -f wide $OLD_COMMIT &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v3 &&\n+\t(\n+\t\tcd full-index-v3 &&\n+\t\tgit sparse-checkout init --cone &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 3 &&\n+\t\tgit update-index --index-version=3\n+\t) &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v4 &&\n+\t(\n+\t\tcd full-index-v4 &&\n+\t\tgit sparse-checkout init --cone &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 4 &&\n+\t\tgit update-index --index-version=4\n+\t)\n+'\n+\n+test_perf_on_all () {\n+\tcommand=\"$@\"\n+\tfor repo in full-index-v3 full-index-v4\n+\tdo\n+\t\ttest_perf \"$command ($repo)\" \"\n+\t\t\t(\n+\t\t\t\tcd $repo &&\n+\t\t\t\techo >>$SPARSE_CONE/a &&\n+\t\t\t\t$command\n+\t\t\t)\n+\t\t\"\n+\tdone\n+}\n+\n+test_perf_on_all git status\n+test_perf_on_all git add -A\n+test_perf_on_all git add .\n+test_perf_on_all git commit -a -m A\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"420011","messageId":"7308c87697f179c06a6dc1abd85b64230060bc25.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 10/20] sparse-checkout: hold pattern list in index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:18Z","receivedAt":"2021-03-23T13:45:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nAs we modify the sparse-checkout definition, we perform index operations\non a pattern_list that only exists in-memory. This allows easy backing\nout in case the index update fails.\n\nHowever, if the index write itself cares about the sparse-checkout\npattern set, we need access to that in-memory copy. Place a pointer to\na 'struct pattern_list' in the index so we can access this on-demand.\nThis will be used in the next change which uses the sparse-checkout\ndefinition to filter out directories that are outside the sparse cone.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/sparse-checkout.c | 17 ++++++++++-------\n cache.h                   |  2 ++\n 2 files changed, 12 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex 2306a9ad98e0..e00b82af727b 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -110,6 +110,8 @@ static int update_working_directory(struct pattern_list *pl)\n \tif (is_index_unborn(r->index))\n \t\treturn UPDATE_SPARSITY_SUCCESS;\n \n+\tr->index->sparse_checkout_patterns = pl;\n+\n \tmemset(&o, 0, sizeof(o));\n \to.verbose_update = isatty(2);\n \to.update = 1;\n@@ -138,6 +140,7 @@ static int update_working_directory(struct pattern_list *pl)\n \telse\n \t\trollback_lock_file(&lock_file);\n \n+\tr->index->sparse_checkout_patterns = NULL;\n \treturn result;\n }\n \n@@ -517,19 +520,18 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)\n {\n \tint result;\n \tint changed_config = 0;\n-\tstruct pattern_list pl;\n-\tmemset(&pl, 0, sizeof(pl));\n+\tstruct pattern_list *pl = xcalloc(1, sizeof(*pl));\n \n \tswitch (m) {\n \tcase ADD:\n \t\tif (core_sparse_checkout_cone)\n-\t\t\tadd_patterns_cone_mode(argc, argv, &pl);\n+\t\t\tadd_patterns_cone_mode(argc, argv, pl);\n \t\telse\n-\t\t\tadd_patterns_literal(argc, argv, &pl);\n+\t\t\tadd_patterns_literal(argc, argv, pl);\n \t\tbreak;\n \n \tcase REPLACE:\n-\t\tadd_patterns_from_input(&pl, argc, argv);\n+\t\tadd_patterns_from_input(pl, argc, argv);\n \t\tbreak;\n \t}\n \n@@ -539,12 +541,13 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)\n \t\tchanged_config = 1;\n \t}\n \n-\tresult = write_patterns_and_update(&pl);\n+\tresult = write_patterns_and_update(pl);\n \n \tif (result && changed_config)\n \t\tset_config(MODE_NO_PATTERNS);\n \n-\tclear_pattern_list(&pl);\n+\tclear_pattern_list(pl);\n+\tfree(pl);\n \treturn result;\n }\n \ndiff --git a/cache.h b/cache.h\nindex 136dd496c95d..8c4464420d0a 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -307,6 +307,7 @@ static inline unsigned int canon_mode(unsigned int mode)\n struct split_index;\n struct untracked_cache;\n struct progress;\n+struct pattern_list;\n \n struct index_state {\n \tstruct cache_entry **cache;\n@@ -338,6 +339,7 @@ struct index_state {\n \tstruct mem_pool *ce_mem_pool;\n \tstruct progress *progress;\n \tstruct repository *repo;\n+\tstruct pattern_list *sparse_checkout_patterns;\n };\n \n /* Name hashing */\n-- \ngitgitgadget\n\n"},{"id":"420020","messageId":"7ebd9570b1ad81720569a770526651c62c152b9f.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 07/20] test-read-cache: print cache entries with --table","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:15Z","receivedAt":"2021-03-23T13:45:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThis table is helpful for discovering data in the index to ensure it is\nbeing written correctly, especially as we build and test the\nsparse-index. This table includes an output format similar to 'git\nls-tree', but should not be compared to that directly. The biggest\nreasons are that 'git ls-tree' includes a tree entry for every\nsubdirectory, even those that would not appear as a sparse directory in\na sparse-index. Further, 'git ls-tree' does not use a trailing directory\nseparator for its tree rows.\n\nThis does not print the stat() information for the blobs. That will be\nadded in a future change with another option. The tests that are added\nin the next few changes care only about the object types and IDs.\nHowever, this future need for full index information justifies the need\nfor this test helper over extending a user-facing feature, such as 'git\nls-files'.\n\nTo make the option parsing slightly more robust, wrap the string\ncomparisons in a loop adapted from test-dir-iterator.c.\n\nCare must be taken with the final check for the 'cnt' variable. We\ncontinue the expectation that the numerical value is the final argument.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/helper/test-read-cache.c | 55 +++++++++++++++++++++++++++++++-------\n 1 file changed, 45 insertions(+), 10 deletions(-)\n\ndiff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c\nindex 244977a29bdf..6cfd8f2de71c 100644\n--- a/t/helper/test-read-cache.c\n+++ b/t/helper/test-read-cache.c\n@@ -1,36 +1,71 @@\n #include \"test-tool.h\"\n #include \"cache.h\"\n #include \"config.h\"\n+#include \"blob.h\"\n+#include \"commit.h\"\n+#include \"tree.h\"\n+\n+static void print_cache_entry(struct cache_entry *ce)\n+{\n+\tconst char *type;\n+\tprintf(\"%06o \", ce->ce_mode & 0177777);\n+\n+\tif (S_ISSPARSEDIR(ce->ce_mode))\n+\t\ttype = tree_type;\n+\telse if (S_ISGITLINK(ce->ce_mode))\n+\t\ttype = commit_type;\n+\telse\n+\t\ttype = blob_type;\n+\n+\tprintf(\"%s %s\\t%s\\n\",\n+\t       type,\n+\t       oid_to_hex(&ce->oid),\n+\t       ce->name);\n+}\n+\n+static void print_cache(struct index_state *istate)\n+{\n+\tint i;\n+\tfor (i = 0; i < istate->cache_nr; i++)\n+\t\tprint_cache_entry(istate->cache[i]);\n+}\n \n int cmd__read_cache(int argc, const char **argv)\n {\n+\tstruct repository *r = the_repository;\n \tint i, cnt = 1;\n \tconst char *name = NULL;\n+\tint table = 0;\n \n-\tif (argc > 1 && skip_prefix(argv[1], \"--print-and-refresh=\", &name)) {\n-\t\targc--;\n-\t\targv++;\n+\tfor (++argv, --argc; *argv && starts_with(*argv, \"--\"); ++argv, --argc) {\n+\t\tif (skip_prefix(*argv, \"--print-and-refresh=\", &name))\n+\t\t\tcontinue;\n+\t\tif (!strcmp(*argv, \"--table\"))\n+\t\t\ttable = 1;\n \t}\n \n-\tif (argc == 2)\n-\t\tcnt = strtol(argv[1], NULL, 0);\n+\tif (argc == 1)\n+\t\tcnt = strtol(argv[0], NULL, 0);\n \tsetup_git_directory();\n \tgit_config(git_default_config, NULL);\n+\n \tfor (i = 0; i < cnt; i++) {\n-\t\tread_cache();\n+\t\trepo_read_index(r);\n \t\tif (name) {\n \t\t\tint pos;\n \n-\t\t\trefresh_index(&the_index, REFRESH_QUIET,\n+\t\t\trefresh_index(r->index, REFRESH_QUIET,\n \t\t\t\t      NULL, NULL, NULL);\n-\t\t\tpos = index_name_pos(&the_index, name, strlen(name));\n+\t\t\tpos = index_name_pos(r->index, name, strlen(name));\n \t\t\tif (pos < 0)\n \t\t\t\tdie(\"%s not in index\", name);\n \t\t\tprintf(\"%s is%s up to date\\n\", name,\n-\t\t\t       ce_uptodate(the_index.cache[pos]) ? \"\" : \" not\");\n+\t\t\t       ce_uptodate(r->index->cache[pos]) ? \"\" : \" not\");\n \t\t\twrite_file(name, \"%d\\n\", i);\n \t\t}\n-\t\tdiscard_cache();\n+\t\tif (table)\n+\t\t\tprint_cache(r->index);\n+\t\tdiscard_index(r->index);\n \t}\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"420012","messageId":"db7bbd06dbcc8ddab7811887a510b160686e4321.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 08/20] test-tool: don't force full index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:16Z","receivedAt":"2021-03-23T13:45: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\nWe will use 'test-tool read-cache --table' to check that a sparse\nindex is written as part of init_repos. Since we will no longer always\nexpand a sparse index into a full index, add an '--expand' parameter\nthat adds a call to ensure_full_index() so we can compare a sparse index\ndirectly against a full index, or at least what the in-memory index\nlooks like when expanded in this way.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/helper/test-read-cache.c               | 13 ++++++++++++-\n t/t1092-sparse-checkout-compatibility.sh |  5 +++++\n 2 files changed, 17 insertions(+), 1 deletion(-)\n\ndiff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c\nindex 6cfd8f2de71c..b52c174acc7a 100644\n--- a/t/helper/test-read-cache.c\n+++ b/t/helper/test-read-cache.c\n@@ -4,6 +4,7 @@\n #include \"blob.h\"\n #include \"commit.h\"\n #include \"tree.h\"\n+#include \"sparse-index.h\"\n \n static void print_cache_entry(struct cache_entry *ce)\n {\n@@ -35,13 +36,19 @@ int cmd__read_cache(int argc, const char **argv)\n \tstruct repository *r = the_repository;\n \tint i, cnt = 1;\n \tconst char *name = NULL;\n-\tint table = 0;\n+\tint table = 0, expand = 0;\n+\n+\tinitialize_the_repository();\n+\tprepare_repo_settings(r);\n+\tr->settings.command_requires_full_index = 0;\n \n \tfor (++argv, --argc; *argv && starts_with(*argv, \"--\"); ++argv, --argc) {\n \t\tif (skip_prefix(*argv, \"--print-and-refresh=\", &name))\n \t\t\tcontinue;\n \t\tif (!strcmp(*argv, \"--table\"))\n \t\t\ttable = 1;\n+\t\telse if (!strcmp(*argv, \"--expand\"))\n+\t\t\texpand = 1;\n \t}\n \n \tif (argc == 1)\n@@ -51,6 +58,10 @@ int cmd__read_cache(int argc, const char **argv)\n \n \tfor (i = 0; i < cnt; i++) {\n \t\trepo_read_index(r);\n+\n+\t\tif (expand)\n+\t\t\tensure_full_index(r->index);\n+\n \t\tif (name) {\n \t\t\tint pos;\n \ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex de5d8461c993..a1aea141c62c 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -130,6 +130,11 @@ test_sparse_match () {\n \ttest_cmp sparse-checkout-err sparse-index-err\n }\n \n+test_expect_success 'expanded in-memory index matches full index' '\n+\tinit_repos &&\n+\ttest_sparse_match test-tool read-cache --expand --table\n+'\n+\n test_expect_success 'status with options' '\n \tinit_repos &&\n \ttest_all_match git status --porcelain=v2 &&\n-- \ngitgitgadget\n\n"},{"id":"420013","messageId":"3ddd5e794b5edc862b6047328f61cad5e6134c9f.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 09/20] unpack-trees: ensure full index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:17Z","receivedAt":"2021-03-23T13:45: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 next change will translate full indexes into sparse indexes at write\ntime. The existing logic provides a way for every sparse index to be\nexpanded to a full index at read time. However, there are cases where an\nindex is written and then continues to be used in-memory to perform\nfurther updates.\n\nunpack_trees() is frequently called after such a write. In particular,\ncommands like 'git reset' do this double-update of the index.\n\nEnsure that we have a full index when entering unpack_trees(), but only\nwhen command_requires_full_index is true. This is always true at the\nmoment, but we will later relax that after unpack_trees() is updated to\nhandle sparse directory entries.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n unpack-trees.c | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex f5f668f532d8..4dd99219073a 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -1567,6 +1567,7 @@ static int verify_absent(const struct cache_entry *,\n  */\n int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)\n {\n+\tstruct repository *repo = the_repository;\n \tint i, ret;\n \tstatic struct cache_entry *dfc;\n \tstruct pattern_list pl;\n@@ -1578,6 +1579,12 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n \ttrace_performance_enter();\n \ttrace2_region_enter(\"unpack_trees\", \"unpack_trees\", the_repository);\n \n+\tprepare_repo_settings(repo);\n+\tif (repo->settings.command_requires_full_index) {\n+\t\tensure_full_index(o->src_index);\n+\t\tensure_full_index(o->dst_index);\n+\t}\n+\n \tif (!core_apply_sparse_checkout || !o->update)\n \t\to->skip_sparse_checkout = 1;\n \tif (!o->skip_sparse_checkout && !o->pl) {\n-- \ngitgitgadget\n\n"},{"id":"420014","messageId":"6db36f33e960d6bfd4a156efc2e070dd9c23378b.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 12/20] submodule: sparse-index should not collapse links","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:20Z","receivedAt":"2021-03-23T13:45: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\nA submodule is stored as a \"Git link\" that actually points to a commit\nwithin a submodule. Submodules are populated or not depending on\nsubmodule configuration, not sparse-checkout. To ensure that the\nsparse-index feature integrates correctly with submodules, we should not\ncollapse a directory if there is a Git link within its range.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n sparse-index.c                           |  1 +\n t/t1092-sparse-checkout-compatibility.sh | 17 +++++++++++++++++\n 2 files changed, 18 insertions(+)\n\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 619ff7c2e217..7631f7bd00b7 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -52,6 +52,7 @@ static int convert_to_sparse_rec(struct index_state *istate,\n \t\tstruct cache_entry *ce = istate->cache[i];\n \n \t\tif (ce_stage(ce) ||\n+\t\t    S_ISGITLINK(ce->ce_mode) ||\n \t\t    !(ce->ce_flags & CE_SKIP_WORKTREE))\n \t\t\tcan_convert = 0;\n \t}\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 1e888d195122..cba5f89b1e96 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -376,4 +376,21 @@ test_expect_success 'clean' '\n \ttest_sparse_match test_path_is_dir folder1\n '\n \n+test_expect_success 'submodule handling' '\n+\tinit_repos &&\n+\n+\ttest_all_match mkdir modules &&\n+\ttest_all_match touch modules/a &&\n+\ttest_all_match git add modules &&\n+\ttest_all_match git commit -m \"add modules directory\" &&\n+\n+\trun_on_all git submodule add \"$(pwd)/initial-repo\" modules/sub &&\n+\ttest_all_match git commit -m \"add submodule\" &&\n+\n+\t# having a submodule prevents \"modules\" from collapse\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tgrep \"100644 blob .*\tmodules/a\" cache &&\n+\tgrep \"160000 commit $(git -C initial-repo rev-parse HEAD)\tmodules/sub\" cache\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"420015","messageId":"7f67adba0498d9ff481cae31ac10be9fe228d3d1.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 06/20] t1092: compare sparse-checkout to sparse-index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:14Z","receivedAt":"2021-03-23T13:45: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\nAdd a new 'sparse-index' repo alongside the 'full-checkout' and\n'sparse-checkout' repos in t1092-sparse-checkout-compatibility.sh. Also\nadd run_on_sparse and test_sparse_match helpers. These helpers will be\nused when the sparse index is implemented.\n\nAdd the GIT_TEST_SPARSE_INDEX environment variable to enable the\nsparse-index by default. This can be enabled across all tests, but that\nwill only affect cases where the sparse-checkout feature is enabled.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/README                                 |  3 +++\n t/t1092-sparse-checkout-compatibility.sh | 24 ++++++++++++++++++++----\n 2 files changed, 23 insertions(+), 4 deletions(-)\n\ndiff --git a/t/README b/t/README\nindex 593d4a4e270c..b98bc563aab5 100644\n--- a/t/README\n+++ b/t/README\n@@ -439,6 +439,9 @@ and \"sha256\".\n GIT_TEST_WRITE_REV_INDEX=<boolean>, when true enables the\n 'pack.writeReverseIndex' setting.\n \n+GIT_TEST_SPARSE_INDEX=<boolean>, when true enables index writes to use the\n+sparse-index format by default.\n+\n Naming Tests\n ------------\n \ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 3725d3997e70..de5d8461c993 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -7,6 +7,7 @@ test_description='compare full workdir to sparse workdir'\n test_expect_success 'setup' '\n \tgit init initial-repo &&\n \t(\n+\t\tGIT_TEST_SPARSE_INDEX=0 &&\n \t\tcd initial-repo &&\n \t\techo a >a &&\n \t\techo \"after deep\" >e &&\n@@ -87,23 +88,32 @@ init_repos () {\n \n \tcp -r initial-repo sparse-checkout &&\n \tgit -C sparse-checkout reset --hard &&\n-\tgit -C sparse-checkout sparse-checkout init --cone &&\n+\n+\tcp -r initial-repo sparse-index &&\n+\tgit -C sparse-index reset --hard &&\n \n \t# initialize sparse-checkout definitions\n-\tgit -C sparse-checkout sparse-checkout set deep\n+\tgit -C sparse-checkout sparse-checkout init --cone &&\n+\tgit -C sparse-checkout sparse-checkout set deep &&\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout init --cone &&\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep\n }\n \n run_on_sparse () {\n \t(\n \t\tcd sparse-checkout &&\n-\t\t\"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n+\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n+\t) &&\n+\t(\n+\t\tcd sparse-index &&\n+\t\tGIT_TEST_SPARSE_INDEX=1 \"$@\" >../sparse-index-out 2>../sparse-index-err\n \t)\n }\n \n run_on_all () {\n \t(\n \t\tcd full-checkout &&\n-\t\t\"$@\" >../full-checkout-out 2>../full-checkout-err\n+\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../full-checkout-out 2>../full-checkout-err\n \t) &&\n \trun_on_sparse \"$@\"\n }\n@@ -114,6 +124,12 @@ test_all_match () {\n \ttest_cmp full-checkout-err sparse-checkout-err\n }\n \n+test_sparse_match () {\n+\trun_on_sparse \"$@\" &&\n+\ttest_cmp sparse-checkout-out sparse-index-out &&\n+\ttest_cmp sparse-checkout-err sparse-index-err\n+}\n+\n test_expect_success 'status with options' '\n \tinit_repos &&\n \ttest_all_match git status --porcelain=v2 &&\n-- \ngitgitgadget\n\n"},{"id":"420016","messageId":"d24bd3348d98f0e8671424812cd009c0bca659e2.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 13/20] unpack-trees: allow sparse directories","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:21Z","receivedAt":"2021-03-23T13:45: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 index_pos_by_traverse_info() currently throws a BUG() when a\ndirectory entry exists exactly in the index. We need to consider that it\nis possible to have a directory in a sparse index as long as that entry\nis itself marked with the skip-worktree bit.\n\nThe 'pos' variable is assigned a negative value if an exact match is not\nfound. Since a directory name can be an exact match, it is no longer an\nerror to have a nonnegative 'pos' value.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n unpack-trees.c | 10 +++++++---\n 1 file changed, 7 insertions(+), 3 deletions(-)\n\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 4dd99219073a..0b888dab2246 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -746,9 +746,13 @@ static int index_pos_by_traverse_info(struct name_entry *names,\n \tstrbuf_make_traverse_path(&name, info, names->path, names->pathlen);\n \tstrbuf_addch(&name, '/');\n \tpos = index_name_pos(o->src_index, name.buf, name.len);\n-\tif (pos >= 0)\n-\t\tBUG(\"This is a directory and should not exist in index\");\n-\tpos = -pos - 1;\n+\tif (pos >= 0) {\n+\t\tif (!o->src_index->sparse_index ||\n+\t\t    !(o->src_index->cache[pos]->ce_flags & CE_SKIP_WORKTREE))\n+\t\t\tBUG(\"This is a directory and should not exist in index\");\n+\t} else {\n+\t\tpos = -pos - 1;\n+\t}\n \tif (pos >= o->src_index->cache_nr ||\n \t    !starts_with(o->src_index->cache[pos]->name, name.buf) ||\n \t    (pos > 0 && starts_with(o->src_index->cache[pos-1]->name, name.buf)))\n-- \ngitgitgadget\n\n"},{"id":"420017","messageId":"7c10d653ca6b03d10dbff27da459d757386bfe01.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 11/20] sparse-index: convert from full to sparse","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:19Z","receivedAt":"2021-03-23T13:45: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\nIf we have a full index, then we can convert it to a sparse index by\nreplacing directories outside of the sparse cone with sparse directory\nentries. The convert_to_sparse() method does this, when the situation is\nappropriate.\n\nFor now, we avoid converting the index to a sparse index if:\n\n 1. the index is split.\n 2. the index is already sparse.\n 3. sparse-checkout is disabled.\n 4. sparse-checkout does not use cone mode.\n\nFinally, we currently limit the conversion to when the\nGIT_TEST_SPARSE_INDEX environment variable is enabled. A mode using Git\nconfig will be added in a later change.\n\nThe trickiest thing about this conversion is that we might not be able\nto mark a directory as a sparse directory just because it is outside the\nsparse cone. There might be unmerged files within that directory, so we\nneed to look for those. Also, if there is some strange reason why a file\nis not marked with CE_SKIP_WORKTREE, then we should give up on\nconverting that directory. There is still hope that some of its\nsubdirectories might be able to convert to sparse, so we keep looking\ndeeper.\n\nThe conversion process is assisted by the cache-tree extension. This is\ncalculated from the full index if it does not already exist. We then\nabandon the cache-tree as it no longer applies to the newly-sparse\nindex. Thus, this cache-tree will be recalculated in every\nsparse-full-sparse round-trip until we integrate the cache-tree\nextension with the sparse index.\n\nSome Git commands use the index after writing it. For example, 'git add'\nwill update the index, then write it to disk, then read its entries to\nreport information. To keep the in-memory index in a full state after\nwriting, we re-expand it to a full one after the write. This is wasteful\nfor commands that only write the index and do not read from it again,\nbut that is only the case until we make those commands \"sparse aware.\"\n\nWe can compare the behavior of the sparse-index in\nt1092-sparse-checkout-compability.sh by using GIT_TEST_SPARSE_INDEX=1\nwhen operating on the 'sparse-index' repo. We can also compare the two\nsparse repos directly, such as comparing their indexes (when expanded to\nfull in the case of the 'sparse-index' repo). We also verify that the\nindex is actually populated with sparse directory entries.\n\nThe 'checkout and reset (mixed)' test is marked for failure when\ncomparing a sparse repo to a full repo, but we can compare the two\nsparse-checkout cases directly to ensure that we are not changing the\nbehavior when using a sparse index.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache-tree.c                             |   3 +\n cache.h                                  |   2 +\n read-cache.c                             |  26 ++++-\n sparse-index.c                           | 139 +++++++++++++++++++++++\n sparse-index.h                           |   1 +\n t/t1092-sparse-checkout-compatibility.sh |  61 +++++++++-\n 6 files changed, 228 insertions(+), 4 deletions(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 2fb483d3c083..5f07a39e501e 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -6,6 +6,7 @@\n #include \"object-store.h\"\n #include \"replace-object.h\"\n #include \"promisor-remote.h\"\n+#include \"sparse-index.h\"\n \n #ifndef DEBUG_CACHE_TREE\n #define DEBUG_CACHE_TREE 0\n@@ -442,6 +443,8 @@ int cache_tree_update(struct index_state *istate, int flags)\n \tif (i)\n \t\treturn i;\n \n+\tensure_full_index(istate);\n+\n \tif (!istate->cache_tree)\n \t\tistate->cache_tree = cache_tree();\n \ndiff --git a/cache.h b/cache.h\nindex 8c4464420d0a..74b43aaa2bd1 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -251,6 +251,8 @@ static inline unsigned int create_ce_mode(unsigned int mode)\n {\n \tif (S_ISLNK(mode))\n \t\treturn S_IFLNK;\n+\tif (S_ISSPARSEDIR(mode))\n+\t\treturn S_IFDIR;\n \tif (S_ISDIR(mode) || S_ISGITLINK(mode))\n \t\treturn S_IFGITLINK;\n \treturn S_IFREG | ce_permissions(mode);\ndiff --git a/read-cache.c b/read-cache.c\nindex dd3980c12b53..b9c08773466c 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 \"sparse-index.h\"\n \n /* Mask for the name length in ce_flags in the on-disk index */\n \n@@ -1002,8 +1003,14 @@ int verify_path(const char *path, unsigned mode)\n \n \t\t\tc = *path++;\n \t\t\tif ((c == '.' && !verify_dotfile(path, mode)) ||\n-\t\t\t    is_dir_sep(c) || c == '\\0')\n+\t\t\t    is_dir_sep(c))\n \t\t\t\treturn 0;\n+\t\t\t/*\n+\t\t\t * allow terminating directory separators for\n+\t\t\t * sparse directory entries.\n+\t\t\t */\n+\t\t\tif (c == '\\0')\n+\t\t\t\treturn S_ISDIR(mode);\n \t\t} else if (c == '\\\\' && protect_ntfs) {\n \t\t\tif (is_ntfs_dotgit(path))\n \t\t\t\treturn 0;\n@@ -3079,6 +3086,14 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n \t\t\t\t unsigned flags)\n {\n \tint ret;\n+\tint was_full = !istate->sparse_index;\n+\n+\tret = convert_to_sparse(istate);\n+\n+\tif (ret) {\n+\t\twarning(_(\"failed to convert to a sparse-index\"));\n+\t\treturn ret;\n+\t}\n \n \t/*\n \t * TODO trace2: replace \"the_repository\" with the actual repo instance\n@@ -3090,6 +3105,9 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n \ttrace2_region_leave_printf(\"index\", \"do_write_index\", the_repository,\n \t\t\t\t   \"%s\", get_lock_file_path(lock));\n \n+\tif (was_full)\n+\t\tensure_full_index(istate);\n+\n \tif (ret)\n \t\treturn ret;\n \tif (flags & COMMIT_LOCK)\n@@ -3180,9 +3198,10 @@ static int write_shared_index(struct index_state *istate,\n \t\t\t      struct tempfile **temp)\n {\n \tstruct split_index *si = istate->split_index;\n-\tint ret;\n+\tint ret, was_full = !istate->sparse_index;\n \n \tmove_cache_to_base_index(istate);\n+\tconvert_to_sparse(istate);\n \n \ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n \t\t\t\t   the_repository, \"%s\", get_tempfile_path(*temp));\n@@ -3190,6 +3209,9 @@ static int write_shared_index(struct index_state *istate,\n \ttrace2_region_leave_printf(\"index\", \"shared/do_write_index\",\n \t\t\t\t   the_repository, \"%s\", get_tempfile_path(*temp));\n \n+\tif (was_full)\n+\t\tensure_full_index(istate);\n+\n \tif (ret)\n \t\treturn ret;\n \tret = adjust_shared_perm(get_tempfile_path(*temp));\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 7095378a1b28..619ff7c2e217 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -4,6 +4,145 @@\n #include \"tree.h\"\n #include \"pathspec.h\"\n #include \"trace2.h\"\n+#include \"cache-tree.h\"\n+#include \"config.h\"\n+#include \"dir.h\"\n+#include \"fsmonitor.h\"\n+\n+static struct cache_entry *construct_sparse_dir_entry(\n+\t\t\t\tstruct index_state *istate,\n+\t\t\t\tconst char *sparse_dir,\n+\t\t\t\tstruct cache_tree *tree)\n+{\n+\tstruct cache_entry *de;\n+\n+\tde = make_cache_entry(istate, S_IFDIR, &tree->oid, sparse_dir, 0, 0);\n+\n+\tde->ce_flags |= CE_SKIP_WORKTREE;\n+\treturn de;\n+}\n+\n+/*\n+ * Returns the number of entries \"inserted\" into the index.\n+ */\n+static int convert_to_sparse_rec(struct index_state *istate,\n+\t\t\t\t int num_converted,\n+\t\t\t\t int start, int end,\n+\t\t\t\t const char *ct_path, size_t ct_pathlen,\n+\t\t\t\t struct cache_tree *ct)\n+{\n+\tint i, can_convert = 1;\n+\tint start_converted = num_converted;\n+\tenum pattern_match_result match;\n+\tint dtype;\n+\tstruct strbuf child_path = STRBUF_INIT;\n+\tstruct pattern_list *pl = istate->sparse_checkout_patterns;\n+\n+\t/*\n+\t * Is the current path outside of the sparse cone?\n+\t * Then check if the region can be replaced by a sparse\n+\t * directory entry (everything is sparse and merged).\n+\t */\n+\tmatch = path_matches_pattern_list(ct_path, ct_pathlen,\n+\t\t\t\t\t  NULL, &dtype, pl, istate);\n+\tif (match != NOT_MATCHED)\n+\t\tcan_convert = 0;\n+\n+\tfor (i = start; can_convert && i < end; i++) {\n+\t\tstruct cache_entry *ce = istate->cache[i];\n+\n+\t\tif (ce_stage(ce) ||\n+\t\t    !(ce->ce_flags & CE_SKIP_WORKTREE))\n+\t\t\tcan_convert = 0;\n+\t}\n+\n+\tif (can_convert) {\n+\t\tstruct cache_entry *se;\n+\t\tse = construct_sparse_dir_entry(istate, ct_path, ct);\n+\n+\t\tistate->cache[num_converted++] = se;\n+\t\treturn 1;\n+\t}\n+\n+\tfor (i = start; i < end; ) {\n+\t\tint count, span, pos = -1;\n+\t\tconst char *base, *slash;\n+\t\tstruct cache_entry *ce = istate->cache[i];\n+\n+\t\t/*\n+\t\t * Detect if this is a normal entry outside of any subtree\n+\t\t * entry.\n+\t\t */\n+\t\tbase = ce->name + ct_pathlen;\n+\t\tslash = strchr(base, '/');\n+\n+\t\tif (slash)\n+\t\t\tpos = cache_tree_subtree_pos(ct, base, slash - base);\n+\n+\t\tif (pos < 0) {\n+\t\t\tistate->cache[num_converted++] = ce;\n+\t\t\ti++;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tstrbuf_setlen(&child_path, 0);\n+\t\tstrbuf_add(&child_path, ce->name, slash - ce->name + 1);\n+\n+\t\tspan = ct->down[pos]->cache_tree->entry_count;\n+\t\tcount = convert_to_sparse_rec(istate,\n+\t\t\t\t\t      num_converted, i, i + span,\n+\t\t\t\t\t      child_path.buf, child_path.len,\n+\t\t\t\t\t      ct->down[pos]->cache_tree);\n+\t\tnum_converted += count;\n+\t\ti += span;\n+\t}\n+\n+\tstrbuf_release(&child_path);\n+\treturn num_converted - start_converted;\n+}\n+\n+int convert_to_sparse(struct index_state *istate)\n+{\n+\tif (istate->split_index || istate->sparse_index ||\n+\t    !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n+\t\treturn 0;\n+\n+\t/*\n+\t * For now, only create a sparse index with the\n+\t * GIT_TEST_SPARSE_INDEX environment variable. We will relax\n+\t * this once we have a proper way to opt-in (and later still,\n+\t * opt-out).\n+\t */\n+\tif (!git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0))\n+\t\treturn 0;\n+\n+\tif (!istate->sparse_checkout_patterns) {\n+\t\tistate->sparse_checkout_patterns = xcalloc(1, sizeof(struct pattern_list));\n+\t\tif (get_sparse_checkout_patterns(istate->sparse_checkout_patterns) < 0)\n+\t\t\treturn 0;\n+\t}\n+\n+\tif (!istate->sparse_checkout_patterns->use_cone_patterns) {\n+\t\twarning(_(\"attempting to use sparse-index without cone mode\"));\n+\t\treturn -1;\n+\t}\n+\n+\tif (cache_tree_update(istate, 0)) {\n+\t\twarning(_(\"unable to update cache-tree, staying full\"));\n+\t\treturn -1;\n+\t}\n+\n+\tremove_fsmonitor(istate);\n+\n+\ttrace2_region_enter(\"index\", \"convert_to_sparse\", istate->repo);\n+\tistate->cache_nr = convert_to_sparse_rec(istate,\n+\t\t\t\t\t\t 0, 0, istate->cache_nr,\n+\t\t\t\t\t\t \"\", 0, istate->cache_tree);\n+\tistate->drop_cache_tree = 1;\n+\tistate->sparse_index = 1;\n+\ttrace2_region_leave(\"index\", \"convert_to_sparse\", istate->repo);\n+\treturn 0;\n+}\n \n static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n {\ndiff --git a/sparse-index.h b/sparse-index.h\nindex 09a20d036c46..64380e121d80 100644\n--- a/sparse-index.h\n+++ b/sparse-index.h\n@@ -3,5 +3,6 @@\n \n struct index_state;\n void ensure_full_index(struct index_state *istate);\n+int convert_to_sparse(struct index_state *istate);\n \n #endif\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex a1aea141c62c..1e888d195122 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -2,6 +2,11 @@\n \n test_description='compare full workdir to sparse workdir'\n \n+# The verify_cache_tree() check is not sparse-aware (yet).\n+# So, disable the check until that integration is complete.\n+GIT_TEST_CHECK_CACHE_TREE=0\n+GIT_TEST_SPLIT_INDEX=0\n+\n . ./test-lib.sh\n \n test_expect_success 'setup' '\n@@ -121,7 +126,9 @@ run_on_all () {\n test_all_match () {\n \trun_on_all \"$@\" &&\n \ttest_cmp full-checkout-out sparse-checkout-out &&\n-\ttest_cmp full-checkout-err sparse-checkout-err\n+\ttest_cmp full-checkout-out sparse-index-out &&\n+\ttest_cmp full-checkout-err sparse-checkout-err &&\n+\ttest_cmp full-checkout-err sparse-index-err\n }\n \n test_sparse_match () {\n@@ -130,6 +137,38 @@ test_sparse_match () {\n \ttest_cmp sparse-checkout-err sparse-index-err\n }\n \n+test_expect_success 'sparse-index contents' '\n+\tinit_repos &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tfor dir in folder1 folder2 x\n+\tdo\n+\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n+\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n+\t\t\t|| return 1\n+\tdone &&\n+\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set folder1 &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tfor dir in deep folder2 x\n+\tdo\n+\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n+\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n+\t\t\t|| return 1\n+\tdone &&\n+\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep/deeper1 &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tfor dir in deep/deeper2 folder1 folder2 x\n+\tdo\n+\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n+\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n+\t\t\t|| return 1\n+\tdone\n+'\n+\n test_expect_success 'expanded in-memory index matches full index' '\n \tinit_repos &&\n \ttest_sparse_match test-tool read-cache --expand --table\n@@ -137,6 +176,7 @@ test_expect_success 'expanded in-memory index matches full index' '\n \n test_expect_success 'status with options' '\n \tinit_repos &&\n+\ttest_sparse_match ls &&\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git status --porcelain=v2 -z -u &&\n \ttest_all_match git status --porcelain=v2 -uno &&\n@@ -273,6 +313,17 @@ test_expect_failure 'checkout and reset (mixed)' '\n \ttest_all_match git reset update-folder2\n '\n \n+# Ensure that sparse-index behaves identically to\n+# sparse-checkout with a full index.\n+test_expect_success 'checkout and reset (mixed) [sparse]' '\n+\tinit_repos &&\n+\n+\ttest_sparse_match git checkout -b reset-test update-deep &&\n+\ttest_sparse_match git reset deepest &&\n+\ttest_sparse_match git reset update-folder1 &&\n+\ttest_sparse_match git reset update-folder2\n+'\n+\n test_expect_success 'merge' '\n \tinit_repos &&\n \n@@ -309,14 +360,20 @@ test_expect_success 'clean' '\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git clean -f &&\n \ttest_all_match git status --porcelain=v2 &&\n+\ttest_sparse_match ls &&\n+\ttest_sparse_match ls folder1 &&\n \n \ttest_all_match git clean -xf &&\n \ttest_all_match git status --porcelain=v2 &&\n+\ttest_sparse_match ls &&\n+\ttest_sparse_match ls folder1 &&\n \n \ttest_all_match git clean -xdf &&\n \ttest_all_match git status --porcelain=v2 &&\n+\ttest_sparse_match ls &&\n+\ttest_sparse_match ls folder1 &&\n \n-\ttest_path_is_dir sparse-checkout/folder1\n+\ttest_sparse_match test_path_is_dir folder1\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"420018","messageId":"bd94e6b7d089abf720657c7bc116a805f2a2314b.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 18/20] cache-tree: integrate with sparse directory entries","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:26Z","receivedAt":"2021-03-23T13:45: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 cache-tree extension was previously disabled with sparse indexes.\nHowever, the cache-tree is an important performance feature for commands\nlike 'git status' and 'git add'. Integrate it with sparse directory\nentries.\n\nWhen writing a sparse index, completely clear and recalculate the cache\ntree. By starting from scratch, the only integration necessary is to\ncheck if we hit a sparse directory entry and create a leaf of the\ncache-tree that has an entry_count of one and no subtrees.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache-tree.c   | 18 ++++++++++++++++++\n sparse-index.c | 10 +++++++++-\n 2 files changed, 27 insertions(+), 1 deletion(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 5f07a39e501e..950a9615db8f 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -256,6 +256,24 @@ static int update_one(struct cache_tree *it,\n \n \t*skip_count = 0;\n \n+\t/*\n+\t * If the first entry of this region is a sparse directory\n+\t * entry corresponding exactly to 'base', then this cache_tree\n+\t * struct is a \"leaf\" in the data structure, pointing to the\n+\t * tree OID specified in the entry.\n+\t */\n+\tif (entries > 0) {\n+\t\tconst struct cache_entry *ce = cache[0];\n+\n+\t\tif (S_ISSPARSEDIR(ce->ce_mode) &&\n+\t\t    ce->ce_namelen == baselen &&\n+\t\t    !strncmp(ce->name, base, baselen)) {\n+\t\t\tit->entry_count = 1;\n+\t\t\toidcpy(&it->oid, &ce->oid);\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n \tif (0 <= it->entry_count && has_object_file(&it->oid))\n \t\treturn it->entry_count;\n \ndiff --git a/sparse-index.c b/sparse-index.c\nindex 30c1a11fd62d..56313e805d9d 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -180,7 +180,11 @@ int convert_to_sparse(struct index_state *istate)\n \tistate->cache_nr = convert_to_sparse_rec(istate,\n \t\t\t\t\t\t 0, 0, istate->cache_nr,\n \t\t\t\t\t\t \"\", 0, istate->cache_tree);\n-\tistate->drop_cache_tree = 1;\n+\n+\t/* Clear and recompute the cache-tree */\n+\tcache_tree_free(&istate->cache_tree);\n+\tcache_tree_update(istate, 0);\n+\n \tistate->sparse_index = 1;\n \ttrace2_region_leave(\"index\", \"convert_to_sparse\", istate->repo);\n \treturn 0;\n@@ -281,5 +285,9 @@ void ensure_full_index(struct index_state *istate)\n \tstrbuf_release(&base);\n \tfree(full);\n \n+\t/* Clear and recompute the cache-tree */\n+\tcache_tree_free(&istate->cache_tree);\n+\tcache_tree_update(istate, 0);\n+\n \ttrace2_region_leave(\"index\", \"ensure_full_index\", istate->repo);\n }\n-- \ngitgitgadget\n\n"},{"id":"420019","messageId":"6f38cef196b0e9dca92e92399532290f39d8cece.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 15/20] sparse-index: create extension for compatibility","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:23Z","receivedAt":"2021-03-23T13:45: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\nPreviously, we enabled the sparse index format only using\nGIT_TEST_SPARSE_INDEX=1. This is not a feasible direction for users to\nactually select this mode. Further, sparse directory entries are not\nunderstood by the index formats as advertised.\n\nWe _could_ add a new index version that explicitly adds these\ncapabilities, but there are nuances to index formats 2, 3, and 4 that\nare still valuable to select as options. Until we add index format\nversion 5, create a repo extension, \"extensions.sparseIndex\", that\nspecifies that the tool reading this repository must understand sparse\ndirectory entries.\n\nThis change only encodes the extension and enables it when\nGIT_TEST_SPARSE_INDEX=1. Later, we will add a more user-friendly CLI\nmechanism.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/extensions.txt |  8 ++++++\n cache.h                             |  1 +\n repo-settings.c                     |  7 ++++++\n repository.h                        |  3 ++-\n setup.c                             |  3 +++\n sparse-index.c                      | 38 +++++++++++++++++++++++++----\n 6 files changed, 54 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config/extensions.txt b/Documentation/config/extensions.txt\nindex 4e23d73cdcad..c02e09af0046 100644\n--- a/Documentation/config/extensions.txt\n+++ b/Documentation/config/extensions.txt\n@@ -6,3 +6,11 @@ extensions.objectFormat::\n Note that this setting should only be set by linkgit:git-init[1] or\n linkgit:git-clone[1].  Trying to change it after initialization will not\n work and will produce hard-to-diagnose issues.\n+\n+extensions.sparseIndex::\n+\tWhen combined with `core.sparseCheckout=true` and\n+\t`core.sparseCheckoutCone=true`, the index may contain entries\n+\tcorresponding to directories outside of the sparse-checkout\n+\tdefinition in lieu of containing each path under such directories.\n+\tVersions of Git that do not understand this extension do not\n+\texpect directory entries in the index.\ndiff --git a/cache.h b/cache.h\nindex 74b43aaa2bd1..8aede373aeb3 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1059,6 +1059,7 @@ struct repository_format {\n \tint worktree_config;\n \tint is_bare;\n \tint hash_algo;\n+\tint sparse_index;\n \tchar *work_tree;\n \tstruct string_list unknown_extensions;\n \tstruct string_list v1_only_extensions;\ndiff --git a/repo-settings.c b/repo-settings.c\nindex d63569e4041e..9677d50f9238 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -85,4 +85,11 @@ void prepare_repo_settings(struct repository *r)\n \t * removed.\n \t */\n \tr->settings.command_requires_full_index = 1;\n+\n+\t/*\n+\t * Initialize this as off.\n+\t */\n+\tr->settings.sparse_index = 0;\n+\tif (!repo_config_get_bool(r, \"extensions.sparseindex\", &value) && value)\n+\t\tr->settings.sparse_index = 1;\n }\ndiff --git a/repository.h b/repository.h\nindex e06a23015697..a45f7520fd9e 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -42,7 +42,8 @@ struct repo_settings {\n \n \tint core_multi_pack_index;\n \n-\tunsigned command_requires_full_index:1;\n+\tunsigned command_requires_full_index:1,\n+\t\t sparse_index:1;\n };\n \n struct repository {\ndiff --git a/setup.c b/setup.c\nindex c04cd25a30df..cd8394564613 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -500,6 +500,9 @@ static enum extension_result handle_extension(const char *var,\n \t\t\treturn error(\"invalid value for 'extensions.objectformat'\");\n \t\tdata->hash_algo = format;\n \t\treturn EXTENSION_OK;\n+\t} else if (!strcmp(ext, \"sparseindex\")) {\n+\t\tdata->sparse_index = 1;\n+\t\treturn EXTENSION_OK;\n \t}\n \treturn EXTENSION_UNKNOWN;\n }\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 7631f7bd00b7..3a6df66faeab 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -102,19 +102,47 @@ static int convert_to_sparse_rec(struct index_state *istate,\n \treturn num_converted - start_converted;\n }\n \n+static int enable_sparse_index(struct repository *repo)\n+{\n+\tconst char *config_path = repo_git_path(repo, \"config.worktree\");\n+\n+\tif (upgrade_repository_format(1) < 0) {\n+\t\twarning(_(\"unable to upgrade repository format to enable sparse-index\"));\n+\t\treturn -1;\n+\t}\n+\tgit_config_set_in_file_gently(config_path,\n+\t\t\t\t      \"extensions.sparseIndex\",\n+\t\t\t\t      \"true\");\n+\n+\tprepare_repo_settings(repo);\n+\trepo->settings.sparse_index = 1;\n+\treturn 0;\n+}\n+\n int convert_to_sparse(struct index_state *istate)\n {\n \tif (istate->split_index || istate->sparse_index ||\n \t    !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n \t\treturn 0;\n \n+\tif (!istate->repo)\n+\t\tistate->repo = the_repository;\n+\n+\t/*\n+\t * The GIT_TEST_SPARSE_INDEX environment variable triggers the\n+\t * extensions.sparseIndex config variable to be on.\n+\t */\n+\tif (git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0)) {\n+\t\tint err = enable_sparse_index(istate->repo);\n+\t\tif (err < 0)\n+\t\t\treturn err;\n+\t}\n+\n \t/*\n-\t * For now, only create a sparse index with the\n-\t * GIT_TEST_SPARSE_INDEX environment variable. We will relax\n-\t * this once we have a proper way to opt-in (and later still,\n-\t * opt-out).\n+\t * Only convert to sparse if extensions.sparseIndex is set.\n \t */\n-\tif (!git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0))\n+\tprepare_repo_settings(istate->repo);\n+\tif (!istate->repo->settings.sparse_index)\n \t\treturn 0;\n \n \tif (!istate->sparse_checkout_patterns) {\n-- \ngitgitgadget\n\n"},{"id":"420021","messageId":"923081e7e079f72835a8997a9234fa58eb1b37de.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 16/20] sparse-checkout: toggle sparse index from builtin","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:24Z","receivedAt":"2021-03-23T13:45: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 sparse index extension is used to signal that index writes should be\nin sparse mode. This was only updated using GIT_TEST_SPARSE_INDEX=1.\n\nAdd a '--[no-]sparse-index' option to 'git sparse-checkout init' that\nspecifies if the sparse index should be used. It also updates the index\nto use the correct format, either way. Add a warning in the\ndocumentation that the use of a repository extension might reduce\ncompatibility with third-party tools. 'git sparse-checkout init' already\nsets extension.worktreeConfig, which places most sparse-checkout users\noutside of the scope of most third-party tools.\n\nUpdate t1092-sparse-checkout-compatibility.sh to use this CLI instead of\nGIT_TEST_SPARSE_INDEX=1.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-sparse-checkout.txt    | 14 +++++++\n builtin/sparse-checkout.c                | 17 ++++++++-\n sparse-index.c                           | 37 +++++++++++++------\n sparse-index.h                           |  3 ++\n t/t1092-sparse-checkout-compatibility.sh | 47 +++++++++++++-----------\n 5 files changed, 84 insertions(+), 34 deletions(-)\n\ndiff --git a/Documentation/git-sparse-checkout.txt b/Documentation/git-sparse-checkout.txt\nindex a0eeaeb02ee3..2ff66c5a4e41 100644\n--- a/Documentation/git-sparse-checkout.txt\n+++ b/Documentation/git-sparse-checkout.txt\n@@ -45,6 +45,20 @@ To avoid interfering with other worktrees, it first enables the\n When `--cone` is provided, the `core.sparseCheckoutCone` setting is\n also set, allowing for better performance with a limited set of\n patterns (see 'CONE PATTERN SET' below).\n++\n+Use the `--[no-]sparse-index` option to toggle the use of the sparse\n+index format. This reduces the size of the index to be more closely\n+aligned with your sparse-checkout definition. This can have significant\n+performance advantages for commands such as `git status` or `git add`.\n+This feature is still experimental. Some commands might be slower with\n+a sparse index until they are properly integrated with the feature.\n++\n+**WARNING:** Using a sparse index requires modifying the index in a way\n+that is not completely understood by external tools. If you have trouble\n+with this compatibility, then run `git sparse-checkout init --no-sparse-index`\n+to rewrite your index to not be sparse. Older versions of Git will not\n+understand the `sparseIndex` repository extension and may fail to interact\n+with your repository until it is disabled.\n \n 'set'::\n \tWrite a set of patterns to the sparse-checkout file, as given as\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex e00b82af727b..ca63e2c64e95 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -14,6 +14,7 @@\n #include \"unpack-trees.h\"\n #include \"wt-status.h\"\n #include \"quote.h\"\n+#include \"sparse-index.h\"\n \n static const char *empty_base = \"\";\n \n@@ -283,12 +284,13 @@ static int set_config(enum sparse_checkout_mode mode)\n }\n \n static char const * const builtin_sparse_checkout_init_usage[] = {\n-\tN_(\"git sparse-checkout init [--cone]\"),\n+\tN_(\"git sparse-checkout init [--cone] [--[no-]sparse-index]\"),\n \tNULL\n };\n \n static struct sparse_checkout_init_opts {\n \tint cone_mode;\n+\tint sparse_index;\n } init_opts;\n \n static int sparse_checkout_init(int argc, const char **argv)\n@@ -303,11 +305,15 @@ static int sparse_checkout_init(int argc, const char **argv)\n \tstatic struct option builtin_sparse_checkout_init_options[] = {\n \t\tOPT_BOOL(0, \"cone\", &init_opts.cone_mode,\n \t\t\t N_(\"initialize the sparse-checkout in cone mode\")),\n+\t\tOPT_BOOL(0, \"sparse-index\", &init_opts.sparse_index,\n+\t\t\t N_(\"toggle the use of a sparse index\")),\n \t\tOPT_END(),\n \t};\n \n \trepo_read_index(the_repository);\n \n+\tinit_opts.sparse_index = -1;\n+\n \targc = parse_options(argc, argv, NULL,\n \t\t\t     builtin_sparse_checkout_init_options,\n \t\t\t     builtin_sparse_checkout_init_usage, 0);\n@@ -326,6 +332,15 @@ static int sparse_checkout_init(int argc, const char **argv)\n \tsparse_filename = get_sparse_checkout_filename();\n \tres = add_patterns_from_file_to_list(sparse_filename, \"\", 0, &pl, NULL);\n \n+\tif (init_opts.sparse_index >= 0) {\n+\t\tif (set_sparse_index_config(the_repository, init_opts.sparse_index) < 0)\n+\t\t\tdie(_(\"failed to modify sparse-index config\"));\n+\n+\t\t/* force an index rewrite */\n+\t\trepo_read_index(the_repository);\n+\t\tthe_repository->index->updated_workdir = 1;\n+\t}\n+\n \t/* If we already have a sparse-checkout file, use it. */\n \tif (res >= 0) {\n \t\tfree(sparse_filename);\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 3a6df66faeab..30c1a11fd62d 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -104,23 +104,37 @@ static int convert_to_sparse_rec(struct index_state *istate,\n \n static int enable_sparse_index(struct repository *repo)\n {\n-\tconst char *config_path = repo_git_path(repo, \"config.worktree\");\n+\tint res;\n \n \tif (upgrade_repository_format(1) < 0) {\n \t\twarning(_(\"unable to upgrade repository format to enable sparse-index\"));\n \t\treturn -1;\n \t}\n-\tgit_config_set_in_file_gently(config_path,\n-\t\t\t\t      \"extensions.sparseIndex\",\n-\t\t\t\t      \"true\");\n+\tres = git_config_set_gently(\"extensions.sparseindex\", \"true\");\n \n \tprepare_repo_settings(repo);\n \trepo->settings.sparse_index = 1;\n-\treturn 0;\n+\treturn res;\n+}\n+\n+int set_sparse_index_config(struct repository *repo, int enable)\n+{\n+\tint res;\n+\n+\tif (enable)\n+\t\treturn enable_sparse_index(repo);\n+\n+\t/* Don't downgrade repository format, just remove the extension. */\n+\tres = git_config_set_gently(\"extensions.sparseindex\", NULL);\n+\n+\tprepare_repo_settings(repo);\n+\trepo->settings.sparse_index = 0;\n+\treturn res;\n }\n \n int convert_to_sparse(struct index_state *istate)\n {\n+\tint test_env;\n \tif (istate->split_index || istate->sparse_index ||\n \t    !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n \t\treturn 0;\n@@ -129,14 +143,13 @@ int convert_to_sparse(struct index_state *istate)\n \t\tistate->repo = the_repository;\n \n \t/*\n-\t * The GIT_TEST_SPARSE_INDEX environment variable triggers the\n-\t * extensions.sparseIndex config variable to be on.\n+\t * If GIT_TEST_SPARSE_INDEX=1, then trigger extensions.sparseIndex\n+\t * to be fully enabled. If GIT_TEST_SPARSE_INDEX=0 (set explicitly),\n+\t * then purposefully disable the setting.\n \t */\n-\tif (git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0)) {\n-\t\tint err = enable_sparse_index(istate->repo);\n-\t\tif (err < 0)\n-\t\t\treturn err;\n-\t}\n+\ttest_env = git_env_bool(\"GIT_TEST_SPARSE_INDEX\", -1);\n+\tif (test_env >= 0)\n+\t\tset_sparse_index_config(istate->repo, test_env);\n \n \t/*\n \t * Only convert to sparse if extensions.sparseIndex is set.\ndiff --git a/sparse-index.h b/sparse-index.h\nindex 64380e121d80..39dcc859735e 100644\n--- a/sparse-index.h\n+++ b/sparse-index.h\n@@ -5,4 +5,7 @@ struct index_state;\n void ensure_full_index(struct index_state *istate);\n int convert_to_sparse(struct index_state *istate);\n \n+struct repository;\n+int set_sparse_index_config(struct repository *repo, int enable);\n+\n #endif\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 47f983217852..f14dc48924d2 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -6,6 +6,7 @@ test_description='compare full workdir to sparse workdir'\n # So, disable the check until that integration is complete.\n GIT_TEST_CHECK_CACHE_TREE=0\n GIT_TEST_SPLIT_INDEX=0\n+GIT_TEST_SPARSE_INDEX=\n \n . ./test-lib.sh\n \n@@ -100,25 +101,26 @@ init_repos () {\n \t# initialize sparse-checkout definitions\n \tgit -C sparse-checkout sparse-checkout init --cone &&\n \tgit -C sparse-checkout sparse-checkout set deep &&\n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout init --cone &&\n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep\n+\tgit -C sparse-index sparse-checkout init --cone --sparse-index &&\n+\ttest_cmp_config -C sparse-index true extensions.sparseindex &&\n+\tgit -C sparse-index sparse-checkout set deep\n }\n \n run_on_sparse () {\n \t(\n \t\tcd sparse-checkout &&\n-\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n+\t\t\"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n \t) &&\n \t(\n \t\tcd sparse-index &&\n-\t\tGIT_TEST_SPARSE_INDEX=1 \"$@\" >../sparse-index-out 2>../sparse-index-err\n+\t\t\"$@\" >../sparse-index-out 2>../sparse-index-err\n \t)\n }\n \n run_on_all () {\n \t(\n \t\tcd full-checkout &&\n-\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../full-checkout-out 2>../full-checkout-err\n+\t\t\"$@\" >../full-checkout-out 2>../full-checkout-err\n \t) &&\n \trun_on_sparse \"$@\"\n }\n@@ -148,7 +150,7 @@ test_expect_success 'sparse-index contents' '\n \t\t\t|| return 1\n \tdone &&\n \n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set folder1 &&\n+\tgit -C sparse-index sparse-checkout set folder1 &&\n \n \ttest-tool -C sparse-index read-cache --table >cache &&\n \tfor dir in deep folder2 x\n@@ -158,7 +160,7 @@ test_expect_success 'sparse-index contents' '\n \t\t\t|| return 1\n \tdone &&\n \n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep/deeper1 &&\n+\tgit -C sparse-index sparse-checkout set deep/deeper1 &&\n \n \ttest-tool -C sparse-index read-cache --table >cache &&\n \tfor dir in deep/deeper2 folder1 folder2 x\n@@ -166,7 +168,14 @@ test_expect_success 'sparse-index contents' '\n \t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n \t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n \t\t\t|| return 1\n-\tdone\n+\tdone &&\n+\n+\t# Disabling the sparse-index removes tree entries with full ones\n+\tgit -C sparse-index sparse-checkout init --no-sparse-index &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\t! grep \"040000 tree\" cache &&\n+\ttest_sparse_match test-tool read-cache --table\n '\n \n test_expect_success 'expanded in-memory index matches full index' '\n@@ -396,19 +405,15 @@ test_expect_success 'submodule handling' '\n test_expect_success 'sparse-index is expanded and converted back' '\n \tinit_repos &&\n \n-\t(\n-\t\tGIT_TEST_SPARSE_INDEX=1 &&\n-\t\texport GIT_TEST_SPARSE_INDEX &&\n-\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n-\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" reset --hard &&\n-\t\ttest_region index convert_to_sparse trace2.txt &&\n-\t\ttest_region index ensure_full_index trace2.txt &&\n-\n-\t\trm trace2.txt &&\n-\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n-\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" status -uno &&\n-\t\ttest_region index ensure_full_index trace2.txt\n-\t)\n+\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\tgit -C sparse-index -c core.fsmonitor=\"\" reset --hard &&\n+\ttest_region index convert_to_sparse trace2.txt &&\n+\ttest_region index ensure_full_index trace2.txt &&\n+\n+\trm trace2.txt &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\tgit -C sparse-index -c core.fsmonitor=\"\" status -uno &&\n+\ttest_region index ensure_full_index trace2.txt\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"420022","messageId":"08d9f5f3c0d126e29ac19b36c87b0c3f43ecfd4a.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 14/20] sparse-index: check index conversion happens","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:22Z","receivedAt":"2021-03-23T13:45: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\nAdd a test case that uses test_region to ensure that we are truly\nexpanding a sparse index to a full one, then converting back to sparse\nwhen writing the index. As we integrate more Git commands with the\nsparse index, we will convert these commands to check that we do _not_\nconvert the sparse index to a full index and instead stay sparse the\nentire time.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/t1092-sparse-checkout-compatibility.sh | 18 ++++++++++++++++++\n 1 file changed, 18 insertions(+)\n\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex cba5f89b1e96..47f983217852 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -393,4 +393,22 @@ test_expect_success 'submodule handling' '\n \tgrep \"160000 commit $(git -C initial-repo rev-parse HEAD)\tmodules/sub\" cache\n '\n \n+test_expect_success 'sparse-index is expanded and converted back' '\n+\tinit_repos &&\n+\n+\t(\n+\t\tGIT_TEST_SPARSE_INDEX=1 &&\n+\t\texport GIT_TEST_SPARSE_INDEX &&\n+\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" reset --hard &&\n+\t\ttest_region index convert_to_sparse trace2.txt &&\n+\t\ttest_region index ensure_full_index trace2.txt &&\n+\n+\t\trm trace2.txt &&\n+\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" status -uno &&\n+\t\ttest_region index ensure_full_index trace2.txt\n+\t)\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"420023","messageId":"6f1ad72c390dc56f9e4a4d724369a0e1c7ac3a94.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 17/20] sparse-checkout: disable sparse-index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:25Z","receivedAt":"2021-03-23T13:45: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\nWe use 'git sparse-checkout init --cone --sparse-index' to toggle the\nsparse-index feature. It makes sense to also disable it when running\n'git sparse-checkout disable'. This is particularly important because it\nremoves the extensions.sparseIndex config option, allowing other tools\nto use this Git repository again.\n\nThis does mean that 'git sparse-checkout init' will not re-enable the\nsparse-index feature, even if it was previously enabled.\n\nWhile testing this feature, I noticed that the sparse-index was not\nbeing written on the first run, but by a second. This was caught by the\ncall to 'test-tool read-cache --table'. This requires adjusting some\nassignments to core_apply_sparse_checkout and pl.use_cone_patterns in\nthe sparse_checkout_init() logic.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/sparse-checkout.c          | 10 +++++++++-\n t/t1091-sparse-checkout-builtin.sh | 13 +++++++++++++\n 2 files changed, 22 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex ca63e2c64e95..585343fa1972 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -280,6 +280,9 @@ static int set_config(enum sparse_checkout_mode mode)\n \t\t\t\t      \"core.sparseCheckoutCone\",\n \t\t\t\t      mode == MODE_CONE_PATTERNS ? \"true\" : NULL);\n \n+\tif (mode == MODE_NO_PATTERNS)\n+\t\tset_sparse_index_config(the_repository, 0);\n+\n \treturn 0;\n }\n \n@@ -341,10 +344,11 @@ static int sparse_checkout_init(int argc, const char **argv)\n \t\tthe_repository->index->updated_workdir = 1;\n \t}\n \n+\tcore_apply_sparse_checkout = 1;\n+\n \t/* If we already have a sparse-checkout file, use it. */\n \tif (res >= 0) {\n \t\tfree(sparse_filename);\n-\t\tcore_apply_sparse_checkout = 1;\n \t\treturn update_working_directory(NULL);\n \t}\n \n@@ -366,6 +370,7 @@ static int sparse_checkout_init(int argc, const char **argv)\n \tadd_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);\n \tstrbuf_addstr(&pattern, \"!/*/\");\n \tadd_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);\n+\tpl.use_cone_patterns = init_opts.cone_mode;\n \n \treturn write_patterns_and_update(&pl);\n }\n@@ -632,6 +637,9 @@ static int sparse_checkout_disable(int argc, const char **argv)\n \tstrbuf_addstr(&match_all, \"/*\");\n \tadd_pattern(strbuf_detach(&match_all, NULL), empty_base, 0, &pl, 0);\n \n+\tprepare_repo_settings(the_repository);\n+\tthe_repository->settings.sparse_index = 0;\n+\n \tif (update_working_directory(&pl))\n \t\tdie(_(\"error while refreshing working directory\"));\n \ndiff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh\nindex fc64e9ed99f4..ff1ad570a255 100755\n--- a/t/t1091-sparse-checkout-builtin.sh\n+++ b/t/t1091-sparse-checkout-builtin.sh\n@@ -205,6 +205,19 @@ test_expect_success 'sparse-checkout disable' '\n \tcheck_files repo a deep folder1 folder2\n '\n \n+test_expect_success 'sparse-index enabled and disabled' '\n+\tgit -C repo sparse-checkout init --cone --sparse-index &&\n+\ttest_cmp_config -C repo true extensions.sparseIndex &&\n+\ttest-tool -C repo read-cache --table >cache &&\n+\tgrep \" tree \" cache &&\n+\n+\tgit -C repo sparse-checkout disable &&\n+\ttest-tool -C repo read-cache --table >cache &&\n+\t! grep \" tree \" cache &&\n+\tgit -C repo config --list >config &&\n+\t! grep extensions.sparseindex config\n+'\n+\n test_expect_success 'cone mode: init and set' '\n \tgit -C repo sparse-checkout init --cone &&\n \tgit -C repo config --list >config &&\n-- \ngitgitgadget\n\n"},{"id":"420024","messageId":"e7190376b806dacf05a20c9dda921d7e1b223b06.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 19/20] sparse-index: loose integration with cache_tree_verify()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:27Z","receivedAt":"2021-03-23T13:45: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 cache_tree_verify() method is run when GIT_TEST_CHECK_CACHE_TREE\nis enabled, which it is by default in the test suite. The logic must\nbe adjusted for the presence of these directory entries.\n\nFor now, leave the test as a simple check for whether the directory\nentry is sparse. Do not go any further until needed.\n\nThis allows us to re-enable GIT_TEST_CHECK_CACHE_TREE in\nt1092-sparse-checkout-compatibility.sh. Further,\np2000-sparse-operations.sh uses the test suite and hence this is enabled\nfor all tests. We need to integrate with it before we run our\nperformance tests with a sparse-index.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache-tree.c                             | 19 +++++++++++++++++++\n t/t1092-sparse-checkout-compatibility.sh |  3 ---\n 2 files changed, 19 insertions(+), 3 deletions(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 950a9615db8f..11bf1fcae6e1 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -808,6 +808,19 @@ int cache_tree_matches_traversal(struct cache_tree *root,\n \treturn 0;\n }\n \n+static void verify_one_sparse(struct repository *r,\n+\t\t\t      struct index_state *istate,\n+\t\t\t      struct cache_tree *it,\n+\t\t\t      struct strbuf *path,\n+\t\t\t      int pos)\n+{\n+\tstruct cache_entry *ce = istate->cache[pos];\n+\n+\tif (!S_ISSPARSEDIR(ce->ce_mode))\n+\t\tBUG(\"directory '%s' is present in index, but not sparse\",\n+\t\t    path->buf);\n+}\n+\n static void verify_one(struct repository *r,\n \t\t       struct index_state *istate,\n \t\t       struct cache_tree *it,\n@@ -830,6 +843,12 @@ static void verify_one(struct repository *r,\n \n \tif (path->len) {\n \t\tpos = index_name_pos(istate, path->buf, path->len);\n+\n+\t\tif (pos >= 0) {\n+\t\t\tverify_one_sparse(r, istate, it, path, pos);\n+\t\t\treturn;\n+\t\t}\n+\n \t\tpos = -pos - 1;\n \t} else {\n \t\tpos = 0;\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex f14dc48924d2..d97bf9b64527 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -2,9 +2,6 @@\n \n test_description='compare full workdir to sparse workdir'\n \n-# The verify_cache_tree() check is not sparse-aware (yet).\n-# So, disable the check until that integration is complete.\n-GIT_TEST_CHECK_CACHE_TREE=0\n GIT_TEST_SPLIT_INDEX=0\n GIT_TEST_SPARSE_INDEX=\n \n-- \ngitgitgadget\n\n"},{"id":"420025","messageId":"bcf0a58eb38c22c9a3b6fbcc4fabfad572ae9bd6.1616507069.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v4 20/20] p2000: add sparse-index repos","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:44:28Z","receivedAt":"2021-03-23T13:45:56Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\np2000-sparse-operations.sh compares different Git commands in\nrepositories with many files at HEAD but using sparse-checkout to focus\non a small portion of those files.\n\nAdd extra copies of the repository that use the sparse-index format so\nwe can track how that affects the performance of different commands.\n\nAt this point in time, the sparse-index is 100% overhead from the CPU\nfront, and this is measurable in these tests:\n\nTest\n---------------------------------------------------------------\n2000.2: git status (full-index-v3)              0.59(0.51+0.12)\n2000.3: git status (full-index-v4)              0.59(0.52+0.11)\n2000.4: git status (sparse-index-v3)            1.40(1.32+0.12)\n2000.5: git status (sparse-index-v4)            1.41(1.36+0.08)\n2000.6: git add -A (full-index-v3)              2.32(1.97+0.19)\n2000.7: git add -A (full-index-v4)              2.17(1.92+0.14)\n2000.8: git add -A (sparse-index-v3)            2.31(2.21+0.15)\n2000.9: git add -A (sparse-index-v4)            2.30(2.20+0.13)\n2000.10: git add . (full-index-v3)              2.39(2.02+0.20)\n2000.11: git add . (full-index-v4)              2.20(1.94+0.16)\n2000.12: git add . (sparse-index-v3)            2.36(2.27+0.12)\n2000.13: git add . (sparse-index-v4)            2.33(2.21+0.16)\n2000.14: git commit -a -m A (full-index-v3)     2.47(2.12+0.20)\n2000.15: git commit -a -m A (full-index-v4)     2.26(2.00+0.17)\n2000.16: git commit -a -m A (sparse-index-v3)   3.01(2.92+0.16)\n2000.17: git commit -a -m A (sparse-index-v4)   3.01(2.94+0.15)\n\nNote that there is very little difference between the v3 and v4 index\nformats when the sparse-index is enabled. This is primarily due to the\nfact that the relative file sizes are the same, and the command time is\nmostly taken up by parsing tree objects to expand the sparse index into\na full one.\n\nWith the current file layout, the index file sizes are given by this\ntable:\n\n       |  full index | sparse index |\n       +-------------+--------------+\n    v3 |     108 MiB |      1.6 MiB |\n    v4 |      80 MiB |      1.2 MiB |\n\nFuture updates will improve the performance of Git commands when the\nindex is sparse.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/perf/p2000-sparse-operations.sh | 19 ++++++++++++++++++-\n 1 file changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh\nindex dddd527b6330..94513c977489 100755\n--- a/t/perf/p2000-sparse-operations.sh\n+++ b/t/perf/p2000-sparse-operations.sh\n@@ -59,12 +59,29 @@ test_expect_success 'setup repo and indexes' '\n \t\tgit sparse-checkout set $SPARSE_CONE &&\n \t\tgit config index.version 4 &&\n \t\tgit update-index --index-version=4\n+\t) &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v3 &&\n+\t(\n+\t\tcd sparse-index-v3 &&\n+\t\tgit sparse-checkout init --cone --sparse-index &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 3 &&\n+\t\tgit update-index --index-version=3\n+\t) &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v4 &&\n+\t(\n+\t\tcd sparse-index-v4 &&\n+\t\tgit sparse-checkout init --cone --sparse-index &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 4 &&\n+\t\tgit update-index --index-version=4\n \t)\n '\n \n test_perf_on_all () {\n \tcommand=\"$@\"\n-\tfor repo in full-index-v3 full-index-v4\n+\tfor repo in full-index-v3 full-index-v4 \\\n+\t\t    sparse-index-v3 sparse-index-v4\n \tdo\n \t\ttest_perf \"$command ($repo)\" \"\n \t\t\t(\n-- \ngitgitgadget\n"},{"id":"420040","messageId":"CABPp-BFixOyd1oX5L-fn8PkYjn7wUx3npr5uZNZTarh3ap-FPw@mail.gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 00/20] Sparse Index: Design, Format, Tests","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-03-23T16:16:11Z","receivedAt":"2021-03-23T16:17:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Mar 23, 2021 at 6:44 AM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> Here is the first full patch series submission coming out of the\n> sparse-index RFC [1].\n>\n...\n>\n> Updates in V4\n> =============\n>\n>  * Rebased onto the latest copy of ab/read-tree.\n>  * Updated the design document as per Junio's comments.\n>  * Updated the submodule handling in the performance test.\n>  * Followed up on some other review from Ævar, mostly style or commit\n>    message things.\n\nRange-diff looks good to me; my Reviewed-by still holds.\n"},{"id":"420058","messageId":"xmqq8s6d3a39.fsf@gitster.g","threadId":"55195","inReplyTo":"97d57947-dd5f-bb06-cc40-37a23f1db5d9@gmail.com","subject":"Re: [PATCH v3 01/20] sparse-index: design doc and format update","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-23T20:10:18Z","receivedAt":"2021-03-23T20:10:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <stolee@gmail.com> writes:\n\n>>> +Three important scale dimensions for a Git worktree are:\n>> \n>> s/worktree/working tree/; The former is the thing the \"git worktree\"\n>> command deals with.  The latter is relevant even when \"git worktree\"\n>> is not used (the traditional \"git clone and you get a working tree\n>> to work in\").\n>\n> I guess I'm distracted by using SKIP_WORKTREE a lot, but \"working\n> directory\" is more specific and hence better.\n\nSince the user's current working directory can be outside any\nworking tree that is governed by any git repository, \"working\ndirectory\" is a term I try to avoid when describing the directory\nwhere a checkout of a revision lives.\n\nDocumentation/glossary-content.txt is where the suggestion for\n\"working tree\" comes from.\n\n> I could rearrange things here. The important things to note are:\n>\n> 1. Updating index entries is very fast, but adds up at large scale.\n\nThis is the \"checkout to match the index to the tree of HEAD\" part,\nignoring the cost of writing working tree files out?\n\n> 2. It is faster to write a file to disk from Git's object database\n>    than it is to compare a file on disk to the copy in the database,\n>    which is frequently necessary when the mtime on disk doesn't match\n>    the mtime in the index.\n\nTrue.  But of course, not having to do either (i.e. having a fresh\ncached stat info) would be even faster ;-).\n\n>> Also it\n>> is unclear what you mean by \"changing HEAD only require updating the\n>> index\".  Certainly when \"git switch\" flips HEAD from one commit to\n>> another, you'd update the index and update the files in the working\n>> tree (in the Populated part that is in the sparse-checkout cone) to\n>> match, no?\n>\n> This is unclear of me. I was thinking more on the lines of \"git reset\"\n> (soft mode) which updates HEAD without changing the files on disk.\n\nOK, and that is in line with your \"updating index entries is very\nfast (but adds up)\".\n\n> After all of this postulating, I think that the offending sentences\n> are better off deleted. They don't add clarity over what can be\n> inferred by an interested reader.\n\nOK.\n\n> I'm mixing terms incorrectly. I think what I really mean is\n>\n>   In fact, these loops expect to see a reference to every\n>   staged file.\n\nOK.\n\n>  The plan is to make all of these integrations \"sparse aware\" so\n>  this expansion through tree parsing is unnecessary and they use\n>  fewer resources than when using a full index.\n\n;-)\n\n> I meant by \"serialized index file\" is that the file written to disk has\n> the sparse directory entries, but the in-core copy will not (except for\n> a very brief moment in time, during do_read_index()).\n\nNice.  That would probably mean cache-tree extension on-disk can go\naway, because we can populate in-core cache-tree from these entries.\nI've always hated the on-disk encoding of that extension.\n\nOr we are not doing this \"extra tree\" everywhere (i.e. limited only\nto the parts that are marked for \"sparse checkout\")?\n\nThanks.\n"},{"id":"420063","messageId":"7cb99ad8-3853-5670-6b9f-7b4afe3603a2@gmail.com","threadId":"55195","inReplyTo":"xmqq8s6d3a39.fsf@gitster.g","subject":"Re: [PATCH v3 01/20] sparse-index: design doc and format update","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-23T20:42:52Z","receivedAt":"2021-03-23T20:43:43Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/23/2021 4:10 PM, Junio C Hamano wrote:\n> Derrick Stolee <stolee@gmail.com> writes:\n> \n>>>> +Three important scale dimensions for a Git worktree are:\n>>>\n>>> s/worktree/working tree/; The former is the thing the \"git worktree\"\n>>> command deals with.  The latter is relevant even when \"git worktree\"\n>>> is not used (the traditional \"git clone and you get a working tree\n>>> to work in\").\n>>\n>> I guess I'm distracted by using SKIP_WORKTREE a lot, but \"working\n>> directory\" is more specific and hence better.\n> \n> Since the user's current working directory can be outside any\n> working tree that is governed by any git repository, \"working\n> directory\" is a term I try to avoid when describing the directory\n> where a checkout of a revision lives.\n> \n> Documentation/glossary-content.txt is where the suggestion for\n> \"working tree\" comes from.\n\nWhoops. Somehow I read that wrong. Thanks for pointing out my error.\n\n>> I meant by \"serialized index file\" is that the file written to disk has\n>> the sparse directory entries, but the in-core copy will not (except for\n>> a very brief moment in time, during do_read_index()).\n> \n> Nice.  That would probably mean cache-tree extension on-disk can go\n> away, because we can populate in-core cache-tree from these entries.\n> I've always hated the on-disk encoding of that extension.\n> \n> Or we are not doing this \"extra tree\" everywhere (i.e. limited only\n> to the parts that are marked for \"sparse checkout\")?\n\nThe current design is to only have these entries when all paths\nwithin the directory are marked with SKIP_WORKTREE. This pairs\nwith the cache-tree extension, which has these directories as\nnodes, but only consuming one cache entry (for itself).\n\nI haven't considered the idea of inserting trees for other\nreasons. Seems like a valuable experiment.\n\nThanks,\n-Stolee\n"},{"id":"420079","messageId":"87wntxpedt.fsf@evledraar.gmail.com","threadId":"55195","inReplyTo":"CABPp-BFA+6--PkJfVfERF-ak9NGd6J0=Jhzro1nqqJ+X-LH3xg@mail.gmail.com","subject":"Re: [RFC/PATCH 2/5] ls-files: make \"mode\" in show_ce() loop a variable","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-24T00:46:38Z","receivedAt":"2021-03-24T00:48:02Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Mar 17 2021, Elijah Newren wrote:\n\n> On Wed, Mar 17, 2021 at 6:28 AM Ævar Arnfjörð Bjarmason\n> <avarab@gmail.com> wrote:\n>>\n>> In a subsequent commit I'll optionally change the mode in a new sparse\n>> mode, let's do this first to make that change smaller.\n>>\n>> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n>> ---\n>>  builtin/ls-files.c | 10 +++++++++-\n>>  1 file changed, 9 insertions(+), 1 deletion(-)\n>>\n>> diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n>> index eb72d16493..4db75351f2 100644\n>> --- a/builtin/ls-files.c\n>> +++ b/builtin/ls-files.c\n>> @@ -242,9 +242,17 @@ static void show_ce(struct repository *repo, struct dir_struct *dir,\n>>                 if (!show_stage) {\n>>                         fputs(tag, stdout);\n>>                 } else {\n>> +                       unsigned int mode = ce->ce_mode;\n>> +                       if (show_sparse && S_ISSPARSEDIR(mode))\n>> +                               /*\n>> +                                * We could just do & 0177777 all the\n>> +                                * time, just make it clear this is\n>> +                                * for --stage-sparse.\n>> +                                */\n>> +                               mode &= 0177777;\n>\n> I could kind of see referencing the magic constant 0177777 in a test-*\n> source file, but it really needs an explanation when showing up in\n> actual git source code.  At least reference something about how\n> cache.h mentions these are the mode bits, or better yet #define this\n> constant somewhere in cache.h with an explanation.\n>\n> Also, what is --stage-sparse?\n\nA relic from a WIP version of this patch. I ended up just calling it\n--sparse in 3/5.\n\n>>                         printf(\"%s%06o %s %d\\t\",\n>>                                tag,\n>> -                              ce->ce_mode,\n>> +                              mode,\n>>                                find_unique_abbrev(&ce->oid, abbrev),\n>>                                ce_stage(ce));\n>>                 }\n>> --\n>> 2.31.0.260.g719c683c1d\n\n"},{"id":"420080","messageId":"87tup1pe3o.fsf@evledraar.gmail.com","threadId":"55195","inReplyTo":"028fb838-f012-e8c7-eb8b-53c810802243@gmail.com","subject":"Re: [RFC/PATCH 3/5] ls-files: add and use a new --sparse option","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-24T00:52:43Z","receivedAt":"2021-03-24T00:53:47Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Mar 17 2021, Derrick Stolee wrote:\n\n> On 3/17/2021 9:28 AM, Ævar Arnfjörð Bjarmason wrote:\n>> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>> diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\n>\n> I want to learn from your suggested changes to the test, here,\n> so forgive my questions here:\n>   \n>> +test_index_entry_like () {\n>> +\tdir=$1\n>> +\tshift\n>> +\tfmt=$1\n>> +\tshift\n>> +\trev=$1\n>> +\tshift\n>> +\tentry=$1\n>> +\tshift\n>> +\tfile=$1\n>> +\tshift\n>\n> Why all the shifts? Why not just use $1, $2, $3,...? My\n> guess is that you want to be able to insert a new parameter\n> in the middle in the future without changing the later\n> numbers, but that seems unlikely, and we could just add\n> the parameter at the end.\n\nIt's just crappy RFC-quality code. I probably copied some other function\nand went with it. No good reason. Yeah it's ugly.\n\n>> +\thash=$(git -C \"$dir\" rev-parse \"$rev\") &&\n>> +\tprintf \"$fmt\\n\" \"$hash\" \"$entry\" >expected &&\n>> +\tif grep \"$entry\" \"$file\" >line\n>> +\tthen\n>> +\t\ttest_cmp expected line\n>> +\telse\n>> +\t\tcat cache &&\n>> +\t\tfalse\n>> +\tfi\n>> +}\n>> +\n>>  test_expect_success 'sparse-index contents' '\n>>  \tinit_repos &&\n>>  \n>> -\ttest-tool -C sparse-index read-cache --table >cache &&\n>> +\tgit -C sparse-index ls-files --sparse >cache &&\n>>  \tfor dir in folder1 folder2 x\n>>  \tdo\n>> -\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n>> -\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n>> -\t\t\t|| return 1\n>> +\t\ttest_index_entry_like sparse-index \"040000 %s 0\\t%s\" \"HEAD:$dir\" \"$dir/\" cache || return 1\n>\n> I see how this uses only one line, but it seems like the\n> test_index_entry_like is too generic to make it not a\n> complicated mess of format strings that need to copy\n> over and over again.\n>\n> Perhaps instead it could be a \"test_entry_is_tree\"\n> and it only passes \"$dir\" and \"cache\"? Then we could drop the loop and\n> just have\n>\n> \ttest_entry_is_tree cache folder1 &&\n> \ttest_entry_is_tree cache folder2 &&\n> \ttest_entry_is_tree cache x &&\n>\n> or we could still use the loop, especially when we test for four trees.\n\nYeah that sounds good. Personally I don't mind 4x similar lines\ncopy/pasted over a for-loop in the tests. You don't need to worry about\nthe || return doing the right thing, and just setting up the for-loop is\nalready 3 lines...\n\n>> -\ttest-tool -C sparse-index read-cache --table >cache &&\n>> +\tgit -C sparse-index ls-files --sparse >cache &&\n>>  \tfor dir in deep/deeper2 folder1 folder2 x\n>>  \tdo\n>> -\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n>> -\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n>> -\t\t\t|| return 1\n>> +\t\ttest_index_entry_like sparse-index \"040000 %s 0\\t%s\" \"HEAD:$dir\" \"$dir/\" cache || return 1\n>>  \tdone &&\n>>  \n>> +\tgrep 040000 cache >lines &&\n>> +\ttest_line_count = 4 lines &&\n>> +\n>\n> The point here is to check that no other entries are trees? We know\n> that this number will be _at least_ 4 based on the loop above.\n\nIt's exactly 4 because we have 4 folders we're checking. But you tell\nme. I was just trying to refactor this dependence on the ls-tree format\nwhile moving it over to ls-files without spending too much time on\nunderstanding all the specifics.\n"},{"id":"420083","messageId":"87r1k5pcmx.fsf@evledraar.gmail.com","threadId":"55195","inReplyTo":"7ebd9570b1ad81720569a770526651c62c152b9f.1616507069.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 07/20] test-read-cache: print cache entries with --table","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-24T01:24:22Z","receivedAt":"2021-03-24T01:25:29Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Mar 23 2021, Derrick Stolee via GitGitGadget wrote:\n\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> This table is helpful for discovering data in the index to ensure it is\n> being written correctly, especially as we build and test the\n> sparse-index. This table includes an output format similar to 'git\n> ls-tree', but should not be compared to that directly. The biggest\n> reasons are that 'git ls-tree' includes a tree entry for every\n> subdirectory, even those that would not appear as a sparse directory in\n> a sparse-index. Further, 'git ls-tree' does not use a trailing directory\n> separator for its tree rows.\n>\n> This does not print the stat() information for the blobs. That will be\n> added in a future change with another option. The tests that are added\n> in the next few changes care only about the object types and IDs.\n> However, this future need for full index information justifies the need\n> for this test helper over extending a user-facing feature, such as 'git\n> ls-files'.\n\nIs that stat() information that's going to be essential to grab in the\nsame process that runs the \"for (i = 0; i < istate->cache_nr; i++)\"\nfor-loop, or stat() information that could be grabbed as:\n\n    git ls-files -z --stage | some-program-that-stats-all-listed-blobs\n\nIt's not so much that I still disagree as I feel like I'm missing\nsomething. I haven't gone through this topic with a fine toothed comb,\nso ...\n\nIf and when these patches land and I'm using this nascent sparse\ncheckout support why wouldn't I want ls-files or another not-a-test-tool\nto support extracting this new information that's in the index?\n\nThat's why I sent the RFC patches at\nhttps://lore.kernel.org/git/20210317132814.30175-2-avarab@gmail.com/ to\nroll this functionality into ls-files.\n\nStill, I think if there's a good reason for why we want this in the\nindex but never want our plumbing to be able to dump it in some\nuser-facing way I think just as a matter of reviewing this code it would\nbe much simpler if it was in ls-files behind some\ngit_env_bool(\"GIT_TEST_...\") flag or something.\n\nOr maybe I'm the only one who spends a lot of time with both ls-files.c\nand test-read-cache.c open while trying to review this trying to keep\ntrack of if and how this helper is and isn't subtly different from\nls-files (as my RFC series shows, not really that different at all...).\nEspecially with the really-just-ls-files-plus-one-thing tool mimicking\nls-tree output, for reasons I still don't get...\n\n> To make the option parsing slightly more robust, wrap the string\n> comparisons in a loop adapted from test-dir-iterator.c.\n>\n> Care must be taken with the final check for the 'cnt' variable. We\n> continue the expectation that the numerical value is the final argument.\n\nI think even if you're set on not having this exposed in some\nbuiltin/*.c command this code would be much clearer based on some\nversion of my\nhttps://lore.kernel.org/git/20210317132814.30175-6-avarab@gmail.com/\ni.e. the part that isn't entirely deleting t/helper/test-read-cache.c,\nwhich would survive as t/helper/test-read-cache-sparse.c or something.\n\nAs that patch shows this code is needlessly convoluted because it's\nserving 3x wildly different in-tree use-cases. I don't see how the very\nsmall amount of de-duplication we're getting is worth the complexity.\n\nAt that point we don't need any care with the cnt variable, because\nwe're not combining the fsmonitor and perf use-cases of reading the\nindex in some loop with the ls-files-alike.\n\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  t/helper/test-read-cache.c | 55 +++++++++++++++++++++++++++++++-------\n>  1 file changed, 45 insertions(+), 10 deletions(-)\n>\n> diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c\n> index 244977a29bdf..6cfd8f2de71c 100644\n> --- a/t/helper/test-read-cache.c\n> +++ b/t/helper/test-read-cache.c\n> @@ -1,36 +1,71 @@\n>  #include \"test-tool.h\"\n>  #include \"cache.h\"\n>  #include \"config.h\"\n> +#include \"blob.h\"\n> +#include \"commit.h\"\n> +#include \"tree.h\"\n> +\n> +static void print_cache_entry(struct cache_entry *ce)\n> +{\n> +\tconst char *type;\n> +\tprintf(\"%06o \", ce->ce_mode & 0177777);\n> +\n> +\tif (S_ISSPARSEDIR(ce->ce_mode))\n> +\t\ttype = tree_type;\n> +\telse if (S_ISGITLINK(ce->ce_mode))\n> +\t\ttype = commit_type;\n> +\telse\n> +\t\ttype = blob_type;\n> +\n> +\tprintf(\"%s %s\\t%s\\n\",\n> +\t       type,\n> +\t       oid_to_hex(&ce->oid),\n> +\t       ce->name);\n> +}\n> +\n> +static void print_cache(struct index_state *istate)\n> +{\n> +\tint i;\n> +\tfor (i = 0; i < istate->cache_nr; i++)\n> +\t\tprint_cache_entry(istate->cache[i]);\n> +}\n>  \n>  int cmd__read_cache(int argc, const char **argv)\n>  {\n> +\tstruct repository *r = the_repository;\n>  \tint i, cnt = 1;\n>  \tconst char *name = NULL;\n> +\tint table = 0;\n>  \n> -\tif (argc > 1 && skip_prefix(argv[1], \"--print-and-refresh=\", &name)) {\n> -\t\targc--;\n> -\t\targv++;\n> +\tfor (++argv, --argc; *argv && starts_with(*argv, \"--\"); ++argv, --argc) {\n> +\t\tif (skip_prefix(*argv, \"--print-and-refresh=\", &name))\n> +\t\t\tcontinue;\n> +\t\tif (!strcmp(*argv, \"--table\"))\n> +\t\t\ttable = 1;\n>  \t}\n>  \n> -\tif (argc == 2)\n> -\t\tcnt = strtol(argv[1], NULL, 0);\n> +\tif (argc == 1)\n> +\t\tcnt = strtol(argv[0], NULL, 0);\n>  \tsetup_git_directory();\n>  \tgit_config(git_default_config, NULL);\n> +\n>  \tfor (i = 0; i < cnt; i++) {\n> -\t\tread_cache();\n> +\t\trepo_read_index(r);\n>  \t\tif (name) {\n>  \t\t\tint pos;\n>  \n> -\t\t\trefresh_index(&the_index, REFRESH_QUIET,\n> +\t\t\trefresh_index(r->index, REFRESH_QUIET,\n>  \t\t\t\t      NULL, NULL, NULL);\n> -\t\t\tpos = index_name_pos(&the_index, name, strlen(name));\n> +\t\t\tpos = index_name_pos(r->index, name, strlen(name));\n>  \t\t\tif (pos < 0)\n>  \t\t\t\tdie(\"%s not in index\", name);\n>  \t\t\tprintf(\"%s is%s up to date\\n\", name,\n> -\t\t\t       ce_uptodate(the_index.cache[pos]) ? \"\" : \" not\");\n> +\t\t\t       ce_uptodate(r->index->cache[pos]) ? \"\" : \" not\");\n>  \t\t\twrite_file(name, \"%d\\n\", i);\n>  \t\t}\n> -\t\tdiscard_cache();\n> +\t\tif (table)\n> +\t\t\tprint_cache(r->index);\n> +\t\tdiscard_index(r->index);\n>  \t}\n>  \treturn 0;\n>  }\n\n"},{"id":"420115","messageId":"ca8a96a4-5897-2484-b195-57e5b3820576@gmail.com","threadId":"55195","inReplyTo":"87r1k5pcmx.fsf@evledraar.gmail.com","subject":"Re: [PATCH v4 07/20] test-read-cache: print cache entries with --table","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-24T12:33:31Z","receivedAt":"2021-03-24T12:34:32Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/23/21 9:24 PM, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Tue, Mar 23 2021, Derrick Stolee via GitGitGadget wrote:\n> \n>> From: Derrick Stolee <dstolee@microsoft.com>\n>>\n>> This table is helpful for discovering data in the index to ensure it is\n>> being written correctly, especially as we build and test the\n>> sparse-index. This table includes an output format similar to 'git\n>> ls-tree', but should not be compared to that directly. The biggest\n>> reasons are that 'git ls-tree' includes a tree entry for every\n>> subdirectory, even those that would not appear as a sparse directory in\n>> a sparse-index. Further, 'git ls-tree' does not use a trailing directory\n>> separator for its tree rows.\n>>\n>> This does not print the stat() information for the blobs. That will be\n>> added in a future change with another option. The tests that are added\n>> in the next few changes care only about the object types and IDs.\n>> However, this future need for full index information justifies the need\n>> for this test helper over extending a user-facing feature, such as 'git\n>> ls-files'.\n> \n> Is that stat() information that's going to be essential to grab in the\n> same process that runs the \"for (i = 0; i < istate->cache_nr; i++)\"\n> for-loop, or stat() information that could be grabbed as:\n> \n>     git ls-files -z --stage | some-program-that-stats-all-listed-blobs\n\nThe point is not to find the stat() data from disk, but to ensure that\nthe stat() data is correctly stored in the index (say, after converting\nan existing index from another format). This pipe strategy does not\nallow for that scenario.\n\n> It's not so much that I still disagree as I feel like I'm missing\n> something. I haven't gone through this topic with a fine toothed comb,\n> so ...\n> \n> If and when these patches land and I'm using this nascent sparse\n> checkout support why wouldn't I want ls-files or another not-a-test-tool\n> to support extracting this new information that's in the index?\n> \n> That's why I sent the RFC patches at\n> https://lore.kernel.org/git/20210317132814.30175-2-avarab@gmail.com/ to\n> roll this functionality into ls-files.\n\nAnd I recommend that you continue to pursue them as an independent\nseries, but I'm not going to incorporate them into this one. I'm\nnot going to distract from this internal data structure with changes\nto user-facing commands until I think it's ready to use. As the design\ndocument describes the plan, I don't expect this to be something I\nwill recommend to users until most of \"Phase 3\" is complete, making\nthe most common Git commands aware of a sparse index. (I expect to\nfast-track a prototype to willing users that covers that functionality\nwhile review continues on the mailing list.)\n\nMaking a change to a builtin is _forever_, and since the only\npurpose right now is to expose the data in a test environment, I\ndon't want to adjust the builtin until either there is a real user\nneed or the feature has otherwise stabilized. If you want to take on\nthat responsibility, then please do.\n\nOtherwise, I will need to eventually handle \"git ls-files\" being\nsparse-aware when eventually removing 'command_requires_full_index',\n(Phase 4) so that would be a good opportunity to adjust the\nexpectations.\n\nThanks,\n-Stolee\n"},{"id":"420175","messageId":"874kgzq4qi.fsf@evledraar.gmail.com","threadId":"55195","inReplyTo":"ca8a96a4-5897-2484-b195-57e5b3820576@gmail.com","subject":"Re: [PATCH v4 07/20] test-read-cache: print cache entries with --table","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-25T03:41:57Z","receivedAt":"2021-03-25T03:43:02Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Mar 24 2021, Derrick Stolee wrote:\n\n> On 3/23/21 9:24 PM, Ævar Arnfjörð Bjarmason wrote:\n>> \n>> On Tue, Mar 23 2021, Derrick Stolee via GitGitGadget wrote:\n>> \n>>> From: Derrick Stolee <dstolee@microsoft.com>\n>>>\n>>> This table is helpful for discovering data in the index to ensure it is\n>>> being written correctly, especially as we build and test the\n>>> sparse-index. This table includes an output format similar to 'git\n>>> ls-tree', but should not be compared to that directly. The biggest\n>>> reasons are that 'git ls-tree' includes a tree entry for every\n>>> subdirectory, even those that would not appear as a sparse directory in\n>>> a sparse-index. Further, 'git ls-tree' does not use a trailing directory\n>>> separator for its tree rows.\n>>>\n>>> This does not print the stat() information for the blobs. That will be\n>>> added in a future change with another option. The tests that are added\n>>> in the next few changes care only about the object types and IDs.\n>>> However, this future need for full index information justifies the need\n>>> for this test helper over extending a user-facing feature, such as 'git\n>>> ls-files'.\n>> \n>> Is that stat() information that's going to be essential to grab in the\n>> same process that runs the \"for (i = 0; i < istate->cache_nr; i++)\"\n>> for-loop, or stat() information that could be grabbed as:\n>> \n>>     git ls-files -z --stage | some-program-that-stats-all-listed-blobs\n>\n> The point is not to find the stat() data from disk, but to ensure that\n> the stat() data is correctly stored in the index (say, after converting\n> an existing index from another format). This pipe strategy does not\n> allow for that scenario.\n\nSo a dump of ce->ce_stat_data, i.e. the same thing ls-files --debug\nprints out now, or...?\n\n>> It's not so much that I still disagree as I feel like I'm missing\n>> something. I haven't gone through this topic with a fine toothed comb,\n>> so ...\n>> \n>> If and when these patches land and I'm using this nascent sparse\n>> checkout support why wouldn't I want ls-files or another not-a-test-tool\n>> to support extracting this new information that's in the index?\n>> \n>> That's why I sent the RFC patches at\n>> https://lore.kernel.org/git/20210317132814.30175-2-avarab@gmail.com/ to\n>> roll this functionality into ls-files.\n>\n> And I recommend that you continue to pursue them as an independent\n> series, but I'm not going to incorporate them into this one. I'm\n> not going to distract from this internal data structure with changes\n> to user-facing commands until I think it's ready to use. As the design\n> document describes the plan, I don't expect this to be something I\n> will recommend to users until most of \"Phase 3\" is complete, making\n> the most common Git commands aware of a sparse index. (I expect to\n> fast-track a prototype to willing users that covers that functionality\n> while review continues on the mailing list.)\n\nThis series is 20 patches. Your current derrickstolee/sparse-index/wip\nis another 36, and from skimming those patches & your design doc those\n56 seem to be partway into Phase I of IV.\n\nSo at the rate things tend to get reviewed / re-rolled & land in git.git\nit seems exceedingly likely that we'll have some part-way implementation\nof this for at least a major release or two. No?\n\nWhich is why I'm suggesting/asking if we shouldn't have something like\nthis debugging helper as part of installed tooling, because people are\ngoing to try it, it's probably going to have bugs and do other weird\nthings, and I'd rather not have to manually build some test-tool to\ndebug some local sparse checkout somewhere.\n\n> Making a change to a builtin is _forever_, and since the only\n> purpose right now is to expose the data in a test environment, I\n> don't want to adjust the builtin until either there is a real user\n> need or the feature has otherwise stabilized. If you want to take on\n> that responsibility, then please do.\n\nThat's just not the case, we have plenty of unstable debug-esque options\nin various built-in commands, in fact ls-files already has a --debug\noption whose docs say:\n\n    This is intended to show as much information as possible for manual\n    inspection; the exact format may change at any time.\n\nIt was added in 84974217151 (ls-files: learn a debugging dump format,\n2010-07-31) and \"just tacks all available data from the cache onto each\nfile's line\" so in a way not adjusting it and using it would be a\nregression, after all this is new data in the cache, so it should print\nit :)\n\nThere's also PARSE_OPT_HIDDEN for other such in-tree use. Whatever the\nsanity/merits of me suggesting that this specific thing be in ls-files\ninstead of a test-helper, it seems far fetched that something like that\nhidden behind a GIT_TEST_* env var (or hidden option, --debug etc.) is\nsomething we'd need to worry about backwards compatibility for.\n\nSo, whatever you think about the merits of including this functionality\nin ls-files I think your stance of this being a no-go for adding to the\nbuiltin is based on a false premise. It's fine to have\nunstable/transitory/debug output in the builtins. We just name &\ndocument them as such.\n\nI also had some feedback in that series and on the earlier iteration\nthat I think is appropriate to be incorporated into a re-roll of this\none, which doesn't have anything to do with the question of whether we\nuse ls-files or the helper in the tests. Such as us showing more stuff\ninto the read-cache.c test-tool v.s. splitting it up making that code\nneedlessly convoluted.\n\nI don't see how recommending that I pursue that as an independent series\nis productive for anyone. So as you re-roll this I should submit another\nseries on top to refactor your in-flight code & tests?\n\nEither my suggestions are just bad, and we shouldn't do them at all, or\nit makes sense to incorporate relevant feedback in re-rolls. I'll let\nother reviewers draw their own conclusions on that.\n\nThat's not a snarky \"I'm right\" b.t.w., I may honestly be full of it on\nthis particular topic.\n\nBut if those suggested changes are worth doing at all, then doing them\nin that way seems like a massive waste of time for everyone involved, or\nmaybe I'm not getting what you're suggesting by pursuing them as an\nindependent series.\n\n> Otherwise, I will need to eventually handle \"git ls-files\" being\n> sparse-aware when eventually removing 'command_requires_full_index',\n> (Phase 4) so that would be a good opportunity to adjust the\n> expectations.\n\nAt which point you'd be adjusting your tests that expect ls-tree format\noutput to using ls-files output, instead of using ls-files-like output\nfrom the beginning?\n\nAt the end of this E-Mail is a patch on top that adds an undocumented\n--debug-sparse in addition to the existing --debug. Running that in the\nmiddle of one of your tests:\n    \n    $ ~/g/git/git ls-files --debug -- a folder1\n    a\n      ctime: 1616641434:474004002\n      mtime: 1616641434:474004002\n      dev: 2306     ino: 28576528\n      uid: 1001     gid: 1001\n      size: 8       flags: 0\n    folder1/a\n      ctime: 0:0\n      mtime: 0:0\n      dev: 0        ino: 0\n      uid: 0        gid: 0\n      size: 0       flags: 40000000\n    $ ~/g/git/git ls-files --debug --debug-sparse -- a folder1\n    a \n      ctime: 1616641434:474004002\n      mtime: 1616641434:474004002\n      dev: 2306     ino: 28576528\n      uid: 1001     gid: 1001\n      size: 8       flags: 0\n    folder1/\n      ctime: 0:0\n      mtime: 0:0\n      dev: 0        ino: 0\n      uid: 0        gid: 0\n      size: 0       flags: 40004000\n    $ ~/g/git/git ls-files --stage -- a folder1\n    100644 e79c5e8f964493290a409888d5413a737e8e5dd5 0       a\n    100644 e79c5e8f964493290a409888d5413a737e8e5dd5 0       folder1/a\n    $ ~/g/git/git ls-files --stage --debug-sparse -- a folder1\n    100644 e79c5e8f964493290a409888d5413a737e8e5dd5 0       a\n    040000 f203181537ff55dcf7896bf8c5b5c35af1514421 0       folder1/\n\nI.e. it gives you everything your helper does and more with a trivial\naddition of a --debug-sparse (which we can later just remove, it's a\ndebug option...).\n\nSee e.g. my recent 15c9649730d (grep/log: remove hidden --debug and\n--grep-debug options, 2021-01-26) which is already in a release, and\nAFAICT nobody has noticed or cared.\n\nI don't know if that's the stat() information you wanted (your WIP\nbranch doesn't have such a change), but presumably it either is the info\nyou want, or ls-files's --debug would want to emit any such such info\nthat's now missing too.\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 13bcc2d8473..e691512d4f8 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -34,6 +34,7 @@ static int show_valid_bit;\n static int show_fsmonitor_bit;\n static int line_terminator = '\\n';\n static int debug_mode;\n+static int debug_sparse_mode;\n static int show_eol;\n static int recurse_submodules;\n static int skipping_duplicates;\n@@ -242,9 +243,17 @@ static void show_ce(struct repository *repo, struct dir_struct *dir,\n \t\tif (!show_stage) {\n \t\t\tfputs(tag, stdout);\n \t\t} else {\n+\t\t\tunsigned int mode = ce->ce_mode;\n+\t\t\tif (debug_sparse_mode && S_ISSPARSEDIR(mode))\n+\t\t\t\t/*\n+\t\t\t\t * We could just do & 0177777 all the\n+\t\t\t\t * time, just make it clear this is\n+\t\t\t\t * for --debug-sparse.\n+\t\t\t\t */\n+\t\t\t\tmode &= 0177777;\n \t\t\tprintf(\"%s%06o %s %d\\t\",\n \t\t\t       tag,\n-\t\t\t       ce->ce_mode,\n+\t\t\t       mode,\n \t\t\t       find_unique_abbrev(&ce->oid, abbrev),\n \t\t\t       ce_stage(ce));\n \t\t}\n@@ -667,6 +676,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t\tN_(\"pretend that paths removed since <tree-ish> are still present\")),\n \t\tOPT__ABBREV(&abbrev),\n \t\tOPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n+\t\tOPT_BOOL(0, \"debug-sparse\", &debug_sparse_mode, N_(\"show sparse debugging data\")),\n \t\tOPT_BOOL(0, \"deduplicate\", &skipping_duplicates,\n \t\t\t N_(\"suppress duplicate entries\")),\n \t\tOPT_END()\n@@ -681,9 +691,6 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\tprefix_len = strlen(prefix);\n \tgit_config(git_default_config, NULL);\n \n-\tif (repo_read_index(the_repository) < 0)\n-\t\tdie(\"index file corrupt\");\n-\n \targc = parse_options(argc, argv, prefix, builtin_ls_files_options,\n \t\t\tls_files_usage, 0);\n \tpl = add_pattern_list(&dir, EXC_CMDL, \"--exclude option\");\n@@ -700,6 +707,10 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\ttag_skip_worktree = \"S \";\n \t\ttag_resolve_undo = \"U \";\n \t}\n+\tif (debug_sparse_mode) {\n+\t\tprepare_repo_settings(the_repository);\n+\t\tthe_repository->settings.command_requires_full_index = 0;\n+\t}\n \tif (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)\n \t\trequire_work_tree = 1;\n \tif (show_unmerged)\n@@ -743,6 +754,12 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\tmax_prefix = common_prefix(&pathspec);\n \tmax_prefix_len = get_common_prefix_len(max_prefix);\n \n+\t/*\n+\t * Read the index after parse options etc. have had a chance\n+\t * to die early.\n+\t */\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(\"index file corrupt\");\n \tprune_index(the_repository->index, max_prefix, max_prefix_len);\n \n \t/* Treat unmatching pathspec elements as errors */\n"},{"id":"420213","messageId":"CABPp-BGRwxn73sntpX8Q5aD-O192RZmOOy_jiU3SQp4-erivtg@mail.gmail.com","threadId":"55195","inReplyTo":"874kgzq4qi.fsf@evledraar.gmail.com","subject":"Re: [PATCH v4 07/20] test-read-cache: print cache entries with --table","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-03-26T00:12:55Z","receivedAt":"2021-03-26T00:14:09Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi,\n\nOn Wed, Mar 24, 2021 at 8:41 PM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> On Wed, Mar 24 2021, Derrick Stolee wrote:\n>\n> > On 3/23/21 9:24 PM, Ævar Arnfjörð Bjarmason wrote:\n> >>\n> >> On Tue, Mar 23 2021, Derrick Stolee via GitGitGadget wrote:\n> >>\n> >>> From: Derrick Stolee <dstolee@microsoft.com>\n> >>>\n...\n> >> It's not so much that I still disagree as I feel like I'm missing\n> >> something. I haven't gone through this topic with a fine toothed comb,\n> >> so ...\n> >>\n> >> If and when these patches land and I'm using this nascent sparse\n> >> checkout support why wouldn't I want ls-files or another not-a-test-tool\n> >> to support extracting this new information that's in the index?\n> >>\n> >> That's why I sent the RFC patches at\n> >> https://lore.kernel.org/git/20210317132814.30175-2-avarab@gmail.com/ to\n> >> roll this functionality into ls-files.\n> >\n> > And I recommend that you continue to pursue them as an independent\n> > series, but I'm not going to incorporate them into this one. I'm\n> > not going to distract from this internal data structure with changes\n> > to user-facing commands until I think it's ready to use. As the design\n> > document describes the plan, I don't expect this to be something I\n> > will recommend to users until most of \"Phase 3\" is complete, making\n> > the most common Git commands aware of a sparse index. (I expect to\n> > fast-track a prototype to willing users that covers that functionality\n> > while review continues on the mailing list.)\n>\n> This series is 20 patches. Your current derrickstolee/sparse-index/wip\n> is another 36, and from skimming those patches & your design doc those\n> 56 seem to be partway into Phase I of IV.\n>\n> So at the rate things tend to get reviewed / re-rolled & land in git.git\n> it seems exceedingly likely that we'll have some part-way implementation\n> of this for at least a major release or two. No?\n>\n> Which is why I'm suggesting/asking if we shouldn't have something like\n> this debugging helper as part of installed tooling, because people are\n> going to try it, it's probably going to have bugs and do other weird\n> things, and I'd rather not have to manually build some test-tool to\n> debug some local sparse checkout somewhere.\n\nI'm curious why you feel it's critical that this particular piece of\ndebugging machinery needs to be prioritized early and exposed; in\nparticular, I'm not sure I follow the \"people are going to try it\"\nassertion.  Are you the one who is going to try it or are you going to\ngive it to your users?  If so, what do you need out of the debugging\ntool?\n\nYou are correct that this will span multiple releases; Stolee already\nsaid he was planning to be working on this for most of 2021.  But just\nbecause pieces of the code exist and are shipped doesn't mean it'll be\nannounced or supported.  For example, the git-2.30 and git-2.31\nrelease notes were completely silent about merge-ort.  It existed in\nboth releases; in fact, the version that ships in git-2.31, could\ntheoretically be used successfully by the vast majority of users for\ntheir daily workflow.  (But it does have known shortcomings and test\nfailures so I definitely did *not* want it to be announced at that\ntime.)\n\n> > Making a change to a builtin is _forever_, and since the only\n> > purpose right now is to expose the data in a test environment, I\n> > don't want to adjust the builtin until either there is a real user\n> > need or the feature has otherwise stabilized. If you want to take on\n> > that responsibility, then please do.\n>\n> That's just not the case, we have plenty of unstable debug-esque options\n> in various built-in commands, in fact ls-files already has a --debug\n> option whose docs say:\n>\n>     This is intended to show as much information as possible for manual\n>     inspection; the exact format may change at any time.\n>\n> It was added in 84974217151 (ls-files: learn a debugging dump format,\n> 2010-07-31) and \"just tacks all available data from the cache onto each\n> file's line\" so in a way not adjusting it and using it would be a\n> regression, after all this is new data in the cache, so it should print\n> it :)\n>\n> There's also PARSE_OPT_HIDDEN for other such in-tree use. Whatever the\n> sanity/merits of me suggesting that this specific thing be in ls-files\n> instead of a test-helper, it seems far fetched that something like that\n> hidden behind a GIT_TEST_* env var (or hidden option, --debug etc.) is\n> something we'd need to worry about backwards compatibility for.\n>\n> So, whatever you think about the merits of including this functionality\n> in ls-files I think your stance of this being a no-go for adding to the\n> builtin is based on a false premise. It's fine to have\n> unstable/transitory/debug output in the builtins. We just name &\n> document them as such.\n>\n> I also had some feedback in that series and on the earlier iteration\n> that I think is appropriate to be incorporated into a re-roll of this\n> one, which doesn't have anything to do with the question of whether we\n> use ls-files or the helper in the tests. Such as us showing more stuff\n> into the read-cache.c test-tool v.s. splitting it up making that code\n> needlessly convoluted.\n\nWell:\n  * you seem to be strongly opposed to test-read-cache.c containing\nthis code (though I don't quite follow why)\n  * Stolee seems to be strongly opposed to modifying\nbuiltin/ls-files.c until he has time to think through how builtins\nshould work.\n\nSo putting it in another test file that looks slightly duplicative of\ntest-read-cache.c might indeed be a good way out of this conundrum.\n:-)\n\n(I'm not opposed to any of the three solutions, I'm mostly chiming in\nhere because I'm worried about possible bubbling frustration; see\nbelow.)\n\n> I don't see how recommending that I pursue that as an independent series\n> is productive for anyone. So as you re-roll this I should submit another\n> series on top to refactor your in-flight code & tests?\n\nYour tone suggests some frustration; I have a suspicion there's some\nlack of understanding or misreading that has occurred (perhaps on my\npart too), and before that misunderstanding morphs into motive\nquestioning, let me see if I might be able to help...\n\nSo far, you have advocated for:\n  A) Moving the checks to ls-files with a permanent new flag (--sparse)\n  B) Duplicating test-read-cache.c (which is admittedly pretty small)\nand then modifying the duplicate to have the new behavior, or\nalternatively:\n  C) Just stating files to get the information\n  D) Creating new debug option(s) to ls-files so that end users can\nuse this in the next few releases before the feature is ready for\nprime time\nYou also mentioned you had read just part of the series.\n\nOption D comes with the problem that it's not at all clear who these\nend-users are, why they want the option, or how we should design it.\nPersonally, I'm totally onboard that ls-files should generally have\nthe ability to show information in the index (e.g. if there are tree\nentries in addition to blob entries, it should be able to show both),\nbut I'm not following the reasoning for why it needs to be there as\npart of the early stages of development of the sparse-index feature\nand who it's supposed to be helping in these next few releases.\n\nThe progression also suggests that Option B might have just been a\nstep along the way and that you were advocating for Option D now.  I\nthink it'd be easy to miss that you still had option B open and\nconsidered it equivalently good to option D (or am I misreading?),\nmuch like you missed how option C wasn't even relevant to the problem\nat hand or option A would have introduced perpetual confusion as a\nmere duplicate of --stage (in the best case scenario, anyway).\nThey're all easy misunderstandings.\n\n> Either my suggestions are just bad, and we shouldn't do them at all, or\n> it makes sense to incorporate relevant feedback in re-rolls. I'll let\n> other reviewers draw their own conclusions on that.\n\nI think that's a bit unfair; Stolee has been incorporating feedback.\nHe even called out fixing up things at your suggestion in v4 of his\nre-roll.\n\n> That's not a snarky \"I'm right\" b.t.w., I may honestly be full of it on\n> this particular topic.\n>\n> But if those suggested changes are worth doing at all, then doing them\n> in that way seems like a massive waste of time for everyone involved, or\n> maybe I'm not getting what you're suggesting by pursuing them as an\n> independent series.\n\nI think you should instead read it as he has no idea why this needs to\nbe exposed in ls-files, who these users are you are asserting will be\nusing it, or how to cater for their needs.  Shouldn't the person who\nimplements this understand those pieces to avoid a massive waste of\ntime?\n\n> > Otherwise, I will need to eventually handle \"git ls-files\" being\n> > sparse-aware when eventually removing 'command_requires_full_index',\n> > (Phase 4) so that would be a good opportunity to adjust the\n> > expectations.\n>\n> At which point you'd be adjusting your tests that expect ls-tree format\n> output to using ls-files output, instead of using ls-files-like output\n> from the beginning?\n\nI don't understand what you're getting at here.  I was the one who\nrequested Stolee make the output look like ls-trees in his original\nRFC series, so if there's a problem with this style of output, I'm to\nblame.  But, what is exactly the problem?  Old-style ls-files output\njust isn't relevant anymore.  ls-tree prints four things: mode, type,\nhash, and filename.  ls-files prints all of those except \"type\".  The\nreason ls-files never included type before was because it was always\n\"blob\".  This series changes that, and adds \"tree\" to the mix.  Once\nyou have different types included in the index, then ls-files has to\nprint all the same fields that ls-tree does...so why not make it look\nsimilar?\n\n> At the end of this E-Mail is a patch on top that adds an undocumented\n> --debug-sparse in addition to the existing --debug. Running that in the\n> middle of one of your tests:\n>\n>     $ ~/g/git/git ls-files --debug -- a folder1\n>     a\n>       ctime: 1616641434:474004002\n>       mtime: 1616641434:474004002\n>       dev: 2306     ino: 28576528\n>       uid: 1001     gid: 1001\n>       size: 8       flags: 0\n>     folder1/a\n>       ctime: 0:0\n>       mtime: 0:0\n>       dev: 0        ino: 0\n>       uid: 0        gid: 0\n>       size: 0       flags: 40000000\n>     $ ~/g/git/git ls-files --debug --debug-sparse -- a folder1\n>     a\n>       ctime: 1616641434:474004002\n>       mtime: 1616641434:474004002\n>       dev: 2306     ino: 28576528\n>       uid: 1001     gid: 1001\n>       size: 8       flags: 0\n>     folder1/\n>       ctime: 0:0\n>       mtime: 0:0\n>       dev: 0        ino: 0\n>       uid: 0        gid: 0\n>       size: 0       flags: 40004000\n>     $ ~/g/git/git ls-files --stage -- a folder1\n>     100644 e79c5e8f964493290a409888d5413a737e8e5dd5 0       a\n>     100644 e79c5e8f964493290a409888d5413a737e8e5dd5 0       folder1/a\n>     $ ~/g/git/git ls-files --stage --debug-sparse -- a folder1\n>     100644 e79c5e8f964493290a409888d5413a737e8e5dd5 0       a\n>     040000 f203181537ff55dcf7896bf8c5b5c35af1514421 0       folder1/\n>\n> I.e. it gives you everything your helper does and more with a trivial\n> addition of a --debug-sparse (which we can later just remove, it's a\n> debug option...).\n>\n> See e.g. my recent 15c9649730d (grep/log: remove hidden --debug and\n> --grep-debug options, 2021-01-26) which is already in a release, and\n> AFAICT nobody has noticed or cared.\n>\n> I don't know if that's the stat() information you wanted (your WIP\n> branch doesn't have such a change), but presumably it either is the info\n> you want, or ls-files's --debug would want to emit any such such info\n> that's now missing too.\n>\n> diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n> index 13bcc2d8473..e691512d4f8 100644\n> --- a/builtin/ls-files.c\n> +++ b/builtin/ls-files.c\n> @@ -34,6 +34,7 @@ static int show_valid_bit;\n>  static int show_fsmonitor_bit;\n>  static int line_terminator = '\\n';\n>  static int debug_mode;\n> +static int debug_sparse_mode;\n>  static int show_eol;\n>  static int recurse_submodules;\n>  static int skipping_duplicates;\n> @@ -242,9 +243,17 @@ static void show_ce(struct repository *repo, struct dir_struct *dir,\n>                 if (!show_stage) {\n>                         fputs(tag, stdout);\n>                 } else {\n> +                       unsigned int mode = ce->ce_mode;\n> +                       if (debug_sparse_mode && S_ISSPARSEDIR(mode))\n> +                               /*\n> +                                * We could just do & 0177777 all the\n> +                                * time, just make it clear this is\n> +                                * for --debug-sparse.\n> +                                */\n> +                               mode &= 0177777;\n>                         printf(\"%s%06o %s %d\\t\",\n>                                tag,\n> -                              ce->ce_mode,\n> +                              mode,\n>                                find_unique_abbrev(&ce->oid, abbrev),\n>                                ce_stage(ce));\n>                 }\n> @@ -667,6 +676,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n>                         N_(\"pretend that paths removed since <tree-ish> are still present\")),\n>                 OPT__ABBREV(&abbrev),\n>                 OPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n> +               OPT_BOOL(0, \"debug-sparse\", &debug_sparse_mode, N_(\"show sparse debugging data\")),\n>                 OPT_BOOL(0, \"deduplicate\", &skipping_duplicates,\n>                          N_(\"suppress duplicate entries\")),\n>                 OPT_END()\n> @@ -681,9 +691,6 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n>                 prefix_len = strlen(prefix);\n>         git_config(git_default_config, NULL);\n>\n> -       if (repo_read_index(the_repository) < 0)\n> -               die(\"index file corrupt\");\n> -\n>         argc = parse_options(argc, argv, prefix, builtin_ls_files_options,\n>                         ls_files_usage, 0);\n>         pl = add_pattern_list(&dir, EXC_CMDL, \"--exclude option\");\n> @@ -700,6 +707,10 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n>                 tag_skip_worktree = \"S \";\n>                 tag_resolve_undo = \"U \";\n>         }\n> +       if (debug_sparse_mode) {\n> +               prepare_repo_settings(the_repository);\n> +               the_repository->settings.command_requires_full_index = 0;\n> +       }\n>         if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)\n>                 require_work_tree = 1;\n>         if (show_unmerged)\n> @@ -743,6 +754,12 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n>                 max_prefix = common_prefix(&pathspec);\n>         max_prefix_len = get_common_prefix_len(max_prefix);\n>\n> +       /*\n> +        * Read the index after parse options etc. have had a chance\n> +        * to die early.\n> +        */\n> +       if (repo_read_index(the_repository) < 0)\n> +               die(\"index file corrupt\");\n>         prune_index(the_repository->index, max_prefix, max_prefix_len);\n>\n>         /* Treat unmatching pathspec elements as errors */\n"},{"id":"420282","messageId":"20210326202940.GC2271@szeder.dev","threadId":"55195","inReplyTo":"6426a5c60e53e30091360c00c61c9123803fe9c1.1616507069.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 01/20] sparse-index: design doc and format update","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2021-03-26T20:29:40Z","receivedAt":"2021-03-26T20:32:24Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Tue, Mar 23, 2021 at 01:44:09PM +0000, Derrick Stolee via GitGitGadget wrote:\n> Currently, the index format is only updated in the presence of\n> extensions.sparseIndex instead of increasing a file format version\n> number. This is temporary, and index v5 is part of the plan for future\n> work in this area.\n\n> diff --git a/Documentation/technical/sparse-index.txt b/Documentation/technical/sparse-index.txt\n> new file mode 100644\n> index 000000000000..62f6dc225a44\n> --- /dev/null\n> +++ b/Documentation/technical/sparse-index.txt\n\n> +To start, we use a new repository extension, `extensions.sparseIndex`, to\n> +allow inserting sparse-directory entries into indexes with file format\n> +versions 2, 3, and 4. This prevents Git versions that do not understand\n> +the sparse-index from operating on one, but it also prevents other\n> +operations that do not use the index at all.\n\nWhy is this not a non-optional index extension?  That would allow\nolder Git versions and other implementations not understanding sparse\ndirectory entries to still perform any operation that doesn't involve\nthe index.  More importantly, that would prevent older Git versions\nand other implementations not understanding repository extensions from\npotentially wreaking havoc when the index contains sparse directory\nentries.  Notably JGit's current version (5.11.0.202103091610-r) does\nstill completely ignore repository extensions, and e.g. happily\nattempts any operations on a SHA256 repository, so it would do the\nsame in the presence of 'extensions.sparseIndex' as well.  JGit does\nrespect non-optional index extensions, see e.g. 87a6bb701a\n(t5310-pack-bitmaps: make JGit tests work with GIT_TEST_SPLIT_INDEX,\n2018-05-10).\n\nThis really should be a non-optional index extension.\n\n> A new format, index v5, will\n> +be introduced that includes sparse-directory entries by default. It might\n> +also introduce other features that have been considered for improving the\n> +index, as well.\n"},{"id":"420330","messageId":"xmqq8s68oxr4.fsf@gitster.g","threadId":"55195","inReplyTo":"20210326202940.GC2271@szeder.dev","subject":"Re: [PATCH v4 01/20] sparse-index: design doc and format update","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-28T01:47:11Z","receivedAt":"2021-03-28T01:48:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"SZEDER Gábor <szeder.dev@gmail.com> writes:\n\n>> +To start, we use a new repository extension, `extensions.sparseIndex`, to\n>> +allow inserting sparse-directory entries into indexes with file format\n>> +versions 2, 3, and 4. This prevents Git versions that do not understand\n>> +the sparse-index from operating on one, but it also prevents other\n>> +operations that do not use the index at all.\n>\n> Why is this not a non-optional index extension?  ...\n> This really should be a non-optional index extension.\n\nYeah, the index extension mechanism was designed with optional and\nrequired kinds because we wanted to support exactly a use case like\nthis one.\n\nThanks for pointing it out.\n"},{"id":"420410","messageId":"87eeg0ng78.fsf@evledraar.gmail.com","threadId":"55195","inReplyTo":"CABPp-BGRwxn73sntpX8Q5aD-O192RZmOOy_jiU3SQp4-erivtg@mail.gmail.com","subject":"Re: [PATCH v4 07/20] test-read-cache: print cache entries with --table","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-28T15:31:53Z","receivedAt":"2021-03-28T15:33:02Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Mar 26 2021, Elijah Newren wrote:\n\n> Hi,\n>\n> On Wed, Mar 24, 2021 at 8:41 PM Ævar Arnfjörð Bjarmason\n> <avarab@gmail.com> wrote:\n>>\n>> On Wed, Mar 24 2021, Derrick Stolee wrote:\n>>\n>> > On 3/23/21 9:24 PM, Ævar Arnfjörð Bjarmason wrote:\n>> >>\n>> >> On Tue, Mar 23 2021, Derrick Stolee via GitGitGadget wrote:\n>> >>\n>> >>> From: Derrick Stolee <dstolee@microsoft.com>\n>> >>>\n> ...\n>> >> It's not so much that I still disagree as I feel like I'm missing\n>> >> something. I haven't gone through this topic with a fine toothed comb,\n>> >> so ...\n>> >>\n>> >> If and when these patches land and I'm using this nascent sparse\n>> >> checkout support why wouldn't I want ls-files or another not-a-test-tool\n>> >> to support extracting this new information that's in the index?\n>> >>\n>> >> That's why I sent the RFC patches at\n>> >> https://lore.kernel.org/git/20210317132814.30175-2-avarab@gmail.com/ to\n>> >> roll this functionality into ls-files.\n>> >\n>> > And I recommend that you continue to pursue them as an independent\n>> > series, but I'm not going to incorporate them into this one. I'm\n>> > not going to distract from this internal data structure with changes\n>> > to user-facing commands until I think it's ready to use. As the design\n>> > document describes the plan, I don't expect this to be something I\n>> > will recommend to users until most of \"Phase 3\" is complete, making\n>> > the most common Git commands aware of a sparse index. (I expect to\n>> > fast-track a prototype to willing users that covers that functionality\n>> > while review continues on the mailing list.)\n>>\n>> This series is 20 patches. Your current derrickstolee/sparse-index/wip\n>> is another 36, and from skimming those patches & your design doc those\n>> 56 seem to be partway into Phase I of IV.\n>>\n>> So at the rate things tend to get reviewed / re-rolled & land in git.git\n>> it seems exceedingly likely that we'll have some part-way implementation\n>> of this for at least a major release or two. No?\n>>\n>> Which is why I'm suggesting/asking if we shouldn't have something like\n>> this debugging helper as part of installed tooling, because people are\n>> going to try it, it's probably going to have bugs and do other weird\n>> things, and I'd rather not have to manually build some test-tool to\n>> debug some local sparse checkout somewhere.\n>\n> I'm curious why you feel it's critical that this particular piece of\n> debugging machinery needs to be prioritized early and exposed; in\n> particular, I'm not sure I follow the \"people are going to try it\"\n> assertion.\n\nThe debugging machinery's already there, the question is why we have a\nneed for duplicating code in-tree.\n\nI just did some cursory review of this topic, and wondered why its tests\ncouldn't use a builtin instead of (mostly) reinventing the wheel.\n\nIt seems to me that the reason for that state is based on a\nmisunderstanding about what we would and wouldn't add to builtin/*.c,\ni.e. that we wouldn't have something like a --debug option, but as\nls-files shows that's not a problem.\n\nSo my interest is twofold:\n\n * Just a comment on \"can we avoid this code duplication\"\n\n * The related one of not wanting to re-learn some custom test helper as\n   (presumably) we get N number of large patch serieses on this topic,\n   if it turns out that we can use an existing well-known tool with\n   minimal changes.\n\n> Are you the one who is going to try it or are you going to\n> give it to your users?  If so, what do you need out of the debugging\n> tool?\n\nI haven't understood the sparse index enough feature enough to know if\nanyone would ever want to run this --debug-sparse outside of the test\nsuite.\n\nIsn't extract info about its internal state going to be useful sooner\nthan later in the scenarios where you'd care enough to run \"ls-files\n--stage\" now?\n\nMaybe I've misunderstood this feature and it's going to be so\ntransparent that nobody will ever have any reason to dump how it's\nworking out of the index...\n\n> You are correct that this will span multiple releases; Stolee already\n> said he was planning to be working on this for most of 2021.  But just\n> because pieces of the code exist and are shipped doesn't mean it'll be\n> announced or supported.  For example, the git-2.30 and git-2.31\n> release notes were completely silent about merge-ort.  It existed in\n> both releases; in fact, the version that ships in git-2.31, could\n> theoretically be used successfully by the vast majority of users for\n> their daily workflow.  (But it does have known shortcomings and test\n> failures so I definitely did *not* want it to be announced at that\n> time.)\n\nYes, and that's fine. But if you'd been bending over backwards to add\nmerge-ort to t/helper/ \"because it's not ready yet\" or something I'd\nhave probably commented to the effect of \"can't we just add it as part\nof builtins but not advertise it?\" which is what you did :)\n\n>> > Making a change to a builtin is _forever_, and since the only\n>> > purpose right now is to expose the data in a test environment, I\n>> > don't want to adjust the builtin until either there is a real user\n>> > need or the feature has otherwise stabilized. If you want to take on\n>> > that responsibility, then please do.\n>>\n>> That's just not the case, we have plenty of unstable debug-esque options\n>> in various built-in commands, in fact ls-files already has a --debug\n>> option whose docs say:\n>>\n>>     This is intended to show as much information as possible for manual\n>>     inspection; the exact format may change at any time.\n>>\n>> It was added in 84974217151 (ls-files: learn a debugging dump format,\n>> 2010-07-31) and \"just tacks all available data from the cache onto each\n>> file's line\" so in a way not adjusting it and using it would be a\n>> regression, after all this is new data in the cache, so it should print\n>> it :)\n>>\n>> There's also PARSE_OPT_HIDDEN for other such in-tree use. Whatever the\n>> sanity/merits of me suggesting that this specific thing be in ls-files\n>> instead of a test-helper, it seems far fetched that something like that\n>> hidden behind a GIT_TEST_* env var (or hidden option, --debug etc.) is\n>> something we'd need to worry about backwards compatibility for.\n>>\n>> So, whatever you think about the merits of including this functionality\n>> in ls-files I think your stance of this being a no-go for adding to the\n>> builtin is based on a false premise. It's fine to have\n>> unstable/transitory/debug output in the builtins. We just name &\n>> document them as such.\n>>\n>> I also had some feedback in that series and on the earlier iteration\n>> that I think is appropriate to be incorporated into a re-roll of this\n>> one, which doesn't have anything to do with the question of whether we\n>> use ls-files or the helper in the tests. Such as us showing more stuff\n>> into the read-cache.c test-tool v.s. splitting it up making that code\n>> needlessly convoluted.\n>\n> Well:\n>   * you seem to be strongly opposed to test-read-cache.c containing\n> this code (though I don't quite follow why)\n\nSee above.\n\n>   * Stolee seems to be strongly opposed to modifying\n> builtin/ls-files.c until he has time to think through how builtins\n> should work.\n\nAs noted above my reading of upthread is that those reasons basically\nboil down to not knowing \"git ls-files --debug\" exists, and that we can\nextend it.\n\n> So putting it in another test file that looks slightly duplicative of\n> test-read-cache.c might indeed be a good way out of this conundrum.\n> :-)\n\nFWIW I think that read-cache.c split is worth doing even if this series\ndoesn't modify t/helper/read-cache.c.\n\nThe \"this is for fsmonitor\" and \"this is for the perf test\" use-cases\nare (as I think my RFC patch shows) clearer once they're split up.\n\n> (I'm not opposed to any of the three solutions, I'm mostly chiming in\n> here because I'm worried about possible bubbling frustration; see\n> below.)\n>\n>> I don't see how recommending that I pursue that as an independent series\n>> is productive for anyone. So as you re-roll this I should submit another\n>> series on top to refactor your in-flight code & tests?\n>\n> Your tone suggests some frustration; I have a suspicion there's some\n> lack of understanding or misreading that has occurred (perhaps on my\n> part too), and before that misunderstanding morphs into motive\n> questioning, let me see if I might be able to help...\n\nHonestly more flabbergasted than anything, so I'm trying to clarify what\nthe author thinks of this direction.\n\nI mean it's fine if it's just a \"I don't think this is important and\ndon't want to spend time on it, but it seems like a good idea\", in which\ncase others have the option of re-rolling some of these patches if they\ncare (at this point I wouldn't).\n\nOr \"this is just a bad idea for XYZ reason\", which is also fine, and\neven more valuable to document for future work in the area.\n\nBut to have another series built on this with refactorings back and\nforth before code's landed on master just seems like needless churn.\n\n> So far, you have advocated for:\n>   A) Moving the checks to ls-files with a permanent new flag (--sparse)\n>   B) Duplicating test-read-cache.c (which is admittedly pretty small)\n> and then modifying the duplicate to have the new behavior, or\n> alternatively:\n>   C) Just stating files to get the information\n>   D) Creating new debug option(s) to ls-files so that end users can\n> use this in the next few releases before the feature is ready for\n> prime time\n> You also mentioned you had read just part of the series.\n>\n> Option D comes with the problem that it's not at all clear who these\n> end-users are, why they want the option, or how we should design it. [...]\n\nI think s/advocated/read the series and sent an flow-of-thought\nnot-ready-for-anything RFC patches on top/ would be more accurate :)\n\nI.e. the A) --sparse thing was just reading the patch and seeing if\nls-files couldn't be made to do this, but yes, having the documented\n--sparse interface might not make sense.\n\nwe discussed B) above.\n\nC) Was a question to clarify what was meant with stat data, since it's\nan offhand comment in the commit message. Does it mean \"stat after the\nfact\" or \"this will have a mode like ls-files --debug has now\"?\n\nRight now I'm just suggesting with D) that this might be rolled into the\ndev-only-not-for-end-users --debug mode. \n\n> I'm totally onboard that ls-files should generally have\n> the ability to show information in the index (e.g. if there are tree\n> entries in addition to blob entries, it should be able to show both),\n> but I'm not following the reasoning for why it needs to be there as\n> part of the early stages of development of the sparse-index feature\n> and who it's supposed to be helping in these next few releases.\n\nWe already are extracting the info at this early stage, just with a\ncustom helper. All I'm suggesting right now is that the motivation for\nthe custom helper is \"this isn't for end users\" then surely having a\npatch around 1/2 the size to add it to already reviewed/tested ls-files\ncode under a --debug option makes more sense.\n\nEspecially since the upthread commit mentions wanting to incorporate\nstat() data. I'm not sure how exactly (there's no outstanding patches,\neven on a WIP branch for it, AFAICT), but most likely it's further\nduplication of data \"ls-files --debug\" already spews out.\n\nSo the patch would be 1/2 the size, and instead of saying \"let's do stat\nstuff in the future\" it would get it for free.\n\nOr not, part of that's speculation on information that's just in\nStolee's head. Hence this side-discussion.\n\n> [...]\n\n[Cut parts hopefully all clarified with the above comments]\n\n>> [..]\n>> At which point you'd be adjusting your tests that expect ls-tree format\n>> output to using ls-files output, instead of using ls-files-like output\n>> from the beginning?\n>\n> I don't understand what you're getting at here.  I was the one who\n> requested Stolee make the output look like ls-trees in his original\n> RFC series, so if there's a problem with this style of output, I'm to\n> blame.\n\nI didn't read the RFC series, so I missed that there was past discussion\non this point.\n\nPerhaps something to roll into an updated commit message? My reading of\nthe current version is that it suggests that the ls-tree-like output is\nimportant to get at the data we need, which my patch-for-discussion\nshows isn't the case.\n\n> [...] Once you have different types included in the index, then\n> ls-files has to print all the same fields that ls-tree does...so why\n> not make it look similar?\n\nI don't have a problem with how the output looks, I happen to like the\nls-tree output better, I've just been suggesting that differing output\n== code duplication.\n\nIn any case. I'm sorry about any comments I've made that came across as\nsnarky or whatever. Since we're talking in a text-based medium I'm going\nto take the reading of a third-party native speaker (you) over mine.\n\nI didn't mean any comments I've made that way, I'm very interested in\nseeing this feature land, and just want to try to help it along. Given\nthe size of this thread over a relatively trivial matter I think that\n\"help\" is probably counterproductive at this point.\n\nI don't think this is criticial or needs to be done or whatever. I've\nonly kept up this thread for the reasons stated above, i.e. it seeming\nto me to be based on the premise that we can't add certain code to\nbuiltin/*.c, and if we can get around that we can make this simpler.\n"},{"id":"420477","messageId":"d7a0550b-90e8-fa37-c456-7c70c089dbec@gmail.com","threadId":"55195","inReplyTo":"xmqq8s68oxr4.fsf@gitster.g","subject":"Re: [PATCH v4 01/20] sparse-index: design doc and format update","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-29T14:32:04Z","receivedAt":"2021-03-29T14:32:45Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/27/2021 9:47 PM, Junio C Hamano wrote:\n> SZEDER Gábor <szeder.dev@gmail.com> writes:\n> \n>>> +To start, we use a new repository extension, `extensions.sparseIndex`, to\n>>> +allow inserting sparse-directory entries into indexes with file format\n>>> +versions 2, 3, and 4. This prevents Git versions that do not understand\n>>> +the sparse-index from operating on one, but it also prevents other\n>>> +operations that do not use the index at all.\n>>\n>> Why is this not a non-optional index extension?  ...\n>> This really should be a non-optional index extension.\n> \n> Yeah, the index extension mechanism was designed with optional and\n> required kinds because we wanted to support exactly a use case like\n> this one.\n> \n> Thanks for pointing it out.\n\nOk, so let me be sure I understand the request, as I believe it is\na very good one:\n\nUsing a REQUIRED index extension that says \"this index has\nsparse-directory entries\" will allow tools that don't touch\nthe index to be compatible with repos using the sparse-index,\nwhile also avoiding a new index version.\n\nI'll work on this right away. Thanks!\n-Stolee\n"},{"id":"420505","messageId":"b1eee802-f20c-5381-366c-729755bfac8a@gmail.com","threadId":"55195","inReplyTo":"87eeg0ng78.fsf@evledraar.gmail.com","subject":"Re: [PATCH v4 07/20] test-read-cache: print cache entries with --table","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-29T19:46:32Z","receivedAt":"2021-03-29T19:47:16Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/28/2021 11:31 AM, Ævar Arnfjörð Bjarmason wrote:> It seems to me that the reason for that state is based on a\n> misunderstanding about what we would and wouldn't add to builtin/*.c,\n> i.e. that we wouldn't have something like a --debug option, but as\n> ls-files shows that's not a problem.\n\nI feel _strongly_ that a change to the user-facing CLI should come\nwith a good reason and care about how it locks-in behavior for the\nfuture.\n\nAny adjustment to 'git ls-files' deserves its own series and\nattention, not in an already-too-large series like this one.\n\nI'm not happy that this series and the next are so long, but that's\nthe best I can do to make them reviewable and still capture a\ncomplete scenario. Hopefully the remaining series after these first\ntwo are smaller. Things like \"what should 'git ls-files' do with a\nsparse index?\" can fit cleanly on top once the core functionality\nof the internals are stable.\n\nI have an _opinion_ that the ls-files output is not well-suited to\ntesting because the --debug output splits details across multiple\nlines. This is a minor point that could probably be corrected by\na complicated script method, but that's why I list this as an\nopinion.\n\n> I mean it's fine if it's just a \"I don't think this is important and\n> don't want to spend time on it, but it seems like a good idea\", in which\n> case others have the option of re-rolling some of these patches if they\n> care (at this point I wouldn't).\n> \n> Or \"this is just a bad idea for XYZ reason\", which is also fine, and\n> even more valuable to document for future work in the area.\n> \n> But to have another series built on this with refactorings back and\n> forth before code's landed on master just seems like needless churn.\n\nI think changing 'ls-files' before the sparse index has stabilized is\npremature. I said that a series like the RFC you sent would be\nappropriate after this concept is more stable. I do _not_ recommend\ntrying to juggle it on top of the work while the patches are in flight.\n\nThanks,\n-Stolee\n"},{"id":"420524","messageId":"xmqq5z19iqj9.fsf@gitster.g","threadId":"55195","inReplyTo":"b1eee802-f20c-5381-366c-729755bfac8a@gmail.com","subject":"Re: [PATCH v4 07/20] test-read-cache: print cache entries with --table","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-29T21:44:10Z","receivedAt":"2021-03-29T21:45:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <stolee@gmail.com> writes:\n\n> I think changing 'ls-files' before the sparse index has stabilized is\n> premature. I said that a series like the RFC you sent would be\n> appropriate after this concept is more stable. I do _not_ recommend\n> trying to juggle it on top of the work while the patches are in flight.\n\nI do not have a problem with either of approaches to help debugging\n(i.e. extending \"ls-files --debug\" or a new test helper), but I am\ncurious to be reminded what the plan for \"git ls-files [-s]\" output\nis, when run in a repository in which sparse cone checkout is used.\n\nDo we see trees and paths outside the cone omitted, or does the act\nof running \"ls-files\" dehydrate the trees into their constituent\nblobs?\n\nThanks.\n"},{"id":"420525","messageId":"CABPp-BF=WQhMMARF0Groump1LSXGHWkV8SXZ-_Fqd=d-8Nn35g@mail.gmail.com","threadId":"55195","inReplyTo":"87eeg0ng78.fsf@evledraar.gmail.com","subject":"Re: [PATCH v4 07/20] test-read-cache: print cache entries with --table","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-03-29T22:02:09Z","receivedAt":"2021-03-29T22:03:25Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi,\n\nOn Sun, Mar 28, 2021 at 8:31 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> On Fri, Mar 26 2021, Elijah Newren wrote:\n>\n[...]\n> > You are correct that this will span multiple releases; Stolee already\n> > said he was planning to be working on this for most of 2021.  But just\n> > because pieces of the code exist and are shipped doesn't mean it'll be\n> > announced or supported.  For example, the git-2.30 and git-2.31\n> > release notes were completely silent about merge-ort.  It existed in\n> > both releases; in fact, the version that ships in git-2.31, could\n> > theoretically be used successfully by the vast majority of users for\n> > their daily workflow.  (But it does have known shortcomings and test\n> > failures so I definitely did *not* want it to be announced at that\n> > time.)\n>\n> Yes, and that's fine. But if you'd been bending over backwards to add\n> merge-ort to t/helper/ \"because it's not ready yet\" or something I'd\n> have probably commented to the effect of \"can't we just add it as part\n> of builtins but not advertise it?\" which is what you did :)\n\nActually, I did add a t/helper/test-fast-rebase.c (which is a few\nhundred lines long) as part of the work on merge-ort, because\nmerge-ort wasn't ready and because rewiring sequencer.c was a huge\namount of work that I didn't want to get distracted by at the time.  I\noriginally suggested making fast-rebase a non-advertised builtin, but\nmultiple reviewers suggested the test helper route instead.\n\n¯\\_(ツ)_/¯\n"},{"id":"420534","messageId":"87im59mufk.fsf@evledraar.gmail.com","threadId":"55195","inReplyTo":"b1eee802-f20c-5381-366c-729755bfac8a@gmail.com","subject":"Re: [PATCH v4 07/20] test-read-cache: print cache entries with --table","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-29T23:06:23Z","receivedAt":"2021-03-29T23:07:01Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Mar 29 2021, Derrick Stolee wrote:\n\n> On 3/28/2021 11:31 AM, Ævar Arnfjörð Bjarmason wrote:> It seems to me that the reason for that state is based on a\n>> misunderstanding about what we would and wouldn't add to builtin/*.c,\n>> i.e. that we wouldn't have something like a --debug option, but as\n>> ls-files shows that's not a problem.\n\nAt the risk of going in circles here...\n\n> I feel _strongly_ that a change to the user-facing CLI should come\n> with a good reason and care about how it locks-in behavior for the\n> future.\n\nAnd I agree with you. Where we disagree is whether lives in builtin/*.c\n== user-facing. I think --debug options are != that. It seems Junio\ndownthread agrees with that.\n\n> Any adjustment to 'git ls-files' deserves its own series and\n> attention[...]\n\nA user-facing change to it yes, but I don't see how use of an (existing\neven) --debug option would warrant any more attention than a new test\nhelper, less actually, it's less new code.\n\n> [...] not in an already-too-large series like this one.\n\nThe alternative way of doing it at the end of\nhttps://lore.kernel.org/git/874kgzq4qi.fsf@evledraar.gmail.com would\nmake this series smaller.\n\nAnyway. As I noted in the E-Mail you're replying to\n(https://lore.kernel.org/git/87eeg0ng78.fsf@evledraar.gmail.com/) I\nreally don't care that much.\n\nI'm just still perplexed at how you keep bringing up use of an\ninternal-only --debug option as \"user-facing\", and here \"already too\nlarge\" when we're talking about a proposed alternate direction that\nwould reduce the size.\n\n> I'm not happy that this series and the next are so long, but that's\n> the best I can do to make them reviewable and still capture a\n> complete scenario. Hopefully the remaining series after these first\n> two are smaller. Things like \"what should 'git ls-files' do with a\n> sparse index?\" can fit cleanly on top once the core functionality\n> of the internals are stable.\n\nSure. I'm fully on board with just moving forward with this in some\nmanner.\n\nI'm not on board with the part of this that seems like it could just be\nrephrased/understood as \"...and we're not touching ls-files even with a\n--debug option now because that would be user-facing[...]\".\n\n> I have an _opinion_ that the ls-files output is not well-suited to\n> testing because the --debug output splits details across multiple\n> lines. This is a minor point that could probably be corrected by\n> a complicated script method, but that's why I list this as an\n> opinion.\n\nIf the --debug it's spewing now isn't handy we can just change the\noutput format. The docs say:\n\n    This is intended to show as much information as possible for manual\n    inspection; the exact format may change at any time.\n\nAnd we don't have existing in-tree users, something like this would make\nit rather trivial:\n    \n    diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n    index f6f9e483b27..7596edc9f9d 100644\n    --- a/builtin/ls-files.c\n    +++ b/builtin/ls-files.c\n    @@ -113,11 +113,11 @@ static void print_debug(const struct cache_entry *ce)\n     \tif (debug_mode) {\n     \t\tconst struct stat_data *sd = &ce->ce_stat_data;\n     \n    -\t\tprintf(\"  ctime: %u:%u\\n\", sd->sd_ctime.sec, sd->sd_ctime.nsec);\n    -\t\tprintf(\"  mtime: %u:%u\\n\", sd->sd_mtime.sec, sd->sd_mtime.nsec);\n    -\t\tprintf(\"  dev: %u\\tino: %u\\n\", sd->sd_dev, sd->sd_ino);\n    -\t\tprintf(\"  uid: %u\\tgid: %u\\n\", sd->sd_uid, sd->sd_gid);\n    -\t\tprintf(\"  size: %u\\tflags: %x\\n\", sd->sd_size, ce->ce_flags);\n    +\t\tprintf(\"  ctime: %u:%u%c\", sd->sd_ctime.sec, sd->sd_ctime.nsec, line_terminator);\n    +\t\tprintf(\"  mtime: %u:%u%c\", sd->sd_mtime.sec, sd->sd_mtime.nsec, line_terminator);\n    +\t\tprintf(\"  dev: %u\\tino: %u%c\", sd->sd_dev, sd->sd_ino, line_terminator);\n    +\t\tprintf(\"  uid: %u\\tgid: %u%c\", sd->sd_uid, sd->sd_gid, line_terminator);\n    +\t\tprintf(\"  size: %u\\tflags: %x%c\", sd->sd_size, ce->ce_flags, line_terminator);\n     \t}\n     }\n\nBut even without that it wouldn't be some complicated post-processing,\njust a pipe to a small perl or awk process.\n     \n>> I mean it's fine if it's just a \"I don't think this is important and\n>> don't want to spend time on it, but it seems like a good idea\", in which\n>> case others have the option of re-rolling some of these patches if they\n>> care (at this point I wouldn't).\n>> \n>> Or \"this is just a bad idea for XYZ reason\", which is also fine, and\n>> even more valuable to document for future work in the area.\n>> \n>> But to have another series built on this with refactorings back and\n>> forth before code's landed on master just seems like needless churn.\n>\n> I think changing 'ls-files' before the sparse index has stabilized is\n> premature. I said that a series like the RFC you sent would be\n> appropriate after this concept is more stable. I do _not_ recommend\n> trying to juggle it on top of the work while the patches are in flight.\n\nJust to clarify, upthread in [1] you said:\n\n    And I recommend that you continue to pursue [these RFC patches] as\n    an independent series, but I'm not going to incorporate them into\n    this one[...]\n\nSo do I understand it right that you're referring to phase IV in your\nopinion being the first point where we'd consider piggy-backing on\nanything in builtin (that \"user-facing\" dilemma again...).\n\nBut at that point wouldn't you have your own ideas about some\nuser-facing ls-files or other porcelain for this, so I'm not sure where\nto place the encouragement that I continue to pursue that RFC series,\nother than setting a reminder in my calendar for 6-12 months in the\nfuture :)\n\n1. https://lore.kernel.org/git/ca8a96a4-5897-2484-b195-57e5b3820576@gmail.com/\n"},{"id":"420571","messageId":"fbd0852b-eba0-a4a2-ed7a-3b12963609ee@gmail.com","threadId":"55195","inReplyTo":"xmqq5z19iqj9.fsf@gitster.g","subject":"Re: [PATCH v4 07/20] test-read-cache: print cache entries with --table","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-30T11:28:55Z","receivedAt":"2021-03-30T11:29:54Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/29/2021 5:44 PM, Junio C Hamano wrote:\n> Derrick Stolee <stolee@gmail.com> writes:\n> \n>> I think changing 'ls-files' before the sparse index has stabilized is\n>> premature. I said that a series like the RFC you sent would be\n>> appropriate after this concept is more stable. I do _not_ recommend\n>> trying to juggle it on top of the work while the patches are in flight.\n> \n> I do not have a problem with either of approaches to help debugging\n> (i.e. extending \"ls-files --debug\" or a new test helper), but I am\n> curious to be reminded what the plan for \"git ls-files [-s]\" output\n> is, when run in a repository in which sparse cone checkout is used.\n> \n> Do we see trees and paths outside the cone omitted, or does the act\n> of running \"ls-files\" dehydrate the trees into their constituent\n> blobs?\n\nAt the moment, end-to-end behavior is identical as before: sparse\ndirectory entries are expanded to all of the contained blobs instead\nof writing the tree entries.\n\nThe sparse-index work will not be complete until every command is\naudited for potential behavior change when disabling the\ncommand_requires_full_index setting. That includes deciding what\nis the best decision for ls-files, and will likely include an option\nfor both possible outputs (tree entries, or expanding to blobs). The\ninteresting discussion that is worth its own topic is whether or not\nthe tree entries should be displayed by default.\n\nSo the plan is: this _will_ be addressed, but in the future after\nthe core functionality and value of the sparse-index is set.\n\nThanks,\n-Stolee\n"},{"id":"420572","messageId":"60e27a5c-b3d1-4a6a-51d5-bdc03591e02d@gmail.com","threadId":"55195","inReplyTo":"87im59mufk.fsf@evledraar.gmail.com","subject":"Re: [PATCH v4 07/20] test-read-cache: print cache entries with --table","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-30T11:41:54Z","receivedAt":"2021-03-30T11:43:02Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/29/2021 7:06 PM, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Mon, Mar 29 2021, Derrick Stolee wrote:\n> \n>> On 3/28/2021 11:31 AM, Ævar Arnfjörð Bjarmason wrote:> It seems to me that the reason for that state is based on a\n>>> misunderstanding about what we would and wouldn't add to builtin/*.c,\n>>> i.e. that we wouldn't have something like a --debug option, but as\n>>> ls-files shows that's not a problem.\n> \n> At the risk of going in circles here...\n> \n>> I feel _strongly_ that a change to the user-facing CLI should come\n>> with a good reason and care about how it locks-in behavior for the\n>> future.\n> \n> And I agree with you. Where we disagree is whether lives in builtin/*.c\n> == user-facing. I think --debug options are != that. It seems Junio\n> downthread agrees with that.\n> \n>> Any adjustment to 'git ls-files' deserves its own series and\n>> attention[...]\n> \n> A user-facing change to it yes, but I don't see how use of an (existing\n> even) --debug option would warrant any more attention than a new test\n> helper, less actually, it's less new code.\n\nI disagree that we can change the expected output of --debug so\nquickly, despite warnings in the documentation. Changing that format\nor creating a new output format requires cognitive load, and we have\nenough of that going on in this area as it is.\n\n>> [...] not in an already-too-large series like this one.\n...\n> I'm just still perplexed at how you keep bringing up use of an\n> internal-only --debug option as \"user-facing\", and here \"already too\n> large\" when we're talking about a proposed alternate direction that\n> would reduce the size.\n\nI'm not saying \"patch size\" or \"code size\" but instead thinking of it\nin terms of how many decisions need to be made. Changing a builtin\nwhen it's not necessary adds to the complexity of the series and\ninterrupts its core goals.\n\nFinally, I have mentioned that I will need extra data for testing a\nnew index format. I don't want to modify the builtin now in a way\nthat is insufficient for the needs in that future series.\n\n> Just to clarify, upthread in [1] you said:\n> \n>     And I recommend that you continue to pursue [these RFC patches] as\n>     an independent series, but I'm not going to incorporate them into\n>     this one[...]\n> \n> So do I understand it right that you're referring to phase IV in your\n> opinion being the first point where we'd consider piggy-backing on\n> anything in builtin (that \"user-facing\" dilemma again...).\n\nI'm saying that if you feel strongly about it, then please pursue the\nchanges to ls-files any time after this series (but probably after\nthe next) solidifies. Having the changes be in a separate series allows\ntime to inspect the behavior change to the builtin in a focused way.\n \n> But at that point wouldn't you have your own ideas about some\n> user-facing ls-files or other porcelain for this, so I'm not sure where\n> to place the encouragement that I continue to pursue that RFC series,\n> other than setting a reminder in my calendar for 6-12 months in the\n> future :)\n\nOtherwise, I will modify ls-files myself in this 6-12 month timeframe,\nbased on the established plan to remove the command_requires_full_index\nsetting.\n\nThanks,\n-Stolee\n"},{"id":"420577","messageId":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v4.git.1616507069.gitgitgadget@gmail.com","subject":"[PATCH v5 00/21] Sparse Index: Design, Format, Tests","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:10:43Z","receivedAt":"2021-03-30T13:12:13Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"Here is the first full patch series submission coming out of the\nsparse-index RFC [1].\n\n[1]\nhttps://lore.kernel.org/git/pull.847.git.1611596533.gitgitgadget@gmail.com/\n\nI won't waste too much space here, because PATCH 1 includes a sizeable\ndesign document that describes the feature, the reasoning behind it, and my\nplan for getting this implemented widely throughout the codebase.\n\nThere are some new things here that were not in the RFC:\n\n * Design doc and format updates. (Patch 1)\n * Performance test script. (Patches 2 and 20)\n\nNotably missing in this series from the RFC:\n\n * The mega-patch inserting ensure_full_index() throughout the codebase.\n   That will be a follow-up series to this one.\n * The integrations with git status and git add to demonstrate the improved\n   performance. Those will also appear in their own series later.\n\nI plan to keep my latest work in this area in my 'sparse-index/wip' branch\n[2]. It includes all of the work from the RFC right now, updated with the\nwork from this series.\n\n[2] https://github.com/derrickstolee/git/tree/sparse-index/wip\n\n\nUpdates in V5\n=============\n\nThis version is updated to use an index extension instead of a repository\nformat extension. Thanks, Szeder! This one change affects the range-diff\nquite a bit, so please review those changes carefully.\n\nIn particular: git sparse-checkout init --cone --sparse-index now sets a new\nindex.sparse config option as an indicator that we should attempt writing\nthe index in sparse form.\n\n\nUpdates in V4\n=============\n\n * Rebased onto the latest copy of ab/read-tree.\n * Updated the design document as per Junio's comments.\n * Updated the submodule handling in the performance test.\n * Followed up on some other review from Ævar, mostly style or commit\n   message things.\n\n\nUpdates in V3\n=============\n\nFor this version, I took Ævar's latest patches and applied them to v2.31.0\nand rebased this series on top. It uses his new \"read_tree_at()\" helper and\nthe associated changes to the function pointer type.\n\n * Fixed more typos. Thanks Martin and Elijah!\n * Updated the test_sparse_match() macro to use \"$@\" instead of $*\n * Added a test that git sparse-checkout init --no-sparse-index rewrites the\n   index to be full.\n\n\nUpdates in V2\n=============\n\n * Various typos and awkward grammar is fixed.\n * Cleaned up unnecessary commands in p2000-sparse-operations.sh\n * Added a comment to the sparse_index member of struct index_state.\n * Used tree_type, commit_type, and blob_type in test-read-cache.c.\n\nThanks, -Stolee\n\nDerrick Stolee (21):\n  sparse-index: design doc and format update\n  t/perf: add performance test for sparse operations\n  t1092: clean up script quoting\n  sparse-index: add guard to ensure full index\n  sparse-index: implement ensure_full_index()\n  t1092: compare sparse-checkout to sparse-index\n  test-read-cache: print cache entries with --table\n  test-tool: don't force full index\n  unpack-trees: ensure full index\n  sparse-checkout: hold pattern list in index\n  sparse-index: add 'sdir' index extension\n  sparse-index: convert from full to sparse\n  submodule: sparse-index should not collapse links\n  unpack-trees: allow sparse directories\n  sparse-index: check index conversion happens\n  sparse-index: add index.sparse config option\n  sparse-checkout: toggle sparse index from builtin\n  sparse-checkout: disable sparse-index\n  cache-tree: integrate with sparse directory entries\n  sparse-index: loose integration with cache_tree_verify()\n  p2000: add sparse-index repos\n\n Documentation/config/index.txt           |   5 +\n Documentation/git-sparse-checkout.txt    |  14 ++\n Documentation/technical/index-format.txt |  19 ++\n Documentation/technical/sparse-index.txt | 175 ++++++++++++++\n Makefile                                 |   1 +\n builtin/sparse-checkout.c                |  44 +++-\n cache-tree.c                             |  40 ++++\n cache.h                                  |  18 +-\n read-cache.c                             |  44 +++-\n repo-settings.c                          |  15 ++\n repository.c                             |  11 +-\n repository.h                             |   3 +\n sparse-index.c                           | 285 +++++++++++++++++++++++\n sparse-index.h                           |  11 +\n t/README                                 |   3 +\n t/helper/test-read-cache.c               |  66 +++++-\n t/perf/p2000-sparse-operations.sh        | 101 ++++++++\n t/t1091-sparse-checkout-builtin.sh       |  13 ++\n t/t1092-sparse-checkout-compatibility.sh | 143 ++++++++++--\n unpack-trees.c                           |  17 +-\n 20 files changed, 988 insertions(+), 40 deletions(-)\n create mode 100644 Documentation/technical/sparse-index.txt\n create mode 100644 sparse-index.c\n create mode 100644 sparse-index.h\n create mode 100755 t/perf/p2000-sparse-operations.sh\n\n\nbase-commit: 47957485b3b731a7860e0554d2bd12c0dce1c75a\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-883%2Fderrickstolee%2Fsparse-index%2Fformat-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-883/derrickstolee/sparse-index/format-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/883\n\nRange-diff vs v4:\n\n  1:  6426a5c60e53 !  1:  7b600d536c6e sparse-index: design doc and format update\n     @@ Documentation/technical/sparse-index.txt (new)\n      +The only noticeable change in behavior will be that the serialized index\n      +file contains sparse-directory entries.\n      +\n     -+To start, we use a new repository extension, `extensions.sparseIndex`, to\n     -+allow inserting sparse-directory entries into indexes with file format\n     ++To start, we use a new required index extension, `sdir`, to allow\n     ++inserting sparse-directory entries into indexes with file format\n      +versions 2, 3, and 4. This prevents Git versions that do not understand\n     -+the sparse-index from operating on one, but it also prevents other\n     -+operations that do not use the index at all. A new format, index v5, will\n     -+be introduced that includes sparse-directory entries by default. It might\n     -+also introduce other features that have been considered for improving the\n     ++the sparse-index from operating on one, while allowing tools that do not\n     ++understand the sparse-index to operate on repositories as long as they do\n     ++not interact with the index. A new format, index v5, will be introduced\n     ++that includes sparse-directory entries by default. It might also\n     ++introduce other features that have been considered for improving the\n      +index, as well.\n      +\n      +Next, consumers of the index will be guarded against operating on a\n  2:  7eabc1d0586c =  2:  202253ec82f3 t/perf: add performance test for sparse operations\n  3:  c9e21d78ecba =  3:  437a0f144e57 t1092: clean up script quoting\n  4:  03cdde756563 =  4:  b7e1bf5c55a7 sparse-index: add guard to ensure full index\n  5:  6b3b6d86385d =  5:  e41d55d2cca9 sparse-index: implement ensure_full_index()\n  6:  7f67adba0498 =  6:  7bfbfbd17321 t1092: compare sparse-checkout to sparse-index\n  7:  7ebd9570b1ad =  7:  a1b8135c0fc8 test-read-cache: print cache entries with --table\n  8:  db7bbd06dbcc =  8:  dd84a2a9121b test-tool: don't force full index\n  9:  3ddd5e794b5e =  9:  b276d2ed5323 unpack-trees: ensure full index\n 10:  7308c87697f1 = 10:  c3651e26dc3a sparse-checkout: hold pattern list in index\n  -:  ------------ > 11:  f926cf8b2e01 sparse-index: add 'sdir' index extension\n 11:  7c10d653ca6b = 12:  c870ae5e8749 sparse-index: convert from full to sparse\n 12:  6db36f33e960 = 13:  bcf0da959ef3 submodule: sparse-index should not collapse links\n 13:  d24bd3348d98 = 14:  7191b48237de unpack-trees: allow sparse directories\n 14:  08d9f5f3c0d1 = 15:  57be9b4a728b sparse-index: check index conversion happens\n 15:  6f38cef196b0 ! 16:  c22b4111e49e sparse-index: create extension for compatibility\n     @@ Metadata\n      Author: Derrick Stolee <dstolee@microsoft.com>\n      \n       ## Commit message ##\n     -    sparse-index: create extension for compatibility\n     +    sparse-index: add index.sparse config option\n      \n     -    Previously, we enabled the sparse index format only using\n     -    GIT_TEST_SPARSE_INDEX=1. This is not a feasible direction for users to\n     -    actually select this mode. Further, sparse directory entries are not\n     -    understood by the index formats as advertised.\n     -\n     -    We _could_ add a new index version that explicitly adds these\n     -    capabilities, but there are nuances to index formats 2, 3, and 4 that\n     -    are still valuable to select as options. Until we add index format\n     -    version 5, create a repo extension, \"extensions.sparseIndex\", that\n     -    specifies that the tool reading this repository must understand sparse\n     -    directory entries.\n     -\n     -    This change only encodes the extension and enables it when\n     -    GIT_TEST_SPARSE_INDEX=1. Later, we will add a more user-friendly CLI\n     -    mechanism.\n     +    When enabled, this config option signals that index writes should\n     +    attempt to use sparse-directory entries.\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n      \n     - ## Documentation/config/extensions.txt ##\n     -@@ Documentation/config/extensions.txt: extensions.objectFormat::\n     - Note that this setting should only be set by linkgit:git-init[1] or\n     - linkgit:git-clone[1].  Trying to change it after initialization will not\n     - work and will produce hard-to-diagnose issues.\n     + ## Documentation/config/index.txt ##\n     +@@ Documentation/config/index.txt: index.recordOffsetTable::\n     + \tDefaults to 'true' if index.threads has been explicitly enabled,\n     + \t'false' otherwise.\n     + \n     ++index.sparse::\n     ++\tWhen enabled, write the index using sparse-directory entries. This\n     ++\thas no effect unless `core.sparseCheckout` and\n     ++\t`core.sparseCheckoutCone` are both enabled. Defaults to 'false'.\n      +\n     -+extensions.sparseIndex::\n     -+\tWhen combined with `core.sparseCheckout=true` and\n     -+\t`core.sparseCheckoutCone=true`, the index may contain entries\n     -+\tcorresponding to directories outside of the sparse-checkout\n     -+\tdefinition in lieu of containing each path under such directories.\n     -+\tVersions of Git that do not understand this extension do not\n     -+\texpect directory entries in the index.\n     + index.threads::\n     + \tSpecifies the number of threads to spawn when loading the index.\n     + \tThis is meant to reduce index load time on multiprocessor machines.\n      \n       ## cache.h ##\n      @@ cache.h: struct repository_format {\n     @@ repo-settings.c: void prepare_repo_settings(struct repository *r)\n      +\t * Initialize this as off.\n      +\t */\n      +\tr->settings.sparse_index = 0;\n     -+\tif (!repo_config_get_bool(r, \"extensions.sparseindex\", &value) && value)\n     ++\tif (!repo_config_get_bool(r, \"index.sparse\", &value) && value)\n      +\t\tr->settings.sparse_index = 1;\n       }\n      \n     @@ repository.h: struct repo_settings {\n       \n       struct repository {\n      \n     - ## setup.c ##\n     -@@ setup.c: static enum extension_result handle_extension(const char *var,\n     - \t\t\treturn error(\"invalid value for 'extensions.objectformat'\");\n     - \t\tdata->hash_algo = format;\n     - \t\treturn EXTENSION_OK;\n     -+\t} else if (!strcmp(ext, \"sparseindex\")) {\n     -+\t\tdata->sparse_index = 1;\n     -+\t\treturn EXTENSION_OK;\n     - \t}\n     - \treturn EXTENSION_UNKNOWN;\n     - }\n     -\n       ## sparse-index.c ##\n      @@ sparse-index.c: static int convert_to_sparse_rec(struct index_state *istate,\n       \treturn num_converted - start_converted;\n     @@ sparse-index.c: static int convert_to_sparse_rec(struct index_state *istate,\n      +{\n      +\tconst char *config_path = repo_git_path(repo, \"config.worktree\");\n      +\n     -+\tif (upgrade_repository_format(1) < 0) {\n     -+\t\twarning(_(\"unable to upgrade repository format to enable sparse-index\"));\n     -+\t\treturn -1;\n     -+\t}\n      +\tgit_config_set_in_file_gently(config_path,\n     -+\t\t\t\t      \"extensions.sparseIndex\",\n     ++\t\t\t\t      \"index.sparse\",\n      +\t\t\t\t      \"true\");\n      +\n      +\tprepare_repo_settings(repo);\n     @@ sparse-index.c: static int convert_to_sparse_rec(struct index_state *istate,\n      +\n      +\t/*\n      +\t * The GIT_TEST_SPARSE_INDEX environment variable triggers the\n     -+\t * extensions.sparseIndex config variable to be on.\n     ++\t * index.sparse config variable to be on.\n      +\t */\n      +\tif (git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0)) {\n      +\t\tint err = enable_sparse_index(istate->repo);\n     @@ sparse-index.c: static int convert_to_sparse_rec(struct index_state *istate,\n      -\t * GIT_TEST_SPARSE_INDEX environment variable. We will relax\n      -\t * this once we have a proper way to opt-in (and later still,\n      -\t * opt-out).\n     -+\t * Only convert to sparse if extensions.sparseIndex is set.\n     ++\t * Only convert to sparse if index.sparse is set.\n       \t */\n      -\tif (!git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0))\n      +\tprepare_repo_settings(istate->repo);\n 16:  923081e7e079 ! 17:  75fe9b0f57da sparse-checkout: toggle sparse index from builtin\n     @@ Documentation/git-sparse-checkout.txt: To avoid interfering with other worktrees\n      +that is not completely understood by external tools. If you have trouble\n      +with this compatibility, then run `git sparse-checkout init --no-sparse-index`\n      +to rewrite your index to not be sparse. Older versions of Git will not\n     -+understand the `sparseIndex` repository extension and may fail to interact\n     -+with your repository until it is disabled.\n     ++understand the sparse directory entries index extension and may fail to\n     ++interact with your repository until it is disabled.\n       \n       'set'::\n       \tWrite a set of patterns to the sparse-checkout file, as given as\n     @@ builtin/sparse-checkout.c: static int sparse_checkout_init(int argc, const char\n      \n       ## sparse-index.c ##\n      @@ sparse-index.c: static int convert_to_sparse_rec(struct index_state *istate,\n     + \treturn num_converted - start_converted;\n     + }\n       \n     - static int enable_sparse_index(struct repository *repo)\n     +-static int enable_sparse_index(struct repository *repo)\n     ++static int set_index_sparse_config(struct repository *repo, int enable)\n       {\n      -\tconst char *config_path = repo_git_path(repo, \"config.worktree\");\n     -+\tint res;\n     - \n     - \tif (upgrade_repository_format(1) < 0) {\n     - \t\twarning(_(\"unable to upgrade repository format to enable sparse-index\"));\n     - \t\treturn -1;\n     - \t}\n     +-\n      -\tgit_config_set_in_file_gently(config_path,\n     --\t\t\t\t      \"extensions.sparseIndex\",\n     +-\t\t\t\t      \"index.sparse\",\n      -\t\t\t\t      \"true\");\n     -+\tres = git_config_set_gently(\"extensions.sparseindex\", \"true\");\n     ++\tint res;\n     ++\tchar *config_path = repo_git_path(repo, \"config.worktree\");\n     ++\tres = git_config_set_in_file_gently(config_path,\n     ++\t\t\t\t\t    \"index.sparse\",\n     ++\t\t\t\t\t    enable ? \"true\" : NULL);\n     ++\tfree(config_path);\n       \n       \tprepare_repo_settings(repo);\n       \trepo->settings.sparse_index = 1;\n     @@ sparse-index.c: static int convert_to_sparse_rec(struct index_state *istate,\n      +\n      +int set_sparse_index_config(struct repository *repo, int enable)\n      +{\n     -+\tint res;\n     -+\n     -+\tif (enable)\n     -+\t\treturn enable_sparse_index(repo);\n     -+\n     -+\t/* Don't downgrade repository format, just remove the extension. */\n     -+\tres = git_config_set_gently(\"extensions.sparseindex\", NULL);\n     ++\tint res = set_index_sparse_config(repo, enable);\n      +\n      +\tprepare_repo_settings(repo);\n     -+\trepo->settings.sparse_index = 0;\n     ++\trepo->settings.sparse_index = enable;\n      +\treturn res;\n       }\n       \n     @@ sparse-index.c: static int convert_to_sparse_rec(struct index_state *istate,\n       \t    !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n       \t\treturn 0;\n      @@ sparse-index.c: int convert_to_sparse(struct index_state *istate)\n     - \t\tistate->repo = the_repository;\n     - \n     - \t/*\n     --\t * The GIT_TEST_SPARSE_INDEX environment variable triggers the\n     --\t * extensions.sparseIndex config variable to be on.\n     -+\t * If GIT_TEST_SPARSE_INDEX=1, then trigger extensions.sparseIndex\n     -+\t * to be fully enabled. If GIT_TEST_SPARSE_INDEX=0 (set explicitly),\n     -+\t * then purposefully disable the setting.\n     + \t * The GIT_TEST_SPARSE_INDEX environment variable triggers the\n     + \t * index.sparse config variable to be on.\n       \t */\n      -\tif (git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0)) {\n      -\t\tint err = enable_sparse_index(istate->repo);\n     @@ sparse-index.c: int convert_to_sparse(struct index_state *istate)\n      +\t\tset_sparse_index_config(istate->repo, test_env);\n       \n       \t/*\n     - \t * Only convert to sparse if extensions.sparseIndex is set.\n     + \t * Only convert to sparse if index.sparse is set.\n      \n       ## sparse-index.h ##\n      @@ sparse-index.h: struct index_state;\n     @@ t/t1092-sparse-checkout-compatibility.sh: init_repos () {\n      -\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout init --cone &&\n      -\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep\n      +\tgit -C sparse-index sparse-checkout init --cone --sparse-index &&\n     -+\ttest_cmp_config -C sparse-index true extensions.sparseindex &&\n     ++\ttest_cmp_config -C sparse-index true index.sparse &&\n      +\tgit -C sparse-index sparse-checkout set deep\n       }\n       \n 17:  6f1ad72c390d ! 18:  7f55a232e647 sparse-checkout: disable sparse-index\n     @@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'sparse-checkout disable\n       \n      +test_expect_success 'sparse-index enabled and disabled' '\n      +\tgit -C repo sparse-checkout init --cone --sparse-index &&\n     -+\ttest_cmp_config -C repo true extensions.sparseIndex &&\n     ++\ttest_cmp_config -C repo true index.sparse &&\n      +\ttest-tool -C repo read-cache --table >cache &&\n      +\tgrep \" tree \" cache &&\n      +\n     @@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'sparse-checkout disable\n      +\ttest-tool -C repo read-cache --table >cache &&\n      +\t! grep \" tree \" cache &&\n      +\tgit -C repo config --list >config &&\n     -+\t! grep extensions.sparseindex config\n     ++\t! grep index.sparse config\n      +'\n      +\n       test_expect_success 'cone mode: init and set' '\n 18:  bd94e6b7d089 = 19:  365901809d9d cache-tree: integrate with sparse directory entries\n 19:  e7190376b806 = 20:  9b068c458898 sparse-index: loose integration with cache_tree_verify()\n 20:  bcf0a58eb38c = 21:  66602733cc95 p2000: add sparse-index repos\n\n-- \ngitgitgadget\n"},{"id":"420578","messageId":"b276d2ed53238465b1416676a58506414022860f.1617109864.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 09/21] unpack-trees: ensure full index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:10:52Z","receivedAt":"2021-03-30T13:12:13Z","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 next change will translate full indexes into sparse indexes at write\ntime. The existing logic provides a way for every sparse index to be\nexpanded to a full index at read time. However, there are cases where an\nindex is written and then continues to be used in-memory to perform\nfurther updates.\n\nunpack_trees() is frequently called after such a write. In particular,\ncommands like 'git reset' do this double-update of the index.\n\nEnsure that we have a full index when entering unpack_trees(), but only\nwhen command_requires_full_index is true. This is always true at the\nmoment, but we will later relax that after unpack_trees() is updated to\nhandle sparse directory entries.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n unpack-trees.c | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex f5f668f532d8..4dd99219073a 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -1567,6 +1567,7 @@ static int verify_absent(const struct cache_entry *,\n  */\n int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)\n {\n+\tstruct repository *repo = the_repository;\n \tint i, ret;\n \tstatic struct cache_entry *dfc;\n \tstruct pattern_list pl;\n@@ -1578,6 +1579,12 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n \ttrace_performance_enter();\n \ttrace2_region_enter(\"unpack_trees\", \"unpack_trees\", the_repository);\n \n+\tprepare_repo_settings(repo);\n+\tif (repo->settings.command_requires_full_index) {\n+\t\tensure_full_index(o->src_index);\n+\t\tensure_full_index(o->dst_index);\n+\t}\n+\n \tif (!core_apply_sparse_checkout || !o->update)\n \t\to->skip_sparse_checkout = 1;\n \tif (!o->skip_sparse_checkout && !o->pl) {\n-- \ngitgitgadget\n\n"},{"id":"420579","messageId":"7b600d536c6e7d06d204001c844d2fa1bb84e2db.1617109864.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 01/21] sparse-index: design doc and format update","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:10:44Z","receivedAt":"2021-03-30T13:12:13Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThis begins a long effort to update the index format to allow sparse\ndirectory entries. This should result in a significant improvement to\nGit commands when HEAD contains millions of files, but the user has\nselected many fewer files to keep in their sparse-checkout definition.\n\nCurrently, the index format is only updated in the presence of\nextensions.sparseIndex instead of increasing a file format version\nnumber. This is temporary, and index v5 is part of the plan for future\nwork in this area.\n\nThe design document details many of the reasons for embarking on this\nwork, and also the plan for completing it safely.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/technical/index-format.txt |   7 +\n Documentation/technical/sparse-index.txt | 175 +++++++++++++++++++++++\n 2 files changed, 182 insertions(+)\n create mode 100644 Documentation/technical/sparse-index.txt\n\ndiff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt\nindex d363a71c37ec..3b74c05647db 100644\n--- a/Documentation/technical/index-format.txt\n+++ b/Documentation/technical/index-format.txt\n@@ -44,6 +44,13 @@ Git index format\n   localization, no special casing of directory separator '/'). Entries\n   with the same name are sorted by their stage field.\n \n+  An index entry typically represents a file. However, if sparse-checkout\n+  is enabled in cone mode (`core.sparseCheckoutCone` is enabled) and the\n+  `extensions.sparseIndex` extension is enabled, then the index may\n+  contain entries for directories outside of the sparse-checkout definition.\n+  These entries have mode `040000`, include the `SKIP_WORKTREE` bit, and\n+  the path ends in a directory separator.\n+\n   32-bit ctime seconds, the last time a file's metadata changed\n     this is stat(2) data\n \ndiff --git a/Documentation/technical/sparse-index.txt b/Documentation/technical/sparse-index.txt\nnew file mode 100644\nindex 000000000000..8d3d80804604\n--- /dev/null\n+++ b/Documentation/technical/sparse-index.txt\n@@ -0,0 +1,175 @@\n+Git Sparse-Index Design Document\n+================================\n+\n+The sparse-checkout feature allows users to focus a working directory on\n+a subset of the files at HEAD. The cone mode patterns, enabled by\n+`core.sparseCheckoutCone`, allow for very fast pattern matching to\n+discover which files at HEAD belong in the sparse-checkout cone.\n+\n+Three important scale dimensions for a Git working directory are:\n+\n+* `HEAD`: How many files are present at `HEAD`?\n+\n+* Populated: How many files are within the sparse-checkout cone.\n+\n+* Modified: How many files has the user modified in the working directory?\n+\n+We will use big-O notation -- O(X) -- to denote how expensive certain\n+operations are in terms of these dimensions.\n+\n+These dimensions are ordered by their magnitude: users (typically) modify\n+fewer files than are populated, and we can only populate files at `HEAD`.\n+\n+Problems occur if there is an extreme imbalance in these dimensions. For\n+example, if `HEAD` contains millions of paths but the populated set has\n+only tens of thousands, then commands like `git status` and `git add` can\n+be dominated by operations that require O(`HEAD`) operations instead of\n+O(Populated). Primarily, the cost is in parsing and rewriting the index,\n+which is filled primarily with files at `HEAD` that are marked with the\n+`SKIP_WORKTREE` bit.\n+\n+The sparse-index intends to take these commands that read and modify the\n+index from O(`HEAD`) to O(Populated). To do this, we need to modify the\n+index format in a significant way: add \"sparse directory\" entries.\n+\n+With cone mode patterns, it is possible to detect when an entire\n+directory will have its contents outside of the sparse-checkout definition.\n+Instead of listing all of the files it contains as individual entries, a\n+sparse-index contains an entry with the directory name, referencing the\n+object ID of the tree at `HEAD` and marked with the `SKIP_WORKTREE` bit.\n+If we need to discover the details for paths within that directory, we\n+can parse trees to find that list.\n+\n+At time of writing, sparse-directory entries violate expectations about the\n+index format and its in-memory data structure. There are many consumers in\n+the codebase that expect to iterate through all of the index entries and\n+see only files. In fact, these loops expect to see a reference to every\n+staged file. One way to handle this is to parse trees to replace a\n+sparse-directory entry with all of the files within that tree as the index\n+is loaded. However, parsing trees is slower than parsing the index format,\n+so that is a slower operation than if we left the index alone. The plan is\n+to make all of these integrations \"sparse aware\" so this expansion through\n+tree parsing is unnecessary and they use fewer resources than when using a\n+full index.\n+\n+The implementation plan below follows four phases to slowly integrate with\n+the sparse-index. The intention is to incrementally update Git commands to\n+interact safely with the sparse-index without significant slowdowns. This\n+may not always be possible, but the hope is that the primary commands that\n+users need in their daily work are dramatically improved.\n+\n+Phase I: Format and initial speedups\n+------------------------------------\n+\n+During this phase, Git learns to enable the sparse-index and safely parse\n+one. Protections are put in place so that every consumer of the in-memory\n+data structure can operate with its current assumption of every file at\n+`HEAD`.\n+\n+At first, every index parse will call a helper method,\n+`ensure_full_index()`, which scans the index for sparse-directory entries\n+(pointing to trees) and replaces them with the full list of paths (with\n+blob contents) by parsing tree objects. This will be slower in all cases.\n+The only noticeable change in behavior will be that the serialized index\n+file contains sparse-directory entries.\n+\n+To start, we use a new required index extension, `sdir`, to allow\n+inserting sparse-directory entries into indexes with file format\n+versions 2, 3, and 4. This prevents Git versions that do not understand\n+the sparse-index from operating on one, while allowing tools that do not\n+understand the sparse-index to operate on repositories as long as they do\n+not interact with the index. A new format, index v5, will be introduced\n+that includes sparse-directory entries by default. It might also\n+introduce other features that have been considered for improving the\n+index, as well.\n+\n+Next, consumers of the index will be guarded against operating on a\n+sparse-index by inserting calls to `ensure_full_index()` or\n+`expand_index_to_path()`. After these guards are in place, we can begin\n+leaving sparse-directory entries in the in-memory index structure.\n+\n+Even after inserting these guards, we will keep expanding sparse-indexes\n+for most Git commands using the `command_requires_full_index` repository\n+setting. This setting will be on by default and disabled one builtin at a\n+time until we have sufficient confidence that all of the index operations\n+are properly guarded.\n+\n+To complete this phase, the commands `git status` and `git add` will be\n+integrated with the sparse-index so that they operate with O(Populated)\n+performance. They will be carefully tested for operations within and\n+outside the sparse-checkout definition.\n+\n+Phase II: Careful integrations\n+------------------------------\n+\n+This phase focuses on ensuring that all index extensions and APIs work\n+well with a sparse-index. This requires significant increases to our test\n+coverage, especially for operations that interact with the working\n+directory outside of the sparse-checkout definition. Some of these\n+behaviors may not be the desirable ones, such as some tests already\n+marked for failure in `t1092-sparse-checkout-compatibility.sh`.\n+\n+The index extensions that may require special integrations are:\n+\n+* FS Monitor\n+* Untracked cache\n+\n+While integrating with these features, we should look for patterns that\n+might lead to better APIs for interacting with the index. Coalescing\n+common usage patterns into an API call can reduce the number of places\n+where sparse-directories need to be handled carefully.\n+\n+Phase III: Important command speedups\n+-------------------------------------\n+\n+At this point, the patterns for testing and implementing sparse-directory\n+logic should be relatively stable. This phase focuses on updating some of\n+the most common builtins that use the index to operate as O(Populated).\n+Here is a potential list of commands that could be valuable to integrate\n+at this point:\n+\n+* `git commit`\n+* `git checkout`\n+* `git merge`\n+* `git rebase`\n+\n+Hopefully, commands such as `git merge` and `git rebase` can benefit\n+instead from merge algorithms that do not use the index as a data\n+structure, such as the merge-ORT strategy. As these topics mature, we\n+may enable the ORT strategy by default for repositories using the\n+sparse-index feature.\n+\n+Along with `git status` and `git add`, these commands cover the majority\n+of users' interactions with the working directory. In addition, we can\n+integrate with these commands:\n+\n+* `git grep`\n+* `git rm`\n+\n+These have been proposed as some whose behavior could change when in a\n+repo with a sparse-checkout definition. It would be good to include this\n+behavior automatically when using a sparse-index. Some clarity is needed\n+to make the behavior switch clear to the user.\n+\n+This phase is the first where parallel work might be possible without too\n+much conflicts between topics.\n+\n+Phase IV: The long tail\n+-----------------------\n+\n+This last phase is less a \"phase\" and more \"the new normal\" after all of\n+the previous work.\n+\n+To start, the `command_requires_full_index` option could be removed in\n+favor of expanding only when hitting an API guard.\n+\n+There are many Git commands that could use special attention to operate as\n+O(Populated), while some might be so rare that it is acceptable to leave\n+them with additional overhead when a sparse-index is present.\n+\n+Here are some commands that might be useful to update:\n+\n+* `git sparse-checkout set`\n+* `git am`\n+* `git clean`\n+* `git stash`\n-- \ngitgitgadget\n\n"},{"id":"420581","messageId":"e41d55d2cca906af2f11265de4d8977b1881219e.1617109864.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 05/21] sparse-index: implement ensure_full_index()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:10:48Z","receivedAt":"2021-03-30T13:12:13Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWe will mark an in-memory index_state as having sparse directory entries\nwith the sparse_index bit. These currently cannot exist, but we will add\na mechanism for collapsing a full index to a sparse one in a later\nchange. That will happen at write time, so we must first allow parsing\nthe format before writing it.\n\nCommands or methods that require a full index in order to operate can\ncall ensure_full_index() to expand that index in-memory. This requires\nparsing trees using that index's repository.\n\nSparse directory entries have a specific 'ce_mode' value. The macro\nS_ISSPARSEDIR(ce->ce_mode) can check if a cache_entry 'ce' has this type.\nThis ce_mode is not possible with the existing index formats, so we don't\nalso verify all properties of a sparse-directory entry, which are:\n\n 1. ce->ce_mode == 0040000\n 2. ce->flags & CE_SKIP_WORKTREE is true\n 3. ce->name[ce->namelen - 1] == '/' (ends in dir separator)\n 4. ce->oid references a tree object.\n\nThese are all semi-enforced in ensure_full_index() to some extent. Any\ndeviation will cause a warning at minimum or a failure in the worst\ncase.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache.h        | 13 ++++++-\n read-cache.c   |  9 +++++\n sparse-index.c | 98 +++++++++++++++++++++++++++++++++++++++++++++++++-\n 3 files changed, 118 insertions(+), 2 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex bb317abc91fb..136dd496c95d 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -204,6 +204,8 @@ struct cache_entry {\n #error \"CE_EXTENDED_FLAGS out of range\"\n #endif\n \n+#define S_ISSPARSEDIR(m) ((m) == S_IFDIR)\n+\n /* Forward structure decls */\n struct pathspec;\n struct child_process;\n@@ -319,7 +321,14 @@ struct index_state {\n \t\t drop_cache_tree : 1,\n \t\t updated_workdir : 1,\n \t\t updated_skipworktree : 1,\n-\t\t fsmonitor_has_run_once : 1;\n+\t\t fsmonitor_has_run_once : 1,\n+\n+\t\t /*\n+\t\t  * sparse_index == 1 when sparse-directory\n+\t\t  * entries exist. Requires sparse-checkout\n+\t\t  * in cone mode.\n+\t\t  */\n+\t\t sparse_index : 1;\n \tstruct hashmap name_hash;\n \tstruct hashmap dir_hash;\n \tstruct object_id oid;\n@@ -722,6 +731,8 @@ int read_index_from(struct index_state *, const char *path,\n \t\t    const char *gitdir);\n int is_index_unborn(struct index_state *);\n \n+void ensure_full_index(struct index_state *istate);\n+\n /* For use with `write_locked_index()`. */\n #define COMMIT_LOCK\t\t(1 << 0)\n #define SKIP_IF_UNCHANGED\t(1 << 1)\ndiff --git a/read-cache.c b/read-cache.c\nindex 1e9a50c6c734..dd3980c12b53 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -101,6 +101,9 @@ static const char *alternate_index_output;\n \n static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n {\n+\tif (S_ISSPARSEDIR(ce->ce_mode))\n+\t\tistate->sparse_index = 1;\n+\n \tistate->cache[nr] = ce;\n \tadd_name_hash(istate, ce);\n }\n@@ -2273,6 +2276,12 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)\n \ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\",\n \t\t\t   istate->cache_nr);\n \n+\tif (!istate->repo)\n+\t\tistate->repo = the_repository;\n+\tprepare_repo_settings(istate->repo);\n+\tif (istate->repo->settings.command_requires_full_index)\n+\t\tensure_full_index(istate);\n+\n \treturn istate->cache_nr;\n \n unmap:\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 82183ead563b..7095378a1b28 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -1,8 +1,104 @@\n #include \"cache.h\"\n #include \"repository.h\"\n #include \"sparse-index.h\"\n+#include \"tree.h\"\n+#include \"pathspec.h\"\n+#include \"trace2.h\"\n+\n+static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n+{\n+\tALLOC_GROW(istate->cache, nr + 1, istate->cache_alloc);\n+\n+\tistate->cache[nr] = ce;\n+\tadd_name_hash(istate, ce);\n+}\n+\n+static int add_path_to_index(const struct object_id *oid,\n+\t\t\t     struct strbuf *base, const char *path,\n+\t\t\t     unsigned int mode, void *context)\n+{\n+\tstruct index_state *istate = (struct index_state *)context;\n+\tstruct cache_entry *ce;\n+\tsize_t len = base->len;\n+\n+\tif (S_ISDIR(mode))\n+\t\treturn READ_TREE_RECURSIVE;\n+\n+\tstrbuf_addstr(base, path);\n+\n+\tce = make_cache_entry(istate, mode, oid, base->buf, 0, 0);\n+\tce->ce_flags |= CE_SKIP_WORKTREE;\n+\tset_index_entry(istate, istate->cache_nr++, ce);\n+\n+\tstrbuf_setlen(base, len);\n+\treturn 0;\n+}\n \n void ensure_full_index(struct index_state *istate)\n {\n-\t/* intentionally left blank */\n+\tint i;\n+\tstruct index_state *full;\n+\tstruct strbuf base = STRBUF_INIT;\n+\n+\tif (!istate || !istate->sparse_index)\n+\t\treturn;\n+\n+\tif (!istate->repo)\n+\t\tistate->repo = the_repository;\n+\n+\ttrace2_region_enter(\"index\", \"ensure_full_index\", istate->repo);\n+\n+\t/* initialize basics of new index */\n+\tfull = xcalloc(1, sizeof(struct index_state));\n+\tmemcpy(full, istate, sizeof(struct index_state));\n+\n+\t/* then change the necessary things */\n+\tfull->sparse_index = 0;\n+\tfull->cache_alloc = (3 * istate->cache_alloc) / 2;\n+\tfull->cache_nr = 0;\n+\tALLOC_ARRAY(full->cache, full->cache_alloc);\n+\n+\tfor (i = 0; i < istate->cache_nr; i++) {\n+\t\tstruct cache_entry *ce = istate->cache[i];\n+\t\tstruct tree *tree;\n+\t\tstruct pathspec ps;\n+\n+\t\tif (!S_ISSPARSEDIR(ce->ce_mode)) {\n+\t\t\tset_index_entry(full, full->cache_nr++, ce);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!(ce->ce_flags & CE_SKIP_WORKTREE))\n+\t\t\twarning(_(\"index entry is a directory, but not sparse (%08x)\"),\n+\t\t\t\tce->ce_flags);\n+\n+\t\t/* recursively walk into cd->name */\n+\t\ttree = lookup_tree(istate->repo, &ce->oid);\n+\n+\t\tmemset(&ps, 0, sizeof(ps));\n+\t\tps.recursive = 1;\n+\t\tps.has_wildcard = 1;\n+\t\tps.max_depth = -1;\n+\n+\t\tstrbuf_setlen(&base, 0);\n+\t\tstrbuf_add(&base, ce->name, strlen(ce->name));\n+\n+\t\tread_tree_at(istate->repo, tree, &base, &ps,\n+\t\t\t     add_path_to_index, full);\n+\n+\t\t/* free directory entries. full entries are re-used */\n+\t\tdiscard_cache_entry(ce);\n+\t}\n+\n+\t/* Copy back into original index. */\n+\tmemcpy(&istate->name_hash, &full->name_hash, sizeof(full->name_hash));\n+\tistate->sparse_index = 0;\n+\tfree(istate->cache);\n+\tistate->cache = full->cache;\n+\tistate->cache_nr = full->cache_nr;\n+\tistate->cache_alloc = full->cache_alloc;\n+\n+\tstrbuf_release(&base);\n+\tfree(full);\n+\n+\ttrace2_region_leave(\"index\", \"ensure_full_index\", istate->repo);\n }\n-- \ngitgitgadget\n\n"},{"id":"420596","messageId":"dd84a2a9121b52c4b9bf0a52d6e52f3e40075c6f.1617109864.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 08/21] test-tool: don't force full index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:10:51Z","receivedAt":"2021-03-30T13:12:13Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWe will use 'test-tool read-cache --table' to check that a sparse\nindex is written as part of init_repos. Since we will no longer always\nexpand a sparse index into a full index, add an '--expand' parameter\nthat adds a call to ensure_full_index() so we can compare a sparse index\ndirectly against a full index, or at least what the in-memory index\nlooks like when expanded in this way.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/helper/test-read-cache.c               | 13 ++++++++++++-\n t/t1092-sparse-checkout-compatibility.sh |  5 +++++\n 2 files changed, 17 insertions(+), 1 deletion(-)\n\ndiff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c\nindex 6cfd8f2de71c..b52c174acc7a 100644\n--- a/t/helper/test-read-cache.c\n+++ b/t/helper/test-read-cache.c\n@@ -4,6 +4,7 @@\n #include \"blob.h\"\n #include \"commit.h\"\n #include \"tree.h\"\n+#include \"sparse-index.h\"\n \n static void print_cache_entry(struct cache_entry *ce)\n {\n@@ -35,13 +36,19 @@ int cmd__read_cache(int argc, const char **argv)\n \tstruct repository *r = the_repository;\n \tint i, cnt = 1;\n \tconst char *name = NULL;\n-\tint table = 0;\n+\tint table = 0, expand = 0;\n+\n+\tinitialize_the_repository();\n+\tprepare_repo_settings(r);\n+\tr->settings.command_requires_full_index = 0;\n \n \tfor (++argv, --argc; *argv && starts_with(*argv, \"--\"); ++argv, --argc) {\n \t\tif (skip_prefix(*argv, \"--print-and-refresh=\", &name))\n \t\t\tcontinue;\n \t\tif (!strcmp(*argv, \"--table\"))\n \t\t\ttable = 1;\n+\t\telse if (!strcmp(*argv, \"--expand\"))\n+\t\t\texpand = 1;\n \t}\n \n \tif (argc == 1)\n@@ -51,6 +58,10 @@ int cmd__read_cache(int argc, const char **argv)\n \n \tfor (i = 0; i < cnt; i++) {\n \t\trepo_read_index(r);\n+\n+\t\tif (expand)\n+\t\t\tensure_full_index(r->index);\n+\n \t\tif (name) {\n \t\t\tint pos;\n \ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex de5d8461c993..a1aea141c62c 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -130,6 +130,11 @@ test_sparse_match () {\n \ttest_cmp sparse-checkout-err sparse-index-err\n }\n \n+test_expect_success 'expanded in-memory index matches full index' '\n+\tinit_repos &&\n+\ttest_sparse_match test-tool read-cache --expand --table\n+'\n+\n test_expect_success 'status with options' '\n \tinit_repos &&\n \ttest_all_match git status --porcelain=v2 &&\n-- \ngitgitgadget\n\n"},{"id":"420580","messageId":"7bfbfbd17321942f8f3a9b799eeb09bf1c58a947.1617109864.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 06/21] t1092: compare sparse-checkout to sparse-index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:10:49Z","receivedAt":"2021-03-30T13:12:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nAdd a new 'sparse-index' repo alongside the 'full-checkout' and\n'sparse-checkout' repos in t1092-sparse-checkout-compatibility.sh. Also\nadd run_on_sparse and test_sparse_match helpers. These helpers will be\nused when the sparse index is implemented.\n\nAdd the GIT_TEST_SPARSE_INDEX environment variable to enable the\nsparse-index by default. This can be enabled across all tests, but that\nwill only affect cases where the sparse-checkout feature is enabled.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/README                                 |  3 +++\n t/t1092-sparse-checkout-compatibility.sh | 24 ++++++++++++++++++++----\n 2 files changed, 23 insertions(+), 4 deletions(-)\n\ndiff --git a/t/README b/t/README\nindex 593d4a4e270c..b98bc563aab5 100644\n--- a/t/README\n+++ b/t/README\n@@ -439,6 +439,9 @@ and \"sha256\".\n GIT_TEST_WRITE_REV_INDEX=<boolean>, when true enables the\n 'pack.writeReverseIndex' setting.\n \n+GIT_TEST_SPARSE_INDEX=<boolean>, when true enables index writes to use the\n+sparse-index format by default.\n+\n Naming Tests\n ------------\n \ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 3725d3997e70..de5d8461c993 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -7,6 +7,7 @@ test_description='compare full workdir to sparse workdir'\n test_expect_success 'setup' '\n \tgit init initial-repo &&\n \t(\n+\t\tGIT_TEST_SPARSE_INDEX=0 &&\n \t\tcd initial-repo &&\n \t\techo a >a &&\n \t\techo \"after deep\" >e &&\n@@ -87,23 +88,32 @@ init_repos () {\n \n \tcp -r initial-repo sparse-checkout &&\n \tgit -C sparse-checkout reset --hard &&\n-\tgit -C sparse-checkout sparse-checkout init --cone &&\n+\n+\tcp -r initial-repo sparse-index &&\n+\tgit -C sparse-index reset --hard &&\n \n \t# initialize sparse-checkout definitions\n-\tgit -C sparse-checkout sparse-checkout set deep\n+\tgit -C sparse-checkout sparse-checkout init --cone &&\n+\tgit -C sparse-checkout sparse-checkout set deep &&\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout init --cone &&\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep\n }\n \n run_on_sparse () {\n \t(\n \t\tcd sparse-checkout &&\n-\t\t\"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n+\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n+\t) &&\n+\t(\n+\t\tcd sparse-index &&\n+\t\tGIT_TEST_SPARSE_INDEX=1 \"$@\" >../sparse-index-out 2>../sparse-index-err\n \t)\n }\n \n run_on_all () {\n \t(\n \t\tcd full-checkout &&\n-\t\t\"$@\" >../full-checkout-out 2>../full-checkout-err\n+\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../full-checkout-out 2>../full-checkout-err\n \t) &&\n \trun_on_sparse \"$@\"\n }\n@@ -114,6 +124,12 @@ test_all_match () {\n \ttest_cmp full-checkout-err sparse-checkout-err\n }\n \n+test_sparse_match () {\n+\trun_on_sparse \"$@\" &&\n+\ttest_cmp sparse-checkout-out sparse-index-out &&\n+\ttest_cmp sparse-checkout-err sparse-index-err\n+}\n+\n test_expect_success 'status with options' '\n \tinit_repos &&\n \ttest_all_match git status --porcelain=v2 &&\n-- \ngitgitgadget\n\n"},{"id":"420582","messageId":"b7e1bf5c55a72bf3da6cc28583f4a241a4c6253f.1617109864.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 04/21] sparse-index: add guard to ensure full index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:10:47Z","receivedAt":"2021-03-30T13:12:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nUpcoming changes will introduce modifications to the index format that\nallow sparse directories. It will be useful to have a mechanism for\nconverting those sparse index files into full indexes by walking the\ntree at those sparse directories. Name this method ensure_full_index()\nas it will guarantee that the index is fully expanded.\n\nThis method is not implemented yet, and instead we focus on the\nscaffolding to declare it and call it at the appropriate time.\n\nAdd a 'command_requires_full_index' member to struct repo_settings. This\nwill be an indicator that we need the index in full mode to do certain\nindex operations. This starts as being true for every command, then we\nwill set it to false as some commands integrate with sparse indexes.\n\nIf 'command_requires_full_index' is true, then we will immediately\nexpand a sparse index to a full one upon reading from disk. This\nsuffices for now, but we will want to add more callers to\nensure_full_index() later.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Makefile        |  1 +\n repo-settings.c |  8 ++++++++\n repository.c    | 11 ++++++++++-\n repository.h    |  2 ++\n sparse-index.c  |  8 ++++++++\n sparse-index.h  |  7 +++++++\n 6 files changed, 36 insertions(+), 1 deletion(-)\n create mode 100644 sparse-index.c\n create mode 100644 sparse-index.h\n\ndiff --git a/Makefile b/Makefile\nindex dfb0f1000fa3..89b1d5374107 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -985,6 +985,7 @@ LIB_OBJS += setup.o\n LIB_OBJS += shallow.o\n LIB_OBJS += sideband.o\n LIB_OBJS += sigchain.o\n+LIB_OBJS += sparse-index.o\n LIB_OBJS += split-index.o\n LIB_OBJS += stable-qsort.o\n LIB_OBJS += strbuf.o\ndiff --git a/repo-settings.c b/repo-settings.c\nindex f7fff0f5ab83..d63569e4041e 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -77,4 +77,12 @@ void prepare_repo_settings(struct repository *r)\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+\t/*\n+\t * This setting guards all index reads to require a full index\n+\t * over a sparse index. After suitable guards are placed in the\n+\t * codebase around uses of the index, this setting will be\n+\t * removed.\n+\t */\n+\tr->settings.command_requires_full_index = 1;\n }\ndiff --git a/repository.c b/repository.c\nindex c98298acd017..a8acae002f71 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -10,6 +10,7 @@\n #include \"object.h\"\n #include \"lockfile.h\"\n #include \"submodule-config.h\"\n+#include \"sparse-index.h\"\n \n /* The main repository */\n static struct repository the_repo;\n@@ -261,6 +262,8 @@ void repo_clear(struct repository *repo)\n \n int repo_read_index(struct repository *repo)\n {\n+\tint res;\n+\n \tif (!repo->index)\n \t\trepo->index = xcalloc(1, sizeof(*repo->index));\n \n@@ -270,7 +273,13 @@ int repo_read_index(struct repository *repo)\n \telse if (repo->index->repo != repo)\n \t\tBUG(\"repo's index should point back at itself\");\n \n-\treturn read_index_from(repo->index, repo->index_file, repo->gitdir);\n+\tres = read_index_from(repo->index, repo->index_file, repo->gitdir);\n+\n+\tprepare_repo_settings(repo);\n+\tif (repo->settings.command_requires_full_index)\n+\t\tensure_full_index(repo->index);\n+\n+\treturn res;\n }\n \n int repo_hold_locked_index(struct repository *repo,\ndiff --git a/repository.h b/repository.h\nindex b385ca3c94b6..e06a23015697 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -41,6 +41,8 @@ struct repo_settings {\n \tenum fetch_negotiation_setting fetch_negotiation_algorithm;\n \n \tint core_multi_pack_index;\n+\n+\tunsigned command_requires_full_index:1;\n };\n \n struct repository {\ndiff --git a/sparse-index.c b/sparse-index.c\nnew file mode 100644\nindex 000000000000..82183ead563b\n--- /dev/null\n+++ b/sparse-index.c\n@@ -0,0 +1,8 @@\n+#include \"cache.h\"\n+#include \"repository.h\"\n+#include \"sparse-index.h\"\n+\n+void ensure_full_index(struct index_state *istate)\n+{\n+\t/* intentionally left blank */\n+}\ndiff --git a/sparse-index.h b/sparse-index.h\nnew file mode 100644\nindex 000000000000..09a20d036c46\n--- /dev/null\n+++ b/sparse-index.h\n@@ -0,0 +1,7 @@\n+#ifndef SPARSE_INDEX_H__\n+#define SPARSE_INDEX_H__\n+\n+struct index_state;\n+void ensure_full_index(struct index_state *istate);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"420583","messageId":"c3651e26dc3ace4602eeb7c7bbe3ee15684de257.1617109864.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 10/21] sparse-checkout: hold pattern list in index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:10:53Z","receivedAt":"2021-03-30T13:12:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nAs we modify the sparse-checkout definition, we perform index operations\non a pattern_list that only exists in-memory. This allows easy backing\nout in case the index update fails.\n\nHowever, if the index write itself cares about the sparse-checkout\npattern set, we need access to that in-memory copy. Place a pointer to\na 'struct pattern_list' in the index so we can access this on-demand.\nThis will be used in the next change which uses the sparse-checkout\ndefinition to filter out directories that are outside the sparse cone.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/sparse-checkout.c | 17 ++++++++++-------\n cache.h                   |  2 ++\n 2 files changed, 12 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex 2306a9ad98e0..e00b82af727b 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -110,6 +110,8 @@ static int update_working_directory(struct pattern_list *pl)\n \tif (is_index_unborn(r->index))\n \t\treturn UPDATE_SPARSITY_SUCCESS;\n \n+\tr->index->sparse_checkout_patterns = pl;\n+\n \tmemset(&o, 0, sizeof(o));\n \to.verbose_update = isatty(2);\n \to.update = 1;\n@@ -138,6 +140,7 @@ static int update_working_directory(struct pattern_list *pl)\n \telse\n \t\trollback_lock_file(&lock_file);\n \n+\tr->index->sparse_checkout_patterns = NULL;\n \treturn result;\n }\n \n@@ -517,19 +520,18 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)\n {\n \tint result;\n \tint changed_config = 0;\n-\tstruct pattern_list pl;\n-\tmemset(&pl, 0, sizeof(pl));\n+\tstruct pattern_list *pl = xcalloc(1, sizeof(*pl));\n \n \tswitch (m) {\n \tcase ADD:\n \t\tif (core_sparse_checkout_cone)\n-\t\t\tadd_patterns_cone_mode(argc, argv, &pl);\n+\t\t\tadd_patterns_cone_mode(argc, argv, pl);\n \t\telse\n-\t\t\tadd_patterns_literal(argc, argv, &pl);\n+\t\t\tadd_patterns_literal(argc, argv, pl);\n \t\tbreak;\n \n \tcase REPLACE:\n-\t\tadd_patterns_from_input(&pl, argc, argv);\n+\t\tadd_patterns_from_input(pl, argc, argv);\n \t\tbreak;\n \t}\n \n@@ -539,12 +541,13 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)\n \t\tchanged_config = 1;\n \t}\n \n-\tresult = write_patterns_and_update(&pl);\n+\tresult = write_patterns_and_update(pl);\n \n \tif (result && changed_config)\n \t\tset_config(MODE_NO_PATTERNS);\n \n-\tclear_pattern_list(&pl);\n+\tclear_pattern_list(pl);\n+\tfree(pl);\n \treturn result;\n }\n \ndiff --git a/cache.h b/cache.h\nindex 136dd496c95d..8c4464420d0a 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -307,6 +307,7 @@ static inline unsigned int canon_mode(unsigned int mode)\n struct split_index;\n struct untracked_cache;\n struct progress;\n+struct pattern_list;\n \n struct index_state {\n \tstruct cache_entry **cache;\n@@ -338,6 +339,7 @@ struct index_state {\n \tstruct mem_pool *ce_mem_pool;\n \tstruct progress *progress;\n \tstruct repository *repo;\n+\tstruct pattern_list *sparse_checkout_patterns;\n };\n \n /* Name hashing */\n-- \ngitgitgadget\n\n"},{"id":"420585","messageId":"202253ec82f34d1302ee7360e4d458ac4af89e82.1617109864.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 02/21] t/perf: add performance test for sparse operations","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:10:45Z","receivedAt":"2021-03-30T13:12:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nCreate a test script that takes the default performance test (the Git\ncodebase) and multiplies it by 256 using four layers of duplicated\ntrees of width four. This results in nearly one million blob entries in\nthe index. Then, we can clone this repository with sparse-checkout\npatterns that demonstrate four copies of the initial repository. Each\nclone will use a different index format or mode so peformance can be\ntested across the different options.\n\nNote that the initial repo is stripped of submodules before doing the\ncopies. This preserves the expected data shape of the sparse index,\nbecause directories containing submodules are not collapsed to a sparse\ndirectory entry.\n\nRun a few Git commands on these clones, especially those that use the\nindex (status, add, commit).\n\nHere are the results on my Linux machine:\n\nTest\n--------------------------------------------------------------\n2000.2: git status (full-index-v3)             0.37(0.30+0.09)\n2000.3: git status (full-index-v4)             0.39(0.32+0.10)\n2000.4: git add -A (full-index-v3)             1.42(1.06+0.20)\n2000.5: git add -A (full-index-v4)             1.26(0.98+0.16)\n2000.6: git add . (full-index-v3)              1.40(1.04+0.18)\n2000.7: git add . (full-index-v4)              1.26(0.98+0.17)\n2000.8: git commit -a -m A (full-index-v3)     1.42(1.11+0.16)\n2000.9: git commit -a -m A (full-index-v4)     1.33(1.08+0.16)\n\nIt is perhaps noteworthy that there is an improvement when using index\nversion 4. This is because the v3 index uses 108 MiB while the v4\nindex uses 80 MiB. Since the repeated portions of the directories are\nvery short (f3/f1/f2, for example) this ratio is less pronounced than in\nsimilarly-sized real repositories.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/perf/p2000-sparse-operations.sh | 84 +++++++++++++++++++++++++++++++\n 1 file changed, 84 insertions(+)\n create mode 100755 t/perf/p2000-sparse-operations.sh\n\ndiff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh\nnew file mode 100755\nindex 000000000000..dddd527b6330\n--- /dev/null\n+++ b/t/perf/p2000-sparse-operations.sh\n@@ -0,0 +1,84 @@\n+#!/bin/sh\n+\n+test_description=\"test performance of Git operations using the index\"\n+\n+. ./perf-lib.sh\n+\n+test_perf_default_repo\n+\n+SPARSE_CONE=f2/f4/f1\n+\n+test_expect_success 'setup repo and indexes' '\n+\tgit reset --hard HEAD &&\n+\n+\t# Remove submodules from the example repo, because our\n+\t# duplication of the entire repo creates an unlikely data shape.\n+\tif git config --file .gitmodules --get-regexp \"submodule.*.path\" >modules\n+\tthen\n+\t\tgit rm $(awk \"{print \\$2}\" modules) &&\n+\t\tgit commit -m \"remove submodules\" || return 1\n+\tfi &&\n+\n+\techo bogus >a &&\n+\tcp a b &&\n+\tgit add a b &&\n+\tgit commit -m \"level 0\" &&\n+\tBLOB=$(git rev-parse HEAD:a) &&\n+\tOLD_COMMIT=$(git rev-parse HEAD) &&\n+\tOLD_TREE=$(git rev-parse HEAD^{tree}) &&\n+\n+\tfor i in $(test_seq 1 4)\n+\tdo\n+\t\tcat >in <<-EOF &&\n+\t\t\t100755 blob $BLOB\ta\n+\t\t\t040000 tree $OLD_TREE\tf1\n+\t\t\t040000 tree $OLD_TREE\tf2\n+\t\t\t040000 tree $OLD_TREE\tf3\n+\t\t\t040000 tree $OLD_TREE\tf4\n+\t\tEOF\n+\t\tNEW_TREE=$(git mktree <in) &&\n+\t\tNEW_COMMIT=$(git commit-tree $NEW_TREE -p $OLD_COMMIT -m \"level $i\") &&\n+\t\tOLD_TREE=$NEW_TREE &&\n+\t\tOLD_COMMIT=$NEW_COMMIT || return 1\n+\tdone &&\n+\n+\tgit sparse-checkout init --cone &&\n+\tgit branch -f wide $OLD_COMMIT &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v3 &&\n+\t(\n+\t\tcd full-index-v3 &&\n+\t\tgit sparse-checkout init --cone &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 3 &&\n+\t\tgit update-index --index-version=3\n+\t) &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v4 &&\n+\t(\n+\t\tcd full-index-v4 &&\n+\t\tgit sparse-checkout init --cone &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 4 &&\n+\t\tgit update-index --index-version=4\n+\t)\n+'\n+\n+test_perf_on_all () {\n+\tcommand=\"$@\"\n+\tfor repo in full-index-v3 full-index-v4\n+\tdo\n+\t\ttest_perf \"$command ($repo)\" \"\n+\t\t\t(\n+\t\t\t\tcd $repo &&\n+\t\t\t\techo >>$SPARSE_CONE/a &&\n+\t\t\t\t$command\n+\t\t\t)\n+\t\t\"\n+\tdone\n+}\n+\n+test_perf_on_all git status\n+test_perf_on_all git add -A\n+test_perf_on_all git add .\n+test_perf_on_all git commit -a -m A\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"420588","messageId":"bcf0da959ef35f989949b40ae1a386885d116331.1617109864.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 13/21] submodule: sparse-index should not collapse links","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:10:56Z","receivedAt":"2021-03-30T13:12:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nA submodule is stored as a \"Git link\" that actually points to a commit\nwithin a submodule. Submodules are populated or not depending on\nsubmodule configuration, not sparse-checkout. To ensure that the\nsparse-index feature integrates correctly with submodules, we should not\ncollapse a directory if there is a Git link within its range.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n sparse-index.c                           |  1 +\n t/t1092-sparse-checkout-compatibility.sh | 17 +++++++++++++++++\n 2 files changed, 18 insertions(+)\n\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 619ff7c2e217..7631f7bd00b7 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -52,6 +52,7 @@ static int convert_to_sparse_rec(struct index_state *istate,\n \t\tstruct cache_entry *ce = istate->cache[i];\n \n \t\tif (ce_stage(ce) ||\n+\t\t    S_ISGITLINK(ce->ce_mode) ||\n \t\t    !(ce->ce_flags & CE_SKIP_WORKTREE))\n \t\t\tcan_convert = 0;\n \t}\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 1e888d195122..cba5f89b1e96 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -376,4 +376,21 @@ test_expect_success 'clean' '\n \ttest_sparse_match test_path_is_dir folder1\n '\n \n+test_expect_success 'submodule handling' '\n+\tinit_repos &&\n+\n+\ttest_all_match mkdir modules &&\n+\ttest_all_match touch modules/a &&\n+\ttest_all_match git add modules &&\n+\ttest_all_match git commit -m \"add modules directory\" &&\n+\n+\trun_on_all git submodule add \"$(pwd)/initial-repo\" modules/sub &&\n+\ttest_all_match git commit -m \"add submodule\" &&\n+\n+\t# having a submodule prevents \"modules\" from collapse\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tgrep \"100644 blob .*\tmodules/a\" cache &&\n+\tgrep \"160000 commit $(git -C initial-repo rev-parse HEAD)\tmodules/sub\" cache\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"420589","messageId":"57be9b4a728b2bb16c841d3bad3c9c876a8839e0.1617109865.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 15/21] sparse-index: check index conversion happens","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:10:58Z","receivedAt":"2021-03-30T13:12:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nAdd a test case that uses test_region to ensure that we are truly\nexpanding a sparse index to a full one, then converting back to sparse\nwhen writing the index. As we integrate more Git commands with the\nsparse index, we will convert these commands to check that we do _not_\nconvert the sparse index to a full index and instead stay sparse the\nentire time.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/t1092-sparse-checkout-compatibility.sh | 18 ++++++++++++++++++\n 1 file changed, 18 insertions(+)\n\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex cba5f89b1e96..47f983217852 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -393,4 +393,22 @@ test_expect_success 'submodule handling' '\n \tgrep \"160000 commit $(git -C initial-repo rev-parse HEAD)\tmodules/sub\" cache\n '\n \n+test_expect_success 'sparse-index is expanded and converted back' '\n+\tinit_repos &&\n+\n+\t(\n+\t\tGIT_TEST_SPARSE_INDEX=1 &&\n+\t\texport GIT_TEST_SPARSE_INDEX &&\n+\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" reset --hard &&\n+\t\ttest_region index convert_to_sparse trace2.txt &&\n+\t\ttest_region index ensure_full_index trace2.txt &&\n+\n+\t\trm trace2.txt &&\n+\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" status -uno &&\n+\t\ttest_region index ensure_full_index trace2.txt\n+\t)\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"420590","messageId":"c22b4111e49ef810810c54d4afba25db342c24a6.1617109865.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 16/21] sparse-index: add index.sparse config option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:10:59Z","receivedAt":"2021-03-30T13:12:14Z","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 enabled, this config option signals that index writes should\nattempt to use sparse-directory entries.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/index.txt |  5 +++++\n cache.h                        |  1 +\n repo-settings.c                |  7 +++++++\n repository.h                   |  3 ++-\n sparse-index.c                 | 34 +++++++++++++++++++++++++++++-----\n 5 files changed, 44 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config/index.txt b/Documentation/config/index.txt\nindex 7cb50b37e98d..75f3a2d10541 100644\n--- a/Documentation/config/index.txt\n+++ b/Documentation/config/index.txt\n@@ -14,6 +14,11 @@ index.recordOffsetTable::\n \tDefaults to 'true' if index.threads has been explicitly enabled,\n \t'false' otherwise.\n \n+index.sparse::\n+\tWhen enabled, write the index using sparse-directory entries. This\n+\thas no effect unless `core.sparseCheckout` and\n+\t`core.sparseCheckoutCone` are both enabled. Defaults to 'false'.\n+\n index.threads::\n \tSpecifies the number of threads to spawn when loading the index.\n \tThis is meant to reduce index load time on multiprocessor machines.\ndiff --git a/cache.h b/cache.h\nindex 74b43aaa2bd1..8aede373aeb3 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1059,6 +1059,7 @@ struct repository_format {\n \tint worktree_config;\n \tint is_bare;\n \tint hash_algo;\n+\tint sparse_index;\n \tchar *work_tree;\n \tstruct string_list unknown_extensions;\n \tstruct string_list v1_only_extensions;\ndiff --git a/repo-settings.c b/repo-settings.c\nindex d63569e4041e..0cfe8b787db2 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -85,4 +85,11 @@ void prepare_repo_settings(struct repository *r)\n \t * removed.\n \t */\n \tr->settings.command_requires_full_index = 1;\n+\n+\t/*\n+\t * Initialize this as off.\n+\t */\n+\tr->settings.sparse_index = 0;\n+\tif (!repo_config_get_bool(r, \"index.sparse\", &value) && value)\n+\t\tr->settings.sparse_index = 1;\n }\ndiff --git a/repository.h b/repository.h\nindex e06a23015697..a45f7520fd9e 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -42,7 +42,8 @@ struct repo_settings {\n \n \tint core_multi_pack_index;\n \n-\tunsigned command_requires_full_index:1;\n+\tunsigned command_requires_full_index:1,\n+\t\t sparse_index:1;\n };\n \n struct repository {\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 7631f7bd00b7..6f4d95d35b1e 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -102,19 +102,43 @@ static int convert_to_sparse_rec(struct index_state *istate,\n \treturn num_converted - start_converted;\n }\n \n+static int enable_sparse_index(struct repository *repo)\n+{\n+\tconst char *config_path = repo_git_path(repo, \"config.worktree\");\n+\n+\tgit_config_set_in_file_gently(config_path,\n+\t\t\t\t      \"index.sparse\",\n+\t\t\t\t      \"true\");\n+\n+\tprepare_repo_settings(repo);\n+\trepo->settings.sparse_index = 1;\n+\treturn 0;\n+}\n+\n int convert_to_sparse(struct index_state *istate)\n {\n \tif (istate->split_index || istate->sparse_index ||\n \t    !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n \t\treturn 0;\n \n+\tif (!istate->repo)\n+\t\tistate->repo = the_repository;\n+\n+\t/*\n+\t * The GIT_TEST_SPARSE_INDEX environment variable triggers the\n+\t * index.sparse config variable to be on.\n+\t */\n+\tif (git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0)) {\n+\t\tint err = enable_sparse_index(istate->repo);\n+\t\tif (err < 0)\n+\t\t\treturn err;\n+\t}\n+\n \t/*\n-\t * For now, only create a sparse index with the\n-\t * GIT_TEST_SPARSE_INDEX environment variable. We will relax\n-\t * this once we have a proper way to opt-in (and later still,\n-\t * opt-out).\n+\t * Only convert to sparse if index.sparse is set.\n \t */\n-\tif (!git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0))\n+\tprepare_repo_settings(istate->repo);\n+\tif (!istate->repo->settings.sparse_index)\n \t\treturn 0;\n \n \tif (!istate->sparse_checkout_patterns) {\n-- \ngitgitgadget\n\n"},{"id":"420591","messageId":"75fe9b0f57da2cfc152a0e48f7573d381b4c44d2.1617109865.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 17/21] sparse-checkout: toggle sparse index from builtin","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:11:00Z","receivedAt":"2021-03-30T13:12:14Z","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 sparse index extension is used to signal that index writes should be\nin sparse mode. This was only updated using GIT_TEST_SPARSE_INDEX=1.\n\nAdd a '--[no-]sparse-index' option to 'git sparse-checkout init' that\nspecifies if the sparse index should be used. It also updates the index\nto use the correct format, either way. Add a warning in the\ndocumentation that the use of a repository extension might reduce\ncompatibility with third-party tools. 'git sparse-checkout init' already\nsets extension.worktreeConfig, which places most sparse-checkout users\noutside of the scope of most third-party tools.\n\nUpdate t1092-sparse-checkout-compatibility.sh to use this CLI instead of\nGIT_TEST_SPARSE_INDEX=1.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-sparse-checkout.txt    | 14 +++++++\n builtin/sparse-checkout.c                | 17 ++++++++-\n sparse-index.c                           | 33 +++++++++++------\n sparse-index.h                           |  3 ++\n t/t1092-sparse-checkout-compatibility.sh | 47 +++++++++++++-----------\n 5 files changed, 80 insertions(+), 34 deletions(-)\n\ndiff --git a/Documentation/git-sparse-checkout.txt b/Documentation/git-sparse-checkout.txt\nindex a0eeaeb02ee3..fdcf43f87cb3 100644\n--- a/Documentation/git-sparse-checkout.txt\n+++ b/Documentation/git-sparse-checkout.txt\n@@ -45,6 +45,20 @@ To avoid interfering with other worktrees, it first enables the\n When `--cone` is provided, the `core.sparseCheckoutCone` setting is\n also set, allowing for better performance with a limited set of\n patterns (see 'CONE PATTERN SET' below).\n++\n+Use the `--[no-]sparse-index` option to toggle the use of the sparse\n+index format. This reduces the size of the index to be more closely\n+aligned with your sparse-checkout definition. This can have significant\n+performance advantages for commands such as `git status` or `git add`.\n+This feature is still experimental. Some commands might be slower with\n+a sparse index until they are properly integrated with the feature.\n++\n+**WARNING:** Using a sparse index requires modifying the index in a way\n+that is not completely understood by external tools. If you have trouble\n+with this compatibility, then run `git sparse-checkout init --no-sparse-index`\n+to rewrite your index to not be sparse. Older versions of Git will not\n+understand the sparse directory entries index extension and may fail to\n+interact with your repository until it is disabled.\n \n 'set'::\n \tWrite a set of patterns to the sparse-checkout file, as given as\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex e00b82af727b..ca63e2c64e95 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -14,6 +14,7 @@\n #include \"unpack-trees.h\"\n #include \"wt-status.h\"\n #include \"quote.h\"\n+#include \"sparse-index.h\"\n \n static const char *empty_base = \"\";\n \n@@ -283,12 +284,13 @@ static int set_config(enum sparse_checkout_mode mode)\n }\n \n static char const * const builtin_sparse_checkout_init_usage[] = {\n-\tN_(\"git sparse-checkout init [--cone]\"),\n+\tN_(\"git sparse-checkout init [--cone] [--[no-]sparse-index]\"),\n \tNULL\n };\n \n static struct sparse_checkout_init_opts {\n \tint cone_mode;\n+\tint sparse_index;\n } init_opts;\n \n static int sparse_checkout_init(int argc, const char **argv)\n@@ -303,11 +305,15 @@ static int sparse_checkout_init(int argc, const char **argv)\n \tstatic struct option builtin_sparse_checkout_init_options[] = {\n \t\tOPT_BOOL(0, \"cone\", &init_opts.cone_mode,\n \t\t\t N_(\"initialize the sparse-checkout in cone mode\")),\n+\t\tOPT_BOOL(0, \"sparse-index\", &init_opts.sparse_index,\n+\t\t\t N_(\"toggle the use of a sparse index\")),\n \t\tOPT_END(),\n \t};\n \n \trepo_read_index(the_repository);\n \n+\tinit_opts.sparse_index = -1;\n+\n \targc = parse_options(argc, argv, NULL,\n \t\t\t     builtin_sparse_checkout_init_options,\n \t\t\t     builtin_sparse_checkout_init_usage, 0);\n@@ -326,6 +332,15 @@ static int sparse_checkout_init(int argc, const char **argv)\n \tsparse_filename = get_sparse_checkout_filename();\n \tres = add_patterns_from_file_to_list(sparse_filename, \"\", 0, &pl, NULL);\n \n+\tif (init_opts.sparse_index >= 0) {\n+\t\tif (set_sparse_index_config(the_repository, init_opts.sparse_index) < 0)\n+\t\t\tdie(_(\"failed to modify sparse-index config\"));\n+\n+\t\t/* force an index rewrite */\n+\t\trepo_read_index(the_repository);\n+\t\tthe_repository->index->updated_workdir = 1;\n+\t}\n+\n \t/* If we already have a sparse-checkout file, use it. */\n \tif (res >= 0) {\n \t\tfree(sparse_filename);\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 6f4d95d35b1e..4c73772c6d6c 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -102,21 +102,32 @@ static int convert_to_sparse_rec(struct index_state *istate,\n \treturn num_converted - start_converted;\n }\n \n-static int enable_sparse_index(struct repository *repo)\n+static int set_index_sparse_config(struct repository *repo, int enable)\n {\n-\tconst char *config_path = repo_git_path(repo, \"config.worktree\");\n-\n-\tgit_config_set_in_file_gently(config_path,\n-\t\t\t\t      \"index.sparse\",\n-\t\t\t\t      \"true\");\n+\tint res;\n+\tchar *config_path = repo_git_path(repo, \"config.worktree\");\n+\tres = git_config_set_in_file_gently(config_path,\n+\t\t\t\t\t    \"index.sparse\",\n+\t\t\t\t\t    enable ? \"true\" : NULL);\n+\tfree(config_path);\n \n \tprepare_repo_settings(repo);\n \trepo->settings.sparse_index = 1;\n-\treturn 0;\n+\treturn res;\n+}\n+\n+int set_sparse_index_config(struct repository *repo, int enable)\n+{\n+\tint res = set_index_sparse_config(repo, enable);\n+\n+\tprepare_repo_settings(repo);\n+\trepo->settings.sparse_index = enable;\n+\treturn res;\n }\n \n int convert_to_sparse(struct index_state *istate)\n {\n+\tint test_env;\n \tif (istate->split_index || istate->sparse_index ||\n \t    !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n \t\treturn 0;\n@@ -128,11 +139,9 @@ int convert_to_sparse(struct index_state *istate)\n \t * The GIT_TEST_SPARSE_INDEX environment variable triggers the\n \t * index.sparse config variable to be on.\n \t */\n-\tif (git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0)) {\n-\t\tint err = enable_sparse_index(istate->repo);\n-\t\tif (err < 0)\n-\t\t\treturn err;\n-\t}\n+\ttest_env = git_env_bool(\"GIT_TEST_SPARSE_INDEX\", -1);\n+\tif (test_env >= 0)\n+\t\tset_sparse_index_config(istate->repo, test_env);\n \n \t/*\n \t * Only convert to sparse if index.sparse is set.\ndiff --git a/sparse-index.h b/sparse-index.h\nindex 64380e121d80..39dcc859735e 100644\n--- a/sparse-index.h\n+++ b/sparse-index.h\n@@ -5,4 +5,7 @@ struct index_state;\n void ensure_full_index(struct index_state *istate);\n int convert_to_sparse(struct index_state *istate);\n \n+struct repository;\n+int set_sparse_index_config(struct repository *repo, int enable);\n+\n #endif\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 47f983217852..472c5337de1b 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -6,6 +6,7 @@ test_description='compare full workdir to sparse workdir'\n # So, disable the check until that integration is complete.\n GIT_TEST_CHECK_CACHE_TREE=0\n GIT_TEST_SPLIT_INDEX=0\n+GIT_TEST_SPARSE_INDEX=\n \n . ./test-lib.sh\n \n@@ -100,25 +101,26 @@ init_repos () {\n \t# initialize sparse-checkout definitions\n \tgit -C sparse-checkout sparse-checkout init --cone &&\n \tgit -C sparse-checkout sparse-checkout set deep &&\n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout init --cone &&\n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep\n+\tgit -C sparse-index sparse-checkout init --cone --sparse-index &&\n+\ttest_cmp_config -C sparse-index true index.sparse &&\n+\tgit -C sparse-index sparse-checkout set deep\n }\n \n run_on_sparse () {\n \t(\n \t\tcd sparse-checkout &&\n-\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n+\t\t\"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n \t) &&\n \t(\n \t\tcd sparse-index &&\n-\t\tGIT_TEST_SPARSE_INDEX=1 \"$@\" >../sparse-index-out 2>../sparse-index-err\n+\t\t\"$@\" >../sparse-index-out 2>../sparse-index-err\n \t)\n }\n \n run_on_all () {\n \t(\n \t\tcd full-checkout &&\n-\t\tGIT_TEST_SPARSE_INDEX=0 \"$@\" >../full-checkout-out 2>../full-checkout-err\n+\t\t\"$@\" >../full-checkout-out 2>../full-checkout-err\n \t) &&\n \trun_on_sparse \"$@\"\n }\n@@ -148,7 +150,7 @@ test_expect_success 'sparse-index contents' '\n \t\t\t|| return 1\n \tdone &&\n \n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set folder1 &&\n+\tgit -C sparse-index sparse-checkout set folder1 &&\n \n \ttest-tool -C sparse-index read-cache --table >cache &&\n \tfor dir in deep folder2 x\n@@ -158,7 +160,7 @@ test_expect_success 'sparse-index contents' '\n \t\t\t|| return 1\n \tdone &&\n \n-\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep/deeper1 &&\n+\tgit -C sparse-index sparse-checkout set deep/deeper1 &&\n \n \ttest-tool -C sparse-index read-cache --table >cache &&\n \tfor dir in deep/deeper2 folder1 folder2 x\n@@ -166,7 +168,14 @@ test_expect_success 'sparse-index contents' '\n \t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n \t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n \t\t\t|| return 1\n-\tdone\n+\tdone &&\n+\n+\t# Disabling the sparse-index removes tree entries with full ones\n+\tgit -C sparse-index sparse-checkout init --no-sparse-index &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\t! grep \"040000 tree\" cache &&\n+\ttest_sparse_match test-tool read-cache --table\n '\n \n test_expect_success 'expanded in-memory index matches full index' '\n@@ -396,19 +405,15 @@ test_expect_success 'submodule handling' '\n test_expect_success 'sparse-index is expanded and converted back' '\n \tinit_repos &&\n \n-\t(\n-\t\tGIT_TEST_SPARSE_INDEX=1 &&\n-\t\texport GIT_TEST_SPARSE_INDEX &&\n-\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n-\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" reset --hard &&\n-\t\ttest_region index convert_to_sparse trace2.txt &&\n-\t\ttest_region index ensure_full_index trace2.txt &&\n-\n-\t\trm trace2.txt &&\n-\t\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n-\t\t\tgit -C sparse-index -c core.fsmonitor=\"\" status -uno &&\n-\t\ttest_region index ensure_full_index trace2.txt\n-\t)\n+\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\tgit -C sparse-index -c core.fsmonitor=\"\" reset --hard &&\n+\ttest_region index convert_to_sparse trace2.txt &&\n+\ttest_region index ensure_full_index trace2.txt &&\n+\n+\trm trace2.txt &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/trace2.txt\" GIT_TRACE2_EVENT_NESTING=10 \\\n+\t\tgit -C sparse-index -c core.fsmonitor=\"\" status -uno &&\n+\ttest_region index ensure_full_index trace2.txt\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"420593","messageId":"437a0f144e57cd4d0811ff652e760cb961b1db1e.1617109864.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 03/21] t1092: clean up script quoting","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:10:46Z","receivedAt":"2021-03-30T13:12:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThis test was introduced in 19a0acc83e4 (t1092: test interesting\nsparse-checkout scenarios, 2021-01-23), but it contains issues with quoting\nthat were not noticed until starting this follow-up series. The old\nmechanism would drop quoting such as in\n\n   test_all_match git commit -m \"touch README.md\"\n\nThe above happened to work because README.md is a file in the\nrepository, so 'git commit -m touch REAMDE.md' would succeed by\naccident.\n\nOther cases included quoting for no good reason, so clean that up now.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/t1092-sparse-checkout-compatibility.sh | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 8cd3e5a8d227..3725d3997e70 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -96,20 +96,20 @@ init_repos () {\n run_on_sparse () {\n \t(\n \t\tcd sparse-checkout &&\n-\t\t$* >../sparse-checkout-out 2>../sparse-checkout-err\n+\t\t\"$@\" >../sparse-checkout-out 2>../sparse-checkout-err\n \t)\n }\n \n run_on_all () {\n \t(\n \t\tcd full-checkout &&\n-\t\t$* >../full-checkout-out 2>../full-checkout-err\n+\t\t\"$@\" >../full-checkout-out 2>../full-checkout-err\n \t) &&\n-\trun_on_sparse $*\n+\trun_on_sparse \"$@\"\n }\n \n test_all_match () {\n-\trun_on_all $* &&\n+\trun_on_all \"$@\" &&\n \ttest_cmp full-checkout-out sparse-checkout-out &&\n \ttest_cmp full-checkout-err sparse-checkout-err\n }\n@@ -119,7 +119,7 @@ test_expect_success 'status with options' '\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git status --porcelain=v2 -z -u &&\n \ttest_all_match git status --porcelain=v2 -uno &&\n-\trun_on_all \"touch README.md\" &&\n+\trun_on_all touch README.md &&\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git status --porcelain=v2 -z -u &&\n \ttest_all_match git status --porcelain=v2 -uno &&\n@@ -135,7 +135,7 @@ test_expect_success 'add, commit, checkout' '\n \twrite_script edit-contents <<-\\EOF &&\n \techo text >>$1\n \tEOF\n-\trun_on_all \"../edit-contents README.md\" &&\n+\trun_on_all ../edit-contents README.md &&\n \n \ttest_all_match git add README.md &&\n \ttest_all_match git status --porcelain=v2 &&\n@@ -144,7 +144,7 @@ test_expect_success 'add, commit, checkout' '\n \ttest_all_match git checkout HEAD~1 &&\n \ttest_all_match git checkout - &&\n \n-\trun_on_all \"../edit-contents README.md\" &&\n+\trun_on_all ../edit-contents README.md &&\n \n \ttest_all_match git add -A &&\n \ttest_all_match git status --porcelain=v2 &&\n@@ -153,7 +153,7 @@ test_expect_success 'add, commit, checkout' '\n \ttest_all_match git checkout HEAD~1 &&\n \ttest_all_match git checkout - &&\n \n-\trun_on_all \"../edit-contents deep/newfile\" &&\n+\trun_on_all ../edit-contents deep/newfile &&\n \n \ttest_all_match git status --porcelain=v2 -uno &&\n \ttest_all_match git status --porcelain=v2 &&\n@@ -186,7 +186,7 @@ test_expect_success 'diff --staged' '\n \twrite_script edit-contents <<-\\EOF &&\n \techo text >>README.md\n \tEOF\n-\trun_on_all \"../edit-contents\" &&\n+\trun_on_all ../edit-contents &&\n \n \ttest_all_match git diff &&\n \ttest_all_match git diff --staged &&\n@@ -280,7 +280,7 @@ test_expect_success 'clean' '\n \techo bogus >>.gitignore &&\n \trun_on_all cp ../.gitignore . &&\n \ttest_all_match git add .gitignore &&\n-\ttest_all_match git commit -m ignore-bogus-files &&\n+\ttest_all_match git commit -m \"ignore bogus files\" &&\n \n \trun_on_sparse mkdir folder1 &&\n \trun_on_all touch folder1/bogus &&\n-- \ngitgitgadget\n\n"},{"id":"420595","messageId":"a1b8135c0fc890b2c2c0271c923b2f8efa8d1465.1617109864.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 07/21] test-read-cache: print cache entries with --table","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:10:50Z","receivedAt":"2021-03-30T13:12:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThis table is helpful for discovering data in the index to ensure it is\nbeing written correctly, especially as we build and test the\nsparse-index. This table includes an output format similar to 'git\nls-tree', but should not be compared to that directly. The biggest\nreasons are that 'git ls-tree' includes a tree entry for every\nsubdirectory, even those that would not appear as a sparse directory in\na sparse-index. Further, 'git ls-tree' does not use a trailing directory\nseparator for its tree rows.\n\nThis does not print the stat() information for the blobs. That will be\nadded in a future change with another option. The tests that are added\nin the next few changes care only about the object types and IDs.\nHowever, this future need for full index information justifies the need\nfor this test helper over extending a user-facing feature, such as 'git\nls-files'.\n\nTo make the option parsing slightly more robust, wrap the string\ncomparisons in a loop adapted from test-dir-iterator.c.\n\nCare must be taken with the final check for the 'cnt' variable. We\ncontinue the expectation that the numerical value is the final argument.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/helper/test-read-cache.c | 55 +++++++++++++++++++++++++++++++-------\n 1 file changed, 45 insertions(+), 10 deletions(-)\n\ndiff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c\nindex 244977a29bdf..6cfd8f2de71c 100644\n--- a/t/helper/test-read-cache.c\n+++ b/t/helper/test-read-cache.c\n@@ -1,36 +1,71 @@\n #include \"test-tool.h\"\n #include \"cache.h\"\n #include \"config.h\"\n+#include \"blob.h\"\n+#include \"commit.h\"\n+#include \"tree.h\"\n+\n+static void print_cache_entry(struct cache_entry *ce)\n+{\n+\tconst char *type;\n+\tprintf(\"%06o \", ce->ce_mode & 0177777);\n+\n+\tif (S_ISSPARSEDIR(ce->ce_mode))\n+\t\ttype = tree_type;\n+\telse if (S_ISGITLINK(ce->ce_mode))\n+\t\ttype = commit_type;\n+\telse\n+\t\ttype = blob_type;\n+\n+\tprintf(\"%s %s\\t%s\\n\",\n+\t       type,\n+\t       oid_to_hex(&ce->oid),\n+\t       ce->name);\n+}\n+\n+static void print_cache(struct index_state *istate)\n+{\n+\tint i;\n+\tfor (i = 0; i < istate->cache_nr; i++)\n+\t\tprint_cache_entry(istate->cache[i]);\n+}\n \n int cmd__read_cache(int argc, const char **argv)\n {\n+\tstruct repository *r = the_repository;\n \tint i, cnt = 1;\n \tconst char *name = NULL;\n+\tint table = 0;\n \n-\tif (argc > 1 && skip_prefix(argv[1], \"--print-and-refresh=\", &name)) {\n-\t\targc--;\n-\t\targv++;\n+\tfor (++argv, --argc; *argv && starts_with(*argv, \"--\"); ++argv, --argc) {\n+\t\tif (skip_prefix(*argv, \"--print-and-refresh=\", &name))\n+\t\t\tcontinue;\n+\t\tif (!strcmp(*argv, \"--table\"))\n+\t\t\ttable = 1;\n \t}\n \n-\tif (argc == 2)\n-\t\tcnt = strtol(argv[1], NULL, 0);\n+\tif (argc == 1)\n+\t\tcnt = strtol(argv[0], NULL, 0);\n \tsetup_git_directory();\n \tgit_config(git_default_config, NULL);\n+\n \tfor (i = 0; i < cnt; i++) {\n-\t\tread_cache();\n+\t\trepo_read_index(r);\n \t\tif (name) {\n \t\t\tint pos;\n \n-\t\t\trefresh_index(&the_index, REFRESH_QUIET,\n+\t\t\trefresh_index(r->index, REFRESH_QUIET,\n \t\t\t\t      NULL, NULL, NULL);\n-\t\t\tpos = index_name_pos(&the_index, name, strlen(name));\n+\t\t\tpos = index_name_pos(r->index, name, strlen(name));\n \t\t\tif (pos < 0)\n \t\t\t\tdie(\"%s not in index\", name);\n \t\t\tprintf(\"%s is%s up to date\\n\", name,\n-\t\t\t       ce_uptodate(the_index.cache[pos]) ? \"\" : \" not\");\n+\t\t\t       ce_uptodate(r->index->cache[pos]) ? \"\" : \" not\");\n \t\t\twrite_file(name, \"%d\\n\", i);\n \t\t}\n-\t\tdiscard_cache();\n+\t\tif (table)\n+\t\t\tprint_cache(r->index);\n+\t\tdiscard_index(r->index);\n \t}\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"420597","messageId":"f926cf8b2e019c710e164521bf90f7d948c7aa3d.1617109864.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 11/21] sparse-index: add 'sdir' index extension","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:10:54Z","receivedAt":"2021-03-30T13:12:14Z","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 index format does not currently allow for sparse directory entries.\nThis violates some expectations that older versions of Git or\nthird-party tools might not understand. We need an indicator inside the\nindex file to warn these tools to not interact with a sparse index\nunless they are aware of sparse directory entries.\n\nAdd a new _required_ index extension, 'sdir', that indicates that the\nindex may contain sparse directory entries. This allows us to continue\nto use the differences in index formats 2, 3, and 4 before we create a\nnew index version 5 in a later change.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/technical/index-format.txt | 12 ++++++++++++\n read-cache.c                             |  9 +++++++++\n 2 files changed, 21 insertions(+)\n\ndiff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt\nindex 3b74c05647db..65da0daaa563 100644\n--- a/Documentation/technical/index-format.txt\n+++ b/Documentation/technical/index-format.txt\n@@ -392,3 +392,15 @@ The remaining data of each directory block is grouped by type:\n \tin this block of entries.\n \n     - 32-bit count of cache entries in this block\n+\n+== Sparse Directory Entries\n+\n+  When using sparse-checkout in cone mode, some entire directories within\n+  the index can be summarized by pointing to a tree object instead of the\n+  entire expanded list of paths within that tree. An index containing such\n+  entries is a \"sparse index\". Index format versions 4 and less were not\n+  implemented with such entries in mind. Thus, for these versions, an\n+  index containing sparse directory entries will include this extension\n+  with signature { 's', 'd', 'i', 'r' }. Like the split-index extension,\n+  tools should avoid interacting with a sparse index unless they understand\n+  this extension.\ndiff --git a/read-cache.c b/read-cache.c\nindex dd3980c12b53..b8f092d1b7eb 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -47,6 +47,7 @@\n #define CACHE_EXT_FSMONITOR 0x46534D4E\t  /* \"FSMN\" */\n #define CACHE_EXT_ENDOFINDEXENTRIES 0x454F4945\t/* \"EOIE\" */\n #define CACHE_EXT_INDEXENTRYOFFSETTABLE 0x49454F54 /* \"IEOT\" */\n+#define CACHE_EXT_SPARSE_DIRECTORIES 0x73646972 /* \"sdir\" */\n \n /* changes that can be kept in $GIT_DIR/index (basically all extensions) */\n #define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \\\n@@ -1763,6 +1764,10 @@ static int read_index_extension(struct index_state *istate,\n \tcase CACHE_EXT_INDEXENTRYOFFSETTABLE:\n \t\t/* already handled in do_read_index() */\n \t\tbreak;\n+\tcase CACHE_EXT_SPARSE_DIRECTORIES:\n+\t\t/* no content, only an indicator */\n+\t\tistate->sparse_index = 1;\n+\t\tbreak;\n \tdefault:\n \t\tif (*ext < 'A' || 'Z' < *ext)\n \t\t\treturn error(_(\"index uses %.4s extension, which we do not understand\"),\n@@ -3020,6 +3025,10 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t\tif (err)\n \t\t\treturn -1;\n \t}\n+\tif (istate->sparse_index) {\n+\t\tif (write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_SPARSE_DIRECTORIES, 0) < 0)\n+\t\t\treturn -1;\n+\t}\n \n \t/*\n \t * CACHE_EXT_ENDOFINDEXENTRIES must be written as the last entry before the SHA1\n-- \ngitgitgadget\n\n"},{"id":"420584","messageId":"9b068c458898edfc5267c06e37563f66fac148cf.1617109865.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 20/21] sparse-index: loose integration with cache_tree_verify()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:11:03Z","receivedAt":"2021-03-30T13:12:15Z","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 cache_tree_verify() method is run when GIT_TEST_CHECK_CACHE_TREE\nis enabled, which it is by default in the test suite. The logic must\nbe adjusted for the presence of these directory entries.\n\nFor now, leave the test as a simple check for whether the directory\nentry is sparse. Do not go any further until needed.\n\nThis allows us to re-enable GIT_TEST_CHECK_CACHE_TREE in\nt1092-sparse-checkout-compatibility.sh. Further,\np2000-sparse-operations.sh uses the test suite and hence this is enabled\nfor all tests. We need to integrate with it before we run our\nperformance tests with a sparse-index.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache-tree.c                             | 19 +++++++++++++++++++\n t/t1092-sparse-checkout-compatibility.sh |  3 ---\n 2 files changed, 19 insertions(+), 3 deletions(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 950a9615db8f..11bf1fcae6e1 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -808,6 +808,19 @@ int cache_tree_matches_traversal(struct cache_tree *root,\n \treturn 0;\n }\n \n+static void verify_one_sparse(struct repository *r,\n+\t\t\t      struct index_state *istate,\n+\t\t\t      struct cache_tree *it,\n+\t\t\t      struct strbuf *path,\n+\t\t\t      int pos)\n+{\n+\tstruct cache_entry *ce = istate->cache[pos];\n+\n+\tif (!S_ISSPARSEDIR(ce->ce_mode))\n+\t\tBUG(\"directory '%s' is present in index, but not sparse\",\n+\t\t    path->buf);\n+}\n+\n static void verify_one(struct repository *r,\n \t\t       struct index_state *istate,\n \t\t       struct cache_tree *it,\n@@ -830,6 +843,12 @@ static void verify_one(struct repository *r,\n \n \tif (path->len) {\n \t\tpos = index_name_pos(istate, path->buf, path->len);\n+\n+\t\tif (pos >= 0) {\n+\t\t\tverify_one_sparse(r, istate, it, path, pos);\n+\t\t\treturn;\n+\t\t}\n+\n \t\tpos = -pos - 1;\n \t} else {\n \t\tpos = 0;\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex 472c5337de1b..12e6c453024f 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -2,9 +2,6 @@\n \n test_description='compare full workdir to sparse workdir'\n \n-# The verify_cache_tree() check is not sparse-aware (yet).\n-# So, disable the check until that integration is complete.\n-GIT_TEST_CHECK_CACHE_TREE=0\n GIT_TEST_SPLIT_INDEX=0\n GIT_TEST_SPARSE_INDEX=\n \n-- \ngitgitgadget\n\n"},{"id":"420586","messageId":"7191b48237dee37c095e7e995913bd37b003dd45.1617109865.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 14/21] unpack-trees: allow sparse directories","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:10:57Z","receivedAt":"2021-03-30T13:12:15Z","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 index_pos_by_traverse_info() currently throws a BUG() when a\ndirectory entry exists exactly in the index. We need to consider that it\nis possible to have a directory in a sparse index as long as that entry\nis itself marked with the skip-worktree bit.\n\nThe 'pos' variable is assigned a negative value if an exact match is not\nfound. Since a directory name can be an exact match, it is no longer an\nerror to have a nonnegative 'pos' value.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n unpack-trees.c | 10 +++++++---\n 1 file changed, 7 insertions(+), 3 deletions(-)\n\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 4dd99219073a..0b888dab2246 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -746,9 +746,13 @@ static int index_pos_by_traverse_info(struct name_entry *names,\n \tstrbuf_make_traverse_path(&name, info, names->path, names->pathlen);\n \tstrbuf_addch(&name, '/');\n \tpos = index_name_pos(o->src_index, name.buf, name.len);\n-\tif (pos >= 0)\n-\t\tBUG(\"This is a directory and should not exist in index\");\n-\tpos = -pos - 1;\n+\tif (pos >= 0) {\n+\t\tif (!o->src_index->sparse_index ||\n+\t\t    !(o->src_index->cache[pos]->ce_flags & CE_SKIP_WORKTREE))\n+\t\t\tBUG(\"This is a directory and should not exist in index\");\n+\t} else {\n+\t\tpos = -pos - 1;\n+\t}\n \tif (pos >= o->src_index->cache_nr ||\n \t    !starts_with(o->src_index->cache[pos]->name, name.buf) ||\n \t    (pos > 0 && starts_with(o->src_index->cache[pos-1]->name, name.buf)))\n-- \ngitgitgadget\n\n"},{"id":"420587","messageId":"c870ae5e8749bb04c1290b607607ac056552e3f9.1617109864.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 12/21] sparse-index: convert from full to sparse","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:10:55Z","receivedAt":"2021-03-30T13:12:15Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nIf we have a full index, then we can convert it to a sparse index by\nreplacing directories outside of the sparse cone with sparse directory\nentries. The convert_to_sparse() method does this, when the situation is\nappropriate.\n\nFor now, we avoid converting the index to a sparse index if:\n\n 1. the index is split.\n 2. the index is already sparse.\n 3. sparse-checkout is disabled.\n 4. sparse-checkout does not use cone mode.\n\nFinally, we currently limit the conversion to when the\nGIT_TEST_SPARSE_INDEX environment variable is enabled. A mode using Git\nconfig will be added in a later change.\n\nThe trickiest thing about this conversion is that we might not be able\nto mark a directory as a sparse directory just because it is outside the\nsparse cone. There might be unmerged files within that directory, so we\nneed to look for those. Also, if there is some strange reason why a file\nis not marked with CE_SKIP_WORKTREE, then we should give up on\nconverting that directory. There is still hope that some of its\nsubdirectories might be able to convert to sparse, so we keep looking\ndeeper.\n\nThe conversion process is assisted by the cache-tree extension. This is\ncalculated from the full index if it does not already exist. We then\nabandon the cache-tree as it no longer applies to the newly-sparse\nindex. Thus, this cache-tree will be recalculated in every\nsparse-full-sparse round-trip until we integrate the cache-tree\nextension with the sparse index.\n\nSome Git commands use the index after writing it. For example, 'git add'\nwill update the index, then write it to disk, then read its entries to\nreport information. To keep the in-memory index in a full state after\nwriting, we re-expand it to a full one after the write. This is wasteful\nfor commands that only write the index and do not read from it again,\nbut that is only the case until we make those commands \"sparse aware.\"\n\nWe can compare the behavior of the sparse-index in\nt1092-sparse-checkout-compability.sh by using GIT_TEST_SPARSE_INDEX=1\nwhen operating on the 'sparse-index' repo. We can also compare the two\nsparse repos directly, such as comparing their indexes (when expanded to\nfull in the case of the 'sparse-index' repo). We also verify that the\nindex is actually populated with sparse directory entries.\n\nThe 'checkout and reset (mixed)' test is marked for failure when\ncomparing a sparse repo to a full repo, but we can compare the two\nsparse-checkout cases directly to ensure that we are not changing the\nbehavior when using a sparse index.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache-tree.c                             |   3 +\n cache.h                                  |   2 +\n read-cache.c                             |  26 ++++-\n sparse-index.c                           | 139 +++++++++++++++++++++++\n sparse-index.h                           |   1 +\n t/t1092-sparse-checkout-compatibility.sh |  61 +++++++++-\n 6 files changed, 228 insertions(+), 4 deletions(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 2fb483d3c083..5f07a39e501e 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -6,6 +6,7 @@\n #include \"object-store.h\"\n #include \"replace-object.h\"\n #include \"promisor-remote.h\"\n+#include \"sparse-index.h\"\n \n #ifndef DEBUG_CACHE_TREE\n #define DEBUG_CACHE_TREE 0\n@@ -442,6 +443,8 @@ int cache_tree_update(struct index_state *istate, int flags)\n \tif (i)\n \t\treturn i;\n \n+\tensure_full_index(istate);\n+\n \tif (!istate->cache_tree)\n \t\tistate->cache_tree = cache_tree();\n \ndiff --git a/cache.h b/cache.h\nindex 8c4464420d0a..74b43aaa2bd1 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -251,6 +251,8 @@ static inline unsigned int create_ce_mode(unsigned int mode)\n {\n \tif (S_ISLNK(mode))\n \t\treturn S_IFLNK;\n+\tif (S_ISSPARSEDIR(mode))\n+\t\treturn S_IFDIR;\n \tif (S_ISDIR(mode) || S_ISGITLINK(mode))\n \t\treturn S_IFGITLINK;\n \treturn S_IFREG | ce_permissions(mode);\ndiff --git a/read-cache.c b/read-cache.c\nindex b8f092d1b7eb..2410e6e0df13 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 \"sparse-index.h\"\n \n /* Mask for the name length in ce_flags in the on-disk index */\n \n@@ -1003,8 +1004,14 @@ int verify_path(const char *path, unsigned mode)\n \n \t\t\tc = *path++;\n \t\t\tif ((c == '.' && !verify_dotfile(path, mode)) ||\n-\t\t\t    is_dir_sep(c) || c == '\\0')\n+\t\t\t    is_dir_sep(c))\n \t\t\t\treturn 0;\n+\t\t\t/*\n+\t\t\t * allow terminating directory separators for\n+\t\t\t * sparse directory entries.\n+\t\t\t */\n+\t\t\tif (c == '\\0')\n+\t\t\t\treturn S_ISDIR(mode);\n \t\t} else if (c == '\\\\' && protect_ntfs) {\n \t\t\tif (is_ntfs_dotgit(path))\n \t\t\t\treturn 0;\n@@ -3088,6 +3095,14 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n \t\t\t\t unsigned flags)\n {\n \tint ret;\n+\tint was_full = !istate->sparse_index;\n+\n+\tret = convert_to_sparse(istate);\n+\n+\tif (ret) {\n+\t\twarning(_(\"failed to convert to a sparse-index\"));\n+\t\treturn ret;\n+\t}\n \n \t/*\n \t * TODO trace2: replace \"the_repository\" with the actual repo instance\n@@ -3099,6 +3114,9 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n \ttrace2_region_leave_printf(\"index\", \"do_write_index\", the_repository,\n \t\t\t\t   \"%s\", get_lock_file_path(lock));\n \n+\tif (was_full)\n+\t\tensure_full_index(istate);\n+\n \tif (ret)\n \t\treturn ret;\n \tif (flags & COMMIT_LOCK)\n@@ -3189,9 +3207,10 @@ static int write_shared_index(struct index_state *istate,\n \t\t\t      struct tempfile **temp)\n {\n \tstruct split_index *si = istate->split_index;\n-\tint ret;\n+\tint ret, was_full = !istate->sparse_index;\n \n \tmove_cache_to_base_index(istate);\n+\tconvert_to_sparse(istate);\n \n \ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n \t\t\t\t   the_repository, \"%s\", get_tempfile_path(*temp));\n@@ -3199,6 +3218,9 @@ static int write_shared_index(struct index_state *istate,\n \ttrace2_region_leave_printf(\"index\", \"shared/do_write_index\",\n \t\t\t\t   the_repository, \"%s\", get_tempfile_path(*temp));\n \n+\tif (was_full)\n+\t\tensure_full_index(istate);\n+\n \tif (ret)\n \t\treturn ret;\n \tret = adjust_shared_perm(get_tempfile_path(*temp));\ndiff --git a/sparse-index.c b/sparse-index.c\nindex 7095378a1b28..619ff7c2e217 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -4,6 +4,145 @@\n #include \"tree.h\"\n #include \"pathspec.h\"\n #include \"trace2.h\"\n+#include \"cache-tree.h\"\n+#include \"config.h\"\n+#include \"dir.h\"\n+#include \"fsmonitor.h\"\n+\n+static struct cache_entry *construct_sparse_dir_entry(\n+\t\t\t\tstruct index_state *istate,\n+\t\t\t\tconst char *sparse_dir,\n+\t\t\t\tstruct cache_tree *tree)\n+{\n+\tstruct cache_entry *de;\n+\n+\tde = make_cache_entry(istate, S_IFDIR, &tree->oid, sparse_dir, 0, 0);\n+\n+\tde->ce_flags |= CE_SKIP_WORKTREE;\n+\treturn de;\n+}\n+\n+/*\n+ * Returns the number of entries \"inserted\" into the index.\n+ */\n+static int convert_to_sparse_rec(struct index_state *istate,\n+\t\t\t\t int num_converted,\n+\t\t\t\t int start, int end,\n+\t\t\t\t const char *ct_path, size_t ct_pathlen,\n+\t\t\t\t struct cache_tree *ct)\n+{\n+\tint i, can_convert = 1;\n+\tint start_converted = num_converted;\n+\tenum pattern_match_result match;\n+\tint dtype;\n+\tstruct strbuf child_path = STRBUF_INIT;\n+\tstruct pattern_list *pl = istate->sparse_checkout_patterns;\n+\n+\t/*\n+\t * Is the current path outside of the sparse cone?\n+\t * Then check if the region can be replaced by a sparse\n+\t * directory entry (everything is sparse and merged).\n+\t */\n+\tmatch = path_matches_pattern_list(ct_path, ct_pathlen,\n+\t\t\t\t\t  NULL, &dtype, pl, istate);\n+\tif (match != NOT_MATCHED)\n+\t\tcan_convert = 0;\n+\n+\tfor (i = start; can_convert && i < end; i++) {\n+\t\tstruct cache_entry *ce = istate->cache[i];\n+\n+\t\tif (ce_stage(ce) ||\n+\t\t    !(ce->ce_flags & CE_SKIP_WORKTREE))\n+\t\t\tcan_convert = 0;\n+\t}\n+\n+\tif (can_convert) {\n+\t\tstruct cache_entry *se;\n+\t\tse = construct_sparse_dir_entry(istate, ct_path, ct);\n+\n+\t\tistate->cache[num_converted++] = se;\n+\t\treturn 1;\n+\t}\n+\n+\tfor (i = start; i < end; ) {\n+\t\tint count, span, pos = -1;\n+\t\tconst char *base, *slash;\n+\t\tstruct cache_entry *ce = istate->cache[i];\n+\n+\t\t/*\n+\t\t * Detect if this is a normal entry outside of any subtree\n+\t\t * entry.\n+\t\t */\n+\t\tbase = ce->name + ct_pathlen;\n+\t\tslash = strchr(base, '/');\n+\n+\t\tif (slash)\n+\t\t\tpos = cache_tree_subtree_pos(ct, base, slash - base);\n+\n+\t\tif (pos < 0) {\n+\t\t\tistate->cache[num_converted++] = ce;\n+\t\t\ti++;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tstrbuf_setlen(&child_path, 0);\n+\t\tstrbuf_add(&child_path, ce->name, slash - ce->name + 1);\n+\n+\t\tspan = ct->down[pos]->cache_tree->entry_count;\n+\t\tcount = convert_to_sparse_rec(istate,\n+\t\t\t\t\t      num_converted, i, i + span,\n+\t\t\t\t\t      child_path.buf, child_path.len,\n+\t\t\t\t\t      ct->down[pos]->cache_tree);\n+\t\tnum_converted += count;\n+\t\ti += span;\n+\t}\n+\n+\tstrbuf_release(&child_path);\n+\treturn num_converted - start_converted;\n+}\n+\n+int convert_to_sparse(struct index_state *istate)\n+{\n+\tif (istate->split_index || istate->sparse_index ||\n+\t    !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n+\t\treturn 0;\n+\n+\t/*\n+\t * For now, only create a sparse index with the\n+\t * GIT_TEST_SPARSE_INDEX environment variable. We will relax\n+\t * this once we have a proper way to opt-in (and later still,\n+\t * opt-out).\n+\t */\n+\tif (!git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0))\n+\t\treturn 0;\n+\n+\tif (!istate->sparse_checkout_patterns) {\n+\t\tistate->sparse_checkout_patterns = xcalloc(1, sizeof(struct pattern_list));\n+\t\tif (get_sparse_checkout_patterns(istate->sparse_checkout_patterns) < 0)\n+\t\t\treturn 0;\n+\t}\n+\n+\tif (!istate->sparse_checkout_patterns->use_cone_patterns) {\n+\t\twarning(_(\"attempting to use sparse-index without cone mode\"));\n+\t\treturn -1;\n+\t}\n+\n+\tif (cache_tree_update(istate, 0)) {\n+\t\twarning(_(\"unable to update cache-tree, staying full\"));\n+\t\treturn -1;\n+\t}\n+\n+\tremove_fsmonitor(istate);\n+\n+\ttrace2_region_enter(\"index\", \"convert_to_sparse\", istate->repo);\n+\tistate->cache_nr = convert_to_sparse_rec(istate,\n+\t\t\t\t\t\t 0, 0, istate->cache_nr,\n+\t\t\t\t\t\t \"\", 0, istate->cache_tree);\n+\tistate->drop_cache_tree = 1;\n+\tistate->sparse_index = 1;\n+\ttrace2_region_leave(\"index\", \"convert_to_sparse\", istate->repo);\n+\treturn 0;\n+}\n \n static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)\n {\ndiff --git a/sparse-index.h b/sparse-index.h\nindex 09a20d036c46..64380e121d80 100644\n--- a/sparse-index.h\n+++ b/sparse-index.h\n@@ -3,5 +3,6 @@\n \n struct index_state;\n void ensure_full_index(struct index_state *istate);\n+int convert_to_sparse(struct index_state *istate);\n \n #endif\ndiff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh\nindex a1aea141c62c..1e888d195122 100755\n--- a/t/t1092-sparse-checkout-compatibility.sh\n+++ b/t/t1092-sparse-checkout-compatibility.sh\n@@ -2,6 +2,11 @@\n \n test_description='compare full workdir to sparse workdir'\n \n+# The verify_cache_tree() check is not sparse-aware (yet).\n+# So, disable the check until that integration is complete.\n+GIT_TEST_CHECK_CACHE_TREE=0\n+GIT_TEST_SPLIT_INDEX=0\n+\n . ./test-lib.sh\n \n test_expect_success 'setup' '\n@@ -121,7 +126,9 @@ run_on_all () {\n test_all_match () {\n \trun_on_all \"$@\" &&\n \ttest_cmp full-checkout-out sparse-checkout-out &&\n-\ttest_cmp full-checkout-err sparse-checkout-err\n+\ttest_cmp full-checkout-out sparse-index-out &&\n+\ttest_cmp full-checkout-err sparse-checkout-err &&\n+\ttest_cmp full-checkout-err sparse-index-err\n }\n \n test_sparse_match () {\n@@ -130,6 +137,38 @@ test_sparse_match () {\n \ttest_cmp sparse-checkout-err sparse-index-err\n }\n \n+test_expect_success 'sparse-index contents' '\n+\tinit_repos &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tfor dir in folder1 folder2 x\n+\tdo\n+\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n+\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n+\t\t\t|| return 1\n+\tdone &&\n+\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set folder1 &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tfor dir in deep folder2 x\n+\tdo\n+\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n+\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n+\t\t\t|| return 1\n+\tdone &&\n+\n+\tGIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep/deeper1 &&\n+\n+\ttest-tool -C sparse-index read-cache --table >cache &&\n+\tfor dir in deep/deeper2 folder1 folder2 x\n+\tdo\n+\t\tTREE=$(git -C sparse-index rev-parse HEAD:$dir) &&\n+\t\tgrep \"040000 tree $TREE\t$dir/\" cache \\\n+\t\t\t|| return 1\n+\tdone\n+'\n+\n test_expect_success 'expanded in-memory index matches full index' '\n \tinit_repos &&\n \ttest_sparse_match test-tool read-cache --expand --table\n@@ -137,6 +176,7 @@ test_expect_success 'expanded in-memory index matches full index' '\n \n test_expect_success 'status with options' '\n \tinit_repos &&\n+\ttest_sparse_match ls &&\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git status --porcelain=v2 -z -u &&\n \ttest_all_match git status --porcelain=v2 -uno &&\n@@ -273,6 +313,17 @@ test_expect_failure 'checkout and reset (mixed)' '\n \ttest_all_match git reset update-folder2\n '\n \n+# Ensure that sparse-index behaves identically to\n+# sparse-checkout with a full index.\n+test_expect_success 'checkout and reset (mixed) [sparse]' '\n+\tinit_repos &&\n+\n+\ttest_sparse_match git checkout -b reset-test update-deep &&\n+\ttest_sparse_match git reset deepest &&\n+\ttest_sparse_match git reset update-folder1 &&\n+\ttest_sparse_match git reset update-folder2\n+'\n+\n test_expect_success 'merge' '\n \tinit_repos &&\n \n@@ -309,14 +360,20 @@ test_expect_success 'clean' '\n \ttest_all_match git status --porcelain=v2 &&\n \ttest_all_match git clean -f &&\n \ttest_all_match git status --porcelain=v2 &&\n+\ttest_sparse_match ls &&\n+\ttest_sparse_match ls folder1 &&\n \n \ttest_all_match git clean -xf &&\n \ttest_all_match git status --porcelain=v2 &&\n+\ttest_sparse_match ls &&\n+\ttest_sparse_match ls folder1 &&\n \n \ttest_all_match git clean -xdf &&\n \ttest_all_match git status --porcelain=v2 &&\n+\ttest_sparse_match ls &&\n+\ttest_sparse_match ls folder1 &&\n \n-\ttest_path_is_dir sparse-checkout/folder1\n+\ttest_sparse_match test_path_is_dir folder1\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"420592","messageId":"66602733cc95d9a53594520cd8b28d3338e258ea.1617109865.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 21/21] p2000: add sparse-index repos","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:11:04Z","receivedAt":"2021-03-30T13:12:15Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\np2000-sparse-operations.sh compares different Git commands in\nrepositories with many files at HEAD but using sparse-checkout to focus\non a small portion of those files.\n\nAdd extra copies of the repository that use the sparse-index format so\nwe can track how that affects the performance of different commands.\n\nAt this point in time, the sparse-index is 100% overhead from the CPU\nfront, and this is measurable in these tests:\n\nTest\n---------------------------------------------------------------\n2000.2: git status (full-index-v3)              0.59(0.51+0.12)\n2000.3: git status (full-index-v4)              0.59(0.52+0.11)\n2000.4: git status (sparse-index-v3)            1.40(1.32+0.12)\n2000.5: git status (sparse-index-v4)            1.41(1.36+0.08)\n2000.6: git add -A (full-index-v3)              2.32(1.97+0.19)\n2000.7: git add -A (full-index-v4)              2.17(1.92+0.14)\n2000.8: git add -A (sparse-index-v3)            2.31(2.21+0.15)\n2000.9: git add -A (sparse-index-v4)            2.30(2.20+0.13)\n2000.10: git add . (full-index-v3)              2.39(2.02+0.20)\n2000.11: git add . (full-index-v4)              2.20(1.94+0.16)\n2000.12: git add . (sparse-index-v3)            2.36(2.27+0.12)\n2000.13: git add . (sparse-index-v4)            2.33(2.21+0.16)\n2000.14: git commit -a -m A (full-index-v3)     2.47(2.12+0.20)\n2000.15: git commit -a -m A (full-index-v4)     2.26(2.00+0.17)\n2000.16: git commit -a -m A (sparse-index-v3)   3.01(2.92+0.16)\n2000.17: git commit -a -m A (sparse-index-v4)   3.01(2.94+0.15)\n\nNote that there is very little difference between the v3 and v4 index\nformats when the sparse-index is enabled. This is primarily due to the\nfact that the relative file sizes are the same, and the command time is\nmostly taken up by parsing tree objects to expand the sparse index into\na full one.\n\nWith the current file layout, the index file sizes are given by this\ntable:\n\n       |  full index | sparse index |\n       +-------------+--------------+\n    v3 |     108 MiB |      1.6 MiB |\n    v4 |      80 MiB |      1.2 MiB |\n\nFuture updates will improve the performance of Git commands when the\nindex is sparse.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/perf/p2000-sparse-operations.sh | 19 ++++++++++++++++++-\n 1 file changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh\nindex dddd527b6330..94513c977489 100755\n--- a/t/perf/p2000-sparse-operations.sh\n+++ b/t/perf/p2000-sparse-operations.sh\n@@ -59,12 +59,29 @@ test_expect_success 'setup repo and indexes' '\n \t\tgit sparse-checkout set $SPARSE_CONE &&\n \t\tgit config index.version 4 &&\n \t\tgit update-index --index-version=4\n+\t) &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v3 &&\n+\t(\n+\t\tcd sparse-index-v3 &&\n+\t\tgit sparse-checkout init --cone --sparse-index &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 3 &&\n+\t\tgit update-index --index-version=3\n+\t) &&\n+\tgit -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v4 &&\n+\t(\n+\t\tcd sparse-index-v4 &&\n+\t\tgit sparse-checkout init --cone --sparse-index &&\n+\t\tgit sparse-checkout set $SPARSE_CONE &&\n+\t\tgit config index.version 4 &&\n+\t\tgit update-index --index-version=4\n \t)\n '\n \n test_perf_on_all () {\n \tcommand=\"$@\"\n-\tfor repo in full-index-v3 full-index-v4\n+\tfor repo in full-index-v3 full-index-v4 \\\n+\t\t    sparse-index-v3 sparse-index-v4\n \tdo\n \t\ttest_perf \"$command ($repo)\" \"\n \t\t\t(\n-- \ngitgitgadget\n"},{"id":"420594","messageId":"7f55a232e6479b62efc7b97d04cdf3ad11a0623d.1617109865.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 18/21] sparse-checkout: disable sparse-index","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:11:01Z","receivedAt":"2021-03-30T13:12:15Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWe use 'git sparse-checkout init --cone --sparse-index' to toggle the\nsparse-index feature. It makes sense to also disable it when running\n'git sparse-checkout disable'. This is particularly important because it\nremoves the extensions.sparseIndex config option, allowing other tools\nto use this Git repository again.\n\nThis does mean that 'git sparse-checkout init' will not re-enable the\nsparse-index feature, even if it was previously enabled.\n\nWhile testing this feature, I noticed that the sparse-index was not\nbeing written on the first run, but by a second. This was caught by the\ncall to 'test-tool read-cache --table'. This requires adjusting some\nassignments to core_apply_sparse_checkout and pl.use_cone_patterns in\nthe sparse_checkout_init() logic.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/sparse-checkout.c          | 10 +++++++++-\n t/t1091-sparse-checkout-builtin.sh | 13 +++++++++++++\n 2 files changed, 22 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex ca63e2c64e95..585343fa1972 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -280,6 +280,9 @@ static int set_config(enum sparse_checkout_mode mode)\n \t\t\t\t      \"core.sparseCheckoutCone\",\n \t\t\t\t      mode == MODE_CONE_PATTERNS ? \"true\" : NULL);\n \n+\tif (mode == MODE_NO_PATTERNS)\n+\t\tset_sparse_index_config(the_repository, 0);\n+\n \treturn 0;\n }\n \n@@ -341,10 +344,11 @@ static int sparse_checkout_init(int argc, const char **argv)\n \t\tthe_repository->index->updated_workdir = 1;\n \t}\n \n+\tcore_apply_sparse_checkout = 1;\n+\n \t/* If we already have a sparse-checkout file, use it. */\n \tif (res >= 0) {\n \t\tfree(sparse_filename);\n-\t\tcore_apply_sparse_checkout = 1;\n \t\treturn update_working_directory(NULL);\n \t}\n \n@@ -366,6 +370,7 @@ static int sparse_checkout_init(int argc, const char **argv)\n \tadd_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);\n \tstrbuf_addstr(&pattern, \"!/*/\");\n \tadd_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);\n+\tpl.use_cone_patterns = init_opts.cone_mode;\n \n \treturn write_patterns_and_update(&pl);\n }\n@@ -632,6 +637,9 @@ static int sparse_checkout_disable(int argc, const char **argv)\n \tstrbuf_addstr(&match_all, \"/*\");\n \tadd_pattern(strbuf_detach(&match_all, NULL), empty_base, 0, &pl, 0);\n \n+\tprepare_repo_settings(the_repository);\n+\tthe_repository->settings.sparse_index = 0;\n+\n \tif (update_working_directory(&pl))\n \t\tdie(_(\"error while refreshing working directory\"));\n \ndiff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh\nindex fc64e9ed99f4..38fc8340f5c9 100755\n--- a/t/t1091-sparse-checkout-builtin.sh\n+++ b/t/t1091-sparse-checkout-builtin.sh\n@@ -205,6 +205,19 @@ test_expect_success 'sparse-checkout disable' '\n \tcheck_files repo a deep folder1 folder2\n '\n \n+test_expect_success 'sparse-index enabled and disabled' '\n+\tgit -C repo sparse-checkout init --cone --sparse-index &&\n+\ttest_cmp_config -C repo true index.sparse &&\n+\ttest-tool -C repo read-cache --table >cache &&\n+\tgrep \" tree \" cache &&\n+\n+\tgit -C repo sparse-checkout disable &&\n+\ttest-tool -C repo read-cache --table >cache &&\n+\t! grep \" tree \" cache &&\n+\tgit -C repo config --list >config &&\n+\t! grep index.sparse config\n+'\n+\n test_expect_success 'cone mode: init and set' '\n \tgit -C repo sparse-checkout init --cone &&\n \tgit -C repo config --list >config &&\n-- \ngitgitgadget\n\n"},{"id":"420598","messageId":"365901809d9d43f1229ac3e81990853b06b8fb35.1617109865.git.gitgitgadget@gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"[PATCH v5 19/21] cache-tree: integrate with sparse directory entries","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-30T13:11:02Z","receivedAt":"2021-03-30T13:12:15Z","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 cache-tree extension was previously disabled with sparse indexes.\nHowever, the cache-tree is an important performance feature for commands\nlike 'git status' and 'git add'. Integrate it with sparse directory\nentries.\n\nWhen writing a sparse index, completely clear and recalculate the cache\ntree. By starting from scratch, the only integration necessary is to\ncheck if we hit a sparse directory entry and create a leaf of the\ncache-tree that has an entry_count of one and no subtrees.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n cache-tree.c   | 18 ++++++++++++++++++\n sparse-index.c | 10 +++++++++-\n 2 files changed, 27 insertions(+), 1 deletion(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 5f07a39e501e..950a9615db8f 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -256,6 +256,24 @@ static int update_one(struct cache_tree *it,\n \n \t*skip_count = 0;\n \n+\t/*\n+\t * If the first entry of this region is a sparse directory\n+\t * entry corresponding exactly to 'base', then this cache_tree\n+\t * struct is a \"leaf\" in the data structure, pointing to the\n+\t * tree OID specified in the entry.\n+\t */\n+\tif (entries > 0) {\n+\t\tconst struct cache_entry *ce = cache[0];\n+\n+\t\tif (S_ISSPARSEDIR(ce->ce_mode) &&\n+\t\t    ce->ce_namelen == baselen &&\n+\t\t    !strncmp(ce->name, base, baselen)) {\n+\t\t\tit->entry_count = 1;\n+\t\t\toidcpy(&it->oid, &ce->oid);\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n \tif (0 <= it->entry_count && has_object_file(&it->oid))\n \t\treturn it->entry_count;\n \ndiff --git a/sparse-index.c b/sparse-index.c\nindex 4c73772c6d6c..95ea17174da3 100644\n--- a/sparse-index.c\n+++ b/sparse-index.c\n@@ -172,7 +172,11 @@ int convert_to_sparse(struct index_state *istate)\n \tistate->cache_nr = convert_to_sparse_rec(istate,\n \t\t\t\t\t\t 0, 0, istate->cache_nr,\n \t\t\t\t\t\t \"\", 0, istate->cache_tree);\n-\tistate->drop_cache_tree = 1;\n+\n+\t/* Clear and recompute the cache-tree */\n+\tcache_tree_free(&istate->cache_tree);\n+\tcache_tree_update(istate, 0);\n+\n \tistate->sparse_index = 1;\n \ttrace2_region_leave(\"index\", \"convert_to_sparse\", istate->repo);\n \treturn 0;\n@@ -273,5 +277,9 @@ void ensure_full_index(struct index_state *istate)\n \tstrbuf_release(&base);\n \tfree(full);\n \n+\t/* Clear and recompute the cache-tree */\n+\tcache_tree_free(&istate->cache_tree);\n+\tcache_tree_update(istate, 0);\n+\n \ttrace2_region_leave(\"index\", \"ensure_full_index\", istate->repo);\n }\n-- \ngitgitgadget\n\n"},{"id":"420647","messageId":"xmqqsg4ccsgd.fsf@gitster.g","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 00/21] Sparse Index: Design, Format, Tests","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-30T20:11:30Z","receivedAt":"2021-03-30T20:12:23Z","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>      @@ repo-settings.c: void prepare_repo_settings(struct repository *r)\n>       +\t * Initialize this as off.\n>       +\t */\n>       +\tr->settings.sparse_index = 0;\n>      -+\tif (!repo_config_get_bool(r, \"extensions.sparseindex\", &value) && value)\n>      ++\tif (!repo_config_get_bool(r, \"index.sparse\", &value) && value)\n>       +\t\tr->settings.sparse_index = 1;\n>        }\n\nIt would be helpful to have a way for the repository owner to say\n\"Even if the version of Git may be capable of handling 'sdir'\nextension, and my checkout uses sparse-cone settings, I do not want\nto use it\", and the other way around, i.e. \"Even if my checkout\ncurrently does not use sparse-cone settings, do use 'sdir'\nextension\".  But for that, .sparse_index member may need to be\ntristate (i.e. forbidden, enable-if-needed, use-even-unneeded)?\n\nWe have a similar setting in index.version; I believe we always\nauto-demote 3 down to 2 when extended flags are not used, and\nI think \"always auto-demote\" would be sufficient (iow,\n\"use-even-unneeded\" may not be necessary, even though that might\nhelp debugging).\n\nThanks.\n"},{"id":"420656","messageId":"0a2e3028-8ebe-86be-d55d-b96aefb1ee53@gmail.com","threadId":"55195","inReplyTo":"xmqqsg4ccsgd.fsf@gitster.g","subject":"Re: [PATCH v5 00/21] Sparse Index: Design, Format, Tests","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-03-30T21:31:42Z","receivedAt":"2021-03-30T21:32:45Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 3/30/2021 4:11 PM, Junio C Hamano wrote:\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>>      @@ repo-settings.c: void prepare_repo_settings(struct repository *r)\n>>       +\t * Initialize this as off.\n>>       +\t */\n>>       +\tr->settings.sparse_index = 0;\n>>      -+\tif (!repo_config_get_bool(r, \"extensions.sparseindex\", &value) && value)\n>>      ++\tif (!repo_config_get_bool(r, \"index.sparse\", &value) && value)\n>>       +\t\tr->settings.sparse_index = 1;\n>>        }\n> \n> It would be helpful to have a way for the repository owner to say\n> \"Even if the version of Git may be capable of handling 'sdir'\n> extension, and my checkout uses sparse-cone settings, I do not want\n> to use it\", and the other way around, i.e. \"Even if my checkout\n> currently does not use sparse-cone settings, do use 'sdir'\n> extension\".  But for that, .sparse_index member may need to be\n> tristate (i.e. forbidden, enable-if-needed, use-even-unneeded)?\n\nI believe as presented, index.sparse=false will prevent the sdir\nextension from being used. If index.sparse=true, then it will only\nbe used if sparse-checkout is enabled in cone mode.\n\nI don't see the value in using the 'sdir' extension when not using\nsparse-checkout in cone mode (and hence there are no sparse directory\nentries in the index). What am I missing?\n\n> We have a similar setting in index.version; I believe we always\n> auto-demote 3 down to 2 when extended flags are not used, and\n> I think \"always auto-demote\" would be sufficient (iow,\n> \"use-even-unneeded\" may not be necessary, even though that might\n> help debugging).\n\nYes, the same is happening here: we auto-demote to not use 'sdir'\nif it the other settings are not configured as well.\n\nThere is the rare scenario where these things all occur:\n\n1. index.sparse = true\n2. core.sparseCheckout = true\n3. core.sparseCheckoutCone = true\n4. Every path in the index matches the cone-mode patterns.\n\nIn this case, convert_to_sparse() is called and the istate->sparse\nbit is set, telling do_write_index() to add the 'sdir' extension.\n\nThis seems like a rare occurrence. Is it still worth adding logic\nto avoid 'sdir' when these are all true?\n\nThanks,\n-Stolee\n"},{"id":"420657","messageId":"xmqqeefwcnxn.fsf@gitster.g","threadId":"55195","inReplyTo":"0a2e3028-8ebe-86be-d55d-b96aefb1ee53@gmail.com","subject":"Re: [PATCH v5 00/21] Sparse Index: Design, Format, Tests","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-30T21:49:08Z","receivedAt":"2021-03-30T21:50:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <stolee@gmail.com> writes:\n\n>> We have a similar setting in index.version; I believe we always\n>> auto-demote 3 down to 2 when extended flags are not used, and\n>> I think \"always auto-demote\" would be sufficient (iow,\n>> \"use-even-unneeded\" may not be necessary, even though that might\n>> help debugging).\n>\n> Yes, the same is happening here: we auto-demote to not use 'sdir'\n> if it the other settings are not configured as well.\n>\n> There is the rare scenario where these things all occur:\n> ...\n> This seems like a rare occurrence. Is it still worth adding logic\n> to avoid 'sdir' when these are all true?\n\nYou'd be the primary one who will be debugging the system while and\nafter this goes through the stabilization effort, so whichever you\nfind is more convenient is good enough for us, I guess.\n\nThanks.\n"},{"id":"420796","messageId":"CABPp-BEYdNtxMEdmaojCaQNR+DZ6eofCR0CnzkSZQp3KEERM1Q@mail.gmail.com","threadId":"55195","inReplyTo":"pull.883.v5.git.1617109864.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 00/21] Sparse Index: Design, Format, Tests","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-04-01T04:38:24Z","receivedAt":"2021-04-01T04:39:56Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Mar 30, 2021 at 6:11 AM Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> Here is the first full patch series submission coming out of the\n> sparse-index RFC [1].\n>\n> [1]\n> https://lore.kernel.org/git/pull.847.git.1611596533.gitgitgadget@gmail.com/\n>\n> I won't waste too much space here, because PATCH 1 includes a sizeable\n> design document that describes the feature, the reasoning behind it, and my\n> plan for getting this implemented widely throughout the codebase.\n>\n> There are some new things here that were not in the RFC:\n>\n>  * Design doc and format updates. (Patch 1)\n>  * Performance test script. (Patches 2 and 20)\n>\n> Notably missing in this series from the RFC:\n>\n>  * The mega-patch inserting ensure_full_index() throughout the codebase.\n>    That will be a follow-up series to this one.\n>  * The integrations with git status and git add to demonstrate the improved\n>    performance. Those will also appear in their own series later.\n>\n> I plan to keep my latest work in this area in my 'sparse-index/wip' branch\n> [2]. It includes all of the work from the RFC right now, updated with the\n> work from this series.\n>\n> [2] https://github.com/derrickstolee/git/tree/sparse-index/wip\n>\n>\n> Updates in V5\n> =============\n>\n> This version is updated to use an index extension instead of a repository\n> format extension. Thanks, Szeder! This one change affects the range-diff\n> quite a bit, so please review those changes carefully.\n>\n> In particular: git sparse-checkout init --cone --sparse-index now sets a new\n> index.sparse config option as an indicator that we should attempt writing\n> the index in sparse form.\n>\n>\n> Updates in V4\n> =============\n>\n>  * Rebased onto the latest copy of ab/read-tree.\n>  * Updated the design document as per Junio's comments.\n>  * Updated the submodule handling in the performance test.\n>  * Followed up on some other review from Ævar, mostly style or commit\n>    message things.\n>\n>\n> Updates in V3\n> =============\n>\n> For this version, I took Ævar's latest patches and applied them to v2.31.0\n> and rebased this series on top. It uses his new \"read_tree_at()\" helper and\n> the associated changes to the function pointer type.\n>\n>  * Fixed more typos. Thanks Martin and Elijah!\n>  * Updated the test_sparse_match() macro to use \"$@\" instead of $*\n>  * Added a test that git sparse-checkout init --no-sparse-index rewrites the\n>    index to be full.\n>\n>\n> Updates in V2\n> =============\n>\n>  * Various typos and awkward grammar is fixed.\n>  * Cleaned up unnecessary commands in p2000-sparse-operations.sh\n>  * Added a comment to the sparse_index member of struct index_state.\n>  * Used tree_type, commit_type, and blob_type in test-read-cache.c.\n>\n> Thanks, -Stolee\n>\n> Derrick Stolee (21):\n>   sparse-index: design doc and format update\n>   t/perf: add performance test for sparse operations\n>   t1092: clean up script quoting\n>   sparse-index: add guard to ensure full index\n>   sparse-index: implement ensure_full_index()\n>   t1092: compare sparse-checkout to sparse-index\n>   test-read-cache: print cache entries with --table\n>   test-tool: don't force full index\n>   unpack-trees: ensure full index\n>   sparse-checkout: hold pattern list in index\n>   sparse-index: add 'sdir' index extension\n>   sparse-index: convert from full to sparse\n>   submodule: sparse-index should not collapse links\n>   unpack-trees: allow sparse directories\n>   sparse-index: check index conversion happens\n>   sparse-index: add index.sparse config option\n>   sparse-checkout: toggle sparse index from builtin\n>   sparse-checkout: disable sparse-index\n>   cache-tree: integrate with sparse directory entries\n>   sparse-index: loose integration with cache_tree_verify()\n>   p2000: add sparse-index repos\n>\n>  Documentation/config/index.txt           |   5 +\n>  Documentation/git-sparse-checkout.txt    |  14 ++\n>  Documentation/technical/index-format.txt |  19 ++\n>  Documentation/technical/sparse-index.txt | 175 ++++++++++++++\n>  Makefile                                 |   1 +\n>  builtin/sparse-checkout.c                |  44 +++-\n>  cache-tree.c                             |  40 ++++\n>  cache.h                                  |  18 +-\n>  read-cache.c                             |  44 +++-\n>  repo-settings.c                          |  15 ++\n>  repository.c                             |  11 +-\n>  repository.h                             |   3 +\n>  sparse-index.c                           | 285 +++++++++++++++++++++++\n>  sparse-index.h                           |  11 +\n>  t/README                                 |   3 +\n>  t/helper/test-read-cache.c               |  66 +++++-\n>  t/perf/p2000-sparse-operations.sh        | 101 ++++++++\n>  t/t1091-sparse-checkout-builtin.sh       |  13 ++\n>  t/t1092-sparse-checkout-compatibility.sh | 143 ++++++++++--\n>  unpack-trees.c                           |  17 +-\n>  20 files changed, 988 insertions(+), 40 deletions(-)\n>  create mode 100644 Documentation/technical/sparse-index.txt\n>  create mode 100644 sparse-index.c\n>  create mode 100644 sparse-index.h\n>  create mode 100755 t/perf/p2000-sparse-operations.sh\n>\n>\n> base-commit: 47957485b3b731a7860e0554d2bd12c0dce1c75a\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-883%2Fderrickstolee%2Fsparse-index%2Fformat-v5\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-883/derrickstolee/sparse-index/format-v5\n> Pull-Request: https://github.com/gitgitgadget/git/pull/883\n>\n> Range-diff vs v4:\n>\n>   1:  6426a5c60e53 !  1:  7b600d536c6e sparse-index: design doc and format update\n>      @@ Documentation/technical/sparse-index.txt (new)\n>       +The only noticeable change in behavior will be that the serialized index\n>       +file contains sparse-directory entries.\n>       +\n>      -+To start, we use a new repository extension, `extensions.sparseIndex`, to\n>      -+allow inserting sparse-directory entries into indexes with file format\n>      ++To start, we use a new required index extension, `sdir`, to allow\n>      ++inserting sparse-directory entries into indexes with file format\n>       +versions 2, 3, and 4. This prevents Git versions that do not understand\n>      -+the sparse-index from operating on one, but it also prevents other\n>      -+operations that do not use the index at all. A new format, index v5, will\n>      -+be introduced that includes sparse-directory entries by default. It might\n>      -+also introduce other features that have been considered for improving the\n>      ++the sparse-index from operating on one, while allowing tools that do not\n>      ++understand the sparse-index to operate on repositories as long as they do\n>      ++not interact with the index. A new format, index v5, will be introduced\n>      ++that includes sparse-directory entries by default. It might also\n>      ++introduce other features that have been considered for improving the\n>       +index, as well.\n>       +\n>       +Next, consumers of the index will be guarded against operating on a\n>   2:  7eabc1d0586c =  2:  202253ec82f3 t/perf: add performance test for sparse operations\n>   3:  c9e21d78ecba =  3:  437a0f144e57 t1092: clean up script quoting\n>   4:  03cdde756563 =  4:  b7e1bf5c55a7 sparse-index: add guard to ensure full index\n>   5:  6b3b6d86385d =  5:  e41d55d2cca9 sparse-index: implement ensure_full_index()\n>   6:  7f67adba0498 =  6:  7bfbfbd17321 t1092: compare sparse-checkout to sparse-index\n>   7:  7ebd9570b1ad =  7:  a1b8135c0fc8 test-read-cache: print cache entries with --table\n>   8:  db7bbd06dbcc =  8:  dd84a2a9121b test-tool: don't force full index\n>   9:  3ddd5e794b5e =  9:  b276d2ed5323 unpack-trees: ensure full index\n>  10:  7308c87697f1 = 10:  c3651e26dc3a sparse-checkout: hold pattern list in index\n>   -:  ------------ > 11:  f926cf8b2e01 sparse-index: add 'sdir' index extension\n>  11:  7c10d653ca6b = 12:  c870ae5e8749 sparse-index: convert from full to sparse\n>  12:  6db36f33e960 = 13:  bcf0da959ef3 submodule: sparse-index should not collapse links\n>  13:  d24bd3348d98 = 14:  7191b48237de unpack-trees: allow sparse directories\n>  14:  08d9f5f3c0d1 = 15:  57be9b4a728b sparse-index: check index conversion happens\n>  15:  6f38cef196b0 ! 16:  c22b4111e49e sparse-index: create extension for compatibility\n>      @@ Metadata\n>       Author: Derrick Stolee <dstolee@microsoft.com>\n>\n>        ## Commit message ##\n>      -    sparse-index: create extension for compatibility\n>      +    sparse-index: add index.sparse config option\n>\n>      -    Previously, we enabled the sparse index format only using\n>      -    GIT_TEST_SPARSE_INDEX=1. This is not a feasible direction for users to\n>      -    actually select this mode. Further, sparse directory entries are not\n>      -    understood by the index formats as advertised.\n>      -\n>      -    We _could_ add a new index version that explicitly adds these\n>      -    capabilities, but there are nuances to index formats 2, 3, and 4 that\n>      -    are still valuable to select as options. Until we add index format\n>      -    version 5, create a repo extension, \"extensions.sparseIndex\", that\n>      -    specifies that the tool reading this repository must understand sparse\n>      -    directory entries.\n>      -\n>      -    This change only encodes the extension and enables it when\n>      -    GIT_TEST_SPARSE_INDEX=1. Later, we will add a more user-friendly CLI\n>      -    mechanism.\n>      +    When enabled, this config option signals that index writes should\n>      +    attempt to use sparse-directory entries.\n>\n>           Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n>\n>      - ## Documentation/config/extensions.txt ##\n>      -@@ Documentation/config/extensions.txt: extensions.objectFormat::\n>      - Note that this setting should only be set by linkgit:git-init[1] or\n>      - linkgit:git-clone[1].  Trying to change it after initialization will not\n>      - work and will produce hard-to-diagnose issues.\n>      + ## Documentation/config/index.txt ##\n>      +@@ Documentation/config/index.txt: index.recordOffsetTable::\n>      +  Defaults to 'true' if index.threads has been explicitly enabled,\n>      +  'false' otherwise.\n>      +\n>      ++index.sparse::\n>      ++ When enabled, write the index using sparse-directory entries. This\n>      ++ has no effect unless `core.sparseCheckout` and\n>      ++ `core.sparseCheckoutCone` are both enabled. Defaults to 'false'.\n>       +\n>      -+extensions.sparseIndex::\n>      -+ When combined with `core.sparseCheckout=true` and\n>      -+ `core.sparseCheckoutCone=true`, the index may contain entries\n>      -+ corresponding to directories outside of the sparse-checkout\n>      -+ definition in lieu of containing each path under such directories.\n>      -+ Versions of Git that do not understand this extension do not\n>      -+ expect directory entries in the index.\n>      + index.threads::\n>      +  Specifies the number of threads to spawn when loading the index.\n>      +  This is meant to reduce index load time on multiprocessor machines.\n>\n>        ## cache.h ##\n>       @@ cache.h: struct repository_format {\n>      @@ repo-settings.c: void prepare_repo_settings(struct repository *r)\n>       +  * Initialize this as off.\n>       +  */\n>       + r->settings.sparse_index = 0;\n>      -+ if (!repo_config_get_bool(r, \"extensions.sparseindex\", &value) && value)\n>      ++ if (!repo_config_get_bool(r, \"index.sparse\", &value) && value)\n>       +         r->settings.sparse_index = 1;\n>        }\n>\n>      @@ repository.h: struct repo_settings {\n>\n>        struct repository {\n>\n>      - ## setup.c ##\n>      -@@ setup.c: static enum extension_result handle_extension(const char *var,\n>      -                  return error(\"invalid value for 'extensions.objectformat'\");\n>      -          data->hash_algo = format;\n>      -          return EXTENSION_OK;\n>      -+ } else if (!strcmp(ext, \"sparseindex\")) {\n>      -+         data->sparse_index = 1;\n>      -+         return EXTENSION_OK;\n>      -  }\n>      -  return EXTENSION_UNKNOWN;\n>      - }\n>      -\n>        ## sparse-index.c ##\n>       @@ sparse-index.c: static int convert_to_sparse_rec(struct index_state *istate,\n>         return num_converted - start_converted;\n>      @@ sparse-index.c: static int convert_to_sparse_rec(struct index_state *istate,\n>       +{\n>       + const char *config_path = repo_git_path(repo, \"config.worktree\");\n>       +\n>      -+ if (upgrade_repository_format(1) < 0) {\n>      -+         warning(_(\"unable to upgrade repository format to enable sparse-index\"));\n>      -+         return -1;\n>      -+ }\n>       + git_config_set_in_file_gently(config_path,\n>      -+                               \"extensions.sparseIndex\",\n>      ++                               \"index.sparse\",\n>       +                               \"true\");\n>       +\n>       + prepare_repo_settings(repo);\n>      @@ sparse-index.c: static int convert_to_sparse_rec(struct index_state *istate,\n>       +\n>       + /*\n>       +  * The GIT_TEST_SPARSE_INDEX environment variable triggers the\n>      -+  * extensions.sparseIndex config variable to be on.\n>      ++  * index.sparse config variable to be on.\n>       +  */\n>       + if (git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0)) {\n>       +         int err = enable_sparse_index(istate->repo);\n>      @@ sparse-index.c: static int convert_to_sparse_rec(struct index_state *istate,\n>       -  * GIT_TEST_SPARSE_INDEX environment variable. We will relax\n>       -  * this once we have a proper way to opt-in (and later still,\n>       -  * opt-out).\n>      -+  * Only convert to sparse if extensions.sparseIndex is set.\n>      ++  * Only convert to sparse if index.sparse is set.\n>          */\n>       - if (!git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0))\n>       + prepare_repo_settings(istate->repo);\n>  16:  923081e7e079 ! 17:  75fe9b0f57da sparse-checkout: toggle sparse index from builtin\n>      @@ Documentation/git-sparse-checkout.txt: To avoid interfering with other worktrees\n>       +that is not completely understood by external tools. If you have trouble\n>       +with this compatibility, then run `git sparse-checkout init --no-sparse-index`\n>       +to rewrite your index to not be sparse. Older versions of Git will not\n>      -+understand the `sparseIndex` repository extension and may fail to interact\n>      -+with your repository until it is disabled.\n>      ++understand the sparse directory entries index extension and may fail to\n>      ++interact with your repository until it is disabled.\n>\n>        'set'::\n>         Write a set of patterns to the sparse-checkout file, as given as\n>      @@ builtin/sparse-checkout.c: static int sparse_checkout_init(int argc, const char\n>\n>        ## sparse-index.c ##\n>       @@ sparse-index.c: static int convert_to_sparse_rec(struct index_state *istate,\n>      +  return num_converted - start_converted;\n>      + }\n>\n>      - static int enable_sparse_index(struct repository *repo)\n>      +-static int enable_sparse_index(struct repository *repo)\n>      ++static int set_index_sparse_config(struct repository *repo, int enable)\n>        {\n>       - const char *config_path = repo_git_path(repo, \"config.worktree\");\n>      -+ int res;\n>      -\n>      -  if (upgrade_repository_format(1) < 0) {\n>      -          warning(_(\"unable to upgrade repository format to enable sparse-index\"));\n>      -          return -1;\n>      -  }\n>      +-\n>       - git_config_set_in_file_gently(config_path,\n>      --                               \"extensions.sparseIndex\",\n>      +-                               \"index.sparse\",\n>       -                               \"true\");\n>      -+ res = git_config_set_gently(\"extensions.sparseindex\", \"true\");\n>      ++ int res;\n>      ++ char *config_path = repo_git_path(repo, \"config.worktree\");\n>      ++ res = git_config_set_in_file_gently(config_path,\n>      ++                                     \"index.sparse\",\n>      ++                                     enable ? \"true\" : NULL);\n>      ++ free(config_path);\n>\n>         prepare_repo_settings(repo);\n>         repo->settings.sparse_index = 1;\n>      @@ sparse-index.c: static int convert_to_sparse_rec(struct index_state *istate,\n>       +\n>       +int set_sparse_index_config(struct repository *repo, int enable)\n>       +{\n>      -+ int res;\n>      -+\n>      -+ if (enable)\n>      -+         return enable_sparse_index(repo);\n>      -+\n>      -+ /* Don't downgrade repository format, just remove the extension. */\n>      -+ res = git_config_set_gently(\"extensions.sparseindex\", NULL);\n>      ++ int res = set_index_sparse_config(repo, enable);\n>       +\n>       + prepare_repo_settings(repo);\n>      -+ repo->settings.sparse_index = 0;\n>      ++ repo->settings.sparse_index = enable;\n>       + return res;\n>        }\n>\n>      @@ sparse-index.c: static int convert_to_sparse_rec(struct index_state *istate,\n>             !core_apply_sparse_checkout || !core_sparse_checkout_cone)\n>                 return 0;\n>       @@ sparse-index.c: int convert_to_sparse(struct index_state *istate)\n>      -          istate->repo = the_repository;\n>      -\n>      -  /*\n>      --  * The GIT_TEST_SPARSE_INDEX environment variable triggers the\n>      --  * extensions.sparseIndex config variable to be on.\n>      -+  * If GIT_TEST_SPARSE_INDEX=1, then trigger extensions.sparseIndex\n>      -+  * to be fully enabled. If GIT_TEST_SPARSE_INDEX=0 (set explicitly),\n>      -+  * then purposefully disable the setting.\n>      +   * The GIT_TEST_SPARSE_INDEX environment variable triggers the\n>      +   * index.sparse config variable to be on.\n>          */\n>       - if (git_env_bool(\"GIT_TEST_SPARSE_INDEX\", 0)) {\n>       -         int err = enable_sparse_index(istate->repo);\n>      @@ sparse-index.c: int convert_to_sparse(struct index_state *istate)\n>       +         set_sparse_index_config(istate->repo, test_env);\n>\n>         /*\n>      -   * Only convert to sparse if extensions.sparseIndex is set.\n>      +   * Only convert to sparse if index.sparse is set.\n>\n>        ## sparse-index.h ##\n>       @@ sparse-index.h: struct index_state;\n>      @@ t/t1092-sparse-checkout-compatibility.sh: init_repos () {\n>       - GIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout init --cone &&\n>       - GIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep\n>       + git -C sparse-index sparse-checkout init --cone --sparse-index &&\n>      -+ test_cmp_config -C sparse-index true extensions.sparseindex &&\n>      ++ test_cmp_config -C sparse-index true index.sparse &&\n>       + git -C sparse-index sparse-checkout set deep\n>        }\n>\n>  17:  6f1ad72c390d ! 18:  7f55a232e647 sparse-checkout: disable sparse-index\n>      @@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'sparse-checkout disable\n>\n>       +test_expect_success 'sparse-index enabled and disabled' '\n>       + git -C repo sparse-checkout init --cone --sparse-index &&\n>      -+ test_cmp_config -C repo true extensions.sparseIndex &&\n>      ++ test_cmp_config -C repo true index.sparse &&\n>       + test-tool -C repo read-cache --table >cache &&\n>       + grep \" tree \" cache &&\n>       +\n>      @@ t/t1091-sparse-checkout-builtin.sh: test_expect_success 'sparse-checkout disable\n>       + test-tool -C repo read-cache --table >cache &&\n>       + ! grep \" tree \" cache &&\n>       + git -C repo config --list >config &&\n>      -+ ! grep extensions.sparseindex config\n>      ++ ! grep index.sparse config\n>       +'\n>       +\n>        test_expect_success 'cone mode: init and set' '\n>  18:  bd94e6b7d089 = 19:  365901809d9d cache-tree: integrate with sparse directory entries\n>  19:  e7190376b806 = 20:  9b068c458898 sparse-index: loose integration with cache_tree_verify()\n>  20:  bcf0a58eb38c = 21:  66602733cc95 p2000: add sparse-index repos\n\nI've read through the range-diff and individually read through the new\npatch 11.  Perhaps unsurprisingly since you addressed all my feedback\nby about round 3, I didn't find any problems with this new version.\nLooks good to me.\n"},{"id":"420797","messageId":"CABPp-BH5vxowVo3DP==EtpN+oTpW4jeD0CdWjbhpoxXFNhjfsw@mail.gmail.com","threadId":"55195","inReplyTo":"0a2e3028-8ebe-86be-d55d-b96aefb1ee53@gmail.com","subject":"Re: [PATCH v5 00/21] Sparse Index: Design, Format, Tests","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-04-01T05:59:57Z","receivedAt":"2021-04-01T06:01:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Mar 30, 2021 at 2:31 PM Derrick Stolee <stolee@gmail.com> wrote:\n>\n> On 3/30/2021 4:11 PM, Junio C Hamano wrote:\n> > \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> >\n> >>      @@ repo-settings.c: void prepare_repo_settings(struct repository *r)\n> >>       +       * Initialize this as off.\n> >>       +       */\n> >>       +      r->settings.sparse_index = 0;\n> >>      -+      if (!repo_config_get_bool(r, \"extensions.sparseindex\", &value) && value)\n> >>      ++      if (!repo_config_get_bool(r, \"index.sparse\", &value) && value)\n> >>       +              r->settings.sparse_index = 1;\n> >>        }\n> >\n> > It would be helpful to have a way for the repository owner to say\n> > \"Even if the version of Git may be capable of handling 'sdir'\n> > extension, and my checkout uses sparse-cone settings, I do not want\n> > to use it\", and the other way around, i.e. \"Even if my checkout\n> > currently does not use sparse-cone settings, do use 'sdir'\n> > extension\".  But for that, .sparse_index member may need to be\n> > tristate (i.e. forbidden, enable-if-needed, use-even-unneeded)?\n>\n> I believe as presented, index.sparse=false will prevent the sdir\n> extension from being used. If index.sparse=true, then it will only\n> be used if sparse-checkout is enabled in cone mode.\n>\n> I don't see the value in using the 'sdir' extension when not using\n> sparse-checkout in cone mode (and hence there are no sparse directory\n> entries in the index). What am I missing?\n>\n> > We have a similar setting in index.version; I believe we always\n> > auto-demote 3 down to 2 when extended flags are not used, and\n> > I think \"always auto-demote\" would be sufficient (iow,\n> > \"use-even-unneeded\" may not be necessary, even though that might\n> > help debugging).\n>\n> Yes, the same is happening here: we auto-demote to not use 'sdir'\n> if it the other settings are not configured as well.\n>\n> There is the rare scenario where these things all occur:\n>\n> 1. index.sparse = true\n> 2. core.sparseCheckout = true\n> 3. core.sparseCheckoutCone = true\n> 4. Every path in the index matches the cone-mode patterns.\n>\n> In this case, convert_to_sparse() is called and the istate->sparse\n> bit is set, telling do_write_index() to add the 'sdir' extension.\n>\n> This seems like a rare occurrence. Is it still worth adding logic\n> to avoid 'sdir' when these are all true?\n\nI'd agree that this would be very rare; probably indicative of someone\neither having a bug in their sparsity patterns or making a simplistic\ntestcase to see how things operate.\n"}]}