feat(git): improve PR panel (#207)

This commit is contained in:
Bohdan Triapitsyn
2026-01-23 19:54:28 +02:00
committed by GitHub
parent 614277eaa1
commit da20c647e2
9 changed files with 306 additions and 83 deletions
+64 -20
View File
@@ -4094,26 +4094,70 @@ async function main(options = {}) {
return res.json({ connected: true, repo, branch, pr: null, checks: null, canMerge: false });
}
// Checks summary (combined status)
// Checks summary: prefer check-runs (Actions), fallback to classic statuses.
let checks = null;
try {
const combined = await octokit.rest.repos.getCombinedStatusForRef({
owner: repo.owner,
repo: repo.repo,
ref: prData.head?.sha,
});
const statuses = Array.isArray(combined?.data?.statuses) ? combined.data.statuses : [];
const counts = { success: 0, failure: 0, pending: 0 };
statuses.forEach((s) => {
if (s.state === 'success') counts.success += 1;
else if (s.state === 'failure' || s.state === 'error') counts.failure += 1;
else if (s.state === 'pending') counts.pending += 1;
});
const total = counts.success + counts.failure + counts.pending;
const state = counts.failure > 0 ? 'failure' : (counts.pending > 0 ? 'pending' : (total > 0 ? 'success' : 'unknown'));
checks = { state, total, ...counts };
} catch {
checks = null;
const sha = prData.head?.sha;
if (sha) {
try {
const runs = await octokit.rest.checks.listForRef({
owner: repo.owner,
repo: repo.repo,
ref: sha,
per_page: 100,
});
const checkRuns = Array.isArray(runs?.data?.check_runs) ? runs.data.check_runs : [];
if (checkRuns.length > 0) {
const counts = { success: 0, failure: 0, pending: 0 };
for (const run of checkRuns) {
const status = run?.status;
const conclusion = run?.conclusion;
if (status === 'queued' || status === 'in_progress') {
counts.pending += 1;
continue;
}
if (!conclusion) {
counts.pending += 1;
continue;
}
if (conclusion === 'success' || conclusion === 'neutral' || conclusion === 'skipped') {
counts.success += 1;
} else {
counts.failure += 1;
}
}
const total = counts.success + counts.failure + counts.pending;
const state = counts.failure > 0
? 'failure'
: (counts.pending > 0 ? 'pending' : (total > 0 ? 'success' : 'unknown'));
checks = { state, total, ...counts };
}
} catch {
// ignore and fall back
}
if (!checks) {
try {
const combined = await octokit.rest.repos.getCombinedStatusForRef({
owner: repo.owner,
repo: repo.repo,
ref: sha,
});
const statuses = Array.isArray(combined?.data?.statuses) ? combined.data.statuses : [];
const counts = { success: 0, failure: 0, pending: 0 };
statuses.forEach((s) => {
if (s.state === 'success') counts.success += 1;
else if (s.state === 'failure' || s.state === 'error') counts.failure += 1;
else if (s.state === 'pending') counts.pending += 1;
});
const total = counts.success + counts.failure + counts.pending;
const state = counts.failure > 0
? 'failure'
: (counts.pending > 0 ? 'pending' : (total > 0 ? 'success' : 'unknown'));
checks = { state, total, ...counts };
} catch {
checks = null;
}
}
}
// Permission check (best-effort)
@@ -4814,7 +4858,7 @@ async function main(options = {}) {
}
}
if (diffs.length === 0) {
return res.status(400).json({ error: 'No diffs available for selected files' });
return res.status(400).json({ error: 'No diffs available for base...head' });
}
const diffSummaries = diffs.map(({ path, diff }) => `FILE: ${path}\n${diff}`).join('\n\n');
+27 -2
View File
@@ -440,11 +440,24 @@ export async function getRangeDiff(directory, { base, head, path, contextLines =
throw new Error('base and head are required');
}
// Prefer remote-tracking base ref so merged commits don't reappear
// when local base branch is stale (common when user stays on feature branch).
let resolvedBase = baseRef;
const originCandidate = `refs/remotes/origin/${baseRef}`;
try {
const verified = await git.raw(['rev-parse', '--verify', originCandidate]);
if (verified && verified.trim()) {
resolvedBase = `origin/${baseRef}`;
}
} catch {
// ignore
}
const args = ['diff', '--no-color'];
if (typeof contextLines === 'number' && !Number.isNaN(contextLines)) {
args.push(`-U${Math.max(0, contextLines)}`);
}
args.push(`${baseRef}...${headRef}`);
args.push(`${resolvedBase}...${headRef}`);
if (path) {
args.push('--', path);
}
@@ -459,7 +472,19 @@ export async function getRangeFiles(directory, { base, head } = {}) {
if (!baseRef || !headRef) {
throw new Error('base and head are required');
}
const raw = await git.raw(['diff', '--name-only', `${baseRef}...${headRef}`]);
let resolvedBase = baseRef;
const originCandidate = `refs/remotes/origin/${baseRef}`;
try {
const verified = await git.raw(['rev-parse', '--verify', originCandidate]);
if (verified && verified.trim()) {
resolvedBase = `origin/${baseRef}`;
}
} catch {
// ignore
}
const raw = await git.raw(['diff', '--name-only', `${resolvedBase}...${headRef}`]);
return String(raw || '')
.split('\n')
.map((l) => l.trim())