From a85fa2e3e74c9acb4f2769c384637cd6116cafd2 Mon Sep 17 00:00:00 2001 From: Guodong Zhu Date: Sun, 22 Mar 2026 00:12:29 -0400 Subject: [PATCH] fix(plugin): preserve branch slashes in worktree path to match CLI The plugin's GitBranchHelper.worktreePathForBranch replaced '/' with '-' in branch names, creating flat directory names (e.g., test-foo). The CLI uses the branch name verbatim, creating nested directories (e.g., test/foo). This caused worktrees created by CLI and plugin for the same branch to land at different paths. Match the CLI behavior: use the branch name directly as a path, letting '/' create subdirectories. Path traversal is still blocked by sanitizeBranchName rejecting '..' in branch names. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../com/block/wt/git/GitBranchHelper.kt | 3 +- .../com/block/wt/git/GitBranchHelperTest.kt | 78 +++++++++++++++++++ .../com/block/wt/model/WorktreeInfoTest.kt | 2 +- 3 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 wt-jetbrains-plugin/src/test/kotlin/com/block/wt/git/GitBranchHelperTest.kt diff --git a/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/git/GitBranchHelper.kt b/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/git/GitBranchHelper.kt index 879899b..8c17596 100644 --- a/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/git/GitBranchHelper.kt +++ b/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/git/GitBranchHelper.kt @@ -13,7 +13,6 @@ object GitBranchHelper { } fun worktreePathForBranch(worktreesBase: Path, branchName: String): Path { - val safeName = branchName.replace("/", "-") - return worktreesBase.resolve(safeName) + return worktreesBase.resolve(branchName) } } diff --git a/wt-jetbrains-plugin/src/test/kotlin/com/block/wt/git/GitBranchHelperTest.kt b/wt-jetbrains-plugin/src/test/kotlin/com/block/wt/git/GitBranchHelperTest.kt new file mode 100644 index 0000000..ecf4d63 --- /dev/null +++ b/wt-jetbrains-plugin/src/test/kotlin/com/block/wt/git/GitBranchHelperTest.kt @@ -0,0 +1,78 @@ +package com.block.wt.git + +import org.junit.Assert.assertEquals +import org.junit.Test +import java.nio.file.Path + +class GitBranchHelperTest { + + private val base: Path = Path.of("/tmp/worktrees") + + // --- sanitizeBranchName tests --- + + @Test + fun sanitizeBranchName_simple() { + assertEquals("main", GitBranchHelper.sanitizeBranchName("main")) + } + + @Test + fun sanitizeBranchName_trims() { + assertEquals("feature", GitBranchHelper.sanitizeBranchName(" feature ")) + } + + @Test(expected = IllegalArgumentException::class) + fun sanitizeBranchName_rejectsPathTraversal() { + GitBranchHelper.sanitizeBranchName("foo/../bar") + } + + @Test(expected = IllegalArgumentException::class) + fun sanitizeBranchName_rejectsDoubleDotAlone() { + GitBranchHelper.sanitizeBranchName("..") + } + + @Test(expected = IllegalArgumentException::class) + fun sanitizeBranchName_rejectsBlank() { + GitBranchHelper.sanitizeBranchName(" ") + } + + @Test(expected = IllegalArgumentException::class) + fun sanitizeBranchName_rejectsLeadingDash() { + GitBranchHelper.sanitizeBranchName("-bad") + } + + // --- worktreePathForBranch tests --- + + @Test + fun worktreePathForBranch_simpleBranch() { + assertEquals( + Path.of("/tmp/worktrees/simple"), + GitBranchHelper.worktreePathForBranch(base, "simple"), + ) + } + + @Test + fun worktreePathForBranch_slashedBranchCreatesNestedPath() { + assertEquals( + Path.of("/tmp/worktrees/feature/foo"), + GitBranchHelper.worktreePathForBranch(base, "feature/foo"), + ) + } + + @Test + fun worktreePathForBranch_deeplyNestedBranch() { + assertEquals( + Path.of("/tmp/worktrees/a/b/c"), + GitBranchHelper.worktreePathForBranch(base, "a/b/c"), + ) + } + + @Test + fun worktreePathForBranch_matchesCLIBehavior() { + // CLI: worktree_path="${WT_WORKTREES_BASE%/}/$branch_name" + // For branch "test/foo" and base "/tmp/worktrees", CLI produces "/tmp/worktrees/test/foo" + assertEquals( + Path.of("/tmp/worktrees/test/foo"), + GitBranchHelper.worktreePathForBranch(base, "test/foo"), + ) + } +} diff --git a/wt-jetbrains-plugin/src/test/kotlin/com/block/wt/model/WorktreeInfoTest.kt b/wt-jetbrains-plugin/src/test/kotlin/com/block/wt/model/WorktreeInfoTest.kt index a8bd0c3..23b51c8 100644 --- a/wt-jetbrains-plugin/src/test/kotlin/com/block/wt/model/WorktreeInfoTest.kt +++ b/wt-jetbrains-plugin/src/test/kotlin/com/block/wt/model/WorktreeInfoTest.kt @@ -260,7 +260,7 @@ class WorktreeInfoTest { fun testWorktreePathForBranch() { val base = Path.of("/worktrees") assertEquals( - Path.of("/worktrees/feature-foo"), + Path.of("/worktrees/feature/foo"), GitBranchHelper.worktreePathForBranch(base, "feature/foo") ) assertEquals(