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
+49 -8
View File
@@ -6,7 +6,7 @@ import ConferenceModel from "./model/conference/conferences.model";
import AnonymousError from "./AnonymousError";
import { IAnonymizedPullRequestDocument } from "./model/anonymizedPullRequests/anonymizedPullRequests.types";
import config from "../config";
import got from "got";
import got, { HTTPError } from "got";
import { octokit } from "./GitHubUtils";
import { ContentAnonimizer } from "./anonymize-utils";
@@ -57,20 +57,61 @@ export default class PullRequest {
const [owner, repo] = this._model.source.repositoryFullName.split("/");
const pull_number = this._model.source.pullRequestId;
const [prInfo, comments, diff] = await Promise.all([
oct.rest.pulls.get({
let prInfo;
try {
prInfo = await oct.rest.pulls.get({
owner,
repo,
pull_number,
}),
oct.paginate("GET /repos/{owner}/{repo}/issues/{issue_number}/comments", {
});
} catch (err) {
if ((err as { status?: number }).status === 404) {
throw new AnonymousError("pull_request_not_found", {
httpStatus: 404,
object: this,
cause: err as Error,
});
}
throw err;
}
const commentsPromise = oct
.paginate("GET /repos/{owner}/{repo}/issues/{issue_number}/comments", {
owner: owner,
repo: repo,
issue_number: pull_number,
per_page: 100,
}),
got(`https://github.com/${owner}/${repo}/pull/${pull_number}.diff`),
]);
})
.catch((err): Array<{
body?: string | null;
created_at: string;
updated_at: string;
user?: { login?: string } | null;
}> => {
if ((err as { status?: number }).status === 404) {
console.warn(
"[WARN] Failed to fetch PR comments (404), continuing without them",
`${owner}/${repo}#${pull_number}`
);
return [];
}
throw err;
});
const diffPromise = got(
`https://github.com/${owner}/${repo}/pull/${pull_number}.diff`
).catch((err) => {
if (err instanceof HTTPError && err.response.statusCode === 404) {
console.warn(
"[WARN] Failed to fetch PR diff (404), continuing without it",
`${owner}/${repo}#${pull_number}`
);
return { body: "" };
}
throw err;
});
const [comments, diff] = await Promise.all([commentsPromise, diffPromise]);
this._model.pullRequest = {
diff: diff.body,
+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,