190 lines
6.1 KiB
JavaScript
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;
|