fix mulitple bugs

This commit is contained in:
tdurieux
2026-05-05 12:34:03 +03:00
parent 7384638993
commit 27d6b56da7
13 changed files with 242 additions and 29 deletions
+65
View File
@@ -3,6 +3,10 @@ import { Readable } from "stream";
import AnonymizedFile from "../AnonymizedFile";
import { SourceBase } from "./Source";
import { IFile } from "../model/files/files.types";
import { octokit } from "../GitHubUtils";
import { isConnected } from "../../server/database";
import RepositoryModel from "../model/repositories/repositories.model";
import AnonymizedRepositoryModel from "../model/anonymizedRepositories/anonymizedRepositories.model";
export interface GitHubBaseData {
getToken: () => string | Promise<string>;
@@ -25,3 +29,64 @@ export default abstract class GitHubBase implements SourceBase {
abstract getFiles(progress?: (status: string) => void): Promise<IFile[]>;
}
/**
* On a 404 from a commit/tree/zip lookup, probe `repos.get` to determine
* whether the repository itself is gone, was renamed, or only the commit is
* missing. When a rename is detected (via the cached numeric GitHub repo id
* on `RepositoryModel.externalId`), the model's `name` is updated in place so
* subsequent lookups succeed.
*/
export async function classifyGitHubMissError(
err: unknown,
data: GitHubBaseData
): Promise<"repo_not_found" | "commit_not_found" | "repo_renamed"> {
const status = (err as { status?: number }).status;
if (status !== 404) return "repo_not_found";
const oct = octokit(await data.getToken());
try {
await oct.repos.get({
owner: data.organization,
repo: data.repoName,
});
return "commit_not_found";
} catch {
// Repo no longer exists at owner/repo. Try to recover via the cached
// numeric GitHub id — if the repo was renamed, GET /repositories/{id}
// resolves to its new full_name. See #409.
if (!isConnected) return "repo_not_found";
const dbModel = await RepositoryModel.findOne({
name: data.organization + "/" + data.repoName,
});
const ghId =
typeof dbModel?.externalId === "string" &&
dbModel.externalId.startsWith("gh_")
? dbModel.externalId.slice(3)
: null;
if (!dbModel || !ghId) return "repo_not_found";
try {
const r = await oct.request("GET /repositories/{id}", { id: ghId });
const newName = (r?.data as { full_name?: string } | undefined)
?.full_name;
if (newName && newName !== dbModel.name) {
const oldName = dbModel.name;
dbModel.name = newName;
await dbModel.save();
// Propagate the rename to every anonymized repo that referenced
// the old source name, so subsequent lookups (admin diagnostic,
// streaming, download, update cron) all hit the correct GitHub
// location without the user having to recreate the configuration.
await AnonymizedRepositoryModel.updateMany(
{ "source.repositoryName": oldName },
{ $set: { "source.repositoryName": newName } }
);
data.organization = newName.split("/")[0];
data.repoName = newName.split("/")[1];
return "repo_renamed";
}
return "repo_not_found";
} catch {
return "repo_not_found";
}
}
}
+6 -2
View File
@@ -3,7 +3,10 @@ import { Readable } from "stream";
import { OctokitResponse } from "@octokit/types";
import storage from "../storage";
import GitHubBase, { GitHubBaseData } from "./GitHubBase";
import GitHubBase, {
GitHubBaseData,
classifyGitHubMissError,
} from "./GitHubBase";
import { FILE_TYPE } from "../storage/Storage";
import { octokit } from "../GitHubUtils";
import AnonymousError from "../AnonymousError";
@@ -30,7 +33,8 @@ export default class GitHubDownload extends GitHubBase {
try {
response = await this.getZipUrl();
} catch (error) {
throw new AnonymousError("repo_not_found", {
const code = await classifyGitHubMissError(error, this.data);
throw new AnonymousError(code, {
httpStatus: (error as { status?: number }).status || 404,
object: this.data,
cause: error as Error,
+29 -6
View File
@@ -55,12 +55,35 @@ export class GitHubRepository {
}
) {
const oct = octokit(opt.accessToken);
const commit = await oct.repos.getCommit({
owner: this.owner,
repo: this.repo,
ref: sha,
});
return commit.data;
try {
const commit = await oct.repos.getCommit({
owner: this.owner,
repo: this.repo,
ref: sha,
});
return commit.data;
} catch (error) {
const status = (error as { status?: number }).status;
if (status === 404) {
// Distinguish: does the repo itself still exist?
let repoExists = false;
try {
await oct.repos.get({ owner: this.owner, repo: this.repo });
repoExists = true;
} catch {
repoExists = false;
}
throw new AnonymousError(
repoExists ? "commit_not_found" : "repo_not_found",
{
httpStatus: 404,
cause: error as Error,
object: this,
}
);
}
throw error;
}
}
async branches(opt: {
+6 -2
View File
@@ -1,5 +1,8 @@
import AnonymizedFile from "../AnonymizedFile";
import GitHubBase, { GitHubBaseData } from "./GitHubBase";
import GitHubBase, {
GitHubBaseData,
classifyGitHubMissError,
} from "./GitHubBase";
import storage from "../storage";
import * as path from "path";
import got from "got";
@@ -274,7 +277,8 @@ export default class GitHubStream extends GitHubBase {
});
}
if (status === 404) {
throw new AnonymousError("repo_not_found", {
const code = await classifyGitHubMissError(error, this.data);
throw new AnonymousError(code, {
httpStatus: 404,
object: this.data,
cause: error as Error,