{"thread":{"id":"3952","subject":"[PATCH 4/4] Invalidate cache-tree entries for touched paths in git-apply.","startedAt":"2006-04-23T23:52:52Z","lastAt":"2006-04-25T23:05:56Z","messageCount":8,"participants":["Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"19026","messageId":"7v3bg3etnv.fsf@assigned-by-dhcp.cox.net","threadId":"3952","inReplyTo":null,"subject":"[PATCH 4/4] Invalidate cache-tree entries for touched paths in git-apply.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-04-23T23:52:52Z","receivedAt":"2006-04-23T23:52:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This updates git-apply to maintain cache-tree information.  With\nthis and the previous write-tree patch, repeated \"apply --index\"\nfollowed by \"write-tree\" on a huge tree will hopefully become\nfaster.\n\nSigned-off-by: Junio C Hamano <junkio@cox.net>\n\n---\n\n * ... then the big rock falls.  With this, I tried to apply and\n   then write-tree \"diff-tree -p $commit^1 $commit\" on top of\n   \"$commit^1\" for the last 20 or so commits in the kernel tree.\n   The \"master\" version takes 0.15 second per patch on my Duron\n   750 with 700MB, while this one does that in 0.06 second.\n   This also helps the memory pressure because we do not have to\n   regenerate unchanged trees.  810 minor faults with the patch\n   vs 2150 minor faults without.\n\n apply.c |   16 ++++++++++++++--\n 1 files changed, 14 insertions(+), 2 deletions(-)\n\n94005d7bb4b55e9984f6505a8916d23d304ccc4d\ndiff --git a/apply.c b/apply.c\nindex 269210a..f7cdefa 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -8,9 +8,14 @@\n  */\n #include <fnmatch.h>\n #include \"cache.h\"\n+#include \"cache-tree.h\"\n #include \"quote.h\"\n #include \"blob.h\"\n \n+static unsigned char active_cache_sha1[20];\n+static struct cache_tree *active_cache_tree;\n+\n+\n //  --check turns on checking that the working tree matches the\n //    files that are being modified, but doesn't apply the patch\n //  --stat does just a diffstat, and doesn't actually apply\n@@ -1717,6 +1722,7 @@ static void remove_file(struct patch *pa\n \tif (write_index) {\n \t\tif (remove_file_from_cache(patch->old_name) < 0)\n \t\t\tdie(\"unable to remove %s from index\", patch->old_name);\n+\t\tcache_tree_invalidate_path(active_cache_tree, patch->old_name);\n \t}\n \tunlink(patch->old_name);\n }\n@@ -1815,6 +1821,7 @@ static void create_file(struct patch *pa\n \t\tmode = S_IFREG | 0644;\n \tcreate_one_file(path, mode, buf, size);\t\n \tadd_index_file(path, mode, buf, size);\n+\tcache_tree_invalidate_path(active_cache_tree, path);\n }\n \n static void write_out_one_result(struct patch *patch)\n@@ -1912,8 +1919,9 @@ static int apply_patch(int fd, const cha\n \tif (write_index)\n \t\tnewfd = hold_index_file_for_update(&cache_file, get_index_file());\n \tif (check_index) {\n-\t\tif (read_cache() < 0)\n+\t\tif (read_cache_1(active_cache_sha1) < 0)\n \t\t\tdie(\"unable to read index file\");\n+\t\tactive_cache_tree = read_cache_tree(active_cache_sha1);\n \t}\n \n \tif ((check || apply) && check_patch_list(list) < 0)\n@@ -1923,9 +1931,13 @@ static int apply_patch(int fd, const cha\n \t\twrite_out_results(list, skipped_patch);\n \n \tif (write_index) {\n-\t\tif (write_cache(newfd, active_cache, active_nr) ||\n+\t\tif (write_cache_1(newfd, active_cache, active_nr,\n+\t\t\t\t  active_cache_sha1) ||\n \t\t    commit_index_file(&cache_file))\n \t\t\tdie(\"Unable to write new cachefile\");\n+\t\tcache_tree_update(active_cache_tree,\n+\t\t\t\t  active_cache, active_nr, 1);\n+\t\twrite_cache_tree(active_cache_sha1, active_cache_tree);\n \t}\n \n \tif (show_index_info)\n-- \n1.3.0.g623a\n"},{"id":"19029","messageId":"7vodyrdas9.fsf@assigned-by-dhcp.cox.net","threadId":"3952","inReplyTo":"7v3bg3etnv.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH 4/4] Invalidate cache-tree entries for touched paths in git-apply.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-04-24T01:25:58Z","receivedAt":"2006-04-24T01:25:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <junkio@cox.net> writes:\n\n>  * ... then the big rock falls.  With this, I tried to apply and\n>    then write-tree \"diff-tree -p $commit^1 $commit\" on top of\n>    \"$commit^1\" for the last 20 or so commits in the kernel tree.\n>    The \"master\" version takes 0.15 second per patch on my Duron\n>    750 with 700MB, while this one does that in 0.06 second.\n>    This also helps the memory pressure because we do not have to\n>    regenerate unchanged trees.  810 minor faults with the patch\n>    vs 2150 minor faults without.\n\nSorry, but not really.  The patch is wrong and the measurement\nwas flawed.\n\nIt was doing unnecessarily more work in git-apply, which made\ngit-write-tree a no-op, and I was measuring only git-write-tree.\n\nThe following goes on top of the series to remove that\nunnecessary work from git-apply.  Unfortunately this makes the\noverall combination a bit slower than before X-<.\n\nBut I have not optimized cache-tree.c for speed; for example,\nits subtree lists are not even sorted.  Once that is done we may\nget decent speedups from the combo.\n\n---\ndiff --git a/apply.c b/apply.c\nindex 5fa2c1e..e283df3 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -1935,8 +1935,6 @@ static int apply_patch(int fd, const cha\n \t\t\t\t  active_cache_sha1) ||\n \t\t    commit_index_file(&cache_file))\n \t\t\tdie(\"Unable to write new cachefile\");\n-\t\tcache_tree_update(active_cache_tree,\n-\t\t\t\t  active_cache, active_nr, 1);\n \t\twrite_cache_tree(active_cache_sha1, active_cache_tree);\n \t}\n \n"},{"id":"19031","messageId":"7vk69fd70m.fsf@assigned-by-dhcp.cox.net","threadId":"3952","inReplyTo":"7vodyrdas9.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH 4/4] Invalidate cache-tree entries for touched paths in git-apply.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-04-24T02:47:21Z","receivedAt":"2006-04-24T02:47:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <junkio@cox.net> writes:\n\n> Junio C Hamano <junkio@cox.net> writes:\n>\n>>  * ... then the big rock falls.  With this, I tried to apply and\n>>    then write-tree \"diff-tree -p $commit^1 $commit\" on top of\n>>    \"$commit^1\" for the last 20 or so commits in the kernel tree.\n>>    The \"master\" version takes 0.15 second per patch on my Duron\n>>    750 with 700MB, while this one does that in 0.06 second.\n>>    This also helps the memory pressure because we do not have to\n>>    regenerate unchanged trees.  810 minor faults with the patch\n>>    vs 2150 minor faults without.\n>\n> Sorry, but not really.  The patch is wrong and the measurement\n> was flawed.\n\nAgain, sorry, but there are some more bugs in the cache-tree\ncode that I need to fix and re-measure.\n\nIn the meantime, please do not use it on your production\nrepositories.  It does not seem to produce corrupt trees, but it\ncreates broken index.aux for no good reason.\n"},{"id":"19033","messageId":"7v8xpvd69s.fsf_-_@assigned-by-dhcp.cox.net","threadId":"3952","inReplyTo":"7vodyrdas9.fsf@assigned-by-dhcp.cox.net","subject":"[PATCH 5/4] Minimum fixups to cache-tree","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-04-24T03:03:27Z","receivedAt":"2006-04-24T03:03:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The first hunk is the repeat of the previous \"oops\".  The second\nis a real fix.\n\nWith this applied, 100-patch series (going from present to past\nat the Linux 2.6 kernel tip) applies correctly with the\nfollowing stats:\n\nTrying w/o the patch...\n24.57user 4.48system 0:34.47elapsed 84%CPU (0avgtext+0avgdata 0maxresident)k\n0inputs+0outputs (0major+322156minor)pagefaults 0swaps\nTrying w/ the patch...\n15.81user 3.00system 0:20.84elapsed 90%CPU (0avgtext+0avgdata 0maxresident)k\n0inputs+0outputs (0major+252063minor)pagefaults 0swaps\n\nSo there definitely is an improvement.  *Grin*.\n\nThe script used to bench this is attached at the end.  \"linus\" is the tracking\nbranch and currently it points at v2.6.17-rc2-g6b426e7\n$HOME/git-master/bin is where I installed the \"master\" version,\nand $J (aka ../git.junio/) is the freshly compiled area with the\npatch series applied (all five of four ;-).\n\n-- >8 --\nMinimum fixups to make things usable.\n\n---\ndiff --git a/apply.c b/apply.c\nindex 5fa2c1e..e283df3 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -1935,8 +1935,6 @@ static int apply_patch(int fd, const cha\n \t\t\t\t  active_cache_sha1) ||\n \t\t    commit_index_file(&cache_file))\n \t\t\tdie(\"Unable to write new cachefile\");\n-\t\tcache_tree_update(active_cache_tree,\n-\t\t\t\t  active_cache, active_nr, 1);\n \t\twrite_cache_tree(active_cache_sha1, active_cache_tree);\n \t}\n \ndiff --git a/cache-tree.c b/cache-tree.c\nindex 4dbdb65..f6d1dd1 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -348,10 +348,7 @@ #endif\n \t}\n \tfor (i = 0; i < it->subtree_nr; i++) {\n \t\tstruct cache_tree_sub *down = it->down[i];\n-\t\tint len = pathlen + down->namelen;\n-\t\tmemcpy(path + pathlen, down->name, down->namelen);\n-\t\tpath[len] = '/';\n-\t\tbuffer = write_one(down->cache_tree, path, len+1,\n+\t\tbuffer = write_one(down->cache_tree, down->name, down->namelen,\n \t\t\t\t   buffer, size, offset);\n \t}\n \treturn buffer;\n\n--\n\n#!/bin/sh\nJ=../git.junio\nM=$HOME/git-master/bin\nexport J M\n\ngit reset --hard linus\n\nprevious=\ngit rev-list -n 100 HEAD |\nwhile read commit\ndo\n\tif test -n \"$previous\"\n\tthen\n\t\tgit-diff-tree -p \"$previous\" \"$commit\" >\"$commit-$previous\"\n\t\techo \"$commit $previous\"\n\tfi\n\tprevious=\"$commit\"\ndone >series\n\ngit reset --hard linus\necho \"Trying w/o the patch...\"\n/usr/bin/time sh -c '\n\twhile read commit previous\n\tdo\n\t\t$M/git-apply --whitespace=nowarn --index \"$commit-$previous\"\n\t\t$M/git-write-tree\n\tdone <series\n' >out-1\ntree1=`git write-tree`\n\ngit reset --hard linus\necho \"Trying w/ the patch...\"\n$J/git-write-tree\n/usr/bin/time sh -c '\n\twhile read commit previous\n\tdo\n\t\t$J/git-apply --whitespace=nowarn --index \"$commit-$previous\"\n\t\t$J/git-write-tree\n\tdone <series\n' >out-2\ntree2=`git write-tree`\ntest \"$tree1\" = \"$tree2\" || exit 1\ncmp out-1 out-2\n"},{"id":"19039","messageId":"7vvesz8r8o.fsf@assigned-by-dhcp.cox.net","threadId":"3952","inReplyTo":"7v8xpvd69s.fsf_-_@assigned-by-dhcp.cox.net","subject":"50% speed-up of \"git-apply && git-write-tree\" sequence.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-04-24T05:41:43Z","receivedAt":"2006-04-24T05:41:43Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"I've fixed-up the series and they are now sitting near the tip\nof \"pu\" branch tonight.  With some more testing I am hoping to\nmerge it in \"next\" soon.\n\nI should recap the overall idea for general public here.\n\nWhen applying a series of patches to your working tree and\nmaking commits out of the results, what happens is roughly this:\n\n 1. Your index file exactly matches your HEAD commit; you may\n    have local modifications to some paths in your working tree\n    as long as they do not interfere with the patch application.\n\n 2. \"git-apply --index\" reads the index file, reads the patch,\n    applies them to both index and the working tree, and writes\n    the updated index file out.\n\n 3. \"git-write-tree\" reads the index file, writes the index\n    entries out as a set of hierarchical tree objects, the top\n    of which is wrapped in a new commit object that has the\n    current HEAD as the parent, and the commit becomes the new\n    HEAD.\n\nYou repeat 1-3 as many times as you apply patches.\n\nWhen dealing with a huge tree, with a typical patch touching\nonly a handful paths, this is not very efficient.  A recent\nkernel source tree has about 1200 directories, each of which\nneeds to be made into the \"tree\" object format in-core from the\ndata in the index file, and we hash it to see if we already have\nthat tree, and if not we write it out after compression (if we\ndo have it, we do not do anything after checking).\n\nThe updated code does this:\n\n (1) When git-write-tree writes out trees from the index, we\n     store <directory, tree SHA1> pair for all the trees we\n     compute, in .git/index.aux file.\n\n (2) When git-write-tree starts up, it reads the index file, and\n     also reads the .git/index.aux file.  The latter file\n     contains a fingerprint of the former, so if the index was\n     updated without updating the index.aux, what is read is\n     discarded.  However, if .git/index.aux is up-to-date, what\n     it records can be reused when we scan the index.\n\n (3) There are a few commands that updates the index file.\n     git-update-index is the most well-known one, but read-tree\n     and apply also update it.\n\n     No command other than apply updates index.aux when writing\n     out index (yet).  However, git-apply has been told about\n     it.  It reads index and index.aux file, and when it updates\n     an index entry, it _invalidates_ the directory information\n     it read from index.aux file, and writes the updated\n     index.aux file out when it is done.\n\n     Note.  Earlier I sent out a fix ([PATCH 5/4]) that\n     contained a two-line removal from apply.c; the code was\n     doing more than just invalidating entries, but actually\n     recomputing the tree.  The fixed code does not hash tree\n     information when git-apply writes index out.\n\n (4) When git-write-tree starts up after git-apply finishes its\n     job, it notices index.aux file matches the fingerprint of\n     the index (so it is up-to-date) and some entries have been\n     _invalidated_.  When it writes out the index, the parts of\n     the directory hierarchy that have not been invalidated do\n     not have to be formatted in-core into \"tree\" -- we already\n     know what tree object they are.  Only the parts that were\n     invalidated need to be computed.\n\nFor this, a new data structure \"cache_tree\" is introduced.  The\nway to use the API is very simple.  You initialize it like this:\n\n\tstruct cache_tree *active_cache_tree;\n\tunsigned char sha1[20];\n\tread_cache_1(sha1);\n        active_cache_tree = read_cache_tree(sha1);\n     \nThe new function read_cache_1() is similar to the traditional\nread_cache(), but it gives back the index file fingerprint.\nThis is given to read_cache_tree() to make sure index.aux is not\nstale.  Then, when you touch a path in the cache, you invalidate\nthe paths in the cache_tree with:\n\n\tcache_tree_invalidate_path(active_cache_tree, path);\n\nFor example, if you are updating an index entry\n\"fs/ext3/inode.c\", it invalidates the cached tree SHA1\ninformation for top-level tree, \"fs\" tree and \"fs/ext3\" tree.\nAll other information you read from index.aux are still valid\n(IOW, three out of 1200 are invalidated).\n\nWhen you are done, you write out the index file and the updated\nindex.aux file like this:\n\n        if (write_cache_1(newfd, active_cache, active_nr, active_cache_sha1) ||\n            commit_index_file(&cache_file))\n                die(\"Unable to write new cachefile\");\n        write_cache_tree(active_cache_sha1, active_cache_tree);\n\nAgain, write_cache_1() has one extra parameter to receive the\nupdated index file fingerprint, and you call write_cache_tree()\nwith it.  It updates index.aux file (with invalidated entries).\n\nOne thing write-tree does different is this (note that it does\nnot update the index file, it just reads from it):\n\n\tif (cache_tree_update(active_cache_tree, active_cache, active_nr,\n\t\t\t      missing_ok))\n\t\tdie(\"git-write-tree: error building trees\");\n\twrite_cache_tree(active_cache_sha1, active_cache_tree);\n\nThe call to cache_tree_update() takes the (possibly partially\ninvalidated) cache_tree, fills in the invalidated parts from the\nindex file (this involves creation of new \"tree\" objects as\nneeded).  So the above call to write_cache_tree() writes a\ncomplete index.aux file out without any invalidated entries.\n\nObviously, the change similar to what I did to apply.c in this\nseries can be done to other index-updating commands.  That's\n\"low hanging fruit\" for the week ;-).\n"},{"id":"19060","messageId":"7vodyq64p7.fsf_-_@assigned-by-dhcp.cox.net","threadId":"3952","inReplyTo":"7vvesz8r8o.fsf@assigned-by-dhcp.cox.net","subject":"maintenance of cache-tree data","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-04-24T21:31:32Z","receivedAt":"2006-04-24T21:31:32Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <junkio@cox.net> writes:\n\n>  (1) When git-write-tree writes out trees from the index, we\n>      store <directory, tree SHA1> pair for all the trees we\n>      compute, in .git/index.aux file.\n\nThere are two reasons I did not make this extra information part\nof the index file.\n\nThe number one reason was because the current index file format\nis pretty dense, and I did not find an obvious hole in the\nfront, in the middle or at the tail to sneak extra data in\nwithout upsetting existing code and without updating index file\nversion.  If this were a change to add a great new feature, that\nmight have warranted bumping the version up, but the cache-tree\nis an optimization and if you lose that information, all you\nhave lost is that now your write-tree needs to recompute the\nwhole tree as before (IOW, not much).\n\nThe second reason was I wanted to do this step-by-step, and\nwanted to do a demonstration of an end-to-end workflow that\ngains from this set of changes (apply followed by write-tree\nwas an obvious minimum set of commands), and while doing so I\ndid not have to disrupt other commands that are unaware of this\nextension.\n\nHaving said that, cache-tree.c has an internal interface to\nserialize the cache-tree data in-core, primarily because I was\nunsure if I wanted to append this information somewhere inside\nthe main index file or have it external when I did it; so we\ncould later push it into the index if we wanted to.\n\nIdeally, everybody who writes index should be converted to\nupdate cache-tree to help eventual write-tree.  Right now, if a\ncommand updates index without updating a matching cache-tree,\nthe whole thing is invalidated; this way, you do not risk using\nstale \"cached\" data.\n\nCurrently the command that primes the cache-tree is write-tree.\nThis may be counterintuitive -- even I myself would expect that\nread-tree would prime it, and various index-updaters invalidate\nsubtrees they touch, and write-tree to use the surviving parts\nto speed up what it needs to do, and write out an updated,\nfully-vaild cache-tree.  I did not do it only because it was not\nnecessary for \"apply then write-tree\" cycle, but read-tree\nshould be taught about cache-tree to help others, _and_ to help\nitself.\n\nWhen \"read-tree -m O A B\" merges three trees, we iterate over\nall index entries, even when only a small part of the tree has\nchanged.  This could be helped in a big way if the current index\nhas valid cache-tree information for the parts unaffected by the\nmerge.  If all three trees have identical tree in higher level\nsubdirectory (e.g. \"fs/\" in the kernel source), and if the index\nhas not touched anything in \"fs/\" since it read from our tree\n(i.e. \"A\"), then we do not even have to descend into that\ndirectory in the working tree to see which index entries are\ndirty.  We can just keep index entries that begin with \"fs/\"\nintact, keep the cache-tree entry for that directory as it was\nread from \"A\".  This would be a big win -- we do not have to\nread tree objects under \"fs/\" (there are 62 trees under \"fs/\",\nso we save uncompressing and undeltifying 180 objects).  This\noperation would need to invalidate entries in cache-tree that\nare involved in the actual merge.  Branch-switching two-tree\nform \"read-tree -m OLD NEW\" probably can benefit from the same\noptimization.\n\nObviously, a single-tree form of read-tree should be able to\nprime the cache-tree with fully valid data before writing the\nindex out.\n\nHaving said that, I am not touching read-tree myself for now; I\nam lazy.\n"},{"id":"19061","messageId":"7vk69e61s4.fsf@assigned-by-dhcp.cox.net","threadId":"3952","inReplyTo":"7vodyq64p7.fsf_-_@assigned-by-dhcp.cox.net","subject":"Re: maintenance of cache-tree data","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-04-24T22:34:35Z","receivedAt":"2006-04-24T22:34:35Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <junkio@cox.net> writes:\n\n> The number one reason was because the current index file format\n> is pretty dense, and I did not find an obvious hole in the\n> front, in the middle or at the tail to sneak extra data in\n> without upsetting existing code and without updating index file\n> version.\n\nWell, I was blind ;-).  As long as the whole-file SHA1 matches,\nread_cache() does not care if we have extra data after the\nseries of active_nr cache entry data in the index file.\n\nI'm working on a patch now.\n"},{"id":"19126","messageId":"7vy7xttfvv.fsf@assigned-by-dhcp.cox.net","threadId":"3952","inReplyTo":"7vk69e61s4.fsf@assigned-by-dhcp.cox.net","subject":"Re: maintenance of cache-tree data","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-04-25T23:05:56Z","receivedAt":"2006-04-25T23:05:56Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <junkio@cox.net> writes:\n\n> Well, I was blind ;-).  As long as the whole-file SHA1 matches,\n> read_cache() does not care if we have extra data after the\n> series of active_nr cache entry data in the index file.\n>\n> I'm working on a patch now.\n\nSo I did.\n\nThere is one bad thing; so far \"write-tree\" was a read-only\nconsumer of the index file, but now it primes the cache-tree\nstructure and needs to update the index.  But that is minor.\n\nWhile I was at it, I made this \"stuffing extra cruft in the\nindex\" slightly more generic than I needed it for this\nparticular application.  What I see this _might_ be useful for\nare:\n\n - We would want to store which commit of a subproject a\n   particular subdirectory came from.  This was one missing\n   piece from the \"bind commit\" proposal that wasn't implemented\n   in the jc/bind branch.\n\n - We might want to record \"at this path there is a directory,\n   albeit empty\"; this cannot be expressed with an usual index\n   entry.\n\n   We might be able to use cache-tree for that, but I think this\n   is something different at the logical level.  While\n   cache-tree is to be fully populated (by write-tree and\n   perhaps read-tree later) and invalidated partially when\n   update-index and friends smudge part of the tree, this is not\n   something we would want to even invalidate (IOW, it should\n   always be up-to-date), so they serve different purposes.\n\n\nI still haven't looked at the read-tree yet, but as I outlined\nin a previous message, its intra-index merge could take\nadvantage of cache-tree.  \"diff-index\", especially \"--cached\"\nkind, also could use it to skip unchanged subtrees altogether.\n"}]}