diff --git a/odb.c b/odb.c index 2c93a9490c6b18..4313bb795e6442 100644 --- a/odb.c +++ b/odb.c @@ -2,6 +2,7 @@ #include "abspath.h" #include "commit-graph.h" #include "config.h" +#include "dir.h" #include "environment.h" #include "gettext.h" #include "hashmap.h" @@ -183,11 +184,28 @@ static int refs_from_alternate_cb(struct odb_files_dir *alternate, void *payload) { struct strbuf path = STRBUF_INIT; + struct strbuf self = STRBUF_INIT; + const char *quarantine; size_t base_len; struct alternate_refs_data *cb = payload; if (!strbuf_realpath(&path, alternate->abspath, 0)) goto out; + + /* + * Inside an object quarantine, our own object directory is one of + * our alternates, but its refs are our own refs, not alternate refs. + * The quarantine is created directly inside it, so it is the parent + * of GIT_QUARANTINE_PATH. + */ + quarantine = getenv(GIT_QUARANTINE_ENVIRONMENT); + if (quarantine && strbuf_realpath(&self, quarantine, 0)) { + strbuf_strip_file_from_path(&self); + strbuf_trim_trailing_dir_sep(&self); + if (!fspathcmp(path.buf, self.buf)) + goto out; + } + if (!strbuf_strip_suffix(&path, "/objects")) goto out; base_len = path.len; @@ -202,6 +220,7 @@ static int refs_from_alternate_cb(struct odb_files_dir *alternate, out: strbuf_release(&path); + strbuf_release(&self); return 0; } diff --git a/t/perf/p5552-push-hidden-refs.sh b/t/perf/p5552-push-hidden-refs.sh new file mode 100755 index 00000000000000..e9aa20629875c7 --- /dev/null +++ b/t/perf/p5552-push-hidden-refs.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +test_description='speed of push into a repository with many hidden refs' +. ./perf-lib.sh + +test_perf_fresh_repo + +test_expect_success 'set up repository with many hidden refs' ' + git init --bare dst.git && + test_commit_bulk --ref=refs/hidden/base --id=hidden 1000 && + git rev-list refs/hidden/base >commits && + for i in $(test_seq 200) + do + sed "s|^\(.*\)$|create refs/hidden/$i/\1 \1|" commits || return 1 + done >input && + git push dst.git refs/hidden/base && + git -C dst.git update-ref --stdin >"$(pwd)/alternate-refs.log" + EOF + git -C hidden.git config core.alternateRefsCommand ./alternate-refs && + test_commit hidden-push && + git push hidden.git HEAD:refs/heads/new && + test_path_is_missing alternate-refs.log +' + +test_expect_success 'quarantine lists refs of other alternates' ' + test_when_finished "rm -f alternate-refs.log" && + git init --bare other.git && + git push other.git HEAD:refs/heads/other && + echo "$(pwd)/other.git/objects" >hidden.git/objects/info/alternates && + test_commit hidden-push-2 && + git push hidden.git HEAD:refs/heads/new2 && + (cd other.git && pwd -P) >expect && + sort -u alternate-refs.log >actual && + test_cmp expect actual +' + +test_expect_success 'rev-list --alternate-refs in a hook skips own refs' ' + test_when_finished "rm -rf altref.git" && + git init --bare altref.git && + git push altref.git HEAD:refs/heads/main && + test_hook -C altref.git pre-receive <<-\EOF && + git rev-list --no-walk --alternate-refs --stdin ../hook-alternate-refs + EOF + test_commit altref-push && + git push altref.git HEAD:refs/heads/new && + test_must_be_empty hook-alternate-refs +' + test_done