spa/.claude/skills/thread-manager/dist/git/git-integration.js

190 lines
6.1 KiB
JavaScript

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GitIntegration = void 0;
const simple_git_1 = __importDefault(require("simple-git"));
const path_1 = __importDefault(require("path"));
const fs_extra_1 = __importDefault(require("fs-extra"));
class GitIntegration {
git;
rootDir;
constructor(rootDir) {
this.rootDir = rootDir || process.cwd();
this.git = (0, simple_git_1.default)(this.rootDir);
}
async isGitRepo() {
try {
return await this.git.checkIsRepo();
}
catch (e) {
return false;
}
}
async getCurrentCommit() {
try {
const log = await this.git.log({ maxCount: 1 });
return log.latest?.hash;
}
catch (e) {
return undefined;
}
}
async getUnstagedDiff(filePath) {
try {
const options = filePath ? [filePath] : [];
return await this.git.diff(options);
}
catch (e) {
console.error('Error getting unstaged diff:', e);
return '';
}
}
async getStagedDiff(filePath) {
try {
const options = ['--cached'];
if (filePath)
options.push(filePath);
return await this.git.diff(options);
}
catch (e) {
console.error('Error getting staged diff:', e);
return '';
}
}
/**
* Get stats for a specific file based on its current state on disk vs HEAD
* This includes both staged and unstaged changes.
*/
async getFileStats(filePath) {
try {
// Use git diff --numstat HEAD -- filePath
// This compares working directory (and stage) against HEAD
const raw = await this.git.raw(['diff', '--numstat', 'HEAD', '--', filePath]);
if (!raw.trim()) {
// If no output, maybe it's a new untracked file?
const status = await this.git.status();
const isUntracked = status.not_added.includes(filePath);
if (isUntracked) {
// For untracked files, count all lines as added
try {
const content = await fs_extra_1.default.readFile(path_1.default.resolve(this.rootDir, filePath), 'utf-8');
const lines = content.split('\n').length;
return { added: lines, deleted: 0, changeType: 'added' };
}
catch (e) {
return { added: 0, deleted: 0, changeType: 'modified' }; // Should not happen if file exists
}
}
return { added: 0, deleted: 0, changeType: 'modified' };
}
const parts = raw.trim().split(/\s+/);
if (parts.length >= 2) {
const added = parseInt(parts[0], 10) || 0;
const deleted = parseInt(parts[1], 10) || 0;
// Determine change type
// This is a simplification. 'added' usually means new file, 'deleted' means file removed.
// git diff --numstat doesn't explicitly say 'new file'.
// We can check git status for more precision if needed.
let changeType = 'modified';
// Check status for better type determination
const status = await this.git.status();
if (status.created.includes(filePath) || status.not_added.includes(filePath)) {
changeType = 'added';
}
else if (status.deleted.includes(filePath)) {
changeType = 'deleted';
}
return { added, deleted, changeType };
}
return { added: 0, deleted: 0, changeType: 'modified' };
}
catch (e) {
console.error(`Error getting file stats for ${filePath}:`, e);
return { added: 0, deleted: 0, changeType: 'modified' };
}
}
/**
* Parse a unified diff string to count lines (fallback if numstat isn't enough)
*/
parseDiff(diff) {
let added = 0;
let deleted = 0;
const lines = diff.split('\n');
for (const line of lines) {
if (line.startsWith('+++') || line.startsWith('---'))
continue;
if (line.startsWith('+'))
added++;
if (line.startsWith('-'))
deleted++;
}
return { added, deleted };
}
/**
* Get current branch name
*/
async getCurrentBranch() {
try {
const result = await this.git.branch();
return result.current;
}
catch (e) {
return null;
}
}
/**
* Create and checkout a new branch
*/
async createAndCheckoutBranch(branchName) {
try {
await this.git.checkoutLocalBranch(branchName);
return true;
}
catch (e) {
console.error('Error creating branch:', e);
return false;
}
}
/**
* Checkout an existing branch
*/
async checkoutBranch(branchName) {
try {
await this.git.checkout(branchName);
return true;
}
catch (e) {
console.error('Error checking out branch:', e);
return false;
}
}
/**
* Check if branch exists
*/
async branchExists(branchName) {
try {
const branches = await this.git.branch();
return branches.all.includes(branchName);
}
catch (e) {
return false;
}
}
/**
* Delete a branch
*/
async deleteBranch(branchName, force = false) {
try {
await this.git.branch([force ? '-D' : '-d', branchName]);
return true;
}
catch (e) {
console.error('Error deleting branch:', e);
return false;
}
}
}
exports.GitIntegration = GitIntegration;