完成了3点的修改:1、添加了文件系统监控,监控文件的动态变化,并及时ui显示。 2、添加了打开刷新,完善点1. 3、git上传优化,子集存在git上传时先删除子集git
This commit is contained in:
@@ -34,6 +34,13 @@ const ConfigView_1 = require("./views/ConfigView");
|
|||||||
const ProjectService_1 = require("./services/ProjectService");
|
const ProjectService_1 = require("./services/ProjectService");
|
||||||
const GitService_1 = require("./services/GitService");
|
const GitService_1 = require("./services/GitService");
|
||||||
const StorageService_1 = require("./services/StorageService");
|
const StorageService_1 = require("./services/StorageService");
|
||||||
|
const debounce = (func, wait) => {
|
||||||
|
let timeout;
|
||||||
|
return (...args) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => func(...args), wait);
|
||||||
|
};
|
||||||
|
};
|
||||||
class ConfigPanel {
|
class ConfigPanel {
|
||||||
static createOrShow(extensionUri) {
|
static createOrShow(extensionUri) {
|
||||||
const column = vscode.window.activeTextEditor?.viewColumn || vscode.ViewColumn.One;
|
const column = vscode.window.activeTextEditor?.viewColumn || vscode.ViewColumn.One;
|
||||||
@@ -61,15 +68,21 @@ class ConfigPanel {
|
|||||||
this.panel = panel;
|
this.panel = panel;
|
||||||
this.extensionUri = extensionUri;
|
this.extensionUri = extensionUri;
|
||||||
this.isWebviewDisposed = false;
|
this.isWebviewDisposed = false;
|
||||||
|
// 初始化服务
|
||||||
this.projectService = new ProjectService_1.ProjectService();
|
this.projectService = new ProjectService_1.ProjectService();
|
||||||
|
// 初始化视图
|
||||||
this.projectView = new ProjectView_1.ProjectView(extensionUri);
|
this.projectView = new ProjectView_1.ProjectView(extensionUri);
|
||||||
this.aircraftView = new AircraftView_1.AircraftView(extensionUri);
|
this.aircraftView = new AircraftView_1.AircraftView(extensionUri);
|
||||||
this.containerView = new ContainerView_1.ContainerView(extensionUri);
|
this.containerView = new ContainerView_1.ContainerView(extensionUri);
|
||||||
this.configView = new ConfigView_1.ConfigView(extensionUri);
|
this.configView = new ConfigView_1.ConfigView(extensionUri);
|
||||||
|
// 加载仓库配置
|
||||||
void this.loadRepoConfigs();
|
void this.loadRepoConfigs();
|
||||||
this.updateWebview();
|
this.updateWebview();
|
||||||
this.setupMessageListener();
|
this.setupMessageListener();
|
||||||
this.panel.onDidDispose(() => {
|
this.panel.onDidDispose(() => {
|
||||||
|
if (this.fileWatcher) {
|
||||||
|
this.fileWatcher.dispose();
|
||||||
|
}
|
||||||
this.isWebviewDisposed = true;
|
this.isWebviewDisposed = true;
|
||||||
ConfigPanel.currentPanel = undefined;
|
ConfigPanel.currentPanel = undefined;
|
||||||
});
|
});
|
||||||
@@ -285,17 +298,32 @@ class ConfigPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async handleOpenProject(data) {
|
async handleOpenProject(data) {
|
||||||
|
// [新增] 在进入视图前,主动扫描磁盘同步数据
|
||||||
|
const changed = await this.projectService.refreshProjectContent(data.projectId);
|
||||||
|
if (changed) {
|
||||||
|
await this.saveCurrentProjectData(); // 如果有变动,立即保存
|
||||||
|
}
|
||||||
this.currentView = 'aircrafts';
|
this.currentView = 'aircrafts';
|
||||||
this.currentProjectId = data.projectId;
|
this.currentProjectId = data.projectId;
|
||||||
this.updateWebview();
|
this.updateWebview();
|
||||||
}
|
}
|
||||||
async handleOpenAircraftConfig(data) {
|
async handleOpenAircraftConfig(data) {
|
||||||
|
// [新增] 进入飞行器详情前,主动同步容器列表
|
||||||
|
const changed = await this.projectService.refreshAircraftContent(data.aircraftId);
|
||||||
|
if (changed) {
|
||||||
|
await this.saveCurrentProjectData();
|
||||||
|
}
|
||||||
this.currentView = 'containers';
|
this.currentView = 'containers';
|
||||||
this.currentProjectId = data.projectId;
|
this.currentProjectId = data.projectId;
|
||||||
this.currentAircraftId = data.aircraftId;
|
this.currentAircraftId = data.aircraftId;
|
||||||
this.updateWebview();
|
this.updateWebview();
|
||||||
}
|
}
|
||||||
async handleOpenContainerConfig(data) {
|
async handleOpenContainerConfig(data) {
|
||||||
|
// [新增] 进入容器详情前,主动同步配置和模块
|
||||||
|
const changed = await this.projectService.refreshContainerContent(data.containerId);
|
||||||
|
if (changed) {
|
||||||
|
await this.saveCurrentProjectData();
|
||||||
|
}
|
||||||
this.currentView = 'configs';
|
this.currentView = 'configs';
|
||||||
this.currentContainerId = data.containerId;
|
this.currentContainerId = data.containerId;
|
||||||
this.updateWebview();
|
this.updateWebview();
|
||||||
@@ -1207,6 +1235,8 @@ class ConfigPanel {
|
|||||||
if (dirContents.length === 0) {
|
if (dirContents.length === 0) {
|
||||||
throw new Error('目录为空,无法上传');
|
throw new Error('目录为空,无法上传');
|
||||||
}
|
}
|
||||||
|
progress.report({ increment: 5, message: '清理嵌套仓库...' });
|
||||||
|
await this.projectService.cleanNestedGitFolders(fullPath);
|
||||||
let isGitRepo = false;
|
let isGitRepo = false;
|
||||||
if (fs.existsSync(path.join(fullPath, '.git'))) {
|
if (fs.existsSync(path.join(fullPath, '.git'))) {
|
||||||
isGitRepo = true;
|
isGitRepo = true;
|
||||||
@@ -1470,6 +1500,8 @@ class ConfigPanel {
|
|||||||
if (projectId) {
|
if (projectId) {
|
||||||
this.currentProjectId = projectId;
|
this.currentProjectId = projectId;
|
||||||
this.currentView = 'aircrafts';
|
this.currentView = 'aircrafts';
|
||||||
|
// [新增] 项目加载成功后,启动文件监听
|
||||||
|
this.setupFileWatcher(projectPath, projectId);
|
||||||
vscode.window.showInformationMessage(`项目数据已从 ${projectPath} 加载`);
|
vscode.window.showInformationMessage(`项目数据已从 ${projectPath} 加载`);
|
||||||
this.updateWebview();
|
this.updateWebview();
|
||||||
return true;
|
return true;
|
||||||
@@ -1481,6 +1513,87 @@ class ConfigPanel {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setupFileWatcher(projectPath, projectId) {
|
||||||
|
// 1. 如果已有监听器,先销毁
|
||||||
|
if (this.fileWatcher) {
|
||||||
|
this.fileWatcher.dispose();
|
||||||
|
}
|
||||||
|
// 2. 创建新的监听器:监听项目目录下的所有变化
|
||||||
|
// Pattern: /path/to/project/**/*
|
||||||
|
const pattern = new vscode.RelativePattern(projectPath, '**/*');
|
||||||
|
this.fileWatcher = vscode.workspace.createFileSystemWatcher(pattern, false, true, false); // 忽略 onChange,只监听 Create/Delete
|
||||||
|
// 3. 定义处理逻辑(使用防抖,避免连续创建文件导致多次刷新)
|
||||||
|
const handleCreate = async (uri) => {
|
||||||
|
if (this.isWebviewDisposed)
|
||||||
|
return;
|
||||||
|
const fsPath = uri.fsPath;
|
||||||
|
const relativePath = path.relative(projectPath, fsPath);
|
||||||
|
const parts = relativePath.split(path.sep); // 使用系统分隔符
|
||||||
|
// 忽略 .git, .dcsp-data.json, 以及隐藏文件
|
||||||
|
if (parts.some(p => p.startsWith('.') || p === 'dcsp-data.json'))
|
||||||
|
return;
|
||||||
|
let dataChanged = false;
|
||||||
|
// 层级 1: 飞行器 (Folder)
|
||||||
|
if (parts.length === 1) {
|
||||||
|
// 检查是文件还是文件夹
|
||||||
|
const stat = await vscode.workspace.fs.stat(uri);
|
||||||
|
if ((stat.type & vscode.FileType.Directory) === vscode.FileType.Directory) {
|
||||||
|
await this.projectService.importAircraftFromExistingFolder(projectId, parts[0]);
|
||||||
|
dataChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 层级 2: 容器 (Folder)
|
||||||
|
else if (parts.length === 2) {
|
||||||
|
const stat = await vscode.workspace.fs.stat(uri);
|
||||||
|
if ((stat.type & vscode.FileType.Directory) === vscode.FileType.Directory) {
|
||||||
|
const aircraft = this.projectService.getAircraftsByProject(projectId).find(a => a.name === parts[0]);
|
||||||
|
if (aircraft) {
|
||||||
|
await this.projectService.importContainerFromExistingFolder(aircraft.id, parts[1]);
|
||||||
|
dataChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 层级 3: 配置 (File) 或 模块文件夹 (Folder)
|
||||||
|
else if (parts.length === 3) {
|
||||||
|
const [aircraftName, containerName, itemName] = parts;
|
||||||
|
const aircraft = this.projectService.getAircraftsByProject(projectId).find(a => a.name === aircraftName);
|
||||||
|
if (aircraft) {
|
||||||
|
const container = this.projectService.getContainersByAircraft(aircraft.id).find(c => c.name === containerName);
|
||||||
|
if (container) {
|
||||||
|
const stat = await vscode.workspace.fs.stat(uri);
|
||||||
|
if ((stat.type & vscode.FileType.File) === vscode.FileType.File) {
|
||||||
|
// 是文件 -> 注册为 Config
|
||||||
|
this.projectService.registerConfigFromDisk(itemName, itemName, container.id);
|
||||||
|
dataChanged = true;
|
||||||
|
}
|
||||||
|
else if ((stat.type & vscode.FileType.Directory) === vscode.FileType.Directory) {
|
||||||
|
// 是文件夹 -> 注册为 ModuleFolder
|
||||||
|
this.projectService.registerModuleFolderFromDisk(itemName, container.id, aircraft.id, projectId, aircraftName, containerName);
|
||||||
|
dataChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dataChanged) {
|
||||||
|
await this.saveCurrentProjectData();
|
||||||
|
this.updateWebview();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleDelete = async (uri) => {
|
||||||
|
if (this.isWebviewDisposed)
|
||||||
|
return;
|
||||||
|
const changed = this.projectService.removeEntityByPath(uri.fsPath, projectId);
|
||||||
|
if (changed) {
|
||||||
|
await this.saveCurrentProjectData();
|
||||||
|
this.updateWebview();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 4. 绑定事件 (防抖建议设为 500ms 左右)
|
||||||
|
const debouncedCreate = debounce(handleCreate, 500);
|
||||||
|
const debouncedDelete = debounce(handleDelete, 500);
|
||||||
|
this.fileWatcher.onDidCreate(uri => debouncedCreate(uri));
|
||||||
|
this.fileWatcher.onDidDelete(uri => debouncedDelete(uri));
|
||||||
|
}
|
||||||
async saveCurrentProjectData() {
|
async saveCurrentProjectData() {
|
||||||
if (!this.currentProjectId) {
|
if (!this.currentProjectId) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -673,6 +673,258 @@ class ProjectService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
registerConfigFromDisk(name, fileName, containerId) {
|
||||||
|
// 查重:只要同一个容器下有相同的 fileName,就视为已存在
|
||||||
|
const existing = this.configs.find(c => c.containerId === containerId && c.fileName === fileName);
|
||||||
|
if (existing)
|
||||||
|
return false;
|
||||||
|
const newId = this.generateUniqueId('cfg', this.configs);
|
||||||
|
this.configs.push({
|
||||||
|
id: newId,
|
||||||
|
name: name,
|
||||||
|
fileName: fileName,
|
||||||
|
containerId: containerId
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* [新方法] 仅在内存中注册模块文件夹(用于监测到磁盘文件夹创建时)
|
||||||
|
*/
|
||||||
|
registerModuleFolderFromDisk(folderName, containerId, aircraftId, projectId, aircraftName, containerName, fullPath) {
|
||||||
|
const relativePath = `/${projectId}/${aircraftName}/${containerName}/${folderName}`;
|
||||||
|
// 查重:只要同一个容器下,有任何一个模块指向了相同的文件夹名,就视为已存在
|
||||||
|
// 这样即使旧数据的 localPath 路径字符串是过时的,也不会重复添加
|
||||||
|
const existing = this.moduleFolders.find(f => {
|
||||||
|
if (f.containerId !== containerId)
|
||||||
|
return false;
|
||||||
|
// 从 localPath 中提取最后一部分(文件夹名)进行比对
|
||||||
|
const existingFolderName = f.localPath.split('/').pop();
|
||||||
|
return existingFolderName === folderName;
|
||||||
|
});
|
||||||
|
if (existing)
|
||||||
|
return false;
|
||||||
|
// 智能检测:如果文件夹下有 .git,则标记为 git 类型
|
||||||
|
let type = 'local';
|
||||||
|
if (fullPath) {
|
||||||
|
const gitDir = path.join(fullPath, '.git');
|
||||||
|
if (fs.existsSync(gitDir)) {
|
||||||
|
type = 'git';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const folderId = this.generateUniqueId(type === 'git' ? 'git-' : 'local-', this.moduleFolders);
|
||||||
|
this.moduleFolders.push({
|
||||||
|
id: folderId,
|
||||||
|
name: folderName,
|
||||||
|
type: type,
|
||||||
|
localPath: relativePath,
|
||||||
|
containerId: containerId,
|
||||||
|
uploaded: type === 'git'
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* [新方法] 处理磁盘删除事件:根据路径移除对应的 Config 或 ModuleFolder
|
||||||
|
*/
|
||||||
|
removeEntityByPath(filePath, projectId) {
|
||||||
|
// 这里的 filePath 是绝对路径,需要反解出是哪个 Config 或 Folder
|
||||||
|
// 这是一个比较繁琐的过程,简化逻辑如下:
|
||||||
|
let changed = false;
|
||||||
|
const projectPath = this.projectPaths.get(projectId);
|
||||||
|
if (!projectPath || !filePath.startsWith(projectPath))
|
||||||
|
return false;
|
||||||
|
// 尝试匹配 Config
|
||||||
|
const configToDelete = this.configs.find(c => {
|
||||||
|
const p = this.getConfigFilePath(c.id);
|
||||||
|
return p && path.normalize(p) === path.normalize(filePath);
|
||||||
|
});
|
||||||
|
if (configToDelete) {
|
||||||
|
this.deleteConfig(configToDelete.id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 尝试匹配 ModuleFolder
|
||||||
|
const folderToDelete = this.moduleFolders.find(f => {
|
||||||
|
const p = this.getModuleFolderFullPath(f);
|
||||||
|
return p && path.normalize(p) === path.normalize(filePath);
|
||||||
|
});
|
||||||
|
if (folderToDelete) {
|
||||||
|
this.deleteModuleFolder(folderToDelete.id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 刷新项目内容:同步飞行器列表
|
||||||
|
*/
|
||||||
|
async refreshProjectContent(projectId) {
|
||||||
|
const projectPath = this.projectPaths.get(projectId);
|
||||||
|
if (!projectPath || !fs.existsSync(projectPath))
|
||||||
|
return false;
|
||||||
|
let changed = false;
|
||||||
|
// 1. 扫描磁盘,添加缺失的飞行器
|
||||||
|
try {
|
||||||
|
const entries = await fs.promises.readdir(projectPath, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isDirectory())
|
||||||
|
continue;
|
||||||
|
if (entry.name.startsWith('.') || entry.name === 'dcsp-data.json')
|
||||||
|
continue;
|
||||||
|
// 检查内存中是否存在
|
||||||
|
const exists = this.getAircraftsByProject(projectId).find(a => a.name === entry.name);
|
||||||
|
if (!exists) {
|
||||||
|
// 不存在则导入
|
||||||
|
await this.importAircraftFromExistingFolder(projectId, entry.name);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error('扫描项目目录失败:', e);
|
||||||
|
}
|
||||||
|
// 2. 检查内存,移除磁盘不存在的飞行器 (清理无效数据)
|
||||||
|
const currentAircrafts = this.getAircraftsByProject(projectId);
|
||||||
|
for (const aircraft of currentAircrafts) {
|
||||||
|
const aircraftPath = path.join(projectPath, aircraft.name);
|
||||||
|
if (!fs.existsSync(aircraftPath)) {
|
||||||
|
this.deleteAircraft(aircraft.id);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 刷新飞行器内容:同步容器列表
|
||||||
|
*/
|
||||||
|
async refreshAircraftContent(aircraftId) {
|
||||||
|
const dirPath = this.getAircraftDirectoryPath(aircraftId);
|
||||||
|
if (!dirPath || !fs.existsSync(dirPath))
|
||||||
|
return false;
|
||||||
|
let changed = false;
|
||||||
|
// 1. 扫描磁盘,添加缺失的容器
|
||||||
|
try {
|
||||||
|
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isDirectory())
|
||||||
|
continue;
|
||||||
|
if (entry.name.startsWith('.'))
|
||||||
|
continue; // 忽略 .git 等
|
||||||
|
const exists = this.getContainersByAircraft(aircraftId).find(c => c.name === entry.name);
|
||||||
|
if (!exists) {
|
||||||
|
await this.importContainerFromExistingFolder(aircraftId, entry.name);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error('扫描飞行器目录失败:', e);
|
||||||
|
}
|
||||||
|
// 2. 检查内存,移除磁盘不存在的容器
|
||||||
|
const currentContainers = this.getContainersByAircraft(aircraftId);
|
||||||
|
for (const container of currentContainers) {
|
||||||
|
const containerPath = path.join(dirPath, container.name);
|
||||||
|
if (!fs.existsSync(containerPath)) {
|
||||||
|
this.deleteContainer(container.id);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 刷新容器内容:同步配置和模块列表
|
||||||
|
*/
|
||||||
|
async refreshContainerContent(containerId) {
|
||||||
|
const dirPath = this.getContainerDirectoryPath(containerId);
|
||||||
|
if (!dirPath || !fs.existsSync(dirPath))
|
||||||
|
return false;
|
||||||
|
const container = this.getContainer(containerId);
|
||||||
|
if (!container)
|
||||||
|
return false;
|
||||||
|
const aircraft = this.getAircraft(container.aircraftId);
|
||||||
|
if (!aircraft)
|
||||||
|
return false;
|
||||||
|
let changed = false;
|
||||||
|
// 1. 扫描磁盘,添加缺失的配置和模块
|
||||||
|
try {
|
||||||
|
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.name.startsWith('.') || entry.name === 'dcsp-data.json')
|
||||||
|
continue;
|
||||||
|
if (entry.isFile()) {
|
||||||
|
// 尝试注册配置 (registerConfigFromDisk 内部会自动去重)
|
||||||
|
const isNew = this.registerConfigFromDisk(entry.name, entry.name, containerId);
|
||||||
|
if (isNew)
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
else if (entry.isDirectory()) {
|
||||||
|
// 尝试注册模块
|
||||||
|
const isNew = this.registerModuleFolderFromDisk(entry.name, containerId, aircraft.id, aircraft.projectId, aircraft.name, container.name);
|
||||||
|
if (isNew)
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error('扫描容器目录失败:', e);
|
||||||
|
}
|
||||||
|
// 2. 检查内存,移除磁盘不存在的 配置
|
||||||
|
const currentConfigs = this.getConfigsByContainer(containerId);
|
||||||
|
for (const config of currentConfigs) {
|
||||||
|
const configPath = path.join(dirPath, config.fileName);
|
||||||
|
if (!fs.existsSync(configPath)) {
|
||||||
|
this.deleteConfig(config.id);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3. 检查内存,移除磁盘不存在的 模块文件夹
|
||||||
|
const currentModules = this.getModuleFoldersByContainer(containerId);
|
||||||
|
for (const folder of currentModules) {
|
||||||
|
// 注意:Git 类型的模块文件夹,其 localPath 也是指向这个目录的
|
||||||
|
// 这里我们简单拼接路径来检查
|
||||||
|
// folder.localPath 格式: /projectId/aircraft/container/folderName
|
||||||
|
const folderName = folder.localPath.split('/').pop();
|
||||||
|
if (folderName) {
|
||||||
|
const folderPath = path.join(dirPath, folderName);
|
||||||
|
if (!fs.existsSync(folderPath)) {
|
||||||
|
this.deleteModuleFolder(folder.id);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* [新增] 递归清理目录下所有的嵌套 .git 文件夹
|
||||||
|
* 作用:保留 rootDir 下的 .git,但删除所有子目录中的 .git
|
||||||
|
* 解决父级上传时因包含子级仓库导致的冲突
|
||||||
|
*/
|
||||||
|
async cleanNestedGitFolders(rootDir) {
|
||||||
|
if (!fs.existsSync(rootDir))
|
||||||
|
return;
|
||||||
|
try {
|
||||||
|
const entries = await fs.promises.readdir(rootDir, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
// 1. 如果遍历到的是当前目录下的 .git,直接跳过(这是我们要保留的主仓库)
|
||||||
|
if (entry.name === '.git')
|
||||||
|
continue;
|
||||||
|
// 2. 只处理目录
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
const subDir = path.join(rootDir, entry.name);
|
||||||
|
const nestedGit = path.join(subDir, '.git');
|
||||||
|
// 检查子目录下是否有 .git
|
||||||
|
if (fs.existsSync(nestedGit)) {
|
||||||
|
console.log(`🧹 发现嵌套 Git 仓库,正在移除以支持父级上传: ${nestedGit}`);
|
||||||
|
// 使用您之前实现的强力删除方法(带权限处理)
|
||||||
|
await this.deleteDirectoryFromDisk(nestedGit);
|
||||||
|
}
|
||||||
|
// 继续递归,防止有多层嵌套
|
||||||
|
await this.cleanNestedGitFolders(subDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(`清理嵌套 Git 失败: ${error}`);
|
||||||
|
// 不抛出错误,尽力而为,以免打断上传流程
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
exports.ProjectService = ProjectService;
|
exports.ProjectService = ProjectService;
|
||||||
//# sourceMappingURL=ProjectService.js.map
|
//# sourceMappingURL=ProjectService.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -26,10 +26,20 @@ interface GitFileTree {
|
|||||||
|
|
||||||
type CloneScope = 'project' | 'aircraft' | 'container' | 'config';
|
type CloneScope = 'project' | 'aircraft' | 'container' | 'config';
|
||||||
|
|
||||||
|
const debounce = (func: Function, wait: number) => {
|
||||||
|
let timeout: NodeJS.Timeout;
|
||||||
|
return (...args: any[]) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => func(...args), wait);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export class ConfigPanel {
|
export class ConfigPanel {
|
||||||
public static currentPanel: ConfigPanel | undefined;
|
public static currentPanel: ConfigPanel | undefined;
|
||||||
public readonly panel: vscode.WebviewPanel;
|
public readonly panel: vscode.WebviewPanel;
|
||||||
private readonly extensionUri: vscode.Uri;
|
private readonly extensionUri: vscode.Uri;
|
||||||
|
|
||||||
|
private fileWatcher: vscode.FileSystemWatcher | undefined;
|
||||||
|
|
||||||
private projectService: ProjectService;
|
private projectService: ProjectService;
|
||||||
|
|
||||||
@@ -78,19 +88,25 @@ export class ConfigPanel {
|
|||||||
this.extensionUri = extensionUri;
|
this.extensionUri = extensionUri;
|
||||||
this.isWebviewDisposed = false;
|
this.isWebviewDisposed = false;
|
||||||
|
|
||||||
|
// 初始化服务
|
||||||
this.projectService = new ProjectService();
|
this.projectService = new ProjectService();
|
||||||
|
|
||||||
|
// 初始化视图
|
||||||
this.projectView = new ProjectView(extensionUri);
|
this.projectView = new ProjectView(extensionUri);
|
||||||
this.aircraftView = new AircraftView(extensionUri);
|
this.aircraftView = new AircraftView(extensionUri);
|
||||||
this.containerView = new ContainerView(extensionUri);
|
this.containerView = new ContainerView(extensionUri);
|
||||||
this.configView = new ConfigView(extensionUri);
|
this.configView = new ConfigView(extensionUri);
|
||||||
|
|
||||||
|
// 加载仓库配置
|
||||||
void this.loadRepoConfigs();
|
void this.loadRepoConfigs();
|
||||||
|
|
||||||
this.updateWebview();
|
this.updateWebview();
|
||||||
this.setupMessageListener();
|
this.setupMessageListener();
|
||||||
|
|
||||||
this.panel.onDidDispose(() => {
|
this.panel.onDidDispose(() => {
|
||||||
|
if (this.fileWatcher) {
|
||||||
|
this.fileWatcher.dispose();
|
||||||
|
}
|
||||||
this.isWebviewDisposed = true;
|
this.isWebviewDisposed = true;
|
||||||
ConfigPanel.currentPanel = undefined;
|
ConfigPanel.currentPanel = undefined;
|
||||||
});
|
});
|
||||||
@@ -374,12 +390,24 @@ export class ConfigPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async handleOpenProject(data: any): Promise<void> {
|
private async handleOpenProject(data: any): Promise<void> {
|
||||||
|
// [新增] 在进入视图前,主动扫描磁盘同步数据
|
||||||
|
const changed = await this.projectService.refreshProjectContent(data.projectId);
|
||||||
|
if (changed) {
|
||||||
|
await this.saveCurrentProjectData(); // 如果有变动,立即保存
|
||||||
|
}
|
||||||
|
|
||||||
this.currentView = 'aircrafts';
|
this.currentView = 'aircrafts';
|
||||||
this.currentProjectId = data.projectId;
|
this.currentProjectId = data.projectId;
|
||||||
this.updateWebview();
|
this.updateWebview();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleOpenAircraftConfig(data: any): Promise<void> {
|
private async handleOpenAircraftConfig(data: any): Promise<void> {
|
||||||
|
// [新增] 进入飞行器详情前,主动同步容器列表
|
||||||
|
const changed = await this.projectService.refreshAircraftContent(data.aircraftId);
|
||||||
|
if (changed) {
|
||||||
|
await this.saveCurrentProjectData();
|
||||||
|
}
|
||||||
|
|
||||||
this.currentView = 'containers';
|
this.currentView = 'containers';
|
||||||
this.currentProjectId = data.projectId;
|
this.currentProjectId = data.projectId;
|
||||||
this.currentAircraftId = data.aircraftId;
|
this.currentAircraftId = data.aircraftId;
|
||||||
@@ -387,6 +415,12 @@ export class ConfigPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async handleOpenContainerConfig(data: any): Promise<void> {
|
private async handleOpenContainerConfig(data: any): Promise<void> {
|
||||||
|
// [新增] 进入容器详情前,主动同步配置和模块
|
||||||
|
const changed = await this.projectService.refreshContainerContent(data.containerId);
|
||||||
|
if (changed) {
|
||||||
|
await this.saveCurrentProjectData();
|
||||||
|
}
|
||||||
|
|
||||||
this.currentView = 'configs';
|
this.currentView = 'configs';
|
||||||
this.currentContainerId = data.containerId;
|
this.currentContainerId = data.containerId;
|
||||||
this.updateWebview();
|
this.updateWebview();
|
||||||
@@ -1533,6 +1567,9 @@ export class ConfigPanel {
|
|||||||
throw new Error('目录为空,无法上传');
|
throw new Error('目录为空,无法上传');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
progress.report({ increment: 5, message: '清理嵌套仓库...' });
|
||||||
|
await this.projectService.cleanNestedGitFolders(fullPath!);
|
||||||
|
|
||||||
let isGitRepo = false;
|
let isGitRepo = false;
|
||||||
if (fs.existsSync(path.join(fullPath!, '.git'))) {
|
if (fs.existsSync(path.join(fullPath!, '.git'))) {
|
||||||
isGitRepo = true;
|
isGitRepo = true;
|
||||||
@@ -1837,6 +1874,9 @@ export class ConfigPanel {
|
|||||||
this.currentProjectId = projectId;
|
this.currentProjectId = projectId;
|
||||||
this.currentView = 'aircrafts';
|
this.currentView = 'aircrafts';
|
||||||
|
|
||||||
|
// [新增] 项目加载成功后,启动文件监听
|
||||||
|
this.setupFileWatcher(projectPath, projectId);
|
||||||
|
|
||||||
vscode.window.showInformationMessage(`项目数据已从 ${projectPath} 加载`);
|
vscode.window.showInformationMessage(`项目数据已从 ${projectPath} 加载`);
|
||||||
this.updateWebview();
|
this.updateWebview();
|
||||||
return true;
|
return true;
|
||||||
@@ -1848,6 +1888,96 @@ export class ConfigPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setupFileWatcher(projectPath: string, projectId: string) {
|
||||||
|
// 1. 如果已有监听器,先销毁
|
||||||
|
if (this.fileWatcher) {
|
||||||
|
this.fileWatcher.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 创建新的监听器:监听项目目录下的所有变化
|
||||||
|
// Pattern: /path/to/project/**/*
|
||||||
|
const pattern = new vscode.RelativePattern(projectPath, '**/*');
|
||||||
|
this.fileWatcher = vscode.workspace.createFileSystemWatcher(pattern, false, true, false); // 忽略 onChange,只监听 Create/Delete
|
||||||
|
|
||||||
|
// 3. 定义处理逻辑(使用防抖,避免连续创建文件导致多次刷新)
|
||||||
|
const handleCreate = async (uri: vscode.Uri) => {
|
||||||
|
if (this.isWebviewDisposed) return;
|
||||||
|
|
||||||
|
const fsPath = uri.fsPath;
|
||||||
|
const relativePath = path.relative(projectPath, fsPath);
|
||||||
|
const parts = relativePath.split(path.sep); // 使用系统分隔符
|
||||||
|
|
||||||
|
// 忽略 .git, .dcsp-data.json, 以及隐藏文件
|
||||||
|
if (parts.some(p => p.startsWith('.') || p === 'dcsp-data.json')) return;
|
||||||
|
|
||||||
|
let dataChanged = false;
|
||||||
|
|
||||||
|
// 层级 1: 飞行器 (Folder)
|
||||||
|
if (parts.length === 1) {
|
||||||
|
// 检查是文件还是文件夹
|
||||||
|
const stat = await vscode.workspace.fs.stat(uri);
|
||||||
|
if ((stat.type & vscode.FileType.Directory) === vscode.FileType.Directory) {
|
||||||
|
await this.projectService.importAircraftFromExistingFolder(projectId, parts[0]);
|
||||||
|
dataChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 层级 2: 容器 (Folder)
|
||||||
|
else if (parts.length === 2) {
|
||||||
|
const stat = await vscode.workspace.fs.stat(uri);
|
||||||
|
if ((stat.type & vscode.FileType.Directory) === vscode.FileType.Directory) {
|
||||||
|
const aircraft = this.projectService.getAircraftsByProject(projectId).find(a => a.name === parts[0]);
|
||||||
|
if (aircraft) {
|
||||||
|
await this.projectService.importContainerFromExistingFolder(aircraft.id, parts[1]);
|
||||||
|
dataChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 层级 3: 配置 (File) 或 模块文件夹 (Folder)
|
||||||
|
else if (parts.length === 3) {
|
||||||
|
const [aircraftName, containerName, itemName] = parts;
|
||||||
|
const aircraft = this.projectService.getAircraftsByProject(projectId).find(a => a.name === aircraftName);
|
||||||
|
if (aircraft) {
|
||||||
|
const container = this.projectService.getContainersByAircraft(aircraft.id).find(c => c.name === containerName);
|
||||||
|
if (container) {
|
||||||
|
const stat = await vscode.workspace.fs.stat(uri);
|
||||||
|
|
||||||
|
if ((stat.type & vscode.FileType.File) === vscode.FileType.File) {
|
||||||
|
// 是文件 -> 注册为 Config
|
||||||
|
this.projectService.registerConfigFromDisk(itemName, itemName, container.id);
|
||||||
|
dataChanged = true;
|
||||||
|
} else if ((stat.type & vscode.FileType.Directory) === vscode.FileType.Directory) {
|
||||||
|
// 是文件夹 -> 注册为 ModuleFolder
|
||||||
|
this.projectService.registerModuleFolderFromDisk(itemName, container.id, aircraft.id, projectId, aircraftName, containerName);
|
||||||
|
dataChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataChanged) {
|
||||||
|
await this.saveCurrentProjectData();
|
||||||
|
this.updateWebview();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (uri: vscode.Uri) => {
|
||||||
|
if (this.isWebviewDisposed) return;
|
||||||
|
|
||||||
|
const changed = this.projectService.removeEntityByPath(uri.fsPath, projectId);
|
||||||
|
if (changed) {
|
||||||
|
await this.saveCurrentProjectData();
|
||||||
|
this.updateWebview();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 4. 绑定事件 (防抖建议设为 500ms 左右)
|
||||||
|
const debouncedCreate = debounce(handleCreate, 500);
|
||||||
|
const debouncedDelete = debounce(handleDelete, 500);
|
||||||
|
|
||||||
|
this.fileWatcher.onDidCreate(uri => debouncedCreate(uri));
|
||||||
|
this.fileWatcher.onDidDelete(uri => debouncedDelete(uri));
|
||||||
|
}
|
||||||
|
|
||||||
private async saveCurrentProjectData(): Promise<void> {
|
private async saveCurrentProjectData(): Promise<void> {
|
||||||
if (!this.currentProjectId) {
|
if (!this.currentProjectId) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -764,4 +764,290 @@ export class ProjectService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
registerConfigFromDisk(name: string, fileName: string, containerId: string): boolean {
|
||||||
|
// 查重:只要同一个容器下有相同的 fileName,就视为已存在
|
||||||
|
const existing = this.configs.find(c => c.containerId === containerId && c.fileName === fileName);
|
||||||
|
if (existing) return false;
|
||||||
|
|
||||||
|
const newId = this.generateUniqueId('cfg', this.configs);
|
||||||
|
this.configs.push({
|
||||||
|
id: newId,
|
||||||
|
name: name,
|
||||||
|
fileName: fileName,
|
||||||
|
containerId: containerId
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [新方法] 仅在内存中注册模块文件夹(用于监测到磁盘文件夹创建时)
|
||||||
|
*/
|
||||||
|
registerModuleFolderFromDisk(
|
||||||
|
folderName: string,
|
||||||
|
containerId: string,
|
||||||
|
aircraftId: string,
|
||||||
|
projectId: string,
|
||||||
|
aircraftName: string,
|
||||||
|
containerName: string,
|
||||||
|
fullPath?: string
|
||||||
|
): boolean {
|
||||||
|
const relativePath = `/${projectId}/${aircraftName}/${containerName}/${folderName}`;
|
||||||
|
|
||||||
|
// 查重:只要同一个容器下,有任何一个模块指向了相同的文件夹名,就视为已存在
|
||||||
|
// 这样即使旧数据的 localPath 路径字符串是过时的,也不会重复添加
|
||||||
|
const existing = this.moduleFolders.find(f => {
|
||||||
|
if (f.containerId !== containerId) return false;
|
||||||
|
// 从 localPath 中提取最后一部分(文件夹名)进行比对
|
||||||
|
const existingFolderName = f.localPath.split('/').pop();
|
||||||
|
return existingFolderName === folderName;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existing) return false;
|
||||||
|
|
||||||
|
// 智能检测:如果文件夹下有 .git,则标记为 git 类型
|
||||||
|
let type: 'local' | 'git' = 'local';
|
||||||
|
if (fullPath) {
|
||||||
|
const gitDir = path.join(fullPath, '.git');
|
||||||
|
if (fs.existsSync(gitDir)) {
|
||||||
|
type = 'git';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const folderId = this.generateUniqueId(type === 'git' ? 'git-' : 'local-', this.moduleFolders);
|
||||||
|
this.moduleFolders.push({
|
||||||
|
id: folderId,
|
||||||
|
name: folderName,
|
||||||
|
type: type,
|
||||||
|
localPath: relativePath,
|
||||||
|
containerId: containerId,
|
||||||
|
uploaded: type === 'git'
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [新方法] 处理磁盘删除事件:根据路径移除对应的 Config 或 ModuleFolder
|
||||||
|
*/
|
||||||
|
removeEntityByPath(filePath: string, projectId: string): boolean {
|
||||||
|
// 这里的 filePath 是绝对路径,需要反解出是哪个 Config 或 Folder
|
||||||
|
// 这是一个比较繁琐的过程,简化逻辑如下:
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
const projectPath = this.projectPaths.get(projectId);
|
||||||
|
if (!projectPath || !filePath.startsWith(projectPath)) return false;
|
||||||
|
|
||||||
|
// 尝试匹配 Config
|
||||||
|
const configToDelete = this.configs.find(c => {
|
||||||
|
const p = this.getConfigFilePath(c.id);
|
||||||
|
return p && path.normalize(p) === path.normalize(filePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (configToDelete) {
|
||||||
|
this.deleteConfig(configToDelete.id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试匹配 ModuleFolder
|
||||||
|
const folderToDelete = this.moduleFolders.find(f => {
|
||||||
|
const p = this.getModuleFolderFullPath(f);
|
||||||
|
return p && path.normalize(p) === path.normalize(filePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (folderToDelete) {
|
||||||
|
this.deleteModuleFolder(folderToDelete.id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新项目内容:同步飞行器列表
|
||||||
|
*/
|
||||||
|
async refreshProjectContent(projectId: string): Promise<boolean> {
|
||||||
|
const projectPath = this.projectPaths.get(projectId);
|
||||||
|
if (!projectPath || !fs.existsSync(projectPath)) return false;
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
// 1. 扫描磁盘,添加缺失的飞行器
|
||||||
|
try {
|
||||||
|
const entries = await fs.promises.readdir(projectPath, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isDirectory()) continue;
|
||||||
|
if (entry.name.startsWith('.') || entry.name === 'dcsp-data.json') continue;
|
||||||
|
|
||||||
|
// 检查内存中是否存在
|
||||||
|
const exists = this.getAircraftsByProject(projectId).find(a => a.name === entry.name);
|
||||||
|
if (!exists) {
|
||||||
|
// 不存在则导入
|
||||||
|
await this.importAircraftFromExistingFolder(projectId, entry.name);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('扫描项目目录失败:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查内存,移除磁盘不存在的飞行器 (清理无效数据)
|
||||||
|
const currentAircrafts = this.getAircraftsByProject(projectId);
|
||||||
|
for (const aircraft of currentAircrafts) {
|
||||||
|
const aircraftPath = path.join(projectPath, aircraft.name);
|
||||||
|
if (!fs.existsSync(aircraftPath)) {
|
||||||
|
this.deleteAircraft(aircraft.id);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新飞行器内容:同步容器列表
|
||||||
|
*/
|
||||||
|
async refreshAircraftContent(aircraftId: string): Promise<boolean> {
|
||||||
|
const dirPath = this.getAircraftDirectoryPath(aircraftId);
|
||||||
|
if (!dirPath || !fs.existsSync(dirPath)) return false;
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
// 1. 扫描磁盘,添加缺失的容器
|
||||||
|
try {
|
||||||
|
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isDirectory()) continue;
|
||||||
|
if (entry.name.startsWith('.')) continue; // 忽略 .git 等
|
||||||
|
|
||||||
|
const exists = this.getContainersByAircraft(aircraftId).find(c => c.name === entry.name);
|
||||||
|
if (!exists) {
|
||||||
|
await this.importContainerFromExistingFolder(aircraftId, entry.name);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('扫描飞行器目录失败:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查内存,移除磁盘不存在的容器
|
||||||
|
const currentContainers = this.getContainersByAircraft(aircraftId);
|
||||||
|
for (const container of currentContainers) {
|
||||||
|
const containerPath = path.join(dirPath, container.name);
|
||||||
|
if (!fs.existsSync(containerPath)) {
|
||||||
|
this.deleteContainer(container.id);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新容器内容:同步配置和模块列表
|
||||||
|
*/
|
||||||
|
async refreshContainerContent(containerId: string): Promise<boolean> {
|
||||||
|
const dirPath = this.getContainerDirectoryPath(containerId);
|
||||||
|
if (!dirPath || !fs.existsSync(dirPath)) return false;
|
||||||
|
|
||||||
|
const container = this.getContainer(containerId);
|
||||||
|
if (!container) return false;
|
||||||
|
|
||||||
|
const aircraft = this.getAircraft(container.aircraftId);
|
||||||
|
if (!aircraft) return false;
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
// 1. 扫描磁盘,添加缺失的配置和模块
|
||||||
|
try {
|
||||||
|
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.name.startsWith('.') || entry.name === 'dcsp-data.json') continue;
|
||||||
|
|
||||||
|
if (entry.isFile()) {
|
||||||
|
// 尝试注册配置 (registerConfigFromDisk 内部会自动去重)
|
||||||
|
const isNew = this.registerConfigFromDisk(entry.name, entry.name, containerId);
|
||||||
|
if (isNew) changed = true;
|
||||||
|
} else if (entry.isDirectory()) {
|
||||||
|
// 尝试注册模块
|
||||||
|
const isNew = this.registerModuleFolderFromDisk(
|
||||||
|
entry.name,
|
||||||
|
containerId,
|
||||||
|
aircraft.id,
|
||||||
|
aircraft.projectId,
|
||||||
|
aircraft.name,
|
||||||
|
container.name
|
||||||
|
);
|
||||||
|
if (isNew) changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('扫描容器目录失败:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查内存,移除磁盘不存在的 配置
|
||||||
|
const currentConfigs = this.getConfigsByContainer(containerId);
|
||||||
|
for (const config of currentConfigs) {
|
||||||
|
const configPath = path.join(dirPath, config.fileName);
|
||||||
|
if (!fs.existsSync(configPath)) {
|
||||||
|
this.deleteConfig(config.id);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查内存,移除磁盘不存在的 模块文件夹
|
||||||
|
const currentModules = this.getModuleFoldersByContainer(containerId);
|
||||||
|
for (const folder of currentModules) {
|
||||||
|
// 注意:Git 类型的模块文件夹,其 localPath 也是指向这个目录的
|
||||||
|
// 这里我们简单拼接路径来检查
|
||||||
|
// folder.localPath 格式: /projectId/aircraft/container/folderName
|
||||||
|
const folderName = folder.localPath.split('/').pop();
|
||||||
|
if (folderName) {
|
||||||
|
const folderPath = path.join(dirPath, folderName);
|
||||||
|
if (!fs.existsSync(folderPath)) {
|
||||||
|
this.deleteModuleFolder(folder.id);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [新增] 递归清理目录下所有的嵌套 .git 文件夹
|
||||||
|
* 作用:保留 rootDir 下的 .git,但删除所有子目录中的 .git
|
||||||
|
* 解决父级上传时因包含子级仓库导致的冲突
|
||||||
|
*/
|
||||||
|
async cleanNestedGitFolders(rootDir: string): Promise<void> {
|
||||||
|
if (!fs.existsSync(rootDir)) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const entries = await fs.promises.readdir(rootDir, { withFileTypes: true });
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
// 1. 如果遍历到的是当前目录下的 .git,直接跳过(这是我们要保留的主仓库)
|
||||||
|
if (entry.name === '.git') continue;
|
||||||
|
|
||||||
|
// 2. 只处理目录
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
const subDir = path.join(rootDir, entry.name);
|
||||||
|
const nestedGit = path.join(subDir, '.git');
|
||||||
|
|
||||||
|
// 检查子目录下是否有 .git
|
||||||
|
if (fs.existsSync(nestedGit)) {
|
||||||
|
console.log(`🧹 发现嵌套 Git 仓库,正在移除以支持父级上传: ${nestedGit}`);
|
||||||
|
// 使用您之前实现的强力删除方法(带权限处理)
|
||||||
|
await this.deleteDirectoryFromDisk(nestedGit);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 继续递归,防止有多层嵌套
|
||||||
|
await this.cleanNestedGitFolders(subDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`清理嵌套 Git 失败: ${error}`);
|
||||||
|
// 不抛出错误,尽力而为,以免打断上传流程
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user