Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[JENKINS-10449] Factored out svnlog filter to use with detection and …
…changelogs
  • Loading branch information
Brent Atkinson committed Dec 15, 2012
1 parent 2957fd7 commit f71918f
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 131 deletions.
13 changes: 11 additions & 2 deletions src/main/java/hudson/scm/DirAwareSVNXMLLogHandler.java
Expand Up @@ -41,7 +41,14 @@ public class DirAwareSVNXMLLogHandler extends SVNXMLLogHandler implements ISVNLo
private boolean myIsOmitLogMessage;

private LinkedList<MergeFrame> myMergeStack;


private SVNLogFilter filter;

public DirAwareSVNXMLLogHandler(ContentHandler contentHandler, SVNLogFilter filter) {
super(contentHandler);
this.filter = filter;
}

public DirAwareSVNXMLLogHandler(ContentHandler contentHandler, ISVNDebugLog log) {
super(contentHandler, log);
}
Expand All @@ -68,7 +75,9 @@ public void setOmitLogMessage(boolean omitLogMessage) {
*/
public void handleLogEntry(SVNLogEntry logEntry) throws SVNException {
try {
sendToHandler(logEntry);
if (!filter.hasExclusionRule() || filter.isIncluded(logEntry)) {
sendToHandler(logEntry);
}
} catch (SAXException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.XML_MALFORMED, e.getLocalizedMessage());
SVNErrorManager.error(err, e, SVNLogType.DEFAULT);
Expand Down
170 changes: 170 additions & 0 deletions src/main/java/hudson/scm/SVNLogFilter.java
@@ -0,0 +1,170 @@
package hudson.scm;

import hudson.Util;
import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.SVNLogEntryPath;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperties;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
* Determines whether a log entry contains changes within the included paths for a project.
*/
public class SVNLogFilter {

private PrintStream log;
private SubversionSCM scm;

private Pattern[] excludedPatterns;
private Pattern[] includedPatterns;
private Set<String> excludedUsers;
private String excludedRevprop;
private Pattern[] excludedCommitMessages;

public SVNLogFilter(SubversionSCM scm, Pattern[] excludedPatterns, Pattern[] includedPatterns, Set<String> excludedUsers, String excludedRevProp, Pattern[] excludedCommitMessages) {
this.scm = scm;
this.excludedPatterns = excludedPatterns;
this.includedPatterns = includedPatterns;
this.excludedUsers = excludedUsers;
this.excludedRevprop = excludedRevProp;
this.excludedCommitMessages = excludedCommitMessages;
}

public void setLog(PrintStream log) {
this.log = log;
}

/**
* Is there any exclusion rule?
*/
public boolean hasExclusionRule() {
return excludedPatterns.length > 0 || !excludedUsers.isEmpty() || excludedRevprop != null || excludedCommitMessages.length > 0 || includedPatterns.length > 0 || scm.isIgnoreDirPropChanges();
}

/**
* Checks if the given log entry should be considered for the purposes
* of SCM polling.
*
* @return <code>true</code> if the should trigger polling, <code>false</code> otherwise
*/
public boolean isIncluded(SVNLogEntry logEntry) {
if (excludedRevprop != null) {
// If the entry includes the exclusion revprop, don't count it as a change
SVNProperties revprops = logEntry.getRevisionProperties();
if (revprops != null && revprops.containsName(excludedRevprop)) {
if (log != null) {
log.println(hudson.scm.subversion.Messages.SubversionSCM_pollChanges_ignoredRevision(
logEntry.getRevision(),
hudson.scm.subversion.Messages.SubversionSCM_pollChanges_ignoredRevision_revprop(excludedRevprop)));
}
return false;
}
}

String author = logEntry.getAuthor();
if (excludedUsers.contains(author)) {
// If the author is an excluded user, don't count this entry as a change
if (log != null) {
log.println(hudson.scm.subversion.Messages.SubversionSCM_pollChanges_ignoredRevision(
logEntry.getRevision(),
hudson.scm.subversion.Messages.SubversionSCM_pollChanges_ignoredRevision_author(author)));
}
return false;
}

if (excludedCommitMessages != null) {
// If the commit message contains one of the excluded messages, don't count it as a change
String commitMessage = logEntry.getMessage();
for (Pattern pattern : excludedCommitMessages) {
if (pattern.matcher(commitMessage).find()) {
return false;
}
}
}

// If there were no changes, don't count this entry as a change
Map<String, SVNLogEntryPath> changedPaths = logEntry.getChangedPaths();
if (changedPaths.isEmpty()) {
return false;
}

// dirPropChanges are changes that modifiy ('M') a directory, i.e. only
// exclude if there are NO changes on files or Adds/Removals
if (scm.isIgnoreDirPropChanges()) {
boolean contentChanged = false;
for (SVNLogEntryPath path : changedPaths.values()) {
if (path.getType() != 'M' || path.getKind() != SVNNodeKind.DIR) {
contentChanged = true;
break;
}
}
if (!contentChanged) {
if (log != null) {
log.println(hudson.scm.subversion.Messages.SubversionSCM_pollChanges_ignoredRevision(
logEntry.getRevision(),
hudson.scm.subversion.Messages.SubversionSCM_pollChanges_ignoredRevision_onlydirprops()));
}
return false;
}
}

// If there are included patterns, see which paths are included
List<String> includedPaths = new ArrayList<String>();
if (includedPatterns.length > 0) {
for (String path : changedPaths.keySet()) {
for (Pattern pattern : includedPatterns) {
if (pattern.matcher(path).matches()) {
includedPaths.add(path);
break;
}
}
}
} else {
includedPaths = new ArrayList<String>(changedPaths.keySet());
}

// If no paths are included don't count this entry as a change
if (includedPaths.isEmpty()) {
if (log != null) {
log.println(hudson.scm.subversion.Messages.SubversionSCM_pollChanges_ignoredRevision(
logEntry.getRevision(),
hudson.scm.subversion.Messages.SubversionSCM_pollChanges_ignoredRevision_noincpath()));
}
return false;
}

// Else, check each changed path
List<String> excludedPaths = new ArrayList<String>();
if (excludedPatterns.length > 0) {
for (String path : includedPaths) {
for (Pattern pattern : excludedPatterns) {
if (pattern.matcher(path).matches()) {
excludedPaths.add(path);
break;
}
}
}
}

// If all included paths are in an excluded region, don't count this entry as a change
if (includedPaths.size() == excludedPaths.size()) {
if (log != null) {
log.println(hudson.scm.subversion.Messages.SubversionSCM_pollChanges_ignoredRevision(
logEntry.getRevision(),
hudson.scm.subversion.Messages.SubversionSCM_pollChanges_ignoredRevision_path(Util.join(excludedPaths, ", "))));
}
return false;
}

// Otherwise, a change is a change
return true;
}

private static final long serialVersionUID = 1L;
}
2 changes: 1 addition & 1 deletion src/main/java/hudson/scm/SubversionChangeLogBuilder.java
Expand Up @@ -103,7 +103,7 @@ public boolean run(Collection<SubversionSCM.External> externals, Result changeLo
SVNLogClient svnlc = manager.getLogClient();
TransformerHandler th = createTransformerHandler();
th.setResult(changeLog);
SVNXMLLogHandler logHandler = new DirAwareSVNXMLLogHandler(th);
SVNXMLLogHandler logHandler = new DirAwareSVNXMLLogHandler(th, scm.createSVNLogFilter());
// work around for http://svnkit.com/tracker/view.php?id=175
th.setDocumentLocator(DUMMY_LOCATOR);
logHandler.startDocument();
Expand Down
139 changes: 11 additions & 128 deletions src/main/java/hudson/scm/SubversionSCM.java
Expand Up @@ -130,9 +130,7 @@
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.SVNLogEntryPath;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationOutcomeListener;
Expand Down Expand Up @@ -1245,22 +1243,23 @@ public PollingResult call() throws IOException {
});
}

public SVNLogFilter createSVNLogFilter() {
return new SVNLogFilter(this, getExcludedRegionsPatterns(), getIncludedRegionsPatterns(),
getExcludedUsersNormalized(), getExcludedRevprop(), getExcludedCommitMessagesPatterns());
}

/**
* Goes through the changes between two revisions and see if all the changes
* are excluded.
*/
private final class SVNLogHandler implements ISVNLogEntryHandler, Serializable {
private boolean changesFound = false;

private final TaskListener listener;
private final Pattern[] excludedPatterns = getExcludedRegionsPatterns();
private final Pattern[] includedPatterns = getIncludedRegionsPatterns();
private final Set<String> excludedUsers = getExcludedUsersNormalized();
private final String excludedRevprop = getExcludedRevpropNormalized();
private final Pattern[] excludedCommitMessages = getExcludedCommitMessagesPatterns();
private boolean changesFound = false;
private SVNLogFilter filter;

private SVNLogHandler(TaskListener listener) {
this.listener = listener;
this.filter = createSVNLogFilter();
this.filter.setLog(listener.getLogger());
}

public boolean isChangesFound() {
Expand All @@ -1274,7 +1273,7 @@ public boolean findNonExcludedChanges(SVNURL url, long from, long to, ISVNAuthen
if (from>to) return false; // empty revision range, meaning no change

// if no exclusion rules are defined, don't waste time going through "svn log".
if (!hasExclusionRule()) return true;
if (!filter.hasExclusionRule()) return true;

final SvnClientManager manager = createClientManager(authProvider);
try {
Expand All @@ -1294,13 +1293,6 @@ public boolean findNonExcludedChanges(SVNURL url, long from, long to, ISVNAuthen
return isChangesFound();
}

/**
* Is there any exclusion rule?
*/
private boolean hasExclusionRule() {
return excludedPatterns.length>0 || !excludedUsers.isEmpty() || excludedRevprop != null || excludedCommitMessages.length>0 || includedPatterns.length>0 || ignoreDirPropChanges;
}

/**
* Handles a log entry passed.
* Check for log entries that should be excluded from triggering a build.
Expand All @@ -1312,120 +1304,11 @@ private boolean hasExclusionRule() {
* @throws org.tmatesoft.svn.core.SVNException
*/
public void handleLogEntry(SVNLogEntry logEntry) throws SVNException {
if (checkLogEntry(logEntry)) {
if (filter.isIncluded(logEntry)) {
changesFound = true;
}
}

/**
* Checks if the given log entry should be considered for the purposes
* of SCM polling.
*
* @return <code>true</code> if the should trigger polling, <code>false</code> otherwise
*/
private boolean checkLogEntry(SVNLogEntry logEntry) {
if (excludedRevprop != null) {
// If the entry includes the exclusion revprop, don't count it as a change
SVNProperties revprops = logEntry.getRevisionProperties();
if (revprops != null && revprops.containsName(excludedRevprop)) {
listener.getLogger().println(Messages.SubversionSCM_pollChanges_ignoredRevision(
logEntry.getRevision(),
Messages.SubversionSCM_pollChanges_ignoredRevision_revprop(excludedRevprop)));
return false;
}
}

String author = logEntry.getAuthor();
if (excludedUsers.contains(author)) {
// If the author is an excluded user, don't count this entry as a change
listener.getLogger().println(Messages.SubversionSCM_pollChanges_ignoredRevision(
logEntry.getRevision(),
Messages.SubversionSCM_pollChanges_ignoredRevision_author(author)));
return false;
}

if (excludedCommitMessages != null) {
// If the commit message contains one of the excluded messages, don't count it as a change
String commitMessage = logEntry.getMessage();
for (Pattern pattern : excludedCommitMessages) {
if (pattern.matcher(commitMessage).find()) {
return false;
}
}
}

// If there were no changes, don't count this entry as a change
Map<String, SVNLogEntryPath> changedPaths = logEntry.getChangedPaths();
if (changedPaths.isEmpty()) {
return false;
}

// dirPropChanges are changes that modifiy ('M') a directory, i.e. only
// exclude if there are NO changes on files or Adds/Removals
if (ignoreDirPropChanges) {
boolean contentChanged = false;
for (SVNLogEntryPath path : changedPaths.values()) {
if (path.getType() != 'M' || path.getKind() != SVNNodeKind.DIR) {
contentChanged = true;
break;
}
}
if (!contentChanged) {
listener.getLogger().println(Messages.SubversionSCM_pollChanges_ignoredRevision(
logEntry.getRevision(),
Messages.SubversionSCM_pollChanges_ignoredRevision_onlydirprops()));
return false;
}
}

// If there are included patterns, see which paths are included
List<String> includedPaths = new ArrayList<String>();
if (includedPatterns.length > 0) {
for (String path : changedPaths.keySet()) {
for (Pattern pattern : includedPatterns) {
if (pattern.matcher(path).matches()) {
includedPaths.add(path);
break;
}
}
}
} else {
includedPaths = new ArrayList<String>(changedPaths.keySet());
}

// If no paths are included don't count this entry as a change
if (includedPaths.isEmpty()) {
listener.getLogger().println(Messages.SubversionSCM_pollChanges_ignoredRevision(
logEntry.getRevision(),
Messages.SubversionSCM_pollChanges_ignoredRevision_noincpath()));
return false;
}

// Else, check each changed path
List<String> excludedPaths = new ArrayList<String>();
if (excludedPatterns.length > 0) {
for (String path : includedPaths) {
for (Pattern pattern : excludedPatterns) {
if (pattern.matcher(path).matches()) {
excludedPaths.add(path);
break;
}
}
}
}

// If all included paths are in an excluded region, don't count this entry as a change
if (includedPaths.size() == excludedPaths.size()) {
listener.getLogger().println(Messages.SubversionSCM_pollChanges_ignoredRevision(
logEntry.getRevision(),
Messages.SubversionSCM_pollChanges_ignoredRevision_path(Util.join(excludedPaths, ", "))));
return false;
}

// Otherwise, a change is a change
return true;
}

private static final long serialVersionUID = 1L;
}

Expand Down

0 comments on commit f71918f

Please sign in to comment.