Created
August 27, 2024 06:11
-
-
Save szhu/2ee1f345de48a6a86d0378fc1326c987 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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