workspace-tools
Version:
A collection of utilities that are useful in a git-controlled monorepo managed by one of these tools:
288 lines • 13.5 kB
JavaScript
;
//
// Assorted other git utilities
// (could be split into separate files later if desired)
//
Object.defineProperty(exports, "__esModule", { value: true });
exports.listAllTrackedFiles = exports.getDefaultBranch = exports.parseRemoteBranch = exports.getRemoteBranch = exports.getParentBranch = exports.revertLocalChanges = exports.stageAndCommit = exports.commit = exports.stage = exports.init = exports.getFileAddedHash = exports.getCurrentHash = exports.getShortBranchName = exports.getFullBranchRef = exports.getBranchName = exports.getUserEmail = exports.getRecentCommitMessages = exports.getStagedChanges = exports.getChangesBetweenRefs = exports.getBranchChanges = exports.getChanges = exports.getUnstagedChanges = exports.fetchRemoteBranch = exports.fetchRemote = exports.getUntrackedChanges = void 0;
const config_1 = require("./config");
const git_1 = require("./git");
const diffArgs = ["--no-pager", "diff", "--name-only", "--relative"];
function getUntrackedChanges(cwdOrOptions) {
const options = typeof cwdOrOptions === "string" ? { cwd: cwdOrOptions } : cwdOrOptions;
const results = (0, git_1.git)(["ls-files", "--others", "--exclude-standard"], {
description: "Gathering information about untracked changes",
throwOnError: true,
...options,
});
return (0, git_1.processGitOutput)(results, { excludeNodeModules: true });
}
exports.getUntrackedChanges = getUntrackedChanges;
function fetchRemote(remoteOrOptions, cwd) {
const { remote, remoteBranch, options, ...gitOptions } = typeof remoteOrOptions === "string"
? ({ remote: remoteOrOptions, cwd: cwd })
: remoteOrOptions;
if (remoteBranch && !remote) {
throw new Error('Must provide "remote" when using "remoteBranch" option');
}
const fetchArgs = [
"fetch",
"--",
...(remote ? [remote] : []),
...(remoteBranch ? [remoteBranch] : []),
...(options || []),
];
(0, git_1.git)(fetchArgs, {
description: remote
? `Fetching ${remoteBranch ? `branch "${remoteBranch}" from ` : ""}remote "${remote}"`
: "Fetching all remotes",
throwOnError: true,
...gitOptions,
});
}
exports.fetchRemote = fetchRemote;
/**
* Fetch from the given remote and branch. Throws an error on failure.
* @deprecated Use `fetchRemote({ remote, remoteBranch, cwd })`
*/
// TODO: move to fetch.ts
function fetchRemoteBranch(remote, remoteBranch, cwd) {
fetchRemote({ remote, remoteBranch, cwd, throwOnError: true });
}
exports.fetchRemoteBranch = fetchRemoteBranch;
function getUnstagedChanges(cwdOrOptions) {
const options = typeof cwdOrOptions === "string" ? { cwd: cwdOrOptions } : cwdOrOptions;
const results = (0, git_1.git)(diffArgs, {
description: "Gathering information about unstaged changes",
throwOnError: true,
...options,
});
return (0, git_1.processGitOutput)(results, { excludeNodeModules: true });
}
exports.getUnstagedChanges = getUnstagedChanges;
/**
* Gets file paths with changes between the current branch and the given branch.
* Throws an error on failure.
*
* @returns An array of relative file paths that have changed
* @deprecated Use `getBranchChanges({ branch, cwd })`
*/
// TODO: move to getChanges.ts
function getChanges(branch, cwd) {
return getChangesBetweenRefs({ fromRef: branch, cwd, throwOnError: true });
}
exports.getChanges = getChanges;
function getBranchChanges(branchOrOptions, cwd) {
const { branch, ...options } = typeof branchOrOptions === "string" ? { branch: branchOrOptions, cwd: cwd } : branchOrOptions;
return getChangesBetweenRefs({ fromRef: branch, throwOnError: true, ...options });
}
exports.getBranchChanges = getBranchChanges;
function getChangesBetweenRefs(fromRef, toRef, options, pattern, cwd) {
let gitOptions;
if (typeof fromRef === "string") {
gitOptions = { cwd: cwd };
}
else {
({ fromRef, toRef, options, pattern, ...gitOptions } = fromRef);
}
const range = `${fromRef}...${toRef || ""}`;
const results = (0, git_1.git)([...diffArgs, ...(options || []), range, ...(pattern ? ["--", pattern] : [])], {
description: `Gathering information about changes between refs (${range})`,
throwOnError: true,
...gitOptions,
});
return (0, git_1.processGitOutput)(results, { excludeNodeModules: true });
}
exports.getChangesBetweenRefs = getChangesBetweenRefs;
function getStagedChanges(cwdOrOptions) {
const options = typeof cwdOrOptions === "string" ? { cwd: cwdOrOptions } : cwdOrOptions;
const results = (0, git_1.git)([...diffArgs, "--staged"], {
description: "Gathering information about staged changes",
throwOnError: true,
...options,
});
return (0, git_1.processGitOutput)(results, { excludeNodeModules: true });
}
exports.getStagedChanges = getStagedChanges;
function getRecentCommitMessages(branchOrOptions, cwd) {
const { branch, ...options } = typeof branchOrOptions === "string" ? { branch: branchOrOptions, cwd: cwd } : branchOrOptions;
const results = (0, git_1.git)(["log", "--decorate", "--pretty=format:%s", `${branch}..HEAD`], {
description: `Getting recent commit messages for branch "${branch}"`,
...options,
});
return (0, git_1.processGitOutput)(results);
}
exports.getRecentCommitMessages = getRecentCommitMessages;
function getUserEmail(cwdOrOptions) {
const options = typeof cwdOrOptions === "string" ? { cwd: cwdOrOptions } : cwdOrOptions;
return (0, config_1.getConfigValue)({ key: "user.email", ...options });
}
exports.getUserEmail = getUserEmail;
function getBranchName(cwdOrOptions) {
const options = typeof cwdOrOptions === "string" ? { cwd: cwdOrOptions } : cwdOrOptions;
const results = (0, git_1.git)(["rev-parse", "--abbrev-ref", "HEAD"], {
description: "Getting current branch name",
...options,
});
return results.success ? results.stdout : null;
}
exports.getBranchName = getBranchName;
function getFullBranchRef(branchOrOptions, cwd) {
const { branch, ...options } = typeof branchOrOptions === "string" ? { branch: branchOrOptions, cwd: cwd } : branchOrOptions;
const showRefResults = (0, git_1.git)(["show-ref", "--heads", branch], options);
return showRefResults.success ? showRefResults.stdout.split(" ")[1] : null;
}
exports.getFullBranchRef = getFullBranchRef;
function getShortBranchName(refOrOptions, cwd) {
const { fullBranchRef, ...options } = typeof refOrOptions === "string" ? { fullBranchRef: refOrOptions, cwd: cwd } : refOrOptions;
// The original command `git name-rev --name-only` returned unreliable results if multiple
// named refs point to the same commit as the branch.
const showRefResults = (0, git_1.git)(["rev-parse", "--abbrev-ref", fullBranchRef], options);
return showRefResults.success ? showRefResults.stdout || null : null;
}
exports.getShortBranchName = getShortBranchName;
function getCurrentHash(cwdOrOptions) {
const options = typeof cwdOrOptions === "string" ? { cwd: cwdOrOptions } : cwdOrOptions;
const results = (0, git_1.git)(["rev-parse", "HEAD"], {
description: "Getting current git hash",
...options,
});
return results.success ? results.stdout : null;
}
exports.getCurrentHash = getCurrentHash;
function getFileAddedHash(filenameOrOptions, cwd) {
const { filename, ...options } = typeof filenameOrOptions === "string" ? { filename: filenameOrOptions, cwd: cwd } : filenameOrOptions;
const results = (0, git_1.git)(["rev-list", "--max-count=1", "HEAD", filename], options);
return results.success ? results.stdout.trim() : undefined;
}
exports.getFileAddedHash = getFileAddedHash;
function init(cwdOrOptions, _email, _username) {
const { email, username, ...options } = typeof cwdOrOptions === "string" ? { cwd: cwdOrOptions, email: _email, username: _username } : cwdOrOptions;
(0, git_1.git)(["init"], { ...options, throwOnError: true });
if (!(0, config_1.getConfigValue)({ key: "user.name", ...options })) {
if (!username) {
throw new Error("must include a username when initializing git repo");
}
(0, git_1.git)(["config", "user.name", username], options);
}
if (!getUserEmail(options)) {
if (!email) {
throw new Error("must include a email when initializing git repo");
}
(0, git_1.git)(["config", "user.email", email], options);
}
}
exports.init = init;
function stage(patternsOrOptions, cwd) {
const { patterns, ...options } = Array.isArray(patternsOrOptions)
? { patterns: patternsOrOptions, cwd: cwd }
: patternsOrOptions;
for (const pattern of patterns) {
(0, git_1.git)(["add", pattern], { ...options, description: `Staging changes (git add ${pattern})` });
}
}
exports.stage = stage;
function commit(messageOrOptions, _cwd, _options) {
const { message, options, ...gitOptions } = typeof messageOrOptions === "string"
? { message: messageOrOptions, cwd: _cwd, options: _options }
: messageOrOptions;
(0, git_1.git)(["commit", "-m", message, ...(options || [])], {
throwOnError: true,
description: "Committing changes",
...gitOptions,
});
}
exports.commit = commit;
function stageAndCommit(patternsOrOptions, message, cwd, commitOptions) {
const options = Array.isArray(patternsOrOptions)
? { patterns: patternsOrOptions, message: message, cwd: cwd, options: commitOptions }
: patternsOrOptions;
stage(options);
commit(options);
}
exports.stageAndCommit = stageAndCommit;
function revertLocalChanges(cwdOrOptions) {
const options = typeof cwdOrOptions === "string" ? { cwd: cwdOrOptions } : cwdOrOptions;
const stash = `workspace-tools_${new Date().getTime()}`;
if (!(0, git_1.git)(["stash", "push", "-u", "-m", stash], options).success) {
return false;
}
const results = (0, git_1.git)(["stash", "list"], options);
if (results.success) {
const matched = results.stdout
.split(/\n/)
.find((line) => line.includes(stash))
?.match(/^[^:]+/);
if (matched) {
(0, git_1.git)(["stash", "drop", matched[0]], options);
return true;
}
}
return false;
}
exports.revertLocalChanges = revertLocalChanges;
/**
* Attempts to determine the parent branch of the current branch using `git show-branch`.
* @returns The parent branch name if found, null otherwise
* @deprecated Does not appear to be used
*/
function getParentBranch(cwd) {
const branchName = getBranchName({ cwd });
if (!branchName || branchName === "HEAD") {
return null;
}
const showBranchResult = (0, git_1.git)(["show-branch", "-a"], { cwd });
if (showBranchResult.success) {
const showBranchLines = showBranchResult.stdout.split(/\n/);
const parentLine = showBranchLines.find((line) => line.includes("*") && !line.includes(branchName) && !line.includes("publish_"));
const matched = parentLine?.match(/\[(.*)\]/);
return matched ? matched[1] : null;
}
return null;
}
exports.getParentBranch = getParentBranch;
function getRemoteBranch(branchOrOptions, cwd) {
const options = typeof branchOrOptions === "string" ? { branch: branchOrOptions, cwd: cwd } : branchOrOptions;
const results = (0, git_1.git)(["rev-parse", "--abbrev-ref", "--symbolic-full-name", `${options.branch}@\{u\}`], options);
return results.success ? results.stdout.trim() : null;
}
exports.getRemoteBranch = getRemoteBranch;
function parseRemoteBranch(branchOrOptions) {
if (typeof branchOrOptions === "string") {
const branch = branchOrOptions;
const firstSlashPos = branch.indexOf("/", 0);
return {
remote: branch.substring(0, firstSlashPos),
remoteBranch: branch.substring(firstSlashPos + 1),
};
}
const { branch, knownRemotes = ["origin", "upstream"], ...options } = branchOrOptions;
if (!branch.includes("/")) {
return { remote: "", remoteBranch: branch };
}
let remote = knownRemotes.find((remote) => branch.startsWith(`${remote}/`));
if (!remote) {
const remotes = (0, git_1.git)(["remote"], options).stdout.trim().split(/\n/);
remote = remotes.find((remote) => branch.startsWith(`${remote}/`));
}
if (remote) {
return { remote, remoteBranch: branch.slice(remote.length + 1) };
}
return { remote: "", remoteBranch: branch };
}
exports.parseRemoteBranch = parseRemoteBranch;
function getDefaultBranch(cwdOrOptions) {
const options = typeof cwdOrOptions === "string" ? { cwd: cwdOrOptions } : cwdOrOptions;
// Default to the legacy 'master' for backwards compat and old git clients
return (0, config_1.getConfigValue)({ key: "init.defaultBranch", ...options }) || "master";
}
exports.getDefaultBranch = getDefaultBranch;
function listAllTrackedFiles(patternsOrOptions, cwd) {
const { patterns, ...options } = Array.isArray(patternsOrOptions)
? { patterns: patternsOrOptions, cwd: cwd }
: patternsOrOptions;
const results = (0, git_1.git)(["ls-files", ...patterns], { throwOnError: true, ...options });
return (0, git_1.processGitOutput)(results);
}
exports.listAllTrackedFiles = listAllTrackedFiles;
//# sourceMappingURL=gitUtilities.js.map