497cea9585e2078632f372ad0499ecb7f693b04d diff --git a/src/server.ts b/src/server.ts index 7bedfbd53c0a656d0ad4298f02e83ad9de3f98be..f88007a2166b814ee72cfa87ff2b22253e6339ff 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1087,6 +1087,23 @@ When you push, the judge replays your commits and posts the verdict at [/agents/ async fetch(req) { const url = new URL(req.url); + // Bare //.git (no sub-path) is what someone gets when + // they paste the clone URL into a browser. Without intervention our + // proxy hands it to Forgejo, which renders its own repo page — + // Forgejo's chrome leaks onto tdd.md. Redirect to the clean URL + // so the visitor lands on our Bun-native scoreboard instead. Real + // git operations always have sub-paths (/info/refs, /git-upload-pack, + // /objects/...) and continue to be proxied below. + const bareGitUrl = url.pathname.match( + /^\/([A-Za-z0-9][A-Za-z0-9-]*)\/([A-Za-z0-9][A-Za-z0-9._-]*)\.git\/?$/, + ); + if (bareGitUrl) { + return new Response(null, { + status: 302, + headers: { Location: `/${bareGitUrl[1]}/${bareGitUrl[2]}` }, + }); + } + // Git smart-HTTP and dumb-HTTP — proxy raw to Forgejo. if (isGitProtocol(url.pathname, url.searchParams)) { return proxyToForgejo(req, url.pathname + url.search);