298 lines
9.2 KiB
Plaintext
298 lines
9.2 KiB
Plaintext
|
'use strict';
|
||
|
|
||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||
|
|
||
|
var spawn = require('spawndamnit');
|
||
|
var fs = require('fs');
|
||
|
var path = require('path');
|
||
|
var getPackages = require('@manypkg/get-packages');
|
||
|
var errors = require('@changesets/errors');
|
||
|
var isSubdir = require('is-subdir');
|
||
|
var micromatch = require('micromatch');
|
||
|
|
||
|
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
|
||
|
|
||
|
var spawn__default = /*#__PURE__*/_interopDefault(spawn);
|
||
|
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
||
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
||
|
var isSubdir__default = /*#__PURE__*/_interopDefault(isSubdir);
|
||
|
var micromatch__default = /*#__PURE__*/_interopDefault(micromatch);
|
||
|
|
||
|
async function add(pathToFile, cwd) {
|
||
|
const gitCmd = await spawn__default["default"]("git", ["add", pathToFile], {
|
||
|
cwd
|
||
|
});
|
||
|
|
||
|
if (gitCmd.code !== 0) {
|
||
|
console.log(pathToFile, gitCmd.stderr.toString());
|
||
|
}
|
||
|
|
||
|
return gitCmd.code === 0;
|
||
|
}
|
||
|
async function commit(message, cwd) {
|
||
|
const gitCmd = await spawn__default["default"]("git", ["commit", "-m", message, "--allow-empty"], {
|
||
|
cwd
|
||
|
});
|
||
|
return gitCmd.code === 0;
|
||
|
}
|
||
|
async function getAllTags(cwd) {
|
||
|
const gitCmd = await spawn__default["default"]("git", ["tag"], {
|
||
|
cwd
|
||
|
});
|
||
|
|
||
|
if (gitCmd.code !== 0) {
|
||
|
throw new Error(gitCmd.stderr.toString());
|
||
|
}
|
||
|
|
||
|
const tags = gitCmd.stdout.toString().trim().split("\n");
|
||
|
return new Set(tags);
|
||
|
} // used to create a single tag at a time for the current head only
|
||
|
|
||
|
async function tag(tagStr, cwd) {
|
||
|
// NOTE: it's important we use the -m flag to create annotated tag otherwise 'git push --follow-tags' won't actually push
|
||
|
// the tags
|
||
|
const gitCmd = await spawn__default["default"]("git", ["tag", tagStr, "-m", tagStr], {
|
||
|
cwd
|
||
|
});
|
||
|
return gitCmd.code === 0;
|
||
|
} // Find the commit where we diverged from `ref` at using `git merge-base`
|
||
|
|
||
|
async function getDivergedCommit(cwd, ref) {
|
||
|
const cmd = await spawn__default["default"]("git", ["merge-base", ref, "HEAD"], {
|
||
|
cwd
|
||
|
});
|
||
|
|
||
|
if (cmd.code !== 0) {
|
||
|
throw new Error(`Failed to find where HEAD diverged from ${ref}. Does ${ref} exist?`);
|
||
|
}
|
||
|
|
||
|
return cmd.stdout.toString().trim();
|
||
|
}
|
||
|
/**
|
||
|
* Get the SHAs for the commits that added files, including automatically
|
||
|
* extending a shallow clone if necessary to determine any commits.
|
||
|
* @param gitPaths - Paths to fetch
|
||
|
* @param options - `cwd` and `short`
|
||
|
*/
|
||
|
|
||
|
async function getCommitsThatAddFiles(gitPaths, {
|
||
|
cwd,
|
||
|
short = false
|
||
|
}) {
|
||
|
// Maps gitPath to commit SHA
|
||
|
const map = new Map(); // Paths we haven't completed processing on yet
|
||
|
|
||
|
let remaining = gitPaths;
|
||
|
|
||
|
do {
|
||
|
// Fetch commit information for all paths we don't have yet
|
||
|
const commitInfos = await Promise.all(remaining.map(async gitPath => {
|
||
|
const [commitSha, parentSha] = (await spawn__default["default"]("git", ["log", "--diff-filter=A", "--max-count=1", short ? "--pretty=format:%h:%p" : "--pretty=format:%H:%p", gitPath], {
|
||
|
cwd
|
||
|
})).stdout.toString().split(":");
|
||
|
return {
|
||
|
path: gitPath,
|
||
|
commitSha,
|
||
|
parentSha
|
||
|
};
|
||
|
})); // To collect commits without parents (usually because they're absent from
|
||
|
// a shallow clone).
|
||
|
|
||
|
let commitsWithMissingParents = [];
|
||
|
|
||
|
for (const info of commitInfos) {
|
||
|
if (info.commitSha) {
|
||
|
if (info.parentSha) {
|
||
|
// We have found the parent of the commit that added the file.
|
||
|
// Therefore we know that the commit is legitimate and isn't simply the boundary of a shallow clone.
|
||
|
map.set(info.path, info.commitSha);
|
||
|
} else {
|
||
|
commitsWithMissingParents.push(info);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (commitsWithMissingParents.length === 0) {
|
||
|
break;
|
||
|
} // The commits we've found may be the real commits or they may be the boundary of
|
||
|
// a shallow clone.
|
||
|
// Can we deepen the clone?
|
||
|
|
||
|
|
||
|
if (await isRepoShallow({
|
||
|
cwd
|
||
|
})) {
|
||
|
// Yes.
|
||
|
await deepenCloneBy({
|
||
|
by: 50,
|
||
|
cwd
|
||
|
});
|
||
|
remaining = commitsWithMissingParents.map(p => p.path);
|
||
|
} else {
|
||
|
// It's not a shallow clone, so all the commit SHAs we have are legitimate.
|
||
|
for (const unresolved of commitsWithMissingParents) {
|
||
|
map.set(unresolved.path, unresolved.commitSha);
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
} while (true);
|
||
|
|
||
|
return gitPaths.map(p => map.get(p));
|
||
|
}
|
||
|
async function isRepoShallow({
|
||
|
cwd
|
||
|
}) {
|
||
|
const isShallowRepoOutput = (await spawn__default["default"]("git", ["rev-parse", "--is-shallow-repository"], {
|
||
|
cwd
|
||
|
})).stdout.toString().trim();
|
||
|
|
||
|
if (isShallowRepoOutput === "--is-shallow-repository") {
|
||
|
// We have an old version of Git (<2.15) which doesn't support `rev-parse --is-shallow-repository`
|
||
|
// In that case, we'll test for the existence of .git/shallow.
|
||
|
// Firstly, find the .git folder for the repo; note that this will be relative to the repo dir
|
||
|
const gitDir = (await spawn__default["default"]("git", ["rev-parse", "--git-dir"], {
|
||
|
cwd
|
||
|
})).stdout.toString().trim();
|
||
|
const fullGitDir = path__default["default"].resolve(cwd, gitDir); // Check for the existence of <gitDir>/shallow
|
||
|
|
||
|
return fs__default["default"].existsSync(path__default["default"].join(fullGitDir, "shallow"));
|
||
|
} else {
|
||
|
// We have a newer Git which supports `rev-parse --is-shallow-repository`. We'll use
|
||
|
// the output of that instead of messing with .git/shallow in case that changes in the future.
|
||
|
return isShallowRepoOutput === "true";
|
||
|
}
|
||
|
}
|
||
|
async function deepenCloneBy({
|
||
|
by,
|
||
|
cwd
|
||
|
}) {
|
||
|
await spawn__default["default"]("git", ["fetch", `--deepen=${by}`], {
|
||
|
cwd
|
||
|
});
|
||
|
}
|
||
|
|
||
|
async function getRepoRoot({
|
||
|
cwd
|
||
|
}) {
|
||
|
const {
|
||
|
stdout,
|
||
|
code,
|
||
|
stderr
|
||
|
} = await spawn__default["default"]("git", ["rev-parse", "--show-toplevel"], {
|
||
|
cwd
|
||
|
});
|
||
|
|
||
|
if (code !== 0) {
|
||
|
throw new Error(stderr.toString());
|
||
|
}
|
||
|
|
||
|
return stdout.toString().trim().replace(/\n|\r/g, "");
|
||
|
}
|
||
|
|
||
|
async function getChangedFilesSince({
|
||
|
cwd,
|
||
|
ref,
|
||
|
fullPath = false
|
||
|
}) {
|
||
|
const divergedAt = await getDivergedCommit(cwd, ref); // Now we can find which files we added
|
||
|
|
||
|
const cmd = await spawn__default["default"]("git", ["diff", "--name-only", divergedAt], {
|
||
|
cwd
|
||
|
});
|
||
|
|
||
|
if (cmd.code !== 0) {
|
||
|
throw new Error(`Failed to diff against ${divergedAt}. Is ${divergedAt} a valid ref?`);
|
||
|
}
|
||
|
|
||
|
const files = cmd.stdout.toString().trim().split("\n").filter(a => a);
|
||
|
if (!fullPath) return files;
|
||
|
const repoRoot = await getRepoRoot({
|
||
|
cwd
|
||
|
});
|
||
|
return files.map(file => path__default["default"].resolve(repoRoot, file));
|
||
|
} // below are less generic functions that we use in combination with other things we are doing
|
||
|
|
||
|
async function getChangedChangesetFilesSinceRef({
|
||
|
cwd,
|
||
|
ref
|
||
|
}) {
|
||
|
try {
|
||
|
const divergedAt = await getDivergedCommit(cwd, ref); // Now we can find which files we added
|
||
|
|
||
|
const cmd = await spawn__default["default"]("git", ["diff", "--name-only", "--diff-filter=d", divergedAt], {
|
||
|
cwd
|
||
|
});
|
||
|
let tester = /.changeset\/[^/]+\.md$/;
|
||
|
const files = cmd.stdout.toString().trim().split("\n").filter(file => tester.test(file));
|
||
|
return files;
|
||
|
} catch (err) {
|
||
|
if (err instanceof errors.GitError) return [];
|
||
|
throw err;
|
||
|
}
|
||
|
}
|
||
|
async function getChangedPackagesSinceRef({
|
||
|
cwd,
|
||
|
ref,
|
||
|
changedFilePatterns = ["**"]
|
||
|
}) {
|
||
|
const changedFiles = await getChangedFilesSince({
|
||
|
ref,
|
||
|
cwd,
|
||
|
fullPath: true
|
||
|
});
|
||
|
return [...(await getPackages.getPackages(cwd)).packages] // sort packages by length of dir, so that we can check for subdirs first
|
||
|
.sort((pkgA, pkgB) => pkgB.dir.length - pkgA.dir.length).filter(pkg => {
|
||
|
const changedPackageFiles = [];
|
||
|
|
||
|
for (let i = changedFiles.length - 1; i >= 0; i--) {
|
||
|
const file = changedFiles[i];
|
||
|
|
||
|
if (isSubdir__default["default"](pkg.dir, file)) {
|
||
|
changedFiles.splice(i, 1);
|
||
|
const relativeFile = file.slice(pkg.dir.length + 1);
|
||
|
changedPackageFiles.push(relativeFile);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return changedPackageFiles.length > 0 && micromatch__default["default"](changedPackageFiles, changedFilePatterns).length > 0;
|
||
|
});
|
||
|
}
|
||
|
async function tagExists(tagStr, cwd) {
|
||
|
const gitCmd = await spawn__default["default"]("git", ["tag", "-l", tagStr], {
|
||
|
cwd
|
||
|
});
|
||
|
const output = gitCmd.stdout.toString().trim();
|
||
|
const tagExists = !!output;
|
||
|
return tagExists;
|
||
|
}
|
||
|
async function getCurrentCommitId({
|
||
|
cwd,
|
||
|
short = false
|
||
|
}) {
|
||
|
return (await spawn__default["default"]("git", ["rev-parse", short && "--short", "HEAD"].filter(Boolean), {
|
||
|
cwd
|
||
|
})).stdout.toString().trim();
|
||
|
}
|
||
|
async function remoteTagExists(tagStr) {
|
||
|
const gitCmd = await spawn__default["default"]("git", ["ls-remote", "--tags", "origin", "-l", tagStr]);
|
||
|
const output = gitCmd.stdout.toString().trim();
|
||
|
const tagExists = !!output;
|
||
|
return tagExists;
|
||
|
}
|
||
|
|
||
|
exports.add = add;
|
||
|
exports.commit = commit;
|
||
|
exports.deepenCloneBy = deepenCloneBy;
|
||
|
exports.getAllTags = getAllTags;
|
||
|
exports.getChangedChangesetFilesSinceRef = getChangedChangesetFilesSinceRef;
|
||
|
exports.getChangedFilesSince = getChangedFilesSince;
|
||
|
exports.getChangedPackagesSinceRef = getChangedPackagesSinceRef;
|
||
|
exports.getCommitsThatAddFiles = getCommitsThatAddFiles;
|
||
|
exports.getCurrentCommitId = getCurrentCommitId;
|
||
|
exports.getDivergedCommit = getDivergedCommit;
|
||
|
exports.isRepoShallow = isRepoShallow;
|
||
|
exports.remoteTagExists = remoteTagExists;
|
||
|
exports.tag = tag;
|
||
|
exports.tagExists = tagExists;
|