"use strict";
/*
 * 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