var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import path from "path";
import Execute from "../Execute";
import LocalFile from "../LocalFile";
import logger from "../Logger";
import CaptureSpan from "../Telemetry/CaptureSpan";
import BadDataException from "../../../Types/Exception/BadDataException";
export default class CodeRepositoryUtil {
    static getCurrentCommitHash(data) {
        return this.runGitCommand(data.repoPath, ["rev-parse", "HEAD"]);
    }
    static async addAllChangedFilesToGit(data) {
        await this.runGitCommand(data.repoPath, ["add", "-A"]);
    }
    static async setAuthorIdentity(data) {
        await this.runGitCommand(data.repoPath, [
            "config",
            "--global",
            "user.name",
            data.authorName,
        ]);
        await this.runGitCommand(data.repoPath, [
            "config",
            "--global",
            "user.email",
            data.authorEmail,
        ]);
    }
    static async discardAllChangesOnCurrentBranch(data) {
        await this.runGitCommand(data.repoPath, ["checkout", "."]);
    }
    // returns the folder name of the cloned repository
    static async cloneRepository(data) {
        await this.runGitCommand(data.repoPath, ["clone", data.repoUrl]);
        const normalizedUrl = data.repoUrl.trim().replace(/\/+$/g, "");
        const lastSegment = normalizedUrl.split("/").pop() || normalizedUrl.split(":").pop() || "";
        const folderName = lastSegment.replace(/\.git$/i, "");
        if (!folderName) {
            throw new BadDataException("Unable to determine repository folder name after cloning.");
        }
        return folderName.trim();
    }
    static async pullChanges(data) {
        await this.runGitCommand(data.repoPath, ["pull"]);
    }
    static async createOrCheckoutBranch(data) {
        try {
            await this.runGitCommand(data.repoPath, [
                "rev-parse",
                "--verify",
                data.branchName,
            ]);
            await this.runGitCommand(data.repoPath, ["checkout", data.branchName]);
        }
        catch (error) {
            const branchLogAttributes = {
                repoPath: data.repoPath,
                branchName: data.branchName,
            };
            logger.debug(`Branch ${data.branchName} not found. Creating a new branch instead.`, branchLogAttributes);
            logger.debug(error, branchLogAttributes);
            await this.runGitCommand(data.repoPath, [
                "checkout",
                "-b",
                data.branchName,
            ]);
        }
    }
    static getFileContent(data) {
        const absolutePath = this.resolvePathWithinRepo(data.repoPath, data.filePath);
        return LocalFile.read(absolutePath);
    }
    // discard all changes in the working directory
    static async discardChanges(data) {
        await this.runGitCommand(data.repoPath, ["checkout", "."]);
    }
    static async writeToFile(data) {
        const totalPath = LocalFile.sanitizeFilePath(`${data.repoPath}/${data.filePath}`);
        await LocalFile.write(totalPath, data.content);
    }
    static async createDirectory(data) {
        const totalPath = LocalFile.sanitizeFilePath(`${data.repoPath}/${data.directoryPath}`);
        await LocalFile.makeDirectory(totalPath);
    }
    static async deleteFile(data) {
        const totalPath = LocalFile.sanitizeFilePath(`${data.repoPath}/${data.filePath}`);
        await LocalFile.deleteFile(totalPath);
    }
    static async deleteDirectory(data) {
        const totalPath = LocalFile.sanitizeFilePath(`${data.repoPath}/${data.directoryPath}`);
        logger.debug("Deleting directory: " + totalPath, {
            repoPath: data.repoPath,
            directoryPath: data.directoryPath,
        });
        await LocalFile.deleteDirectory(totalPath);
    }
    static async createBranch(data) {
        const createBranchLogAttributes = {
            repoPath: data.repoPath,
            branchName: data.branchName,
        };
        logger.debug(`Creating git branch '${data.branchName}' in ${path.resolve(data.repoPath)}`, createBranchLogAttributes);
        const stdout = await this.runGitCommand(data.repoPath, [
            "checkout",
            "-b",
            data.branchName,
        ]);
        logger.debug(stdout, createBranchLogAttributes);
    }
    static async checkoutBranch(data) {
        const checkoutLogAttributes = {
            repoPath: data.repoPath,
            branchName: data.branchName,
        };
        logger.debug(`Checking out git branch '${data.branchName}' in ${path.resolve(data.repoPath)}`, checkoutLogAttributes);
        const stdout = await this.runGitCommand(data.repoPath, [
            "checkout",
            data.branchName,
        ]);
        logger.debug(stdout, checkoutLogAttributes);
    }
    static async addFilesToGit(data) {
        const repoRoot = path.resolve(data.repoPath);
        const sanitizedRelativeFilePaths = [];
        for (const inputFilePath of data.filePaths) {
            const normalizedPath = inputFilePath.startsWith("/")
                ? inputFilePath.substring(1)
                : inputFilePath;
            if (normalizedPath.trim() === "") {
                continue;
            }
            const absoluteFilePath = this.resolvePathWithinRepo(data.repoPath, normalizedPath);
            const relativeFilePath = path.relative(repoRoot, absoluteFilePath);
            if (relativeFilePath.trim() === "") {
                continue;
            }
            sanitizedRelativeFilePaths.push(LocalFile.sanitizeFilePath(relativeFilePath));
        }
        const addFilesLogAttributes = {
            repoPath: data.repoPath,
        };
        if (sanitizedRelativeFilePaths.length === 0) {
            logger.debug("git add skipped because no file paths were provided", addFilesLogAttributes);
            return;
        }
        logger.debug(`Adding ${sanitizedRelativeFilePaths.length} file(s) to git in ${path.resolve(data.repoPath)}`, addFilesLogAttributes);
        const stdout = await this.runGitCommand(data.repoPath, [
            "add",
            ...sanitizedRelativeFilePaths,
        ]);
        logger.debug(stdout, addFilesLogAttributes);
    }
    static async setUsername(data) {
        const setUsernameLogAttributes = {
            repoPath: data.repoPath,
        };
        logger.debug(`Setting git user.name in ${path.resolve(data.repoPath)}`, setUsernameLogAttributes);
        const stdout = await this.runGitCommand(data.repoPath, [
            "config",
            "user.name",
            data.username,
        ]);
        logger.debug(stdout, setUsernameLogAttributes);
    }
    static async commitChanges(data) {
        const commitLogAttributes = {
            repoPath: data.repoPath,
        };
        logger.debug("Executing git commit", commitLogAttributes);
        const stdout = await Execute.executeCommandFile({
            command: "git",
            args: ["commit", "-m", data.message],
            cwd: data.repoPath,
        });
        logger.debug(stdout, commitLogAttributes);
    }
    static async getGitCommitHashForFile(data) {
        if (!data.filePath.startsWith("/")) {
            data.filePath = "/" + data.filePath;
        }
        if (!data.repoPath.startsWith("/")) {
            data.repoPath = "/" + data.repoPath;
        }
        const { repoPath, filePath } = data;
        const repoRoot = path.resolve(repoPath);
        const absoluteTarget = this.resolvePathWithinRepo(repoPath, filePath);
        const relativeTarget = path.relative(repoRoot, absoluteTarget);
        const gitArgument = LocalFile.sanitizeFilePath(`./${relativeTarget}`);
        const commitHashLogAttributes = {
            repoPath: data.repoPath,
            filePath: data.filePath,
        };
        logger.debug(`Getting last commit hash for ${gitArgument} in ${repoRoot}`, commitHashLogAttributes);
        const hash = await this.runGitCommand(repoRoot, [
            "log",
            "-1",
            "--pretty=format:%H",
            gitArgument,
        ]);
        logger.debug(hash, commitHashLogAttributes);
        return hash.trim();
    }
    static async listFilesInDirectory(data) {
        if (!data.directoryPath.startsWith("/")) {
            data.directoryPath = "/" + data.directoryPath;
        }
        if (!data.repoPath.startsWith("/")) {
            data.repoPath = "/" + data.repoPath;
        }
        const { directoryPath, repoPath } = data;
        let totalPath = `${repoPath}/${directoryPath}`;
        totalPath = LocalFile.sanitizeFilePath(totalPath); // clean up the path
        const entries = await LocalFile.readDirectory(totalPath);
        return entries.map((entry) => {
            return entry.name;
        });
    }
    static async getFilesInDirectory(data) {
        if (!data.directoryPath.startsWith("/")) {
            data.directoryPath = "/" + data.directoryPath;
        }
        if (!data.repoPath.startsWith("/")) {
            data.repoPath = "/" + data.repoPath;
        }
        const { directoryPath, repoPath } = data;
        let totalPath = `${repoPath}/${directoryPath}`;
        totalPath = LocalFile.sanitizeFilePath(totalPath); // clean up the path
        const files = {};
        const subDirectories = [];
        const entries = await LocalFile.readDirectory(totalPath);
        for (const entry of entries) {
            const fileName = entry.name;
            const filePath = LocalFile.sanitizeFilePath(`${directoryPath}/${fileName}`);
            if (data.ignoreFilesOrDirectories.includes(fileName)) {
                continue;
            }
            if (entry.isDirectory()) {
                subDirectories.push(LocalFile.sanitizeFilePath(`${directoryPath}/${fileName}`));
                continue;
            }
            else if (data.acceptedFileExtensions &&
                data.acceptedFileExtensions.length > 0) {
                let shouldSkip = true;
                for (const fileExtension of data.acceptedFileExtensions) {
                    if (fileName.endsWith(fileExtension)) {
                        shouldSkip = false;
                        break;
                    }
                }
                if (shouldSkip) {
                    continue;
                }
            }
            files[filePath] = {
                fileContent: await this.getFileContent({
                    filePath: LocalFile.sanitizeFilePath(`${directoryPath}/${fileName}`),
                    repoPath,
                }),
                filePath: filePath,
            };
        }
        return {
            files,
            subDirectories: subDirectories,
        };
    }
    static async getFilesInDirectoryRecursive(data) {
        let files = {};
        const { files: filesInDirectory, subDirectories } = await this.getFilesInDirectory({
            directoryPath: data.directoryPath,
            repoPath: data.repoPath,
            acceptedFileExtensions: data.acceptedFileExtensions,
            ignoreFilesOrDirectories: data.ignoreFilesOrDirectories,
        });
        files = Object.assign(Object.assign({}, files), filesInDirectory);
        for (const subDirectory of subDirectories) {
            files = Object.assign(Object.assign({}, files), (await this.getFilesInDirectoryRecursive({
                repoPath: data.repoPath,
                directoryPath: subDirectory,
                acceptedFileExtensions: data.acceptedFileExtensions,
                ignoreFilesOrDirectories: data.ignoreFilesOrDirectories,
            })));
        }
        return files;
    }
    static runGitCommand(repoPath, args) {
        const cwd = path.resolve(repoPath);
        logger.debug(`Executing git command in ${cwd}: git ${args
            .map((arg) => {
            return arg.includes(" ") ? `"${arg}"` : arg;
        })
            .join(" ")}`, { repoPath });
        return Execute.executeCommandFile({
            command: "git",
            args,
            cwd,
        });
    }
    static resolvePathWithinRepo(repoPath, targetPath) {
        const root = path.resolve(repoPath);
        const sanitizedTarget = LocalFile.sanitizeFilePath(targetPath).replace(/^\/+/, "");
        const absoluteTarget = path.resolve(root, sanitizedTarget);
        if (absoluteTarget !== root &&
            !absoluteTarget.startsWith(root + path.sep)) {
            throw new BadDataException("File path is outside the repository");
        }
        return absoluteTarget;
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "getCurrentCommitHash", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "addAllChangedFilesToGit", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "setAuthorIdentity", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "discardAllChangesOnCurrentBranch", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "cloneRepository", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "pullChanges", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "createOrCheckoutBranch", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "getFileContent", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "discardChanges", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "writeToFile", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "createDirectory", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "deleteFile", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "deleteDirectory", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "createBranch", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "checkoutBranch", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "addFilesToGit", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "setUsername", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "commitChanges", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "getGitCommitHashForFile", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "listFilesInDirectory", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "getFilesInDirectory", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], CodeRepositoryUtil, "getFilesInDirectoryRecursive", null);
//# sourceMappingURL=CodeRepository.js.map