Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,25 @@ data class Pipeline(
} else if (useMultibranchWorkspace) {
writer.writeln("// Calculate custom workspace for multibranch pipelines")
writer.writeln("// This prevents workspace path truncation and branch conflicts")
writer.writeln("// by using a relative path that includes the sanitized job name")
writer.writeln("// by using a relative path that includes the sanitized job path")
writer.writeln("def customWorkspacePath = null")
writer.writeln("if (env.BRANCH_NAME) {")
val innerWriter = writer.inner()
innerWriter.writeln("// Decode URL-encoded characters (e.g., %2F -> /) from job name and sanitize for safe filesystem paths")
innerWriter.writeln("def decodedJobName = java.net.URLDecoder.decode(env.JOB_NAME, 'UTF-8')")
innerWriter.writeln("def safeJobName = decodedJobName.replaceAll(/[^A-Za-z0-9._-]/, '_')")
innerWriter.writeln("customWorkspacePath = \"./workspace//\${safeJobName}\"")
innerWriter.writeln("// Preserve folder hierarchy from JOB_NAME and sanitize only the final segment")
innerWriter.writeln("def rawParts = env.JOB_NAME.tokenize('/')")
innerWriter.writeln("def folderParts = rawParts.size() > 1 ? rawParts[0..-2] : []")
innerWriter.writeln("def rawLeaf = rawParts ? rawParts[-1] : env.JOB_NAME")
innerWriter.writeln("")
innerWriter.writeln("// Decode last segment (handles %2F etc); then replace '/' and non-safe chars with '_'")
innerWriter.writeln("def decodedLeaf = java.net.URLDecoder.decode(rawLeaf, 'UTF-8')")
innerWriter.writeln("def safeLeaf = decodedLeaf.replaceAll('/', '_').replaceAll(/[^A-Za-z0-9._-]/, '_')")
innerWriter.writeln("")
innerWriter.writeln("// Sanitize folder parts but keep '/' between them")
innerWriter.writeln("def safeFolderPath = folderParts.collect { it.replaceAll(/[^A-Za-z0-9._-]/, '_') }.join('/')")
innerWriter.writeln("")
innerWriter.writeln(
"customWorkspacePath = safeFolderPath ? \"./workspace//\${safeFolderPath}/\${safeLeaf}\" : \"./workspace//\${safeLeaf}\"",
)
writer.writeln("}")
writer.writeln("")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ class PipelineWorkspaceTest {
println("=== Generated Jenkinsfile with useMultibranchWorkspace=true ===")
println(result)

// Verify it contains the runtime check
// Verify it contains the runtime check and path building logic
assertTrue(result.contains("if (env.BRANCH_NAME)"), "Should check for BRANCH_NAME to detect multibranch")
assertTrue(result.contains("def decodedJobName = java.net.URLDecoder.decode(env.JOB_NAME, 'UTF-8')"), "Should decode URL-encoded job name")
assertTrue(result.contains("def safeJobName = decodedJobName.replaceAll(/[^A-Za-z0-9._-]/, '_')"), "Should sanitize job name")
assertTrue(result.contains("def rawParts = env.JOB_NAME.tokenize('/')"))
assertTrue(result.contains("def decodedLeaf = java.net.URLDecoder.decode(rawLeaf, 'UTF-8')"))
assertTrue(result.contains("def safeLeaf = decodedLeaf.replaceAll('/', '_').replaceAll(/[^A-Za-z0-9._-]/, '_')"))
assertTrue(result.contains("def safeFolderPath = folderParts.collect { it.replaceAll(/[^A-Za-z0-9._-]/, '_') }.join('/')"))
assertTrue(result.contains("customWorkspacePath"), "Should define customWorkspacePath")
assertTrue(result.contains("customWorkspace customWorkspacePath"), "Should use customWorkspacePath in agent")
}
Expand Down
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.21.14
0.21.15
Loading