Skip to content

Commit

Permalink
✨ feat(svn): 增强SVN插件的路径检测与日志管理
Browse files Browse the repository at this point in the history
- 新增日志工具类Logger用于统一日志管理
- 【功能】实现多级SVN路径检测策略:
  - SVN插件配置
  - 自定义配置
  - 自动检测
  - 默认路径
- 【配置】添加SVN环境配置管理,支持自定义PATH和locale
- 【健壮性】优化SVN命令执行环境,增加初始化检查
- 新增getSvnPath工具函数实现路径检测逻辑
- 优化错误处理和日志输出机制
  • Loading branch information
littleCareless committed Feb 11, 2025
1 parent ff9c098 commit c90a69d
Showing 1 changed file with 176 additions and 13 deletions.
189 changes: 176 additions & 13 deletions src/scm/SvnProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,86 @@ import { getMessage, formatMessage } from "../utils/i18n";

const exec = promisify(childProcess.exec);

// 添加日志工具类
const enum LogLevel {
Info,
Warning,
Error
}

class Logger {
static log(level: LogLevel, message: string, ...args: any[]) {
switch (level) {
case LogLevel.Info:
// 可以根据环境配置是否输出info级别日志
if (process.env.NODE_ENV !== 'production') {
console.log(message, ...args);
}
break;
case LogLevel.Warning:
console.warn(message, ...args);
break;
case LogLevel.Error:
console.error(message, ...args);
break;
}
}
}

interface SvnConfig {
svnPath?: string;
environmentConfig: {
path: string[];
locale: string;
}
}

// 默认配置
const DEFAULT_CONFIG: SvnConfig = {
environmentConfig: {
path: ['/usr/local/bin', '/opt/homebrew/bin'],
locale: 'en_US.UTF-8'
}
};

// 添加 SVN 路径检测
const getSvnPath = async (): Promise<string> => {
try {
// 1. 优先检查SVN插件配置
const svnConfig = vscode.workspace.getConfiguration('svn');
const svnPluginPath = svnConfig.get<string>('path');
if (svnPluginPath) {
Logger.log(LogLevel.Info, 'Using SVN path from SVN plugin config:', svnPluginPath);
return svnPluginPath;
}

// 2. 检查自定义配置
const customConfig = vscode.workspace.getConfiguration('svn-commit-gen');
const customPath = customConfig.get<string>('svnPath');
if (customPath) {
Logger.log(LogLevel.Info, 'Using SVN path from custom config:', customPath);
return customPath;
}

// 3. 自动检测
const { stdout } = await exec('which svn');
const detectedPath = stdout.trim();
if (detectedPath) {
Logger.log(LogLevel.Info, 'Detected SVN path:', detectedPath);
return detectedPath;
}

// 4. 使用默认路径
const defaultPath = '/opt/homebrew/bin/svn';
Logger.log(LogLevel.Warning, 'Using default SVN path:', defaultPath);
return defaultPath;

} catch (error) {
Logger.log(LogLevel.Error, 'Failed to determine SVN path:', error);
throw new Error('Unable to locate SVN executable');
}
};

/**
* SVN源代码管理提供者实现
* @implements {ISCMProvider}
Expand All @@ -24,6 +104,12 @@ export class SvnProvider implements ISCMProvider {
/** SVN仓库集合 */
private repositories: any;

/** 存储 SVN 路径 */
private svnPath: string = 'svn';

private config: SvnConfig;
private initialized: boolean = false;

/**
* 创建SVN提供者实例
* @param svnExtension - VS Code SVN扩展实例
Expand All @@ -36,6 +122,78 @@ export class SvnProvider implements ISCMProvider {
throw new Error(getMessage("workspace.not.found"));
}
this.workspaceRoot = workspaceRoot;

// 初始化时设置 SVN 路径
getSvnPath().then(path => {
this.svnPath = path;
});

// 加载配置
this.config = this.loadConfig();

// 同步初始化
this.initialize();
}

private loadConfig(): SvnConfig {
try {
const config = vscode.workspace.getConfiguration('svn-commit-gen');
const envConfig = {
path: config.get<string[]>('environmentPath') || DEFAULT_CONFIG.environmentConfig.path,
locale: config.get<string>('locale') || DEFAULT_CONFIG.environmentConfig.locale
};

if (!Array.isArray(envConfig.path) || !envConfig.locale) {
throw new Error(getMessage("svn.invalid.env.config"));
}

return {
svnPath: config.get<string>('svnPath'),
environmentConfig: envConfig
};
} catch (error) {
Logger.log(LogLevel.Error, 'Failed to load SVN config:', error);
throw new Error(formatMessage("svn.config.load.failed", [error]));
}
}

private async initialize() {
try {
const svnPath = await getSvnPath();
this.svnPath = svnPath;
this.initialized = true;

// 验证SVN可执行
const { stdout } = await exec(`"${this.svnPath}" --version`);
Logger.log(LogLevel.Info, 'SVN version:', stdout.split('\n')[0]);
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
Logger.log(LogLevel.Error, 'Failed to initialize SVN:', message);
throw new Error(formatMessage("svn.initialization.failed", [message]));
}
}

private async detectSvnPath(): Promise<string | null> {
try {
const { stdout } = await exec('which svn');
const path = stdout.trim();
Logger.log(LogLevel.Info, 'Detected SVN path:', path);
return path;
} catch (error) {
Logger.log(LogLevel.Warning, formatMessage("svn.path.detection.failed", [error]));
return null;
}
}

private getEnvironmentConfig() {
if (!this.config?.environmentConfig) {
throw new Error(getMessage("svn.invalid.env.config"));
}
return {
...process.env,
PATH: `${process.env.PATH}:${this.config.environmentConfig.path.join(':')}`,
LC_ALL: this.config.environmentConfig.locale
};
}

/**
Expand Down Expand Up @@ -73,8 +231,13 @@ export class SvnProvider implements ISCMProvider {
*/
private async getFileStatus(file: string): Promise<string> {
try {
const { stdout: status } = await exec(`svn status "${file}"`, {
if (!this.initialized) {
throw new Error(getMessage("svn.not.initialized"));
}

const { stdout: status } = await exec(`"${this.svnPath}" status "${file}"`, {
cwd: this.workspaceRoot,
env: this.getEnvironmentConfig()
});

if (!status) {
Expand All @@ -89,10 +252,7 @@ export class SvnProvider implements ISCMProvider {
}
return "Modified File";
} catch (error) {
console.error(
"Failed to get file status:",
error instanceof Error ? error.message : error
);
Logger.log(LogLevel.Error, 'Failed to get file status:', error);
return "Unknown";
}
}
Expand All @@ -105,11 +265,15 @@ export class SvnProvider implements ISCMProvider {
*/
async getDiff(files?: string[]): Promise<string | undefined> {
try {
if (!this.initialized) {
throw new Error(getMessage("svn.not.initialized"));
}

let diffOutput = "";

if (files && files.length > 0) {
for (const file of files) {
console.log("file", file);
Logger.log(LogLevel.Info, 'Processing file:', file);
const fileStatus = await this.getFileStatus(file);
const escapedFile = file.replace(/"/g, '\\"');

Expand All @@ -118,20 +282,22 @@ export class SvnProvider implements ISCMProvider {
diffOutput += `\n=== ${fileStatus}: ${file} ===\n`;
continue;
}

const { stdout } = await exec(`svn diff "${escapedFile}"`, {
console.log("escapedFile", escapedFile);
const { stdout } = await exec(`"${this.svnPath}" diff "${escapedFile}"`, {
cwd: this.workspaceRoot,
maxBuffer: 1024 * 1024 * 10,
env: this.getEnvironmentConfig()
});

if (stdout.trim()) {
diffOutput += `\n=== ${fileStatus}: ${file} ===\n${stdout}`;
}
}
} else {
const { stdout } = await exec("svn diff", {
const { stdout } = await exec(`"${this.svnPath}" diff`, {
cwd: this.workspaceRoot,
maxBuffer: 1024 * 1024 * 10,
env: this.getEnvironmentConfig()
});
diffOutput = stdout;
}
Expand All @@ -157,10 +323,7 @@ export class SvnProvider implements ISCMProvider {
// 如果未启用简化,直接返回原始diff
return diffOutput;
} catch (error) {
console.error(
"SVN diff failed:",
error instanceof Error ? error.message : error
);
Logger.log(LogLevel.Error, 'SVN diff failed:', error);
if (error instanceof Error) {
vscode.window.showErrorMessage(
formatMessage("git.diff.failed", [error.message])
Expand Down

0 comments on commit c90a69d

Please sign in to comment.