import { execSync } from 'child_process'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import * as uuid from 'uuid'; /* * Checkout a copy of a git directory, from a specified commit/branch, to a temp location. * This can be used to compare the current state of the directory to another commit. * Ful paths from the git root are preserved in the temp checkout. * * @param localDir A local directory path. Can be absolute or relative. Must be in a git repo. * @param branch A branch or commit directory to use for the checkout. */ export class GitCheckoutTmp { private _gitRoot: string | undefined; private _tmpDir: string | undefined; public checkout(localDir: string, branch: string): void { this._gitRoot = findGitRoot(localDir); const gitRelDir = path .relative(this._gitRoot, localDir) .replace(/\\/g, '/'); if (!gitRelDir) { throw `localDir for checkout cannot be the git root!`; } this._tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), uuid.v4())); try { execSync( `git -C ${this._gitRoot} --work-tree=${this._tmpDir} checkout ${branch} -- ${gitRelDir}`, ); } catch (e) { this.cleanUp(); throw e; } } /* * Location of the temp directory. Root directory of the checked out files. */ public get tmpDir(): string | undefined { return this._tmpDir; } /* * Location of the git root dir. Root directory of the original files. */ public get gitRoot(): string | undefined { return this._gitRoot; } /* * Clean up temp files. This will remove the temp directory if it exists. */ public cleanUp(): void { try { if (this._tmpDir !== undefined) { fs.rmdirSync(this._tmpDir, { recursive: true }); } } catch (e) { console.error(`Failed to clean up tmp dir ${this._tmpDir}: ${e}`); } this._gitRoot = undefined; this._tmpDir = undefined; } } /* * Finds the git root directory for a local path (relative or absolute). */ export function findGitRoot(dir: string): string { return execSync(`git -C ${dir} rev-parse --show-toplevel`).toString().trim(); }