"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.GitService = void 0; // src/panels/services/GitService.ts const fs = __importStar(require("fs")); const isomorphic_git_1 = __importDefault(require("isomorphic-git")); const node_1 = __importDefault(require("isomorphic-git/http/node")); const path = __importStar(require("path")); const child_process_1 = require("child_process"); const util_1 = require("util"); const execAsync = (0, util_1.promisify)(child_process_1.exec); /** * Git操作服务 */ class GitService { /** * 获取远程分支列表 */ static async fetchBranches(url, username, token) { try { const options = { http: node_1.default, url: url }; if (username || token) { options.onAuth = () => ({ username: username || '', password: token || '' }); } const refs = await isomorphic_git_1.default.listServerRefs(options); const branchRefs = refs.filter((ref) => ref.ref.startsWith('refs/heads/') || ref.ref.startsWith('refs/remotes/origin/')); const branches = branchRefs.map((ref) => { let branchName; if (ref.ref.startsWith('refs/remotes/')) { branchName = ref.ref.replace('refs/remotes/origin/', ''); } else { branchName = ref.ref.replace('refs/heads/', ''); } return { name: branchName, isCurrent: branchName === 'main' || branchName === 'master', selected: false }; }); return branches; } catch (error) { console.error('获取分支失败:', error); throw error; } } /** * 克隆仓库 */ static async cloneRepository(url, localPath, branch = 'main', onProgress, username, token) { const parentDir = path.dirname(localPath); await fs.promises.mkdir(parentDir, { recursive: true }); // 检查目录是否已存在且非空 let dirExists = false; try { await fs.promises.access(localPath); dirExists = true; } catch { dirExists = false; } if (dirExists) { const dirContents = await fs.promises.readdir(localPath); // 修正后的逻辑:过滤掉所有以 "." 开头的隐藏文件/目录。 // 只有当存在非 "." 开头的文件或目录时,才阻止克隆。 const nonBenignFiles = dirContents.filter(item => !item.startsWith('.')); if (nonBenignFiles.length > 0) { throw new Error('目标目录不为空,请清空目录或选择其他路径'); } } const options = { fs, http: node_1.default, dir: localPath, url, singleBranch: true, depth: 1, ref: branch, onProgress }; if (username || token) { options.onAuth = () => ({ username: username || '', password: token || '' }); } await isomorphic_git_1.default.clone(options); } /** * 拉取最新更改 */ static async pullChanges(localPath) { await isomorphic_git_1.default.pull({ fs: fs, http: node_1.default, dir: localPath, author: { name: 'DCSP User', email: 'user@dcsp.local' }, fastForward: true }); } /** * 构建分支树结构 */ static buildBranchTree(branches) { const root = []; branches.forEach(branch => { const parts = branch.name.split('/'); let currentLevel = root; for (let i = 0; i < parts.length; i++) { const part = parts[i]; const isLeaf = i === parts.length - 1; const fullName = parts.slice(0, i + 1).join('/'); let node = currentLevel.find((n) => n.name === part); if (!node) { node = { name: part, fullName: fullName, isLeaf: isLeaf, children: [], level: i, expanded: true }; currentLevel.push(node); } if (isLeaf) { node.branch = branch; } currentLevel = node.children; } }); return root; } /** * 初始化Git仓库 */ static async initRepository(localPath, branchName) { // 使用 -b 参数初始化时直接设置分支名 await execAsync(`git init && git checkout -b "${branchName}"`, { cwd: localPath }); } /** * 移除远程仓库 */ static async removeRemote(localPath) { try { await execAsync('git remote remove origin', { cwd: localPath }); } catch (error) { // 忽略“没有该远程仓库”的错误 if (error.stderr && !error.stderr.includes("No such remote")) { throw error; } if (error.stdout && !error.stdout.includes("No such remote")) { throw error; } } } /** * 添加远程仓库 */ static async addRemote(localPath, repoUrl, username, token) { let finalUrl = repoUrl; if (username || token) { const u = encodeURIComponent(username || ''); const t = encodeURIComponent(token || ''); // 注意: 仅在 URL 是 http/https 时添加 auth if (repoUrl.startsWith('http')) { finalUrl = repoUrl.replace('://', `://${u}:${t}@`); } } await execAsync(`git remote add origin "${finalUrl}"`, { cwd: localPath }); } /** * 提交初始文件 */ static async commitInitialFiles(localPath) { try { await execAsync('git add .', { cwd: localPath }); await execAsync(`git commit -m "Initial commit from DCSP - ${new Date().toLocaleString()}"`, { cwd: localPath }); } catch (error) { if (error.stderr?.includes('nothing to commit')) { console.log('没有需要提交的更改'); } else { throw error; } } } /** * 推送到远程仓库 */ static async pushToRemote(localPath, branchName) { await execAsync(`git push -u origin "${branchName}" --force`, { cwd: localPath }); } /** * 使用Git命令提交并推送 (用于原有的 Git 模块更新) */ static async commitAndPush(localPath) { await execAsync('git add .', { cwd: localPath }); await execAsync(`git commit -m "Auto commit from DCSP - ${new Date().toLocaleString()}"`, { cwd: localPath }); await execAsync('git push', { cwd: localPath }); } /** * 检查是否有未提交的更改 */ static async hasUncommittedChanges(localPath) { try { const { stdout } = await execAsync('git status --porcelain', { cwd: localPath }); return !!stdout.trim(); } catch { return false; } } /** * 推送到指定仓库URL */ static async pushToRepoUrl(localPath, repoUrl, branchName, username, token) { let finalUrl = repoUrl; if (username && token) { const u = encodeURIComponent(username); const t = encodeURIComponent(token); finalUrl = repoUrl.replace('://', `://${u}:${t}@`); } const commands = [ 'git fetch --unshallow || true', `git checkout -B "${branchName}"`, `git push -u "${finalUrl}" "${branchName}" --force` ]; await execAsync(commands.join(' && '), { cwd: localPath }); } /** * 提交并推送到指定分支和远程 (用于 Project/Aircraft/Container 上传) */ static async commitAndPushToBranch(localPath, branchName, repoUrl, // 这里的 repoUrl 是为了构造带 auth 的 finalUrl username, token) { let finalUrl = repoUrl || 'origin'; if (repoUrl && (username || token)) { const u = encodeURIComponent(username || ''); const t = encodeURIComponent(token || ''); // 注意: 仅在 URL 是 http/https 时添加 auth if (repoUrl.startsWith('http')) { finalUrl = repoUrl.replace('://', `://${u}:${t}@`); } } const commands = [ 'git add .', // 允许没有更改时 commit 失败 (Exit code 1),所以使用 || true `git commit -m "Auto commit from DCSP - ${new Date().toLocaleString()}" || true`, `git checkout -B "${branchName}"`, `git push -u "${finalUrl}" "${branchName}" --force` // 强制推送 ]; await execAsync(commands.join(' && '), { cwd: localPath }); } /** * 生成模块文件夹名称 */ static generateModuleFolderName(url, branch) { const repoName = url.split('/').pop()?.replace('.git', '') || 'unknown-repo'; let folderName = branch.trim(); folderName = folderName.replace(/^\/+/, "").replace(/\/+$/, ""); // 去掉两端 "/" folderName = folderName.replace(/\//g, "-"); // "/" 转为 "-" folderName = folderName.replace(/[<>:"\\|?*\r\n\t]/g, "-"); // 替换 Windows 不允许的字符 if (!folderName) folderName = "default"; // 空的 fallback return { displayName: repoName, folderName }; } /** * 构建文件树 * 这里显式忽略: * - .git 目录 * - .dcsp-data.json * - 其它以 . 开头的隐藏文件/目录 */ static async buildFileTree(dir, relativePath = '') { try { const files = await fs.promises.readdir(dir); const tree = []; for (const file of files) { // 1. 不要解析 .git if (file === '.git') continue; // 2. 不要解析项目数据文件 if (file === 'dcsp-data.json') continue; // 3. 其它所有隐藏文件/目录统统忽略 if (file.startsWith('.')) continue; const filePath = path.join(dir, file); const stats = await fs.promises.stat(filePath); const currentRelativePath = path.join(relativePath, file); if (stats.isDirectory()) { const children = await GitService.buildFileTree(filePath, currentRelativePath); tree.push({ name: file, type: 'folder', path: currentRelativePath, children: children }); } else { tree.push({ name: file, type: 'file', path: currentRelativePath }); } } return tree; } catch (error) { console.error('构建文件树失败:', error); return []; } } } exports.GitService = GitService; //# sourceMappingURL=GitService.js.map