diff --git a/package-lock.json b/package-lock.json index 6252833..5995725 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "gitbun", - "version": "1.4.0", + "version": "1.13.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gitbun", - "version": "1.4.0", + "version": "1.13.0", "license": "MIT", "dependencies": { "chalk": "^5.6.2", "commander": "^14.0.3", "cosmiconfig": "^9.0.0", + "ignore": "^7.0.5", "inquirer": "^13.2.4", "node-fetch": "^3.3.2", "ora": "^9.3.0", @@ -2564,16 +2565,6 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/@typescript-eslint/parser": { "version": "8.56.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", @@ -4096,6 +4087,16 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/espree": { "version": "11.1.1", "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", @@ -4811,10 +4812,9 @@ } }, "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "license": "MIT", "engines": { "node": ">= 4" diff --git a/package.json b/package.json index 8bc560e..2ed2908 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "chalk": "^5.6.2", "commander": "^14.0.3", "cosmiconfig": "^9.0.0", + "ignore": "^7.0.5", "inquirer": "^13.2.4", "node-fetch": "^3.3.2", "ora": "^9.3.0", diff --git a/src/analyzer/fileFilter.ts b/src/analyzer/fileFilter.ts index 3e8d8ca..5eae157 100644 --- a/src/analyzer/fileFilter.ts +++ b/src/analyzer/fileFilter.ts @@ -1,5 +1,20 @@ import { execFileSync } from "node:child_process"; +import fs from 'fs'; +import path from 'path'; +import ignore from 'ignore'; + +export function getFilteredFiles(files: string[], rootDir: string): string[] { + const ig = ignore(); + + const ignorePath = path.join(rootDir, '.gitbunignore'); + if (fs.existsSync(ignorePath)) { + ig.add(fs.readFileSync(ignorePath).toString()); + } else { + ig.add(['package-lock.json', 'dist/', 'build/', 'node_modules/']); + } + return files.filter(file => !ig.ignores(file)); +} export type FileChange = { path: string; additions: number; diff --git a/src/git/getStagedFiles.ts b/src/git/getStagedFiles.ts index 3619895..ecd9590 100644 --- a/src/git/getStagedFiles.ts +++ b/src/git/getStagedFiles.ts @@ -1,33 +1,76 @@ import simpleGit from "simple-git"; +import fs from "fs"; +import path from "path"; +import ignore from "ignore"; +import { execSync } from "child_process"; const git = simpleGit(); export type FileStatus = "A" | "M" | "D"; -export async function getStagedFiles(): Promise<{ path: string; status: FileStatus }[]> { +/** + * Helper function to safely fetch the root absolute path of the current Git repository. + */ +function getGitRepoRoot(): string { + try { + return execSync("git rev-parse --show-toplevel", { encoding: "utf8" }).trim(); + } catch (error) { + // Fallback to process.cwd() if not inside a valid git repo + return process.cwd(); + } +} + +export async function getStagedFiles(): Promise< + { path: string; status: FileStatus }[] +> { const output = await git.diff(["--cached", "--name-status"]); const lines = output .split("\n") - .map(l => l.trim()) + .map((l) => l.trim()) .filter(Boolean); - return lines.map(line => { + const files = lines.map((line) => { const parts = line.split(/\s+/); let status = parts[0]; if (status === "R") { return { path: parts[2], - status: "M" as FileStatus + status: "M" as FileStatus, }; } - + if (status !== "A" && status !== "M" && status !== "D") { status = "M"; } return { path: parts[1], - status: status as FileStatus + status: status as FileStatus, }; }); + + // --- GITBUNIGNORE FILTERING LOGIC --- + const ig = ignore(); + + // FIXED: Now dynamically resolves the actual git repository root directory + const repoRoot = getGitRepoRoot(); + const ignorePath = path.join(repoRoot, ".gitbunignore"); + + // 1. Check if .gitbunignore exists at the root + if (fs.existsSync(ignorePath)) { + ig.add(fs.readFileSync(ignorePath).toString()); + } else { + // 2. Default out-of-the-box exclusions to save tokens + ig.add([ + "package-lock.json", + "yarn.lock", + "pnpm-lock.yaml", + "dist/", + "build/", + "node_modules/", + ]); + } + + // 3. Filter the staged files array before returning it + return files.filter((file) => !ig.ignores(file.path)); }