How can I limit a Jenkins build step to ONLY given changeset?
Asked Answered
G

3

9

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?

Gilson answered 10/9, 2019 at 19:11 Comment(3)
Try changeset "some-directory/**/*"Unfailing
But this won't fix the problem, that the pipeline will react on changes outside of "some-directory". I'm gonna make my question more precise on this point.Gilson
I believe this means, that the pipeline will react once it finds this location within your changeset. Since this folder is always present and its a match, it reacts - even though the change is not in that directory.Claycomb
D
5

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.)

Dunstable answered 4/8, 2020 at 10:15 Comment(1)
It looks like == 0 and == 1 are checking the exit status code, correct? I wasn't able to reproduce a 1 exit code for a --quiet --invert-match grep. Is there a way to force git to produce the exit code? Alternatively, could the -c flag be used to count matches?Rotz
S
1

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
    }
  }
}
Socman answered 13/1, 2023 at 13:0 Comment(0)
B
0

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)
}
Bibeau answered 15/6, 2021 at 10:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.