Reputation: 1748
I'm using Jenkins declarative pipeline and I'm trying to execute a specific build stage only if changes were made ONLY in a specified directory.
So my directory hierarchy looks something like this:
root
├─ some-directory
| ├─ sub-directory
| | └─ file-1
| ├─ file-1
| └─ file-2
├─ another-directory
| ├─ file-1
| └─ file-2
├─ file-x
└─ file-y
This is the current code:
stage ("Deploy branches") {
agent any
when {
allOf {
not { branch 'master' }
changeset "some-directory/**"
}
}
steps {
// do stuff
}
}
This deploys whenever something was changed in "some-directory" but also when something outside of "some-directory" was changed. I would like this step to run if nothing else but the contents of "some-directory" were changed.
This is what the Jenkins docs say about the "changeset" directive:
changeset
Execute the stage if the build’s SCM changeset contains one or more files matching the given string or glob. Example: when { changeset "**/*.js" }
By default the path matching will be case insensitive, this can be turned off with the caseSensitive parameter, for example: when { changeset glob: "ReadMe.*", caseSensitive: true }
"If the changeset contains one or more files matching the given string or glob." means the pipelines works as designed, but what I nwould eed is
"If the files in the changeset match only the given string or glob."
Unfortunately I couldn't find anything about that in the docs or somewhere else on the internet.
Do you have any suggestions how I could make this possible?
Upvotes: 9
Views: 26417
Reputation: 11
We were not able to use when { changeset "**/*.js" }
, because the pipline was generated dynamically and it is not possible to use when {}
inside of a script {}
. You will get the following error:
java.lang.NoSuchMethodError: No such DSL method 'when' found among steps
We created a wrapper function to check for changes by using the same implementation as the changeset
declaration does.
import org.jenkinsci.plugins.pipeline.modeldefinition.when.impl.ChangeSetConditional
def caseSensitive = false
def hasChanges(String pattern) {
def changeLogSets = currentBuild.changeSets
def conditional = new ChangeSetConditional(pattern)
for (set in changeLogSets) {
def entries = set.items
for (entry in entries) {
if (conditional.changeSetMatches(entry, pattern, caseSensitive)) {
return true;
}
}
}
return false;
}
Usage:
script {
for (library in libraries) {
def changesFound = hasChanges("${library.path}/**");
if (changesFound) {
// do stuff
}
}
}
Upvotes: 1
Reputation: 321
I believe that changeset
cannot do what you are trying to achieve ; but it can be replaced with an expression
shelling out to compute the Git diff and do the relevant filtering. I had a similar issue and it worked fine for me.
The following should work for your use-case:
stage ("Deploy branches") {
agent any
when {
allOf {
not { branch 'master' }
changeset "some-directory/**"
expression { // there are changes in some-directory/...
sh(returnStatus: true, script: 'git diff origin/master --name-only | grep --quiet "^some-directory/.*"') == 0
}
expression { // ...and nowhere else.
sh(returnStatus: true, script: 'git diff origin/master --name-only | grep --quiet --invert-match "^some-directory/.*"') == 1
}
}
}
steps {
// do stuff
}
}
(this hardcodes the comparison with origin/master
, which was my use-case ; if that’s not sophisticated enough it might be possible to use some Jenkins variable containing the target branch of the pull-request.)
Upvotes: 6
Reputation: 437
Some old jenkins pipeline versions doesn't support when statement.
You can check for changes like this too:
def rc = sh(
script: "git status -s ${dir} | grep -q ${dir}",
returnStatus: true
)
if(!rc) {
doSomething(dir)
}
Upvotes: 0