diff options
author | Duarte Meneses <duarte.meneses@sonarsource.com> | 2020-05-12 14:14:38 -0500 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2020-06-11 20:04:56 +0000 |
commit | 3352e9f378dfb2929a19d362f4b5ae21bd33f0db (patch) | |
tree | 561d899ce84e2faaffc8bfca6718c1158443c613 | |
parent | 46a49f0b5ef205f5632b44dc07221eed79ec803d (diff) | |
download | sonarqube-3352e9f378dfb2929a19d362f4b5ae21bd33f0db.tar.gz sonarqube-3352e9f378dfb2929a19d362f4b5ae21bd33f0db.zip |
SONAR-13390 SONAR-13391 New Code Reference Branch
50 files changed, 809 insertions, 122 deletions
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolder.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolder.java index da91f4797eb..d5b3b9dbf61 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolder.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolder.java @@ -50,6 +50,13 @@ public interface AnalysisMetadataHolder { long getAnalysisDate(); /** + * @throws IllegalStateException if no fork date has been set + */ + @CheckForNull + Long getForkDate(); + + + /** * Tell whether the analysisDate has been set. */ boolean hasAnalysisDateBeenSet(); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java index b12b79b0139..ef17288e84e 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java @@ -50,6 +50,7 @@ public class AnalysisMetadataHolderImpl implements MutableAnalysisMetadataHolder private final InitializedProperty<Map<String, QualityProfile>> qProfilesPerLanguage = new InitializedProperty<>(); private final InitializedProperty<Map<String, ScannerPlugin>> pluginsByKey = new InitializedProperty<>(); private final InitializedProperty<String> scmRevision = new InitializedProperty<>(); + private final InitializedProperty<Long> forkDate = new InitializedProperty<>(); private final PlatformEditionProvider editionProvider; @@ -117,6 +118,20 @@ public class AnalysisMetadataHolderImpl implements MutableAnalysisMetadataHolder } @Override + public MutableAnalysisMetadataHolder setForkDate(@Nullable Long date) { + checkState(!forkDate.isInitialized(), "Fork date has already been set"); + this.forkDate.setProperty(date); + return this; + } + + @Override + @CheckForNull + public Long getForkDate() { + checkState(forkDate.isInitialized(), "Fork date has not been set"); + return this.forkDate.getProperty(); + } + + @Override public boolean isFirstAnalysis() { return getBaseAnalysis() == null; } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/analysis/MutableAnalysisMetadataHolder.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/analysis/MutableAnalysisMetadataHolder.java index 61a098f9e3b..7ffa27cd47a 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/analysis/MutableAnalysisMetadataHolder.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/analysis/MutableAnalysisMetadataHolder.java @@ -47,6 +47,11 @@ public interface MutableAnalysisMetadataHolder extends AnalysisMetadataHolder { MutableAnalysisMetadataHolder setAnalysisDate(long date); /** + * @throws IllegalStateException if the fork date has already been set + */ + MutableAnalysisMetadataHolder setForkDate(@Nullable Long date); + + /** * @throws IllegalStateException if baseAnalysis has already been set */ MutableAnalysisMetadataHolder setBaseAnalysis(@Nullable Analysis baseAnalysis); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueCounter.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueCounter.java index 91fadde60ca..bae5df00769 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueCounter.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueCounter.java @@ -150,7 +150,7 @@ public class IssueCounter extends IssueVisitor { currentCounters.add(issue); if (analysisMetadataHolder.isPullRequest()) { currentCounters.addOnPeriod(issue); - } else if (periodHolder.hasPeriod()) { + } else if (periodHolder.hasPeriodDate()) { Period period = periodHolder.getPeriod(); if (period.isOnPeriod(issue.creationDate())){ currentCounters.addOnPeriod(issue); @@ -196,7 +196,7 @@ public class IssueCounter extends IssueVisitor { } private void addNewMeasures(Component component) { - if (!periodHolder.hasPeriod() && !analysisMetadataHolder.isPullRequest()) { + if (!periodHolder.hasPeriodDate() && !analysisMetadataHolder.isPullRequest()) { return; } double unresolvedVariations = currentCounters.counterForPeriod().unresolved; diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregator.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregator.java index 1bd97ca7542..722d6342f27 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregator.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregator.java @@ -84,7 +84,7 @@ public class NewEffortAggregator extends IssueVisitor { if (issue.resolution() == null && issue.effortInMinutes() != null) { if (analysisMetadataHolder.isPullRequest()) { counter.add(issue, null); - } else if (periodHolder.hasPeriod()) { + } else if (periodHolder.hasPeriodDate()) { counter.add(issue, periodHolder.getPeriod()); } } @@ -92,7 +92,7 @@ public class NewEffortAggregator extends IssueVisitor { @Override public void afterComponent(Component component) { - if (periodHolder.hasPeriod() || analysisMetadataHolder.isPullRequest()) { + if (periodHolder.hasPeriodDate() || analysisMetadataHolder.isPullRequest()) { computeMeasure(component, newMaintainabilityEffortMetric, counter.maintainabilitySum); computeMeasure(component, newReliabilityEffortMetric, counter.reliabilitySum); computeMeasure(component, newSecurityEffortMetric, counter.securitySum); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/NewCodePeriodResolver.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/NewCodePeriodResolver.java index 3f4c0a6f61b..101c3f020e1 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/NewCodePeriodResolver.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/NewCodePeriodResolver.java @@ -25,11 +25,13 @@ import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Optional; import java.util.function.Supplier; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.MessageException; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.SnapshotDto; @@ -50,31 +52,44 @@ public class NewCodePeriodResolver { private static final Logger LOG = Loggers.get(NewCodePeriodResolver.class); private final DbClient dbClient; + private final AnalysisMetadataHolder analysisMetadataHolder; - public NewCodePeriodResolver(DbClient dbClient) { + public NewCodePeriodResolver(DbClient dbClient, AnalysisMetadataHolder analysisMetadataHolder) { this.dbClient = dbClient; + this.analysisMetadataHolder = analysisMetadataHolder; } - public Period resolve(DbSession dbSession, String branchUuid, NewCodePeriodDto newCodePeriodDto, long referenceDate, String projectVersion) { - return toPeriod(newCodePeriodDto.getType(), newCodePeriodDto.getValue(), dbSession, projectVersion, branchUuid, referenceDate); + @CheckForNull + public Period resolve(DbSession dbSession, String branchUuid, NewCodePeriodDto newCodePeriodDto, String projectVersion) { + return toPeriod(newCodePeriodDto.getType(), newCodePeriodDto.getValue(), dbSession, projectVersion, branchUuid); } - private Period toPeriod(NewCodePeriodType type, @Nullable String value, DbSession dbSession, String projectVersion, String rootUuid, long referenceDate) { + @CheckForNull + private Period toPeriod(NewCodePeriodType type, @Nullable String value, DbSession dbSession, String projectVersion, String rootUuid) { switch (type) { case NUMBER_OF_DAYS: checkNotNullValue(value, type); Integer days = NewCodePeriodParser.parseDays(value); - return resolveByDays(dbSession, rootUuid, days, value, referenceDate); + return resolveByDays(dbSession, rootUuid, days, value, analysisMetadataHolder.getAnalysisDate()); case PREVIOUS_VERSION: return resolveByPreviousVersion(dbSession, rootUuid, projectVersion); case SPECIFIC_ANALYSIS: checkNotNullValue(value, type); return resolveBySpecificAnalysis(dbSession, rootUuid, value); + case REFERENCE_BRANCH: + checkNotNullValue(value, type); + return resolveByReferenceBranch(value); default: throw new IllegalStateException("Unexpected type: " + type); } } + private Period resolveByReferenceBranch(String value) { + Long forkDate = analysisMetadataHolder.getForkDate(); + // forkDate can be null if the scanner failed to find it + return newPeriod(NewCodePeriodType.REFERENCE_BRANCH, value, forkDate); + } + private Period resolveBySpecificAnalysis(DbSession dbSession, String rootUuid, String value) { SnapshotDto baseline = dbClient.snapshotDao().selectByUuid(dbSession, value) .filter(t -> t.getComponentUuid().equals(rootUuid)) @@ -135,7 +150,7 @@ public class NewCodePeriodResolver { return period.get(); } - private static Period newPeriod(NewCodePeriodType type, @Nullable String value, long date) { + private static Period newPeriod(NewCodePeriodType type, @Nullable String value, @Nullable Long date) { return new Period(type.name(), value, date); } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/Period.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/Period.java index 474adf14536..4ef0713a81c 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/Period.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/Period.java @@ -33,14 +33,13 @@ import static org.sonar.api.utils.DateUtils.truncateToSeconds; @Immutable public class Period { private final String mode; - @CheckForNull private final String modeParameter; - private final long snapshotDate; + private final Long date; - public Period(String mode, @Nullable String modeParameter, long snapshotDate) { + public Period(String mode, @Nullable String modeParameter, @Nullable Long date) { this.mode = requireNonNull(mode); this.modeParameter = modeParameter; - this.snapshotDate = snapshotDate; + this.date = date; } public String getMode() { @@ -52,8 +51,9 @@ public class Period { return modeParameter; } - public long getSnapshotDate() { - return snapshotDate; + @CheckForNull + public Long getDate() { + return date; } @Override @@ -65,18 +65,19 @@ public class Period { return false; } Period period = (Period) o; - return snapshotDate == period.snapshotDate - && mode.equals(period.mode) - && Objects.equals(modeParameter, period.modeParameter); + return Objects.equals(date, period.date) && Objects.equals(mode, period.mode) && Objects.equals(modeParameter, period.modeParameter); } public boolean isOnPeriod(Date date) { - return date.getTime() > truncateToSeconds(snapshotDate); + if (this.date == null) { + return false; + } + return date.getTime() > truncateToSeconds(this.date); } @Override public int hashCode() { - return hash(mode, modeParameter, snapshotDate); + return hash(mode, modeParameter, date); } @Override @@ -84,7 +85,7 @@ public class Period { return toStringHelper(this) .add("mode", mode) .add("modeParameter", modeParameter) - .add("snapshotDate", snapshotDate) + .add("date", date) .toString(); } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolder.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolder.java index 9a5a4c49509..da825b3e580 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolder.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolder.java @@ -35,6 +35,14 @@ public interface PeriodHolder { */ boolean hasPeriod(); + + /** + * Finds out whether the holder contains a Period with a date + * + * @throws IllegalStateException if the periods haven't been initialized + */ + boolean hasPeriodDate(); + /** * Retrieve the period from the Holder. * diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderImpl.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderImpl.java index a88294aebdf..9330d635d16 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderImpl.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderImpl.java @@ -48,6 +48,12 @@ public class PeriodHolderImpl implements PeriodHolder { } @Override + public boolean hasPeriodDate() { + checkHolderIsInitialized(); + return period != null && period.getDate() != null; + } + + @Override public Period getPeriod() { checkHolderIsInitialized(); checkState(period != null, "There is no period. Use hasPeriod() before calling this method"); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitor.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitor.java index c9d70906bbf..601742564ee 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitor.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitor.java @@ -104,7 +104,7 @@ public class NewReliabilityAndSecurityRatingMeasuresVisitor extends PathAwareVis } private void computeAndSaveMeasures(Component component, Path<Counter> path) { - if (!periodHolder.hasPeriod() && !analysisMetadataHolder.isPullRequest()) { + if (!periodHolder.hasPeriodDate() && !analysisMetadataHolder.isPullRequest()) { return; } initRatingsToA(path); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewSecurityReviewMeasuresVisitor.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewSecurityReviewMeasuresVisitor.java index cbd94f0f20b..b3e1c92de6d 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewSecurityReviewMeasuresVisitor.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewSecurityReviewMeasuresVisitor.java @@ -67,7 +67,7 @@ public class NewSecurityReviewMeasuresVisitor extends PathAwareVisitorAdapter<Se @Override public void visitProject(Component project, Path<SecurityReviewCounter> path) { computeMeasure(project, path); - if (!periodHolder.hasPeriod() && !analysisMetadataHolder.isPullRequest()) { + if (!periodHolder.hasPeriodDate() && !analysisMetadataHolder.isPullRequest()) { return; } // The following measures are only computed on projects level as they are required to compute the others measures on applications @@ -86,7 +86,7 @@ public class NewSecurityReviewMeasuresVisitor extends PathAwareVisitorAdapter<Se } private void computeMeasure(Component component, Path<SecurityReviewCounter> path) { - if (!periodHolder.hasPeriod() && !analysisMetadataHolder.isPullRequest()) { + if (!periodHolder.hasPeriodDate() && !analysisMetadataHolder.isPullRequest()) { return; } componentIssuesRepository.getIssues(component) diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/source/NewLinesRepository.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/source/NewLinesRepository.java index 6f6ad4fac23..9ec855d4621 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/source/NewLinesRepository.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/source/NewLinesRepository.java @@ -48,7 +48,7 @@ public class NewLinesRepository { } public boolean newLinesAvailable() { - return analysisMetadataHolder.isPullRequest() || periodHolder.hasPeriod(); + return analysisMetadataHolder.isPullRequest() || periodHolder.hasPeriodDate(); } public Optional<Set<Integer>> getNewLines(Component file) { @@ -66,7 +66,7 @@ public class NewLinesRepository { * if a line is new or not. */ private Optional<Set<Integer>> computeNewLinesFromScm(Component component) { - if (!periodHolder.hasPeriod() && !analysisMetadataHolder.isPullRequest()) { + if (!periodHolder.hasPeriodDate() && !analysisMetadataHolder.isPullRequest()) { return Optional.empty(); } @@ -80,7 +80,7 @@ public class NewLinesRepository { Set<Integer> lines = new HashSet<>(); // in PRs, we consider changes introduced in this analysis as new, hence subtracting 1. - long referenceDate = analysisMetadataHolder.isPullRequest() ? analysisMetadataHolder.getAnalysisDate() - 1 : periodHolder.getPeriod().getSnapshotDate(); + long referenceDate = analysisMetadataHolder.isPullRequest() ? analysisMetadataHolder.getAnalysisDate() - 1 : periodHolder.getPeriod().getDate(); for (int i=0; i<allChangesets.length; i++) { if (isLineInPeriod(allChangesets[i].getDate(), referenceDate)) { lines.add(i+1); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java index 8e8d003b2d2..bf2e27c5e92 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java @@ -34,6 +34,7 @@ import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.newcodeperiod.NewCodePeriodDao; import org.sonar.db.newcodeperiod.NewCodePeriodDto; +import org.sonar.db.newcodeperiod.NewCodePeriodType; /** * Populates the {@link PeriodHolder} @@ -53,7 +54,7 @@ public class LoadPeriodsStep implements ComputationStep { private final NewCodePeriodResolver resolver; public LoadPeriodsStep(AnalysisMetadataHolder analysisMetadataHolder, NewCodePeriodDao newCodePeriodDao, TreeRootHolder treeRootHolder, - PeriodHolderImpl periodsHolder, DbClient dbClient, NewCodePeriodResolver resolver) { + PeriodHolderImpl periodsHolder, DbClient dbClient, NewCodePeriodResolver resolver) { this.analysisMetadataHolder = analysisMetadataHolder; this.newCodePeriodDao = newCodePeriodDao; this.treeRootHolder = treeRootHolder; @@ -69,7 +70,7 @@ public class LoadPeriodsStep implements ComputationStep { @Override public void execute(ComputationStep.Context context) { - if (analysisMetadataHolder.isFirstAnalysis() || !analysisMetadataHolder.isBranch()) { + if (!analysisMetadataHolder.isBranch()) { periodsHolder.setPeriod(null); return; } @@ -85,9 +86,14 @@ public class LoadPeriodsStep implements ComputationStep { () -> getGlobalSetting(dbSession) )); - long analysisDate = analysisMetadataHolder.getAnalysisDate(); - Period period = dto.map(d -> resolver.resolve(dbSession, branchUuid, d, analysisDate, projectVersion)) - .orElseGet(() -> resolver.resolve(dbSession, branchUuid, NewCodePeriodDto.defaultInstance(), analysisDate, projectVersion)); + NewCodePeriodDto newCodePeriod = dto.orElse(NewCodePeriodDto.defaultInstance()); + + if (analysisMetadataHolder.isFirstAnalysis() && newCodePeriod.getType() != NewCodePeriodType.REFERENCE_BRANCH) { + periodsHolder.setPeriod(null); + return; + } + + Period period = resolver.resolve(dbSession, branchUuid, newCodePeriod, projectVersion); periodsHolder.setPeriod(period); } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java index 86d5c3d89db..9fe44ff2796 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java @@ -92,6 +92,7 @@ public class LoadReportAnalysisMetadataHolderStep implements ComputationStep { } private void loadMetadata(ScannerReport.Metadata reportMetadata) { + analysisMetadata.setForkDate(reportMetadata.getForkDate() > 0 ? reportMetadata.getForkDate() : null); analysisMetadata.setAnalysisDate(reportMetadata.getAnalysisDate()); analysisMetadata.setRootComponentRef(reportMetadata.getRootComponentRef()); analysisMetadata.setCrossProjectDuplicationEnabled(reportMetadata.getCrossProjectDuplicationActivated()); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisStep.java index b5af1a33a9b..8f730c72cba 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisStep.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisStep.java @@ -97,7 +97,7 @@ public class PersistAnalysisStep implements ComputationStep { Period period = periodHolder.getPeriod(); snapshotDto.setPeriodMode(period.getMode()); snapshotDto.setPeriodParam(period.getModeParameter()); - snapshotDto.setPeriodDate(period.getSnapshotDate()); + snapshotDto.setPeriodDate(period.getDate()); } private SnapshotDto createAnalysis(String snapshotUuid, Component component) { diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderImplTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderImplTest.java index b44c61bdc63..8f17bc111fb 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderImplTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderImplTest.java @@ -139,6 +139,33 @@ public class AnalysisMetadataHolderImplTest { } @Test + public void getForkDate_returns_date_with_same_time_as_the_one_set_with_setForkDate() { + + underTest.setForkDate(SOME_DATE); + + assertThat(underTest.getForkDate()).isEqualTo(SOME_DATE); + } + + @Test + public void getForkDate_throws_ISE_when_holder_is_not_initialized() { + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Fork date has not been set"); + + new AnalysisMetadataHolderImpl(editionProvider).getForkDate(); + } + + @Test + public void setForkDate_throws_ISE_when_called_twice() { + AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl(editionProvider); + underTest.setForkDate(SOME_DATE); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Fork date has already been set"); + + underTest.setForkDate(SOME_DATE); + } + + @Test public void hasAnalysisDateBeenSet_returns_false_when_holder_is_not_initialized() { assertThat(new AnalysisMetadataHolderImpl(editionProvider).hasAnalysisDateBeenSet()).isFalse(); } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCounterTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCounterTest.java index 9e8f6f17d0e..8943b9682fe 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCounterTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCounterTest.java @@ -285,15 +285,15 @@ public class IssueCounterTest { underTest.beforeComponent(FILE1); // created before -> existing issues (so ignored) - underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER, period.getSnapshotDate() - 1000000L).setType(RuleType.CODE_SMELL)); + underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER, period.getDate() - 1000000L).setType(RuleType.CODE_SMELL)); // created during the first analysis starting the period -> existing issues (so ignored) - underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER, period.getSnapshotDate()).setType(RuleType.BUG)); + underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER, period.getDate()).setType(RuleType.BUG)); // created after -> 4 new issues but 1 is closed - underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, CRITICAL, period.getSnapshotDate() + 100000L).setType(RuleType.CODE_SMELL)); - underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, CRITICAL, period.getSnapshotDate() + 100000L).setType(RuleType.BUG)); - underTest.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR, period.getSnapshotDate() + 200000L).setType(RuleType.BUG)); - underTest.onIssue(FILE1, createSecurityHotspot(period.getSnapshotDate() + 100000L)); - underTest.onIssue(FILE1, createSecurityHotspot(period.getSnapshotDate() + 100000L).setResolution(RESOLUTION_WONT_FIX).setStatus(STATUS_CLOSED)); + underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, CRITICAL, period.getDate() + 100000L).setType(RuleType.CODE_SMELL)); + underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, CRITICAL, period.getDate() + 100000L).setType(RuleType.BUG)); + underTest.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR, period.getDate() + 200000L).setType(RuleType.BUG)); + underTest.onIssue(FILE1, createSecurityHotspot(period.getDate() + 100000L)); + underTest.onIssue(FILE1, createSecurityHotspot(period.getDate() + 100000L).setResolution(RESOLUTION_WONT_FIX).setStatus(STATUS_CLOSED)); underTest.afterComponent(FILE1); underTest.beforeComponent(FILE2); @@ -368,15 +368,15 @@ public class IssueCounterTest { underTest.beforeComponent(FILE1); // created before -> existing issues (so ignored) - underTest.onIssue(FILE1, createSecurityHotspot(period.getSnapshotDate() - 1000000L)); + underTest.onIssue(FILE1, createSecurityHotspot(period.getDate() - 1000000L)); // created during the first analysis starting the period -> existing issues (so ignored) - underTest.onIssue(FILE1, createSecurityHotspot(period.getSnapshotDate())); + underTest.onIssue(FILE1, createSecurityHotspot(period.getDate())); // created after, but closed - underTest.onIssue(FILE1, createSecurityHotspot(period.getSnapshotDate() + 100000L).setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_WONT_FIX)); + underTest.onIssue(FILE1, createSecurityHotspot(period.getDate() + 100000L).setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_WONT_FIX)); for (String severity : Arrays.asList(CRITICAL, BLOCKER, MAJOR)) { - DefaultIssue issue = createSecurityHotspot(period.getSnapshotDate() + 100000L); + DefaultIssue issue = createSecurityHotspot(period.getDate() + 100000L); issue.setSeverity(severity); underTest.onIssue(FILE1, issue); } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregatorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregatorTest.java index 305e85d6e3f..e0478a02e5c 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregatorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregatorTest.java @@ -55,9 +55,9 @@ public class NewEffortAggregatorTest { private static final Period PERIOD = new Period(NewCodePeriodType.PREVIOUS_VERSION.name(), null, 1_500_000_000L); private static final long[] OLD_ISSUES_DATES = new long[]{ - PERIOD.getSnapshotDate(), - PERIOD.getSnapshotDate() - 1, - PERIOD.getSnapshotDate() - 1_200_000L, + PERIOD.getDate(), + PERIOD.getDate() - 1, + PERIOD.getDate() - 1_200_000L, }; private static final Component FILE = ReportComponent.builder(Component.Type.FILE, 1).setUuid("FILE").build(); @@ -296,7 +296,7 @@ public class NewEffortAggregatorTest { return newCodeSmellIssueWithoutEffort() .setEffort(Duration.create(effort)) .setType(RuleType.CODE_SMELL) - .setCreationDate(new Date(PERIOD.getSnapshotDate() + 10_000L)); + .setCreationDate(new Date(PERIOD.getDate() + 10_000L)); } private DefaultIssue oldCodeSmellIssue(long effort) { @@ -310,7 +310,7 @@ public class NewEffortAggregatorTest { return newCodeSmellIssueWithoutEffort() .setEffort(Duration.create(effort)) .setType(RuleType.BUG) - .setCreationDate(new Date(PERIOD.getSnapshotDate() + 10_000L)); + .setCreationDate(new Date(PERIOD.getDate() + 10_000L)); } private DefaultIssue oldBugIssue(long effort) { @@ -324,7 +324,7 @@ public class NewEffortAggregatorTest { return newCodeSmellIssueWithoutEffort() .setEffort(Duration.create(effort)) .setType(RuleType.VULNERABILITY) - .setCreationDate(new Date(PERIOD.getSnapshotDate() + 10_000L)); + .setCreationDate(new Date(PERIOD.getDate() + 10_000L)); } private DefaultIssue oldVulnerabilityIssue(long effort) { @@ -337,7 +337,7 @@ public class NewEffortAggregatorTest { private static DefaultIssue newCodeSmellIssueWithoutEffort() { return new DefaultIssue() .setType(CODE_SMELL) - .setCreationDate(new Date(PERIOD.getSnapshotDate() + 10_000L)); + .setCreationDate(new Date(PERIOD.getDate() + 10_000L)); } private static DefaultIssue newBugIssueWithoutEffort() { diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderImplTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderImplTest.java index 59a9dbe754e..0e91051b4f5 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderImplTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderImplTest.java @@ -64,9 +64,17 @@ public class PeriodHolderImplTest { } @Test + public void hasPeriodDate_returns_false_if_date_is_null() { + underTest.setPeriod(createPeriodWithoutDate()); + assertThat(underTest.hasPeriod()).isTrue(); + assertThat(underTest.hasPeriodDate()).isFalse(); + } + + @Test public void hasPeriod_returns_true_only_if_period_exists_in_holder() { underTest.setPeriod(createPeriod()); assertThat(underTest.hasPeriod()).isTrue(); + assertThat(underTest.hasPeriodDate()).isTrue(); } @Test @@ -80,4 +88,8 @@ public class PeriodHolderImplTest { private static Period createPeriod() { return new Period(1 + "mode", null, 1000L); } + + private static Period createPeriodWithoutDate() { + return new Period(1 + "mode", null, null); + } } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderRule.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderRule.java index e8d70adb878..b68559c35de 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderRule.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderRule.java @@ -57,6 +57,11 @@ public class PeriodHolderRule implements TestRule, PeriodHolder { } @Override + public boolean hasPeriodDate() { + return delegate.hasPeriodDate(); + } + + @Override public Period getPeriod() { return delegate.getPeriod(); } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/PeriodTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/PeriodTest.java index b47743e7eb1..feb1b049231 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/PeriodTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/PeriodTest.java @@ -40,13 +40,13 @@ public class PeriodTest { assertThat(period.getMode()).isEqualTo(NewCodePeriodType.PREVIOUS_VERSION.name()); assertThat(period.getModeParameter()).isEqualTo(SOME_MODE_PARAM); - assertThat(period.getSnapshotDate()).isEqualTo(SOME_SNAPSHOT_DATE); + assertThat(period.getDate()).isEqualTo(SOME_SNAPSHOT_DATE); } @Test public void verify_to_string() { assertThat(new Period(NewCodePeriodType.PREVIOUS_VERSION.name(), "2.3", 1420034400000L).toString()) - .isEqualTo("Period{mode=PREVIOUS_VERSION, modeParameter=2.3, snapshotDate=1420034400000}"); + .isEqualTo("Period{mode=PREVIOUS_VERSION, modeParameter=2.3, date=1420034400000}"); } @Test diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepTest.java index b58ffb3c481..56ec2a33de2 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepTest.java @@ -87,7 +87,7 @@ public class LoadPeriodsStepTest extends BaseStepTest { private PeriodHolderImpl periodsHolder = new PeriodHolderImpl(); private System2 system2Mock = mock(System2.class); private NewCodePeriodDao dao = new NewCodePeriodDao(system2Mock, new SequenceUuidFactory()); - private NewCodePeriodResolver newCodePeriodResolver = new NewCodePeriodResolver(dbTester.getDbClient()); + private NewCodePeriodResolver newCodePeriodResolver = new NewCodePeriodResolver(dbTester.getDbClient(), analysisMetadataHolder); private ZonedDateTime analysisDate = ZonedDateTime.of(2019, 3, 20, 5, 30, 40, 0, ZoneId.systemDefault()); private LoadPeriodsStep underTest = new LoadPeriodsStep(analysisMetadataHolder, dao, treeRootHolder, periodsHolder, dbTester.getDbClient(), newCodePeriodResolver); @@ -112,20 +112,23 @@ public class LoadPeriodsStepTest extends BaseStepTest { @Test public void no_period_on_first_analysis() { + setupRoot(project); + when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(true); underTest.execute(new TestComputationStepContext()); verify(analysisMetadataHolder).isFirstAnalysis(); + verify(analysisMetadataHolder).isBranch(); + verify(analysisMetadataHolder).getProject(); assertThat(periodsHolder.hasPeriod()).isFalse(); verifyNoMoreInteractions(analysisMetadataHolder); } @Test - public void no_period_if_not_LLB() { + public void no_period_date_if_not_branch() { when(analysisMetadataHolder.isBranch()).thenReturn(false); underTest.execute(new TestComputationStepContext()); - verify(analysisMetadataHolder).isFirstAnalysis(); verify(analysisMetadataHolder).isBranch(); assertThat(periodsHolder.hasPeriod()).isFalse(); verifyNoMoreInteractions(analysisMetadataHolder); @@ -170,6 +173,30 @@ public class LoadPeriodsStepTest extends BaseStepTest { testNumberOfDays(branch); } + @Test + public void load_reference_branch() { + ComponentDto branch = dbTester.components().insertProjectBranch(project); + setupRoot(branch); + + setProjectPeriod(project.uuid(), NewCodePeriodType.REFERENCE_BRANCH, "master"); + when(analysisMetadataHolder.getForkDate()).thenReturn(123456789L); + + underTest.execute(new TestComputationStepContext()); + assertPeriod(NewCodePeriodType.REFERENCE_BRANCH, "master", 123456789L); + } + + @Test + public void load_reference_branch_without_fork_date_in_report() { + ComponentDto branch = dbTester.components().insertProjectBranch(project); + setupRoot(branch); + + setProjectPeriod(project.uuid(), NewCodePeriodType.REFERENCE_BRANCH, "master"); + when(analysisMetadataHolder.getForkDate()).thenReturn(null); + + underTest.execute(new TestComputationStepContext()); + assertPeriod(NewCodePeriodType.REFERENCE_BRANCH, "master", null); + } + private void testNumberOfDays(ComponentDto projectOrBranch) { setupRoot(projectOrBranch); @@ -455,12 +482,12 @@ public class LoadPeriodsStepTest extends BaseStepTest { dbTester.newCodePeriods().insert(type, value); } - private void assertPeriod(NewCodePeriodType type, @Nullable String value, long snapshotDate) { + private void assertPeriod(NewCodePeriodType type, @Nullable String value, @Nullable Long date) { Period period = periodsHolder.getPeriod(); assertThat(period).isNotNull(); assertThat(period.getMode()).isEqualTo(type.name()); assertThat(period.getModeParameter()).isEqualTo(value); - assertThat(period.getSnapshotDate()).isEqualTo(snapshotDate); + assertThat(period.getDate()).isEqualTo(date); } private void verifyDebugLogs(String log, String... otherLogs) { diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepTest.java index d70e526d92a..17d681d58e9 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepTest.java @@ -116,6 +116,18 @@ public class LoadReportAnalysisMetadataHolderStepTest { } @Test + public void set_fork_date() { + reportReader.setMetadata( + newBatchReportBuilder() + .setForkDate(ANALYSIS_DATE) + .build()); + + underTest.execute(new TestComputationStepContext()); + + assertThat(analysisMetadataHolder.getForkDate()).isEqualTo(ANALYSIS_DATE); + } + + @Test public void set_project_from_dto() { reportReader.setMetadata( newBatchReportBuilder() diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java index bb15edf1fc0..24fe8bd4ca6 100644 --- a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java +++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java @@ -39,29 +39,18 @@ import static org.apache.commons.lang.StringUtils.defaultIfBlank; public class AnalysisMetadataHolderRule extends ExternalResource implements MutableAnalysisMetadataHolder { private final InitializedProperty<Boolean> organizationsEnabled = new InitializedProperty<>(); - private final InitializedProperty<Organization> organization = new InitializedProperty<>(); - private final InitializedProperty<String> uuid = new InitializedProperty<>(); - private final InitializedProperty<Long> analysisDate = new InitializedProperty<>(); - + private final InitializedProperty<Long> forkDate = new InitializedProperty<>(); private final InitializedProperty<Analysis> baseAnalysis = new InitializedProperty<>(); - private final InitializedProperty<Boolean> crossProjectDuplicationEnabled = new InitializedProperty<>(); - private final InitializedProperty<Branch> branch = new InitializedProperty<>(); - private final InitializedProperty<String> pullRequestId = new InitializedProperty<>(); - private final InitializedProperty<Project> project = new InitializedProperty<>(); - private final InitializedProperty<Integer> rootComponentRef = new InitializedProperty<>(); - private final InitializedProperty<Map<String, QualityProfile>> qProfilesPerLanguage = new InitializedProperty<>(); - private final InitializedProperty<Map<String, ScannerPlugin>> pluginsByKey = new InitializedProperty<>(); - private final InitializedProperty<String> scmRevision = new InitializedProperty<>(); @Override @@ -121,12 +110,23 @@ public class AnalysisMetadataHolderRule extends ExternalResource implements Muta return this; } + @Override public MutableAnalysisMetadataHolder setForkDate(@Nullable Long date) { + forkDate.setProperty(date); + return this; + } + @Override public long getAnalysisDate() { checkState(analysisDate.isInitialized(), "Analysis date has not been set"); return this.analysisDate.getProperty(); } + @CheckForNull + @Override + public Long getForkDate() { + return forkDate.getProperty(); + } + @Override public boolean hasAnalysisDateBeenSet() { return analysisDate.isInitialized(); diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/analysis/MutableAnalysisMetadataHolderRule.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/analysis/MutableAnalysisMetadataHolderRule.java index 05ccaba95d9..e8f09ed99d9 100644 --- a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/analysis/MutableAnalysisMetadataHolderRule.java +++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/analysis/MutableAnalysisMetadataHolderRule.java @@ -77,11 +77,21 @@ public class MutableAnalysisMetadataHolderRule extends ExternalResource implemen return this; } + @Override public MutableAnalysisMetadataHolder setForkDate(@Nullable Long date) { + return delegate.setForkDate(date); + } + @Override public long getAnalysisDate() { return delegate.getAnalysisDate(); } + @CheckForNull + @Override + public Long getForkDate() { + return delegate.getForkDate(); + } + @Override public boolean hasAnalysisDateBeenSet() { return delegate.hasAnalysisDateBeenSet(); @@ -181,8 +191,6 @@ public class MutableAnalysisMetadataHolderRule extends ExternalResource implemen return delegate.getScannerPluginsByKey(); } - - @Override public MutableAnalysisMetadataHolder setScmRevision(String scmRevisionId) { delegate.setScmRevision(scmRevisionId); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodType.java b/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodType.java index 232f1f36d91..d74a4cdece3 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodType.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodType.java @@ -22,5 +22,6 @@ package org.sonar.db.newcodeperiod; public enum NewCodePeriodType { PREVIOUS_VERSION, NUMBER_OF_DAYS, - SPECIFIC_ANALYSIS + SPECIFIC_ANALYSIS, + REFERENCE_BRANCH } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/SnapshotDtoToWsPeriod.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/SnapshotDtoToWsPeriod.java index 541a0302568..4f0214825a2 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/SnapshotDtoToWsPeriod.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/SnapshotDtoToWsPeriod.java @@ -32,15 +32,11 @@ class SnapshotDtoToWsPeriod { } static Optional<Measures.Period> snapshotToWsPeriods(@Nullable SnapshotDto snapshot) { - if (snapshot == null) { + if (snapshot == null || snapshot.getPeriodMode() == null) { return Optional.empty(); } - if (snapshot.getPeriodDate() != null) { - return Optional.of(snapshotDtoToWsPeriod(snapshot)); - } - - return Optional.empty(); + return Optional.of(snapshotDtoToWsPeriod(snapshot)); } private static Measures.Period snapshotDtoToWsPeriod(SnapshotDto snapshot) { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/ListAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/ListAction.java index 161afe2d542..13326b68047 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/ListAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/ListAction.java @@ -157,6 +157,8 @@ public class ListAction implements NewCodePeriodsWsAction { return NewCodePeriods.NewCodePeriodType.PREVIOUS_VERSION; case SPECIFIC_ANALYSIS: return NewCodePeriods.NewCodePeriodType.SPECIFIC_ANALYSIS; + case REFERENCE_BRANCH: + return NewCodePeriods.NewCodePeriodType.REFERENCE_BRANCH; default: throw new IllegalStateException("Unexpected type: " + type); } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/SetAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/SetAction.java index b79f029d3a0..c82efac609b 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/SetAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/SetAction.java @@ -47,6 +47,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static java.lang.String.format; import static org.sonar.db.newcodeperiod.NewCodePeriodType.NUMBER_OF_DAYS; import static org.sonar.db.newcodeperiod.NewCodePeriodType.PREVIOUS_VERSION; +import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH; import static org.sonar.db.newcodeperiod.NewCodePeriodType.SPECIFIC_ANALYSIS; public class SetAction implements NewCodePeriodsWsAction { @@ -60,8 +61,8 @@ public class SetAction implements NewCodePeriodsWsAction { private static final String END_ITEM_LIST = "</li>"; private static final Set<NewCodePeriodType> OVERALL_TYPES = EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS); - private static final Set<NewCodePeriodType> PROJECT_TYPES = EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS); - private static final Set<NewCodePeriodType> BRANCH_TYPES = EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS, SPECIFIC_ANALYSIS); + private static final Set<NewCodePeriodType> PROJECT_TYPES = EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS, REFERENCE_BRANCH); + private static final Set<NewCodePeriodType> BRANCH_TYPES = EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS, SPECIFIC_ANALYSIS, REFERENCE_BRANCH); private final DbClient dbClient; private final UserSession userSession; @@ -105,7 +106,8 @@ public class SetAction implements NewCodePeriodsWsAction { BEGIN_LIST + BEGIN_ITEM_LIST + SPECIFIC_ANALYSIS.name() + " - can be set at branch level only" + END_ITEM_LIST + BEGIN_ITEM_LIST + PREVIOUS_VERSION.name() + " - can be set at any level (global, project, branch)" + END_ITEM_LIST + - BEGIN_ITEM_LIST + NUMBER_OF_DAYS.name() + " - can be set can be set at any level (global, project, branch)" + END_ITEM_LIST + + BEGIN_ITEM_LIST + NUMBER_OF_DAYS.name() + " - can be set at any level (global, project, branch)" + END_ITEM_LIST + + BEGIN_ITEM_LIST + REFERENCE_BRANCH.name() + " - can only be set for projects and branches" + END_ITEM_LIST + END_LIST ); action.createParam(PARAM_VALUE) @@ -115,6 +117,7 @@ public class SetAction implements NewCodePeriodsWsAction { BEGIN_ITEM_LIST + "the uuid of an analysis, when type is " + SPECIFIC_ANALYSIS.name() + END_ITEM_LIST + BEGIN_ITEM_LIST + "no value, when type is " + PREVIOUS_VERSION.name() + END_ITEM_LIST + BEGIN_ITEM_LIST + "a number, when type is " + NUMBER_OF_DAYS.name() + END_ITEM_LIST + + BEGIN_ITEM_LIST + "a string, when type is " + REFERENCE_BRANCH.name() + END_ITEM_LIST + END_LIST ); } @@ -182,6 +185,10 @@ public class SetAction implements NewCodePeriodsWsAction { SnapshotDto analysis = getAnalysis(dbSession, value, project, branch); dto.setValue(analysis.getUuid()); break; + case REFERENCE_BRANCH: + requireValue(type, value); + dto.setValue(value); + break; default: throw new IllegalStateException("Unexpected type: " + type); } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/ShowAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/ShowAction.java index 43163e09d5d..ca79dfe3966 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/ShowAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/ShowAction.java @@ -38,6 +38,8 @@ import org.sonar.server.user.UserSession; import org.sonarqube.ws.NewCodePeriods; import static java.lang.String.format; +import static org.sonar.db.permission.OrganizationPermission.SCAN; +import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.NewCodePeriods.ShowWSResponse; @@ -60,11 +62,12 @@ public class ShowAction implements NewCodePeriodsWsAction { @Override public void define(WebService.NewController context) { WebService.NewAction action = context.createAction("show") - .setDescription("Shows a setting for the New Code Period.<br>" + - "Requires one of the following permissions: " + + .setDescription("Shows a setting for the New Code Period.<br> " + + "If the component requested doesn't exist or if no new code period is set for it, a value is inherited from the project or from the global setting." + + "Requires one of the following permissions if a component is specified: " + "<ul>" + - "<li>'Administer System'</li>" + "<li>'Administer' rights on the specified component</li>" + + "<li>'Execute analysis' rights on the specified component</li>" + "</ul>") .setSince("8.0") .setResponseExample(getClass().getResource("show-example.json")) @@ -88,16 +91,25 @@ public class ShowAction implements NewCodePeriodsWsAction { try (DbSession dbSession = dbClient.openSession(false)) { ProjectDto project = null; BranchDto branch = null; + boolean inherited = false; if (projectKey != null) { - project = getProject(dbSession, projectKey); - userSession.checkProjectPermission(UserRole.ADMIN, project); - if (branchKey != null) { - branch = getBranch(dbSession, project, branchKey); + try { + project = getProject(dbSession, projectKey); + checkPermission(project); + if (branchKey != null) { + try { + branch = getBranch(dbSession, project, branchKey); + } catch (NotFoundException e) { + inherited = true; + } + } + } catch (NotFoundException e) { + inherited = true; } } - ShowWSResponse.Builder builder = get(dbSession, project, branch, false); + ShowWSResponse.Builder builder = get(dbSession, project, branch, inherited); if (project != null) { builder.setProjectKey(project.getKey()); @@ -109,6 +121,15 @@ public class ShowAction implements NewCodePeriodsWsAction { } } + private void checkPermission(ProjectDto project) { + if (userSession.hasProjectPermission(UserRole.SCAN, project) || + userSession.hasProjectPermission(UserRole.ADMIN, project) || + userSession.hasPermission(SCAN, project.getOrganizationUuid())) { + return; + } + throw insufficientPrivilegesException(); + } + private ShowWSResponse.Builder get(DbSession dbSession, @Nullable ProjectDto project, @Nullable BranchDto branch, boolean inherited) { if (project == null) { Optional<NewCodePeriodDto> dto = newCodePeriodDao.selectGlobal(dbSession); @@ -151,6 +172,8 @@ public class ShowAction implements NewCodePeriodsWsAction { return NewCodePeriods.NewCodePeriodType.PREVIOUS_VERSION; case SPECIFIC_ANALYSIS: return NewCodePeriods.NewCodePeriodType.SPECIFIC_ANALYSIS; + case REFERENCE_BRANCH: + return NewCodePeriods.NewCodePeriodType.REFERENCE_BRANCH; default: throw new IllegalStateException("Unexpected type: " + type); } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatter.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatter.java index 8928346d324..391c97d91c3 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatter.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatter.java @@ -49,8 +49,7 @@ public class QualityGateDetailsFormatter { return newResponseWithoutQualityGateDetails(); } - JsonParser parser = new JsonParser(); - JsonObject json = parser.parse(optionalMeasureData.get()).getAsJsonObject(); + JsonObject json = JsonParser.parseString(optionalMeasureData.get()).getAsJsonObject(); ProjectStatusResponse.Status qualityGateStatus = measureLevelToQualityGateStatus(json.get("level").getAsString()); projectStatusBuilder.setStatus(qualityGateStatus); @@ -80,8 +79,8 @@ public class QualityGateDetailsFormatter { periodBuilder.clear(); SnapshotDto snapshot = this.optionalSnapshot.get(); - String periodMode = snapshot.getPeriodMode(); - if (isNullOrEmpty(periodMode)) { + + if (isNullOrEmpty(snapshot.getPeriodMode())) { return; } periodBuilder.setIndex(1); diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/newcodeperiod/ws/SetActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/newcodeperiod/ws/SetActionTest.java index 2ebdbdbf1a5..122414be00f 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/newcodeperiod/ws/SetActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/newcodeperiod/ws/SetActionTest.java @@ -126,7 +126,7 @@ public class SetActionTest { logInAsProjectAdministrator(project); expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("Invalid type 'SPECIFIC_ANALYSIS'. Projects can only be set with types: [PREVIOUS_VERSION, NUMBER_OF_DAYS]"); + expectedException.expectMessage("Invalid type 'SPECIFIC_ANALYSIS'. Projects can only be set with types: [PREVIOUS_VERSION, NUMBER_OF_DAYS, REFERENCE_BRANCH]"); ws.newRequest() .setParam("project", project.getKey()) @@ -322,7 +322,8 @@ public class SetActionTest { return new Object[][]{ {NewCodePeriodType.NUMBER_OF_DAYS, "5"}, {NewCodePeriodType.SPECIFIC_ANALYSIS, "analysis-uuid"}, - {NewCodePeriodType.PREVIOUS_VERSION, null} + {NewCodePeriodType.PREVIOUS_VERSION, null}, + {NewCodePeriodType.REFERENCE_BRANCH, "master"} }; } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/newcodeperiod/ws/ShowActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/newcodeperiod/ws/ShowActionTest.java index e2cbfdea650..b67fe6cb374 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/newcodeperiod/ws/ShowActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/newcodeperiod/ws/ShowActionTest.java @@ -38,7 +38,6 @@ import org.sonar.db.newcodeperiod.NewCodePeriodType; import org.sonar.server.component.ComponentFinder; import org.sonar.server.component.TestComponentFinder; import org.sonar.server.exceptions.ForbiddenException; -import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.WsActionTester; import org.sonarqube.ws.NewCodePeriods; @@ -88,31 +87,21 @@ public class ShowActionTest { } @Test - public void throw_NFE_if_project_not_found() { - expectedException.expect(NotFoundException.class); - expectedException.expectMessage("Project 'unknown' not found"); - - ws.newRequest() - .setParam("project", "unknown") - .execute(); - } - - @Test - public void throw_NFE_if_branch_not_found() { + public void throw_FE_if_no_project_permission() { ComponentDto project = componentDb.insertPublicProject(); - logInAsProjectAdministrator(project); - expectedException.expect(NotFoundException.class); - expectedException.expectMessage("Branch 'unknown' in project '" + project.getKey() + "' not found"); + expectedException.expect(ForbiddenException.class); + expectedException.expectMessage("Insufficient privileges"); ws.newRequest() .setParam("project", project.getKey()) - .setParam("branch", "unknown") .execute(); } @Test - public void throw_FE_if_no_project_permission() { + public void throw_FE_if_project_issue_admin() { ComponentDto project = componentDb.insertPublicProject(); + logInAsProjectIssueAdmin(project); + expectedException.expect(ForbiddenException.class); expectedException.expectMessage("Insufficient privileges"); @@ -217,6 +206,32 @@ public class ShowActionTest { assertResponse(response, project.getKey(), "branch", NewCodePeriods.NewCodePeriodType.NUMBER_OF_DAYS, "3", true); } + @Test + public void show_inherited_if_project_not_found() { + tester.insert(new NewCodePeriodDto().setType(NewCodePeriodType.NUMBER_OF_DAYS).setValue("3")); + + ShowWSResponse response = ws.newRequest() + .setParam("project", "unknown") + .executeProtobuf(ShowWSResponse.class); + + assertResponse(response, "", "", NewCodePeriods.NewCodePeriodType.NUMBER_OF_DAYS, "3", true); + } + + @Test + public void show_inherited_if_branch_not_found() { + ComponentDto project = componentDb.insertPublicProject(); + logInAsProjectScan(project); + + tester.insert(project.projectUuid(), NewCodePeriodType.NUMBER_OF_DAYS, "3"); + + ShowWSResponse response = ws.newRequest() + .setParam("project", project.getKey()) + .setParam("branch", "unknown") + .executeProtobuf(ShowWSResponse.class); + + assertResponse(response, project.getKey(), "", NewCodePeriods.NewCodePeriodType.NUMBER_OF_DAYS, "3", true); + } + private void assertResponse(ShowWSResponse response, String projectKey, String branchKey, NewCodePeriods.NewCodePeriodType type, String value, boolean inherited) { assertThat(response.getBranchKey()).isEqualTo(branchKey); assertThat(response.getProjectKey()).isEqualTo(projectKey); @@ -229,4 +244,12 @@ public class ShowActionTest { userSession.logIn().addProjectPermission(UserRole.ADMIN, project); } + private void logInAsProjectScan(ComponentDto project) { + userSession.logIn().addProjectPermission(UserRole.SCAN, project); + } + + private void logInAsProjectIssueAdmin(ComponentDto project) { + userSession.logIn().addProjectPermission(UserRole.ISSUE_ADMIN, project); + } + } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/ScmProvider.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/ScmProvider.java index be44d13eb41..379a3cfddf3 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/ScmProvider.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/ScmProvider.java @@ -21,6 +21,7 @@ package org.sonar.api.batch.scm; import java.io.File; import java.nio.file.Path; +import java.time.Instant; import java.util.Map; import java.util.Set; import javax.annotation.CheckForNull; @@ -82,6 +83,17 @@ public abstract class ScmProvider { } /** + * Find the date of the merge base between the current branch and the given reference branch. + * + * @return null if the SCM provider was not able to compute the date + * @since 8.4 + */ + @CheckForNull + public Instant forkDate(String referenceBranchName, Path rootBaseDir) { + return null; + } + + /** * The relative path from SCM root * @since 7.0 */ diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/GlobalContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/GlobalContainer.java index 02cb795c25b..6a7f5c663ff 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/GlobalContainer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/GlobalContainer.java @@ -49,8 +49,10 @@ import org.sonar.core.util.UuidFactoryImpl; import org.sonar.scanner.extension.ScannerCoreExtensionsInstaller; import org.sonar.scanner.platform.DefaultServer; import org.sonar.scanner.repository.DefaultMetricsRepositoryLoader; +import org.sonar.scanner.repository.DefaultNewCodePeriodLoader; import org.sonar.scanner.repository.MetricsRepositoryLoader; import org.sonar.scanner.repository.MetricsRepositoryProvider; +import org.sonar.scanner.repository.NewCodePeriodLoader; import org.sonar.scanner.repository.settings.DefaultGlobalSettingsLoader; import org.sonar.scanner.repository.settings.GlobalSettingsLoader; import org.sonar.scanner.scan.ProjectScanContainer; @@ -118,6 +120,7 @@ public class GlobalContainer extends ComponentContainer { addIfMissing(ScannerPluginInstaller.class, PluginInstaller.class); add(CoreExtensionRepositoryImpl.class, CoreExtensionsLoader.class, ScannerCoreExtensionsInstaller.class); addIfMissing(DefaultGlobalSettingsLoader.class, GlobalSettingsLoader.class); + addIfMissing(DefaultNewCodePeriodLoader.class, NewCodePeriodLoader.class); addIfMissing(DefaultMetricsRepositoryLoader.class, MetricsRepositoryLoader.class); } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java index 76b6d52c589..3eb0ed89517 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java @@ -21,6 +21,7 @@ package org.sonar.scanner.report; import java.io.File; import java.nio.file.Path; +import java.time.Instant; import java.util.LinkedList; import java.util.Map.Entry; import java.util.regex.Pattern; @@ -38,6 +39,7 @@ import org.sonar.scanner.fs.InputModuleHierarchy; import org.sonar.scanner.protocol.output.ScannerReport; import org.sonar.scanner.protocol.output.ScannerReport.Metadata.BranchType; import org.sonar.scanner.protocol.output.ScannerReportWriter; +import org.sonar.scanner.repository.ForkDateSupplier; import org.sonar.scanner.rule.QProfile; import org.sonar.scanner.rule.QualityProfiles; import org.sonar.scanner.scan.ScanProperties; @@ -57,13 +59,13 @@ public class MetadataPublisher implements ReportPublisherStep { private final ScannerPluginRepository pluginRepository; private final BranchConfiguration branchConfiguration; private final ScmRevision scmRevision; - + private final ForkDateSupplier forkDateSupplier; @Nullable private final ScmConfiguration scmConfiguration; public MetadataPublisher(ProjectInfo projectInfo, InputModuleHierarchy moduleHierarchy, ScanProperties properties, QualityProfiles qProfiles, CpdSettings cpdSettings, ScannerPluginRepository pluginRepository, BranchConfiguration branchConfiguration, - ScmRevision scmRevision, @Nullable ScmConfiguration scmConfiguration) { + ScmRevision scmRevision, ForkDateSupplier forkDateSupplier, @Nullable ScmConfiguration scmConfiguration) { this.projectInfo = projectInfo; this.moduleHierarchy = moduleHierarchy; this.properties = properties; @@ -72,12 +74,13 @@ public class MetadataPublisher implements ReportPublisherStep { this.pluginRepository = pluginRepository; this.branchConfiguration = branchConfiguration; this.scmRevision = scmRevision; + this.forkDateSupplier = forkDateSupplier; this.scmConfiguration = scmConfiguration; } - public MetadataPublisher(ProjectInfo projectInfo, InputModuleHierarchy moduleHierarchy, ScanProperties properties, - QualityProfiles qProfiles, CpdSettings cpdSettings, ScannerPluginRepository pluginRepository, BranchConfiguration branchConfiguration, ScmRevision scmRevision) { - this(projectInfo, moduleHierarchy, properties, qProfiles, cpdSettings, pluginRepository, branchConfiguration, scmRevision, null); + public MetadataPublisher(ProjectInfo projectInfo, InputModuleHierarchy moduleHierarchy, ScanProperties properties, QualityProfiles qProfiles, + CpdSettings cpdSettings, ScannerPluginRepository pluginRepository, BranchConfiguration branchConfiguration, ScmRevision scmRevision, ForkDateSupplier forkDateSupplier) { + this(projectInfo, moduleHierarchy, properties, qProfiles, cpdSettings, pluginRepository, branchConfiguration, scmRevision, forkDateSupplier, null); } @Override @@ -99,6 +102,7 @@ public class MetadataPublisher implements ReportPublisherStep { } addScmInformation(builder); + addForkPoint(builder); for (QProfile qp : qProfiles.findAll()) { builder.putQprofilesPerLanguage(qp.getLanguage(), ScannerReport.Metadata.QProfile.newBuilder() @@ -118,6 +122,13 @@ public class MetadataPublisher implements ReportPublisherStep { writer.writeMetadata(builder.build()); } + private void addForkPoint(ScannerReport.Metadata.Builder builder) { + Instant date = forkDateSupplier.get(); + if (date != null) { + builder.setForkDate(date.toEpochMilli()); + } + } + private void addModulesRelativePaths(ScannerReport.Metadata.Builder builder) { LinkedList<DefaultInputModule> queue = new LinkedList<>(); queue.add(moduleHierarchy.root()); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/DefaultNewCodePeriodLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/DefaultNewCodePeriodLoader.java new file mode 100644 index 00000000000..456e43a4aa9 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/DefaultNewCodePeriodLoader.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.scanner.repository; + +import java.io.IOException; +import java.io.InputStream; +import org.sonar.scanner.bootstrap.DefaultScannerWsClient; +import org.sonarqube.ws.NewCodePeriods; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.HttpException; + +import static org.sonar.api.impl.utils.ScannerUtils.encodeForUrl; + +public class DefaultNewCodePeriodLoader implements NewCodePeriodLoader { + private static final String WS_URL = "/api/new_code_periods/show.protobuf"; + + private final DefaultScannerWsClient wsClient; + + public DefaultNewCodePeriodLoader(DefaultScannerWsClient wsClient) { + this.wsClient = wsClient; + } + + @Override public NewCodePeriods.ShowWSResponse load(String projectKey, String branchName) { + String url = WS_URL + "?project=" + encodeForUrl(projectKey) + "&branch=" + encodeForUrl(branchName); + try { + return call(url); + } catch (HttpException | IOException e) { + throw new IllegalStateException("Failed to get the New Code definition: " + e.getMessage(), e); + } + } + + private NewCodePeriods.ShowWSResponse call(String url) throws IOException { + GetRequest getRequest = new GetRequest(url); + try (InputStream is = wsClient.call(getRequest).contentStream()) { + return NewCodePeriods.ShowWSResponse.parseFrom(is); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/ForkDateSupplier.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/ForkDateSupplier.java new file mode 100644 index 00000000000..9bbed47083e --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/ForkDateSupplier.java @@ -0,0 +1,93 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.scanner.repository; + +import java.time.Instant; +import javax.annotation.CheckForNull; +import org.sonar.api.batch.fs.internal.DefaultInputProject; +import org.sonar.api.notifications.AnalysisWarnings; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; +import org.sonar.scanner.report.ChangedLinesPublisher; +import org.sonar.scanner.scan.branch.BranchConfiguration; +import org.sonar.scanner.scan.branch.ProjectBranches; +import org.sonar.scanner.scm.ScmConfiguration; +import org.sonarqube.ws.NewCodePeriods; + +public class ForkDateSupplier { + private static final Logger LOG = Loggers.get(ChangedLinesPublisher.class); + private static final String LOG_MSG_WS = "Load New Code definition"; + + private final NewCodePeriodLoader newCodePeriodLoader; + private final BranchConfiguration branchConfiguration; + private final DefaultInputProject project; + private final ScmConfiguration scmConfiguration; + private final ProjectBranches branches; + private final AnalysisWarnings analysisWarnings; + + public ForkDateSupplier(NewCodePeriodLoader newCodePeriodLoader, BranchConfiguration branchConfiguration, + DefaultInputProject project, ScmConfiguration scmConfiguration, ProjectBranches branches, AnalysisWarnings analysisWarnings) { + this.newCodePeriodLoader = newCodePeriodLoader; + this.branchConfiguration = branchConfiguration; + this.project = project; + this.scmConfiguration = scmConfiguration; + this.branches = branches; + this.analysisWarnings = analysisWarnings; + } + + @CheckForNull + public Instant get() { + // branches will be empty in CE + if (branchConfiguration.isPullRequest() || branches.isEmpty()) { + return null; + } + + Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG_WS); + String branchName = branchConfiguration.branchName() != null ? branchConfiguration.branchName() : branches.defaultBranchName(); + NewCodePeriods.ShowWSResponse newCode = newCodePeriodLoader.load(project.key(), branchName); + profiler.stopInfo(); + if (newCode.getType() != NewCodePeriods.NewCodePeriodType.REFERENCE_BRANCH) { + return null; + } + + String referenceBranchName = newCode.getValue(); + if (branchName.equals(referenceBranchName)) { + LOG.warn("New Code reference branch is set to the branch being analyzed. Skipping the computation of New Code"); + return null; + } + + LOG.info("Computing New Code since fork with '{}'", referenceBranchName); + if (scmConfiguration.isDisabled() || scmConfiguration.provider() == null) { + LOG.warn("SCM provider is disabled. No New Code will be computed."); + analysisWarnings.addUnique("The scanner failed to compute New Code because no SCM provider was found. Please check your scanner logs."); + return null; + } + + Instant forkdate = scmConfiguration.provider().forkDate(referenceBranchName, project.getBaseDir()); + if (forkdate != null) { + LOG.debug("Fork detected at '{}'", referenceBranchName, forkdate); + } else { + analysisWarnings.addUnique("The scanner failed to compute New Code. Please check your scanner logs."); + LOG.warn("Failed to detect fork date. No New Code will be computed.", referenceBranchName); + } + return forkdate; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/NewCodePeriodLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/NewCodePeriodLoader.java new file mode 100644 index 00000000000..e6a7d63fd85 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/NewCodePeriodLoader.java @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.scanner.repository; + +import org.sonarqube.ws.NewCodePeriods; + +public interface NewCodePeriodLoader { + NewCodePeriods.ShowWSResponse load(String projectKey, String branchName); +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java index 263913d0062..7e14856626d 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java @@ -91,6 +91,7 @@ import org.sonar.scanner.report.TestExecutionPublisher; import org.sonar.scanner.repository.ContextPropertiesCache; import org.sonar.scanner.repository.DefaultProjectRepositoriesLoader; import org.sonar.scanner.repository.DefaultQualityProfileLoader; +import org.sonar.scanner.repository.ForkDateSupplier; import org.sonar.scanner.repository.ProjectRepositoriesLoader; import org.sonar.scanner.repository.ProjectRepositoriesSupplier; import org.sonar.scanner.repository.QualityProfileLoader; @@ -239,6 +240,7 @@ public class ProjectScanContainer extends ComponentContainer { ProjectCoverageAndDuplicationExclusions.class, // Report + ForkDateSupplier.class, ScannerMetrics.class, ReportPublisher.class, AnalysisContextReportPublisher.class, diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectBranchesProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectBranchesProvider.java index 616ee42f83f..0e4cc266258 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectBranchesProvider.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectBranchesProvider.java @@ -19,7 +19,7 @@ */ package org.sonar.scanner.scan.branch; -import com.google.common.collect.ImmutableList; +import java.util.Collections; import org.picocontainer.annotations.Nullable; import org.picocontainer.injectors.ProviderAdapter; import org.sonar.api.utils.log.Logger; @@ -40,7 +40,7 @@ public class ProjectBranchesProvider extends ProviderAdapter { } if (loader == null) { - this.branches = new ProjectBranches(ImmutableList.of()); + this.branches = new ProjectBranches(Collections.emptyList()); return this.branches; } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/BatchTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/BatchTest.java index 0628ed9525a..fc1d6a5d68a 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/BatchTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/BatchTest.java @@ -19,6 +19,9 @@ */ package org.sonar.batch.bootstrapper; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -27,8 +30,11 @@ import static org.junit.Assert.assertNull; import static org.mockito.Mockito.mock; public class BatchTest { + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"); + @Test public void testBuilder() { + System.out.println(FORMATTER.format(LocalDate.parse("2019-05-02").atStartOfDay(ZoneId.systemDefault()))); Batch batch = newBatch(); assertNotNull(batch); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/WsTestUtil.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/WsTestUtil.java index 30986ae83bc..e8e3f273c81 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/WsTestUtil.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/WsTestUtil.java @@ -92,5 +92,10 @@ public class WsTestUtil { } return StringUtils.equals(item.getPath(), path); } + + @Override + public String toString() { + return "Request with path: " + path; + } } } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java index a3a1024621d..ce2224dc165 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java @@ -60,6 +60,7 @@ import org.sonar.scanner.report.CeTaskReportDataHolder; import org.sonar.scanner.repository.FileData; import org.sonar.scanner.repository.MetricsRepository; import org.sonar.scanner.repository.MetricsRepositoryLoader; +import org.sonar.scanner.repository.NewCodePeriodLoader; import org.sonar.scanner.repository.ProjectRepositories; import org.sonar.scanner.repository.ProjectRepositoriesLoader; import org.sonar.scanner.repository.QualityProfileLoader; @@ -74,6 +75,7 @@ import org.sonar.scanner.scan.branch.BranchConfigurationLoader; import org.sonar.scanner.scan.branch.BranchType; import org.sonar.scanner.scan.branch.ProjectBranches; import org.sonar.scanner.scan.branch.ProjectPullRequests; +import org.sonarqube.ws.NewCodePeriods; import org.sonarqube.ws.Qualityprofiles.SearchWsResponse.QualityProfile; import org.sonarqube.ws.Rules.ListResponse.Rule; @@ -91,6 +93,7 @@ public class ScannerMediumTester extends ExternalResource { private final FakePluginInstaller pluginInstaller = new FakePluginInstaller(); private final FakeGlobalSettingsLoader globalSettingsLoader = new FakeGlobalSettingsLoader(); private final FakeProjectSettingsLoader projectSettingsLoader = new FakeProjectSettingsLoader(); + private final FakeNewCodePeriodLoader newCodePeriodLoader = new FakeNewCodePeriodLoader(); private final FakeRulesLoader rulesLoader = new FakeRulesLoader(); private final FakeQualityProfileLoader qualityProfiles = new FakeQualityProfileLoader(); private final FakeActiveRulesLoader activeRules = new FakeActiveRulesLoader(); @@ -226,6 +229,11 @@ public class ScannerMediumTester extends ExternalResource { return this; } + public ScannerMediumTester setNewCodePeriod(NewCodePeriods.NewCodePeriodType type, String value) { + newCodePeriodLoader.set(NewCodePeriods.ShowWSResponse.newBuilder().setType(type).setValue(value).build()); + return this; + } + @Override protected void before() { try { @@ -294,6 +302,7 @@ public class ScannerMediumTester extends ExternalResource { tester.activeRules, tester.globalSettingsLoader, tester.projectSettingsLoader, + tester.newCodePeriodLoader, tester.sonarRuntime, tester.reportMetadataHolder, result) @@ -511,6 +520,19 @@ public class ScannerMediumTester extends ExternalResource { } } + private static class FakeNewCodePeriodLoader implements NewCodePeriodLoader { + private NewCodePeriods.ShowWSResponse response; + + @Override + public NewCodePeriods.ShowWSResponse load(String projectKey, String branchName) { + return response; + } + + public void set(NewCodePeriods.ShowWSResponse response) { + this.response = response; + } + } + private static class FakeProjectSettingsLoader implements ProjectSettingsLoader { private Map<String, String> projectSettings = new HashMap<>(); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/branch/BranchMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/branch/BranchMediumTest.java index 19668da6de3..917d721ae3d 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/branch/BranchMediumTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/branch/BranchMediumTest.java @@ -40,6 +40,7 @@ import org.sonar.scanner.repository.FileData; import org.sonar.scanner.scan.branch.BranchType; import org.sonar.xoo.XooPlugin; import org.sonar.xoo.rule.XooRulesDefinition; +import org.sonarqube.ws.NewCodePeriods; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -74,6 +75,7 @@ public class BranchMediumTest { .readMetadata(Files.newInputStream(filepath), StandardCharsets.UTF_8, FILE_PATH) .hash(); tester.addFileData(FILE_PATH, new FileData(md5sum, "1.1")); + tester.setNewCodePeriod(NewCodePeriods.NewCodePeriodType.PREVIOUS_VERSION, ""); } @Test diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MetadataPublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MetadataPublisherTest.java index 934eefaa7a3..53d37e99363 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MetadataPublisherTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MetadataPublisherTest.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Instant; import java.util.Collections; import java.util.Date; import java.util.Optional; @@ -49,6 +50,7 @@ import org.sonar.scanner.fs.InputModuleHierarchy; import org.sonar.scanner.protocol.output.ScannerReport; import org.sonar.scanner.protocol.output.ScannerReportReader; import org.sonar.scanner.protocol.output.ScannerReportWriter; +import org.sonar.scanner.repository.ForkDateSupplier; import org.sonar.scanner.rule.QProfile; import org.sonar.scanner.rule.QualityProfiles; import org.sonar.scanner.scan.ScanProperties; @@ -78,6 +80,7 @@ public class MetadataPublisherTest { private ProjectInfo projectInfo = mock(ProjectInfo.class); private CpdSettings cpdSettings = mock(CpdSettings.class); private InputModuleHierarchy inputModuleHierarchy; + private ForkDateSupplier forkDateSupplier = mock(ForkDateSupplier.class); private ScannerPluginRepository pluginRepository = mock(ScannerPluginRepository.class); private BranchConfiguration branches; private ScmConfiguration scmConfiguration; @@ -115,12 +118,13 @@ public class MetadataPublisherTest { scmConfiguration = mock(ScmConfiguration.class); when(scmConfiguration.provider()).thenReturn(scmProvider); underTest = new MetadataPublisher(projectInfo, inputModuleHierarchy, properties, qProfiles, cpdSettings, - pluginRepository, branches, scmRevision, scmConfiguration); + pluginRepository, branches, scmRevision, forkDateSupplier, scmConfiguration); } @Test public void write_metadata() throws Exception { Date date = new Date(); + when(forkDateSupplier.get()).thenReturn(Instant.ofEpochMilli(123456789L)); when(qProfiles.findAll()).thenReturn(Collections.singletonList(new QProfile("q1", "Q1", "java", date))); when(pluginRepository.getPluginsByKey()).thenReturn(ImmutableMap.of( "java", new ScannerPlugin("java", 12345L, null), @@ -132,6 +136,7 @@ public class MetadataPublisherTest { ScannerReportReader reader = new ScannerReportReader(outputDir); ScannerReport.Metadata metadata = reader.readMetadata(); + assertThat(metadata.getForkDate()).isEqualTo(123456789L); assertThat(metadata.getAnalysisDate()).isEqualTo(1234567L); assertThat(metadata.getProjectKey()).isEqualTo("root"); assertThat(metadata.getModulesProjectRelativePathByKeyMap()).containsOnly(entry("module", "modulePath"), entry("root", "")); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/DefaultNewCodePeriodLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/DefaultNewCodePeriodLoaderTest.java new file mode 100644 index 00000000000..6299aa05c6e --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/DefaultNewCodePeriodLoaderTest.java @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.scanner.repository; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.scanner.WsTestUtil; +import org.sonar.scanner.bootstrap.DefaultScannerWsClient; +import org.sonarqube.ws.NewCodePeriods; + +import static org.mockito.Mockito.mock; + +public class DefaultNewCodePeriodLoaderTest { + @Rule + public ExpectedException exception = ExpectedException.none(); + + private DefaultScannerWsClient wsClient = mock(DefaultScannerWsClient.class); + private DefaultNewCodePeriodLoader underTest = new DefaultNewCodePeriodLoader(wsClient); + + @Test + public void loads_new_code_period() throws IOException { + prepareCallWithResults(); + underTest.load("project", "branch"); + verifyCalledPath("/api/new_code_periods/show.protobuf?project=project&branch=branch"); + } + + private void verifyCalledPath(String expectedPath) { + WsTestUtil.verifyCall(wsClient, expectedPath); + } + + private void prepareCallWithResults() throws IOException { + WsTestUtil.mockStream(wsClient, createResponse(NewCodePeriods.NewCodePeriodType.REFERENCE_BRANCH, "master")); + } + + private InputStream createResponse(NewCodePeriods.NewCodePeriodType type, String value) throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + NewCodePeriods.ShowWSResponse response = NewCodePeriods.ShowWSResponse.newBuilder() + .setType(type) + .setValue(value) + .build(); + + response.writeTo(os); + return new ByteArrayInputStream(os.toByteArray()); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/ForkDateSupplierTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/ForkDateSupplierTest.java new file mode 100644 index 00000000000..895d81634c9 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/ForkDateSupplierTest.java @@ -0,0 +1,167 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.scanner.repository; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Instant; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.batch.fs.internal.DefaultInputProject; +import org.sonar.api.batch.scm.ScmProvider; +import org.sonar.api.notifications.AnalysisWarnings; +import org.sonar.scanner.scan.branch.BranchConfiguration; +import org.sonar.scanner.scan.branch.BranchType; +import org.sonar.scanner.scan.branch.ProjectBranches; +import org.sonar.scanner.scm.ScmConfiguration; +import org.sonarqube.ws.NewCodePeriods; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class ForkDateSupplierTest { + private final static String PROJECT_KEY = "project"; + private final static String BRANCH_KEY = "branch"; + private final static Path BASE_DIR = Paths.get("root"); + + private NewCodePeriodLoader newCodePeriodLoader = mock(NewCodePeriodLoader.class); + private BranchConfiguration branchConfiguration = mock(BranchConfiguration.class); + private DefaultInputProject project = mock(DefaultInputProject.class); + private ScmConfiguration scmConfiguration = mock(ScmConfiguration.class); + private ScmProvider scmProvider = mock(ScmProvider.class); + private ProjectBranches projectBranches = mock(ProjectBranches.class); + private AnalysisWarnings analysisWarnings = mock(AnalysisWarnings.class); + private ForkDateSupplier forkDateSupplier = new ForkDateSupplier(newCodePeriodLoader, branchConfiguration, project, scmConfiguration, projectBranches, analysisWarnings); + + @Before + public void setUp() { + when(projectBranches.isEmpty()).thenReturn(false); + when(project.key()).thenReturn(PROJECT_KEY); + when(project.getBaseDir()).thenReturn(BASE_DIR); + when(scmConfiguration.isDisabled()).thenReturn(false); + when(scmConfiguration.provider()).thenReturn(scmProvider); + } + + @Test + public void returns_forkDate_for_branches_with_ref() { + Instant date = Instant.now(); + when(branchConfiguration.branchType()).thenReturn(BranchType.BRANCH); + when(branchConfiguration.branchName()).thenReturn(BRANCH_KEY); + when(scmProvider.forkDate("master", BASE_DIR)).thenReturn(date); + when(newCodePeriodLoader.load(PROJECT_KEY, BRANCH_KEY)).thenReturn(createResponse(NewCodePeriods.NewCodePeriodType.REFERENCE_BRANCH, "master")); + + assertThat(forkDateSupplier.get()).isEqualTo(date); + } + + @Test + public void uses_default_branch_if_no_branch_specified() { + Instant date = Instant.now(); + when(branchConfiguration.branchType()).thenReturn(BranchType.BRANCH); + when(branchConfiguration.branchName()).thenReturn(null); + when(projectBranches.defaultBranchName()).thenReturn("default"); + when(newCodePeriodLoader.load(PROJECT_KEY, "default")).thenReturn(createResponse(NewCodePeriods.NewCodePeriodType.REFERENCE_BRANCH, "master")); + when(scmProvider.forkDate("master", BASE_DIR)).thenReturn(date); + + assertThat(forkDateSupplier.get()).isEqualTo(date); + + verifyNoInteractions(analysisWarnings); + } + + @Test + public void returns_null_if_no_branches() { + when(projectBranches.isEmpty()).thenReturn(true); + + assertThat(forkDateSupplier.get()).isNull(); + + verify(branchConfiguration).isPullRequest(); + verify(projectBranches).isEmpty(); + verifyNoMoreInteractions(branchConfiguration); + verifyNoInteractions(scmConfiguration, scmProvider, analysisWarnings, newCodePeriodLoader); + } + + @Test + public void returns_null_if_scm_disabled() { + when(branchConfiguration.branchType()).thenReturn(BranchType.BRANCH); + when(branchConfiguration.branchName()).thenReturn(BRANCH_KEY); + when(scmConfiguration.isDisabled()).thenReturn(true); + when(newCodePeriodLoader.load(PROJECT_KEY, BRANCH_KEY)).thenReturn(createResponse(NewCodePeriods.NewCodePeriodType.REFERENCE_BRANCH, "master")); + + assertThat(forkDateSupplier.get()).isNull(); + + verify(scmConfiguration).isDisabled(); + verify(branchConfiguration, times(2)).branchName(); + verify(branchConfiguration).isPullRequest(); + verify(analysisWarnings).addUnique(anyString()); + + verifyNoInteractions(scmProvider); + verifyNoMoreInteractions(branchConfiguration); + } + + @Test + public void returns_null_if_reference_branch_is_the_branch_being_analyzed() { + when(branchConfiguration.branchType()).thenReturn(BranchType.BRANCH); + when(branchConfiguration.branchName()).thenReturn(BRANCH_KEY); + when(newCodePeriodLoader.load(PROJECT_KEY, BRANCH_KEY)).thenReturn(createResponse(NewCodePeriods.NewCodePeriodType.REFERENCE_BRANCH, BRANCH_KEY)); + + assertThat(forkDateSupplier.get()).isNull(); + + verify(branchConfiguration, times(2)).branchName(); + verify(branchConfiguration).isPullRequest(); + verify(newCodePeriodLoader).load(PROJECT_KEY, BRANCH_KEY); + + verifyNoInteractions(scmProvider, analysisWarnings, scmConfiguration); + verifyNoMoreInteractions(branchConfiguration); + } + + @Test + public void returns_null_if_pull_request() { + when(branchConfiguration.isPullRequest()).thenReturn(true); + assertThat(forkDateSupplier.get()).isNull(); + + verify(branchConfiguration).isPullRequest(); + + verifyNoInteractions(newCodePeriodLoader, analysisWarnings, scmProvider, scmConfiguration); + verifyNoMoreInteractions(branchConfiguration); + } + + @Test + public void returns_null_if_new_code_period_is_not_ref() { + when(branchConfiguration.isPullRequest()).thenReturn(true); + when(branchConfiguration.branchName()).thenReturn(BRANCH_KEY); + when(newCodePeriodLoader.load(PROJECT_KEY, BRANCH_KEY)).thenReturn(createResponse(NewCodePeriods.NewCodePeriodType.NUMBER_OF_DAYS, "2")); + + assertThat(forkDateSupplier.get()).isNull(); + + verifyNoInteractions(scmProvider, analysisWarnings, scmConfiguration); + } + + private NewCodePeriods.ShowWSResponse createResponse(NewCodePeriods.NewCodePeriodType type, String value) { + return NewCodePeriods.ShowWSResponse.newBuilder() + .setType(type) + .setValue(value) + .build(); + } +} diff --git a/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto index 7aad1a281af..b984bf7abbb 100644 --- a/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto +++ b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto @@ -56,6 +56,8 @@ message Metadata { string target_branch_name = 18; + int64 forkDate = 19; + message QProfile { string key = 1; string name = 2; diff --git a/sonar-ws/src/main/protobuf/ws-newcodeperiods.proto b/sonar-ws/src/main/protobuf/ws-newcodeperiods.proto index 73b7fdc5350..56d874e7b8f 100644 --- a/sonar-ws/src/main/protobuf/ws-newcodeperiods.proto +++ b/sonar-ws/src/main/protobuf/ws-newcodeperiods.proto @@ -44,4 +44,5 @@ enum NewCodePeriodType { PREVIOUS_VERSION = 1; NUMBER_OF_DAYS = 2; SPECIFIC_ANALYSIS = 3; + REFERENCE_BRANCH = 4; } |