3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.ce.task.projectanalysis.source;
22 import com.google.common.base.Preconditions;
23 import java.util.HashMap;
24 import java.util.HashSet;
26 import java.util.Optional;
28 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
29 import org.sonar.ce.task.projectanalysis.batch.BatchReportReader;
30 import org.sonar.ce.task.projectanalysis.component.Component;
31 import org.sonar.ce.task.projectanalysis.period.PeriodHolder;
32 import org.sonar.ce.task.projectanalysis.scm.Changeset;
33 import org.sonar.ce.task.projectanalysis.scm.ScmInfo;
34 import org.sonar.ce.task.projectanalysis.scm.ScmInfoRepository;
35 import org.sonar.db.newcodeperiod.NewCodePeriodType;
37 public class NewLinesRepository {
38 private final BatchReportReader reportReader;
39 private final AnalysisMetadataHolder analysisMetadataHolder;
40 private final ScmInfoRepository scmInfoRepository;
41 private final PeriodHolder periodHolder;
42 private final Map<Component, Optional<Set<Integer>>> reportChangedLinesCache = new HashMap<>();
44 public NewLinesRepository(BatchReportReader reportReader, AnalysisMetadataHolder analysisMetadataHolder, PeriodHolder periodHolder, ScmInfoRepository scmInfoRepository) {
45 this.reportReader = reportReader;
46 this.analysisMetadataHolder = analysisMetadataHolder;
47 this.scmInfoRepository = scmInfoRepository;
48 this.periodHolder = periodHolder;
51 public boolean newLinesAvailable() {
52 return analysisMetadataHolder.isPullRequest() || periodHolder.hasPeriodDate() || isReferenceBranch();
55 public Optional<Set<Integer>> getNewLines(Component file) {
56 Preconditions.checkArgument(file.getType() == Component.Type.FILE, "Changed lines are only available on files, but was: " + file.getType().name());
57 if (!newLinesAvailable()) {
58 return Optional.empty();
60 Optional<Set<Integer>> reportChangedLines = getChangedLinesFromReport(file);
61 if (reportChangedLines.isPresent()) {
62 return reportChangedLines;
64 return computeNewLinesFromScm(file);
68 * If the changed lines are not in the report or if we are not analyzing a P/R or a branch using a "reference branch", we fall back to this method.
69 * If there is a period and SCM information, we compare the change dates of each line with the start of the period to figure out if a line is new or not.
71 private Optional<Set<Integer>> computeNewLinesFromScm(Component component) {
72 Optional<ScmInfo> scmInfoOpt = scmInfoRepository.getScmInfo(component);
73 if (scmInfoOpt.isEmpty()) {
74 return Optional.empty();
77 ScmInfo scmInfo = scmInfoOpt.get();
78 Changeset[] allChangesets = scmInfo.getAllChangesets();
79 Set<Integer> lines = new HashSet<>();
81 // in PRs, we consider changes introduced in this analysis as new, hence subtracting 1.
82 long referenceDate = useAnalysisDateAsReferenceDate() ? (analysisMetadataHolder.getAnalysisDate() - 1) : periodHolder.getPeriod().getDate();
83 for (int i = 0; i < allChangesets.length; i++) {
84 if (isLineInPeriod(allChangesets[i].getDate(), referenceDate)) {
89 return Optional.of(lines);
92 private boolean useAnalysisDateAsReferenceDate() {
93 return analysisMetadataHolder.isPullRequest() || NewCodePeriodType.REFERENCE_BRANCH.name().equals(periodHolder.getPeriod().getMode());
97 * A line belongs to a Period if its date is older than the SNAPSHOT's date of the period.
99 private static boolean isLineInPeriod(long lineDate, long referenceDate) {
100 return lineDate > referenceDate;
103 private Optional<Set<Integer>> getChangedLinesFromReport(Component file) {
104 if (analysisMetadataHolder.isPullRequest() || isReferenceBranch()) {
105 return reportChangedLinesCache.computeIfAbsent(file, this::readFromReport);
108 return Optional.empty();
111 private boolean isReferenceBranch() {
112 return periodHolder.hasPeriod() && periodHolder.getPeriod().getMode().equals(NewCodePeriodType.REFERENCE_BRANCH.name());
115 private Optional<Set<Integer>> readFromReport(Component file) {
116 return reportReader.readComponentChangedLines(file.getReportAttributes().getRef())
117 .map(c -> new HashSet<>(c.getLineList()));