{"thread":{"id":"63719","subject":"git treeless-clone + wait + pull → problem, again pull → OK","startedAt":"2025-07-01T09:29:49Z","lastAt":"2025-07-22T09:24:04Z","messageCount":3,"participants":["Дилян Палаузов","Jeff King"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"520999","messageId":"e7a2fdff63d9a90ef4dc1341fa642fff5197b64a.camel@aegee.org","threadId":"63719","inReplyTo":null,"subject":"git treeless-clone + wait + pull → problem, again pull → OK","fromName":"Дилян Палаузов","fromEmail":"dilyan.palauzov@aegee.org","sentAt":"2025-07-01T09:24:05Z","receivedAt":"2025-07-01T09:29:49Z","isPatch":false,"sender":{"key":"dilyan.palauzov@aegee.org","avatar":null},"body":"Hello,\n\nthe problem is that when I do a treeless or blobless clone and some time later git pull, git prints many, many lines that it tries to fetch data, then I interrupt with Ctrl+C, then do git pull again and then it completes.  However I never tried to precisely document this until now:\n\nOn 26 June 2025 I do\n\n$ git clone --filter=tree:0 https://github.com/git/git.git\n…\n$ git show --oneline\ncf6f63ea6 (HEAD -> master, origin/master, origin/HEAD) The fourth batch\n\n\nToday I do \n\n$ git pull\nFrom https://github.com/git/git\n   cf6f63ea6..83014dc05  master     -> origin/master\n   74e6fc65d..83e99ddf4  next       -> origin/next\n + bc3287e71...a842a7780 seen       -> origin/seen  (forced update)\n   fefffbb31..7af8e2e03  todo       -> origin/todo\nfatal: You are attempting to fetch cf6f63ea6bf35173e02e18bdc6a4ba41288acff9, which is in the commit graph file but not in the object database.\nThis is probably due to repo corruption.\nIf you are attempting to repair this repo corruption by refetching the missing object, use 'git fetch --refetch' with the missing object.\nfatal: could not fetch 5e66731277a4d791043dc51e2804dc0b496c523b from promisor remote\n\n$ git pull\nUpdating cf6f63ea6..83014dc05                   \nremote: Enumerating objects: 22, done.                                                                          \nremote: Counting objects: 100% (21/21), done.   \nremote: Compressing objects: 100% (21/21), done.\nReceiving objects: 100% (22/22), 137.56 KiB | 8.60 MiB/s, done.\nremote: Total 22 (delta 0), reused 1 (delta 0), pack-reused 1 (from 1)\nFast-forward                                            \n Documentation/RelNotes/2.51.0.adoc     |  15 +++    \n Documentation/config/merge.adoc        |  14 +-   \n Documentation/git-merge.adoc           |   2 +-\n Documentation/git-stash.adoc           |  29 ++++-\n Documentation/merge-options.adoc       |   3 +\n builtin/merge.c                        |  66 +++++++++-\n builtin/pull.c                         |   3 +\n builtin/stash.c                        | 460 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n contrib/coccinelle/commit.cocci        |   3 +-\n hash.h                                 |   1 +\n object-name.c                          |   6 +-\n t/t0021-conversion.sh                  |   4 +-\n t/t0610-reftable-basics.sh             |   6 +-\n t/t0612-reftable-jgit-compatibility.sh |  13 +-\n t/t0613-reftable-write-options.sh      |  24 +---\n t/t1400-update-ref.sh                  |  10 +-\n t/t3903-stash.sh                       | 101 +++++++++++++++\n t/t5004-archive-corner-cases.sh        |   5 +-\n t/t6422-merge-rename-corner-cases.sh   |  10 +-\n t/t7422-submodule-output.sh            |   9 +-\n t/t7600-merge.sh                       |  74 ++++++++++-\n t/test-lib-functions.sh                |  16 ++-\n 22 files changed, 789 insertions(+), 85 deletions(-)\n\nAs can be seen, after treeless (or blobless) clone, git pull has to be executed twice to complete the operation, the first time calling always git fails.  With always I mean over longer period of time I tried this with many different repositories.\n\ngit version 2.50.0\n\nKind regards\n  Дилян\n"},{"id":"522418","messageId":"20250722091749.GA864077@coredump.intra.peff.net","threadId":"63719","inReplyTo":"e7a2fdff63d9a90ef4dc1341fa642fff5197b64a.camel@aegee.org","subject":"Re: git treeless-clone + wait + pull → problem, again pull → OK","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-07-22T09:17:49Z","receivedAt":"2025-07-22T09:17:51Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"[+cc Jonathan Tan]\n\nOn Tue, Jul 01, 2025 at 12:24:05PM +0300, Дилян Палаузов wrote:\n\n> the problem is that when I do a treeless or blobless clone and some\n> time later git pull, git prints many, many lines that it tries to\n> fetch data, then I interrupt with Ctrl+C, then do git pull again and\n> then it completes.  However I never tried to precisely document this\n> until now:\n> \n> On 26 June 2025 I do\n> \n> $ git clone --filter=tree:0 https://github.com/git/git.git\n> …\n> $ git show --oneline\n> cf6f63ea6 (HEAD -> master, origin/master, origin/HEAD) The fourth batch\n> \n> \n> Today I do \n> \n> $ git pull\n> From https://github.com/git/git\n>    cf6f63ea6..83014dc05  master     -> origin/master\n>    74e6fc65d..83e99ddf4  next       -> origin/next\n>  + bc3287e71...a842a7780 seen       -> origin/seen  (forced update)\n>    fefffbb31..7af8e2e03  todo       -> origin/todo\n> fatal: You are attempting to fetch cf6f63ea6bf35173e02e18bdc6a4ba41288acff9, which is in the commit graph file but not in the object database.\n> This is probably due to repo corruption.\n> If you are attempting to repair this repo corruption by refetching the missing object, use 'git fetch --refetch' with the missing object.\n> fatal: could not fetch 5e66731277a4d791043dc51e2804dc0b496c523b from promisor remote\n\nI took a look at this a few weeks ago and came up with a reproducible\nrecipe:\n\n  git init repo\n  cd repo\n\n  url=https://github.com/git/git.git\n  git fetch --filter=tree:0 $url cf6f63ea6bf35173e02e18bdc6a4ba41288acff9:refs/heads/foo\n  git checkout foo\n  git commit-graph write --reachable\n  git fetch $url\n\nThat final fetch ends up spawning a seemingly endless (though I suspect\nactually finite) series of child fetches. Looking at the callstack, it's\ncoming from fetch_submodules(), which wants to do tree diffs in the\nfetched history looking for changed submodules. But of course we don't\nhave those trees, so we fault them in one by one.\n\nWhich certainly seems non-ideal. But what is more interesting is that\nwhile doing so, we eventually hit that same fatal error:\n\n  fatal: You are attempting to fetch cf6f63ea6bf35173e02e18bdc6a4ba41288acff9, which is in the commit graph file but not in the object database.\n\nI tried swapping out $url for a local copy of the repo (to stop\nhammering poor GitHub's servers). But it doesn't seem to reproduce. That\nplus the apparently-random time of failure makes me think it's a race\ncondition.\n\nAnd there is something interesting happening in the background here:\neach of those sub-fetches may kick off an asynchronous \"git maintenance\"\nrun. Which will find something useful to do because we're building up a\nbig pile of packs. So it eventually tries to repack.\n\nAnd so it seems our race is that fetch sees the commit in the commit\ngraph but sometimes _not_ the actual packfile (because it got repacked).\nUsually we'd try to re-scan the packfiles for exactly this case. But the\ncaller in deref_without_lazy_fetch() does not do so. It calls\nhas_object() without any flags, avoiding the re-scan, like this:\n\n          commit = lookup_commit_in_graph(the_repository, oid);\n          if (commit) {\n                  if (mark_tags_complete_and_check_obj_db) {\n                          if (!odb_has_object(the_repository->objects, oid, 0))\n                                  die_in_commit_graph_only(oid);\n                  }\n                  return commit;\n          }\n\nThis is due to 5d4cc78f72 (fetch-pack: die if in commit graph but not\nobj db, 2024-11-05). I'm not sure I fully understand all of the details\nof that commit, so I don't have a solution. Maybe it should be passing\nHAS_OBJECT_RECHECK_PACKED?\n\nNormally I'd worry about performance, since fetch is often asking about\nobjects we don't expect to have (and that re-scan is expensive and\nnormally just confirms that no, we don't have the object). But in this\ncase we'll only hit this check if we called lookup_commit_in_graph().\nWhich implies we _do_ expect to have it (or it's a weird state where the\ngraph file is stale). So maybe it would be OK to use that flag in this\ncall?\n\n\nI don't think this race is strictly limited to using filters. It's just\nthat it's a convenient way to start a big string of fetches that will\nrace with repacks.\n\nWhether or not fetch should avoid kicking off that big string of\nfetches, I don't know. Passing --no-recurse-submodules obviously dulls\nthe pain. Perhaps the default behavior ought to be different in a\ntree-less repo. Or maybe those tree diffs should be done with\nlazy-fetching turned off (there is no point in recursing for a version\nof a submodule whose parent tree we don't even have!). But I think\nthat's all orthogonal to the race.\n\nHmm. So I actually was just intending to write up my notes from a few\nweeks ago. But I think I may have talked myself into the idea that this\npatch is the right fix:\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 5e74235fc0..7288e2d251 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -142,7 +142,8 @@ static struct commit *deref_without_lazy_fetch(const struct object_id *oid,\n \tcommit = lookup_commit_in_graph(the_repository, oid);\n \tif (commit) {\n \t\tif (mark_tags_complete_and_check_obj_db) {\n-\t\t\tif (!odb_has_object(the_repository->objects, oid, 0))\n+\t\t\tif (!odb_has_object(the_repository->objects, oid,\n+\t\t\t\t\t    HAS_OBJECT_RECHECK_PACKED))\n \t\t\t\tdie_in_commit_graph_only(oid);\n \t\t}\n \t\treturn commit;\n\nI'd like to hear what Jonathan Tan thinks, though.\n\n-Peff\n"},{"id":"522419","messageId":"20250722092403.GA889790@coredump.intra.peff.net","threadId":"63719","inReplyTo":"20250722091749.GA864077@coredump.intra.peff.net","subject":"Re: git treeless-clone + wait + pull → problem, again pull → OK","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-07-22T09:24:03Z","receivedAt":"2025-07-22T09:24:04Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jul 22, 2025 at 05:17:49AM -0400, Jeff King wrote:\n\n> Whether or not fetch should avoid kicking off that big string of\n> fetches, I don't know. Passing --no-recurse-submodules obviously dulls\n> the pain. Perhaps the default behavior ought to be different in a\n> tree-less repo. Or maybe those tree diffs should be done with\n> lazy-fetching turned off (there is no point in recursing for a version\n> of a submodule whose parent tree we don't even have!). But I think\n> that's all orthogonal to the race.\n\nIn an ideal world, I'd imagine that something like this would make\nsense:\n\ndiff --git a/submodule.c b/submodule.c\nindex f8373a9ea7..e064fefd9a 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1851,7 +1851,12 @@ int fetch_submodules(struct repository *r,\n \tstrvec_push(&spf.args, \"--recurse-submodules-default\");\n \t/* default value, \"--submodule-prefix\" and its value are added later */\n \n-\tcalculate_changed_submodule_paths(r, &spf.changed_submodule_names);\n+\t{\n+\t\tint save = fetch_if_missing;\n+\t\tfetch_if_missing = 0;\n+\t\tcalculate_changed_submodule_paths(r, &spf.changed_submodule_names);\n+\t\tfetch_if_missing = save;\n+\t}\n \tstring_list_sort(&spf.changed_submodule_names);\n \trun_processes_parallel(&opts);\n \n\nBut it doesn't work, because all of the diff code under the hood in the\ncalculate_changed_submodule_paths() call is not prepared for trees to be\nmissing. So you just get:\n\n  fatal: unable to read tree (3a112b53a40e2d1240b2a4d01d5e616e0f4f09fd)\n\nor similar. We'd need to teach the diff code some permissive mode where\nit quietly ignores trees we don't have locally.\n\n-Peff\n"}]}