import { call, noprint, print_no_command } from './process.js';
import { fread, fmkdir } from './file.js';
import { colored } from './utils.js';
export class Git {
    static exe = 'git';
    cwd;
    constructor(cwd) {
        this.cwd = cwd;
    }
    static async init(cwd, print = true) {
        const git = new Git(cwd);
        await git.call(['init'], { print });
        return git;
    }
    static async clone(repo, fpd, shallow = false) {
        await fmkdir(fpd);
        let git = new this(fpd);
        await git.call([
            'clone',
            ...typeof shallow === 'string' ? ['--branch', shallow, '--single-branch'] : [],
            ...shallow ? ['--depth', '1'] : [],
            repo,
            '.'
        ]);
        return git;
    }
    async call(args = [], { color = true, print, on_stderr, throw_code, } = {}) {
        return call(Git.exe, [
            ...color ? [] : [
                '-c', 'color.status=false',
                '-c', 'color.ui=false',
                '-c', 'color.branch=false',
                '-c', 'color.diff=false',
            ],
            ...args,
        ], {
            cwd: this.cwd,
            print,
            throw_code,
            on_stderr
        });
    }
    async get_branch() {
        const head = (await fread(`${this.cwd}.git/HEAD`, { print: false })).trim();
        const branch = head.find('ref: refs/heads/{branch}').branch || head;
        if (!branch)
            throw new Error(`${this.cwd} 仓库 Git.get_branch 无法获取到 branch`);
        return branch;
        // 需要 100+ ms ，性能太差
        // this.branch = (await this.git('branch --show-current')).stdout.trim().strip_ansi()
    }
    /** - print: `true` */
    async get_branches(print = true) {
        return (await this.call(['branch', '-a'], { print })).stdout
            .split_lines()
            .map(l => l.strip_ansi())
            .filter(branch => !branch.includes('origin/HEAD'))
            .map(branch => branch.rm(/^\* /))
            .trim_lines();
    }
    async has_branch(branch) {
        return (await this.get_branches(false))
            .includes(branch);
    }
    /** - last?: `4` */
    async log({ n = 4, graph = false, format, } = {}) {
        if (!graph && !format)
            // 多行 commit message 会被 join 在一起，用空格隔开
            format = ['ct', 'cn', 'H', 's'].map(x => `%${x}`).join('%x00');
        return this.call([
            'log',
            ...graph ? ['--graph'] : [],
            `-${n}`,
            ...format ? [`--pretty=format:${format}`] : [],
        ], { color: !format, print: !format });
    }
    async get_last_commits(n = 100) {
        const { stdout } = await this.log({ n });
        return stdout.split_lines()
            .map(line => {
            const [unixtime, commiter, hash, message] = line.split('\x00');
            return {
                time: new Date(Number(unixtime) * 1000),
                commiter,
                hash,
                message
            };
        });
    }
    /** - short?: `true` */
    async get_last_commit_hash(short = true) {
        const hash = (await fread(`${this.cwd}.git/refs/heads/${await this.get_branch()}`, noprint))
            .trim();
        return short ? hash.slice(0, 6) : hash;
    }
    /** @example form.fetch({ remote: 'origin', args: ['pull/770/head:form-list-fields-value'] }) */
    async fetch({ print = true, remote, args = [] } = {}) {
        let stderrs = [];
        await this.call([
            'fetch',
            remote || '--all',
            '--prune',
            ...args
        ], {
            ...print_stdout,
            on_stderr(chunk) {
                stderrs.push(chunk);
                if (chunk.includes('main'))
                    chunk = chunk.replaceAll('main', 'main'.yellow);
                if (stderrs.length === 1 && chunk.startsWith('来自')) {
                    const lf = chunk.indexOf('\n');
                    chunk = chunk.slice(0, lf).replace('来自', '更新源').blue + chunk.slice(lf);
                }
                process.stdout.write(chunk);
            }
        });
        const stderr = stderrs.join('');
        if (print)
            console.log(colored((stderr ? '更新 ' : '') +
                `${this.cwd.fname.slice(0, -1)} 仓库` +
                (remote ? `源 ${remote} ` : '') +
                (args.length > 0 ? ` ${args.join(' ')} ` : '') +
                (stderr ? '成功' : '无更新'), 'green', Boolean(stderr)));
        return stderr;
    }
    async checkout(branch = 'main') {
        let create = false;
        if (branch !== 'main') {
            const branches = await this.get_branches(false);
            // 无本地及远程对应分支
            if (!(branches.includes(branch) ||
                branches.find(b => b.endsWith(`/${branch}`) && b.startsWith('remotes/'))))
                create = true;
        }
        if (create)
            console.log(`创建分支 ${branch}`);
        await this._checkout(branch, create);
    }
    async _checkout(branch, create, remote_branch) {
        await this.call([
            'checkout',
            ...create ? ['-b'] : [],
            branch,
            ...remote_branch ? [remote_branch] : []
        ], print_no_command);
    }
    /** 基于远程分支创建相同且干净的本地分支 (reset)，用于 ci
        会自动先通过 this.fetch 更新 remote_branch 对应的远程仓库
        - branch: 远程分支名，必须类似 origin/main 这样的格式 */
    async checkout_remote(remote_branch) {
        const [remote, name] = remote_branch.split2('/');
        await this.fetch({ remote });
        const branches = await this.get_branches(false);
        if (!branches.includes(`remotes/${remote_branch}`))
            throw new Error(`远程分支 ${remote_branch} 不存在`);
        // 存在对应的本地分支
        if (branches.includes(name)) {
            await this._checkout(name, false);
            await this.reset(remote_branch);
        }
        else
            await this._checkout(name, true, remote_branch);
    }
    async get_version_info(version) {
        const branch = await this.get_branch();
        const { hash, time, commiter, message } = (await this.get_last_commits(1))[0];
        version ||= branch;
        const timestr = time.to_formal_str();
        return {
            version,
            branch,
            time: timestr,
            hash: hash.slice(0, 6),
            commiter,
            message
        };
    }
    /** 重置分支到最后提交的状态 */
    async reset(remote_branch) {
        console.log(`重置分支到${remote_branch ? ` ${remote_branch}` : '最后提交的状态'}`);
        await this.call([
            'reset',
            '--hard',
            ...remote_branch ? [remote_branch] : []
        ], print_no_command);
    }
}
const print_stdout = { print: { code: false, command: false, stdout: true, stderr: true } };
//# sourceMappingURL=git.js.map