- TRUNK - For developing new features
- BRANCH “x-1″ - The most recent release (which is still in QA)
- BRANCH “x-2″ - The current production release
So, right now each (2-week) development cycle includes the following steps:
- disable all Jenkins-jobs for BRANCH “x-2″ (because release “x-1″ became productive)
- create BRANCH “x” (i.e. the new release-branch for QA) from TRUNK
- copy all “x-1″ Jenkins-jobs to “x”
- modify all new “x” jobs to access the correct branch
Solution: Write a Groovy script and execute it with the Commandline Client:
import hudson.model.*This script disables all jobs with name “SPRINT[x-2]", creates the branch “SPRINT[x]", and copies all jobs with name “SPRINT[x-1]” to “SPRINT[x]", thus modifying the job parameters and the checkout URLs.
import hudson.scm.*
import org.tmatesoft.svn.core.*
import org.tmatesoft.svn.core.auth.*
import org.tmatesoft.svn.core.wc.*
sprint = "SPRINT"
jenkins = Hudson.instance
sprintJobs = jenkins.items.findAll{job -> job.name.indexOf(sprint) >= 0}
// find out the number of the current sprint:
thisSprint = 0
sprintJobs.each { job ->
pos = job.name.indexOf(sprint) + sprint.length()
// TODO handle NumberFormatException
// TODO handle different number length
jobSprint = job.name.substring(pos, pos + 2) as int
thisSprint = Math.max(thisSprint, jobSprint)
}
prevSprint = thisSprint - 1
nextSprint = thisSprint + 1
println("*********************\n* Closing Sprint " + nextSprint + " *\n*********************")
// disable all jobs for the last sprint:
sprintJobs.findAll{job -> job.name.indexOf(sprint + prevSprint) >= 0}.each { job ->
println("Disabling " + job.name)
job.disabled = true
job.save()
}
// create branch (based on the given SCM location and the nextSprint value):
if (args.length > 0 && args[0].indexOf("/trunk/") > 0) {
scmTrunk = args[0]
scmBranch = scmTrunk.replaceAll("/trunk/", "/branches/" + sprint + nextSprint + "/")
println("Creating branch " + scmBranch + " based on " + scmTrunk)
SVNCopyClient svnClient = new SVNCopyClient((ISVNAuthenticationManager)null, null)
SVNCommitInfo svnResult = svnClient.doCopy(
[new SVNCopySource(SVNRevision.HEAD, SVNRevision.HEAD, SVNURL.parseURIEncoded(scmTrunk))] as SVNCopySource[],
SVNURL.parseURIEncoded(scmBranch), false, true, true, "Jenkins: Sprint closed, branch created", new SVNProperties())
if (svnResult.errorMessage) {
println("Branching failed: " + svnResult.fullMessage)
} else {
println("Branch " + scmBranch + " created at revision " + svnResult.newRevision)
}
}
// copy jobs of the current sprint to the next sprint:
sprintJobs.findAll{job -> job.name.indexOf(sprint + thisSprint) >= 0}.each { job ->
newName = job.name.replaceAll(sprint + thisSprint, sprint + nextSprint)
println("Creating new job " + newName + " based on " + job.name)
newJob = jenkins.copy(job, newName)
// modify the job parameters to access the correct branch:
prop = newJob.getProperty(ParametersDefinitionProperty.class)
if (prop) {
prop.parameterDefinitions.each{ param ->
if (param.defaultValue.startsWith(sprint)) {
println("Changing parameter '" + param.name + "' of job " + newName)
param.defaultValue = sprint + nextSprint
}
}
}
// create modified SCM locations and assign them to the new job:
newLocations = []
job.scm.locations.each { location ->
newLocations << new SubversionSCM.ModuleLocation(
location.remote.replaceAll(sprint + thisSprint, sprint + nextSprint), location.local)
}
println("Changing SCM locations of job " + newJob.name + ": " + newLocations)
newJob.scm = new SubversionSCM(newLocations, job.scm.workspaceUpdater, job.scm.browser,
job.scm.excludedRegions, job.scm.excludedUsers, job.scm.excludedRevprop,
job.scm.excludedCommitMessages, job.scm.includedRegions)
newJob.save()
}
For executing this script, see my next blog entry.