{"thread":{"id":"52093","subject":"[PATCH 1/1] commit-graph: fix writing first commit-graph during fetch","startedAt":"2019-10-22T17:29:02Z","lastAt":"2019-10-30T14:31:48Z","messageCount":23,"participants":["Derrick Stolee via GitGitGadget","Jeff King","SZEDER Gábor","Derrick Stolee"],"isPatch":true,"patchVersion":1,"patchTotal":1},"messages":[{"id":"384613","messageId":"a1e5280d4b61a972426063574f1ea890a7dab73b.1571765336.git.gitgitgadget@gmail.com","threadId":"52093","inReplyTo":"pull.415.git.1571765335.gitgitgadget@gmail.com","subject":"[PATCH 1/1] commit-graph: fix writing first commit-graph during fetch","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-22T17:28:55Z","receivedAt":"2019-10-22T17:29:02Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWhile dogfooding, Johannes found a bug in the fetch.writeCommitGraph\nconfig behavior. While his example initially happened during a clone\nwith --recurse-submodules, we found that it is actually a problem with\nthe first fetch after a new clone and no existing commit-graph file:\n\n\t$ git clone <url> test\n\t$ cd test\n\t$ git -c fetch.writeCommitGraph=true fetch origin\n\tComputing commit graph generation numbers: 100% (12/12), done.\n\tBUG: commit-graph.c:886: missing parent <hash1> for commit <hash2>\n\tAborted (core dumped)\n\nIn the repo I had cloned, there were really 60 commits to scan, but\nonly 12 were in the list to write when calling\ncompute_generation_numbers(). A commit in the list expects to see a\nparent, but that parent is not in the list.\n\nAs an initial test, I converted the code in builtin/fetch.c that calls\nwrite_commit_graph_reachable() to instead launch a \"git commit-graph\nwrite --reachable --split\" process. That code worked, but is not how we\nwant the feature to work long-term.\n\nThat test did demonstrate that the issue must be something to do with\ninternal state of the 'git fetch' process.\n\nThe write_commit_graph() method in commit-graph.c ensures the commits we\nplan to write are \"closed under reachability\" using close_reachable().\nThis method walks from the input commits, and uses the UNINTERESTING\nflag to mark which commits have already been visited. This allows the\nwalk to take O(N) time, where N is the number of commits, instead of\nO(P) time, where P is the number of paths. (The number of paths can be\nexponential in the number of commits.)\n\nHowever, the UNINTERESTING flag is used in lots of places in the\ncodebase. This flag usually means some barrier to stop a commit walk,\nsuch as in revision-walking to compare histories. It is not often\ncleared after the walk completes because the starting points of those\nwalks do not have the UNINTERESTING flag, and clear_commit_marks() would\nstop immediately.\n\nThis is happening during a 'git fetch' call with a remote. The fetch\nnegotiation is comparing the remote refs with the local refs and marking\nsome commits as UNINTERESTING.\n\nYou may ask: did this feature ever work at all? Yes, it did, as long as\nyou had a commit-graph covering all of your local refs. My testing was\nunfortunately limited to this scenario. The UNINTERESTING commits are\nalways part of the \"old\" commit-graph, and when we add new commits to a\ntop layer of the commit-graph chain those are not needed. If we happen\nto merge layers of the chain, then the commits are added as a list, not\nusing a commit walk. Further, the test added for this config option in\nt5510-fetch.sh uses local filesystem clones, which somehow avoids this\nlogic.\n\nI tested running clear_commit_marks_many() to clear the UNINTERESTING\nflag inside close_reachable(), but the tips did not have the flag, so\nthat did nothing.\n\nInstead, I finally arrived on the conclusion that I should use a flag\nthat is not used in any other part of the code. In commit-reach.c, a\nnumber of flags were defined for commit walk algorithms. The REACHABLE\nflag seemed like it made the most sense, and it seems it was not\nactually used in the file.\n\nAdd the REACHABLE flag to commit-graph.c and use it instead of\nUNINTERESTING in close_reachable(). This fixes the bug in manual\ntesting.\n\nI have failed to produce a test using the file:// protocol that\ndemonstrates this bug.\n\nReported-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n commit-graph.c | 11 +++++++----\n commit-reach.c |  1 -\n object.h       |  3 ++-\n 3 files changed, 9 insertions(+), 6 deletions(-)\n\ndiff --git a/commit-graph.c b/commit-graph.c\nindex fc4a43b8d6..0aea7b2ae5 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -41,6 +41,9 @@\n #define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * GRAPH_CHUNKLOOKUP_WIDTH \\\n \t\t\t+ GRAPH_FANOUT_SIZE + the_hash_algo->rawsz)\n \n+/* Remember to update object flag allocation in object.h */\n+#define REACHABLE       (1u<<15)\n+\n char *get_commit_graph_filename(const char *obj_dir)\n {\n \tchar *filename = xstrfmt(\"%s/info/commit-graph\", obj_dir);\n@@ -1030,11 +1033,11 @@ static void add_missing_parents(struct write_commit_graph_context *ctx, struct c\n {\n \tstruct commit_list *parent;\n \tfor (parent = commit->parents; parent; parent = parent->next) {\n-\t\tif (!(parent->item->object.flags & UNINTERESTING)) {\n+\t\tif (!(parent->item->object.flags & REACHABLE)) {\n \t\t\tALLOC_GROW(ctx->oids.list, ctx->oids.nr + 1, ctx->oids.alloc);\n \t\t\toidcpy(&ctx->oids.list[ctx->oids.nr], &(parent->item->object.oid));\n \t\t\tctx->oids.nr++;\n-\t\t\tparent->item->object.flags |= UNINTERESTING;\n+\t\t\tparent->item->object.flags |= REACHABLE;\n \t\t}\n \t}\n }\n@@ -1052,7 +1055,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)\n \t\tdisplay_progress(ctx->progress, i + 1);\n \t\tcommit = lookup_commit(ctx->r, &ctx->oids.list[i]);\n \t\tif (commit)\n-\t\t\tcommit->object.flags |= UNINTERESTING;\n+\t\t\tcommit->object.flags |= REACHABLE;\n \t}\n \tstop_progress(&ctx->progress);\n \n@@ -1089,7 +1092,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)\n \t\tcommit = lookup_commit(ctx->r, &ctx->oids.list[i]);\n \n \t\tif (commit)\n-\t\t\tcommit->object.flags &= ~UNINTERESTING;\n+\t\t\tcommit->object.flags &= ~REACHABLE;\n \t}\n \tstop_progress(&ctx->progress);\n }\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 3ea174788a..4ca7e706a1 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -10,7 +10,6 @@\n #include \"commit-reach.h\"\n \n /* Remember to update object flag allocation in object.h */\n-#define REACHABLE       (1u<<15)\n #define PARENT1\t\t(1u<<16)\n #define PARENT2\t\t(1u<<17)\n #define STALE\t\t(1u<<18)\ndiff --git a/object.h b/object.h\nindex 0120892bbd..25f5ab3d54 100644\n--- a/object.h\n+++ b/object.h\n@@ -68,7 +68,8 @@ struct object_array {\n  * bisect.c:                                        16\n  * bundle.c:                                        16\n  * http-push.c:                                     16-----19\n- * commit-reach.c:                                15-------19\n+ * commit-graph.c:                                15\n+ * commit-reach.c:                                  16-----19\n  * sha1-name.c:                                              20\n  * list-objects-filter.c:                                      21\n  * builtin/fsck.c:           0--3\n-- \ngitgitgadget\n"},{"id":"384614","messageId":"pull.415.git.1571765335.gitgitgadget@gmail.com","threadId":"52093","inReplyTo":null,"subject":"[PATCH 0/1] [v2.24.0-rc0 BUG] fetch.writeCommitGraph fails on first fetch","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-22T17:28:54Z","receivedAt":"2019-10-22T17:29:02Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"While dogfooding, Johannes found a bug in the fetch.writeCommitGraph config\nbehavior. While his example initially happened during a clone with\n--recurse-submodules, we found that it is actually a problem with the first\nfetch after a new clone and no existing commit-graph file:\n\n$ git clone <url> test\n$ cd test\n$ git -c fetch.writeCommitGraph=true fetch origin\nComputing commit graph generation numbers: 100% (12/12), done.\nBUG: commit-graph.c:886: missing parent <hash1> for commit <hash2>\nAborted (core dumped)\n\nIn the repo I had cloned, there were really 60 commits to scan, but only 12\nwere in the list to write when calling compute_generation_numbers(). A\ncommit in the list expects to see a parent, but that parent is not in the\nlist.\n\nThe details above are the start of the commit message for [PATCH 1/1]. I\ntried to include as much detail as I could for how I investigated the\nproblem and why I think this is the right solution.\n\nI would like help creating a test that demonstrates this problem. It does\nnot appear to trigger on the simplest examples. The simple example I used\nfor my testing was https://github.com/derrickstolee/numbers\n\nThanks, -Stolee\n\n/cc johannes.schindelin@gmx.de, peff@peff.net\n\nDerrick Stolee (1):\n  commit-graph: fix writing first commit-graph during fetch\n\n commit-graph.c | 11 +++++++----\n commit-reach.c |  1 -\n object.h       |  3 ++-\n 3 files changed, 9 insertions(+), 6 deletions(-)\n\n\nbase-commit: d966095db01190a2196e31195ea6fa0c722aa732\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-415%2Fderrickstolee%2Ffetch-first-write-fail-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-415/derrickstolee/fetch-first-write-fail-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/415\n-- \ngitgitgadget\n"},{"id":"384626","messageId":"20191022203316.GC12270@sigill.intra.peff.net","threadId":"52093","inReplyTo":"a1e5280d4b61a972426063574f1ea890a7dab73b.1571765336.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/1] commit-graph: fix writing first commit-graph during fetch","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2019-10-22T20:33:16Z","receivedAt":"2019-10-22T20:33:18Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Oct 22, 2019 at 05:28:55PM +0000, Derrick Stolee via GitGitGadget wrote:\n\n> However, the UNINTERESTING flag is used in lots of places in the\n> codebase. This flag usually means some barrier to stop a commit walk,\n> such as in revision-walking to compare histories. It is not often\n> cleared after the walk completes because the starting points of those\n> walks do not have the UNINTERESTING flag, and clear_commit_marks() would\n> stop immediately.\n\nOof. Nicely explained, and your fix makes sense.\n\nThe global-ness of revision flags always makes me nervous about doing\nmore things in-process (this isn't the first such bug we've had).\n\nI have a dream of converting most uses of flags into using a\ncommit-slab. That provides cheap access to an auxiliary structure, so\neach traversal, etc, could keep its own flag structure. I'm not sure if\nit would have a performance impact, though. Even though it's O(1), it is\nan indirect lookup, which could have some memory-access impact (though\nmy guess is it would be lost in the noise).\n\nOne of the sticking points is that all object types, not just commits,\nuse flags. But we only assign slab ids to commits. I noticed recently\nthat \"struct object\" has quite a few spare bits in it these days,\nbecause the switch to a 32-byte oid means 64-bit machines now have an\nextra 4 bytes of padding. I wonder if we could use that to store an\nindex field.\n\nAnyway, that's getting far off the topic; clearly we need a fix in the\nmeantime, and what you have here looks good to me.\n\n> I tested running clear_commit_marks_many() to clear the UNINTERESTING\n> flag inside close_reachable(), but the tips did not have the flag, so\n> that did nothing.\n\nAnother option would be clear_object_flags(), which just walks all of\nthe in-memory structs. Your REACHABLE solution is cheaper, though.\n\n> Instead, I finally arrived on the conclusion that I should use a flag\n> that is not used in any other part of the code. In commit-reach.c, a\n> number of flags were defined for commit walk algorithms. The REACHABLE\n> flag seemed like it made the most sense, and it seems it was not\n> actually used in the file.\n\nYeah, being able to remove it from commit-reach.c surprised me for a\nmoment. To further add to the confusion, builtin/fsck.c has its own\nREACHABLE flag (with a different bit and a totally different purpose). I\ndon't think there's any practical problem there, though.\n\n> I have failed to produce a test using the file:// protocol that\n> demonstrates this bug.\n\nHmm, from the description, it sounds like it should be easy. I might\npoke at it a bit.\n\n-Peff\n"},{"id":"384636","messageId":"20191022214553.GA18314@sigill.intra.peff.net","threadId":"52093","inReplyTo":"20191022203316.GC12270@sigill.intra.peff.net","subject":"Re: [PATCH 1/1] commit-graph: fix writing first commit-graph during fetch","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2019-10-22T21:45:54Z","receivedAt":"2019-10-22T21:46:01Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Oct 22, 2019 at 04:33:16PM -0400, Jeff King wrote:\n\n> > I have failed to produce a test using the file:// protocol that\n> > demonstrates this bug.\n> \n> Hmm, from the description, it sounds like it should be easy. I might\n> poke at it a bit.\n\nHmph. I can reproduce it here, but it seems to depend on the repository.\nIf I do this:\n\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex ecabbe1616..8d473a456f 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -583,6 +583,14 @@ test_expect_success 'fetch.writeCommitGraph' '\n \t)\n '\n \n+test_expect_success 'fetch.writeCommitGraph with a bigger repo' '\n+\tgit clone \"$TEST_DIRECTORY/..\" repo &&\n+\t(\n+\t\tcd repo &&\n+\t\tgit -c fetch.writeCommitGraph fetch origin\n+\t)\n+'\n+\n # configured prune tests\n \n set_config_tristate () {\n\nit reliably triggers the bug. But if I make a synthetic repo, even it\nhas a lot of commits (thousands or more), it doesn't trigger. I thought\nmaybe it had to do with having commits that were not at tips (since the\ntip ones presumably _are_ fed into the graph generation process). But\nthat doesn't seem to help.\n\nPuzzling...\n\n-Peff\n"},{"id":"384645","messageId":"20191022233556.GF4348@szeder.dev","threadId":"52093","inReplyTo":"20191022214553.GA18314@sigill.intra.peff.net","subject":"Re: [PATCH 1/1] commit-graph: fix writing first commit-graph during fetch","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-10-22T23:35:56Z","receivedAt":"2019-10-22T23:36:03Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Tue, Oct 22, 2019 at 05:45:54PM -0400, Jeff King wrote:\n> On Tue, Oct 22, 2019 at 04:33:16PM -0400, Jeff King wrote:\n> \n> > > I have failed to produce a test using the file:// protocol that\n> > > demonstrates this bug.\n> > \n> > Hmm, from the description, it sounds like it should be easy. I might\n> > poke at it a bit.\n> \n> Hmph. I can reproduce it here, but it seems to depend on the repository.\n> If I do this:\n> \n> diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\n> index ecabbe1616..8d473a456f 100755\n> --- a/t/t5510-fetch.sh\n> +++ b/t/t5510-fetch.sh\n> @@ -583,6 +583,14 @@ test_expect_success 'fetch.writeCommitGraph' '\n>  \t)\n>  '\n>  \n> +test_expect_success 'fetch.writeCommitGraph with a bigger repo' '\n> +\tgit clone \"$TEST_DIRECTORY/..\" repo &&\n> +\t(\n> +\t\tcd repo &&\n> +\t\tgit -c fetch.writeCommitGraph fetch origin\n> +\t)\n> +'\n> +\n>  # configured prune tests\n>  \n>  set_config_tristate () {\n> \n> it reliably triggers the bug. But if I make a synthetic repo, even it\n> has a lot of commits (thousands or more), it doesn't trigger. I thought\n> maybe it had to do with having commits that were not at tips (since the\n> tip ones presumably _are_ fed into the graph generation process). But\n> that doesn't seem to help.\n> \n> Puzzling...\n\nSubmodules?\n\n  $ cd ~/src/git/\n  $ git quotelog 86cfd61e6b\n  86cfd61e6b (sha1dc: optionally use sha1collisiondetection as a submodule, 2017-07-01)\n  $ git init --bare good.git\n  Initialized empty Git repository in /home/szeder/src/git/good.git/\n  $ git push -q good.git 86cfd61e6b^:refs/heads/master\n  $ git clone good.git good-clone\n  Cloning into 'good-clone'...\n  done.\n  $ git -c fetch.writeCommitGraph -C good-clone fetch origin\n  Computing commit graph generation numbers: 100% (46958/46958), done.\n  $ git init --bare bad.git\n  Initialized empty Git repository in /home/szeder/src/git/bad.git/\n  $ git push -q bad.git 86cfd61e6b:refs/heads/master\n  $ git clone bad.git bad-clone\n  Cloning into 'bad-clone'...\n  done.\n  $ git -c fetch.writeCommitGraph -C bad-clone fetch origin\n  Computing commit graph generation numbers: 100% (1/1), done.\n  BUG: commit-graph.c:886: missing parent 9936c1b52a39fa14fca04f937df3e75f7498ac66 for commit 86cfd61e6bc12745751c43b4f69886b290cd85cb\n  Aborted\n\nIn the cover letter Derrick mentioned that he used\nhttps://github.com/derrickstolee/numbers for testing, and that repo\nhas a submodule as well.\n\n"},{"id":"384655","messageId":"b7473363-b257-c00b-7338-a7e1d51bb01b@gmail.com","threadId":"52093","inReplyTo":"20191022233556.GF4348@szeder.dev","subject":"Re: [PATCH 1/1] commit-graph: fix writing first commit-graph during fetch","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-10-23T00:35:57Z","receivedAt":"2019-10-23T00:36:01Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"\n\nOn 10/22/2019 7:35 PM, SZEDER Gábor wrote:\n> On Tue, Oct 22, 2019 at 05:45:54PM -0400, Jeff King wrote:\n>> Puzzling...\n> \n> Submodules?\n> \n>   $ cd ~/src/git/\n>   $ git quotelog 86cfd61e6b\n>   86cfd61e6b (sha1dc: optionally use sha1collisiondetection as a submodule, 2017-07-01)\n>   $ git init --bare good.git\n>   Initialized empty Git repository in /home/szeder/src/git/good.git/\n>   $ git push -q good.git 86cfd61e6b^:refs/heads/master\n>   $ git clone good.git good-clone\n>   Cloning into 'good-clone'...\n>   done.\n>   $ git -c fetch.writeCommitGraph -C good-clone fetch origin\n>   Computing commit graph generation numbers: 100% (46958/46958), done.\n>   $ git init --bare bad.git\n>   Initialized empty Git repository in /home/szeder/src/git/bad.git/\n>   $ git push -q bad.git 86cfd61e6b:refs/heads/master\n>   $ git clone bad.git bad-clone\n>   Cloning into 'bad-clone'...\n>   done.\n>   $ git -c fetch.writeCommitGraph -C bad-clone fetch origin\n>   Computing commit graph generation numbers: 100% (1/1), done.\n>   BUG: commit-graph.c:886: missing parent 9936c1b52a39fa14fca04f937df3e75f7498ac66 for commit 86cfd61e6bc12745751c43b4f69886b290cd85cb\n>   Aborted\n> \n> In the cover letter Derrick mentioned that he used\n> https://github.com/derrickstolee/numbers for testing, and that repo\n> has a submodule as well.\n\nI completely forgot that I put a submodule in that repo. It makes sense\nthat the Git repo also has one. Thanks for the test! I'll get it into\nthe test suite tomorrow.\n\n-Stolee\n"},{"id":"384656","messageId":"20191023004820.GA19611@sigill.intra.peff.net","threadId":"52093","inReplyTo":"b7473363-b257-c00b-7338-a7e1d51bb01b@gmail.com","subject":"Re: [PATCH 1/1] commit-graph: fix writing first commit-graph during fetch","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2019-10-23T00:48:21Z","receivedAt":"2019-10-23T00:48:23Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Oct 22, 2019 at 08:35:57PM -0400, Derrick Stolee wrote:\n\n> > In the cover letter Derrick mentioned that he used\n> > https://github.com/derrickstolee/numbers for testing, and that repo\n> > has a submodule as well.\n> \n> I completely forgot that I put a submodule in that repo. It makes sense\n> that the Git repo also has one. Thanks for the test! I'll get it into\n> the test suite tomorrow.\n\nAh, right. Good catch Gábor. The test below fails for me without your\npatch, and succeeds with it (the extra empty commit is necessary so that\nwe have a parent).\n\nI admit I am puzzled, though, _why_ the presence of the submodule\nmatters. That is, from your explanation, I thought the issue was simply\nthat `fetch` walked (and marked) some commits, and the flags overlapped\nwith what the commit-graph code expected.\n\nI could guess that the presence of the submodule triggers some analysis\nfor --recurse-submodules. But then we don't actually recurse (maybe\nbecause they're not activated? In which case maybe we shouldn't be doing\nthat extra walk to look for submodules if there aren't any activated\nones in our local repo).\n\nI'd also wonder if it would be possible to trigger in another way (say,\ndue to want/have negotiation, though I guess most of the walking there\nis done on the server side). But it may not be worth digging too far\ninto it.\n\n-Peff\n\n---\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex ecabbe1616..1b092fec0b 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -583,6 +583,21 @@ test_expect_success 'fetch.writeCommitGraph' '\n \t)\n '\n \n+test_expect_success 'fetch.writeCommitGraph with a submodule' '\n+\tgit init has-submodule &&\n+\t(\n+\t\tcd has-submodule &&\n+\t\tgit commit --allow-empty -m parent &&\n+\t\tgit submodule add ../three &&\n+\t\tgit commit -m \"add submodule\"\n+\t) &&\n+\tgit clone has-submodule submod-clone &&\n+\t(\n+\t\tcd submod-clone &&\n+\t\tgit -c fetch.writeCommitGraph fetch origin\n+\t)\n+'\n+\n # configured prune tests\n \n set_config_tristate () {\n"},{"id":"384657","messageId":"20191023012256.GA24104@sigill.intra.peff.net","threadId":"52093","inReplyTo":"20191023004820.GA19611@sigill.intra.peff.net","subject":"Re: [PATCH 1/1] commit-graph: fix writing first commit-graph during fetch","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2019-10-23T01:22:57Z","receivedAt":"2019-10-23T01:22:59Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Oct 22, 2019 at 08:48:20PM -0400, Jeff King wrote:\n\n> I admit I am puzzled, though, _why_ the presence of the submodule\n> matters. That is, from your explanation, I thought the issue was simply\n> that `fetch` walked (and marked) some commits, and the flags overlapped\n> with what the commit-graph code expected.\n> \n> I could guess that the presence of the submodule triggers some analysis\n> for --recurse-submodules. But then we don't actually recurse (maybe\n> because they're not activated? In which case maybe we shouldn't be doing\n> that extra walk to look for submodules if there aren't any activated\n> ones in our local repo).\n\nIndeed, that seems to be it. If I do this:\n\n  git init repo\n  cd repo\n  cat >.gitmodules <<\\EOF\n  [submodule \"foo\"]\n  path = foo\n  url = https://example.com\n  EOF\n  time git fetch /path/to/git.git\n\nthen we end up traversing the whole git.git history a second time, even\nthough we should know off the bat that there are no active submodules\nthat we would recurse to.\n\nDoing this makes the problem go away:\n\ndiff --git a/submodule.c b/submodule.c\nindex 0f199c5137..0db2f18b93 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1193,7 +1193,7 @@ static void calculate_changed_submodule_paths(struct repository *r,\n \tstruct string_list_item *name;\n \n \t/* No need to check if there are no submodules configured */\n-\tif (!submodule_from_path(r, NULL, NULL))\n+\tif (!is_submodule_active(r, NULL))\n \t\treturn;\n \n \targv_array_push(&argv, \"--\"); /* argv[0] program name */\n\nbut causes some tests to fail (I think that in some cases we're supposed\nto auto-initialize, and we'd probably need to cover that case, too).\n\nAll of this is outside of your fix, of course, but:\n\n  1. I'm satisfied now that I understand why the test triggers the\n     problem.\n\n  2. You may want have a real activated submodule in your test. Right\n     now we'll trigger the submodule-recursion check even without that,\n     but in the future we might do something like the hunk above. In\n     which case your test wouldn't be checking anything interesting\n     anymore.\n\n-Peff\n"},{"id":"384699","messageId":"6ac0a05746df8ac3b1dd788f525b2620bc4d9a08.1571835695.git.gitgitgadget@gmail.com","threadId":"52093","inReplyTo":"pull.415.v2.git.1571835695.gitgitgadget@gmail.com","subject":"[PATCH v2 1/2] t5510-fetch.sh: demonstrate fetch.writeCommitGraph bug","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-23T13:01:34Z","receivedAt":"2019-10-23T13:01: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\nWhile dogfooding, Johannes found a bug in the fetch.writeCommitGraph\nconfig behavior. His example initially happened during a clone with\n--recurse-submodules, we found that this happens with the first fetch\nafter cloning a repository that contains a submodule:\n\n\t$ git clone <url> test\n\t$ cd test\n\t$ git -c fetch.writeCommitGraph=true fetch origin\n\tComputing commit graph generation numbers: 100% (12/12), done.\n\tBUG: commit-graph.c:886: missing parent <hash1> for commit <hash2>\n\tAborted (core dumped)\n\nIn the repo I had cloned, there were really 60 commits to scan, but\nonly 12 were in the list to write when calling\ncompute_generation_numbers(). A commit in the list expects to see a\nparent, but that parent is not in the list.\n\nA follow-up will fix the bug, but first we create a test that\ndemonstrates the problem.\n\nI used \"test_expect_failure\" for the entire test instead of\n\"test_must_fail\" only on the command that I expect to fail. This is\nbecause the BUG() returns an exit code so test_must_fail complains.\n\nHelped-by: Jeff King <peff@peff.net>\nHelped-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nHelped-by: Szeder Gábor <szeder.dev@gmail.com>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/t5510-fetch.sh | 17 +++++++++++++++++\n 1 file changed, 17 insertions(+)\n\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex ecabbe1616..e8ae3af0b6 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -583,6 +583,23 @@ test_expect_success 'fetch.writeCommitGraph' '\n \t)\n '\n \n+test_expect_failure 'fetch.writeCommitGraph with submodules' '\n+\tpwd=\"$(pwd)\" &&\n+\tgit clone dups super &&\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule add \"file://$pwd/three\" &&\n+\t\tgit commit -m \"add submodule\"\n+\t) &&\n+\tgit clone \"super\" writeError &&\n+\t(\n+\t\tcd writeError &&\n+\t\ttest_path_is_missing .git/objects/info/commit-graphs/commit-graph-chain &&\n+\t\tgit -c fetch.writeCommitGraph=true fetch origin &&\n+\t\ttest_path_is_file .git/objects/info/commit-graphs/commit-graph-chain\n+\t)\n+'\n+\n # configured prune tests\n \n set_config_tristate () {\n-- \ngitgitgadget\n\n"},{"id":"384700","messageId":"pull.415.v2.git.1571835695.gitgitgadget@gmail.com","threadId":"52093","inReplyTo":"pull.415.git.1571765335.gitgitgadget@gmail.com","subject":"[PATCH v2 0/2] [v2.24.0-rc0 BUG] fetch.writeCommitGraph fails on first fetch","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-23T13:01:33Z","receivedAt":"2019-10-23T13:01:40Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"UPDATE for V2: We now know the full repro, and a test is added. Thanks\nSzeder and Peff for your insights!\n\nWhile dogfooding, Johannes found a bug in the fetch.writeCommitGraph config\nbehavior. While his example initially happened during a clone with\n--recurse-submodules, (UPDATE) and the submodule is important, but\n--recurse-submodules is not:\n\n$ git clone <url> test\n$ cd test\n$ git -c fetch.writeCommitGraph=true fetch origin\nComputing commit graph generation numbers: 100% (12/12), done.\nBUG: commit-graph.c:886: missing parent <hash1> for commit <hash2>\nAborted (core dumped)\n\nIn the repo I had cloned, there were really 60 commits to scan, but only 12\nwere in the list to write when calling compute_generation_numbers(). A\ncommit in the list expects to see a parent, but that parent is not in the\nlist. The simple example I used for my testing was \nhttps://github.com/derrickstolee/numbers. Thie repo HAS A SUBMODULE, I just\nforgot. Sorry for derailing the investigation somewhat.\n\nThe details above are the start of the commit message for [PATCH 1/2],\nincluding a test that fails when fetching after cloning a repo with a\nsubmodule.\n\nIn [PATCH 2/2], I actually have the fix. I tried to include as much detail\nas I could for how I investigated the problem and why I think this is the\nright solution. I added details that have come from the on-list discussion,\nincluding what the submodule code is doing and why REACHABLE is no longer\nused in commit-reach.c.\n\nThanks, -Stolee\n\nDerrick Stolee (2):\n  t5510-fetch.sh: demonstrate fetch.writeCommitGraph bug\n  commit-graph: fix writing first commit-graph during fetch\n\n commit-graph.c   | 11 +++++++----\n commit-reach.c   |  1 -\n object.h         |  3 ++-\n t/t5510-fetch.sh | 17 +++++++++++++++++\n 4 files changed, 26 insertions(+), 6 deletions(-)\n\n\nbase-commit: d966095db01190a2196e31195ea6fa0c722aa732\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-415%2Fderrickstolee%2Ffetch-first-write-fail-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-415/derrickstolee/fetch-first-write-fail-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/415\n\nRange-diff vs v1:\n\n -:  ---------- > 1:  6ac0a05746 t5510-fetch.sh: demonstrate fetch.writeCommitGraph bug\n 1:  a1e5280d4b ! 2:  ca59b118f1 commit-graph: fix writing first commit-graph during fetch\n     @@ -2,10 +2,13 @@\n      \n          commit-graph: fix writing first commit-graph during fetch\n      \n     -    While dogfooding, Johannes found a bug in the fetch.writeCommitGraph\n     -    config behavior. While his example initially happened during a clone\n     -    with --recurse-submodules, we found that it is actually a problem with\n     -    the first fetch after a new clone and no existing commit-graph file:\n     +    The previous commit includes a failing test for an issue around\n     +    fetch.writeCommitGraph and fetching in a repo with a submodule. Here, we\n     +    fix that bug and set the test to \"test_expect_success\".\n     +\n     +    The prolem arises with this set of commands when the remote repo at\n     +    <url> has a submodule. Note that --recurse-submodules is not needed to\n     +    demonstrate the bug.\n      \n                  $ git clone <url> test\n                  $ cd test\n     @@ -14,12 +17,7 @@\n                  BUG: commit-graph.c:886: missing parent <hash1> for commit <hash2>\n                  Aborted (core dumped)\n      \n     -    In the repo I had cloned, there were really 60 commits to scan, but\n     -    only 12 were in the list to write when calling\n     -    compute_generation_numbers(). A commit in the list expects to see a\n     -    parent, but that parent is not in the list.\n     -\n     -    As an initial test, I converted the code in builtin/fetch.c that calls\n     +    As an initial fix, I converted the code in builtin/fetch.c that calls\n          write_commit_graph_reachable() to instead launch a \"git commit-graph\n          write --reachable --split\" process. That code worked, but is not how we\n          want the feature to work long-term.\n     @@ -60,20 +58,28 @@\n          flag inside close_reachable(), but the tips did not have the flag, so\n          that did nothing.\n      \n     +    It turns out that the calculate_changed_submodule_paths() method is at\n     +    fault. Thanks, Peff, for pointing out this detail! More specifically,\n     +    for each submodule, the collect_changed_submodules() runs a revision\n     +    walk to essentially do file-history on the list of submodules. That\n     +    revision walk marks commits UNININTERESTING if they are simiplified away\n     +    by not changing the submodule.\n     +\n          Instead, I finally arrived on the conclusion that I should use a flag\n          that is not used in any other part of the code. In commit-reach.c, a\n          number of flags were defined for commit walk algorithms. The REACHABLE\n          flag seemed like it made the most sense, and it seems it was not\n     -    actually used in the file.\n     +    actually used in the file. The REACHABLE flag was used in early versions\n     +    of commit-reach.c, but was removed by 4fbcca4 (commit-reach: make\n     +    can_all_from_reach... linear, 2018-07-20).\n      \n          Add the REACHABLE flag to commit-graph.c and use it instead of\n          UNINTERESTING in close_reachable(). This fixes the bug in manual\n          testing.\n      \n     -    I have failed to produce a test using the file:// protocol that\n     -    demonstrates this bug.\n     -\n          Reported-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n     +    Helped-by: Jeff King <peff@peff.net>\n     +    Helped-by: Szeder Gábor <szeder.dev@gmail.com>\n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n      \n       diff --git a/commit-graph.c b/commit-graph.c\n     @@ -147,3 +153,16 @@\n        * sha1-name.c:                                              20\n        * list-objects-filter.c:                                      21\n        * builtin/fsck.c:           0--3\n     +\n     + diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\n     + --- a/t/t5510-fetch.sh\n     + +++ b/t/t5510-fetch.sh\n     +@@\n     + \t)\n     + '\n     + \n     +-test_expect_failure 'fetch.writeCommitGraph with submodules' '\n     ++test_expect_success 'fetch.writeCommitGraph with submodules' '\n     + \tpwd=\"$(pwd)\" &&\n     + \tgit clone dups super &&\n     + \t(\n\n-- \ngitgitgadget\n"},{"id":"384701","messageId":"ca59b118f1fa4176214f55b8993145b5e1db39a0.1571835695.git.gitgitgadget@gmail.com","threadId":"52093","inReplyTo":"pull.415.v2.git.1571835695.gitgitgadget@gmail.com","subject":"[PATCH v2 2/2] commit-graph: fix writing first commit-graph during fetch","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-23T13:01:35Z","receivedAt":"2019-10-23T13:01: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 previous commit includes a failing test for an issue around\nfetch.writeCommitGraph and fetching in a repo with a submodule. Here, we\nfix that bug and set the test to \"test_expect_success\".\n\nThe prolem arises with this set of commands when the remote repo at\n<url> has a submodule. Note that --recurse-submodules is not needed to\ndemonstrate the bug.\n\n\t$ git clone <url> test\n\t$ cd test\n\t$ git -c fetch.writeCommitGraph=true fetch origin\n\tComputing commit graph generation numbers: 100% (12/12), done.\n\tBUG: commit-graph.c:886: missing parent <hash1> for commit <hash2>\n\tAborted (core dumped)\n\nAs an initial fix, I converted the code in builtin/fetch.c that calls\nwrite_commit_graph_reachable() to instead launch a \"git commit-graph\nwrite --reachable --split\" process. That code worked, but is not how we\nwant the feature to work long-term.\n\nThat test did demonstrate that the issue must be something to do with\ninternal state of the 'git fetch' process.\n\nThe write_commit_graph() method in commit-graph.c ensures the commits we\nplan to write are \"closed under reachability\" using close_reachable().\nThis method walks from the input commits, and uses the UNINTERESTING\nflag to mark which commits have already been visited. This allows the\nwalk to take O(N) time, where N is the number of commits, instead of\nO(P) time, where P is the number of paths. (The number of paths can be\nexponential in the number of commits.)\n\nHowever, the UNINTERESTING flag is used in lots of places in the\ncodebase. This flag usually means some barrier to stop a commit walk,\nsuch as in revision-walking to compare histories. It is not often\ncleared after the walk completes because the starting points of those\nwalks do not have the UNINTERESTING flag, and clear_commit_marks() would\nstop immediately.\n\nThis is happening during a 'git fetch' call with a remote. The fetch\nnegotiation is comparing the remote refs with the local refs and marking\nsome commits as UNINTERESTING.\n\nYou may ask: did this feature ever work at all? Yes, it did, as long as\nyou had a commit-graph covering all of your local refs. My testing was\nunfortunately limited to this scenario. The UNINTERESTING commits are\nalways part of the \"old\" commit-graph, and when we add new commits to a\ntop layer of the commit-graph chain those are not needed. If we happen\nto merge layers of the chain, then the commits are added as a list, not\nusing a commit walk. Further, the test added for this config option in\nt5510-fetch.sh uses local filesystem clones, which somehow avoids this\nlogic.\n\nI tested running clear_commit_marks_many() to clear the UNINTERESTING\nflag inside close_reachable(), but the tips did not have the flag, so\nthat did nothing.\n\nIt turns out that the calculate_changed_submodule_paths() method is at\nfault. Thanks, Peff, for pointing out this detail! More specifically,\nfor each submodule, the collect_changed_submodules() runs a revision\nwalk to essentially do file-history on the list of submodules. That\nrevision walk marks commits UNININTERESTING if they are simiplified away\nby not changing the submodule.\n\nInstead, I finally arrived on the conclusion that I should use a flag\nthat is not used in any other part of the code. In commit-reach.c, a\nnumber of flags were defined for commit walk algorithms. The REACHABLE\nflag seemed like it made the most sense, and it seems it was not\nactually used in the file. The REACHABLE flag was used in early versions\nof commit-reach.c, but was removed by 4fbcca4 (commit-reach: make\ncan_all_from_reach... linear, 2018-07-20).\n\nAdd the REACHABLE flag to commit-graph.c and use it instead of\nUNINTERESTING in close_reachable(). This fixes the bug in manual\ntesting.\n\nReported-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nHelped-by: Jeff King <peff@peff.net>\nHelped-by: Szeder Gábor <szeder.dev@gmail.com>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n commit-graph.c   | 11 +++++++----\n commit-reach.c   |  1 -\n object.h         |  3 ++-\n t/t5510-fetch.sh |  2 +-\n 4 files changed, 10 insertions(+), 7 deletions(-)\n\ndiff --git a/commit-graph.c b/commit-graph.c\nindex fc4a43b8d6..0aea7b2ae5 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -41,6 +41,9 @@\n #define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * GRAPH_CHUNKLOOKUP_WIDTH \\\n \t\t\t+ GRAPH_FANOUT_SIZE + the_hash_algo->rawsz)\n \n+/* Remember to update object flag allocation in object.h */\n+#define REACHABLE       (1u<<15)\n+\n char *get_commit_graph_filename(const char *obj_dir)\n {\n \tchar *filename = xstrfmt(\"%s/info/commit-graph\", obj_dir);\n@@ -1030,11 +1033,11 @@ static void add_missing_parents(struct write_commit_graph_context *ctx, struct c\n {\n \tstruct commit_list *parent;\n \tfor (parent = commit->parents; parent; parent = parent->next) {\n-\t\tif (!(parent->item->object.flags & UNINTERESTING)) {\n+\t\tif (!(parent->item->object.flags & REACHABLE)) {\n \t\t\tALLOC_GROW(ctx->oids.list, ctx->oids.nr + 1, ctx->oids.alloc);\n \t\t\toidcpy(&ctx->oids.list[ctx->oids.nr], &(parent->item->object.oid));\n \t\t\tctx->oids.nr++;\n-\t\t\tparent->item->object.flags |= UNINTERESTING;\n+\t\t\tparent->item->object.flags |= REACHABLE;\n \t\t}\n \t}\n }\n@@ -1052,7 +1055,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)\n \t\tdisplay_progress(ctx->progress, i + 1);\n \t\tcommit = lookup_commit(ctx->r, &ctx->oids.list[i]);\n \t\tif (commit)\n-\t\t\tcommit->object.flags |= UNINTERESTING;\n+\t\t\tcommit->object.flags |= REACHABLE;\n \t}\n \tstop_progress(&ctx->progress);\n \n@@ -1089,7 +1092,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)\n \t\tcommit = lookup_commit(ctx->r, &ctx->oids.list[i]);\n \n \t\tif (commit)\n-\t\t\tcommit->object.flags &= ~UNINTERESTING;\n+\t\t\tcommit->object.flags &= ~REACHABLE;\n \t}\n \tstop_progress(&ctx->progress);\n }\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 3ea174788a..4ca7e706a1 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -10,7 +10,6 @@\n #include \"commit-reach.h\"\n \n /* Remember to update object flag allocation in object.h */\n-#define REACHABLE       (1u<<15)\n #define PARENT1\t\t(1u<<16)\n #define PARENT2\t\t(1u<<17)\n #define STALE\t\t(1u<<18)\ndiff --git a/object.h b/object.h\nindex 0120892bbd..25f5ab3d54 100644\n--- a/object.h\n+++ b/object.h\n@@ -68,7 +68,8 @@ struct object_array {\n  * bisect.c:                                        16\n  * bundle.c:                                        16\n  * http-push.c:                                     16-----19\n- * commit-reach.c:                                15-------19\n+ * commit-graph.c:                                15\n+ * commit-reach.c:                                  16-----19\n  * sha1-name.c:                                              20\n  * list-objects-filter.c:                                      21\n  * builtin/fsck.c:           0--3\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex e8ae3af0b6..7bfbcc2036 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -583,7 +583,7 @@ test_expect_success 'fetch.writeCommitGraph' '\n \t)\n '\n \n-test_expect_failure 'fetch.writeCommitGraph with submodules' '\n+test_expect_success 'fetch.writeCommitGraph with submodules' '\n \tpwd=\"$(pwd)\" &&\n \tgit clone dups super &&\n \t(\n-- \ngitgitgadget\n"},{"id":"384705","messageId":"20191023141813.GA26017@szeder.dev","threadId":"52093","inReplyTo":"6ac0a05746df8ac3b1dd788f525b2620bc4d9a08.1571835695.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 1/2] t5510-fetch.sh: demonstrate fetch.writeCommitGraph bug","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-10-23T14:18:13Z","receivedAt":"2019-10-23T14:18:22Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Wed, Oct 23, 2019 at 01:01:34PM +0000, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <dstolee@microsoft.com>\n> \n> While dogfooding, Johannes found a bug in the fetch.writeCommitGraph\n> config behavior. His example initially happened during a clone with\n> --recurse-submodules, we found that this happens with the first fetch\n> after cloning a repository that contains a submodule:\n> \n> \t$ git clone <url> test\n> \t$ cd test\n> \t$ git -c fetch.writeCommitGraph=true fetch origin\n> \tComputing commit graph generation numbers: 100% (12/12), done.\n> \tBUG: commit-graph.c:886: missing parent <hash1> for commit <hash2>\n> \tAborted (core dumped)\n> \n> In the repo I had cloned, there were really 60 commits to scan, but\n> only 12 were in the list to write when calling\n> compute_generation_numbers(). A commit in the list expects to see a\n> parent, but that parent is not in the list.\n> \n> A follow-up will fix the bug, but first we create a test that\n> demonstrates the problem.\n> \n> I used \"test_expect_failure\" for the entire test instead of\n> \"test_must_fail\" only on the command that I expect to fail. This is\n> because the BUG() returns an exit code so test_must_fail complains.\n\nI don't think this paragraph is necessary; using 'test_expect_failure'\nis the way to demonstrate a known breakage.\n\n'test_must_fail' should only be used when the failure is the desired\nbehavior of a git command.  (I used the word \"desired\" here, because\nyou just used the word \"expect\" above in the sense that \"I expect it\nto fail, because I know it's buggy, and I want to demonstrate that\nbug\")\n\n> Helped-by: Jeff King <peff@peff.net>\n> Helped-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> Helped-by: Szeder Gábor <szeder.dev@gmail.com>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  t/t5510-fetch.sh | 17 +++++++++++++++++\n>  1 file changed, 17 insertions(+)\n> \n> diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\n> index ecabbe1616..e8ae3af0b6 100755\n> --- a/t/t5510-fetch.sh\n> +++ b/t/t5510-fetch.sh\n> @@ -583,6 +583,23 @@ test_expect_success 'fetch.writeCommitGraph' '\n>  \t)\n>  '\n>  \n> +test_expect_failure 'fetch.writeCommitGraph with submodules' '\n> +\tpwd=\"$(pwd)\" &&\n> +\tgit clone dups super &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit submodule add \"file://$pwd/three\" &&\n\nNits: couldn't the URL simply be file://$TRASH_DIRECTORY/three ?\n\n> +\t\tgit commit -m \"add submodule\"\n> +\t) &&\n> +\tgit clone \"super\" writeError &&\n\nThere is a write error now, because we have a bug, but after the next\npatch the bug will be fixed and we won't have a write error anymore.\n\n> +\t(\n> +\t\tcd writeError &&\n> +\t\ttest_path_is_missing .git/objects/info/commit-graphs/commit-graph-chain &&\n> +\t\tgit -c fetch.writeCommitGraph=true fetch origin &&\n> +\t\ttest_path_is_file .git/objects/info/commit-graphs/commit-graph-chain\n> +\t)\n> +'\n> +\n>  # configured prune tests\n>  \n>  set_config_tristate () {\n> -- \n> gitgitgadget\n> \n"},{"id":"384707","messageId":"20191023150402.GB26017@szeder.dev","threadId":"52093","inReplyTo":"ca59b118f1fa4176214f55b8993145b5e1db39a0.1571835695.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/2] commit-graph: fix writing first commit-graph during fetch","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-10-23T15:04:02Z","receivedAt":"2019-10-23T15:04:10Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Wed, Oct 23, 2019 at 01:01:35PM +0000, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <dstolee@microsoft.com>\n> \n> The previous commit includes a failing test for an issue around\n> fetch.writeCommitGraph and fetching in a repo with a submodule. Here, we\n> fix that bug and set the test to \"test_expect_success\".\n> \n> The prolem arises with this set of commands when the remote repo at\n\ns/prolem/problem/\n\n> <url> has a submodule. Note that --recurse-submodules is not needed to\n> demonstrate the bug.\n> \n> \t$ git clone <url> test\n> \t$ cd test\n> \t$ git -c fetch.writeCommitGraph=true fetch origin\n> \tComputing commit graph generation numbers: 100% (12/12), done.\n> \tBUG: commit-graph.c:886: missing parent <hash1> for commit <hash2>\n> \tAborted (core dumped)\n> \n> As an initial fix, I converted the code in builtin/fetch.c that calls\n> write_commit_graph_reachable() to instead launch a \"git commit-graph\n> write --reachable --split\" process. That code worked, but is not how we\n> want the feature to work long-term.\n> \n> That test did demonstrate that the issue must be something to do with\n> internal state of the 'git fetch' process.\n> \n> The write_commit_graph() method in commit-graph.c ensures the commits we\n> plan to write are \"closed under reachability\" using close_reachable().\n> This method walks from the input commits, and uses the UNINTERESTING\n> flag to mark which commits have already been visited. This allows the\n> walk to take O(N) time, where N is the number of commits, instead of\n> O(P) time, where P is the number of paths. (The number of paths can be\n> exponential in the number of commits.)\n> \n> However, the UNINTERESTING flag is used in lots of places in the\n> codebase. This flag usually means some barrier to stop a commit walk,\n> such as in revision-walking to compare histories. It is not often\n> cleared after the walk completes because the starting points of those\n> walks do not have the UNINTERESTING flag, and clear_commit_marks() would\n> stop immediately.\n> \n> This is happening during a 'git fetch' call with a remote. The fetch\n> negotiation is comparing the remote refs with the local refs and marking\n> some commits as UNINTERESTING.\n> \n> You may ask: did this feature ever work at all? Yes, it did, as long as\n> you had a commit-graph covering all of your local refs. My testing was\n> unfortunately limited to this scenario. The UNINTERESTING commits are\n> always part of the \"old\" commit-graph, and when we add new commits to a\n> top layer of the commit-graph chain those are not needed. If we happen\n> to merge layers of the chain, then the commits are added as a list, not\n> using a commit walk. Further, the test added for this config option in\n> t5510-fetch.sh uses local filesystem clones, which somehow avoids this\n> logic.\n\nDoes this last sentence still holds, given that a submodule plays a\ncrucial role in triggering this bug?  I think it either doesn't, or\nI still don't completely understand the situation.\n\n> I tested running clear_commit_marks_many() to clear the UNINTERESTING\n> flag inside close_reachable(), but the tips did not have the flag, so\n> that did nothing.\n> \n> It turns out that the calculate_changed_submodule_paths() method is at\n> fault. Thanks, Peff, for pointing out this detail! More specifically,\n> for each submodule, the collect_changed_submodules() runs a revision\n> walk to essentially do file-history on the list of submodules. That\n> revision walk marks commits UNININTERESTING if they are simiplified away\n\ns/simiplified/simplified/\n\n> by not changing the submodule.\n> \n> Instead, I finally arrived on the conclusion that I should use a flag\n> that is not used in any other part of the code. In commit-reach.c, a\n> number of flags were defined for commit walk algorithms. The REACHABLE\n> flag seemed like it made the most sense, and it seems it was not\n> actually used in the file. The REACHABLE flag was used in early versions\n> of commit-reach.c, but was removed by 4fbcca4 (commit-reach: make\n> can_all_from_reach... linear, 2018-07-20).\n> \n> Add the REACHABLE flag to commit-graph.c and use it instead of\n> UNINTERESTING in close_reachable(). This fixes the bug in manual\n> testing.\n\nI'm inclined to agree that using a flag that is not used anywhere else\nis the safest thing to do, and at -rcX time safest is good.  I'm not\nsure whether it's the right thing to do in the long term, though.\n\nFurthermore, calling this flag REACHABLE is misleading, because the\ncode actually means SEEN.\nConsider the following sequence of commands:\n\n  # Create a pack with two commits\n  $ git commit --allow-empty -m one &&\n  $ git commit --allow-empty -m two &&\n  $ git repack -ad &&\n  # Make one of those commits unreachable\n  $ git reset --hard HEAD^ &&\n  # Not even from reflogs!\n  $ git reflog expire --expire-unreachable=now --all\n  # Now write a commit-graph from that pack file\n  $ git commit-graph write\n  Computing commit graph generation numbers: 100% (2/2), done.\n\nIt added two commits to the commit-graph, although one of them is\nclearly not reachable anymore, so marking it as REACHABLE while\nenumerating all commits feels wrong.\n\n> Reported-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> Helped-by: Jeff King <peff@peff.net>\n> Helped-by: Szeder Gábor <szeder.dev@gmail.com>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  commit-graph.c   | 11 +++++++----\n>  commit-reach.c   |  1 -\n>  object.h         |  3 ++-\n>  t/t5510-fetch.sh |  2 +-\n>  4 files changed, 10 insertions(+), 7 deletions(-)\n> \n> diff --git a/commit-graph.c b/commit-graph.c\n> index fc4a43b8d6..0aea7b2ae5 100644\n> --- a/commit-graph.c\n> +++ b/commit-graph.c\n> @@ -41,6 +41,9 @@\n>  #define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * GRAPH_CHUNKLOOKUP_WIDTH \\\n>  \t\t\t+ GRAPH_FANOUT_SIZE + the_hash_algo->rawsz)\n>  \n> +/* Remember to update object flag allocation in object.h */\n> +#define REACHABLE       (1u<<15)\n> +\n>  char *get_commit_graph_filename(const char *obj_dir)\n>  {\n>  \tchar *filename = xstrfmt(\"%s/info/commit-graph\", obj_dir);\n> @@ -1030,11 +1033,11 @@ static void add_missing_parents(struct write_commit_graph_context *ctx, struct c\n>  {\n>  \tstruct commit_list *parent;\n>  \tfor (parent = commit->parents; parent; parent = parent->next) {\n> -\t\tif (!(parent->item->object.flags & UNINTERESTING)) {\n> +\t\tif (!(parent->item->object.flags & REACHABLE)) {\n>  \t\t\tALLOC_GROW(ctx->oids.list, ctx->oids.nr + 1, ctx->oids.alloc);\n>  \t\t\toidcpy(&ctx->oids.list[ctx->oids.nr], &(parent->item->object.oid));\n>  \t\t\tctx->oids.nr++;\n> -\t\t\tparent->item->object.flags |= UNINTERESTING;\n> +\t\t\tparent->item->object.flags |= REACHABLE;\n>  \t\t}\n>  \t}\n>  }\n> @@ -1052,7 +1055,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)\n>  \t\tdisplay_progress(ctx->progress, i + 1);\n>  \t\tcommit = lookup_commit(ctx->r, &ctx->oids.list[i]);\n>  \t\tif (commit)\n> -\t\t\tcommit->object.flags |= UNINTERESTING;\n> +\t\t\tcommit->object.flags |= REACHABLE;\n>  \t}\n>  \tstop_progress(&ctx->progress);\n>  \n> @@ -1089,7 +1092,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)\n>  \t\tcommit = lookup_commit(ctx->r, &ctx->oids.list[i]);\n>  \n>  \t\tif (commit)\n> -\t\t\tcommit->object.flags &= ~UNINTERESTING;\n> +\t\t\tcommit->object.flags &= ~REACHABLE;\n>  \t}\n>  \tstop_progress(&ctx->progress);\n>  }\n> diff --git a/commit-reach.c b/commit-reach.c\n> index 3ea174788a..4ca7e706a1 100644\n> --- a/commit-reach.c\n> +++ b/commit-reach.c\n> @@ -10,7 +10,6 @@\n>  #include \"commit-reach.h\"\n>  \n>  /* Remember to update object flag allocation in object.h */\n> -#define REACHABLE       (1u<<15)\n>  #define PARENT1\t\t(1u<<16)\n>  #define PARENT2\t\t(1u<<17)\n>  #define STALE\t\t(1u<<18)\n> diff --git a/object.h b/object.h\n> index 0120892bbd..25f5ab3d54 100644\n> --- a/object.h\n> +++ b/object.h\n> @@ -68,7 +68,8 @@ struct object_array {\n>   * bisect.c:                                        16\n>   * bundle.c:                                        16\n>   * http-push.c:                                     16-----19\n> - * commit-reach.c:                                15-------19\n> + * commit-graph.c:                                15\n> + * commit-reach.c:                                  16-----19\n>   * sha1-name.c:                                              20\n>   * list-objects-filter.c:                                      21\n>   * builtin/fsck.c:           0--3\n> diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\n> index e8ae3af0b6..7bfbcc2036 100755\n> --- a/t/t5510-fetch.sh\n> +++ b/t/t5510-fetch.sh\n> @@ -583,7 +583,7 @@ test_expect_success 'fetch.writeCommitGraph' '\n>  \t)\n>  '\n>  \n> -test_expect_failure 'fetch.writeCommitGraph with submodules' '\n> +test_expect_success 'fetch.writeCommitGraph with submodules' '\n>  \tpwd=\"$(pwd)\" &&\n>  \tgit clone dups super &&\n>  \t(\n> -- \n> gitgitgadget\n"},{"id":"384722","messageId":"a9ba6007-aeb9-96b4-9436-8e956798be59@gmail.com","threadId":"52093","inReplyTo":"20191023141813.GA26017@szeder.dev","subject":"Re: [PATCH v2 1/2] t5510-fetch.sh: demonstrate fetch.writeCommitGraph bug","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-10-23T20:46:42Z","receivedAt":"2019-10-23T20:46:46Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 10/23/2019 10:18 AM, SZEDER Gábor wrote:\n> On Wed, Oct 23, 2019 at 01:01:34PM +0000, Derrick Stolee via GitGitGadget wrote:\n>> From: Derrick Stolee <dstolee@microsoft.com>\n>>\n>> While dogfooding, Johannes found a bug in the fetch.writeCommitGraph\n>> config behavior. His example initially happened during a clone with\n>> --recurse-submodules, we found that this happens with the first fetch\n>> after cloning a repository that contains a submodule:\n>>\n>> \t$ git clone <url> test\n>> \t$ cd test\n>> \t$ git -c fetch.writeCommitGraph=true fetch origin\n>> \tComputing commit graph generation numbers: 100% (12/12), done.\n>> \tBUG: commit-graph.c:886: missing parent <hash1> for commit <hash2>\n>> \tAborted (core dumped)\n>>\n>> In the repo I had cloned, there were really 60 commits to scan, but\n>> only 12 were in the list to write when calling\n>> compute_generation_numbers(). A commit in the list expects to see a\n>> parent, but that parent is not in the list.\n>>\n>> A follow-up will fix the bug, but first we create a test that\n>> demonstrates the problem.\n>>\n>> I used \"test_expect_failure\" for the entire test instead of\n>> \"test_must_fail\" only on the command that I expect to fail. This is\n>> because the BUG() returns an exit code so test_must_fail complains.\n> \n> I don't think this paragraph is necessary; using 'test_expect_failure'\n> is the way to demonstrate a known breakage.\n> \n> 'test_must_fail' should only be used when the failure is the desired\n> behavior of a git command.  (I used the word \"desired\" here, because\n> you just used the word \"expect\" above in the sense that \"I expect it\n> to fail, because I know it's buggy, and I want to demonstrate that\n> bug\")\n\nI guess that I prefer pointing out which line of the test fails, and\nmaking that part of the test (that must otherwise pass). However, you\nare right that test_expect_failure does a good job of communicating that\nthe test script shows an existing bug. Those are not always cleaned up\nimmediately, but at least we can find them easily.\n\n>> Helped-by: Jeff King <peff@peff.net>\n>> Helped-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n>> Helped-by: Szeder Gábor <szeder.dev@gmail.com>\n>> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n>> ---\n>>  t/t5510-fetch.sh | 17 +++++++++++++++++\n>>  1 file changed, 17 insertions(+)\n>>\n>> diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\n>> index ecabbe1616..e8ae3af0b6 100755\n>> --- a/t/t5510-fetch.sh\n>> +++ b/t/t5510-fetch.sh\n>> @@ -583,6 +583,23 @@ test_expect_success 'fetch.writeCommitGraph' '\n>>  \t)\n>>  '\n>>  \n>> +test_expect_failure 'fetch.writeCommitGraph with submodules' '\n>> +\tpwd=\"$(pwd)\" &&\n>> +\tgit clone dups super &&\n>> +\t(\n>> +\t\tcd super &&\n>> +\t\tgit submodule add \"file://$pwd/three\" &&\n> \n> Nits: couldn't the URL simply be file://$TRASH_DIRECTORY/three ?\n\nTrue, that would be better. Thanks!\n\n \n>> +\t\tgit commit -m \"add submodule\"\n>> +\t) &&\n>> +\tgit clone \"super\" writeError &&\n> \n> There is a write error now, because we have a bug, but after the next\n> patch the bug will be fixed and we won't have a write error anymore.\n\nGood point.\n\nThanks!\n-Stolee\n"},{"id":"384757","messageId":"e2eb7b76-a29b-04c4-e698-f3a67ae56ecc@gmail.com","threadId":"52093","inReplyTo":"20191023150402.GB26017@szeder.dev","subject":"Re: [PATCH v2 2/2] commit-graph: fix writing first commit-graph during fetch","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-10-24T10:39:51Z","receivedAt":"2019-10-24T10:39:57Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 10/23/2019 11:04 AM, SZEDER Gábor wrote:\n> On Wed, Oct 23, 2019 at 01:01:35PM +0000, Derrick Stolee via GitGitGadget wrote:\n>>\n>> You may ask: did this feature ever work at all? Yes, it did, as long as\n>> you had a commit-graph covering all of your local refs. My testing was\n>> unfortunately limited to this scenario. The UNINTERESTING commits are\n>> always part of the \"old\" commit-graph, and when we add new commits to a\n>> top layer of the commit-graph chain those are not needed. If we happen\n>> to merge layers of the chain, then the commits are added as a list, not\n>> using a commit walk. Further, the test added for this config option in\n>> t5510-fetch.sh uses local filesystem clones, which somehow avoids this\n>> logic.\n> \n> Does this last sentence still holds, given that a submodule plays a\n> crucial role in triggering this bug?  I think it either doesn't, or\n> I still don't completely understand the situation.\n\nThis does not apply anymore. I forgot to delete it.\n \n>> I tested running clear_commit_marks_many() to clear the UNINTERESTING\n>> flag inside close_reachable(), but the tips did not have the flag, so\n>> that did nothing.\n>>\n>> It turns out that the calculate_changed_submodule_paths() method is at\n>> fault. Thanks, Peff, for pointing out this detail! More specifically,\n>> for each submodule, the collect_changed_submodules() runs a revision\n>> walk to essentially do file-history on the list of submodules. That\n>> revision walk marks commits UNININTERESTING if they are simiplified away\n> \n> s/simiplified/simplified/\n> \n>> by not changing the submodule.\n>>\n>> Instead, I finally arrived on the conclusion that I should use a flag\n>> that is not used in any other part of the code. In commit-reach.c, a\n>> number of flags were defined for commit walk algorithms. The REACHABLE\n>> flag seemed like it made the most sense, and it seems it was not\n>> actually used in the file. The REACHABLE flag was used in early versions\n>> of commit-reach.c, but was removed by 4fbcca4 (commit-reach: make\n>> can_all_from_reach... linear, 2018-07-20).\n>>\n>> Add the REACHABLE flag to commit-graph.c and use it instead of\n>> UNINTERESTING in close_reachable(). This fixes the bug in manual\n>> testing.\n> \n> I'm inclined to agree that using a flag that is not used anywhere else\n> is the safest thing to do, and at -rcX time safest is good.  I'm not\n> sure whether it's the right thing to do in the long term, though.\n> \n> Furthermore, calling this flag REACHABLE is misleading, because the\n> code actually means SEEN.\n> Consider the following sequence of commands:\n> \n>   # Create a pack with two commits\n>   $ git commit --allow-empty -m one &&\n>   $ git commit --allow-empty -m two &&\n>   $ git repack -ad &&\n>   # Make one of those commits unreachable\n>   $ git reset --hard HEAD^ &&\n>   # Not even from reflogs!\n>   $ git reflog expire --expire-unreachable=now --all\n>   # Now write a commit-graph from that pack file\n>   $ git commit-graph write\n>   Computing commit graph generation numbers: 100% (2/2), done.\n> \n> It added two commits to the commit-graph, although one of them is\n> clearly not reachable anymore, so marking it as REACHABLE while\n> enumerating all commits feels wrong.\n\nSince you are using \"git commit-graph write\", the command is scanning\nall pack-files for commits to include. Even in this case, the\nclose_reachable() method needs to walk to see if any commits are missing.\n(It could be that the root commit is loose for some strange reason.)\n\nIn this case, we are marking REACHABLE the commits that can be reached\nfrom our \"starting\" commits. In your example we start with every commit.\n\nIf you had used `git commit-graph write --stdin-packs` and provided a\nsmall pack name over stdin, the concept would be similar and even more\npronounced: the pack (perhaps downloaded via 'fetch') is not likely to\ncontain every commit, so we need to walk all reachable commits from\nthose included.\n\nI'll have a v3 today with the requested fixes.\n\nThanks,\n-Stolee\n"},{"id":"384764","messageId":"ce53b5a7bfeffa8e676cdb7ba17f0f5176ae3567.1571919488.git.gitgitgadget@gmail.com","threadId":"52093","inReplyTo":"pull.415.v3.git.1571919488.gitgitgadget@gmail.com","subject":"[PATCH v3 1/2] t5510-fetch.sh: demonstrate fetch.writeCommitGraph bug","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-24T12:18:07Z","receivedAt":"2019-10-24T12:18: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\nWhile dogfooding, Johannes found a bug in the fetch.writeCommitGraph\nconfig behavior. His example initially happened during a clone with\n--recurse-submodules, we found that this happens with the first fetch\nafter cloning a repository that contains a submodule:\n\n\t$ git clone <url> test\n\t$ cd test\n\t$ git -c fetch.writeCommitGraph=true fetch origin\n\tComputing commit graph generation numbers: 100% (12/12), done.\n\tBUG: commit-graph.c:886: missing parent <hash1> for commit <hash2>\n\tAborted (core dumped)\n\nIn the repo I had cloned, there were really 60 commits to scan, but\nonly 12 were in the list to write when calling\ncompute_generation_numbers(). A commit in the list expects to see a\nparent, but that parent is not in the list.\n\nA follow-up will fix the bug, but first we create a test that\ndemonstrates the problem.\n\nHelped-by: Jeff King <peff@peff.net>\nHelped-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nHelped-by: Szeder Gábor <szeder.dev@gmail.com>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/t5510-fetch.sh | 16 ++++++++++++++++\n 1 file changed, 16 insertions(+)\n\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex ecabbe1616..cc76e1ce50 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -583,6 +583,22 @@ test_expect_success 'fetch.writeCommitGraph' '\n \t)\n '\n \n+test_expect_failure 'fetch.writeCommitGraph with submodules' '\n+\tgit clone dups super &&\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule add \"file://$TRASH_DIRECTORY/three\" &&\n+\t\tgit commit -m \"add submodule\"\n+\t) &&\n+\tgit clone \"super\" super-clone &&\n+\t(\n+\t\tcd super-clone &&\n+\t\ttest_path_is_missing .git/objects/info/commit-graphs/commit-graph-chain &&\n+\t\tgit -c fetch.writeCommitGraph=true fetch origin &&\n+\t\ttest_path_is_file .git/objects/info/commit-graphs/commit-graph-chain\n+\t)\n+'\n+\n # configured prune tests\n \n set_config_tristate () {\n-- \ngitgitgadget\n\n"},{"id":"384765","messageId":"pull.415.v3.git.1571919488.gitgitgadget@gmail.com","threadId":"52093","inReplyTo":"pull.415.v2.git.1571835695.gitgitgadget@gmail.com","subject":"[PATCH v3 0/2] [v2.24.0-rc0 BUG] fetch.writeCommitGraph fails on first fetch","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-24T12:18:06Z","receivedAt":"2019-10-24T12:18:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"UPDATE for V2: We now know the full repro, and a test is added. Thanks\nSzeder and Peff for your insights!\n\nUPDATE in V3: Cleaned up the commit messages and some test details.\n\nWhile dogfooding, Johannes found a bug in the fetch.writeCommitGraph config\nbehavior. While his example initially happened during a clone with\n--recurse-submodules, (UPDATE) and the submodule is important, but\n--recurse-submodules is not:\n\n$ git clone <url> test\n$ cd test\n$ git -c fetch.writeCommitGraph=true fetch origin\nComputing commit graph generation numbers: 100% (12/12), done.\nBUG: commit-graph.c:886: missing parent <hash1> for commit <hash2>\nAborted (core dumped)\n\nIn the repo I had cloned, there were really 60 commits to scan, but only 12\nwere in the list to write when calling compute_generation_numbers(). A\ncommit in the list expects to see a parent, but that parent is not in the\nlist. The simple example I used for my testing was \nhttps://github.com/derrickstolee/numbers. Thie repo HAS A SUBMODULE, I just\nforgot. Sorry for derailing the investigation somewhat.\n\nThe details above are the start of the commit message for [PATCH 1/2],\nincluding a test that fails when fetching after cloning a repo with a\nsubmodule.\n\nIn [PATCH 2/2], I actually have the fix. I tried to include as much detail\nas I could for how I investigated the problem and why I think this is the\nright solution. I added details that have come from the on-list discussion,\nincluding what the submodule code is doing and why REACHABLE is no longer\nused in commit-reach.c.\n\nThanks, -Stolee\n\nDerrick Stolee (2):\n  t5510-fetch.sh: demonstrate fetch.writeCommitGraph bug\n  commit-graph: fix writing first commit-graph during fetch\n\n commit-graph.c   | 11 +++++++----\n commit-reach.c   |  1 -\n object.h         |  3 ++-\n t/t5510-fetch.sh | 16 ++++++++++++++++\n 4 files changed, 25 insertions(+), 6 deletions(-)\n\n\nbase-commit: d966095db01190a2196e31195ea6fa0c722aa732\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-415%2Fderrickstolee%2Ffetch-first-write-fail-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-415/derrickstolee/fetch-first-write-fail-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/415\n\nRange-diff vs v2:\n\n 1:  6ac0a05746 ! 1:  ce53b5a7bf t5510-fetch.sh: demonstrate fetch.writeCommitGraph bug\n     @@ -22,10 +22,6 @@\n          A follow-up will fix the bug, but first we create a test that\n          demonstrates the problem.\n      \n     -    I used \"test_expect_failure\" for the entire test instead of\n     -    \"test_must_fail\" only on the command that I expect to fail. This is\n     -    because the BUG() returns an exit code so test_must_fail complains.\n     -\n          Helped-by: Jeff King <peff@peff.net>\n          Helped-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n          Helped-by: Szeder Gábor <szeder.dev@gmail.com>\n     @@ -39,16 +35,15 @@\n       '\n       \n      +test_expect_failure 'fetch.writeCommitGraph with submodules' '\n     -+\tpwd=\"$(pwd)\" &&\n      +\tgit clone dups super &&\n      +\t(\n      +\t\tcd super &&\n     -+\t\tgit submodule add \"file://$pwd/three\" &&\n     ++\t\tgit submodule add \"file://$TRASH_DIRECTORY/three\" &&\n      +\t\tgit commit -m \"add submodule\"\n      +\t) &&\n     -+\tgit clone \"super\" writeError &&\n     ++\tgit clone \"super\" super-clone &&\n      +\t(\n     -+\t\tcd writeError &&\n     ++\t\tcd super-clone &&\n      +\t\ttest_path_is_missing .git/objects/info/commit-graphs/commit-graph-chain &&\n      +\t\tgit -c fetch.writeCommitGraph=true fetch origin &&\n      +\t\ttest_path_is_file .git/objects/info/commit-graphs/commit-graph-chain\n 2:  ca59b118f1 ! 2:  edacfff490 commit-graph: fix writing first commit-graph during fetch\n     @@ -6,7 +6,7 @@\n          fetch.writeCommitGraph and fetching in a repo with a submodule. Here, we\n          fix that bug and set the test to \"test_expect_success\".\n      \n     -    The prolem arises with this set of commands when the remote repo at\n     +    The problem arises with this set of commands when the remote repo at\n          <url> has a submodule. Note that --recurse-submodules is not needed to\n          demonstrate the bug.\n      \n     @@ -44,16 +44,6 @@\n          negotiation is comparing the remote refs with the local refs and marking\n          some commits as UNINTERESTING.\n      \n     -    You may ask: did this feature ever work at all? Yes, it did, as long as\n     -    you had a commit-graph covering all of your local refs. My testing was\n     -    unfortunately limited to this scenario. The UNINTERESTING commits are\n     -    always part of the \"old\" commit-graph, and when we add new commits to a\n     -    top layer of the commit-graph chain those are not needed. If we happen\n     -    to merge layers of the chain, then the commits are added as a list, not\n     -    using a commit walk. Further, the test added for this config option in\n     -    t5510-fetch.sh uses local filesystem clones, which somehow avoids this\n     -    logic.\n     -\n          I tested running clear_commit_marks_many() to clear the UNINTERESTING\n          flag inside close_reachable(), but the tips did not have the flag, so\n          that did nothing.\n     @@ -62,7 +52,7 @@\n          fault. Thanks, Peff, for pointing out this detail! More specifically,\n          for each submodule, the collect_changed_submodules() runs a revision\n          walk to essentially do file-history on the list of submodules. That\n     -    revision walk marks commits UNININTERESTING if they are simiplified away\n     +    revision walk marks commits UNININTERESTING if they are simplified away\n          by not changing the submodule.\n      \n          Instead, I finally arrived on the conclusion that I should use a flag\n     @@ -163,6 +153,6 @@\n       \n      -test_expect_failure 'fetch.writeCommitGraph with submodules' '\n      +test_expect_success 'fetch.writeCommitGraph with submodules' '\n     - \tpwd=\"$(pwd)\" &&\n       \tgit clone dups super &&\n       \t(\n     + \t\tcd super &&\n\n-- \ngitgitgadget\n"},{"id":"384766","messageId":"edacfff490052f9d3a6d90410adfa4c0e288b9a7.1571919488.git.gitgitgadget@gmail.com","threadId":"52093","inReplyTo":"pull.415.v3.git.1571919488.gitgitgadget@gmail.com","subject":"[PATCH v3 2/2] commit-graph: fix writing first commit-graph during fetch","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-24T12:18:08Z","receivedAt":"2019-10-24T12:18:16Z","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 previous commit includes a failing test for an issue around\nfetch.writeCommitGraph and fetching in a repo with a submodule. Here, we\nfix that bug and set the test to \"test_expect_success\".\n\nThe problem arises with this set of commands when the remote repo at\n<url> has a submodule. Note that --recurse-submodules is not needed to\ndemonstrate the bug.\n\n\t$ git clone <url> test\n\t$ cd test\n\t$ git -c fetch.writeCommitGraph=true fetch origin\n\tComputing commit graph generation numbers: 100% (12/12), done.\n\tBUG: commit-graph.c:886: missing parent <hash1> for commit <hash2>\n\tAborted (core dumped)\n\nAs an initial fix, I converted the code in builtin/fetch.c that calls\nwrite_commit_graph_reachable() to instead launch a \"git commit-graph\nwrite --reachable --split\" process. That code worked, but is not how we\nwant the feature to work long-term.\n\nThat test did demonstrate that the issue must be something to do with\ninternal state of the 'git fetch' process.\n\nThe write_commit_graph() method in commit-graph.c ensures the commits we\nplan to write are \"closed under reachability\" using close_reachable().\nThis method walks from the input commits, and uses the UNINTERESTING\nflag to mark which commits have already been visited. This allows the\nwalk to take O(N) time, where N is the number of commits, instead of\nO(P) time, where P is the number of paths. (The number of paths can be\nexponential in the number of commits.)\n\nHowever, the UNINTERESTING flag is used in lots of places in the\ncodebase. This flag usually means some barrier to stop a commit walk,\nsuch as in revision-walking to compare histories. It is not often\ncleared after the walk completes because the starting points of those\nwalks do not have the UNINTERESTING flag, and clear_commit_marks() would\nstop immediately.\n\nThis is happening during a 'git fetch' call with a remote. The fetch\nnegotiation is comparing the remote refs with the local refs and marking\nsome commits as UNINTERESTING.\n\nI tested running clear_commit_marks_many() to clear the UNINTERESTING\nflag inside close_reachable(), but the tips did not have the flag, so\nthat did nothing.\n\nIt turns out that the calculate_changed_submodule_paths() method is at\nfault. Thanks, Peff, for pointing out this detail! More specifically,\nfor each submodule, the collect_changed_submodules() runs a revision\nwalk to essentially do file-history on the list of submodules. That\nrevision walk marks commits UNININTERESTING if they are simplified away\nby not changing the submodule.\n\nInstead, I finally arrived on the conclusion that I should use a flag\nthat is not used in any other part of the code. In commit-reach.c, a\nnumber of flags were defined for commit walk algorithms. The REACHABLE\nflag seemed like it made the most sense, and it seems it was not\nactually used in the file. The REACHABLE flag was used in early versions\nof commit-reach.c, but was removed by 4fbcca4 (commit-reach: make\ncan_all_from_reach... linear, 2018-07-20).\n\nAdd the REACHABLE flag to commit-graph.c and use it instead of\nUNINTERESTING in close_reachable(). This fixes the bug in manual\ntesting.\n\nReported-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nHelped-by: Jeff King <peff@peff.net>\nHelped-by: Szeder Gábor <szeder.dev@gmail.com>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n commit-graph.c   | 11 +++++++----\n commit-reach.c   |  1 -\n object.h         |  3 ++-\n t/t5510-fetch.sh |  2 +-\n 4 files changed, 10 insertions(+), 7 deletions(-)\n\ndiff --git a/commit-graph.c b/commit-graph.c\nindex fc4a43b8d6..0aea7b2ae5 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -41,6 +41,9 @@\n #define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * GRAPH_CHUNKLOOKUP_WIDTH \\\n \t\t\t+ GRAPH_FANOUT_SIZE + the_hash_algo->rawsz)\n \n+/* Remember to update object flag allocation in object.h */\n+#define REACHABLE       (1u<<15)\n+\n char *get_commit_graph_filename(const char *obj_dir)\n {\n \tchar *filename = xstrfmt(\"%s/info/commit-graph\", obj_dir);\n@@ -1030,11 +1033,11 @@ static void add_missing_parents(struct write_commit_graph_context *ctx, struct c\n {\n \tstruct commit_list *parent;\n \tfor (parent = commit->parents; parent; parent = parent->next) {\n-\t\tif (!(parent->item->object.flags & UNINTERESTING)) {\n+\t\tif (!(parent->item->object.flags & REACHABLE)) {\n \t\t\tALLOC_GROW(ctx->oids.list, ctx->oids.nr + 1, ctx->oids.alloc);\n \t\t\toidcpy(&ctx->oids.list[ctx->oids.nr], &(parent->item->object.oid));\n \t\t\tctx->oids.nr++;\n-\t\t\tparent->item->object.flags |= UNINTERESTING;\n+\t\t\tparent->item->object.flags |= REACHABLE;\n \t\t}\n \t}\n }\n@@ -1052,7 +1055,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)\n \t\tdisplay_progress(ctx->progress, i + 1);\n \t\tcommit = lookup_commit(ctx->r, &ctx->oids.list[i]);\n \t\tif (commit)\n-\t\t\tcommit->object.flags |= UNINTERESTING;\n+\t\t\tcommit->object.flags |= REACHABLE;\n \t}\n \tstop_progress(&ctx->progress);\n \n@@ -1089,7 +1092,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)\n \t\tcommit = lookup_commit(ctx->r, &ctx->oids.list[i]);\n \n \t\tif (commit)\n-\t\t\tcommit->object.flags &= ~UNINTERESTING;\n+\t\t\tcommit->object.flags &= ~REACHABLE;\n \t}\n \tstop_progress(&ctx->progress);\n }\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 3ea174788a..4ca7e706a1 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -10,7 +10,6 @@\n #include \"commit-reach.h\"\n \n /* Remember to update object flag allocation in object.h */\n-#define REACHABLE       (1u<<15)\n #define PARENT1\t\t(1u<<16)\n #define PARENT2\t\t(1u<<17)\n #define STALE\t\t(1u<<18)\ndiff --git a/object.h b/object.h\nindex 0120892bbd..25f5ab3d54 100644\n--- a/object.h\n+++ b/object.h\n@@ -68,7 +68,8 @@ struct object_array {\n  * bisect.c:                                        16\n  * bundle.c:                                        16\n  * http-push.c:                                     16-----19\n- * commit-reach.c:                                15-------19\n+ * commit-graph.c:                                15\n+ * commit-reach.c:                                  16-----19\n  * sha1-name.c:                                              20\n  * list-objects-filter.c:                                      21\n  * builtin/fsck.c:           0--3\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex cc76e1ce50..e16b7b8db2 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -583,7 +583,7 @@ test_expect_success 'fetch.writeCommitGraph' '\n \t)\n '\n \n-test_expect_failure 'fetch.writeCommitGraph with submodules' '\n+test_expect_success 'fetch.writeCommitGraph with submodules' '\n \tgit clone dups super &&\n \t(\n \t\tcd super &&\n-- \ngitgitgadget\n"},{"id":"384767","messageId":"20191024121822.GL4348@szeder.dev","threadId":"52093","inReplyTo":"6ac0a05746df8ac3b1dd788f525b2620bc4d9a08.1571835695.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 1/2] t5510-fetch.sh: demonstrate fetch.writeCommitGraph bug","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-10-24T12:18:22Z","receivedAt":"2019-10-24T12:18:28Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Wed, Oct 23, 2019 at 01:01:34PM +0000, Derrick Stolee via GitGitGadget wrote:\n> diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\n> index ecabbe1616..e8ae3af0b6 100755\n> --- a/t/t5510-fetch.sh\n> +++ b/t/t5510-fetch.sh\n> @@ -583,6 +583,23 @@ test_expect_success 'fetch.writeCommitGraph' '\n>  \t)\n>  '\n>  \n> +test_expect_failure 'fetch.writeCommitGraph with submodules' '\n> +\tpwd=\"$(pwd)\" &&\n> +\tgit clone dups super &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit submodule add \"file://$pwd/three\" &&\n> +\t\tgit commit -m \"add submodule\"\n> +\t) &&\n> +\tgit clone \"super\" writeError &&\n> +\t(\n> +\t\tcd writeError &&\n> +\t\ttest_path_is_missing .git/objects/info/commit-graphs/commit-graph-chain &&\n> +\t\tgit -c fetch.writeCommitGraph=true fetch origin &&\n> +\t\ttest_path_is_file .git/objects/info/commit-graphs/commit-graph-chain\n\nWith the fix applied this test fails when run with\nGIT_TEST_COMMIT_GRAPH=1:\n\n  + cd writeError\n  + test_path_is_missing .git/objects/info/commit-graphs/commit-graph-chain\n  + test -e .git/objects/info/commit-graphs/commit-graph-chain\n  + git -c fetch.writeCommitGraph=true fetch origin\n  + test_path_is_file .git/objects/info/commit-graphs/commit-graph-chain\n  + test -f .git/objects/info/commit-graphs/commit-graph-chain\n  + echo File .git/objects/info/commit-graphs/commit-graph-chain doesn't exist.\n  File .git/objects/info/commit-graphs/commit-graph-chain doesn't exist.\n  + false\n  error: last command exited with $?=1\n\nI think GIT_TEST_COMMIT_GRAPH should be unset for both\nfetch.writeCommitGraph tests.\n\n> +\t)\n> +'\n> +\n>  # configured prune tests\n>  \n>  set_config_tristate () {\n> -- \n> gitgitgadget\n> \n"},{"id":"384771","messageId":"pull.415.v4.git.1571924442.gitgitgadget@gmail.com","threadId":"52093","inReplyTo":"pull.415.v3.git.1571919488.gitgitgadget@gmail.com","subject":"[PATCH v4 0/2] [v2.24.0-rc0 BUG] fetch.writeCommitGraph fails on first fetch","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-24T13:40:40Z","receivedAt":"2019-10-24T13:40:48Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"UPDATE for V2: We now know the full repro, and a test is added. Thanks\nSzeder and Peff for your insights!\n\nUPDATE in V3: Cleaned up the commit messages and some test details.\n\nUPDATE in V4: There is an unfortunate interaction with GIT_TEST_COMMIT_GRAPH\nthat requires an update to the test. Sorry for not noticing this earlier.\nThanks, Johannes for pointing it out!\n\nWhile dogfooding, Johannes found a bug in the fetch.writeCommitGraph config\nbehavior. While his example initially happened during a clone with\n--recurse-submodules, (UPDATE) and the submodule is important, but\n--recurse-submodules is not:\n\n$ git clone <url> test\n$ cd test\n$ git -c fetch.writeCommitGraph=true fetch origin\nComputing commit graph generation numbers: 100% (12/12), done.\nBUG: commit-graph.c:886: missing parent <hash1> for commit <hash2>\nAborted (core dumped)\n\nIn the repo I had cloned, there were really 60 commits to scan, but only 12\nwere in the list to write when calling compute_generation_numbers(). A\ncommit in the list expects to see a parent, but that parent is not in the\nlist. The simple example I used for my testing was \nhttps://github.com/derrickstolee/numbers. Thie repo HAS A SUBMODULE, I just\nforgot. Sorry for derailing the investigation somewhat.\n\nThe details above are the start of the commit message for [PATCH 1/2],\nincluding a test that fails when fetching after cloning a repo with a\nsubmodule.\n\nIn [PATCH 2/2], I actually have the fix. I tried to include as much detail\nas I could for how I investigated the problem and why I think this is the\nright solution. I added details that have come from the on-list discussion,\nincluding what the submodule code is doing and why REACHABLE is no longer\nused in commit-reach.c.\n\nThanks, -Stolee\n\nDerrick Stolee (2):\n  t5510-fetch.sh: demonstrate fetch.writeCommitGraph bug\n  commit-graph: fix writing first commit-graph during fetch\n\n commit-graph.c   | 11 +++++++----\n commit-reach.c   |  1 -\n object.h         |  3 ++-\n t/t5510-fetch.sh | 16 ++++++++++++++++\n 4 files changed, 25 insertions(+), 6 deletions(-)\n\n\nbase-commit: d966095db01190a2196e31195ea6fa0c722aa732\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-415%2Fderrickstolee%2Ffetch-first-write-fail-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-415/derrickstolee/fetch-first-write-fail-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/415\n\nRange-diff vs v3:\n\n 1:  ce53b5a7bf ! 1:  8150108471 t5510-fetch.sh: demonstrate fetch.writeCommitGraph bug\n     @@ -20,7 +20,10 @@\n          parent, but that parent is not in the list.\n      \n          A follow-up will fix the bug, but first we create a test that\n     -    demonstrates the problem.\n     +    demonstrates the problem. This test must be careful about an existing\n     +    commit-graph file, since GIT_TEST_COMMIT_GRAPH=1 will cause the repo we\n     +    are cloning to already have one. This then prevents the incremtnal\n     +    commit-graph write during the first 'git fetch'.\n      \n          Helped-by: Jeff King <peff@peff.net>\n          Helped-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n     @@ -44,7 +47,7 @@\n      +\tgit clone \"super\" super-clone &&\n      +\t(\n      +\t\tcd super-clone &&\n     -+\t\ttest_path_is_missing .git/objects/info/commit-graphs/commit-graph-chain &&\n     ++\t\trm -rf .git/objects/info &&\n      +\t\tgit -c fetch.writeCommitGraph=true fetch origin &&\n      +\t\ttest_path_is_file .git/objects/info/commit-graphs/commit-graph-chain\n      +\t)\n 2:  edacfff490 = 2:  6d01e90591 commit-graph: fix writing first commit-graph during fetch\n\n-- \ngitgitgadget\n"},{"id":"384772","messageId":"81501084713c129327d150c66779505ca4f1b271.1571924442.git.gitgitgadget@gmail.com","threadId":"52093","inReplyTo":"pull.415.v4.git.1571924442.gitgitgadget@gmail.com","subject":"[PATCH v4 1/2] t5510-fetch.sh: demonstrate fetch.writeCommitGraph bug","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-24T13:40:41Z","receivedAt":"2019-10-24T13:40:48Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWhile dogfooding, Johannes found a bug in the fetch.writeCommitGraph\nconfig behavior. His example initially happened during a clone with\n--recurse-submodules, we found that this happens with the first fetch\nafter cloning a repository that contains a submodule:\n\n\t$ git clone <url> test\n\t$ cd test\n\t$ git -c fetch.writeCommitGraph=true fetch origin\n\tComputing commit graph generation numbers: 100% (12/12), done.\n\tBUG: commit-graph.c:886: missing parent <hash1> for commit <hash2>\n\tAborted (core dumped)\n\nIn the repo I had cloned, there were really 60 commits to scan, but\nonly 12 were in the list to write when calling\ncompute_generation_numbers(). A commit in the list expects to see a\nparent, but that parent is not in the list.\n\nA follow-up will fix the bug, but first we create a test that\ndemonstrates the problem. This test must be careful about an existing\ncommit-graph file, since GIT_TEST_COMMIT_GRAPH=1 will cause the repo we\nare cloning to already have one. This then prevents the incremtnal\ncommit-graph write during the first 'git fetch'.\n\nHelped-by: Jeff King <peff@peff.net>\nHelped-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nHelped-by: Szeder Gábor <szeder.dev@gmail.com>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n t/t5510-fetch.sh | 16 ++++++++++++++++\n 1 file changed, 16 insertions(+)\n\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex ecabbe1616..195781ce8d 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -583,6 +583,22 @@ test_expect_success 'fetch.writeCommitGraph' '\n \t)\n '\n \n+test_expect_failure 'fetch.writeCommitGraph with submodules' '\n+\tgit clone dups super &&\n+\t(\n+\t\tcd super &&\n+\t\tgit submodule add \"file://$TRASH_DIRECTORY/three\" &&\n+\t\tgit commit -m \"add submodule\"\n+\t) &&\n+\tgit clone \"super\" super-clone &&\n+\t(\n+\t\tcd super-clone &&\n+\t\trm -rf .git/objects/info &&\n+\t\tgit -c fetch.writeCommitGraph=true fetch origin &&\n+\t\ttest_path_is_file .git/objects/info/commit-graphs/commit-graph-chain\n+\t)\n+'\n+\n # configured prune tests\n \n set_config_tristate () {\n-- \ngitgitgadget\n\n"},{"id":"384773","messageId":"6d01e905911bb68b85caa2f151e723aa542eb263.1571924442.git.gitgitgadget@gmail.com","threadId":"52093","inReplyTo":"pull.415.v4.git.1571924442.gitgitgadget@gmail.com","subject":"[PATCH v4 2/2] commit-graph: fix writing first commit-graph during fetch","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-24T13:40:42Z","receivedAt":"2019-10-24T13:40:50Z","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 previous commit includes a failing test for an issue around\nfetch.writeCommitGraph and fetching in a repo with a submodule. Here, we\nfix that bug and set the test to \"test_expect_success\".\n\nThe problem arises with this set of commands when the remote repo at\n<url> has a submodule. Note that --recurse-submodules is not needed to\ndemonstrate the bug.\n\n\t$ git clone <url> test\n\t$ cd test\n\t$ git -c fetch.writeCommitGraph=true fetch origin\n\tComputing commit graph generation numbers: 100% (12/12), done.\n\tBUG: commit-graph.c:886: missing parent <hash1> for commit <hash2>\n\tAborted (core dumped)\n\nAs an initial fix, I converted the code in builtin/fetch.c that calls\nwrite_commit_graph_reachable() to instead launch a \"git commit-graph\nwrite --reachable --split\" process. That code worked, but is not how we\nwant the feature to work long-term.\n\nThat test did demonstrate that the issue must be something to do with\ninternal state of the 'git fetch' process.\n\nThe write_commit_graph() method in commit-graph.c ensures the commits we\nplan to write are \"closed under reachability\" using close_reachable().\nThis method walks from the input commits, and uses the UNINTERESTING\nflag to mark which commits have already been visited. This allows the\nwalk to take O(N) time, where N is the number of commits, instead of\nO(P) time, where P is the number of paths. (The number of paths can be\nexponential in the number of commits.)\n\nHowever, the UNINTERESTING flag is used in lots of places in the\ncodebase. This flag usually means some barrier to stop a commit walk,\nsuch as in revision-walking to compare histories. It is not often\ncleared after the walk completes because the starting points of those\nwalks do not have the UNINTERESTING flag, and clear_commit_marks() would\nstop immediately.\n\nThis is happening during a 'git fetch' call with a remote. The fetch\nnegotiation is comparing the remote refs with the local refs and marking\nsome commits as UNINTERESTING.\n\nI tested running clear_commit_marks_many() to clear the UNINTERESTING\nflag inside close_reachable(), but the tips did not have the flag, so\nthat did nothing.\n\nIt turns out that the calculate_changed_submodule_paths() method is at\nfault. Thanks, Peff, for pointing out this detail! More specifically,\nfor each submodule, the collect_changed_submodules() runs a revision\nwalk to essentially do file-history on the list of submodules. That\nrevision walk marks commits UNININTERESTING if they are simplified away\nby not changing the submodule.\n\nInstead, I finally arrived on the conclusion that I should use a flag\nthat is not used in any other part of the code. In commit-reach.c, a\nnumber of flags were defined for commit walk algorithms. The REACHABLE\nflag seemed like it made the most sense, and it seems it was not\nactually used in the file. The REACHABLE flag was used in early versions\nof commit-reach.c, but was removed by 4fbcca4 (commit-reach: make\ncan_all_from_reach... linear, 2018-07-20).\n\nAdd the REACHABLE flag to commit-graph.c and use it instead of\nUNINTERESTING in close_reachable(). This fixes the bug in manual\ntesting.\n\nReported-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nHelped-by: Jeff King <peff@peff.net>\nHelped-by: Szeder Gábor <szeder.dev@gmail.com>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n commit-graph.c   | 11 +++++++----\n commit-reach.c   |  1 -\n object.h         |  3 ++-\n t/t5510-fetch.sh |  2 +-\n 4 files changed, 10 insertions(+), 7 deletions(-)\n\ndiff --git a/commit-graph.c b/commit-graph.c\nindex fc4a43b8d6..0aea7b2ae5 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -41,6 +41,9 @@\n #define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * GRAPH_CHUNKLOOKUP_WIDTH \\\n \t\t\t+ GRAPH_FANOUT_SIZE + the_hash_algo->rawsz)\n \n+/* Remember to update object flag allocation in object.h */\n+#define REACHABLE       (1u<<15)\n+\n char *get_commit_graph_filename(const char *obj_dir)\n {\n \tchar *filename = xstrfmt(\"%s/info/commit-graph\", obj_dir);\n@@ -1030,11 +1033,11 @@ static void add_missing_parents(struct write_commit_graph_context *ctx, struct c\n {\n \tstruct commit_list *parent;\n \tfor (parent = commit->parents; parent; parent = parent->next) {\n-\t\tif (!(parent->item->object.flags & UNINTERESTING)) {\n+\t\tif (!(parent->item->object.flags & REACHABLE)) {\n \t\t\tALLOC_GROW(ctx->oids.list, ctx->oids.nr + 1, ctx->oids.alloc);\n \t\t\toidcpy(&ctx->oids.list[ctx->oids.nr], &(parent->item->object.oid));\n \t\t\tctx->oids.nr++;\n-\t\t\tparent->item->object.flags |= UNINTERESTING;\n+\t\t\tparent->item->object.flags |= REACHABLE;\n \t\t}\n \t}\n }\n@@ -1052,7 +1055,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)\n \t\tdisplay_progress(ctx->progress, i + 1);\n \t\tcommit = lookup_commit(ctx->r, &ctx->oids.list[i]);\n \t\tif (commit)\n-\t\t\tcommit->object.flags |= UNINTERESTING;\n+\t\t\tcommit->object.flags |= REACHABLE;\n \t}\n \tstop_progress(&ctx->progress);\n \n@@ -1089,7 +1092,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)\n \t\tcommit = lookup_commit(ctx->r, &ctx->oids.list[i]);\n \n \t\tif (commit)\n-\t\t\tcommit->object.flags &= ~UNINTERESTING;\n+\t\t\tcommit->object.flags &= ~REACHABLE;\n \t}\n \tstop_progress(&ctx->progress);\n }\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 3ea174788a..4ca7e706a1 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -10,7 +10,6 @@\n #include \"commit-reach.h\"\n \n /* Remember to update object flag allocation in object.h */\n-#define REACHABLE       (1u<<15)\n #define PARENT1\t\t(1u<<16)\n #define PARENT2\t\t(1u<<17)\n #define STALE\t\t(1u<<18)\ndiff --git a/object.h b/object.h\nindex 0120892bbd..25f5ab3d54 100644\n--- a/object.h\n+++ b/object.h\n@@ -68,7 +68,8 @@ struct object_array {\n  * bisect.c:                                        16\n  * bundle.c:                                        16\n  * http-push.c:                                     16-----19\n- * commit-reach.c:                                15-------19\n+ * commit-graph.c:                                15\n+ * commit-reach.c:                                  16-----19\n  * sha1-name.c:                                              20\n  * list-objects-filter.c:                                      21\n  * builtin/fsck.c:           0--3\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 195781ce8d..4b60282689 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -583,7 +583,7 @@ test_expect_success 'fetch.writeCommitGraph' '\n \t)\n '\n \n-test_expect_failure 'fetch.writeCommitGraph with submodules' '\n+test_expect_success 'fetch.writeCommitGraph with submodules' '\n \tgit clone dups super &&\n \t(\n \t\tcd super &&\n-- \ngitgitgadget\n"},{"id":"385160","messageId":"20191030143142.GW4348@szeder.dev","threadId":"52093","inReplyTo":"e2eb7b76-a29b-04c4-e698-f3a67ae56ecc@gmail.com","subject":"Re: [PATCH v2 2/2] commit-graph: fix writing first commit-graph during fetch","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-10-30T14:31:42Z","receivedAt":"2019-10-30T14:31:48Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Thu, Oct 24, 2019 at 06:39:51AM -0400, Derrick Stolee wrote:\n> >> Instead, I finally arrived on the conclusion that I should use a flag\n> >> that is not used in any other part of the code. In commit-reach.c, a\n> >> number of flags were defined for commit walk algorithms. The REACHABLE\n> >> flag seemed like it made the most sense, and it seems it was not\n> >> actually used in the file. The REACHABLE flag was used in early versions\n> >> of commit-reach.c, but was removed by 4fbcca4 (commit-reach: make\n> >> can_all_from_reach... linear, 2018-07-20).\n> >>\n> >> Add the REACHABLE flag to commit-graph.c and use it instead of\n> >> UNINTERESTING in close_reachable(). This fixes the bug in manual\n> >> testing.\n> > \n> > I'm inclined to agree that using a flag that is not used anywhere else\n> > is the safest thing to do, and at -rcX time safest is good.  I'm not\n> > sure whether it's the right thing to do in the long term, though.\n> > \n> > Furthermore, calling this flag REACHABLE is misleading, because the\n> > code actually means SEEN.\n> > Consider the following sequence of commands:\n> > \n> >   # Create a pack with two commits\n> >   $ git commit --allow-empty -m one &&\n> >   $ git commit --allow-empty -m two &&\n> >   $ git repack -ad &&\n> >   # Make one of those commits unreachable\n> >   $ git reset --hard HEAD^ &&\n> >   # Not even from reflogs!\n> >   $ git reflog expire --expire-unreachable=now --all\n> >   # Now write a commit-graph from that pack file\n> >   $ git commit-graph write\n> >   Computing commit graph generation numbers: 100% (2/2), done.\n> > \n> > It added two commits to the commit-graph, although one of them is\n> > clearly not reachable anymore, so marking it as REACHABLE while\n> > enumerating all commits feels wrong.\n> \n> Since you are using \"git commit-graph write\", the command is scanning\n> all pack-files for commits to include. Even in this case, the\n> close_reachable() method needs to walk to see if any commits are missing.\n> (It could be that the root commit is loose for some strange reason.)\n> \n> In this case, we are marking REACHABLE the commits that can be reached\n> from our \"starting\" commits. In your example we start with every commit.\n\nThat's exactly my point.  fsck already uses the REACHABLE flag to mark\nobjects that are reachable not only from all refs (including the HEADs\nof all worktrees), but their reflogs and the index as well.\n\nHowever, in close_unreachable() we start with a bunch of commits and\nthen go into a loop adding their parents to an array, while marking\neach parent to prevent them from being added multiple times in a mergy\nhistory.  We have a good couple of similar traversals in our code\nbase, and in revision.c, builtin/describe.c, walker.c,\nnegotiator/default.c (and at this point I stopped looking) we mark\nthose parents as SEEN.\n\nOn a somewhat related note: 'git commit-graph write --reachable'\ndoesn't include HEAD; should it?\n\n> If you had used `git commit-graph write --stdin-packs` and provided a\n> small pack name over stdin, the concept would be similar and even more\n> pronounced: the pack (perhaps downloaded via 'fetch') is not likely to\n> contain every commit, so we need to walk all reachable commits from\n> those included.\n> \n> I'll have a v3 today with the requested fixes.\n> \n> Thanks,\n> -Stolee\n"}]}