354 lines
13 KiB
JavaScript
354 lines
13 KiB
JavaScript
"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
|