/** * Git LFS 大文件支持模块 * 参考 Cline 的 LFS 模式检测 */ import * as fs from 'fs/promises'; import * as path from 'path'; import { minimatch } from 'minimatch'; /** * LFS 模式加载器 * 从 .gitattributes 文件中加载 LFS 模式 */ export class LFSPatternLoader { private patterns: string[] = []; private loaded = false; /** * 从工作目录加载 LFS 模式 */ async loadPatterns(workDir: string): Promise { const gitattributesPath = path.join(workDir, '.gitattributes'); try { const content = await fs.readFile(gitattributesPath, 'utf-8'); this.patterns = this.parseLfsPatterns(content); this.loaded = true; } catch { // .gitattributes 不存在或无法读取 this.patterns = []; this.loaded = true; } } /** * 解析 .gitattributes 内容,提取 LFS 模式 */ private parseLfsPatterns(content: string): string[] { const patterns: string[] = []; for (const line of content.split('\n')) { const trimmed = line.trim(); // 跳过空行和注释 if (!trimmed || trimmed.startsWith('#')) { continue; } // 匹配 LFS 配置行: pattern filter=lfs diff=lfs merge=lfs -text // 或简单形式: pattern filter=lfs if (trimmed.includes('filter=lfs')) { // 提取模式(第一个空白字符前的部分) const match = trimmed.match(/^(\S+)/); if (match) { patterns.push(match[1]); } } } return patterns; } /** * 检查文件是否为 LFS 管理的文件 */ isLfsFile(filePath: string): boolean { if (!this.loaded) { return false; } // 规范化路径(使用正斜杠) const normalizedPath = filePath.replace(/\\/g, '/'); return this.patterns.some((pattern) => minimatch(normalizedPath, pattern, { matchBase: true }) ); } /** * 获取所有 LFS 模式 */ getPatterns(): string[] { return [...this.patterns]; } /** * 获取排除模式(用于 .gitignore) */ getExcludePatterns(): string[] { return this.patterns.map((p) => { // 确保模式以 / 开头(相对于仓库根目录) if (!p.startsWith('/') && !p.startsWith('*')) { return `/${p}`; } return p; }); } /** * 检查是否已加载 */ isLoaded(): boolean { return this.loaded; } /** * 重置(清除已加载的模式) */ reset(): void { this.patterns = []; this.loaded = false; } /** * 过滤文件列表,排除 LFS 文件 */ filterNonLfsFiles(files: string[]): string[] { return files.filter((file) => !this.isLfsFile(file)); } /** * 获取 LFS 文件列表 */ filterLfsFiles(files: string[]): string[] { return files.filter((file) => this.isLfsFile(file)); } } /** * 常见的大文件扩展名(作为 LFS 候选) */ export const COMMON_LARGE_FILE_EXTENSIONS = [ // 图片 '.psd', '.ai', '.eps', '.tiff', '.raw', '.cr2', '.nef', // 视频 '.mp4', '.mov', '.avi', '.mkv', '.wmv', '.flv', // 音频 '.mp3', '.wav', '.flac', '.aac', '.ogg', // 压缩文件 '.zip', '.tar', '.gz', '.rar', '.7z', // 二进制 '.exe', '.dll', '.so', '.dylib', // 数据文件 '.db', '.sqlite', '.mdb', // 其他 '.pdf', '.docx', '.xlsx', '.pptx', ]; /** * 检查文件扩展名是否为常见大文件类型 */ export function isCommonLargeFile(filePath: string): boolean { const ext = path.extname(filePath).toLowerCase(); return COMMON_LARGE_FILE_EXTENSIONS.includes(ext); } /** * 创建 LFS 模式加载器实例 */ export function createLFSPatternLoader(): LFSPatternLoader { return new LFSPatternLoader(); }