How to get list of changed files since last build in Jenkins/Hudson
Asked Answered
A

11

34

I have set up Jenkins, but I would like to find out what files were added/changed between the current build and the previous build. I'd like to run some long running tests depending on whether or not certain parts of the source tree were changed.

Having scoured the Internet I can find no mention of this ability within Hudson/Jenkins though suggestions were made to use SVN post-commit hooks. Maybe it's so simple that everyone (except me) knows how to do it!

Is this possible?

Adlib answered 7/6, 2011 at 3:43 Comment(0)
A
5

The CI server will show you the list of changes, if you are polling for changes and using SVN update. However, you seem to want to be changing the behaviour of the build depending on which files were modified. I don't think there is any out-of-the-box way to do that with Jenkins alone.

A post-commit hook is a reasonable idea. You could parameterize the job, and have your hook script launch the build with the parameter value set according to the changes committed. I'm not sure how difficult that might be for you.

However, you may want to consider splitting this into two separate jobs - one that runs on every commit, and a separate one for the long-running tests that you don't always need. Personally I prefer to keep job behaviour consistent between executions. Otherwise traceability suffers.

Armandoarmature answered 7/6, 2011 at 6:6 Comment(2)
"you seem to want to be changing the behaviour of the build depending on which files were modified" That is done, for example, with Maven incremental builds (plugin).Pistoia
In Jenkins\jobs\projectName\builds\75\ I don't see the last changelog file but on the Jenkins dashboard, I see the last-changes option and changes so where to find exactly this last-change file?Lucindalucine
L
18

I have done it the following way. I am not sure if that is the right way, but it seems to be working. You need to get the Jenkins Groovy plugin installed and do the following script.

import hudson.model.*;
import hudson.util.*;
import hudson.scm.*;
import hudson.plugins.accurev.*

def thr = Thread.currentThread();
def build = thr?.executable;

def changeSet= build.getChangeSet();

changeSet.getItems();

ChangeSet.getItems() gives you the changes. Since I use accurev, I did List<AccurevTransaction> accurevTransList = changeSet.getItems();.

Here, the modified list contains duplicate files/names if it has been committed more than once during the current build window.

Leonie answered 27/2, 2012 at 22:15 Comment(3)
Used your code to come to this solution: https://mcmap.net/q/197030/-process-only-changed-files, thanks a lot!Toaster
if the safe navigation (?) operator is used on thr, isn't it possible that build could be null, and then you'd get a null pointer exception at build.getChangeSet() the next line down?Sharpen
I'm having "groovy.lang.MissingPropertyException: No such property: executable for class: java.lang.Thread"... Surely I missed something?Furore
A
5

The CI server will show you the list of changes, if you are polling for changes and using SVN update. However, you seem to want to be changing the behaviour of the build depending on which files were modified. I don't think there is any out-of-the-box way to do that with Jenkins alone.

A post-commit hook is a reasonable idea. You could parameterize the job, and have your hook script launch the build with the parameter value set according to the changes committed. I'm not sure how difficult that might be for you.

However, you may want to consider splitting this into two separate jobs - one that runs on every commit, and a separate one for the long-running tests that you don't always need. Personally I prefer to keep job behaviour consistent between executions. Otherwise traceability suffers.

Armandoarmature answered 7/6, 2011 at 6:6 Comment(2)
"you seem to want to be changing the behaviour of the build depending on which files were modified" That is done, for example, with Maven incremental builds (plugin).Pistoia
In Jenkins\jobs\projectName\builds\75\ I don't see the last changelog file but on the Jenkins dashboard, I see the last-changes option and changes so where to find exactly this last-change file?Lucindalucine
C
5
echo $SVN_REVISION
svn_last_successful_build_revision=`curl $JOB_URL'lastSuccessfulBuild/api/json' | python -c 'import json,sys;obj=json.loads(sys.stdin.read());print obj["'"changeSet"'"]["'"revisions"'"][0]["'"revision"'"]'`
diff=`svn di -r$SVN_REVISION:$svn_last_successful_build_revision --summarize`
Counterweigh answered 14/3, 2012 at 6:10 Comment(2)
can you please explain how to do this and where? your solution seems to be the one i am looking for but i don't know how to implement it.Underthrust
@user1725378 run in build shell for subversion onlyCounterweigh
T
5

You can use the Jenkins Remote Access API to get a machine-readable description of the current build, including its full change set. The subtlety here is that if you have a 'quiet period' configured, Jenkins may batch multiple commits to the same repository into a single build, so relying on a single revision number is a bit naive.

I like to keep my Subversion post-commit hooks relatively simple and hand things off to the CI server. To do this, I use wget to trigger the build, something like this...

/usr/bin/wget --output-document "-" --timeout=2 \
    https://ci.example.com/jenkins/job/JOBID/build?token=MYTOKEN

The job is then configured on the Jenkins side to execute a Python script that leverages the BUILD_URL environment variable and constructs the URL for the API from that. The URL ends up looking like this:

https://ci.example.com/jenkins/job/JOBID/BUILDID/api/json/

Here's some sample Python code that could be run inside the shell script. I've left out any error handling or HTTP authentication stuff to keep things readable here.

import os
import json
import urllib2


# Make the URL 
build_url = os.environ['BUILD_URL']
api = build_url + 'api/json/'

# Call the Jenkins server and figured out what changed
f = urllib2.urlopen(api)
build = json.loads(f.read())
change_set = build['changeSet']
items = change_set['items']
touched = []
for item in items:
    touched += item['affectedPaths']
Trimble answered 3/4, 2012 at 18:34 Comment(0)
A
4

Using the Build Flow plugin and Git:

final changeSet = build.getChangeSet()
final changeSetIterator = changeSet.iterator()
while (changeSetIterator.hasNext()) {
  final gitChangeSet = changeSetIterator.next()
  for (final path : gitChangeSet.getPaths()) {
    println path.getPath()
  }
}
Annitaanniversary answered 25/9, 2014 at 17:56 Comment(3)
Is there a way to get the absolute path of the file? This gives the path relative to the repository, so I can't figure out what repository the file was in.Chatman
I don't know off the top of my head. You'll have to go through the source, github.com/jenkinsci/git-plugin/blob/master/src/main/java/…, and/or experiment yourself.Annitaanniversary
Build Flow Plugin is now deprecated, for me (Jenkins 2.89) this solution is not working.Preachy
P
3

With Jenkins pipelines (pipeline supporting APIs plugin 2.2 or above), this solution is working for me:

def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
  def entries = changeLogSets[i].items
  for (int j = 0; j < entries.length; j++) {
    def entry = entries[j]
    def files = new ArrayList(entry.affectedFiles)
    for (int k = 0; k < files.size(); k++) {
      def file = files[k]
      println file.path
    }
  }
}

See How to access changelogs in a pipeline job.

Preachy answered 15/1, 2018 at 14:36 Comment(0)
H
2

Through Groovy:

<!-- CHANGE SET -->
<% changeSet = build.changeSet
if (changeSet != null) {
hadChanges = false %>
<h2>Changes</h2>
<ul>
<% changeSet.each { cs ->
hadChanges = true
aUser = cs.author %>
<li>Commit <b>${cs.revision}</b> by <b><%= aUser != null ? aUser.displayName :      it.author.displayName %>:</b> (${cs.msg})
<ul>
<% cs.affectedFiles.each { %>
<li class="change-${it.editType.name}"><b>${it.editType.name}</b>: ${it.path}                              </li> <%  } %> </ul>   </li> <%  }

 if (!hadChanges) { %>  
  <li>No Changes !!</li>
 <%  } %>   </ul> <% } %>
Holbert answered 3/1, 2013 at 20:42 Comment(1)
The changeSet for a new branch is always empty. The Jenkins team is aware of this, and due to the complexity of Git, they don't intend to fix it, because there is no universally-correct way to determine the right parent to compare with: issues.jenkins.io/browse/JENKINS-26354Lowman
A
2
#!/bin/bash

set -e

job_name="whatever"
JOB_URL="http://myserver:8080/job/${job_name}/"
FILTER_PATH="path/to/folder/to/monitor"

python_func="import json, sys
obj = json.loads(sys.stdin.read())
ch_list = obj['changeSet']['items']
_list = [ j['affectedPaths'] for j in ch_list ]
for outer in _list:
  for inner in outer:
    print inner
"

_affected_files=`curl --silent ${JOB_URL}${BUILD_NUMBER}'/api/json' | python -c "$python_func"`

if [ -z "`echo \"$_affected_files\" | grep \"${FILTER_PATH}\"`" ]; then
  echo "[INFO] no changes detected in ${FILTER_PATH}"
  exit 0
else
  echo "[INFO] changed files detected: "
  for a_file in `echo "$_affected_files" | grep "${FILTER_PATH}"`; do
    echo "    $a_file"
  done;
fi;

It is slightly different - I needed a script for Git on a particular folder... So, I wrote a check based on jollychang.

It can be added directly to the job's exec shell script. If no files are detected it will exit 0, i.e. SUCCESS... this way you can always trigger on check-ins to the repository, but build when files in the folder of interest change.

But... If you wanted to build on-demand (i.e. clicking Build Now) with the changed from the last build.. you would change _affected_files to:

_affected_files=`curl --silent $JOB_URL'lastSuccessfulBuild/api/json' | python -c "$python_func"`
Atlantes answered 4/10, 2016 at 21:45 Comment(0)
H
1

I tried to add that to comments but code in comments is no way:

Just want to prettify code from heroin's answer:

def changedFiles = []
def changeLogSets = currentBuild.changeSets
for (entries in changeLogSets) {
    for (entry in entries) {
        for (file in entry.affectedFiles) {
            echo "Found changed file: ${file.path}"
            changedFiles += "${file.path}"
        }
    }
}

Keep in mind for some cases git plugin returns empty changeSet, like:

  • First run in newly created branch
  • 'Build now' button build while there is no new commits on remote since last build

Refer to https://issues.jenkins-ci.org/browse/JENKINS-26354 for more details.

Hesse answered 23/12, 2019 at 22:44 Comment(0)
P
0

Note: You have to use Jenkins' own SVN client to get a change list. Doing it through a shell build step won't list the changes in the build.

Planoconcave answered 28/9, 2011 at 10:21 Comment(0)
G
0

It's simple, but this works for me:

$DirectoryA = "D:\Jenkins\jobs\projectName\builds"  ####Jenkind directory
$firstfolder = Get-ChildItem -Path $DirectoryA | Where-Object {$_.PSIsContainer} | Sort-Object LastWriteTime -Descending | Select-Object -First 1
$DirectoryB = $DirectoryA + "\" + $firstfolder

$sVnLoGfIle = $DirectoryB + "\" + "changelog.xml"

write-host $sVnLoGfIle
Gualterio answered 19/7, 2018 at 5:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.