Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | 2x | import type { Octokit } from '@octokit/rest';
import { warnAction } from '../../cli/output/tty.js';
/**
* Result from fetching follow-up changes between two commits.
*/
export interface FollowUpChanges {
/** Map of file path to patch content */
patches: Map<string, string>;
/** Commit messages from the follow-up commits */
commitMessages: string[];
}
/**
* Fetch the patches and commit messages between two commits.
*/
export async function fetchFollowUpChanges(
octokit: Octokit,
owner: string,
repo: string,
baseSha: string,
headSha: string
): Promise<FollowUpChanges> {
const { data } = await octokit.repos.compareCommits({
owner,
repo,
base: baseSha,
head: headSha,
});
const patches = new Map<string, string>();
for (const file of data.files ?? []) {
if (file.patch) {
patches.set(file.filename, file.patch);
}
}
const commitMessages: string[] = [];
for (const commit of data.commits ?? []) {
if (commit.commit.message) {
commitMessages.push(commit.commit.message);
}
}
return { patches, commitMessages };
}
/**
* Fetch file content at a specific commit SHA.
*/
export async function fetchFileContent(
octokit: Octokit,
owner: string,
repo: string,
path: string,
sha: string
): Promise<string> {
const { data } = await octokit.repos.getContent({
owner,
repo,
path,
ref: sha,
});
if (Array.isArray(data)) {
throw new Error(`Path "${path}" is a directory, not a file`);
}
if (data.type !== 'file' || !data.content) {
throw new Error(`Path "${path}" is not a file or content unavailable`);
}
return Buffer.from(data.content, 'base64').toString('utf-8');
}
/**
* Fetch specific lines from a file at a commit.
* startLine and endLine are 1-indexed and inclusive.
*/
export async function fetchFileLines(
octokit: Octokit,
owner: string,
repo: string,
path: string,
sha: string,
startLine: number,
endLine: number
): Promise<string> {
const content = await fetchFileContent(octokit, owner, repo, path, sha);
const lines = content.split('\n');
return lines
.slice(startLine - 1, endLine)
.map((line, i) => `${startLine + i}: ${line}`)
.join('\n');
}
const ADD_THREAD_REPLY_MUTATION = `
mutation($threadId: ID!, $body: String!) {
addPullRequestReviewThreadReply(input: {
pullRequestReviewThreadId: $threadId,
body: $body
}) {
comment {
id
}
}
}
`;
/**
* Post a reply to a review thread.
*/
export async function postThreadReply(
octokit: Octokit,
threadId: string,
body: string
): Promise<void> {
try {
await octokit.graphql(ADD_THREAD_REPLY_MUTATION, {
threadId,
body,
});
} catch (error) {
warnAction(`Failed to post thread reply: ${error}`);
throw error;
}
}
/**
* Format a reply for a failed fix attempt.
*/
export function formatFailedFixReply(commitSha: string, reasoning: string): string {
const shortSha = commitSha.slice(0, 7);
return `**Fix attempt detected** (commit ${shortSha})
${reasoning}
The original issue appears unresolved. Please review and try again.
<sub>Evaluated by Warden</sub>`;
}
|