mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-05-15 06:30:26 +02:00
fix mulitple bugs
This commit is contained in:
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+52
-2
@@ -2079,7 +2079,7 @@ code {
|
||||
}
|
||||
|
||||
/* Progress bars on dashboard */
|
||||
.progress {
|
||||
.progress:not(.quota-progress) {
|
||||
min-width: 120px !important;
|
||||
}
|
||||
|
||||
@@ -3830,6 +3830,8 @@ textarea::selection {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 999px;
|
||||
margin-bottom: 0;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
.paper-page .quota-progress .progress-bar {
|
||||
background: var(--color);
|
||||
@@ -4010,6 +4012,21 @@ textarea::selection {
|
||||
background: var(--hover-bg-color);
|
||||
}
|
||||
.paper-table .paper-table-row.repo-inactive { opacity: 0.55; }
|
||||
.paper-table .paper-table-row.repo-error {
|
||||
background: rgba(180, 35, 24, 0.04);
|
||||
border-left: 2px solid #B42318;
|
||||
padding-left: 6px;
|
||||
}
|
||||
.paper-table .paper-table-row.repo-error:hover { background: rgba(180, 35, 24, 0.08); }
|
||||
.dark-mode .paper-table .paper-table-row.repo-error { background: rgba(240, 138, 130, 0.05); border-left-color: #F08A82; }
|
||||
.dark-mode .paper-table .paper-table-row.repo-error:hover { background: rgba(240, 138, 130, 0.1); }
|
||||
.paper-table .cell-status .status-sub-error,
|
||||
.cell-status .status-sub.status-sub-error {
|
||||
color: #B42318;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
.dark-mode .paper-table .cell-status .status-sub-error,
|
||||
.dark-mode .cell-status .status-sub.status-sub-error { color: #F08A82; }
|
||||
.paper-table .cell-anon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -4450,10 +4467,43 @@ textarea::selection {
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.paper-page .quota-row { grid-template-columns: 1fr; gap: 14px; padding: 16px; }
|
||||
.paper-page .paper-page-title { font-size: clamp(1.4rem, 6vw, 1.9rem); margin: 4px 0 2px; }
|
||||
.paper-page .paper-page-lede { font-size: 13px; line-height: 1.4; margin-bottom: 0; }
|
||||
.paper-page .btn-ink { padding: 6px 12px; font-size: 13px; }
|
||||
|
||||
.paper-page .quota-row {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px 12px;
|
||||
padding: 0;
|
||||
margin: 12px 0 0;
|
||||
}
|
||||
.paper-page .dashboard-filter-row {
|
||||
padding: 8px 0;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.paper-page .quota-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 2px;
|
||||
}
|
||||
.paper-page .quota-label { font-size: 10px; letter-spacing: 0.1em; }
|
||||
.paper-page .quota-value { font-size: 11px; }
|
||||
|
||||
.paper-page .dashboard-filter-row { gap: 8px; }
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.paper-page .paper-page-lede {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.paper-page .quota-row { gap: 8px; }
|
||||
.paper-page .quota-label { font-size: 9px; letter-spacing: 0.06em; }
|
||||
.paper-page .quota-value { font-size: 10px; white-space: nowrap; }
|
||||
}
|
||||
|
||||
/* Empty state */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"repository_not_accessible": "This repository is currently not accessible.",
|
||||
"repo_is_updating": "Anonymous GitHub is still processing the repository, it can take several minutes.",
|
||||
"invalid_repo": "The provided repository is not valid.",
|
||||
"invalid_source_repository": "The repository source name is invalid; expected the form 'owner/name'.",
|
||||
"repoId_not_defined": "A repository ID must be provided.",
|
||||
"repoUrl_not_defined": "The repository URL needs to be defined.",
|
||||
"repoId_already_used": "The repository ID is already used.",
|
||||
@@ -25,6 +26,8 @@
|
||||
"branch_not_specified": "The branch is not specified.",
|
||||
"branch_not_found": "The branch of the repository cannot be found.",
|
||||
"commit_not_specified": "A commit must be specified.",
|
||||
"commit_not_found": "The configured commit no longer exists in the source repository. It may have been force-pushed, rebased, or removed.",
|
||||
"repo_renamed": "The source repository appears to have been renamed or moved on GitHub. Please refresh and try again — the cached name has been updated.",
|
||||
"invalid_commit_format": "The commit hash format is invalid. It must be a hexadecimal string.",
|
||||
"pull_request_not_found": "The requested pull request could not be found.",
|
||||
"pull_request_expired": "This pull request has expired and is no longer available.",
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
<div
|
||||
class="paper-table-row"
|
||||
role="row"
|
||||
ng-class="{'repo-inactive': repo.status == 'expired' || repo.status == 'removed', 'row-selected': selected[repo.repoId]}"
|
||||
ng-class="{'repo-inactive': repo.status == 'expired' || repo.status == 'removed', 'repo-error': repo.status == 'error', 'row-selected': selected[repo.repoId]}"
|
||||
ng-repeat="repo in repositories | filter:repoFiler | orderBy:orderBy as filteredRepositories"
|
||||
>
|
||||
<div role="cell" style="width: 28px;">
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
<div
|
||||
class="paper-table-row"
|
||||
role="row"
|
||||
ng-class="{'repo-inactive': repo.status == 'expired' || repo.status == 'removed'}"
|
||||
ng-class="{'repo-inactive': repo.status == 'expired' || repo.status == 'removed', 'repo-error': repo.status == 'error'}"
|
||||
ng-repeat="repo in repositories | filter:repoFiler | orderBy:orderBy as filteredRepositories"
|
||||
>
|
||||
<div class="cell-anon" role="cell">
|
||||
@@ -190,8 +190,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="cell-status" role="cell">
|
||||
<span class="status-dot" ng-class="{'status-removed': repo.status == 'removed' || repo.status == 'expired', 'status-ready': repo.status == 'ready', 'status-error': repo.status == 'error'}"></span>
|
||||
<span ng-bind="repo.status | title"></span>
|
||||
<span class="status-line">
|
||||
<span class="status-dot" ng-class="{'status-removed': repo.status == 'removed' || repo.status == 'expired', 'status-ready': repo.status == 'ready', 'status-error': repo.status == 'error'}"></span>
|
||||
<span ng-bind="repo.status | title"></span>
|
||||
</span>
|
||||
<span class="status-sub status-sub-error" ng-if="repo.status == 'error' && repo.statusMessage" title="{{repo.statusMessage}}" ng-bind="repo.statusMessage"></span>
|
||||
</div>
|
||||
<div class="cell-views num" role="cell" ng-bind="::repo.pageView | number"></div>
|
||||
<div class="cell-expires" role="cell" ng-bind="repo.anonymizeDate | humanTime"></div>
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
<div
|
||||
class="paper-table-row"
|
||||
role="row"
|
||||
ng-class="{'repo-inactive': item.status == 'expired' || item.status == 'removed' || item.status == 'error'}"
|
||||
ng-class="{'repo-inactive': item.status == 'expired' || item.status == 'removed', 'repo-error': item.status == 'error'}"
|
||||
ng-repeat="item in items | filter:itemFilter | orderBy:orderBy as filteredItems"
|
||||
>
|
||||
<div class="cell-anon" role="cell">
|
||||
@@ -206,7 +206,8 @@
|
||||
></span>
|
||||
<span ng-bind="item.status | title"></span>
|
||||
</div>
|
||||
<div class="status-sub" ng-if="item.anonymizeDate" title="Last anonymized {{item.anonymizeDate | humanTime}}" ng-bind="item.anonymizeDate | humanTime"></div>
|
||||
<div class="status-sub status-sub-error" ng-if="item.status == 'error' && item.statusMessage" title="{{item.statusMessage}}" ng-bind="item.statusMessage"></div>
|
||||
<div class="status-sub" ng-if="item.status != 'error' && item.anonymizeDate" title="Last anonymized {{item.anonymizeDate | humanTime}}" ng-bind="item.anonymizeDate | humanTime"></div>
|
||||
</div>
|
||||
<div class="cell-views num" role="cell" ng-bind="item.pageView | number"></div>
|
||||
<div class="cell-expires" role="cell">
|
||||
|
||||
+20
-1
@@ -1374,7 +1374,18 @@ angular
|
||||
if (res.data.options.expirationDate) {
|
||||
$scope.options.expirationDate = new Date(res.data.options.expirationDate);
|
||||
}
|
||||
$scope.details = (await $http.get(`/api/pr/${res.data.source.repositoryFullName}/${res.data.source.pullRequestId}`)).data;
|
||||
try {
|
||||
$scope.details = (await $http.get(`/api/pr/${res.data.source.repositoryFullName}/${res.data.source.pullRequestId}`)).data;
|
||||
} catch (error) {
|
||||
const code = error && error.data && error.data.error;
|
||||
if (code) {
|
||||
$translate("ERRORS." + code).then((translation) => {
|
||||
$scope.addToast({ title: "Error", date: new Date(), body: translation });
|
||||
$scope.error = translation;
|
||||
}, console.error);
|
||||
displayErrorMessage(code);
|
||||
}
|
||||
}
|
||||
$scope.$apply();
|
||||
},
|
||||
() => { $location.url("/404"); }
|
||||
@@ -2867,3 +2878,11 @@ angular
|
||||
getConference();
|
||||
},
|
||||
]);
|
||||
|
||||
$(document).on("click", "#navbarSupportedContent .nav-link", function (e) {
|
||||
if ($(this).attr("data-toggle") === "dropdown") return;
|
||||
var $collapse = $("#navbarSupportedContent");
|
||||
if ($collapse.hasClass("show")) {
|
||||
$collapse.collapse("hide");
|
||||
}
|
||||
});
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+49
-8
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user