"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_test_1 = require("node:test");
const strict_1 = __importDefault(require("node:assert/strict"));
const node_child_process_1 = require("node:child_process");
const fs = __importStar(require("node:fs"));
const os = __importStar(require("node:os"));
const path = __importStar(require("node:path"));
const git_1 = require("../git");
// ─── slugify ──────────────────────────────────────────────────────────────────
(0, node_test_1.describe)('slugify', () => {
    (0, node_test_1.it)('lowercases input', () => {
        strict_1.default.equal((0, git_1.slugify)('FIX LOGIN BUG'), 'fix-login-bug');
    });
    (0, node_test_1.it)('replaces spaces with dashes', () => {
        strict_1.default.equal((0, git_1.slugify)('fix login bug'), 'fix-login-bug');
    });
    (0, node_test_1.it)('removes special characters', () => {
        strict_1.default.equal((0, git_1.slugify)('feat: add @user support!'), 'feat-add-user-support');
    });
    (0, node_test_1.it)('collapses multiple separators into one dash', () => {
        strict_1.default.equal((0, git_1.slugify)('hello---world'), 'hello-world');
    });
    (0, node_test_1.it)('strips leading and trailing dashes', () => {
        strict_1.default.equal((0, git_1.slugify)('---hello---'), 'hello');
    });
    (0, node_test_1.it)('truncates to 50 characters', () => {
        strict_1.default.equal((0, git_1.slugify)('a'.repeat(100)).length, 50);
    });
    (0, node_test_1.it)('handles empty string', () => {
        strict_1.default.equal((0, git_1.slugify)(''), '');
    });
    (0, node_test_1.it)('handles string with only special chars', () => {
        strict_1.default.equal((0, git_1.slugify)('!!!'), '');
    });
});
// ─── isProtectedBranch ────────────────────────────────────────────────────────
(0, node_test_1.describe)('isProtectedBranch', () => {
    (0, node_test_1.it)('recognizes main as protected', () => {
        strict_1.default.equal((0, git_1.isProtectedBranch)('main'), true);
    });
    (0, node_test_1.it)('recognizes master as protected', () => {
        strict_1.default.equal((0, git_1.isProtectedBranch)('master'), true);
    });
    (0, node_test_1.it)('recognizes develop as protected', () => {
        strict_1.default.equal((0, git_1.isProtectedBranch)('develop'), true);
    });
    (0, node_test_1.it)('recognizes production as protected', () => {
        strict_1.default.equal((0, git_1.isProtectedBranch)('production'), true);
    });
    (0, node_test_1.it)('is case-insensitive', () => {
        strict_1.default.equal((0, git_1.isProtectedBranch)('MAIN'), true);
        strict_1.default.equal((0, git_1.isProtectedBranch)('Master'), true);
        strict_1.default.equal((0, git_1.isProtectedBranch)('DEVELOP'), true);
    });
    (0, node_test_1.it)('does not flag feature branches', () => {
        strict_1.default.equal((0, git_1.isProtectedBranch)('feature/login'), false);
        strict_1.default.equal((0, git_1.isProtectedBranch)('abc123/fix-bug'), false);
        strict_1.default.equal((0, git_1.isProtectedBranch)('hotfix/urgent'), false);
    });
});
// ─── Integration tests using temp git repos ───────────────────────────────────
function gitCmd(args, cwd) {
    const result = (0, node_child_process_1.spawnSync)('git', args, { cwd, encoding: 'utf8' });
    if (result.status !== 0) {
        throw new Error(`git ${args.join(' ')} failed: ${result.stderr}`);
    }
}
function initRepo(dir) {
    gitCmd(['init', '-b', 'main'], dir);
    gitCmd(['config', 'user.email', 'test@test.com'], dir);
    gitCmd(['config', 'user.name', 'Test'], dir);
    fs.writeFileSync(path.join(dir, 'README.md'), '# test\n');
    gitCmd(['add', '.'], dir);
    gitCmd(['commit', '-m', 'initial commit'], dir);
}
(0, node_test_1.describe)('getCurrentBranch (integration)', () => {
    let tmpDir;
    let originalCwd;
    (0, node_test_1.beforeEach)(() => {
        originalCwd = process.cwd();
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-git-test-'));
        initRepo(tmpDir);
        process.chdir(tmpDir);
    });
    (0, node_test_1.afterEach)(() => {
        process.chdir(originalCwd);
        fs.rmSync(tmpDir, { recursive: true, force: true });
    });
    (0, node_test_1.it)('returns the current branch name', () => {
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'main');
    });
    (0, node_test_1.it)('returns the new branch after checkout -b', () => {
        gitCmd(['checkout', '-b', 'feature/test'], tmpDir);
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'feature/test');
    });
});
(0, node_test_1.describe)('createBranch with expectedBase (integration)', () => {
    let tmpDir;
    let originalCwd;
    (0, node_test_1.beforeEach)(() => {
        originalCwd = process.cwd();
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-git-test-'));
        initRepo(tmpDir);
        process.chdir(tmpDir);
    });
    (0, node_test_1.afterEach)(() => {
        process.chdir(originalCwd);
        fs.rmSync(tmpDir, { recursive: true, force: true });
    });
    (0, node_test_1.it)('succeeds when on the expected base branch', () => {
        strict_1.default.equal((0, git_1.createBranch)('task123/fix-bug', 'main'), true);
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'task123/fix-bug');
    });
    (0, node_test_1.it)('fails when not on the expected base branch', () => {
        gitCmd(['checkout', '-b', 'some-other-branch'], tmpDir);
        strict_1.default.equal((0, git_1.createBranch)('task123/fix-bug', 'main'), false);
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'some-other-branch');
    });
    (0, node_test_1.it)('works without expectedBase (backward compatible)', () => {
        strict_1.default.equal((0, git_1.createBranch)('task123/fix-bug'), true);
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'task123/fix-bug');
    });
});
(0, node_test_1.describe)('createBranchFromRemote (integration)', () => {
    let tmpDir;
    let bareDir;
    let originalCwd;
    (0, node_test_1.beforeEach)(() => {
        originalCwd = process.cwd();
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-git-test-'));
        bareDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-bare-'));
        gitCmd(['init', '--bare', '-b', 'main'], bareDir);
        initRepo(tmpDir);
        gitCmd(['remote', 'add', 'origin', bareDir], tmpDir);
        gitCmd(['push', 'origin', 'main'], tmpDir);
        process.chdir(tmpDir);
    });
    (0, node_test_1.afterEach)(() => {
        process.chdir(originalCwd);
        fs.rmSync(tmpDir, { recursive: true, force: true });
        fs.rmSync(bareDir, { recursive: true, force: true });
    });
    (0, node_test_1.it)('creates a branch from origin/main at the latest remote commit', () => {
        // Push a new commit to origin from a separate clone
        const cloneDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-clone-'));
        try {
            gitCmd(['clone', bareDir, cloneDir], cloneDir);
            gitCmd(['config', 'user.email', 'other@test.com'], cloneDir);
            gitCmd(['config', 'user.name', 'Other'], cloneDir);
            fs.writeFileSync(path.join(cloneDir, 'new.txt'), 'remote commit');
            gitCmd(['add', '.'], cloneDir);
            gitCmd(['commit', '-m', 'remote commit'], cloneDir);
            gitCmd(['push', 'origin', 'main'], cloneDir);
        }
        finally {
            fs.rmSync(cloneDir, { recursive: true, force: true });
        }
        strict_1.default.equal((0, git_1.createBranchFromRemote)('origin', 'main', 'task123/fix-bug'), true);
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'task123/fix-bug');
        // The new branch should contain the remote commit's file
        strict_1.default.ok(fs.existsSync(path.join(tmpDir, 'new.txt')));
    });
    (0, node_test_1.it)('does not require the local base branch to be checked out', () => {
        gitCmd(['checkout', '-b', 'some-other-branch'], tmpDir);
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'some-other-branch');
        strict_1.default.equal((0, git_1.createBranchFromRemote)('origin', 'main', 'task456/new-feature'), true);
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'task456/new-feature');
    });
    (0, node_test_1.it)('stashes dirty changes before creating the branch', () => {
        fs.writeFileSync(path.join(tmpDir, 'dirty.txt'), 'uncommitted');
        strict_1.default.equal((0, git_1.hasChanges)(), true);
        strict_1.default.equal((0, git_1.createBranchFromRemote)('origin', 'main', 'task789/clean-start'), true);
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'task789/clean-start');
    });
    (0, node_test_1.it)('branches from the remote ref, not the local base branch', () => {
        // Push a new commit to origin from a separate clone
        const cloneDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-clone-'));
        try {
            gitCmd(['clone', bareDir, cloneDir], cloneDir);
            gitCmd(['config', 'user.email', 'other@test.com'], cloneDir);
            gitCmd(['config', 'user.name', 'Other'], cloneDir);
            fs.writeFileSync(path.join(cloneDir, 'remote-only.txt'), 'only on remote');
            gitCmd(['add', '.'], cloneDir);
            gitCmd(['commit', '-m', 'remote-only commit'], cloneDir);
            gitCmd(['push', 'origin', 'main'], cloneDir);
        }
        finally {
            fs.rmSync(cloneDir, { recursive: true, force: true });
        }
        // Local main is behind origin/main — don't pull
        strict_1.default.equal((0, git_1.createBranchFromRemote)('origin', 'main', 'task999/from-remote'), true);
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'task999/from-remote');
        // Branch should have the remote commit even though local main doesn't
        strict_1.default.ok(fs.existsSync(path.join(tmpDir, 'remote-only.txt')));
    });
    (0, node_test_1.it)('fails when the remote base branch does not exist', () => {
        strict_1.default.equal((0, git_1.createBranchFromRemote)('origin', 'nonexistent', 'task000/bad'), false);
    });
    (0, node_test_1.it)('falls back to checking out existing branch when checkout -b fails (pending task follow-up)', () => {
        // Simulate the pending-task scenario: branch already exists on the remote
        // because a previous run created it. A follow-up comment triggers another run
        // which calls createBranchFromRemote again — checkout -b fails because
        // the branch already exists, so it should fallback to fetchAndCheckoutBranch.
        gitCmd(['checkout', '-b', 'task123/existing-branch'], tmpDir);
        fs.writeFileSync(path.join(tmpDir, 'work.txt'), 'previous work');
        gitCmd(['add', '.'], tmpDir);
        gitCmd(['commit', '-m', 'work on existing branch'], tmpDir);
        gitCmd(['push', 'origin', 'task123/existing-branch'], tmpDir);
        // Switch back to main so we're not already on the target branch
        gitCmd(['checkout', 'main'], tmpDir);
        // Now createBranchFromRemote should fail on checkout -b (branch exists)
        // but succeed by falling back to checking out the existing branch
        strict_1.default.equal((0, git_1.createBranchFromRemote)('origin', 'main', 'task123/existing-branch'), true);
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'task123/existing-branch');
        // Should have the file from the existing branch
        strict_1.default.ok(fs.existsSync(path.join(tmpDir, 'work.txt')));
    });
    (0, node_test_1.it)('falls back and picks up new remote commits on existing branch', () => {
        // Create the branch, push it, then switch away — keep local branch alive
        // so checkout -b will fail and trigger the fallback path
        gitCmd(['checkout', '-b', 'task456/followup'], tmpDir);
        fs.writeFileSync(path.join(tmpDir, 'v1.txt'), 'first version');
        gitCmd(['add', '.'], tmpDir);
        gitCmd(['commit', '-m', 'v1'], tmpDir);
        gitCmd(['push', 'origin', 'task456/followup'], tmpDir);
        gitCmd(['checkout', 'main'], tmpDir);
        // Push a new commit to that branch from a separate clone
        const cloneDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-clone-'));
        try {
            gitCmd(['clone', bareDir, cloneDir], cloneDir);
            gitCmd(['config', 'user.email', 'other@test.com'], cloneDir);
            gitCmd(['config', 'user.name', 'Other'], cloneDir);
            gitCmd(['checkout', 'task456/followup'], cloneDir);
            fs.writeFileSync(path.join(cloneDir, 'v2.txt'), 'second version');
            gitCmd(['add', '.'], cloneDir);
            gitCmd(['commit', '-m', 'v2'], cloneDir);
            gitCmd(['push', 'origin', 'task456/followup'], cloneDir);
        }
        finally {
            fs.rmSync(cloneDir, { recursive: true, force: true });
        }
        // checkout -b fails (branch exists locally), fallback checks out + pulls
        strict_1.default.equal((0, git_1.createBranchFromRemote)('origin', 'main', 'task456/followup'), true);
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'task456/followup');
        // Should have both the original and the new remote commit's files
        strict_1.default.ok(fs.existsSync(path.join(tmpDir, 'v1.txt')));
        strict_1.default.ok(fs.existsSync(path.join(tmpDir, 'v2.txt')));
    });
});
(0, node_test_1.describe)('commit with expectedBranch (integration)', () => {
    let tmpDir;
    let originalCwd;
    (0, node_test_1.beforeEach)(() => {
        originalCwd = process.cwd();
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-git-test-'));
        initRepo(tmpDir);
        process.chdir(tmpDir);
    });
    (0, node_test_1.afterEach)(() => {
        process.chdir(originalCwd);
        fs.rmSync(tmpDir, { recursive: true, force: true });
    });
    (0, node_test_1.it)('succeeds when on the expected branch', () => {
        gitCmd(['checkout', '-b', 'task123/fix-bug'], tmpDir);
        fs.writeFileSync(path.join(tmpDir, 'file.txt'), 'content');
        strict_1.default.equal((0, git_1.addAll)(), true);
        strict_1.default.equal((0, git_1.commit)('test commit', 'task123/fix-bug'), true);
    });
    (0, node_test_1.it)('fails when on a different branch than expected', () => {
        gitCmd(['checkout', '-b', 'task123/fix-bug'], tmpDir);
        gitCmd(['checkout', '-b', 'wrong-branch'], tmpDir);
        fs.writeFileSync(path.join(tmpDir, 'file.txt'), 'content');
        strict_1.default.equal((0, git_1.addAll)(), true);
        strict_1.default.equal((0, git_1.commit)('test commit', 'task123/fix-bug'), false);
    });
    (0, node_test_1.it)('refuses to commit when expectedBranch is a protected branch', () => {
        fs.writeFileSync(path.join(tmpDir, 'file.txt'), 'content');
        strict_1.default.equal((0, git_1.addAll)(), true);
        strict_1.default.equal((0, git_1.commit)('test commit', 'main'), false);
    });
    (0, node_test_1.it)('works without expectedBranch (backward compatible)', () => {
        fs.writeFileSync(path.join(tmpDir, 'file.txt'), 'content');
        strict_1.default.equal((0, git_1.addAll)(), true);
        strict_1.default.equal((0, git_1.commit)('test commit'), true);
    });
});
(0, node_test_1.describe)('push validation (integration)', () => {
    let tmpDir;
    let bareDir;
    let originalCwd;
    (0, node_test_1.beforeEach)(() => {
        originalCwd = process.cwd();
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-git-test-'));
        bareDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-bare-'));
        gitCmd(['init', '--bare', '-b', 'main'], bareDir);
        initRepo(tmpDir);
        gitCmd(['remote', 'add', 'origin', bareDir], tmpDir);
        gitCmd(['push', 'origin', 'main'], tmpDir);
        process.chdir(tmpDir);
    });
    (0, node_test_1.afterEach)(() => {
        process.chdir(originalCwd);
        fs.rmSync(tmpDir, { recursive: true, force: true });
        fs.rmSync(bareDir, { recursive: true, force: true });
    });
    (0, node_test_1.it)('refuses to push to a protected branch', () => {
        strict_1.default.equal((0, git_1.push)('origin', 'main'), false);
    });
    (0, node_test_1.it)('refuses to push when current branch does not match target', () => {
        gitCmd(['checkout', '-b', 'task123/fix-bug'], tmpDir);
        strict_1.default.equal((0, git_1.push)('origin', 'task456/other-task'), false);
    });
    (0, node_test_1.it)('succeeds when current branch matches target and is not protected', () => {
        gitCmd(['checkout', '-b', 'task123/fix-bug'], tmpDir);
        fs.writeFileSync(path.join(tmpDir, 'file.txt'), 'content');
        gitCmd(['add', '.'], tmpDir);
        gitCmd(['commit', '-m', 'add file'], tmpDir);
        strict_1.default.equal((0, git_1.push)('origin', 'task123/fix-bug'), true);
    });
    (0, node_test_1.it)('uses explicit HEAD refspec so stale local branches are not pushed', () => {
        gitCmd(['checkout', '-b', 'task123/fix-bug'], tmpDir);
        fs.writeFileSync(path.join(tmpDir, 'file.txt'), 'v1');
        gitCmd(['add', '.'], tmpDir);
        gitCmd(['commit', '-m', 'v1'], tmpDir);
        strict_1.default.equal((0, git_1.push)('origin', 'task123/fix-bug'), true);
        fs.writeFileSync(path.join(tmpDir, 'file.txt'), 'v2');
        gitCmd(['add', '.'], tmpDir);
        gitCmd(['commit', '-m', 'v2'], tmpDir);
        strict_1.default.equal((0, git_1.push)('origin', 'task123/fix-bug'), true);
        const log = (0, node_child_process_1.spawnSync)('git', ['log', '--oneline', 'origin/task123/fix-bug'], {
            cwd: tmpDir,
            encoding: 'utf8',
        });
        strict_1.default.ok(log.stdout.includes('v2'));
    });
});
(0, node_test_1.describe)('stashChanges (integration)', () => {
    let tmpDir;
    let originalCwd;
    (0, node_test_1.beforeEach)(() => {
        originalCwd = process.cwd();
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-git-test-'));
        initRepo(tmpDir);
        process.chdir(tmpDir);
    });
    (0, node_test_1.afterEach)(() => {
        process.chdir(originalCwd);
        fs.rmSync(tmpDir, { recursive: true, force: true });
    });
    (0, node_test_1.it)('returns true and does nothing when working tree is clean', () => {
        strict_1.default.equal((0, git_1.hasChanges)(), false);
        strict_1.default.equal((0, git_1.stashChanges)(), true);
        strict_1.default.equal((0, git_1.hasChanges)(), false);
    });
    (0, node_test_1.it)('stashes uncommitted changes so working tree is clean afterward', () => {
        fs.writeFileSync(path.join(tmpDir, 'dirty.txt'), 'uncommitted');
        strict_1.default.equal((0, git_1.hasChanges)(), true);
        strict_1.default.equal((0, git_1.stashChanges)(), true);
        strict_1.default.equal((0, git_1.hasChanges)(), false);
    });
    (0, node_test_1.it)('stashes untracked files (using -u flag)', () => {
        fs.writeFileSync(path.join(tmpDir, 'untracked.txt'), 'new file');
        strict_1.default.equal((0, git_1.hasChanges)(), true);
        strict_1.default.equal((0, git_1.stashChanges)(), true);
        strict_1.default.equal((0, git_1.hasChanges)(), false);
    });
});
(0, node_test_1.describe)('fetchAndCheckout with dirty working tree (integration)', () => {
    let tmpDir;
    let bareDir;
    let originalCwd;
    (0, node_test_1.beforeEach)(() => {
        originalCwd = process.cwd();
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-git-test-'));
        bareDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-bare-'));
        gitCmd(['init', '--bare', '-b', 'main'], bareDir);
        initRepo(tmpDir);
        gitCmd(['remote', 'add', 'origin', bareDir], tmpDir);
        gitCmd(['push', 'origin', 'main'], tmpDir);
        process.chdir(tmpDir);
    });
    (0, node_test_1.afterEach)(() => {
        process.chdir(originalCwd);
        fs.rmSync(tmpDir, { recursive: true, force: true });
        fs.rmSync(bareDir, { recursive: true, force: true });
    });
    (0, node_test_1.it)('succeeds even when there are uncommitted changes (stashes them)', () => {
        gitCmd(['checkout', '-b', 'feature/dirty'], tmpDir);
        fs.writeFileSync(path.join(tmpDir, 'leftover.txt'), 'from previous run');
        strict_1.default.equal((0, git_1.hasChanges)(), true);
        strict_1.default.equal((0, git_1.fetchAndCheckout)('origin', 'main'), true);
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'main');
    });
    (0, node_test_1.it)('succeeds even when there are untracked files (stashes them)', () => {
        fs.writeFileSync(path.join(tmpDir, 'untracked.txt'), 'untracked');
        strict_1.default.equal((0, git_1.hasChanges)(), true);
        strict_1.default.equal((0, git_1.fetchAndCheckout)('origin', 'main'), true);
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'main');
    });
});
(0, node_test_1.describe)('fetchAndCheckout sync verification (integration)', () => {
    let tmpDir;
    let bareDir;
    let originalCwd;
    (0, node_test_1.beforeEach)(() => {
        originalCwd = process.cwd();
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-git-test-'));
        bareDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-bare-'));
        gitCmd(['init', '--bare', '-b', 'main'], bareDir);
        initRepo(tmpDir);
        gitCmd(['remote', 'add', 'origin', bareDir], tmpDir);
        gitCmd(['push', 'origin', 'main'], tmpDir);
        process.chdir(tmpDir);
    });
    (0, node_test_1.afterEach)(() => {
        process.chdir(originalCwd);
        fs.rmSync(tmpDir, { recursive: true, force: true });
        fs.rmSync(bareDir, { recursive: true, force: true });
    });
    (0, node_test_1.it)('succeeds when local main matches origin/main', () => {
        gitCmd(['checkout', '-b', 'feature/test'], tmpDir);
        strict_1.default.equal((0, git_1.fetchAndCheckout)('origin', 'main'), true);
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'main');
    });
    (0, node_test_1.it)('succeeds when origin has new commits (fast-forward)', () => {
        const cloneDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-clone-'));
        try {
            gitCmd(['clone', bareDir, cloneDir], cloneDir);
            gitCmd(['config', 'user.email', 'other@test.com'], cloneDir);
            gitCmd(['config', 'user.name', 'Other'], cloneDir);
            fs.writeFileSync(path.join(cloneDir, 'new.txt'), 'from clone');
            gitCmd(['add', '.'], cloneDir);
            gitCmd(['commit', '-m', 'remote commit'], cloneDir);
            gitCmd(['push', 'origin', 'main'], cloneDir);
        }
        finally {
            fs.rmSync(cloneDir, { recursive: true, force: true });
        }
        gitCmd(['checkout', '-b', 'feature/test'], tmpDir);
        strict_1.default.equal((0, git_1.fetchAndCheckout)('origin', 'main'), true);
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'main');
    });
    (0, node_test_1.it)('fails when local main has diverged from origin/main', () => {
        const cloneDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-clone-'));
        try {
            gitCmd(['clone', bareDir, cloneDir], cloneDir);
            gitCmd(['config', 'user.email', 'other@test.com'], cloneDir);
            gitCmd(['config', 'user.name', 'Other'], cloneDir);
            fs.writeFileSync(path.join(cloneDir, 'remote.txt'), 'from clone');
            gitCmd(['add', '.'], cloneDir);
            gitCmd(['commit', '-m', 'remote commit'], cloneDir);
            gitCmd(['push', 'origin', 'main'], cloneDir);
        }
        finally {
            fs.rmSync(cloneDir, { recursive: true, force: true });
        }
        fs.writeFileSync(path.join(tmpDir, 'local.txt'), 'local only');
        gitCmd(['add', '.'], tmpDir);
        gitCmd(['commit', '-m', 'local commit'], tmpDir);
        // pull will create a merge commit, making local ahead of origin/main
        strict_1.default.equal((0, git_1.fetchAndCheckout)('origin', 'main'), false);
    });
});
// ─── checkConflictsWithBase (integration) ─────────────────────────────────────
(0, node_test_1.describe)('checkConflictsWithBase (integration)', () => {
    let tmpDir;
    let bareDir;
    let originalCwd;
    (0, node_test_1.beforeEach)(() => {
        originalCwd = process.cwd();
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-git-test-'));
        bareDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-bare-'));
        gitCmd(['init', '--bare', '-b', 'main'], bareDir);
        initRepo(tmpDir);
        gitCmd(['remote', 'add', 'origin', bareDir], tmpDir);
        gitCmd(['push', 'origin', 'main'], tmpDir);
        process.chdir(tmpDir);
    });
    (0, node_test_1.afterEach)(() => {
        process.chdir(originalCwd);
        fs.rmSync(tmpDir, { recursive: true, force: true });
        fs.rmSync(bareDir, { recursive: true, force: true });
    });
    (0, node_test_1.it)('reports clean when branch is up to date with base', () => {
        gitCmd(['checkout', '-b', 'feature/test'], tmpDir);
        fs.writeFileSync(path.join(tmpDir, 'feature.txt'), 'new file');
        gitCmd(['add', '.'], tmpDir);
        gitCmd(['commit', '-m', 'feature commit'], tmpDir);
        gitCmd(['fetch', 'origin'], tmpDir);
        const result = (0, git_1.checkConflictsWithBase)('origin', 'main');
        strict_1.default.equal(result.clean, true);
        strict_1.default.equal(result.behindCommits, 0);
        strict_1.default.deepEqual(result.conflictFiles, []);
    });
    (0, node_test_1.it)('reports clean when base has new non-conflicting commits', () => {
        gitCmd(['checkout', '-b', 'feature/test'], tmpDir);
        fs.writeFileSync(path.join(tmpDir, 'feature.txt'), 'new file');
        gitCmd(['add', '.'], tmpDir);
        gitCmd(['commit', '-m', 'feature commit'], tmpDir);
        // Add a non-conflicting commit to main via bare repo
        const cloneDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-clone-'));
        try {
            gitCmd(['clone', bareDir, cloneDir], cloneDir);
            gitCmd(['config', 'user.email', 'other@test.com'], cloneDir);
            gitCmd(['config', 'user.name', 'Other'], cloneDir);
            fs.writeFileSync(path.join(cloneDir, 'other.txt'), 'no conflict');
            gitCmd(['add', '.'], cloneDir);
            gitCmd(['commit', '-m', 'main commit'], cloneDir);
            gitCmd(['push', 'origin', 'main'], cloneDir);
        }
        finally {
            fs.rmSync(cloneDir, { recursive: true, force: true });
        }
        gitCmd(['fetch', 'origin'], tmpDir);
        const result = (0, git_1.checkConflictsWithBase)('origin', 'main');
        strict_1.default.equal(result.clean, true);
        strict_1.default.ok(result.behindCommits > 0);
        strict_1.default.deepEqual(result.conflictFiles, []);
    });
    (0, node_test_1.it)('detects conflicts when base and branch modify the same file', () => {
        gitCmd(['checkout', '-b', 'feature/test'], tmpDir);
        fs.writeFileSync(path.join(tmpDir, 'README.md'), '# feature change\n');
        gitCmd(['add', '.'], tmpDir);
        gitCmd(['commit', '-m', 'feature commit'], tmpDir);
        const cloneDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-clone-'));
        try {
            gitCmd(['clone', bareDir, cloneDir], cloneDir);
            gitCmd(['config', 'user.email', 'other@test.com'], cloneDir);
            gitCmd(['config', 'user.name', 'Other'], cloneDir);
            fs.writeFileSync(path.join(cloneDir, 'README.md'), '# main change\n');
            gitCmd(['add', '.'], cloneDir);
            gitCmd(['commit', '-m', 'main commit'], cloneDir);
            gitCmd(['push', 'origin', 'main'], cloneDir);
        }
        finally {
            fs.rmSync(cloneDir, { recursive: true, force: true });
        }
        gitCmd(['fetch', 'origin'], tmpDir);
        const result = (0, git_1.checkConflictsWithBase)('origin', 'main');
        strict_1.default.equal(result.clean, false);
        strict_1.default.ok(result.conflictFiles.includes('README.md'));
    });
    (0, node_test_1.it)('leaves working tree clean after conflict detection (aborts trial merge)', () => {
        gitCmd(['checkout', '-b', 'feature/test'], tmpDir);
        fs.writeFileSync(path.join(tmpDir, 'README.md'), '# feature\n');
        gitCmd(['add', '.'], tmpDir);
        gitCmd(['commit', '-m', 'feature'], tmpDir);
        const cloneDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-clone-'));
        try {
            gitCmd(['clone', bareDir, cloneDir], cloneDir);
            gitCmd(['config', 'user.email', 'other@test.com'], cloneDir);
            gitCmd(['config', 'user.name', 'Other'], cloneDir);
            fs.writeFileSync(path.join(cloneDir, 'README.md'), '# main\n');
            gitCmd(['add', '.'], cloneDir);
            gitCmd(['commit', '-m', 'main'], cloneDir);
            gitCmd(['push', 'origin', 'main'], cloneDir);
        }
        finally {
            fs.rmSync(cloneDir, { recursive: true, force: true });
        }
        gitCmd(['fetch', 'origin'], tmpDir);
        (0, git_1.checkConflictsWithBase)('origin', 'main');
        // Should be back on feature branch with no merge in progress
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'feature/test');
        strict_1.default.equal((0, git_1.hasChanges)(), false);
    });
});
// ─── mergeBaseBranch + commitMerge (integration) ─────────────────────────────
(0, node_test_1.describe)('mergeBaseBranch and commitMerge (integration)', () => {
    let tmpDir;
    let bareDir;
    let originalCwd;
    (0, node_test_1.beforeEach)(() => {
        originalCwd = process.cwd();
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-git-test-'));
        bareDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-bare-'));
        gitCmd(['init', '--bare', '-b', 'main'], bareDir);
        initRepo(tmpDir);
        gitCmd(['remote', 'add', 'origin', bareDir], tmpDir);
        gitCmd(['push', 'origin', 'main'], tmpDir);
        process.chdir(tmpDir);
    });
    (0, node_test_1.afterEach)(() => {
        process.chdir(originalCwd);
        fs.rmSync(tmpDir, { recursive: true, force: true });
        fs.rmSync(bareDir, { recursive: true, force: true });
    });
    (0, node_test_1.it)('merges cleanly when there are no conflicts', () => {
        gitCmd(['checkout', '-b', 'feature/test'], tmpDir);
        fs.writeFileSync(path.join(tmpDir, 'feature.txt'), 'new file');
        gitCmd(['add', '.'], tmpDir);
        gitCmd(['commit', '-m', 'feature commit'], tmpDir);
        const cloneDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-clone-'));
        try {
            gitCmd(['clone', bareDir, cloneDir], cloneDir);
            gitCmd(['config', 'user.email', 'other@test.com'], cloneDir);
            gitCmd(['config', 'user.name', 'Other'], cloneDir);
            fs.writeFileSync(path.join(cloneDir, 'other.txt'), 'no conflict');
            gitCmd(['add', '.'], cloneDir);
            gitCmd(['commit', '-m', 'main commit'], cloneDir);
            gitCmd(['push', 'origin', 'main'], cloneDir);
        }
        finally {
            fs.rmSync(cloneDir, { recursive: true, force: true });
        }
        gitCmd(['fetch', 'origin'], tmpDir);
        strict_1.default.equal((0, git_1.mergeBaseBranch)('origin', 'main'), true);
        strict_1.default.ok(fs.existsSync(path.join(tmpDir, 'other.txt')));
    });
    (0, node_test_1.it)('returns false when there are conflicts, and abortMerge restores state', () => {
        gitCmd(['checkout', '-b', 'feature/test'], tmpDir);
        fs.writeFileSync(path.join(tmpDir, 'README.md'), '# feature\n');
        gitCmd(['add', '.'], tmpDir);
        gitCmd(['commit', '-m', 'feature'], tmpDir);
        const cloneDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-clone-'));
        try {
            gitCmd(['clone', bareDir, cloneDir], cloneDir);
            gitCmd(['config', 'user.email', 'other@test.com'], cloneDir);
            gitCmd(['config', 'user.name', 'Other'], cloneDir);
            fs.writeFileSync(path.join(cloneDir, 'README.md'), '# main\n');
            gitCmd(['add', '.'], cloneDir);
            gitCmd(['commit', '-m', 'main'], cloneDir);
            gitCmd(['push', 'origin', 'main'], cloneDir);
        }
        finally {
            fs.rmSync(cloneDir, { recursive: true, force: true });
        }
        gitCmd(['fetch', 'origin'], tmpDir);
        strict_1.default.equal((0, git_1.mergeBaseBranch)('origin', 'main'), false);
        (0, git_1.abortMerge)();
        strict_1.default.equal((0, git_1.getCurrentBranch)(), 'feature/test');
        strict_1.default.equal((0, git_1.hasChanges)(), false);
    });
    (0, node_test_1.it)('commitMerge completes a conflicted merge after manual resolution', () => {
        gitCmd(['checkout', '-b', 'feature/test'], tmpDir);
        fs.writeFileSync(path.join(tmpDir, 'README.md'), '# feature\n');
        gitCmd(['add', '.'], tmpDir);
        gitCmd(['commit', '-m', 'feature'], tmpDir);
        const cloneDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-clone-'));
        try {
            gitCmd(['clone', bareDir, cloneDir], cloneDir);
            gitCmd(['config', 'user.email', 'other@test.com'], cloneDir);
            gitCmd(['config', 'user.name', 'Other'], cloneDir);
            fs.writeFileSync(path.join(cloneDir, 'README.md'), '# main\n');
            gitCmd(['add', '.'], cloneDir);
            gitCmd(['commit', '-m', 'main'], cloneDir);
            gitCmd(['push', 'origin', 'main'], cloneDir);
        }
        finally {
            fs.rmSync(cloneDir, { recursive: true, force: true });
        }
        gitCmd(['fetch', 'origin'], tmpDir);
        (0, git_1.mergeBaseBranch)('origin', 'main');
        // Manually resolve the conflict
        fs.writeFileSync(path.join(tmpDir, 'README.md'), '# merged\n');
        strict_1.default.equal((0, git_1.commitMerge)('Merge main into feature'), true);
        strict_1.default.equal((0, git_1.hasChanges)(), false);
    });
});
//# sourceMappingURL=git.test.js.map