Skip to content

feat: Add SVN commit support with GUI interface similar to Git commits #6117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3ff0042
build: 将 node-ipc 依赖版本从 ^12.0.0 降级至 ^11.1.0
ubuntutester202 Jul 21, 2025
e692bef
refactor(global.d.ts): 移除不必要的eslint禁用注释
ubuntutester202 Jul 21, 2025
4cf80d5
feat(svn): 添加SVN提交搜索功能和相关组件支持
ubuntutester202 Jul 22, 2025
61799a9
feat(svn): 添加SVN调试功能
ubuntutester202 Jul 22, 2025
5de97ab
feat(svn): 添加对svn-changes提及的支持,主要是让API请求包含svn的status、diff信息
ubuntutester202 Jul 22, 2025
15bcf77
feat(svn): 当存在未跟踪文件时显示内容预览
ubuntutester202 Jul 22, 2025
3124968
feat(svn): 添加对SVN版本号提及的支持
ubuntutester202 Jul 22, 2025
d6fcee2
docs(registerCommands): 将注释从中文改为英文
ubuntutester202 Jul 22, 2025
e0de69c
fix: 添加对SVN目录的排除并改进错误处理
ubuntutester202 Jul 22, 2025
d5e565c
feat(svn): 增强SVN调试功能并改进错误处理
ubuntutester202 Jul 23, 2025
e7e5631
build: 升级 node-ipc 依赖至 v12.0.0
ubuntutester202 Jul 23, 2025
f5e366f
feat(svn): 添加SVN工具函数的单元测试
ubuntutester202 Jul 23, 2025
b558b32
feat(svn): 增强SVN提交信息获取功能,添加版本号格式验证
ubuntutester202 Jul 23, 2025
0015192
fix(test): 修复一开始的依赖环境问题。版本不同会导致check types失败等问题。
ubuntutester202 Jul 23, 2025
cf20133
refactor: 清理processUserContentMentions.ts中的调试语句,与主分支保持一致
ubuntutester202 Jul 25, 2025
14852c0
feat(svn): Add SVN context function support, and is turned off by def…
ubuntutester202 Jul 25, 2025
122b557
feat(i18n): Add international support for SVN context function
ubuntutester202 Jul 25, 2025
758ba9d
Merge main into feat-6100: Resolve conflicts and integrate diagnostic…
ubuntutester202 Jul 25, 2025
5b860fe
fix(test): Fix TypeScript errors,Add missing enableSvnContext to test…
ubuntutester202 Jul 25, 2025
9497c21
fix(svn): Unify the SVN revision number format to the 'r' prefix and …
ubuntutester202 Jul 25, 2025
5d7e3de
feat(svn): Add cross-platform SVN output encoding conversion function
ubuntutester202 Jul 25, 2025
b4d2b08
feat(test): Adds comprehensive test of SVN submission information
ubuntutester202 Jul 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 40 additions & 38 deletions locales/ca/README.md

Large diffs are not rendered by default.

78 changes: 40 additions & 38 deletions locales/de/README.md

Large diffs are not rendered by default.

78 changes: 40 additions & 38 deletions locales/es/README.md

Large diffs are not rendered by default.

78 changes: 40 additions & 38 deletions locales/fr/README.md

Large diffs are not rendered by default.

78 changes: 40 additions & 38 deletions locales/hi/README.md

Large diffs are not rendered by default.

78 changes: 40 additions & 38 deletions locales/id/README.md

Large diffs are not rendered by default.

78 changes: 40 additions & 38 deletions locales/it/README.md

Large diffs are not rendered by default.

78 changes: 40 additions & 38 deletions locales/ja/README.md

Large diffs are not rendered by default.

78 changes: 40 additions & 38 deletions locales/ko/README.md

Large diffs are not rendered by default.

78 changes: 40 additions & 38 deletions locales/nl/README.md

Large diffs are not rendered by default.

78 changes: 40 additions & 38 deletions locales/pl/README.md

Large diffs are not rendered by default.

78 changes: 40 additions & 38 deletions locales/pt-BR/README.md

Large diffs are not rendered by default.

78 changes: 40 additions & 38 deletions locales/ru/README.md

Large diffs are not rendered by default.

78 changes: 40 additions & 38 deletions locales/tr/README.md

Large diffs are not rendered by default.

78 changes: 40 additions & 38 deletions locales/vi/README.md

Large diffs are not rendered by default.

78 changes: 40 additions & 38 deletions locales/zh-CN/README.md

Large diffs are not rendered by default.

78 changes: 40 additions & 38 deletions locales/zh-TW/README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export const globalSettingsSchema = z.object({
maxOpenTabsContext: z.number().optional(),
maxWorkspaceFiles: z.number().optional(),
showRooIgnoredFiles: z.boolean().optional(),
enableSvnContext: z.boolean().optional(),
maxReadFileLine: z.number().optional(),

terminalOutputLineLimit: z.number().optional(),
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ export const commandIds = [
"focusInput",
"acceptInput",
"focusPanel",

"debugSvn",
] as const

export type CommandId = (typeof commandIds)[number]
Expand Down
77 changes: 77 additions & 0 deletions src/activate/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { CodeIndexManager } from "../services/code-index/manager"
import { importSettingsWithFeedback } from "../core/config/importExport"
import { MdmService } from "../services/mdm/MdmService"
import { t } from "../i18n"
import { checkSvnInstalled, checkSvnRepo, getSvnRepositoryInfo, searchSvnCommits, SvnLogger } from "../utils/svn"

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

visibleProvider.postMessageToWebview({ type: "acceptInput" })
},
debugSvn: async () => {
const workspaceFolders = vscode.workspace.workspaceFolders
if (!workspaceFolders || workspaceFolders.length === 0) {
vscode.window.showErrorMessage("No workspace folder is open")
return
}

const workspaceRoot = workspaceFolders[0].uri.fsPath
SvnLogger.info(`Starting SVN debug for workspace: ${workspaceRoot}`)

try {
// Check SVN installation and repository status
// Note: These functions now handle their own user-friendly error messages
SvnLogger.info("Checking if SVN is installed...")
const svnInstalled = await checkSvnInstalled()
SvnLogger.info(`SVN installed: ${svnInstalled}`)

if (!svnInstalled) {
// Error message already shown by checkSvnInstalled
return
}

SvnLogger.info("Checking if current directory is an SVN repository...")
const isSvnRepo = await checkSvnRepo(workspaceRoot)
SvnLogger.info(`Is SVN repository: ${isSvnRepo}`)

if (!isSvnRepo) {
// Error message already shown by checkSvnRepo
return
}

// Get SVN repository information
SvnLogger.info("Getting SVN repository information...")
const repoInfo = await getSvnRepositoryInfo(workspaceRoot)
SvnLogger.info(`Repository info: ${JSON.stringify(repoInfo, null, 2)}`)

// Search for recent commits
SvnLogger.info("Searching for recent commits...")
const commits = await searchSvnCommits("", workspaceRoot)
SvnLogger.info(`Found ${commits.length} commits`)

commits.forEach((commit, index) => {
SvnLogger.info(`Commit ${index + 1}: r${commit.revision} - ${commit.message}`)
})

// Show success message
const action = await vscode.window.showInformationMessage(
`SVN Debug Complete! Found ${commits.length} commits.\n\nRepository URL: ${repoInfo.repositoryUrl || "Unknown"}\nWorking Copy Root: ${repoInfo.workingCopyRoot || "Unknown"}\n\nCheck "Roo Code - SVN Debug" output channel for detailed information.`,
{ modal: false },
"Show Output",
)

if (action === "Show Output") {
SvnLogger.showOutput()
}
} catch (error) {
// This should rarely happen now since individual functions handle their own errors
const svnError = error instanceof Error ? error : new Error(String(error))
SvnLogger.error("SVN debug failed", svnError)

vscode.window
.showErrorMessage(
"SVN debug operation failed",
{
modal: false,
detail: `Unexpected error: ${svnError.message}\n\nPlease check the SVN Debug output channel for more details.`,
},
"Show Output",
)
.then((action) => {
if (action === "Show Output") {
SvnLogger.showOutput()
}
})
}
},
})

export const openClineInNewTab = async ({ context, outputChannel }: Omit<RegisterCommandOptions, "provider">) => {
Expand Down
4 changes: 2 additions & 2 deletions src/api/providers/bedrock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,8 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH

if (this.options.awsUseApiKey && this.options.awsApiKey) {
// Use API key/token-based authentication if enabled and API key is set
clientConfig.token = { token: this.options.awsApiKey }
clientConfig.authSchemePreference = ["httpBearerAuth"] // Otherwise there's no end of credential problems.
;(clientConfig as any).token = { token: this.options.awsApiKey }
;(clientConfig as any).authSchemePreference = ["httpBearerAuth"] // Otherwise there's no end of credential problems.
} else if (this.options.awsUseProfile && this.options.awsProfile) {
// Use profile-based credentials if enabled and profile is set
clientConfig.credentials = fromIni({
Expand Down
36 changes: 36 additions & 0 deletions src/core/mentions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { isBinaryFile } from "isbinaryfile"
import { mentionRegexGlobal, unescapeSpaces } from "../../shared/context-mentions"

import { getCommitInfo, getWorkingState } from "../../utils/git"
import { getSvnWorkingState, getSvnCommitInfoForMentions } from "../../utils/svn"
import { getWorkspacePath } from "../../utils/path"

import { openFile } from "../../integrations/misc/open-file"
Expand Down Expand Up @@ -80,6 +81,7 @@ export async function parseMentions(
fileContextTracker?: FileContextTracker,
rooIgnoreController?: RooIgnoreController,
showRooIgnoredFiles: boolean = true,
enableSvnContext: boolean = false,
includeDiagnosticMessages: boolean = true,
maxDiagnosticMessages: number = 50,
): Promise<string> {
Expand All @@ -97,8 +99,12 @@ export async function parseMentions(
return `Workspace Problems (see below for diagnostics)`
} else if (mention === "git-changes") {
return `Working directory changes (see below for details)`
} else if (enableSvnContext && mention === "svn-changes") {
return `Working directory changes (see below for details)`
} else if (/^[a-f0-9]{7,40}$/.test(mention)) {
return `Git commit '${mention}' (see below for commit info)`
} else if (enableSvnContext && /^r\d+$/.test(mention)) {
return `SVN revision '${mention}' (see below for commit info)`
} else if (mention === "terminal") {
return `Terminal Output (see below for output)`
}
Expand Down Expand Up @@ -179,13 +185,43 @@ export async function parseMentions(
} catch (error) {
parsedText += `\n\n<git_working_state>\nError fetching working state: ${error.message}\n</git_working_state>`
}
} else if (enableSvnContext && mention === "svn-changes") {
try {
const svnWorkingState = await getSvnWorkingState(cwd)
console.log("[DEBUG] SVN working state object:", JSON.stringify(svnWorkingState, null, 2))

// Format the SVN working state properly
let formattedState = ""
if (svnWorkingState.status) {
formattedState += `Status:\n${svnWorkingState.status}\n\n`
}
if (svnWorkingState.diff) {
formattedState += `Diff:\n${svnWorkingState.diff}`
}
if (!formattedState.trim()) {
formattedState = "No changes detected in working directory."
}

console.log("[DEBUG] Formatted SVN state for AI:", formattedState)
parsedText += `\n\n<svn_working_state>\n${formattedState}\n</svn_working_state>`
} catch (error) {
console.error("[DEBUG] Error fetching SVN working state:", error)
parsedText += `\n\n<svn_working_state>\nError fetching SVN working state: ${error.message}\n</svn_working_state>`
}
} else if (/^[a-f0-9]{7,40}$/.test(mention)) {
try {
const commitInfo = await getCommitInfo(mention, cwd)
parsedText += `\n\n<git_commit hash="${mention}">\n${commitInfo}\n</git_commit>`
} catch (error) {
parsedText += `\n\n<git_commit hash="${mention}">\nError fetching commit info: ${error.message}\n</git_commit>`
}
} else if (enableSvnContext && /^r\d+$/.test(mention)) {
try {
const commitInfo = await getSvnCommitInfoForMentions(mention, cwd)
parsedText += `\n\n<svn_commit revision="${mention}">\n${commitInfo}\n</svn_commit>`
} catch (error) {
parsedText += `\n\n<svn_commit revision="${mention}">\nError fetching commit info: ${error.message}\n</svn_commit>`
}
} else if (mention === "terminal") {
try {
const terminalOutput = await getLatestTerminalOutput()
Expand Down
5 changes: 5 additions & 0 deletions src/core/mentions/processUserContentMentions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export async function processUserContentMentions({
fileContextTracker,
rooIgnoreController,
showRooIgnoredFiles = true,
enableSvnContext = false,
includeDiagnosticMessages = true,
maxDiagnosticMessages = 50,
}: {
Expand All @@ -22,6 +23,7 @@ export async function processUserContentMentions({
fileContextTracker: FileContextTracker
rooIgnoreController?: any
showRooIgnoredFiles?: boolean
enableSvnContext?: boolean
includeDiagnosticMessages?: boolean
maxDiagnosticMessages?: number
}) {
Expand Down Expand Up @@ -50,6 +52,7 @@ export async function processUserContentMentions({
fileContextTracker,
rooIgnoreController,
showRooIgnoredFiles,
enableSvnContext,
includeDiagnosticMessages,
maxDiagnosticMessages,
),
Expand All @@ -69,6 +72,7 @@ export async function processUserContentMentions({
fileContextTracker,
rooIgnoreController,
showRooIgnoredFiles,
enableSvnContext,
includeDiagnosticMessages,
maxDiagnosticMessages,
),
Expand All @@ -89,6 +93,7 @@ export async function processUserContentMentions({
fileContextTracker,
rooIgnoreController,
showRooIgnoredFiles,
enableSvnContext,
includeDiagnosticMessages,
maxDiagnosticMessages,
),
Expand Down
2 changes: 2 additions & 0 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,7 @@ export class Task extends EventEmitter<ClineEvents> {

const {
showRooIgnoredFiles = true,
enableSvnContext = false,
includeDiagnosticMessages = true,
maxDiagnosticMessages = 50,
} = (await this.providerRef.deref()?.getState()) ?? {}
Expand All @@ -1239,6 +1240,7 @@ export class Task extends EventEmitter<ClineEvents> {
fileContextTracker: this.fileContextTracker,
rooIgnoreController: this.rooIgnoreController,
showRooIgnoredFiles,
enableSvnContext,
includeDiagnosticMessages,
maxDiagnosticMessages,
})
Expand Down
5 changes: 5 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,8 @@ export class ClineProvider
profileThresholds,
alwaysAllowFollowupQuestions,
followupAutoApproveTimeoutMs,
diagnosticsEnabled,
enableSvnContext,
includeDiagnosticMessages,
maxDiagnosticMessages,
} = await this.getState()
Expand Down Expand Up @@ -1561,6 +1563,8 @@ export class ClineProvider
hasOpenedModeSelector: this.getGlobalState("hasOpenedModeSelector") ?? false,
alwaysAllowFollowupQuestions: alwaysAllowFollowupQuestions ?? false,
followupAutoApproveTimeoutMs: followupAutoApproveTimeoutMs ?? 60000,
diagnosticsEnabled: diagnosticsEnabled ?? true,
enableSvnContext: enableSvnContext ?? false,
includeDiagnosticMessages: includeDiagnosticMessages ?? true,
maxDiagnosticMessages: maxDiagnosticMessages ?? 50,
}
Expand Down Expand Up @@ -1701,6 +1705,7 @@ export class ClineProvider
browserToolEnabled: stateValues.browserToolEnabled ?? true,
telemetrySetting: stateValues.telemetrySetting || "unset",
showRooIgnoredFiles: stateValues.showRooIgnoredFiles ?? true,
enableSvnContext: stateValues.enableSvnContext ?? false,
maxReadFileLine: stateValues.maxReadFileLine ?? -1,
maxConcurrentFileReads: stateValues.maxConcurrentFileReads ?? 5,
historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false,
Expand Down
3 changes: 3 additions & 0 deletions src/core/webview/__tests__/ClineProvider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,9 @@ describe("ClineProvider", () => {
profileThresholds: {},
hasOpenedModeSelector: false,
diagnosticsEnabled: true,
enableSvnContext: true,
includeDiagnosticMessages: true,
maxDiagnosticMessages: 50,
}

const message: ExtensionMessage = {
Expand Down
23 changes: 23 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { fileExistsAtPath } from "../../utils/fs"
import { playTts, setTtsEnabled, setTtsSpeed, stopTts } from "../../utils/tts"
import { singleCompletionHandler } from "../../utils/single-completion-handler"
import { searchCommits } from "../../utils/git"
import { searchSvnCommits } from "../../utils/svn"
import { exportSettings, importSettingsWithFeedback } from "../config/importExport"
import { getOpenAiModels } from "../../api/providers/openai"
import { getVsCodeLmModels } from "../../api/providers/vscode-lm"
Expand Down Expand Up @@ -1257,6 +1258,10 @@ export const webviewMessageHandler = async (
await updateGlobalState("showRooIgnoredFiles", message.bool ?? true)
await provider.postStateToWebview()
break
case "enableSvnContext":
await updateGlobalState("enableSvnContext", message.bool ?? true)
await provider.postStateToWebview()
break
case "hasOpenedModeSelector":
await updateGlobalState("hasOpenedModeSelector", message.bool ?? true)
await provider.postStateToWebview()
Expand Down Expand Up @@ -1410,6 +1415,24 @@ export const webviewMessageHandler = async (
}
break
}
case "searchSvnCommits": {
const cwd = provider.cwd
if (cwd) {
try {
const svnCommits = await searchSvnCommits(message.query || "", cwd)
await provider.postMessageToWebview({
type: "svnCommitSearchResults",
svnCommits,
})
} catch (error) {
provider.log(
`Error searching SVN commits: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
vscode.window.showErrorMessage("Error searching SVN commits")
}
}
break
}
case "searchFiles": {
const workspacePath = getWorkspacePath()

Expand Down
13 changes: 8 additions & 5 deletions src/integrations/editor/DiffViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,10 @@ export class DiffViewProvider {
}
}

async saveChanges(diagnosticsEnabled: boolean = true, writeDelayMs: number = DEFAULT_WRITE_DELAY_MS): Promise<{
async saveChanges(
diagnosticsEnabled: boolean = true,
writeDelayMs: number = DEFAULT_WRITE_DELAY_MS,
): Promise<{
newProblemsMessage: string | undefined
userEdits: string | undefined
finalContent: string | undefined
Expand Down Expand Up @@ -222,22 +225,22 @@ export class DiffViewProvider {
// and can address them accordingly. If problems don't change immediately after
// applying a fix, won't be notified, which is generally fine since the
// initial fix is usually correct and it may just take time for linters to catch up.

let newProblemsMessage = ""

if (diagnosticsEnabled) {
// Add configurable delay to allow linters time to process and clean up issues
// like unused imports (especially important for Go and other languages)
// Ensure delay is non-negative
const safeDelayMs = Math.max(0, writeDelayMs)

try {
await delay(safeDelayMs)
} catch (error) {
// Log error but continue - delay failure shouldn't break the save operation
console.warn(`Failed to apply write delay: ${error}`)
}

const postDiagnostics = vscode.languages.getDiagnostics()

// Get diagnostic settings from state
Expand Down
5 changes: 5 additions & 0 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,11 @@
"command": "roo-cline.acceptInput",
"title": "%command.acceptInput.title%",
"category": "%configuration.title%"
},
{
"command": "roo-cline.debugSvn",
"title": "%command.debugSvn.title%",
"category": "%configuration.title%"
}
],
"menus": {
Expand Down
1 change: 1 addition & 0 deletions src/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"command.terminal.fixCommand.title": "Fix This Command",
"command.terminal.explainCommand.title": "Explain This Command",
"command.acceptInput.title": "Accept Input/Suggestion",
"command.debugSvn.title": "Debug SVN Integration",
"configuration.title": "Roo Code",
"commands.allowedCommands.description": "Commands that can be auto-executed when 'Always approve execute operations' is enabled",
"commands.deniedCommands.description": "Command prefixes that will be automatically denied without asking for approval. In case of conflicts with allowed commands, the longest prefix match takes precedence. Add * to deny all commands.",
Expand Down
1 change: 1 addition & 0 deletions src/services/checkpoints/excludes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ const getLfsPatterns = async (workspacePath: string) => {

export const getExcludePatterns = async (workspacePath: string) => [
".git/",
".svn/",
...getBuildArtifactPatterns(),
...getMediaFilePatterns(),
...getCacheFilePatterns(),
Expand Down
Loading