From 9d5242885e4cad5accfd81dbb0ea0a2fd115644e Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Sun, 27 Jun 2021 23:42:00 +0200 Subject: [PATCH 1/7] Add delta forensics for PR and branch builds. --- .../forensics/miner/ForensicsBuildAction.java | 64 ++++++++++++++++--- .../forensics/miner/RepositoryMiner.java | 26 +++++++- .../forensics/miner/RepositoryMinerStep.java | 49 +++++++++++--- .../miner/ForensicsBuildAction/summary.jelly | 26 +++++--- 4 files changed, 136 insertions(+), 29 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/forensics/miner/ForensicsBuildAction.java b/src/main/java/io/jenkins/plugins/forensics/miner/ForensicsBuildAction.java index 34011f98..27263e2d 100644 --- a/src/main/java/io/jenkins/plugins/forensics/miner/ForensicsBuildAction.java +++ b/src/main/java/io/jenkins/plugins/forensics/miner/ForensicsBuildAction.java @@ -2,12 +2,15 @@ import org.apache.commons.lang3.StringUtils; +import edu.hm.hafner.util.FilteredLog; +import edu.hm.hafner.util.NoSuchElementException; import edu.hm.hafner.util.VisibleForTesting; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.kohsuke.stapler.StaplerProxy; import hudson.model.Run; +import io.jenkins.plugins.forensics.reference.ReferenceFinder; import io.jenkins.plugins.util.BuildAction; /** @@ -21,17 +24,22 @@ public class ForensicsBuildAction extends BuildAction impl private static final long serialVersionUID = -263122257268060032L; private static final String DEFAULT_FILE_NAME = "repository-statistics.xml"; - private final int numberOfFiles; private final int miningDurationSeconds; private final String urlName; private String scmKey; // since 0.9.0 private String fileName; // since 0.9.0 + private final int numberOfFiles; private final int totalLinesOfCode; // since 1.1.0 private final int totalChurn; // since 1.1.0 private CommitStatistics commitStatistics; // since 1.1.0 + private final int deltaNumberOfFiles; // since 1.2.0 + private final int deltaTotalLinesOfCode; // since 1.2.0 + private final int deltaTotalChurn; // since 1.2.0 + private CommitStatistics deltaCommitStatistics; // since 1.2.0 + /** * Creates a new instance of {@link ForensicsBuildAction}. * @@ -48,7 +56,7 @@ public class ForensicsBuildAction extends BuildAction impl */ public ForensicsBuildAction(final Run owner, final RepositoryStatistics repositoryStatistics, final int miningDurationSeconds, final String scmKey, final int number) { - this(owner, repositoryStatistics, true, miningDurationSeconds, scmKey, number); + this(owner, repositoryStatistics, new RepositoryStatistics(), miningDurationSeconds, scmKey, number); } /** @@ -58,8 +66,8 @@ public ForensicsBuildAction(final Run owner, final RepositoryStatistics re * the associated build that created the statistics * @param repositoryStatistics * the statistics to persist with this action - * @param canSerialize - * determines whether the result should be persisted in the build folder + * @param deltaStatistics + * the statistics compared to the reference build (if available) * @param miningDurationSeconds * the duration of the mining operation in [s] * @param scmKey @@ -67,22 +75,34 @@ public ForensicsBuildAction(final Run owner, final RepositoryStatistics re * @param number * unique number of the results (used as part of the serialization file name) */ + public ForensicsBuildAction(final Run owner, + final RepositoryStatistics repositoryStatistics, final RepositoryStatistics deltaStatistics, + final int miningDurationSeconds, final String scmKey, final int number) { + this(owner, repositoryStatistics, deltaStatistics, true, miningDurationSeconds, scmKey, number); + } + @VisibleForTesting - ForensicsBuildAction(final Run owner, final RepositoryStatistics repositoryStatistics, + ForensicsBuildAction(final Run owner, + final RepositoryStatistics repositoryStatistics, final RepositoryStatistics deltaStatistics, final boolean canSerialize, final int miningDurationSeconds, final String scmKey, final int number) { super(owner, repositoryStatistics, false); - numberOfFiles = repositoryStatistics.size(); this.miningDurationSeconds = miningDurationSeconds; this.scmKey = scmKey; fileName = createFileName(number); urlName = createUrlName(number); + numberOfFiles = repositoryStatistics.size(); totalLinesOfCode = repositoryStatistics.getTotalLinesOfCode(); totalChurn = repositoryStatistics.getTotalChurn(); commitStatistics = repositoryStatistics.getLatestStatistics(); + deltaNumberOfFiles = deltaStatistics.size(); + deltaTotalLinesOfCode = deltaStatistics.getTotalLinesOfCode(); + deltaTotalChurn = deltaStatistics.getTotalChurn(); + deltaCommitStatistics = deltaStatistics.getLatestStatistics(); + if (canSerialize) { createXmlStream().write(owner.getRootDir().toPath().resolve(fileName), repositoryStatistics); } @@ -100,6 +120,9 @@ protected Object readResolve() { if (commitStatistics == null) { commitStatistics = new CommitStatistics(); } + if (deltaCommitStatistics == null) { + deltaCommitStatistics = new CommitStatistics(); + } return super.readResolve(); } @@ -158,14 +181,14 @@ public String getUrlName() { return urlName; } - public int getNumberOfFiles() { - return numberOfFiles; - } - public int getMiningDurationSeconds() { return miningDurationSeconds; } + public int getNumberOfFiles() { + return numberOfFiles; + } + public int getTotalLinesOfCode() { return totalLinesOfCode; } @@ -178,6 +201,27 @@ public CommitStatistics getCommitStatistics() { return commitStatistics; } + public Run getReferenceBuild() { + return new ReferenceFinder().findReference(getOwner(), new FilteredLog("")) + .orElseThrow(() -> new NoSuchElementException("No reference build available")); + } + + public int getDeltaNumberOfFiles() { + return deltaNumberOfFiles; + } + + public int getDeltaTotalLinesOfCode() { + return deltaTotalLinesOfCode; + } + + public int getDeltaTotalChurn() { + return deltaTotalChurn; + } + + public CommitStatistics getDeltaCommitStatistics() { + return deltaCommitStatistics; + } + public String getScmKey() { return scmKey; } diff --git a/src/main/java/io/jenkins/plugins/forensics/miner/RepositoryMiner.java b/src/main/java/io/jenkins/plugins/forensics/miner/RepositoryMiner.java index 2cba2ead..e016ebb9 100644 --- a/src/main/java/io/jenkins/plugins/forensics/miner/RepositoryMiner.java +++ b/src/main/java/io/jenkins/plugins/forensics/miner/RepositoryMiner.java @@ -4,6 +4,8 @@ import edu.hm.hafner.util.FilteredLog; +import hudson.model.Run; + /** * Obtains commit statistics for a source code repository. Computation of the commit statistics should be done * incrementally, if supported by the underlying SCM (i.e., only commits new in the current build should be @@ -15,7 +17,10 @@ public abstract class RepositoryMiner implements Serializable { private static final long serialVersionUID = -8878714986510536182L; /** - * Obtains commit statistics for a source code repository. + * Obtains commit statistics for a source code repository. If the {@code previousStatistics} are filled with results + * of a previous build, then an incremental mining will be started that only inspects the additional commits from + * the current build. Otherwise, the whole repository will be mined (which might take some time for large + * repositories). * * @param previousStatistics * the repository statistics of the previous build - if there is no such build then an empty instance will @@ -30,6 +35,25 @@ public abstract class RepositoryMiner implements Serializable { public abstract RepositoryStatistics mine(RepositoryStatistics previousStatistics, FilteredLog logger) throws InterruptedException; + /** + * Obtains delta commit statistics for a source code repository. Starts an incremental mining from the latest commit + * from the current build up to the common ancestor commit with the provided {@code referenceBuild}. + * + * @param referenceBuild + * the reference build to use + * @param logger + * the logger to use + * + * @return the statistics containing the commit statistics for the current build up to the common ancestor with the + * reference build + * @throws InterruptedException + * if the user canceled the processing + * @since 1.2.0 + */ + public RepositoryStatistics mine(final Run referenceBuild, final FilteredLog logger) throws InterruptedException { + return new RepositoryStatistics(); + } + /** * A repository miner that does nothing. */ diff --git a/src/main/java/io/jenkins/plugins/forensics/miner/RepositoryMinerStep.java b/src/main/java/io/jenkins/plugins/forensics/miner/RepositoryMinerStep.java index 0cca6a6e..ca4865da 100644 --- a/src/main/java/io/jenkins/plugins/forensics/miner/RepositoryMinerStep.java +++ b/src/main/java/io/jenkins/plugins/forensics/miner/RepositoryMinerStep.java @@ -1,6 +1,7 @@ package io.jenkins.plugins.forensics.miner; import java.util.List; +import java.util.Optional; import org.apache.commons.lang3.StringUtils; @@ -25,6 +26,7 @@ import hudson.tasks.Recorder; import jenkins.tasks.SimpleBuildStep; +import io.jenkins.plugins.forensics.reference.ReferenceFinder; import io.jenkins.plugins.forensics.util.ScmResolver; import io.jenkins.plugins.util.BuildAction; import io.jenkins.plugins.util.LogHandler; @@ -106,31 +108,60 @@ private void mineRepositories(final Run run, final FilePath workspace, fin RepositoryMiner miner = MinerFactory.findMiner(repository, run, workspace, listener, logger); logHandler.log(logger); - RepositoryStatistics repositoryStatistics = previousBuildStatistics(scm, run); + RepositoryStatistics repositoryStatistics = previousBuildStatistics(run); RepositoryStatistics addedRepositoryStatistics = miner.mine(repositoryStatistics, logger); + RepositoryStatistics deltaStatistics = createDeltaStatistics(miner, run, logger); + logHandler.log(logger); int miningDurationSeconds = (int) (1 + (System.nanoTime() - startOfMining) / 1_000_000_000L); - run.addAction(new ForensicsBuildAction(run, addedRepositoryStatistics, miningDurationSeconds, - repository.getKey(), number++)); + run.addAction(new ForensicsBuildAction(run, addedRepositoryStatistics, deltaStatistics, + miningDurationSeconds, repository.getKey(), number++)); } } - private RepositoryStatistics previousBuildStatistics(final String repository, final Run run) { + /** + * Returns the delta statistics of the current build compared to the best common ancestor commit with the reference + * build. If no reference build has been found, then an empty mining result is returned. + * + * @param miner + * the miner + * @param run + * the current build + * @param logger + * the logger + * + * @return the delta statistics if a reference build with previous results has been found + * @throws InterruptedException + * if the user stops the processing + */ + private RepositoryStatistics createDeltaStatistics(final RepositoryMiner miner, + final Run run, final FilteredLog logger) throws InterruptedException { + Optional> referenceBuild = new ReferenceFinder().findReference(run, logger); + if (referenceBuild.isPresent()) { // can't use streams due to checked exception + return miner.mine(referenceBuild.get(), logger); + } + return new RepositoryStatistics(); + } + + private RepositoryStatistics previousBuildStatistics(final Run run) { for (Run build = run.getPreviousBuild(); build != null; build = build.getPreviousBuild()) { List actions = build.getActions(ForensicsBuildAction.class); if (!actions.isEmpty()) { - return actions.stream() - .filter(a -> a.getScmKey().contains(repository)) - .findAny() - .map(BuildAction::getResult) - .orElse(new RepositoryStatistics()); + return extractResult(actions).orElse(new RepositoryStatistics()); } } return new RepositoryStatistics(); } + private Optional extractResult(final List actions) { + return actions.stream() + .filter(a -> a.getScmKey().contains(scm)) + .findAny() + .map(BuildAction::getResult); + } + @Override public Descriptor getDescriptor() { return (Descriptor) super.getDescriptor(); diff --git a/src/main/resources/io/jenkins/plugins/forensics/miner/ForensicsBuildAction/summary.jelly b/src/main/resources/io/jenkins/plugins/forensics/miner/ForensicsBuildAction/summary.jelly index 04ef129b..5eeb69fe 100644 --- a/src/main/resources/io/jenkins/plugins/forensics/miner/ForensicsBuildAction/summary.jelly +++ b/src/main/resources/io/jenkins/plugins/forensics/miner/ForensicsBuildAction/summary.jelly @@ -3,21 +3,29 @@ ${%title}: ${it.scmKey} - + +
  • ${%summary(size(it.result))} - (total lines of code: ${it.result.totalLinesOfCode}, total churn: ${it.result.totalChurn}) + (total lines of code: ${it.totalLinesOfCode}, total churn: ${it.totalChurn})
  • - New commits: ${s.commitCount} (from ${s.authorCount} authors in ${s.filesCount} files) -
  • -
  • - New added lines: ${s.addedLines} -
  • -
  • - New deleted lines: ${s.deletedLines} + New commits since previous build: ${s.commitCount} + (from ${s.authorCount} authors + in ${s.filesCount} files + with ${s.addedLines} added + and ${s.deletedLines} deleted lines)
  • + +
  • + New commits since reference build ${it.referenceBuild.displayName}: ${d.commitCount} + (from ${d.authorCount} authors + in ${d.filesCount} files + with ${d.addedLines} added + and ${d.deletedLines} deleted lines) +
  • +
  • Miner runtime: ${it.miningDurationSeconds} seconds
  • From 58150b80d4d0e36ff736ff311d87be5416e28dc9 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Mon, 28 Jun 2021 09:45:02 +0200 Subject: [PATCH 2/7] Allow dependency from miner to reference. --- .../io/jenkins/plugins/forensics/miner/ForensicsBuildAction.java | 1 + src/test/resources/design.puml | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/java/io/jenkins/plugins/forensics/miner/ForensicsBuildAction.java b/src/main/java/io/jenkins/plugins/forensics/miner/ForensicsBuildAction.java index 27263e2d..89779a5d 100644 --- a/src/main/java/io/jenkins/plugins/forensics/miner/ForensicsBuildAction.java +++ b/src/main/java/io/jenkins/plugins/forensics/miner/ForensicsBuildAction.java @@ -20,6 +20,7 @@ * * @author Ullrich Hafner */ +@SuppressWarnings("PMD.DataClass") public class ForensicsBuildAction extends BuildAction implements StaplerProxy { private static final long serialVersionUID = -263122257268060032L; private static final String DEFAULT_FILE_NAME = "repository-statistics.xml"; diff --git a/src/test/resources/design.puml b/src/test/resources/design.puml index dd5ced63..e909e65a 100644 --- a/src/test/resources/design.puml +++ b/src/test/resources/design.puml @@ -14,6 +14,7 @@ skinparam component { [Blamer] --> [Utilities] [Miner] --> [Utilities] +[Miner] -> [Reference Recorder] [Reference Recorder] --> [Utilities] @enduml From 695ab1ece273939f4eaae6bb721018574b77333c Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Thu, 1 Jul 2021 00:26:13 +0200 Subject: [PATCH 3/7] Create a new action that stores the commit diffs. --- .../miner/CommitStatisticsBuildAction.java | 86 ++++++++++++++++++ .../forensics/miner/ForensicsBuildAction.java | 64 ++----------- .../forensics/miner/RepositoryMiner.java | 26 +----- .../forensics/miner/RepositoryMinerStep.java | 52 +++-------- .../CommitStatisticsBuildAction/summary.jelly | 20 ++++ .../summary.properties | 1 + src/main/webapp/icons/LICENSE.txt | 2 +- src/main/webapp/icons/diff-stat-24x24.png | Bin 0 -> 576 bytes src/main/webapp/icons/diff-stat-48x48.png | Bin 0 -> 796 bytes 9 files changed, 131 insertions(+), 120 deletions(-) create mode 100644 src/main/java/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction.java create mode 100644 src/main/resources/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction/summary.jelly create mode 100644 src/main/resources/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction/summary.properties create mode 100644 src/main/webapp/icons/diff-stat-24x24.png create mode 100644 src/main/webapp/icons/diff-stat-48x48.png diff --git a/src/main/java/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction.java b/src/main/java/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction.java new file mode 100644 index 00000000..66eca458 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction.java @@ -0,0 +1,86 @@ +package io.jenkins.plugins.forensics.miner; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; + +import edu.umd.cs.findbugs.annotations.CheckForNull; + +import hudson.model.Action; +import hudson.model.InvisibleAction; +import hudson.model.Run; +import jenkins.model.RunAction2; +import jenkins.tasks.SimpleBuildStep.LastBuildAction; + +import io.jenkins.plugins.forensics.reference.ReferenceBuild; + +/** + * Controls the life cycle of the commit statistics in a job. This action persists the results of a build and displays a + * summary on the build page. The actual visualization of the results is defined in the matching {@code summary.jelly} + * file. + * + * @author Ullrich Hafner + */ +@SuppressWarnings("PMD.DataClass") +public class CommitStatisticsBuildAction extends InvisibleAction implements LastBuildAction, RunAction2, Serializable { + private static final long serialVersionUID = -263122257268060032L; + + private Run owner; + + private final String scmKey; + private final CommitStatistics commitStatistics; + + /** + * Creates a new instance of {@link CommitStatisticsBuildAction}. + * + * @param owner + * the associated build that created the statistics + * @param scmKey + * key of the repository + * @param commitStatistics + * the statistics to persist with this action + */ + public CommitStatisticsBuildAction(final Run owner, + final String scmKey, final CommitStatistics commitStatistics) { + this.owner = owner; + this.scmKey = scmKey; + this.commitStatistics = commitStatistics; + } + + public Run getOwner() { + return owner; + } + + public String getScmKey() { + return scmKey; + } + + public CommitStatistics getCommitStatistics() { + return commitStatistics; + } + + @CheckForNull + public ReferenceBuild getReferenceBuild() { + return getOwner().getAction(ReferenceBuild.class); + } + + @Override + public String toString() { + return String.format("%s [%s]", scmKey, commitStatistics); + } + + @Override + public void onAttached(final Run run) { + owner = run; + } + + @Override + public void onLoad(final Run run) { + owner = run; + } + + @Override + public Collection getProjectActions() { + return Collections.emptyList(); + } +} diff --git a/src/main/java/io/jenkins/plugins/forensics/miner/ForensicsBuildAction.java b/src/main/java/io/jenkins/plugins/forensics/miner/ForensicsBuildAction.java index 89779a5d..24540ca8 100644 --- a/src/main/java/io/jenkins/plugins/forensics/miner/ForensicsBuildAction.java +++ b/src/main/java/io/jenkins/plugins/forensics/miner/ForensicsBuildAction.java @@ -2,19 +2,16 @@ import org.apache.commons.lang3.StringUtils; -import edu.hm.hafner.util.FilteredLog; -import edu.hm.hafner.util.NoSuchElementException; import edu.hm.hafner.util.VisibleForTesting; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.kohsuke.stapler.StaplerProxy; import hudson.model.Run; -import io.jenkins.plugins.forensics.reference.ReferenceFinder; import io.jenkins.plugins.util.BuildAction; /** - * Controls the live cycle of the forensics results in a job. This action persists the results of a build and displays a + * Controls the life cycle of the forensics results in a job. This action persists the results of a build and displays a * summary on the build page. The actual visualization of the results is defined in the matching {@code summary.jelly} * file. This action also provides access to the forensics details: these are rendered using a new view instance. * @@ -36,11 +33,6 @@ public class ForensicsBuildAction extends BuildAction impl private final int totalChurn; // since 1.1.0 private CommitStatistics commitStatistics; // since 1.1.0 - private final int deltaNumberOfFiles; // since 1.2.0 - private final int deltaTotalLinesOfCode; // since 1.2.0 - private final int deltaTotalChurn; // since 1.2.0 - private CommitStatistics deltaCommitStatistics; // since 1.2.0 - /** * Creates a new instance of {@link ForensicsBuildAction}. * @@ -57,7 +49,7 @@ public class ForensicsBuildAction extends BuildAction impl */ public ForensicsBuildAction(final Run owner, final RepositoryStatistics repositoryStatistics, final int miningDurationSeconds, final String scmKey, final int number) { - this(owner, repositoryStatistics, new RepositoryStatistics(), miningDurationSeconds, scmKey, number); + this(owner, repositoryStatistics, true, miningDurationSeconds, scmKey, number); } /** @@ -67,8 +59,8 @@ public ForensicsBuildAction(final Run owner, final RepositoryStatistics re * the associated build that created the statistics * @param repositoryStatistics * the statistics to persist with this action - * @param deltaStatistics - * the statistics compared to the reference build (if available) + * @param canSerialize + * determines whether the result should be persisted in the build folder * @param miningDurationSeconds * the duration of the mining operation in [s] * @param scmKey @@ -76,34 +68,22 @@ public ForensicsBuildAction(final Run owner, final RepositoryStatistics re * @param number * unique number of the results (used as part of the serialization file name) */ - public ForensicsBuildAction(final Run owner, - final RepositoryStatistics repositoryStatistics, final RepositoryStatistics deltaStatistics, - final int miningDurationSeconds, final String scmKey, final int number) { - this(owner, repositoryStatistics, deltaStatistics, true, miningDurationSeconds, scmKey, number); - } - @VisibleForTesting - ForensicsBuildAction(final Run owner, - final RepositoryStatistics repositoryStatistics, final RepositoryStatistics deltaStatistics, + ForensicsBuildAction(final Run owner, final RepositoryStatistics repositoryStatistics, final boolean canSerialize, final int miningDurationSeconds, final String scmKey, final int number) { super(owner, repositoryStatistics, false); + numberOfFiles = repositoryStatistics.size(); this.miningDurationSeconds = miningDurationSeconds; this.scmKey = scmKey; fileName = createFileName(number); urlName = createUrlName(number); - numberOfFiles = repositoryStatistics.size(); totalLinesOfCode = repositoryStatistics.getTotalLinesOfCode(); totalChurn = repositoryStatistics.getTotalChurn(); commitStatistics = repositoryStatistics.getLatestStatistics(); - deltaNumberOfFiles = deltaStatistics.size(); - deltaTotalLinesOfCode = deltaStatistics.getTotalLinesOfCode(); - deltaTotalChurn = deltaStatistics.getTotalChurn(); - deltaCommitStatistics = deltaStatistics.getLatestStatistics(); - if (canSerialize) { createXmlStream().write(owner.getRootDir().toPath().resolve(fileName), repositoryStatistics); } @@ -121,9 +101,6 @@ protected Object readResolve() { if (commitStatistics == null) { commitStatistics = new CommitStatistics(); } - if (deltaCommitStatistics == null) { - deltaCommitStatistics = new CommitStatistics(); - } return super.readResolve(); } @@ -182,14 +159,14 @@ public String getUrlName() { return urlName; } - public int getMiningDurationSeconds() { - return miningDurationSeconds; - } - public int getNumberOfFiles() { return numberOfFiles; } + public int getMiningDurationSeconds() { + return miningDurationSeconds; + } + public int getTotalLinesOfCode() { return totalLinesOfCode; } @@ -202,27 +179,6 @@ public CommitStatistics getCommitStatistics() { return commitStatistics; } - public Run getReferenceBuild() { - return new ReferenceFinder().findReference(getOwner(), new FilteredLog("")) - .orElseThrow(() -> new NoSuchElementException("No reference build available")); - } - - public int getDeltaNumberOfFiles() { - return deltaNumberOfFiles; - } - - public int getDeltaTotalLinesOfCode() { - return deltaTotalLinesOfCode; - } - - public int getDeltaTotalChurn() { - return deltaTotalChurn; - } - - public CommitStatistics getDeltaCommitStatistics() { - return deltaCommitStatistics; - } - public String getScmKey() { return scmKey; } diff --git a/src/main/java/io/jenkins/plugins/forensics/miner/RepositoryMiner.java b/src/main/java/io/jenkins/plugins/forensics/miner/RepositoryMiner.java index e016ebb9..2cba2ead 100644 --- a/src/main/java/io/jenkins/plugins/forensics/miner/RepositoryMiner.java +++ b/src/main/java/io/jenkins/plugins/forensics/miner/RepositoryMiner.java @@ -4,8 +4,6 @@ import edu.hm.hafner.util.FilteredLog; -import hudson.model.Run; - /** * Obtains commit statistics for a source code repository. Computation of the commit statistics should be done * incrementally, if supported by the underlying SCM (i.e., only commits new in the current build should be @@ -17,10 +15,7 @@ public abstract class RepositoryMiner implements Serializable { private static final long serialVersionUID = -8878714986510536182L; /** - * Obtains commit statistics for a source code repository. If the {@code previousStatistics} are filled with results - * of a previous build, then an incremental mining will be started that only inspects the additional commits from - * the current build. Otherwise, the whole repository will be mined (which might take some time for large - * repositories). + * Obtains commit statistics for a source code repository. * * @param previousStatistics * the repository statistics of the previous build - if there is no such build then an empty instance will @@ -35,25 +30,6 @@ public abstract class RepositoryMiner implements Serializable { public abstract RepositoryStatistics mine(RepositoryStatistics previousStatistics, FilteredLog logger) throws InterruptedException; - /** - * Obtains delta commit statistics for a source code repository. Starts an incremental mining from the latest commit - * from the current build up to the common ancestor commit with the provided {@code referenceBuild}. - * - * @param referenceBuild - * the reference build to use - * @param logger - * the logger to use - * - * @return the statistics containing the commit statistics for the current build up to the common ancestor with the - * reference build - * @throws InterruptedException - * if the user canceled the processing - * @since 1.2.0 - */ - public RepositoryStatistics mine(final Run referenceBuild, final FilteredLog logger) throws InterruptedException { - return new RepositoryStatistics(); - } - /** * A repository miner that does nothing. */ diff --git a/src/main/java/io/jenkins/plugins/forensics/miner/RepositoryMinerStep.java b/src/main/java/io/jenkins/plugins/forensics/miner/RepositoryMinerStep.java index ca4865da..a747a8da 100644 --- a/src/main/java/io/jenkins/plugins/forensics/miner/RepositoryMinerStep.java +++ b/src/main/java/io/jenkins/plugins/forensics/miner/RepositoryMinerStep.java @@ -1,7 +1,6 @@ package io.jenkins.plugins.forensics.miner; import java.util.List; -import java.util.Optional; import org.apache.commons.lang3.StringUtils; @@ -26,7 +25,6 @@ import hudson.tasks.Recorder; import jenkins.tasks.SimpleBuildStep; -import io.jenkins.plugins.forensics.reference.ReferenceFinder; import io.jenkins.plugins.forensics.util.ScmResolver; import io.jenkins.plugins.util.BuildAction; import io.jenkins.plugins.util.LogHandler; @@ -39,6 +37,8 @@ *
  • total number of different authors
  • *
  • creation time
  • *
  • last modification time
  • + *
  • lines of code (from the commit details)
  • + *
  • code churn (changed lines since created)
  • *
* Stores the created statistics in a {@link RepositoryStatistics} instance. The result is attached to * a {@link Run} by registering a {@link ForensicsBuildAction}. @@ -64,6 +64,7 @@ public RepositoryMinerStep() { * * @return this */ + @SuppressWarnings("unused") @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "Deserialization of instances that do not have all fields yet") protected Object readResolve() { if (scm == null) { @@ -108,60 +109,31 @@ private void mineRepositories(final Run run, final FilePath workspace, fin RepositoryMiner miner = MinerFactory.findMiner(repository, run, workspace, listener, logger); logHandler.log(logger); - RepositoryStatistics repositoryStatistics = previousBuildStatistics(run); + RepositoryStatistics repositoryStatistics = previousBuildStatistics(scm, run); RepositoryStatistics addedRepositoryStatistics = miner.mine(repositoryStatistics, logger); - RepositoryStatistics deltaStatistics = createDeltaStatistics(miner, run, logger); - logHandler.log(logger); int miningDurationSeconds = (int) (1 + (System.nanoTime() - startOfMining) / 1_000_000_000L); - run.addAction(new ForensicsBuildAction(run, addedRepositoryStatistics, deltaStatistics, - miningDurationSeconds, repository.getKey(), number++)); + run.addAction(new ForensicsBuildAction(run, addedRepositoryStatistics, miningDurationSeconds, + repository.getKey(), number++)); } } - /** - * Returns the delta statistics of the current build compared to the best common ancestor commit with the reference - * build. If no reference build has been found, then an empty mining result is returned. - * - * @param miner - * the miner - * @param run - * the current build - * @param logger - * the logger - * - * @return the delta statistics if a reference build with previous results has been found - * @throws InterruptedException - * if the user stops the processing - */ - private RepositoryStatistics createDeltaStatistics(final RepositoryMiner miner, - final Run run, final FilteredLog logger) throws InterruptedException { - Optional> referenceBuild = new ReferenceFinder().findReference(run, logger); - if (referenceBuild.isPresent()) { // can't use streams due to checked exception - return miner.mine(referenceBuild.get(), logger); - } - return new RepositoryStatistics(); - } - - private RepositoryStatistics previousBuildStatistics(final Run run) { + private RepositoryStatistics previousBuildStatistics(final String repository, final Run run) { for (Run build = run.getPreviousBuild(); build != null; build = build.getPreviousBuild()) { List actions = build.getActions(ForensicsBuildAction.class); if (!actions.isEmpty()) { - return extractResult(actions).orElse(new RepositoryStatistics()); + return actions.stream() + .filter(a -> a.getScmKey().contains(repository)) + .findAny() + .map(BuildAction::getResult) + .orElse(new RepositoryStatistics()); } } return new RepositoryStatistics(); } - private Optional extractResult(final List actions) { - return actions.stream() - .filter(a -> a.getScmKey().contains(scm)) - .findAny() - .map(BuildAction::getResult); - } - @Override public Descriptor getDescriptor() { return (Descriptor) super.getDescriptor(); diff --git a/src/main/resources/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction/summary.jelly b/src/main/resources/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction/summary.jelly new file mode 100644 index 00000000..67ee3425 --- /dev/null +++ b/src/main/resources/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction/summary.jelly @@ -0,0 +1,20 @@ + + + + + ${%title}: ${it.scmKey} + +
    +
  • + Commits: ${s.commitCount} - compared to target branch of build +
  • +
  • + Changed files: ${s.filesCount} +
  • +
  • + Changed lines: ${s.addedLines} added, ${s.deletedLines} deleted +
  • +
+
+ +
diff --git a/src/main/resources/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction/summary.properties b/src/main/resources/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction/summary.properties new file mode 100644 index 00000000..3c096b16 --- /dev/null +++ b/src/main/resources/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction/summary.properties @@ -0,0 +1 @@ +title=Commit Diff Statistics diff --git a/src/main/webapp/icons/LICENSE.txt b/src/main/webapp/icons/LICENSE.txt index 69fbbd56..b5d249c5 100644 --- a/src/main/webapp/icons/LICENSE.txt +++ b/src/main/webapp/icons/LICENSE.txt @@ -1,4 +1,4 @@ -License for FontAwesome icon: CC BY 4.0 License +License for FontAwesome icons: CC BY 4.0 License https://fontawesome.com/license/free diff --git a/src/main/webapp/icons/diff-stat-24x24.png b/src/main/webapp/icons/diff-stat-24x24.png new file mode 100644 index 0000000000000000000000000000000000000000..566c7a08299465fa4dbcca749ecb4660386af404 GIT binary patch literal 576 zcmV-G0>AxWU0t9lQW|dH6HnwS)gYa2r?%u%fDOgCKZS6yrEPW39cCB*~jF3};0>ilUn$a>+Fu zpjKAQvTQ>{_UiTe`XXV@01=U8g9~}ZVlWt-oy}&OA|k4K6+l(Dj4@*oN!so9V`EHp zfVK9rh&(*8znz10G#Wjv(!tRYO(v6lU@d4jFS^`i>lsDm&JEpPzP z@Ap5eDysU)_x*z^!G%hP415E!EX%$x6As5ZIId2o(*?TAh69K(<}2`l{CMW*dj5Xz z;GK!cb5BGzRdowE?f6j&d~xtyRMqZ6Ka??MUsXQ_L2!4TH;STLBC`9dA^$P`bwKGY za#63>JMVekYT4d&Iz3zpFdPn7$K&xKP&&g#qjAGpTOH7BHq$tcw^jAj$vst7;2m(T zGC)KWc=S6@6h**481jF2i>!#O8)G&cyw?D`5I{s;sOl5orGq^Mc76gXO{M-`dR5f` O0000i_@%8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10;x$v zK~!jg?V7)96hRnWH2y%rLPZD%O7Lza7Lw>6VPPu@F-q(rh>c(= zXk!s}avUheLW!gh#8{|>or1ASa#3M3pT%8ba^8jPX7-kodz#yw*?sqWXTO=TbfT1%Da0!2<{|psG)dF^3#$uj5l~d4LKq>q=Ne z`c(A@;ETvPVBVGXMtuIZ{A>^eXI%-GN~II3It!qxuJ7(TURuJf!jE%s7={-`hv8XEeZQe4|6mH`nN0}cX2QS$nW-0VL zpy&*y)+53@N94HOE6Mc{dT zxiO})VRt?Vf`5)_HEll%bqr)ptd}{elajqaWUc*@LOijFWgrNG(X;|dO53N97-J$J z2YdpqJJdc5+yGEjBO;+IVG&u5qUh!D@bFieo}PYJn|!w$OP=Rl@I>S)u#|N)3Fxub zP71)()YLxT_Xl0B9;oVh5m{TZ8OQCi%57i(SWRtOXp9-B)(1S#J6R|c-Z<2L2AqNa zU=->Y$eNCUtZ91#ao5_dL(@=4vy=O|;a&%*;%8rBazoa$Qv&18&xr zw={(o78ZOGHk!4zyHRPe0000 Date: Thu, 1 Jul 2021 00:34:46 +0200 Subject: [PATCH 4/7] Fix warnings. --- .../plugins/forensics/miner/CommitStatisticsBuildAction.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction.java b/src/main/java/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction.java index 66eca458..67c2989d 100644 --- a/src/main/java/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction.java +++ b/src/main/java/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction.java @@ -25,7 +25,7 @@ public class CommitStatisticsBuildAction extends InvisibleAction implements LastBuildAction, RunAction2, Serializable { private static final long serialVersionUID = -263122257268060032L; - private Run owner; + private transient Run owner; private final String scmKey; private final CommitStatistics commitStatistics; @@ -42,6 +42,8 @@ public class CommitStatisticsBuildAction extends InvisibleAction implements Last */ public CommitStatisticsBuildAction(final Run owner, final String scmKey, final CommitStatistics commitStatistics) { + super(); + this.owner = owner; this.scmKey = scmKey; this.commitStatistics = commitStatistics; From acc0fb2d662fa5617b854b282a9fd4bc9a9d98ff Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Thu, 1 Jul 2021 00:39:55 +0200 Subject: [PATCH 5/7] Suppress SpotBugs warning. --- .../plugins/forensics/miner/CommitStatisticsBuildAction.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction.java b/src/main/java/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction.java index 67c2989d..bb60b001 100644 --- a/src/main/java/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction.java +++ b/src/main/java/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction.java @@ -5,6 +5,7 @@ import java.util.Collections; import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.model.Action; import hudson.model.InvisibleAction; @@ -25,6 +26,7 @@ public class CommitStatisticsBuildAction extends InvisibleAction implements LastBuildAction, RunAction2, Serializable { private static final long serialVersionUID = -263122257268060032L; + @SuppressFBWarnings(value = "SE", justification = "transient field owner ist restored using a Jenkins callback") private transient Run owner; private final String scmKey; From 570ee9c542e012d4de876fbff22247dcff68a669 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Thu, 1 Jul 2021 00:42:44 +0200 Subject: [PATCH 6/7] Restore old summary. --- .../miner/ForensicsBuildAction/summary.jelly | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/main/resources/io/jenkins/plugins/forensics/miner/ForensicsBuildAction/summary.jelly b/src/main/resources/io/jenkins/plugins/forensics/miner/ForensicsBuildAction/summary.jelly index 5eeb69fe..cf1b724d 100644 --- a/src/main/resources/io/jenkins/plugins/forensics/miner/ForensicsBuildAction/summary.jelly +++ b/src/main/resources/io/jenkins/plugins/forensics/miner/ForensicsBuildAction/summary.jelly @@ -3,29 +3,18 @@ ${%title}: ${it.scmKey} - - +
  • ${%summary(size(it.result))} - (total lines of code: ${it.totalLinesOfCode}, total churn: ${it.totalChurn}) + (total lines of code: ${it.result.totalLinesOfCode}, total churn: ${it.result.totalChurn})
  • - New commits since previous build: ${s.commitCount} - (from ${s.authorCount} authors - in ${s.filesCount} files - with ${s.addedLines} added - and ${s.deletedLines} deleted lines) + New commits: ${s.commitCount} (from ${s.authorCount} authors in ${s.filesCount} files) +
  • +
  • + New lines: ${s.addedLines} added, ${s.deletedLines} deleted
  • - -
  • - New commits since reference build ${it.referenceBuild.displayName}: ${d.commitCount} - (from ${d.authorCount} authors - in ${d.filesCount} files - with ${d.addedLines} added - and ${d.deletedLines} deleted lines) -
  • -
  • Miner runtime: ${it.miningDurationSeconds} seconds
  • From a8de01dd5e36c6ff3e0ccade787200280b905390 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Fri, 2 Jul 2021 16:24:41 +0200 Subject: [PATCH 7/7] Improve layout of summary. --- .../miner/CommitStatisticsBuildAction/summary.jelly | 8 +++++++- .../forensics/miner/ForensicsBuildAction/summary.jelly | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/resources/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction/summary.jelly b/src/main/resources/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction/summary.jelly index 67ee3425..443e0098 100644 --- a/src/main/resources/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction/summary.jelly +++ b/src/main/resources/io/jenkins/plugins/forensics/miner/CommitStatisticsBuildAction/summary.jelly @@ -6,7 +6,13 @@
    • - Commits: ${s.commitCount} - compared to target branch of build + Commits: ${s.commitCount} - + + + compared to target branch of build + + compared to previous build +
    • Changed files: ${s.filesCount} diff --git a/src/main/resources/io/jenkins/plugins/forensics/miner/ForensicsBuildAction/summary.jelly b/src/main/resources/io/jenkins/plugins/forensics/miner/ForensicsBuildAction/summary.jelly index cf1b724d..cf266e05 100644 --- a/src/main/resources/io/jenkins/plugins/forensics/miner/ForensicsBuildAction/summary.jelly +++ b/src/main/resources/io/jenkins/plugins/forensics/miner/ForensicsBuildAction/summary.jelly @@ -13,7 +13,7 @@ New commits: ${s.commitCount} (from ${s.authorCount} authors in ${s.filesCount} files)
    • - New lines: ${s.addedLines} added, ${s.deletedLines} deleted + Changed lines: ${s.addedLines} added, ${s.deletedLines} deleted
    • Miner runtime: ${it.miningDurationSeconds} seconds