feat(checkpoint): 添加 Checkpoint 可视化管理功能
Core 层增强:
- 添加 safety.ts: 7点安全检查机制
- 添加 session-tracker.ts: 会话级检查点跟踪
- 添加 lock.ts: 并发控制文件锁
- 添加 lfs.ts: Git LFS 大文件支持
- 添加 path-validator.ts: 路径验证
- 添加 commit-message.ts: 智能提交消息生成
- 增强 manager.ts: 支持三种恢复模式、unrevert 撤销回滚
Server 层:
- 添加 checkpoints.ts: 16个 REST API 端点
- GET/POST /checkpoints: 列表/创建检查点
- GET/DELETE /checkpoints/🆔 获取/删除检查点
- GET /checkpoints/:id/diff: 获取差异
- POST /checkpoints/:id/restore: 恢复到检查点
- POST /checkpoints/unrevert: 撤销回滚
- GET /checkpoints/:id/safety-check: 安全检查
UI 层:
- 添加 CheckpointPanel.tsx: 检查点列表面板
- 添加 CheckpointDiffViewer.tsx: 差异查看器
- 添加 RestoreDialog.tsx: 恢复确认对话框
- 添加 16 个 API 客户端函数
- 添加完整的 TypeScript 类型定义
Web/Desktop 集成:
- 添加 History 按钮到工具栏
- 集成 CheckpointPanel 组件
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* 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<void> {
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user