@atomist/sdm
Version:
Atomist Software Delivery Machine SDK
126 lines • 4.75 kB
JavaScript
;
/*
* Copyright © 2020 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseNameStatusDiff = exports.diffPush = exports.k8sSpecRegExp = exports.k8sSpecGlob = void 0;
const child_process_1 = require("@atomist/automation-client/lib/util/child_process");
/**
* Glob pattern used to identify Kubernetes spec files in a
* repository. It only matched JSON and YAML files at the root of the
* repository.
*/
exports.k8sSpecGlob = "*.@(json|yaml|yml)";
/**
* Regular expression used to identify Kubernetes spec files in a
* repository. It only matched JSON and YAML files at the root of the
* repository.
*/
exports.k8sSpecRegExp = /^[^/]+\.(?:json|ya?ml)$/;
/**
* Determine all changed Kubernetes resource spec files in a push. A
* file is considered a Kubernetes resource spec if it matches
* [[k8sSpecRegExp]]. Changes are returned in commit order, with the
* commit order unchanged from the push event sent by cortex, which it
* typically chronologically with the oldest commit first. Within a
* commit, the changes are sorted first by operation, with deletes
* before applies, and then by path using `sort(localCompare)`.
* Deletes are sorted first because renames are processed as adds and
* deletes and we want to avoid a rename resulting in a resource being
* deleted. If you want to control the order of operations, spread
* the operations across multiple commits in the same push.
*
* @param project project with the changed Kubernetes resource specs.
* @param push git push with changes
* @param tag git commit message tag indicating automated commits that should be ignored
* @param log goal execution progress log
* @return sorted resource spec changes
*/
async function diffPush(project, push, tag, log) {
const changes = [];
const commits = push.commits.filter(c => !c.message.includes(tag));
for (const commit of commits) {
try {
const sha = commit.sha;
const args = ["diff", "-z", "--no-renames", "--name-status", "--diff-filter=ADM", `${sha}~1`, sha, "--"];
const opts = { cwd: project.baseDir };
const diffResult = await child_process_1.execPromise("git", args, opts);
const newChanges = parseNameStatusDiff(sha, diffResult.stdout);
changes.push(...newChanges);
}
catch (e) {
e.message = `Failed to diff commit ${commit.sha}, skipping: ${e.message}`;
log.write(e.message);
}
}
return changes;
}
exports.diffPush = diffPush;
/**
* Convert output of `git diff -z --name-status` to a list of file
* changes. The returned changes are sorted by file path.
*
* @param sha git commit SHA
* @param diff output from git diff command
* @return sorted resource spec changes
*/
function parseNameStatusDiff(sha, diff) {
const changes = [];
const diffContent = diff.trim().replace(/\0$/, "");
if (!diffContent) {
return changes;
}
const fields = diffContent.split("\0");
for (let i = 0; i < fields.length; i += 2) {
const f = fields[i];
if (!f) {
throw new Error(`Empty git diff status`);
}
const path = fields[i + 1];
if (!path) {
throw new Error(`Missing path from git diff status: ${f}`);
}
if (!exports.k8sSpecRegExp.test(path)) {
continue;
}
if (f === "A" || f.startsWith("M")) {
changes.push({ change: "apply", path, sha });
}
else if (f === "D") {
changes.push({ change: "delete", path, sha });
}
else {
throw new Error(`Unexpected git diff status for path '${path}' in commit ${sha}: ${f}`);
}
}
return changes.sort(compareDiff);
}
exports.parseNameStatusDiff = parseNameStatusDiff;
/**
* Sort "delete" changes before "apply". Within each category, sort
* by path.
*/
function compareDiff(a, b) {
if (a.change === b.change) {
return a.path.localeCompare(b.path);
}
else if (a.change === "delete") {
return -1;
}
else {
return 1;
}
}
//# sourceMappingURL=diff.js.map