From 7da5ac7569b3569686ee6a5d659eca8475db4a27 Mon Sep 17 00:00:00 2001 From: Reder9 Date: Mon, 20 Oct 2025 08:50:24 -0500 Subject: [PATCH 1/2] Additional fix for handling folder paths with custom workspaces --- .../jenkins/pipelinekt/core/Pipeline.kt | 19 ++++++++++++++----- .../pipelinekt/core/PipelineWorkspaceTest.kt | 8 +++++--- version.txt | 2 +- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/Pipeline.kt b/core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/Pipeline.kt index 4b4a309..a06888d 100644 --- a/core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/Pipeline.kt +++ b/core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/Pipeline.kt @@ -77,14 +77,23 @@ 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("") } diff --git a/core/src/test/kotlin/com/code42/jenkins/pipelinekt/core/PipelineWorkspaceTest.kt b/core/src/test/kotlin/com/code42/jenkins/pipelinekt/core/PipelineWorkspaceTest.kt index db8a1ae..36bb3f4 100644 --- a/core/src/test/kotlin/com/code42/jenkins/pipelinekt/core/PipelineWorkspaceTest.kt +++ b/core/src/test/kotlin/com/code42/jenkins/pipelinekt/core/PipelineWorkspaceTest.kt @@ -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") } diff --git a/version.txt b/version.txt index e129616..24895d5 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.21.14 \ No newline at end of file +0.21.15 \ No newline at end of file From 94d49bd01223174f782e2e1d653561a8aa9ccb6d Mon Sep 17 00:00:00 2001 From: Reder9 Date: Mon, 20 Oct 2025 08:53:40 -0500 Subject: [PATCH 2/2] Spotless fix --- .../kotlin/com/code42/jenkins/pipelinekt/core/Pipeline.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/Pipeline.kt b/core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/Pipeline.kt index a06888d..f41693e 100644 --- a/core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/Pipeline.kt +++ b/core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/Pipeline.kt @@ -93,7 +93,9 @@ data class Pipeline( 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}\"") + innerWriter.writeln( + "customWorkspacePath = safeFolderPath ? \"./workspace//\${safeFolderPath}/\${safeLeaf}\" : \"./workspace//\${safeLeaf}\"", + ) writer.writeln("}") writer.writeln("") }