Skip to main content
Glama
listGitFiles.ts5.76 kB
import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { getAppLogger } from '@intlayer/config'; import configuration from '@intlayer/config/built'; import simpleGit from 'simple-git'; export type DiffMode = 'gitDiff' | 'uncommitted' | 'unpushed' | 'untracked'; const getGitRootDir = async (): Promise<string | null> => { try { const git = simpleGit(); const rootDir = await git.revparse(['--show-toplevel']); return rootDir.trim(); } catch (error) { const appLogger = getAppLogger(configuration); appLogger(`Error getting git root directory: ${error}`, { level: 'error', }); return null; } }; export type ListGitFilesOptions = { mode: DiffMode[]; baseRef?: string; currentRef?: string; absolute?: boolean; }; export const listGitFiles = async ({ mode, baseRef = 'origin/main', currentRef = 'HEAD', // HEAD points to the current branch's latest commit absolute = true, }: ListGitFilesOptions) => { try { const git = simpleGit(); const diff: Set<string> = new Set(); if (mode.includes('untracked')) { const status = await git.status(); status.not_added.forEach((f) => diff.add(f)); } if (mode.includes('uncommitted')) { // Get uncommitted changes const uncommittedDiff = await git.diff(['--name-only', 'HEAD']); const uncommittedFiles = uncommittedDiff.split('\n').filter(Boolean); uncommittedFiles.forEach((file) => diff.add(file)); } if (mode.includes('unpushed')) { // Get unpushed commits const unpushedDiff = await git.diff(['--name-only', '@{push}...HEAD']); const unpushedFiles = unpushedDiff.split('\n').filter(Boolean); unpushedFiles.forEach((file) => diff.add(file)); } if (mode.includes('gitDiff')) { // Get the base branch (usually main/master) from CI environment await git.fetch(baseRef); const diffBranch = await git.diff([ '--name-only', `${baseRef}...${currentRef}`, ]); const gitDiffFiles = diffBranch.split('\n').filter(Boolean); gitDiffFiles.forEach((file) => diff.add(file)); } if (absolute) { const gitRootDir = await getGitRootDir(); if (!gitRootDir) { return []; } return Array.from(diff).map((file) => join(gitRootDir, file)); } return Array.from(diff); } catch (error) { console.warn('Failed to get changes list:', error); } }; export type ListGitLinesOptions = { mode: DiffMode[]; baseRef?: string; currentRef?: string; }; export const listGitLines = async ( filePath: string, { mode, baseRef = 'origin/main', currentRef = 'HEAD', // HEAD points to the current branch's latest commit }: ListGitLinesOptions ): Promise<number[]> => { const git = simpleGit(); // We collect **line numbers** (1-based) that were modified/added by the diff. // Using a Set ensures uniqueness when the same line is reported by several modes. const changedLines: Set<number> = new Set(); /** * Extracts line numbers from a diff generated with `--unified=0`. * Each hunk header looks like: @@ -<oldStart>,<oldCount> +<newStart>,<newCount> @@ * We consider both the "+" (new) side for additions and the "-" (old) side for deletions. * For deletions, we add the line before and after the deletion point in the current file. */ const collectLinesFromDiff = (diffOutput: string) => { const hunkRegex = /@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/g; let match: RegExpExecArray | null; while ((match = hunkRegex.exec(diffOutput)) !== null) { const oldCount = match[2] ? Number(match[2]) : 1; const newStart = Number(match[3]); const newCount = match[4] ? Number(match[4]) : 1; // Handle additions/modifications (+ side) if (newCount > 0) { for (let i = 0; i < newCount; i++) { changedLines.add(newStart + i); } } // Handle deletions (- side) if (oldCount > 0 && newCount === 0) { // For deletions, add the line before and after the deletion point // The deletion point in the new file is at newStart if (newStart > 1) { changedLines.add(newStart - 1); // Line before deletion } changedLines.add(newStart); // Line after deletion (if it exists) } } }; // 1. Handle untracked files – when a file is untracked its entire content is new. if (mode.includes('untracked')) { const status = await git.status(); const isUntracked = status.not_added.includes(filePath); if (isUntracked) { try { const content = readFileSync(filePath, 'utf-8'); content.split('\n').forEach((_, idx) => changedLines.add(idx + 1)); } catch { // ignore read errors – file may have been deleted, etc. } } } // 2. Uncommitted changes (working tree vs HEAD) if (mode.includes('uncommitted')) { const diffOutput = await git.diff(['--unified=0', 'HEAD', '--', filePath]); collectLinesFromDiff(diffOutput); } // 3. Unpushed commits – compare local branch to its upstream if (mode.includes('unpushed')) { const diffOutput = await git.diff([ '--unified=0', '@{push}...HEAD', '--', filePath, ]); collectLinesFromDiff(diffOutput); } // 4. Regular git diff between baseRef and currentRef (e.g., CI pull-request diff) if (mode.includes('gitDiff')) { await git.fetch(baseRef); const diffOutput = await git.diff([ '--unified=0', `${baseRef}...${currentRef}`, '--', filePath, ]); collectLinesFromDiff(diffOutput); } // Return the list sorted for convenience return Array.from(changedLines).sort((a, b) => a - b); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server