From ecc7a9871e4c6634d463e3be141c452c15540924 Mon Sep 17 00:00:00 2001 From: Niklas Roeske Date: Wed, 12 Nov 2025 13:36:27 +0100 Subject: [PATCH 1/6] #153 set development branch for finish gitflow release --- .../cloudogu/ces/cesbuildlib/GitFlow.groovy | 10 ++-- .../ces/cesbuildlib/GitFlowTest.groovy | 55 +++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/com/cloudogu/ces/cesbuildlib/GitFlow.groovy b/src/com/cloudogu/ces/cesbuildlib/GitFlow.groovy index ee1b629e..db294d67 100644 --- a/src/com/cloudogu/ces/cesbuildlib/GitFlow.groovy +++ b/src/com/cloudogu/ces/cesbuildlib/GitFlow.groovy @@ -32,7 +32,7 @@ class GitFlow implements Serializable { * * @param releaseVersion the version that is going to be released */ - void finishRelease(String releaseVersion, String productionBranch = "master") { + void finishRelease(String releaseVersion, String productionBranch = "master", String developmentBranch = "develop") { String branchName = git.getBranchName() // Stop the build here if there is already a tag for this version on remote. @@ -46,7 +46,7 @@ class GitFlow implements Serializable { git.fetch() // Stop the build if there are new changes on develop that are not merged into this feature branch. - if (git.originBranchesHaveDiverged(branchName, 'develop')) { + if (git.originBranchesHaveDiverged(branchName, developmentBranch)) { script.error('There are changes on develop branch that are not merged into release. Please merge and restart process.') } @@ -56,7 +56,7 @@ class GitFlow implements Serializable { String releaseBranchAuthor = git.commitAuthorName String releaseBranchEmail = git.commitAuthorEmail - git.checkoutLatest('develop') + git.checkoutLatest(developmentBranch) git.checkoutLatest(productionBranch) // Merge release branch into productionBranch @@ -65,7 +65,7 @@ class GitFlow implements Serializable { // Create tag. Use -f because the created tag will persist when build has failed. git.setTag(releaseVersion, "release version ${releaseVersion}", true) // Merge release branch into develop - git.checkout('develop') + git.checkout(developmentBranch) // Set author of release Branch as author of merge commit // Otherwise the author of the last commit on develop would author the commit, which is unexpected git.mergeNoFastForward(branchName, releaseBranchAuthor, releaseBranchEmail) @@ -77,7 +77,7 @@ class GitFlow implements Serializable { git.checkout(releaseVersion) // Push changes and tags - git.push("origin ${productionBranch} develop ${releaseVersion}") + git.push("origin ${productionBranch} ${developmentBranch} ${releaseVersion}") git.deleteOriginBranch(branchName) } } diff --git a/test/com/cloudogu/ces/cesbuildlib/GitFlowTest.groovy b/test/com/cloudogu/ces/cesbuildlib/GitFlowTest.groovy index ef013d5f..9f1361c3 100644 --- a/test/com/cloudogu/ces/cesbuildlib/GitFlowTest.groovy +++ b/test/com/cloudogu/ces/cesbuildlib/GitFlowTest.groovy @@ -140,6 +140,61 @@ class GitFlowTest { assertEquals("git push --delete origin myReleaseBranch", scriptMock.allActualArgs[i++]) } + @Test + void testFinishReleaseWithCustomMainAndDevelopBranch() { + String releaseBranchAuthorName = 'release' + String releaseBranchEmail = 'rele@s.e' + String releaseBranchAuthor = createGitAuthorString(releaseBranchAuthorName, releaseBranchEmail) + String developBranchAuthorName = 'develop' + String developBranchEmail = 'develop@a.a' + String developBranchAuthor = createGitAuthorString(developBranchAuthorName, developBranchEmail) + scriptMock.expectedShRetValueForScript.put('git --no-pager show -s --format=\'%an <%ae>\' HEAD', + [releaseBranchAuthor, releaseBranchAuthor, releaseBranchAuthor, releaseBranchAuthor, + // these two are the ones where the release branch author is stored: + releaseBranchAuthor, releaseBranchAuthor, + developBranchAuthor, developBranchAuthor + ]) + scriptMock.expectedShRetValueForScript.put('git push origin main/1.0 dev/1.0 myVersion', 0) + + scriptMock.expectedDefaultShRetValue = "" + scriptMock.env.BRANCH_NAME = "myReleaseBranch" + Git git = new Git(scriptMock) + GitFlow gitflow = new GitFlow(scriptMock, git) + gitflow.finishRelease("myVersion", "main/1.0", "dev/1.0") + + scriptMock.allActualArgs.removeAll("echo ") + scriptMock.allActualArgs.removeAll("git --no-pager show -s --format='%an <%ae>' HEAD") + int i = 0 + assertEquals("git ls-remote origin refs/tags/myVersion", scriptMock.allActualArgs[i++]) + assertEquals("git config 'remote.origin.fetch' '+refs/heads/*:refs/remotes/origin/*'", scriptMock.allActualArgs[i++]) + assertEquals("git fetch --all", scriptMock.allActualArgs[i++]) + assertEquals("git log origin/myReleaseBranch..origin/dev/1.0 --oneline", scriptMock.allActualArgs[i++]) + assertEquals("git checkout myReleaseBranch", scriptMock.allActualArgs[i++]) + assertEquals("git reset --hard origin/myReleaseBranch", scriptMock.allActualArgs[i++]) + assertEquals("git checkout dev/1.0", scriptMock.allActualArgs[i++]) + assertEquals("git reset --hard origin/dev/1.0", scriptMock.allActualArgs[i++]) + assertEquals("git checkout main/1.0", scriptMock.allActualArgs[i++]) + assertEquals("git reset --hard origin/main/1.0", scriptMock.allActualArgs[i++]) + + // Author & Email 1 (calls 'git --no-pager...' twice) + assertEquals("git merge --no-ff myReleaseBranch", scriptMock.allActualArgs[i++]) + assertAuthor(0, releaseBranchAuthorName, releaseBranchEmail) + + // Author & Email 2 (calls 'git --no-pager...' twice) + assertEquals("git tag -f -m \"release version myVersion\" myVersion", scriptMock.allActualArgs[i++]) + assertAuthor(1, releaseBranchAuthorName, releaseBranchEmail) + + assertEquals("git checkout dev/1.0", scriptMock.allActualArgs[i++]) + // Author & Email 3 (calls 'git --no-pager...' twice) + assertEquals("git merge --no-ff myReleaseBranch", scriptMock.allActualArgs[i++]) + assertAuthor(2, releaseBranchAuthorName, releaseBranchEmail) + + assertEquals("git branch -d myReleaseBranch", scriptMock.allActualArgs[i++]) + assertEquals("git checkout myVersion", scriptMock.allActualArgs[i++]) + assertEquals("git push origin main/1.0 dev/1.0 myVersion", scriptMock.allActualArgs[i++]) + assertEquals("git push --delete origin myReleaseBranch", scriptMock.allActualArgs[i++]) + } + @Test void testThrowsErrorWhenTagAlreadyExists() { scriptMock.expectedShRetValueForScript.put('git ls-remote origin refs/tags/myVersion', 'thisIsATag') From 7265f0c600417d82591a24e69c5d1508d7f17134 Mon Sep 17 00:00:00 2001 From: Niklas Roeske Date: Wed, 12 Nov 2025 13:39:20 +0100 Subject: [PATCH 2/6] #153 update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 261ddd7d..17308219 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- Set development branch on finishing gitflow release + ## [4.3.0](https://github.com/cloudogu/ces-build-lib/releases/tag/4.3.0) - 2025-08-21 ### Changed - Updates the BATS shell test image to 1.12 which supports the `--report-formatter` switch From adcc4c71795cb05d1037bb23ec792eb3d2315d5b Mon Sep 17 00:00:00 2001 From: Niklas Roeske Date: Mon, 17 Nov 2025 11:26:15 +0100 Subject: [PATCH 3/6] #153 make release process more secure by checking BASE_VERSION in Makefile --- src/com/cloudogu/ces/cesbuildlib/Git.groovy | 5 + .../cloudogu/ces/cesbuildlib/GitFlow.groovy | 15 +++ .../cloudogu/ces/cesbuildlib/Makefile.groovy | 31 +++++ .../ces/cesbuildlib/GitFlowTest.groovy | 120 ++++++++++++++++++ .../ces/cesbuildlib/MakefileTest.groovy | 71 ++++++++++- 5 files changed, 241 insertions(+), 1 deletion(-) diff --git a/src/com/cloudogu/ces/cesbuildlib/Git.groovy b/src/com/cloudogu/ces/cesbuildlib/Git.groovy index 5d62fe45..a228b942 100644 --- a/src/com/cloudogu/ces/cesbuildlib/Git.groovy +++ b/src/com/cloudogu/ces/cesbuildlib/Git.groovy @@ -500,4 +500,9 @@ class Git implements Serializable { script.echo commandOutput return commandOutput } + + boolean branchExists(String branch) { + def branchFound = this.executeGitWithCredentials("show-ref refs/remotes/origin/${branch}") + return branchFound != null && branchFound.length() > 0 + } } diff --git a/src/com/cloudogu/ces/cesbuildlib/GitFlow.groovy b/src/com/cloudogu/ces/cesbuildlib/GitFlow.groovy index db294d67..fdd7824a 100644 --- a/src/com/cloudogu/ces/cesbuildlib/GitFlow.groovy +++ b/src/com/cloudogu/ces/cesbuildlib/GitFlow.groovy @@ -4,6 +4,7 @@ class GitFlow implements Serializable { private def script private Git git Sh sh + private Makefile makefile GitFlow(script, Git git) { this.script = script @@ -11,6 +12,11 @@ class GitFlow implements Serializable { this.sh = new Sh(script) } + GitFlow(script, Git git, Makefile makefile) { + this(script, git) + this.makefile = makefile + } + /** * @return if this branch is a release branch according to git flow */ @@ -45,6 +51,15 @@ class GitFlow implements Serializable { // Make sure all branches are fetched git.fetch() + // Check if a backport release is configured by setting BASE_VERSION inside Makefile + if (makefile != null) { + def baseVersion = makefile.getBaseVersion() + if (baseVersion != null && baseVersion != "") { + productionBranch = makefile.getMainBranchName() + developmentBranch = makefile.getDevelopBranchName() + } + } + // Stop the build if there are new changes on develop that are not merged into this feature branch. if (git.originBranchesHaveDiverged(branchName, developmentBranch)) { script.error('There are changes on develop branch that are not merged into release. Please merge and restart process.') diff --git a/src/com/cloudogu/ces/cesbuildlib/Makefile.groovy b/src/com/cloudogu/ces/cesbuildlib/Makefile.groovy index 2297cc0f..645bbf67 100644 --- a/src/com/cloudogu/ces/cesbuildlib/Makefile.groovy +++ b/src/com/cloudogu/ces/cesbuildlib/Makefile.groovy @@ -20,4 +20,35 @@ class Makefile { String getVersion() { return sh.returnStdOut('grep -e "^VERSION=" Makefile | sed "s/VERSION=//g"') } + + /** + * Retrieves the value of the BASE_VERSION Variable defined in the Makefile. + */ + String getBaseVersion() { + return sh.returnStdOut('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"') + } + + /** + * Retrieves the value of the BASE_VERSION Variable defined in the Makefile. + */ + String getDevelopBranchName() { + def develop = "develop" + def baseVersion = getBaseVersion() + if (baseVersion != null && baseVersion != "") { + return baseVersion + "/" + develop + } + return develop + } + + /** + * Retrieves the value of the BASE_VERSION Variable defined in the Makefile. + */ + String getMainBranchName() { + def main = "main" + def baseVersion = getBaseVersion() + if (baseVersion != null && baseVersion != "") { + return baseVersion + "/" + main + } + return main + } } diff --git a/test/com/cloudogu/ces/cesbuildlib/GitFlowTest.groovy b/test/com/cloudogu/ces/cesbuildlib/GitFlowTest.groovy index 9f1361c3..0195d1bb 100644 --- a/test/com/cloudogu/ces/cesbuildlib/GitFlowTest.groovy +++ b/test/com/cloudogu/ces/cesbuildlib/GitFlowTest.groovy @@ -195,6 +195,126 @@ class GitFlowTest { assertEquals("git push --delete origin myReleaseBranch", scriptMock.allActualArgs[i++]) } + @Test + void testFinishReleaseWithBaseVersionSet() { + String releaseBranchAuthorName = 'release' + String releaseBranchEmail = 'rele@s.e' + String releaseBranchAuthor = createGitAuthorString(releaseBranchAuthorName, releaseBranchEmail) + String developBranchAuthorName = 'develop' + String developBranchEmail = 'develop@a.a' + String developBranchAuthor = createGitAuthorString(developBranchAuthorName, developBranchEmail) + scriptMock.expectedShRetValueForScript.put('git --no-pager show -s --format=\'%an <%ae>\' HEAD', + [releaseBranchAuthor, releaseBranchAuthor, releaseBranchAuthor, releaseBranchAuthor, + // these two are the ones where the release branch author is stored: + releaseBranchAuthor, releaseBranchAuthor, + developBranchAuthor, developBranchAuthor + ]) + scriptMock.expectedShRetValueForScript.put('git push origin 4.2.2/main 4.2.2/develop myVersion', 0) + + scriptMock.expectedDefaultShRetValue = "" + scriptMock.env.BRANCH_NAME = "myReleaseBranch" + scriptMock.expectedShRetValueForScript.put('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString(), "4.2.2".toString()) + + Git git = new Git(scriptMock) + Makefile makefile = new Makefile(scriptMock) + GitFlow gitflow = new GitFlow(scriptMock, git, makefile) + gitflow.finishRelease("myVersion", "4.2.2/main", "4.2.2/develop") + + scriptMock.allActualArgs.removeAll("echo ") + scriptMock.allActualArgs.removeAll("git --no-pager show -s --format='%an <%ae>' HEAD") + int i = 0 + assertEquals("git ls-remote origin refs/tags/myVersion", scriptMock.allActualArgs[i++]) + assertEquals("git config 'remote.origin.fetch' '+refs/heads/*:refs/remotes/origin/*'", scriptMock.allActualArgs[i++]) + assertEquals("git fetch --all", scriptMock.allActualArgs[i++]) + assertEquals("grep -e \"^BASE_VERSION=\" Makefile | sed \"s/BASE_VERSION=//g\"", scriptMock.allActualArgs[i++]) + assertEquals("grep -e \"^BASE_VERSION=\" Makefile | sed \"s/BASE_VERSION=//g\"", scriptMock.allActualArgs[i++]) + assertEquals("grep -e \"^BASE_VERSION=\" Makefile | sed \"s/BASE_VERSION=//g\"", scriptMock.allActualArgs[i++]) + assertEquals("git log origin/myReleaseBranch..origin/4.2.2/develop --oneline", scriptMock.allActualArgs[i++]) + assertEquals("git checkout myReleaseBranch", scriptMock.allActualArgs[i++]) + assertEquals("git reset --hard origin/myReleaseBranch", scriptMock.allActualArgs[i++]) + assertEquals("git checkout 4.2.2/develop", scriptMock.allActualArgs[i++]) + assertEquals("git reset --hard origin/4.2.2/develop", scriptMock.allActualArgs[i++]) + assertEquals("git checkout 4.2.2/main", scriptMock.allActualArgs[i++]) + assertEquals("git reset --hard origin/4.2.2/main", scriptMock.allActualArgs[i++]) + + // Author & Email 1 (calls 'git --no-pager...' twice) + assertEquals("git merge --no-ff myReleaseBranch", scriptMock.allActualArgs[i++]) + assertAuthor(0, releaseBranchAuthorName, releaseBranchEmail) + + // Author & Email 2 (calls 'git --no-pager...' twice) + assertEquals("git tag -f -m \"release version myVersion\" myVersion", scriptMock.allActualArgs[i++]) + assertAuthor(1, releaseBranchAuthorName, releaseBranchEmail) + + assertEquals("git checkout 4.2.2/develop", scriptMock.allActualArgs[i++]) + // Author & Email 3 (calls 'git --no-pager...' twice) + assertEquals("git merge --no-ff myReleaseBranch", scriptMock.allActualArgs[i++]) + assertAuthor(2, releaseBranchAuthorName, releaseBranchEmail) + + assertEquals("git branch -d myReleaseBranch", scriptMock.allActualArgs[i++]) + assertEquals("git checkout myVersion", scriptMock.allActualArgs[i++]) + assertEquals("git push origin 4.2.2/main 4.2.2/develop myVersion", scriptMock.allActualArgs[i++]) + assertEquals("git push --delete origin myReleaseBranch", scriptMock.allActualArgs[i++]) + } + + @Test + void testFinishReleaseWithBaseVersionUnSet() { + String releaseBranchAuthorName = 'release' + String releaseBranchEmail = 'rele@s.e' + String releaseBranchAuthor = createGitAuthorString(releaseBranchAuthorName, releaseBranchEmail) + String developBranchAuthorName = 'develop' + String developBranchEmail = 'develop@a.a' + String developBranchAuthor = createGitAuthorString(developBranchAuthorName, developBranchEmail) + scriptMock.expectedShRetValueForScript.put('git --no-pager show -s --format=\'%an <%ae>\' HEAD', + [releaseBranchAuthor, releaseBranchAuthor, releaseBranchAuthor, releaseBranchAuthor, + // these two are the ones where the release branch author is stored: + releaseBranchAuthor, releaseBranchAuthor, + developBranchAuthor, developBranchAuthor + ]) + scriptMock.expectedShRetValueForScript.put('git push origin master develop myVersion', 0) + + scriptMock.expectedDefaultShRetValue = "" + scriptMock.env.BRANCH_NAME = "myReleaseBranch" + scriptMock.expectedShRetValueForScript.put('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString(), "".toString()) + + Git git = new Git(scriptMock) + Makefile makefile = new Makefile(scriptMock) + GitFlow gitflow = new GitFlow(scriptMock, git, makefile) + gitflow.finishRelease("myVersion", ) + + scriptMock.allActualArgs.removeAll("echo ") + scriptMock.allActualArgs.removeAll("git --no-pager show -s --format='%an <%ae>' HEAD") + int i = 0 + assertEquals("git ls-remote origin refs/tags/myVersion", scriptMock.allActualArgs[i++]) + assertEquals("git config 'remote.origin.fetch' '+refs/heads/*:refs/remotes/origin/*'", scriptMock.allActualArgs[i++]) + assertEquals("git fetch --all", scriptMock.allActualArgs[i++]) + assertEquals("grep -e \"^BASE_VERSION=\" Makefile | sed \"s/BASE_VERSION=//g\"", scriptMock.allActualArgs[i++]) + assertEquals("git log origin/myReleaseBranch..origin/develop --oneline", scriptMock.allActualArgs[i++]) + assertEquals("git checkout myReleaseBranch", scriptMock.allActualArgs[i++]) + assertEquals("git reset --hard origin/myReleaseBranch", scriptMock.allActualArgs[i++]) + assertEquals("git checkout develop", scriptMock.allActualArgs[i++]) + assertEquals("git reset --hard origin/develop", scriptMock.allActualArgs[i++]) + assertEquals("git checkout master", scriptMock.allActualArgs[i++]) + assertEquals("git reset --hard origin/master", scriptMock.allActualArgs[i++]) + + // Author & Email 1 (calls 'git --no-pager...' twice) + assertEquals("git merge --no-ff myReleaseBranch", scriptMock.allActualArgs[i++]) + assertAuthor(0, releaseBranchAuthorName, releaseBranchEmail) + + // Author & Email 2 (calls 'git --no-pager...' twice) + assertEquals("git tag -f -m \"release version myVersion\" myVersion", scriptMock.allActualArgs[i++]) + assertAuthor(1, releaseBranchAuthorName, releaseBranchEmail) + + assertEquals("git checkout develop", scriptMock.allActualArgs[i++]) + // Author & Email 3 (calls 'git --no-pager...' twice) + assertEquals("git merge --no-ff myReleaseBranch", scriptMock.allActualArgs[i++]) + assertAuthor(2, releaseBranchAuthorName, releaseBranchEmail) + + assertEquals("git branch -d myReleaseBranch", scriptMock.allActualArgs[i++]) + assertEquals("git checkout myVersion", scriptMock.allActualArgs[i++]) + assertEquals("git push origin master develop myVersion", scriptMock.allActualArgs[i++]) + assertEquals("git push --delete origin myReleaseBranch", scriptMock.allActualArgs[i++]) + } + @Test void testThrowsErrorWhenTagAlreadyExists() { scriptMock.expectedShRetValueForScript.put('git ls-remote origin refs/tags/myVersion', 'thisIsATag') diff --git a/test/com/cloudogu/ces/cesbuildlib/MakefileTest.groovy b/test/com/cloudogu/ces/cesbuildlib/MakefileTest.groovy index 8e74d4ab..96a7ebff 100644 --- a/test/com/cloudogu/ces/cesbuildlib/MakefileTest.groovy +++ b/test/com/cloudogu/ces/cesbuildlib/MakefileTest.groovy @@ -1,17 +1,86 @@ package com.cloudogu.ces.cesbuildlib +import org.junit.Test + import static org.assertj.core.api.Assertions.assertThat class MakefileTest { + @Test void testGetVersion() { def scriptMock = new ScriptMock() scriptMock.expectedShRetValueForScript.put('grep -e "^VERSION=" Makefile | sed "s/VERSION=//g"'.toString(), "4.2.2".toString()) Makefile makefile = new Makefile(scriptMock) - makefile.getVersion() + def result = makefile.getVersion() assertThat(scriptMock.allActualArgs[0]).isEqualTo('grep -e "^VERSION=" Makefile | sed "s/VERSION=//g"'.toString()) + assertThat(result).isEqualTo("4.2.2") + } + + @Test + void testGetBaseVersion() { + def scriptMock = new ScriptMock() + scriptMock.expectedShRetValueForScript.put('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString(), "4.2.2".toString()) + + Makefile makefile = new Makefile(scriptMock) + + def result = makefile.getBaseVersion() + + assertThat(scriptMock.allActualArgs[0]).isEqualTo('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString()) + assertThat(result).isEqualTo("4.2.2") + } + + @Test + void testIsBackportDevelopBranchName() { + def scriptMock = new ScriptMock() + scriptMock.expectedShRetValueForScript.put('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString(), "4.2.2".toString()) + + Makefile makefile = new Makefile(scriptMock) + + def result = makefile.getDevelopBranchName() + + assertThat(scriptMock.allActualArgs[0]).isEqualTo('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString()) + assertThat(result).isEqualTo("4.2.2/develop") + } + + @Test + void testIsBackportMainBranchName() { + def scriptMock = new ScriptMock() + scriptMock.expectedShRetValueForScript.put('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString(), "4.2.2".toString()) + + Makefile makefile = new Makefile(scriptMock) + + def result = makefile.getMainBranchName() + + assertThat(scriptMock.allActualArgs[0]).isEqualTo('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString()) + assertThat(result).isEqualTo("4.2.2/main") + } + + @Test + void testIsStandardDevelopBranchName() { + def scriptMock = new ScriptMock() + scriptMock.expectedShRetValueForScript.put('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString(), "".toString()) + + Makefile makefile = new Makefile(scriptMock) + + def result = makefile.getDevelopBranchName() + + assertThat(scriptMock.allActualArgs[0]).isEqualTo('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString()) + assertThat(result).isEqualTo("develop") + } + + @Test + void testIsStandardMainBranchName() { + def scriptMock = new ScriptMock() + scriptMock.expectedShRetValueForScript.put('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString(), "".toString()) + + Makefile makefile = new Makefile(scriptMock) + + def result = makefile.getMainBranchName() + + assertThat(scriptMock.allActualArgs[0]).isEqualTo('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString()) + assertThat(result).isEqualTo("main") } } From aab6d6a69141bac2b71a78edff7ff4662a9e348d Mon Sep 17 00:00:00 2001 From: Niklas Roeske Date: Fri, 21 Nov 2025 09:21:26 +0100 Subject: [PATCH 4/6] #153 check unallowed backport --- .../cloudogu/ces/cesbuildlib/GitFlow.groovy | 18 ++++--- .../ces/cesbuildlib/GitFlowTest.groovy | 53 +++++++++++++++++-- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/com/cloudogu/ces/cesbuildlib/GitFlow.groovy b/src/com/cloudogu/ces/cesbuildlib/GitFlow.groovy index fdd7824a..69a05113 100644 --- a/src/com/cloudogu/ces/cesbuildlib/GitFlow.groovy +++ b/src/com/cloudogu/ces/cesbuildlib/GitFlow.groovy @@ -31,6 +31,16 @@ class GitFlow implements Serializable { return git.getSimpleBranchName().equals("develop") } + boolean isUnallowedBackportRelease(String productionBranch, String developmentBranch) { + if (makefile != null) { + def baseVersion = makefile.getBaseVersion() + if (baseVersion != null && baseVersion != "" && (!productionBranch.contains(baseVersion) || !developmentBranch.contains(baseVersion))) { + return true + } + } + return false + } + /** * Finishes a git flow release and pushes all merged branches to remote * @@ -52,12 +62,8 @@ class GitFlow implements Serializable { git.fetch() // Check if a backport release is configured by setting BASE_VERSION inside Makefile - if (makefile != null) { - def baseVersion = makefile.getBaseVersion() - if (baseVersion != null && baseVersion != "") { - productionBranch = makefile.getMainBranchName() - developmentBranch = makefile.getDevelopBranchName() - } + if (isUnallowedBackportRelease(productionBranch, developmentBranch)) { + script.error('The Variable BASE_VERSION is set in the Makefile. The release should not be merged into main / master / develop or other backport branches.') } // Stop the build if there are new changes on develop that are not merged into this feature branch. diff --git a/test/com/cloudogu/ces/cesbuildlib/GitFlowTest.groovy b/test/com/cloudogu/ces/cesbuildlib/GitFlowTest.groovy index 0195d1bb..e0b9a506 100644 --- a/test/com/cloudogu/ces/cesbuildlib/GitFlowTest.groovy +++ b/test/com/cloudogu/ces/cesbuildlib/GitFlowTest.groovy @@ -227,8 +227,6 @@ class GitFlowTest { assertEquals("git config 'remote.origin.fetch' '+refs/heads/*:refs/remotes/origin/*'", scriptMock.allActualArgs[i++]) assertEquals("git fetch --all", scriptMock.allActualArgs[i++]) assertEquals("grep -e \"^BASE_VERSION=\" Makefile | sed \"s/BASE_VERSION=//g\"", scriptMock.allActualArgs[i++]) - assertEquals("grep -e \"^BASE_VERSION=\" Makefile | sed \"s/BASE_VERSION=//g\"", scriptMock.allActualArgs[i++]) - assertEquals("grep -e \"^BASE_VERSION=\" Makefile | sed \"s/BASE_VERSION=//g\"", scriptMock.allActualArgs[i++]) assertEquals("git log origin/myReleaseBranch..origin/4.2.2/develop --oneline", scriptMock.allActualArgs[i++]) assertEquals("git checkout myReleaseBranch", scriptMock.allActualArgs[i++]) assertEquals("git reset --hard origin/myReleaseBranch", scriptMock.allActualArgs[i++]) @@ -279,7 +277,7 @@ class GitFlowTest { Git git = new Git(scriptMock) Makefile makefile = new Makefile(scriptMock) GitFlow gitflow = new GitFlow(scriptMock, git, makefile) - gitflow.finishRelease("myVersion", ) + gitflow.finishRelease("myVersion") scriptMock.allActualArgs.removeAll("echo ") scriptMock.allActualArgs.removeAll("git --no-pager show -s --format='%an <%ae>' HEAD") @@ -338,6 +336,55 @@ class GitFlowTest { assertEquals("There are changes on develop branch that are not merged into release. Please merge and restart process.", err.getMessage()) } + @Test + void testIsUnallowedBackportReleaseIsUnallowedStandardBranches() { + scriptMock.expectedShRetValueForScript.put('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString(), "4.2".toString()) + + Git git = new Git(scriptMock) + Makefile makefile = new Makefile(scriptMock) + GitFlow gitflow = new GitFlow(scriptMock, git, makefile) + def result = gitflow.isUnallowedBackportRelease("main", "develop") + + assertEquals(true, result) + } + + @Test + void testIsUnallowedBackportReleaseIsUnallowedStandardBranches2() { + scriptMock.expectedShRetValueForScript.put('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString(), "4.2".toString()) + + Git git = new Git(scriptMock) + Makefile makefile = new Makefile(scriptMock) + GitFlow gitflow = new GitFlow(scriptMock, git, makefile) + def result = gitflow.isUnallowedBackportRelease("master", "develop") + + assertEquals(true, result) + } + + @Test + void testIsUnallowedBackportReleaseIsUnallowedWrongVersionBranches() { + scriptMock.expectedShRetValueForScript.put('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString(), "4.2".toString()) + + Git git = new Git(scriptMock) + Makefile makefile = new Makefile(scriptMock) + GitFlow gitflow = new GitFlow(scriptMock, git, makefile) + def result = gitflow.isUnallowedBackportRelease("4.3/main", "4.3/develop") + + assertEquals(true, result) + } + + @Test + void testIsUnallowedBackportReleaseIsAllowed() { + scriptMock.expectedShRetValueForScript.put('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString(), "4.2".toString()) + + Git git = new Git(scriptMock) + Makefile makefile = new Makefile(scriptMock) + GitFlow gitflow = new GitFlow(scriptMock, git, makefile) + def result = gitflow.isUnallowedBackportRelease("4.2/main", "4.2/develop") + + assertEquals(false, result) + } + + void assertAuthor(int withEnvInvocationIndex, String author, String email) { def withEnvMap = scriptMock.actualWithEnvAsMap(withEnvInvocationIndex) assert withEnvMap['GIT_AUTHOR_NAME'] == author From 51e67702c18a603435f0764dcf7d8e503bfc898a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20G=C3=BCrtler?= Date: Fri, 21 Nov 2025 16:04:37 +0100 Subject: [PATCH 5/6] #153 support legacy master branch in git flow --- src/com/cloudogu/ces/cesbuildlib/Makefile.groovy | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/com/cloudogu/ces/cesbuildlib/Makefile.groovy b/src/com/cloudogu/ces/cesbuildlib/Makefile.groovy index 645bbf67..fd8024ca 100644 --- a/src/com/cloudogu/ces/cesbuildlib/Makefile.groovy +++ b/src/com/cloudogu/ces/cesbuildlib/Makefile.groovy @@ -29,9 +29,9 @@ class Makefile { } /** - * Retrieves the value of the BASE_VERSION Variable defined in the Makefile. + * Creates the develop branch for Git Flow based on the base version. */ - String getDevelopBranchName() { + String getGitFlowDevelopBranch() { def develop = "develop" def baseVersion = getBaseVersion() if (baseVersion != null && baseVersion != "") { @@ -41,14 +41,14 @@ class Makefile { } /** - * Retrieves the value of the BASE_VERSION Variable defined in the Makefile. + * Creates the main branch for Git Flow based on the base version. */ - String getMainBranchName() { - def main = "main" + String getGitFlowMainBranch(defaultBranch="main") { def baseVersion = getBaseVersion() if (baseVersion != null && baseVersion != "") { - return baseVersion + "/" + main + // The master branch is legacy so we don't create one here, even if it was passed as parameter. + return baseVersion + "/main" } - return main + return defaultBranch } } From 07840421554897fc131f941df2e9958c2a7bcf3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20G=C3=BCrtler?= Date: Fri, 21 Nov 2025 16:47:25 +0100 Subject: [PATCH 6/6] #153 add tests and refactor --- .../cloudogu/ces/cesbuildlib/Makefile.groovy | 8 ++-- .../ces/cesbuildlib/MakefileTest.groovy | 37 +++++++++++++++++-- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/com/cloudogu/ces/cesbuildlib/Makefile.groovy b/src/com/cloudogu/ces/cesbuildlib/Makefile.groovy index fd8024ca..dea0540d 100644 --- a/src/com/cloudogu/ces/cesbuildlib/Makefile.groovy +++ b/src/com/cloudogu/ces/cesbuildlib/Makefile.groovy @@ -29,9 +29,9 @@ class Makefile { } /** - * Creates the develop branch for Git Flow based on the base version. + * Determines the develop branch for Git Flow based on the base version. */ - String getGitFlowDevelopBranch() { + String determineGitFlowDevelopBranch() { def develop = "develop" def baseVersion = getBaseVersion() if (baseVersion != null && baseVersion != "") { @@ -41,9 +41,9 @@ class Makefile { } /** - * Creates the main branch for Git Flow based on the base version. + * Determines the main branch for Git Flow based on the base version. */ - String getGitFlowMainBranch(defaultBranch="main") { + String determineGitFlowMainBranch(defaultBranch="main") { def baseVersion = getBaseVersion() if (baseVersion != null && baseVersion != "") { // The master branch is legacy so we don't create one here, even if it was passed as parameter. diff --git a/test/com/cloudogu/ces/cesbuildlib/MakefileTest.groovy b/test/com/cloudogu/ces/cesbuildlib/MakefileTest.groovy index 96a7ebff..c24a08bc 100644 --- a/test/com/cloudogu/ces/cesbuildlib/MakefileTest.groovy +++ b/test/com/cloudogu/ces/cesbuildlib/MakefileTest.groovy @@ -1,6 +1,7 @@ package com.cloudogu.ces.cesbuildlib import org.junit.Test +import org.junit.jupiter.api.DisplayName import static org.assertj.core.api.Assertions.assertThat @@ -39,7 +40,7 @@ class MakefileTest { Makefile makefile = new Makefile(scriptMock) - def result = makefile.getDevelopBranchName() + def result = makefile.determineGitFlowDevelopBranch() assertThat(scriptMock.allActualArgs[0]).isEqualTo('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString()) assertThat(result).isEqualTo("4.2.2/develop") @@ -52,7 +53,7 @@ class MakefileTest { Makefile makefile = new Makefile(scriptMock) - def result = makefile.getMainBranchName() + def result = makefile.determineGitFlowMainBranch() assertThat(scriptMock.allActualArgs[0]).isEqualTo('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString()) assertThat(result).isEqualTo("4.2.2/main") @@ -65,7 +66,7 @@ class MakefileTest { Makefile makefile = new Makefile(scriptMock) - def result = makefile.getDevelopBranchName() + def result = makefile.determineGitFlowDevelopBranch() assertThat(scriptMock.allActualArgs[0]).isEqualTo('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString()) assertThat(result).isEqualTo("develop") @@ -78,9 +79,37 @@ class MakefileTest { Makefile makefile = new Makefile(scriptMock) - def result = makefile.getMainBranchName() + def result = makefile.determineGitFlowMainBranch() assertThat(scriptMock.allActualArgs[0]).isEqualTo('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString()) assertThat(result).isEqualTo("main") } + + @Test + @DisplayName("should replace master with main if BASE_VERSION is used and the default branch is master") + void shouldReplaceMasterWithMain() { + def scriptMock = new ScriptMock() + scriptMock.expectedShRetValueForScript.put('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString(), "4.5".toString()) + + Makefile makefile = new Makefile(scriptMock) + + def result = makefile.determineGitFlowMainBranch("master") + + assertThat(scriptMock.allActualArgs[0]).isEqualTo('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString()) + assertThat(result).isEqualTo("4.5/main") + } + + @Test + @DisplayName("should use default branch if BASE_VERSION is not used") + void shouldUseDefaultBranch() { + def scriptMock = new ScriptMock() + scriptMock.expectedShRetValueForScript.put('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString(), "".toString()) + + Makefile makefile = new Makefile(scriptMock) + + def result = makefile.determineGitFlowMainBranch("master") + + assertThat(scriptMock.allActualArgs[0]).isEqualTo('grep -e "^BASE_VERSION=" Makefile | sed "s/BASE_VERSION=//g"'.toString()) + assertThat(result).isEqualTo("master") + } }