At any point of time, we have (at least) three different codebases of interest:
  • TRUNK - For developing new features
  • BRANCH “x-1″ - The most recent release (which is still in QA)
  • BRANCH “x-2″ - The current production release
The CI-jobs (unit tests, integration tests etc) must run for each codebase, and each codebase should maintain its own history.
So, right now each (2-week) development cycle includes the following steps:
  1. disable all Jenkins-jobs for BRANCH “x-2″ (because release “x-1″ became productive)
  2. create BRANCH “x” (i.e. the new release-branch for QA) from TRUNK
  3. copy all “x-1″ Jenkins-jobs to “x”
  4. modify all new “x” jobs to access the correct branch
For #2, we are using the Subversion Tagging Plugin, but all other steps are performed manually.
Solution: Write a Groovy script and execute it with the Commandline Client:
import hudson.model.*
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()
}
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.
For executing this script, see my next blog entry.