{"thread":{"id":"57988","subject":"An endless loop fetching issue with partial clone, alternates and commit graph","startedAt":"2022-06-12T16:17:50Z","lastAt":"2022-06-12T16:17:50Z","messageCount":1,"participants":["Han Xin"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"457089","messageId":"20220612161707.21807-1-chiyutianyi@gmail.com","threadId":"57988","inReplyTo":null,"subject":"An endless loop fetching issue with partial clone, alternates and commit graph","fromName":"Han Xin","fromEmail":"chiyutianyi@gmail.com","sentAt":"2022-06-12T16:17:07Z","receivedAt":"2022-06-12T16:17:50Z","isPatch":false,"sender":{"key":"chiyutianyi@gmail.com","avatar":null},"body":"We found an issue that could create an endless loop where alternates\nobjects are used improperly.\n\nWhile do fetching in a partial cloned repository with a commit graph,\nderef_without_lazy_fetch_extended() will call lookup_commit_in_graph()\nto find the commit object. We can found the code in commit-graph.c:\n\n     struct commit *lookup_commit_in_graph(struct repository *repo, const struct object_id *id)\n     {\n          ...\n          if (!search_commit_pos_in_graph(id, repo->objects->commit_graph, &pos))\n               return NULL;\n          if (!repo_has_object_file(repo, id))\n               return NULL;\n\nIf we found the object in the commit graph, but missing it in the repository,\nwe will go into an endless loop:\n     git fetch -> deref_without_lazy_fetch_extended() -> \n          lookup_commit_in_graph() -> repo_has_object_file() -> \n               promisor_remote_get_direct() -> fetch_objects() ->\n                    git fetch\n\nI know that the reason for this issue is due to improper use of\nalternates, we can ensure that objects will not be lost by maintaining\nall the references. But shouldn't we do something about this unusual\nusage, it will cause a fetch bombardment of the remote git service.\n\nWe can reproduce this issue with the following test case, it will\ngenerate a lot of git processes, please be careful to stop it.\n------------------------------------------------------\n#!/bin/sh\n\ntest_description='test for an endless loop fetching'\n\nGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\nexport GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n\n. ./test-lib.sh\n\ntest_expect_success 'setup' '\n    git init --bare dest.git &&\n    test_commit one &&\n    git checkout -b testbranch &&\n    test_commit two &&\n    git push dest.git --all\n'\n\ntest_expect_success 'prepare a alternates repository without testbranch' '\n    git clone -b $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME dest.git alternates &&\n    oid=$(git -C alternates rev-parse refs/remotes/origin/testbranch) &&\n    git -C alternates update-ref -d refs/remotes/origin/testbranch &&\n    git -C alternates gc --prune=now\n'\n\ntest_expect_success 'prepare a repository with commit-graph' '\n    git init source &&\n    echo \"$(pwd)/dest.git/objects\" >source/.git/objects/info/alternates &&\n    git -C source remote add origin \"$(pwd)/dest.git\" &&\n    git -C source config remote.origin.promisor true &&\n    git -C source config remote.origin.partialclonefilter blob:none &&\n    git -C source fetch origin &&\n    (\n        cd source &&\n        test_commit three &&\n        git -c gc.writeCommitGraph=true gc\n    )\n'\n\ntest_expect_success 'change alternates' '\n    echo \"$(pwd)/alternates/.git/objects\" >source/.git/objects/info/alternates &&\n    # this will bring an endless loop fetching\n    git -C source fetch origin $oid\n'\n\ntest_done\n\n------------------------------------------------------\n\nThanks\n-Han Xin\n"}]}