No more emails are being sent on compilation errors from the automation server
Context
We have an old and arcane VCS at work. To get it to work with Jenkins, I created a job that polls the VCS once an hour and creates commits in a locel Git repo if any changes are found. The git commits hold in their description all of the tasks (aka commits) that were commited in the past hour.
After we compile our environments we parse the git change log to get all of the info from the git description so we can create our own nicely formatted table of commits entered into our VCS.
The issue
Since a given commit we stopped getting error emails from the automation server.
After a quick analysis, I discovered that the issue was due to the commit message containing the string IL!. (Yes, I'm the one that entered that commit, oopsie).
Here is the code that deals with the parsing the changelog:
def tasks = cs.comment.replaceAll("\n", " ").replaceAll("Fix Task for IL!", "Fix Task for ").split('IL!')
We split based on that specific string (IL!). We can't use \n as it is sometimes part of the description itself.
Fixing the issue
To fix the issue I decided to try to get a full regex match on for task, instead of some simple search and replace.
Regex
Task description
A task description has the following fields:
- Commit ID: IL!<multiple digits>
- Timestamp: date/month/year hour:minutes <AM|PM>
- User name: first name _ last name abbreviated
- Message: Anything goes (including newlines)
- Newline
Solutions found on the web
Searching for groovy splitting based on regex I found the following solution
def m = (source =~ /\d+/)
def splits = []
def lastMatch = 0
while(m.find()) {
// grab the string in between the end of the last match
// and the start of the current one (empty string if none)
splits << source.substring(lastMatch, m.start())
// grab the delimiter
splits << m.group()
// keep looking from the end of the current match
lastMatch = m.end()
}
// grab everything after the end of the last match
splits << source.substring(lastMatch)
As mentioned over there:
[The split method itself is essentially this algorithm but without the splits << m.group() line, except that it drops trailing empty strings]
The other option mentioned there is using regex with lookahead:
"a+b+c+d".split("((?<=\\+)|(?=\\+))").each{ println it }
Building the regex
We start with a dummy text:
text = "IL!228074 12/3/18 3:46 PM tsvi_m Fix backdoor paths for foo and\nbar\nIL!228074\nIL!228074 12/3/18 3:46 PM tsvi_m Fix backdoor paths for foo and\nbar\n"
The end result we expect is a list with 2 items:
"IL!228074 12/3/18 3:46 PM tsvi_m Fix backdoor paths for foo and bar IL!228074"
"IL!228074 12/3/18 3:46 PM tsvi_m Fix backdoor paths for foo and bar"
Based on the Task description above, our regex should be as follows:
IL!\d+ \d{1,2}/\d{1,2}/\d{1,2} \d{1,2}:\d{1,2} [AP]M [a-z_]* .*
Creating the code
I used Jenkins' script console to play around with the above code to understand what it does.
The issue with our case, is we need to find every starting match as we don't have any indication of where the comment ends. This led me to split the loop into 2. The first loop, finds all the matching occurences of a starting match and stores them.
After that we loop over the matches found and pull the substring.
Here is our solution:
m = (text =~ /IL!\d+ \d{1,2}\/\d{1,2}\/\d{1,2} \d{1,2}:\d{1,2} [AP]M [a-z_]* .*/)
matchStarts = []
tasks = []
while(m.find()) {
matchStarts << m.start()
}
matchStarts.eachWithIndex { matchStart, index ->
if (matchStarts[index + 1]) {
tasks << text.substring(matchStart, matchStarts[index + 1]).replace("\n", " ").trim()
} else {
tasks << text.substring(matchStart).replace("\n", " ").trim()
}
}
I don't like the if/else clause, something better might be in the format of if lastMatch { matchEnd = last char }?
For now it works, I've opened up a question on StackOverflow
If you have a better solution, feel free to answer over there or discuss it here in the comments.