get-diff.ts•4.53 kB
import axios from "axios";
import * as child_process from "child_process";
import * as fs from "fs";
import * as path from "path";
import * as dotenv from "dotenv";
// Load environment variables
dotenv.config();
// Configurations
const AZURE_PAT = process.env.AZURE_PAT;
const EXCLUDE_FILES = ["package-lock.json"]; // Files to ignore in the diff
/**
* Main function that processes the pull request URL and returns the differences
* @param pullRequestUrl URL of the pull request in Azure DevOps
* @returns Content of the diff
*/
export async function getPullRequestDiff(
pullRequestUrl: string
): Promise<string> {
try {
const { sshUrl, sourceRefName, targetRefName } = await getPullRequestInfo(
pullRequestUrl
);
const diff = getGitDiff(
sshUrl,
targetRefName,
sourceRefName,
EXCLUDE_FILES
);
if (!diff) {
return "No differences found!";
}
return diff;
} catch (error) {
if (error instanceof Error) {
throw new Error(`Error processing pull request: ${error.message}`);
}
throw new Error(`Unknown error processing pull request`);
}
}
export function getPullRequestDetails(pullRequestUrl: string): {
organization: string;
project: string;
repositoryId: string;
pullRequestNumber: string;
} {
const infos = pullRequestUrl.split("/");
const organization = infos[infos.length - 6];
const project = infos[infos.length - 5];
const repositoryId = infos[infos.length - 3];
const pullRequestNumber = infos[infos.length - 1];
return {
organization,
project,
repositoryId,
pullRequestNumber,
};
}
/**
* Extracts pull request information from the URL
* @param pullRequestUrl URL of the pull request in Azure DevOps
* @returns Information needed to get the diff
*/
export async function getPullRequestInfo(pullRequestUrl: string): Promise<{
sshUrl: string;
sourceRefName: string;
targetRefName: string;
}> {
const { organization, project, repositoryId, pullRequestNumber } =
getPullRequestDetails(pullRequestUrl);
const url = `https://dev.azure.com/${organization}/${project}/_apis/git/repositories/${repositoryId}/pullRequests/${pullRequestNumber}?api-version=7.1-preview.1`;
const response = await axios.get(url, {
auth: {
username: "",
password: AZURE_PAT || "",
},
});
if (response.status !== 200) {
throw new Error(
`Error getting pull request information: ${
response.status
} - ${JSON.stringify(response.data)}`
);
}
const { sshUrl } = response.data.repository;
const { sourceRefName, targetRefName } = response.data;
return {
sshUrl,
sourceRefName,
targetRefName,
};
}
/**
* Gets the diff between two branches
* @param repoUrl URL of the Git repository
* @param baseBranch Destination branch (ex: develop)
* @param targetBranch Source branch of the feature (ex: feature/123)
* @param excludeFiles Files to be ignored in the diff
* @returns The diff
*/
function getGitDiff(
repoUrl: string,
baseBranch: string,
targetBranch: string,
excludeFiles: string[]
): string {
// Remove the refs/heads/ prefix if it exists
baseBranch = baseBranch.replace("refs/heads/", "");
targetBranch = targetBranch.replace("refs/heads/", "");
// Create temporary directory
const tempDir = path.join(process.cwd(), "temp");
fs.mkdirSync(tempDir, { recursive: true });
try {
// Initialize Git and configure remote
child_process.execSync("git init", { cwd: tempDir });
child_process.execSync(`git remote add origin ${repoUrl}`, {
cwd: tempDir,
});
child_process.execSync(
`git fetch --depth=1000 origin ${baseBranch} ${targetBranch}`,
{ cwd: tempDir }
);
// Find the common ancestor (divergence point)
const mergeBase = child_process
.execSync(`git merge-base origin/${baseBranch} origin/${targetBranch}`, {
cwd: tempDir,
})
.toString()
.trim();
// Prepare arguments for excluded files
const excludeArgs = excludeFiles
.map((file) => `":(exclude)${file}"`)
.join(" ");
// Get the diff
const diff = child_process
.execSync(
`git diff ${mergeBase} origin/${targetBranch} --unified=20 -- ${excludeArgs}`,
{ cwd: tempDir }
)
.toString();
return diff;
} catch (error) {
throw new Error(`Error executing git diff: ${error}`);
} finally {
// Clean the temporary directory
fs.rmSync(tempDir, { recursive: true, force: true });
}
}