Gerrit trigger for repo projects
Some projects are composed of several git repositories that are dependent in some way. These repositories are usually managed using `repo
` tool. Typical example of such project is Android open-source project (AOSP).
Challenges of verification builds
One change in one repository may need another change in a different repository in order to be complete and the whole project working without errors. These inter-repository dependencies complicate verification builds. Meaning that fetching just one change is not enough to successfully complete a verification build.
Trigger only one build
Each uploaded change triggers one build. We can’t do anything about that if we want to keep the trigger automatic. But Gerrit Trigger plugin configuration has a feature for stopping builds that have the same topic. It means that when a new verification build for related changes is started, the already running build (if there is one) is stopped.
This can be configured in ‘Manage Jenkins’ → ‘Gerrit Trigger’ → ‘Abort patch sets with same topic’:
It is also recommended to change value of ‘Build Schedule Delay’ to at least 10 seconds. This option is located also in ‘Gerrit Trigger’ configuration under ‘Advanced …’ → ‘Miscellaneous’.
Fetch changes
All related changes have the same topic, which makes it easier to fetch. The topic is stored in ‘GERRIT_TOPIC’ environment variable. The goal is to checkout the latest change for each project.
We use Gerrit REST API `GET /changes/?q=...
.`
https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
curl --user "jenkins-builder:${GERRIT_JENKINSBUILDER_PASSWORD}" "https://${GERRIT_URL}/a/changes/?q=topic:\"${GERRIT_TOPIC}\"+status:open+parentproject:${PARENT_PROJECT}&o=CURRENT_REVISION&o=CURRENT_COMMIT"
The response contains all Gerrit changes with given GERRIT_TOPIC
that are open and their project’s parent is PARENT_PROJECT
. We parse the response into a map `PROJECT : [ GerritChange, GerritChange ]
`.
Now for each project we find the latest change by finding the one that is descendant of all the changes. We use git to compare the relation between two commits:
# git merge-base --is-ancestor <maybe-ancestor-commit> <descendant-commit>
git -C ${PROJECT_PATH} merge-base --is-ancestor ${COMMIT_A} ${COMMIT_B}
We checkout the one that is descendant of the other commits:
# repo download {[project] change[/patchset]}
repo download ${PROJECT_NAME} ${LATEST_CHANGENUMBER}/${LATEST_CURRENT_PATCHSET}
Set ‘Verified’ flag
The build is triggered for only one specific change, so only that one receives automatically the review. We have to set review for all the changes that we used. We can keep a list of changes from the ‘Fetch changes’ section and set review for each one using Gerrit REST API `POST /changes/{change-id}/revisions/{revision-id}/review
`.
https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#set-review
curl -X POST --user "jenkins-builder:${GERRIT_JENKINSBUILDER_PASSWORD}" --header "Content-Type: application/json; charset=UTF-8" -d "{ \"labels\": { \"Verified\": 1 } }" "https://${GERRIT_URL}/a/changes/${GERRIT_CHANGE_ID}/revisions/${GERRIT_REVISION_COMMIT_ID}/review"
The response contains changes in review made to the change with change ID GERRIT_CHANGE_ID
(“Change-Id:” in commit message) and patchset identified by its commit ID GERRIT_REVISION_COMMIT_ID
.
Implementation in Repo Jenkins Library
https://gitea.amarulasolutions.com/i-tools/repo_jenkins_lib
Main classes
Example usage of library
@Library('repo_jenkins_lib')
import com.amarula.gerrit.GerritChange
import com.amarula.repo.Repo
node {
stage('Repo init and sync') {
def changes = []
def value = GerritChange.FAILURE
withCredentials([usernamePassword(
credentialsId: 'jenkins-builder-amarula_gerrit-rest-api',
passwordVariable: 'GERRIT_RESTAPI_PASS',
usernameVariable: 'GERRIT_RESTAPI_USER')]) {
try {
def project = new Repo(this, env,
'ssh://gitea@gitea.amarulasolutions.com:38745/myAndroidProject/manifest.git')
sshagent(['someCredentialId1', 'someCredentialId2']) {
// repo init
project.init()
// checkout topic changes for manifest
changes.addAll(project.checkoutTopicForManifest(env.GERRIT_TOPIC))
// repo sync
project.sync()
// checkout topic changes for projects
changes.addAll(project.checkoutTopic(env.GERRIT_TOPIC))
// build project
sh 'make'
value = GerritChange.SUCCESS
}
} finally {
for (change in changes) {
change.setVerified(value)
}
}
}
} /* END 'Repo init and sync' */
}