Skip to content

Commit 61799a9

Browse files
feat(svn): 添加SVN调试功能
添加SVN集成调试功能,包括: 1. 新增debugSvn命令用于检查SVN安装和仓库状态 2. 实现SvnLogger用于记录调试信息 3. 增强现有SVN工具函数添加日志记录 4. 在package.json注册新命令
1 parent 4cf80d5 commit 61799a9

File tree

4 files changed

+206
-17
lines changed

4 files changed

+206
-17
lines changed

packages/types/src/vscode.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export const commandIds = [
5353
"focusInput",
5454
"acceptInput",
5555
"focusPanel",
56+
57+
"debugSvn",
5658
] as const
5759

5860
export type CommandId = (typeof commandIds)[number]

src/activate/registerCommands.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { CodeIndexManager } from "../services/code-index/manager"
1616
import { importSettingsWithFeedback } from "../core/config/importExport"
1717
import { MdmService } from "../services/mdm/MdmService"
1818
import { t } from "../i18n"
19+
import { checkSvnInstalled, checkSvnRepo, getSvnRepositoryInfo, searchSvnCommits, SvnLogger } from "../utils/svn"
1920

2021
/**
2122
* Helper to get the visible ClineProvider instance or log if not found.
@@ -218,6 +219,60 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
218219

219220
visibleProvider.postMessageToWebview({ type: "acceptInput" })
220221
},
222+
debugSvn: async () => {
223+
const workspaceFolders = vscode.workspace.workspaceFolders
224+
if (!workspaceFolders || workspaceFolders.length === 0) {
225+
vscode.window.showErrorMessage("No workspace folder is open")
226+
return
227+
}
228+
229+
const workspaceRoot = workspaceFolders[0].uri.fsPath
230+
SvnLogger.info(`Starting SVN debug for workspace: ${workspaceRoot}`)
231+
232+
try {
233+
// 检查 SVN 是否安装
234+
SvnLogger.info("Checking if SVN is installed...")
235+
const svnInstalled = await checkSvnInstalled()
236+
SvnLogger.info(`SVN installed: ${svnInstalled}`)
237+
238+
if (!svnInstalled) {
239+
vscode.window.showErrorMessage("SVN is not installed or not in PATH")
240+
return
241+
}
242+
243+
// 检查是否是 SVN 仓库
244+
SvnLogger.info("Checking if current directory is an SVN repository...")
245+
const isSvnRepo = await checkSvnRepo(workspaceRoot)
246+
SvnLogger.info(`Is SVN repository: ${isSvnRepo}`)
247+
248+
if (!isSvnRepo) {
249+
vscode.window.showWarningMessage("Current workspace is not an SVN repository")
250+
return
251+
}
252+
253+
// 获取 SVN 仓库信息
254+
SvnLogger.info("Getting SVN repository information...")
255+
const repoInfo = await getSvnRepositoryInfo(workspaceRoot)
256+
SvnLogger.info(`Repository info: ${JSON.stringify(repoInfo, null, 2)}`)
257+
258+
// 搜索最近的提交
259+
SvnLogger.info("Searching for recent commits...")
260+
const commits = await searchSvnCommits("", workspaceRoot, 5)
261+
SvnLogger.info(`Found ${commits.length} commits`)
262+
commits.forEach((commit, index) => {
263+
SvnLogger.info(`Commit ${index + 1}: r${commit.revision} - ${commit.message}`)
264+
})
265+
266+
vscode.window.showInformationMessage(
267+
`SVN Debug Complete! Found ${commits.length} commits. Check "Roo Code - SVN Debug" output channel for details.`,
268+
)
269+
} catch (error) {
270+
SvnLogger.error("SVN debug failed", error)
271+
vscode.window.showErrorMessage(
272+
`SVN debug failed: ${error instanceof Error ? error.message : String(error)}`,
273+
)
274+
}
275+
},
221276
})
222277

223278
export const openClineInNewTab = async ({ context, outputChannel }: Omit<RegisterCommandOptions, "provider">) => {

src/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@
174174
"command": "roo-cline.acceptInput",
175175
"title": "%command.acceptInput.title%",
176176
"category": "%configuration.title%"
177+
},
178+
{
179+
"command": "roo-cline.debugSvn",
180+
"title": "Debug SVN Integration",
181+
"category": "%configuration.title%"
177182
}
178183
],
179184
"menus": {

src/utils/svn.ts

Lines changed: 144 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,70 @@ import { truncateOutput } from "../integrations/misc/extract-text"
88
const execAsync = promisify(exec)
99
const SVN_OUTPUT_LINE_LIMIT = 500
1010

11+
// SVN Debug Logger
12+
export class SvnLogger {
13+
private static outputChannel: vscode.OutputChannel | null = null
14+
15+
static getOutputChannel(): vscode.OutputChannel {
16+
if (!this.outputChannel) {
17+
this.outputChannel = vscode.window.createOutputChannel("Roo Code - SVN Debug")
18+
}
19+
return this.outputChannel
20+
}
21+
22+
static debug(message: string, ...args: any[]) {
23+
const timestamp = new Date().toISOString()
24+
const logMessage = `[${timestamp}] [DEBUG] ${message}`
25+
26+
// Log to console
27+
console.log(logMessage, ...args)
28+
29+
// Log to VS Code output channel
30+
const channel = this.getOutputChannel()
31+
channel.appendLine(logMessage)
32+
if (args.length > 0) {
33+
channel.appendLine(` Args: ${JSON.stringify(args, null, 2)}`)
34+
}
35+
}
36+
37+
static error(message: string, error?: any) {
38+
const timestamp = new Date().toISOString()
39+
const logMessage = `[${timestamp}] [ERROR] ${message}`
40+
41+
// Log to console
42+
console.error(logMessage, error)
43+
44+
// Log to VS Code output channel
45+
const channel = this.getOutputChannel()
46+
channel.appendLine(logMessage)
47+
if (error) {
48+
channel.appendLine(` Error: ${error.toString()}`)
49+
if (error.stack) {
50+
channel.appendLine(` Stack: ${error.stack}`)
51+
}
52+
}
53+
}
54+
55+
static info(message: string, ...args: any[]) {
56+
const timestamp = new Date().toISOString()
57+
const logMessage = `[${timestamp}] [INFO] ${message}`
58+
59+
// Log to console
60+
console.log(logMessage, ...args)
61+
62+
// Log to VS Code output channel
63+
const channel = this.getOutputChannel()
64+
channel.appendLine(logMessage)
65+
if (args.length > 0) {
66+
channel.appendLine(` Data: ${JSON.stringify(args, null, 2)}`)
67+
}
68+
}
69+
70+
static showOutput() {
71+
this.getOutputChannel().show()
72+
}
73+
}
74+
1175
export interface SvnRepositoryInfo {
1276
repositoryUrl?: string
1377
repositoryName?: string
@@ -27,42 +91,57 @@ export interface SvnCommit {
2791
* @returns SVN repository information or empty object if not an SVN repository
2892
*/
2993
export async function getSvnRepositoryInfo(workspaceRoot: string): Promise<SvnRepositoryInfo> {
94+
SvnLogger.debug("getSvnRepositoryInfo called", { workspaceRoot })
95+
3096
try {
3197
const svnDir = path.join(workspaceRoot, ".svn")
98+
SvnLogger.debug("Checking SVN directory", { svnDir })
3299

33100
// Check if .svn directory exists
34101
try {
35102
await fs.access(svnDir)
36-
} catch {
37-
// Not an SVN repository
103+
SvnLogger.debug("SVN directory found")
104+
} catch (error) {
105+
SvnLogger.debug("SVN directory not found - not an SVN repository", {
106+
error: (error instanceof Error ? error : new Error(String(error))).toString(),
107+
})
38108
return {}
39109
}
40110

41111
const svnInfo: SvnRepositoryInfo = {}
42112

43113
// Try to get SVN info using svn info command
44114
try {
115+
SvnLogger.debug("Executing 'svn info' command", { cwd: workspaceRoot })
45116
const { stdout } = await execAsync("svn info", { cwd: workspaceRoot })
117+
SvnLogger.debug("SVN info command output", { stdout })
46118

47119
// Parse SVN info output
48120
const urlMatch = stdout.match(/^URL:\s*(.+)$/m)
49121
if (urlMatch && urlMatch[1]) {
50122
const url = urlMatch[1].trim()
51123
svnInfo.repositoryUrl = url
52124
svnInfo.repositoryName = extractSvnRepositoryName(url)
125+
SvnLogger.debug("Extracted repository info", { url, repositoryName: svnInfo.repositoryName })
126+
} else {
127+
SvnLogger.debug("No URL found in SVN info output")
53128
}
54129

55130
const rootMatch = stdout.match(/^Working Copy Root Path:\s*(.+)$/m)
56131
if (rootMatch && rootMatch[1]) {
57132
svnInfo.workingCopyRoot = rootMatch[1].trim()
133+
SvnLogger.debug("Found working copy root", { workingCopyRoot: svnInfo.workingCopyRoot })
134+
} else {
135+
SvnLogger.debug("No working copy root found in SVN info output")
58136
}
59137
} catch (error) {
60-
// Ignore SVN info errors
138+
SvnLogger.error("SVN info command failed", error instanceof Error ? error : new Error(String(error)))
61139
}
62140

141+
SvnLogger.info("Final SVN repository info", svnInfo)
63142
return svnInfo
64143
} catch (error) {
65-
// Return empty object on any error
144+
SvnLogger.error("Error in getSvnRepositoryInfo", error instanceof Error ? error : new Error(String(error)))
66145
return {}
67146
}
68147
}
@@ -118,11 +197,18 @@ export async function getWorkspaceSvnInfo(): Promise<SvnRepositoryInfo> {
118197
* @returns True if it's an SVN repository, false otherwise
119198
*/
120199
export async function checkSvnRepo(cwd: string): Promise<boolean> {
200+
SvnLogger.debug("checkSvnRepo called", { cwd })
201+
121202
try {
122203
const svnDir = path.join(cwd, ".svn")
123204
await fs.access(svnDir)
205+
SvnLogger.debug("SVN repository detected", { svnDir })
124206
return true
125-
} catch {
207+
} catch (error) {
208+
SvnLogger.debug("Not an SVN repository", {
209+
cwd,
210+
error: (error instanceof Error ? error : new Error(String(error))).toString(),
211+
})
126212
return false
127213
}
128214
}
@@ -132,10 +218,17 @@ export async function checkSvnRepo(cwd: string): Promise<boolean> {
132218
* @returns True if SVN is available, false otherwise
133219
*/
134220
export async function checkSvnInstalled(): Promise<boolean> {
221+
SvnLogger.debug("checkSvnInstalled called")
222+
135223
try {
136-
await execAsync("svn --version")
224+
const { stdout } = await execAsync("svn --version")
225+
SvnLogger.debug("SVN is installed", { version: stdout.split("\n")[0] })
137226
return true
138-
} catch {
227+
} catch (error) {
228+
SvnLogger.error(
229+
"SVN is not installed or not available",
230+
error instanceof Error ? error : new Error(String(error)),
231+
)
139232
return false
140233
}
141234
}
@@ -146,49 +239,83 @@ export async function checkSvnInstalled(): Promise<boolean> {
146239
* @param cwd The working directory
147240
* @returns Array of matching SVN commits
148241
*/
149-
export async function searchSvnCommits(query: string, cwd: string): Promise<SvnCommit[]> {
242+
export async function searchSvnCommits(query: string, cwd: string, p0: number): Promise<SvnCommit[]> {
243+
SvnLogger.debug("searchSvnCommits called", { query, cwd })
244+
150245
try {
151246
// Check if SVN is available
152-
if (!(await checkSvnInstalled()) || !(await checkSvnRepo(cwd))) {
247+
const svnInstalled = await checkSvnInstalled()
248+
const isSvnRepo = await checkSvnRepo(cwd)
249+
250+
SvnLogger.debug("SVN availability check", { svnInstalled, isSvnRepo })
251+
252+
if (!svnInstalled || !isSvnRepo) {
253+
SvnLogger.debug("SVN not available or not a repository, returning empty array")
153254
return []
154255
}
155256

156257
const commits: SvnCommit[] = []
157258

158259
// If query looks like a revision number, search for that specific revision
159260
if (/^\d+$/.test(query)) {
261+
SvnLogger.debug("Query looks like revision number, searching for specific revision", { revision: query })
160262
try {
161-
const { stdout } = await execAsync(`svn log -r ${query} --xml`, { cwd })
263+
const command = `svn log -r ${query} --xml`
264+
SvnLogger.debug("Executing revision search command", { command, cwd })
265+
266+
const { stdout } = await execAsync(command, { cwd })
267+
SvnLogger.debug("Revision search output", { stdout })
268+
162269
const revisionCommits = parseSvnLogXml(stdout)
163270
commits.push(...revisionCommits)
164-
} catch {
165-
// Revision might not exist, continue with general search
271+
SvnLogger.debug("Found commits for revision", {
272+
count: revisionCommits.length,
273+
commits: revisionCommits,
274+
})
275+
} catch (error) {
276+
SvnLogger.debug("Revision search failed, continuing with general search", {
277+
error: (error instanceof Error ? error : new Error(String(error))).toString(),
278+
})
166279
}
167280
}
168281

169282
// Search in commit messages (get recent commits and filter)
170283
try {
171-
const { stdout } = await execAsync("svn log -l 100 --xml", { cwd })
284+
const command = "svn log -l 100 --xml"
285+
SvnLogger.debug("Executing message search command", { command, cwd })
286+
287+
const { stdout } = await execAsync(command, { cwd })
288+
SvnLogger.debug("Message search output length", { outputLength: stdout.length })
289+
172290
const allCommits = parseSvnLogXml(stdout)
291+
SvnLogger.debug("Parsed all commits", { count: allCommits.length })
173292

174293
// Filter commits by message content
175294
const messageMatches = allCommits.filter(
176295
(commit) => commit.message.toLowerCase().includes(query.toLowerCase()) || commit.revision === query,
177296
)
297+
SvnLogger.debug("Filtered commits by message", { matchCount: messageMatches.length, query })
178298

179299
// Add unique commits (avoid duplicates from revision search)
180300
messageMatches.forEach((commit) => {
181301
if (!commits.some((c) => c.revision === commit.revision)) {
182302
commits.push(commit)
183303
}
184304
})
185-
} catch {
186-
// Ignore errors in message search
305+
} catch (error) {
306+
SvnLogger.error("Message search failed", error instanceof Error ? error : new Error(String(error)))
187307
}
188308

189-
return commits.slice(0, 20) // Limit results
309+
const finalCommits = commits.slice(0, 20) // Limit results
310+
SvnLogger.info("Search completed", {
311+
query,
312+
totalFound: commits.length,
313+
returned: finalCommits.length,
314+
commits: finalCommits,
315+
})
316+
return finalCommits
190317
} catch (error) {
191-
console.error("Error searching SVN commits:", error)
318+
SvnLogger.error("Error searching SVN commits", error instanceof Error ? error : new Error(String(error)))
192319
return []
193320
}
194321
}

0 commit comments

Comments
 (0)