Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.jenkins.plugins.forensics.delta;

import java.io.Serializable;
import java.util.Optional;

import edu.hm.hafner.util.FilteredLog;

import io.jenkins.plugins.forensics.delta.model.Delta;

/**
* Calculates the code difference - so called 'delta' - between two commits.
*
* @author Florian Orendi
*/
public abstract class DeltaCalculator implements Serializable {

private static final long serialVersionUID = 8641535877389921937L;

/**
* Calculates the {@link Delta} between two passed commits.
*
* @param currentCommitId
* The currently processed commit ID
* @param referenceCommitId
* The reference commit ID
* @param logger
* The used log
*
* @return the delta if it could be calculated
*/
public abstract Optional<Delta> calculateDelta(String currentCommitId, String referenceCommitId,
FilteredLog logger);

/**
* A delta calculator that does nothing.
*/
public static class NullDeltaCalculator extends DeltaCalculator {

private static final long serialVersionUID = 1564285974889709821L;

@Override
public Optional<Delta> calculateDelta(final String currentCommitId, final String referenceCommitId,
final FilteredLog logger) {
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package io.jenkins.plugins.forensics.delta;

import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;

import edu.hm.hafner.util.FilteredLog;

import hudson.ExtensionPoint;
import hudson.FilePath;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.scm.SCM;

import io.jenkins.plugins.forensics.delta.DeltaCalculator.NullDeltaCalculator;
import io.jenkins.plugins.forensics.util.ScmResolver;
import io.jenkins.plugins.util.JenkinsFacade;

/**
* Jenkins extension point that allows plugins to create {@link DeltaCalculator} instances based on a supported {@link
* SCM}.
*
* @author Florian Orendi
*/
public abstract class DeltaCalculatorFactory implements ExtensionPoint {

private static final Function<Optional<DeltaCalculator>, Stream<? extends DeltaCalculator>> OPTIONAL_MAPPER
= o -> o.map(Stream::of).orElseGet(Stream::empty);

/**
* Returns a {@link DeltaCalculator} for the specified {@link Run build}.
*
* @param run
* the current build
* @param scmDirectories
* paths to search for the SCM repository
* @param listener
* a task listener
* @param logger
* a logger to report error messages
*
* @return a delta calculator for the SCM of the specified build or a {@link NullDeltaCalculator} if the SCM is not
* supported
*/
public static DeltaCalculator findDeltaCalculator(final Run<?, ?> run,
final Collection<FilePath> scmDirectories, final TaskListener listener, final FilteredLog logger) {
return scmDirectories.stream()
.map(directory -> findDeltaCalculator(run, directory, listener, logger))
.flatMap(OPTIONAL_MAPPER)
.findFirst()
.orElseGet(() -> createNullDeltaCalculator(logger));
}

/**
* Returns a {@link DeltaCalculator} for the specified {@link SCM repository}.
*
* @param scm
* the key of the SCM repository (substring that must be part of the SCM key)
* @param run
* the current build
* @param workTree
* the working tree of the repository
* @param listener
* a task listener
* @param logger
* a logger to report error messages
*
* @return a delta calculator for the SCM of the specified build or a {@link NullDeltaCalculator} if the SCM is not
* supported
*/
public static DeltaCalculator findDeltaCalculator(final String scm, final Run<?, ?> run,
final FilePath workTree, final TaskListener listener, final FilteredLog logger) {
Collection<? extends SCM> scms = new ScmResolver().getScms(run, scm);
if (scms.isEmpty()) {
logger.logInfo("-> no SCMs found to be processed");
return new NullDeltaCalculator();
}
return findAllDeltaCalculatorFactoryInstances().stream()
.map(deltaCalculatorFactory -> deltaCalculatorFactory.createDeltaCalculator(scms.iterator().next(), run,
workTree, listener, logger))
.flatMap(OPTIONAL_MAPPER)
.findFirst()
.orElseGet(() -> createNullDeltaCalculator(logger));
}

/**
* Returns a {@link DeltaCalculator} for the specified {@link Run build} and {@link FilePath}.
*
* @param run
* the current build
* @param workTree
* the working tree of the repository
* @param listener
* a task listener
* @param logger
* a logger to report error messages
*
* @return a delta calculator for the SCM of the specified build or a {@link NullDeltaCalculator} if the SCM is not
* supported
*/
private static Optional<DeltaCalculator> findDeltaCalculator(final Run<?, ?> run, final FilePath workTree,
final TaskListener listener, final FilteredLog logger) {
SCM scm = new ScmResolver().getScm(run);
return findAllDeltaCalculatorFactoryInstances().stream()
.map(deltaCalculatorFactory -> deltaCalculatorFactory.createDeltaCalculator(scm, run, workTree,
listener, logger))
.flatMap(OPTIONAL_MAPPER)
.findFirst();
}

/**
* Creates a {@link NullDeltaCalculator} that can be used if no installed delta calculator has been found.
*
* @param logger
* The log
*
* @return the created delta calculator instance
*/
private static DeltaCalculator createNullDeltaCalculator(final FilteredLog logger) {
if (findAllDeltaCalculatorFactoryInstances().isEmpty()) {
logger.logInfo(
"-> No delta calculator installed yet. "
+ "You need to install the 'git-forensics' plugin to enable it for Git.");
}
else {
logger.logInfo("-> No suitable delta calculator found.");
}
return new NullDeltaCalculator();
}

/**
* Searches for all installed Jenkins extensions for {@link DeltaCalculatorFactory}.
*
* @return all found extensions
*/
private static List<DeltaCalculatorFactory> findAllDeltaCalculatorFactoryInstances() {
return new JenkinsFacade().getExtensionsFor(DeltaCalculatorFactory.class);
}

/**
* Returns a {@link DeltaCalculator} for the specified {@link SCM}.
*
* @param scm
* the {@link SCM} to create the delta calculator for
* @param run
* the current build
* @param workspace
* the workspace of the current build
* @param listener
* a task listener
* @param logger
* a logger to report error messages
*
* @return a matching delta calculator instance
*/
public abstract Optional<DeltaCalculator> createDeltaCalculator(SCM scm, Run<?, ?> run, FilePath workspace,
TaskListener listener, FilteredLog logger);
}
77 changes: 77 additions & 0 deletions src/main/java/io/jenkins/plugins/forensics/delta/model/Change.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package io.jenkins.plugins.forensics.delta.model;

import java.io.Serializable;
import java.util.Objects;

/**
* A change made on specific lines within a specific file.
*
* <p>The lines are defined by a starting and an ending point (1-based line counter), describing the made changes within
* the new version of the file. In case of a deleted file, the line range describes the deleted lines within the old
* version of the file, since only then it is possible to determine what has been deleted.
*
* @author Florian Orendi
*/
@SuppressWarnings("PMD.DataClass")
public class Change implements Serializable {

private static final long serialVersionUID = 1543635877389921937L;

private final ChangeEditType changeEditType;

/**
* The starting point of the change (1-based).
*/
private final int fromLine;
/**
* The ending point of the change (1-based).
*/
private final int toLine;

/**
* Constructor for an instance which wraps a specific change within a file.
*
* @param changeEditType
* The type of the change
* @param fromLine
* The starting line
* @param toLine
* The ending line
*/
public Change(final ChangeEditType changeEditType, final int fromLine, final int toLine) {
this.changeEditType = changeEditType;
this.fromLine = fromLine;
this.toLine = toLine;
}

public ChangeEditType getEditType() {
return changeEditType;
}

public int getFromLine() {
return fromLine;
}

public int getToLine() {
return toLine;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Change change = (Change) o;
return fromLine == change.fromLine
&& toLine == change.toLine
&& changeEditType == change.changeEditType;
}

@Override
public int hashCode() {
return Objects.hash(changeEditType, fromLine, toLine);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.jenkins.plugins.forensics.delta.model;

/**
* The edit type of a single change within a specific file.
*
* @author Florian Orendi
*/
public enum ChangeEditType {
/**
* The new content replaces old content.
*/
REPLACE,
/**
* New content has been added.
*/
INSERT,
/**
* Content has been deleted.
*/
DELETE,
/**
* Nothing happened with the content.
*/
EMPTY,
/**
* The edit type could not be determined.
*/
UNDEFINED
}
Loading