Skip to content

Instantly share code, notes, and snippets.

@szhu
Created August 27, 2024 06:11
Show Gist options
  • Save szhu/2ee1f345de48a6a86d0378fc1326c987 to your computer and use it in GitHub Desktop.
Save szhu/2ee1f345de48a6a86d0378fc1326c987 to your computer and use it in GitHub Desktop.
#!/usr/bin/env deno run --allow-env --allow-read --allow-run
// Was this file modified in any PR?
import { readline } from "https://deno.land/x/readline@v1.1.0/mod.ts";
declare global {
export const Deno: any;
export interface ArrayConstructor {
fromAsync<T>(asyncIterable: AsyncIterable<T>): Promise<T[]>;
}
}
async function* $(cmd: string[]) {
const process = Deno.run({
cmd,
stdout: "piped",
stderr: "piped",
stdin: "piped",
});
for await (const line of readline(process.stdout)) {
yield new TextDecoder().decode(line) as string;
}
}
async function $lines(cmd: string[]): Promise<string[]> {
const lines = await Array.fromAsync($(cmd));
return lines[lines.length - 1] === "" ? lines.slice(0, -1) : lines;
}
//
async function getRemoteBranches() {
return await $lines([
"git",
"for-each-ref",
"--format",
"%(refname)",
"refs/remotes/origin",
]);
}
interface Pr {
owner: string;
repo: string;
number: number;
commit: string;
}
async function getPrsByBranches() {
// gh pr list --json headRefName
const responseText = (
await $lines([
"gh",
"pr",
"list",
"--json",
"headRepositoryOwner,headRepository,number,headRefName,headRefOid",
])
).join();
const responseJson = JSON.parse(responseText);
return Object.fromEntries<Pr>(
responseJson.map((pr: any) => [
"refs/remotes/origin/" + pr.headRefName,
{
owner: pr.headRepositoryOwner.login,
repo: pr.headRepository.name,
number: pr.number,
commit: pr.headRefOid,
},
]),
);
}
async function getChangedFiles(branch: string) {
return await $lines([
"git",
"diff",
"--name-only",
branch,
"refs/remotes/origin/main",
]);
}
async function* getAllChangedFiles(branches: string[]) {
for (const branch of branches) {
for (const path of await getChangedFiles(branch)) {
yield { branch, path } as ChangedFile;
}
}
}
interface ChangedFile {
branch: string;
path: string;
}
interface ChangedFileDb {
[path: string]: string[] | undefined;
}
function insertChangedFile(db: ChangedFileDb, file: ChangedFile) {
if (db[file.path] == null) {
db[file.path] = [];
}
db[file.path]!.push(file.branch);
}
async function makeChangedFilesDb(branches: string[]) {
const db: ChangedFileDb = {};
for await (const changedFile of getAllChangedFiles(branches)) {
insertChangedFile(db, changedFile);
}
return db;
}
function prUrl(pr: Pr) {
return `https://github.com/${pr.owner}/${pr.repo}/pull/${pr.number}/files`;
}
function fileHistoryAtUrl(pr: Pr, path: string) {
return `https://github.com/${pr.owner}/${pr.repo}/commits/${pr.commit.slice(0, 7)}/${path}`;
}
async function main(path?: string) {
const prs = await getPrsByBranches();
const branches = Object.keys(prs);
const db = await makeChangedFilesDb(branches);
if (path == null) {
for (const [path, branches] of Object.entries(db)) {
console.log(path);
for (const branch of branches!) {
console.log(` ${prUrl(prs[branch]!)}`);
}
}
} else {
for (const branch of db[path] ?? []) {
const pr = prs[branch]!;
console.log(`This file was modified in PR #${pr.number}:`);
console.log(prUrl(pr));
console.log(fileHistoryAtUrl(pr, path));
}
}
}
await main(...Deno.args);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment