现在代码上传逻辑存在问题
This commit is contained in:
325
src/panels/services/GitService.ts
Normal file
325
src/panels/services/GitService.ts
Normal file
@@ -0,0 +1,325 @@
|
||||
import * as fs from 'fs';
|
||||
import git from 'isomorphic-git';
|
||||
import http from 'isomorphic-git/http/node';
|
||||
import * as path from 'path';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { GitBranch, GitFileTree } from '../types/CommonTypes';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
/**
|
||||
* Git操作服务
|
||||
*/
|
||||
export class GitService {
|
||||
/**
|
||||
* 获取远程分支列表
|
||||
*/
|
||||
static async fetchBranches(
|
||||
url: string,
|
||||
username?: string,
|
||||
token?: string
|
||||
): Promise<GitBranch[]> {
|
||||
try {
|
||||
const options: any = {
|
||||
http: http,
|
||||
url: url
|
||||
};
|
||||
|
||||
if (username || token) {
|
||||
options.onAuth = () => ({
|
||||
username: username || '',
|
||||
password: token || ''
|
||||
});
|
||||
}
|
||||
|
||||
const refs = await git.listServerRefs(options);
|
||||
|
||||
const branchRefs = refs.filter((ref: any) =>
|
||||
ref.ref.startsWith('refs/heads/') || ref.ref.startsWith('refs/remotes/origin/')
|
||||
);
|
||||
|
||||
const branches: GitBranch[] = branchRefs.map((ref: any) => {
|
||||
let branchName: string;
|
||||
|
||||
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: string,
|
||||
localPath: string,
|
||||
branch: string = 'main',
|
||||
onProgress?: (event: any) => void,
|
||||
username?: string,
|
||||
token?: string
|
||||
): Promise<void> {
|
||||
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);
|
||||
if (dirContents.length > 0 && dirContents.some(item => item !== '.git')) {
|
||||
throw new Error('目标目录不为空,请清空目录或选择其他路径');
|
||||
}
|
||||
}
|
||||
|
||||
const options: any = {
|
||||
fs,
|
||||
http,
|
||||
dir: localPath,
|
||||
url,
|
||||
singleBranch: true,
|
||||
depth: 1,
|
||||
ref: branch,
|
||||
onProgress
|
||||
};
|
||||
|
||||
if (username || token) {
|
||||
options.onAuth = () => ({
|
||||
username: username || '',
|
||||
password: token || ''
|
||||
});
|
||||
}
|
||||
|
||||
await git.clone(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉取最新更改
|
||||
*/
|
||||
static async pullChanges(localPath: string): Promise<void> {
|
||||
await git.pull({
|
||||
fs: fs,
|
||||
http: http,
|
||||
dir: localPath,
|
||||
author: { name: 'DCSP User', email: 'user@dcsp.local' },
|
||||
fastForward: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建分支树结构
|
||||
*/
|
||||
static buildBranchTree(branches: GitBranch[]): any[] {
|
||||
const root: any[] = [];
|
||||
|
||||
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: any) => 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: string, branchName: string): Promise<void> {
|
||||
await execAsync(`git init && git checkout -b "${branchName}"`, { cwd: localPath });
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加远程仓库
|
||||
*/
|
||||
static async addRemote(
|
||||
localPath: string,
|
||||
repoUrl: string,
|
||||
username?: string,
|
||||
token?: string
|
||||
): Promise<void> {
|
||||
let finalUrl = repoUrl;
|
||||
|
||||
if (username && token) {
|
||||
const u = encodeURIComponent(username);
|
||||
const t = encodeURIComponent(token);
|
||||
finalUrl = repoUrl.replace('://', `://${u}:${t}@`);
|
||||
}
|
||||
|
||||
await execAsync(`git remote add origin "${finalUrl}"`, { cwd: localPath });
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交初始文件
|
||||
*/
|
||||
static async commitInitialFiles(localPath: string): Promise<void> {
|
||||
try {
|
||||
await execAsync('git add .', { cwd: localPath });
|
||||
await execAsync(
|
||||
`git commit -m "Initial commit from DCSP - ${new Date().toLocaleString()}"`,
|
||||
{ cwd: localPath }
|
||||
);
|
||||
} catch (error: any) {
|
||||
if (error.stderr?.includes('nothing to commit')) {
|
||||
console.log('没有需要提交的更改');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送到远程仓库
|
||||
*/
|
||||
static async pushToRemote(localPath: string, branchName: string): Promise<void> {
|
||||
await execAsync(`git push -u origin "${branchName}" --force`, { cwd: localPath });
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用Git命令提交并推送
|
||||
*/
|
||||
static async commitAndPush(localPath: string): Promise<void> {
|
||||
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: string): Promise<boolean> {
|
||||
try {
|
||||
const { stdout } = await execAsync('git status --porcelain', { cwd: localPath });
|
||||
return !!stdout.trim();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送到指定仓库URL
|
||||
*/
|
||||
static async pushToRepoUrl(
|
||||
localPath: string,
|
||||
repoUrl: string,
|
||||
branchName: string,
|
||||
username?: string,
|
||||
token?: string
|
||||
): Promise<void> {
|
||||
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', // 防止没有 shallow 时直接失败
|
||||
`git checkout -B "${branchName}"`,
|
||||
`git push -u "${finalUrl}" "${branchName}" --force`
|
||||
];
|
||||
|
||||
await execAsync(commands.join(' && '), { cwd: localPath });
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成模块文件夹名称
|
||||
*/
|
||||
static generateModuleFolderName(url: string, branch: string): { displayName: string; folderName: string } {
|
||||
const repoName = url.split('/').pop()?.replace('.git', '') || 'unknown-repo';
|
||||
const branchSafeName = branch.replace(/[^a-zA-Z0-9-_]/g, '-');
|
||||
|
||||
return {
|
||||
displayName: repoName,
|
||||
folderName: branchSafeName
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建文件树
|
||||
*/
|
||||
static async buildFileTree(dir: string, relativePath: string = ''): Promise<GitFileTree[]> {
|
||||
try {
|
||||
const files = await fs.promises.readdir(dir);
|
||||
const tree: GitFileTree[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
if (file.startsWith('.') && file !== '.git') continue;
|
||||
if (file === '.dcsp-data.json') 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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user