mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add weave semantic merge driver integration for worktrees
This commit is contained in:
parent
aca95ccd04
commit
d95a8764f6
7 changed files with 681 additions and 1 deletions
264
packages/core/src/worktree/__tests__/weave-merge-driver.spec.ts
Normal file
264
packages/core/src/worktree/__tests__/weave-merge-driver.spec.ts
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
import * as fs from "fs/promises"
|
||||
import * as os from "os"
|
||||
import * as path from "path"
|
||||
|
||||
import { WeaveMergeDriverService, WEAVE_SUPPORTED_EXTENSIONS, buildGitattributesLines } from "../weave-merge-driver.js"
|
||||
|
||||
// Helper to run git commands in a directory using execFile for proper argument handling
|
||||
import { execFileSync } from "child_process"
|
||||
|
||||
function execGit(cwd: string, args: string[]): void {
|
||||
execFileSync("git", args, { cwd, stdio: "pipe" })
|
||||
}
|
||||
|
||||
describe("WeaveMergeDriverService", () => {
|
||||
let service: WeaveMergeDriverService
|
||||
let tempDir: string
|
||||
|
||||
beforeEach(async () => {
|
||||
service = new WeaveMergeDriverService()
|
||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "weave-test-"))
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await fs.rm(tempDir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
describe("buildGitattributesLines", () => {
|
||||
it("should build gitattributes lines for default extensions", () => {
|
||||
const lines = buildGitattributesLines()
|
||||
expect(lines.length).toBe(WEAVE_SUPPORTED_EXTENSIONS.length)
|
||||
expect(lines[0]).toBe("*.py merge=weave")
|
||||
expect(lines[1]).toBe("*.js merge=weave")
|
||||
expect(lines[2]).toBe("*.jsx merge=weave")
|
||||
expect(lines[3]).toBe("*.ts merge=weave")
|
||||
})
|
||||
|
||||
it("should build gitattributes lines for custom extensions", () => {
|
||||
const lines = buildGitattributesLines(["*.py", "*.rs"])
|
||||
expect(lines).toEqual(["*.py merge=weave", "*.rs merge=weave"])
|
||||
})
|
||||
})
|
||||
|
||||
describe("WEAVE_SUPPORTED_EXTENSIONS", () => {
|
||||
it("should include common language extensions", () => {
|
||||
expect(WEAVE_SUPPORTED_EXTENSIONS).toContain("*.py")
|
||||
expect(WEAVE_SUPPORTED_EXTENSIONS).toContain("*.js")
|
||||
expect(WEAVE_SUPPORTED_EXTENSIONS).toContain("*.ts")
|
||||
expect(WEAVE_SUPPORTED_EXTENSIONS).toContain("*.tsx")
|
||||
expect(WEAVE_SUPPORTED_EXTENSIONS).toContain("*.rs")
|
||||
expect(WEAVE_SUPPORTED_EXTENSIONS).toContain("*.go")
|
||||
expect(WEAVE_SUPPORTED_EXTENSIONS).toContain("*.java")
|
||||
})
|
||||
})
|
||||
|
||||
describe("isInstalled", () => {
|
||||
it("should return a boolean", async () => {
|
||||
const result = await service.isInstalled()
|
||||
expect(typeof result).toBe("boolean")
|
||||
})
|
||||
})
|
||||
|
||||
describe("getVersion", () => {
|
||||
it("should return undefined when weave is not installed", async () => {
|
||||
// Override PATH to ensure weave isn't found
|
||||
const originalPath = process.env.PATH
|
||||
process.env.PATH = ""
|
||||
try {
|
||||
const version = await service.getVersion()
|
||||
expect(version).toBeUndefined()
|
||||
} finally {
|
||||
process.env.PATH = originalPath
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("isConfiguredInGitConfig", () => {
|
||||
it("should return false for a repo without weave config", async () => {
|
||||
execGit(tempDir, ["init"])
|
||||
execGit(tempDir, ["config", "user.email", "test@test.com"])
|
||||
execGit(tempDir, ["config", "user.name", "Test"])
|
||||
|
||||
const result = await service.isConfiguredInGitConfig(tempDir)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it("should return true when weave merge driver is configured", async () => {
|
||||
execGit(tempDir, ["init"])
|
||||
execGit(tempDir, ["config", "user.email", "test@test.com"])
|
||||
execGit(tempDir, ["config", "user.name", "Test"])
|
||||
execGit(tempDir, ["config", "--local", "merge.weave.driver", "weave merge %O %A %B %P"])
|
||||
|
||||
const result = await service.isConfiguredInGitConfig(tempDir)
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it("should return false for a non-git directory", async () => {
|
||||
const result = await service.isConfiguredInGitConfig(tempDir)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("isConfiguredInGitattributes", () => {
|
||||
it("should return false when .gitattributes does not exist", async () => {
|
||||
execGit(tempDir, ["init"])
|
||||
|
||||
const result = await service.isConfiguredInGitattributes(tempDir)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it("should return false when .gitattributes exists but has no weave entries", async () => {
|
||||
execGit(tempDir, ["init"])
|
||||
await fs.writeFile(path.join(tempDir, ".gitattributes"), "*.txt text\n")
|
||||
|
||||
const result = await service.isConfiguredInGitattributes(tempDir)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it("should return true when .gitattributes contains weave entries", async () => {
|
||||
execGit(tempDir, ["init"])
|
||||
await fs.writeFile(path.join(tempDir, ".gitattributes"), "*.py merge=weave\n*.ts merge=weave\n")
|
||||
|
||||
const result = await service.isConfiguredInGitattributes(tempDir)
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getStatus", () => {
|
||||
it("should return full status for an unconfigured repo", async () => {
|
||||
execGit(tempDir, ["init"])
|
||||
|
||||
const status = await service.getStatus(tempDir)
|
||||
expect(status.isConfiguredInGitConfig).toBe(false)
|
||||
expect(status.isConfiguredInGitattributes).toBe(false)
|
||||
expect(status.isFullyConfigured).toBe(false)
|
||||
expect(typeof status.isInstalled).toBe("boolean")
|
||||
})
|
||||
|
||||
it("should report isFullyConfigured when both config and gitattributes are set", async () => {
|
||||
execGit(tempDir, ["init"])
|
||||
execGit(tempDir, ["config", "--local", "merge.weave.driver", "weave merge %O %A %B %P"])
|
||||
execGit(tempDir, ["config", "--local", "merge.weave.name", "Weave semantic merge driver"])
|
||||
await fs.writeFile(path.join(tempDir, ".gitattributes"), "*.ts merge=weave\n")
|
||||
|
||||
const status = await service.getStatus(tempDir)
|
||||
expect(status.isConfiguredInGitConfig).toBe(true)
|
||||
expect(status.isConfiguredInGitattributes).toBe(true)
|
||||
expect(status.isFullyConfigured).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("configureGitConfig", () => {
|
||||
it("should add merge.weave entries to local git config", async () => {
|
||||
execGit(tempDir, ["init"])
|
||||
execGit(tempDir, ["config", "user.email", "test@test.com"])
|
||||
execGit(tempDir, ["config", "user.name", "Test"])
|
||||
|
||||
await service.configureGitConfig(tempDir)
|
||||
|
||||
const result = await service.isConfiguredInGitConfig(tempDir)
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("configureGitattributes", () => {
|
||||
it("should create .gitattributes when it does not exist", async () => {
|
||||
execGit(tempDir, ["init"])
|
||||
|
||||
const added = await service.configureGitattributes(tempDir, ["*.py", "*.ts"])
|
||||
expect(added).toEqual(["*.py", "*.ts"])
|
||||
|
||||
const content = await fs.readFile(path.join(tempDir, ".gitattributes"), "utf-8")
|
||||
expect(content).toContain("*.py merge=weave")
|
||||
expect(content).toContain("*.ts merge=weave")
|
||||
expect(content).toContain("# Weave semantic merge driver")
|
||||
})
|
||||
|
||||
it("should append to existing .gitattributes", async () => {
|
||||
execGit(tempDir, ["init"])
|
||||
await fs.writeFile(path.join(tempDir, ".gitattributes"), "*.txt text\n")
|
||||
|
||||
const added = await service.configureGitattributes(tempDir, ["*.py"])
|
||||
expect(added).toEqual(["*.py"])
|
||||
|
||||
const content = await fs.readFile(path.join(tempDir, ".gitattributes"), "utf-8")
|
||||
expect(content).toContain("*.txt text")
|
||||
expect(content).toContain("*.py merge=weave")
|
||||
})
|
||||
|
||||
it("should not duplicate existing weave entries", async () => {
|
||||
execGit(tempDir, ["init"])
|
||||
await fs.writeFile(path.join(tempDir, ".gitattributes"), "*.py merge=weave\n")
|
||||
|
||||
const added = await service.configureGitattributes(tempDir, ["*.py", "*.ts"])
|
||||
expect(added).toEqual(["*.ts"])
|
||||
|
||||
const content = await fs.readFile(path.join(tempDir, ".gitattributes"), "utf-8")
|
||||
// Should only have one *.py line
|
||||
const pyMatches = content.match(/\*\.py merge=weave/g)
|
||||
expect(pyMatches?.length).toBe(1)
|
||||
})
|
||||
|
||||
it("should return empty array when all entries already exist", async () => {
|
||||
execGit(tempDir, ["init"])
|
||||
await fs.writeFile(path.join(tempDir, ".gitattributes"), "*.py merge=weave\n*.ts merge=weave\n")
|
||||
|
||||
const added = await service.configureGitattributes(tempDir, ["*.py", "*.ts"])
|
||||
expect(added).toEqual([])
|
||||
})
|
||||
|
||||
it("should throw for non-git directory", async () => {
|
||||
await expect(service.configureGitattributes(tempDir)).rejects.toThrow("Not a git repository")
|
||||
})
|
||||
})
|
||||
|
||||
describe("configure", () => {
|
||||
it("should return failure when weave is not installed", async () => {
|
||||
execGit(tempDir, ["init"])
|
||||
|
||||
// Override PATH to ensure weave isn't found
|
||||
const originalPath = process.env.PATH
|
||||
process.env.PATH = ""
|
||||
try {
|
||||
const result = await service.configure(tempDir)
|
||||
expect(result.success).toBe(false)
|
||||
expect(result.message).toContain("weave is not installed")
|
||||
expect(result.addedExtensions).toEqual([])
|
||||
} finally {
|
||||
process.env.PATH = originalPath
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("unconfigure", () => {
|
||||
it("should remove weave config and gitattributes entries", async () => {
|
||||
execGit(tempDir, ["init"])
|
||||
execGit(tempDir, ["config", "--local", "merge.weave.driver", "weave merge %O %A %B %P"])
|
||||
execGit(tempDir, ["config", "--local", "merge.weave.name", "Weave semantic merge driver"])
|
||||
await fs.writeFile(
|
||||
path.join(tempDir, ".gitattributes"),
|
||||
"*.txt text\n# Weave semantic merge driver\n*.py merge=weave\n*.ts merge=weave\n",
|
||||
)
|
||||
|
||||
const result = await service.unconfigure(tempDir)
|
||||
expect(result.success).toBe(true)
|
||||
|
||||
// Check git config was removed
|
||||
const isConfigured = await service.isConfiguredInGitConfig(tempDir)
|
||||
expect(isConfigured).toBe(false)
|
||||
|
||||
// Check gitattributes was cleaned
|
||||
const content = await fs.readFile(path.join(tempDir, ".gitattributes"), "utf-8")
|
||||
expect(content).toContain("*.txt text")
|
||||
expect(content).not.toContain("merge=weave")
|
||||
expect(content).not.toContain("# Weave semantic merge driver")
|
||||
})
|
||||
|
||||
it("should succeed even when no weave config exists", async () => {
|
||||
execGit(tempDir, ["init"])
|
||||
|
||||
const result = await service.unconfigure(tempDir)
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -11,3 +11,10 @@ export * from "./types.js"
|
|||
// Services
|
||||
export { WorktreeService, worktreeService } from "./worktree-service.js"
|
||||
export { WorktreeIncludeService, worktreeIncludeService, type CopyProgressCallback } from "./worktree-include.js"
|
||||
export {
|
||||
WeaveMergeDriverService,
|
||||
weaveMergeDriverService,
|
||||
WEAVE_SUPPORTED_EXTENSIONS,
|
||||
buildGitattributesLines,
|
||||
type WeaveMergeDriverStatus,
|
||||
} from "./weave-merge-driver.js"
|
||||
|
|
|
|||
312
packages/core/src/worktree/weave-merge-driver.ts
Normal file
312
packages/core/src/worktree/weave-merge-driver.ts
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
/**
|
||||
* WeaveMergeDriverService
|
||||
*
|
||||
* Platform-agnostic service for detecting and configuring the weave semantic merge driver.
|
||||
* Weave uses tree-sitter to merge at the function/class level, reducing false conflicts
|
||||
* when parallel worktree tasks edit different functions in the same file.
|
||||
*
|
||||
* @see https://github.com/ataraxy-labs/weave
|
||||
*/
|
||||
|
||||
import { exec } from "child_process"
|
||||
import * as fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
import { promisify } from "util"
|
||||
|
||||
const execAsync = promisify(exec)
|
||||
|
||||
/**
|
||||
* Supported file extensions for the weave merge driver.
|
||||
* These correspond to languages that weave/tree-sitter can parse semantically.
|
||||
*/
|
||||
export const WEAVE_SUPPORTED_EXTENSIONS = [
|
||||
"*.py",
|
||||
"*.js",
|
||||
"*.jsx",
|
||||
"*.ts",
|
||||
"*.tsx",
|
||||
"*.rs",
|
||||
"*.go",
|
||||
"*.java",
|
||||
"*.c",
|
||||
"*.cpp",
|
||||
"*.h",
|
||||
"*.hpp",
|
||||
"*.rb",
|
||||
"*.swift",
|
||||
"*.kt",
|
||||
"*.scala",
|
||||
"*.cs",
|
||||
]
|
||||
|
||||
/**
|
||||
* The gitattributes lines that configure weave as the merge driver for supported file types.
|
||||
*/
|
||||
export function buildGitattributesLines(extensions: string[] = WEAVE_SUPPORTED_EXTENSIONS): string[] {
|
||||
return extensions.map((ext) => `${ext} merge=weave`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Status of the weave merge driver in a repository.
|
||||
*/
|
||||
export interface WeaveMergeDriverStatus {
|
||||
/** Whether the weave binary is found on PATH */
|
||||
isInstalled: boolean
|
||||
/** Version string if installed, undefined otherwise */
|
||||
version?: string
|
||||
/** Whether the merge driver is configured in the repo's git config */
|
||||
isConfiguredInGitConfig: boolean
|
||||
/** Whether .gitattributes contains weave merge driver entries */
|
||||
isConfiguredInGitattributes: boolean
|
||||
/** Whether setup is fully complete (both git config and gitattributes) */
|
||||
isFullyConfigured: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Service for managing the weave semantic merge driver configuration.
|
||||
* All methods are platform-agnostic and don't depend on VSCode APIs.
|
||||
*/
|
||||
export class WeaveMergeDriverService {
|
||||
/**
|
||||
* Check if weave is installed and available on PATH.
|
||||
*/
|
||||
async isInstalled(): Promise<boolean> {
|
||||
try {
|
||||
await execAsync("weave --version")
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the weave version string.
|
||||
*/
|
||||
async getVersion(): Promise<string | undefined> {
|
||||
try {
|
||||
const { stdout } = await execAsync("weave --version")
|
||||
return stdout.trim()
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the weave merge driver is configured in the repo's local git config.
|
||||
*/
|
||||
async isConfiguredInGitConfig(cwd: string): Promise<boolean> {
|
||||
try {
|
||||
const { stdout } = await execAsync("git config --local merge.weave.driver", { cwd })
|
||||
return stdout.trim().length > 0
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if .gitattributes contains weave merge driver entries.
|
||||
*/
|
||||
async isConfiguredInGitattributes(cwd: string): Promise<boolean> {
|
||||
try {
|
||||
const gitRoot = await this.getGitRoot(cwd)
|
||||
if (!gitRoot) return false
|
||||
|
||||
const gitattributesPath = path.join(gitRoot, ".gitattributes")
|
||||
const content = await fs.readFile(gitattributesPath, "utf-8")
|
||||
return content.includes("merge=weave")
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full status of the weave merge driver for a repository.
|
||||
*/
|
||||
async getStatus(cwd: string): Promise<WeaveMergeDriverStatus> {
|
||||
const [isInstalled, version, isConfiguredInGitConfig, isConfiguredInGitattributes] = await Promise.all([
|
||||
this.isInstalled(),
|
||||
this.getVersion(),
|
||||
this.isConfiguredInGitConfig(cwd),
|
||||
this.isConfiguredInGitattributes(cwd),
|
||||
])
|
||||
|
||||
return {
|
||||
isInstalled,
|
||||
version,
|
||||
isConfiguredInGitConfig,
|
||||
isConfiguredInGitattributes,
|
||||
isFullyConfigured: isConfiguredInGitConfig && isConfiguredInGitattributes,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the weave merge driver in the repo's local git config.
|
||||
* Adds:
|
||||
* [merge "weave"]
|
||||
* name = Weave semantic merge driver
|
||||
* driver = weave merge %O %A %B %P
|
||||
*/
|
||||
async configureGitConfig(cwd: string): Promise<void> {
|
||||
await execAsync('git config --local merge.weave.name "Weave semantic merge driver"', { cwd })
|
||||
await execAsync('git config --local merge.weave.driver "weave merge %O %A %B %P"', { cwd })
|
||||
}
|
||||
|
||||
/**
|
||||
* Add weave merge driver entries to .gitattributes.
|
||||
* If .gitattributes already exists, appends the entries (avoiding duplicates).
|
||||
* If it doesn't exist, creates it.
|
||||
*
|
||||
* @param cwd - Current working directory (must be in a git repo)
|
||||
* @param extensions - File extensions to configure (defaults to WEAVE_SUPPORTED_EXTENSIONS)
|
||||
* @returns The list of extensions that were actually added (excludes already-configured ones)
|
||||
*/
|
||||
async configureGitattributes(cwd: string, extensions: string[] = WEAVE_SUPPORTED_EXTENSIONS): Promise<string[]> {
|
||||
const gitRoot = await this.getGitRoot(cwd)
|
||||
if (!gitRoot) {
|
||||
throw new Error("Not a git repository")
|
||||
}
|
||||
|
||||
const gitattributesPath = path.join(gitRoot, ".gitattributes")
|
||||
let existingContent = ""
|
||||
|
||||
try {
|
||||
existingContent = await fs.readFile(gitattributesPath, "utf-8")
|
||||
} catch {
|
||||
// File doesn't exist yet, that's fine
|
||||
}
|
||||
|
||||
const existingLines = new Set(existingContent.split("\n").map((line) => line.trim()))
|
||||
const newLines = buildGitattributesLines(extensions)
|
||||
const addedExtensions: string[] = []
|
||||
|
||||
const linesToAdd: string[] = []
|
||||
for (let i = 0; i < newLines.length; i++) {
|
||||
const line = newLines[i]!
|
||||
const ext = extensions[i]!
|
||||
if (!existingLines.has(line)) {
|
||||
linesToAdd.push(line)
|
||||
addedExtensions.push(ext)
|
||||
}
|
||||
}
|
||||
|
||||
if (linesToAdd.length > 0) {
|
||||
const separator =
|
||||
existingContent.length > 0 && !existingContent.endsWith("\n")
|
||||
? "\n\n# Weave semantic merge driver\n"
|
||||
: existingContent.length > 0
|
||||
? "\n# Weave semantic merge driver\n"
|
||||
: "# Weave semantic merge driver\n"
|
||||
|
||||
const newContent = existingContent + separator + linesToAdd.join("\n") + "\n"
|
||||
await fs.writeFile(gitattributesPath, newContent, "utf-8")
|
||||
}
|
||||
|
||||
return addedExtensions
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully configure the weave merge driver for a repository.
|
||||
* Sets up both the git config and .gitattributes entries.
|
||||
*
|
||||
* @param cwd - Current working directory (must be in a git repo)
|
||||
* @param extensions - File extensions to configure (defaults to WEAVE_SUPPORTED_EXTENSIONS)
|
||||
* @returns Status after configuration
|
||||
*/
|
||||
async configure(
|
||||
cwd: string,
|
||||
extensions: string[] = WEAVE_SUPPORTED_EXTENSIONS,
|
||||
): Promise<{ success: boolean; message: string; addedExtensions: string[] }> {
|
||||
const isInstalled = await this.isInstalled()
|
||||
if (!isInstalled) {
|
||||
return {
|
||||
success: false,
|
||||
message:
|
||||
"weave is not installed. Install it with: brew install ataraxy-labs/tap/weave (macOS) or cargo install weave-merge (other platforms). See https://github.com/ataraxy-labs/weave",
|
||||
addedExtensions: [],
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await this.configureGitConfig(cwd)
|
||||
const addedExtensions = await this.configureGitattributes(cwd, extensions)
|
||||
|
||||
const extMsg =
|
||||
addedExtensions.length > 0
|
||||
? `Added ${addedExtensions.length} file pattern(s) to .gitattributes.`
|
||||
: ".gitattributes already configured."
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Weave semantic merge driver configured. ${extMsg}`,
|
||||
addedExtensions,
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
return {
|
||||
success: false,
|
||||
message: `Failed to configure weave merge driver: ${errorMessage}`,
|
||||
addedExtensions: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove weave merge driver configuration from a repository.
|
||||
* Removes the git config entries and weave lines from .gitattributes.
|
||||
*/
|
||||
async unconfigure(cwd: string): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
// Remove git config entries
|
||||
try {
|
||||
await execAsync("git config --local --remove-section merge.weave", { cwd })
|
||||
} catch {
|
||||
// Section may not exist
|
||||
}
|
||||
|
||||
// Remove weave lines from .gitattributes
|
||||
const gitRoot = await this.getGitRoot(cwd)
|
||||
if (gitRoot) {
|
||||
const gitattributesPath = path.join(gitRoot, ".gitattributes")
|
||||
try {
|
||||
const content = await fs.readFile(gitattributesPath, "utf-8")
|
||||
const lines = content.split("\n")
|
||||
const filteredLines = lines.filter(
|
||||
(line) => !line.includes("merge=weave") && line.trim() !== "# Weave semantic merge driver",
|
||||
)
|
||||
|
||||
// Clean up double blank lines
|
||||
const cleanedContent = filteredLines.join("\n").replace(/\n{3,}/g, "\n\n")
|
||||
await fs.writeFile(gitattributesPath, cleanedContent, "utf-8")
|
||||
} catch {
|
||||
// .gitattributes may not exist
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Weave merge driver configuration removed.",
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
return {
|
||||
success: false,
|
||||
message: `Failed to remove weave configuration: ${errorMessage}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the git repository root path.
|
||||
*/
|
||||
private async getGitRoot(cwd: string): Promise<string | null> {
|
||||
try {
|
||||
const { stdout } = await execAsync("git rev-parse --show-toplevel", { cwd })
|
||||
return stdout.trim()
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance for convenience
|
||||
export const weaveMergeDriverService = new WeaveMergeDriverService()
|
||||
|
|
@ -101,6 +101,8 @@ export interface ExtensionMessage {
|
|||
| "worktreeDefaults"
|
||||
| "worktreeIncludeStatus"
|
||||
| "branchWorktreeIncludeResult"
|
||||
| "weaveMergeDriverStatus"
|
||||
| "weaveMergeDriverResult"
|
||||
| "folderSelected"
|
||||
| "skills"
|
||||
| "fileContent"
|
||||
|
|
@ -236,6 +238,14 @@ export interface ExtensionMessage {
|
|||
copyProgressBytesCopied?: number
|
||||
copyProgressTotalBytes?: number
|
||||
copyProgressItemName?: string
|
||||
// Weave merge driver status
|
||||
weaveMergeDriverStatus?: {
|
||||
isInstalled: boolean
|
||||
version?: string
|
||||
isConfiguredInGitConfig: boolean
|
||||
isConfiguredInGitattributes: boolean
|
||||
isFullyConfigured: boolean
|
||||
}
|
||||
// folderSelected
|
||||
path?: string
|
||||
}
|
||||
|
|
@ -572,6 +582,9 @@ export interface WebviewMessage {
|
|||
| "createWorktreeInclude"
|
||||
| "checkoutBranch"
|
||||
| "browseForWorktreePath"
|
||||
| "getWeaveMergeDriverStatus"
|
||||
| "configureWeaveMergeDriver"
|
||||
| "unconfigureWeaveMergeDriver"
|
||||
// Skills messages
|
||||
| "requestSkills"
|
||||
| "createSkill"
|
||||
|
|
|
|||
|
|
@ -86,6 +86,9 @@ import {
|
|||
handleCheckBranchWorktreeInclude,
|
||||
handleCreateWorktreeInclude,
|
||||
handleCheckoutBranch,
|
||||
handleGetWeaveMergeDriverStatus,
|
||||
handleConfigureWeaveMergeDriver,
|
||||
handleUnconfigureWeaveMergeDriver,
|
||||
} from "./worktree"
|
||||
|
||||
export const webviewMessageHandler = async (
|
||||
|
|
@ -3555,6 +3558,59 @@ export const webviewMessageHandler = async (
|
|||
break
|
||||
}
|
||||
|
||||
case "getWeaveMergeDriverStatus": {
|
||||
try {
|
||||
const weaveMergeDriverStatus = await handleGetWeaveMergeDriverStatus(provider)
|
||||
await provider.postMessageToWebview({ type: "weaveMergeDriverStatus", weaveMergeDriverStatus })
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
provider.log(`Error getting weave merge driver status: ${errorMessage}`)
|
||||
await provider.postMessageToWebview({
|
||||
type: "weaveMergeDriverStatus",
|
||||
weaveMergeDriverStatus: {
|
||||
isInstalled: false,
|
||||
isConfiguredInGitConfig: false,
|
||||
isConfiguredInGitattributes: false,
|
||||
isFullyConfigured: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case "configureWeaveMergeDriver": {
|
||||
try {
|
||||
const { success, message: text } = await handleConfigureWeaveMergeDriver(provider)
|
||||
await provider.postMessageToWebview({ type: "weaveMergeDriverResult", success, text })
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
await provider.postMessageToWebview({
|
||||
type: "weaveMergeDriverResult",
|
||||
success: false,
|
||||
text: errorMessage,
|
||||
})
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case "unconfigureWeaveMergeDriver": {
|
||||
try {
|
||||
const { success, message: text } = await handleUnconfigureWeaveMergeDriver(provider)
|
||||
await provider.postMessageToWebview({ type: "weaveMergeDriverResult", success, text })
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
await provider.postMessageToWebview({
|
||||
type: "weaveMergeDriverResult",
|
||||
success: false,
|
||||
text: errorMessage,
|
||||
})
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case "browseForWorktreePath": {
|
||||
try {
|
||||
const options: vscode.OpenDialogOptions = {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,13 @@ import type {
|
|||
WorktreeListResponse,
|
||||
WorktreeDefaultsResponse,
|
||||
} from "@roo-code/types"
|
||||
import { worktreeService, worktreeIncludeService, type CopyProgressCallback } from "@roo-code/core"
|
||||
import {
|
||||
worktreeService,
|
||||
worktreeIncludeService,
|
||||
weaveMergeDriverService,
|
||||
type CopyProgressCallback,
|
||||
} from "@roo-code/core"
|
||||
import type { WeaveMergeDriverStatus } from "@roo-code/core"
|
||||
|
||||
import type { ClineProvider } from "../ClineProvider"
|
||||
|
||||
|
|
@ -277,3 +283,22 @@ export async function handleCheckoutBranch(provider: ClineProvider, branch: stri
|
|||
const cwd = provider.cwd
|
||||
return worktreeService.checkoutBranch(cwd, branch)
|
||||
}
|
||||
|
||||
export async function handleGetWeaveMergeDriverStatus(provider: ClineProvider): Promise<WeaveMergeDriverStatus> {
|
||||
const cwd = provider.cwd
|
||||
return weaveMergeDriverService.getStatus(cwd)
|
||||
}
|
||||
|
||||
export async function handleConfigureWeaveMergeDriver(provider: ClineProvider): Promise<WorktreeResult> {
|
||||
const cwd = provider.cwd
|
||||
const result = await weaveMergeDriverService.configure(cwd)
|
||||
return {
|
||||
success: result.success,
|
||||
message: result.message,
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleUnconfigureWeaveMergeDriver(provider: ClineProvider): Promise<WorktreeResult> {
|
||||
const cwd = provider.cwd
|
||||
return weaveMergeDriverService.unconfigure(cwd)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ export {
|
|||
handleCheckBranchWorktreeInclude,
|
||||
handleCreateWorktreeInclude,
|
||||
handleCheckoutBranch,
|
||||
handleGetWeaveMergeDriverStatus,
|
||||
handleConfigureWeaveMergeDriver,
|
||||
handleUnconfigureWeaveMergeDriver,
|
||||
} from "./handlers"
|
||||
|
||||
// Re-export types from @roo-code/types for convenience
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue