]> source.dussan.org Git - sonarqube.git/blob
f0fc255cd388843ecf3463b43cdd87dcb7bdc6f8
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
20 package org.sonar.ce.task.projectanalysis.source;
21
22 import com.google.common.base.Preconditions;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.Map;
26 import java.util.Optional;
27 import java.util.Set;
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;
36
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<>();
43
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;
49   }
50
51   public boolean newLinesAvailable() {
52     return analysisMetadataHolder.isPullRequest() || periodHolder.hasPeriodDate() || isReferenceBranch();
53   }
54
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();
59     }
60     Optional<Set<Integer>> reportChangedLines = getChangedLinesFromReport(file);
61     if (reportChangedLines.isPresent()) {
62       return reportChangedLines;
63     }
64     return computeNewLinesFromScm(file);
65   }
66
67   /**
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.
70    */
71   private Optional<Set<Integer>> computeNewLinesFromScm(Component component) {
72     Optional<ScmInfo> scmInfoOpt = scmInfoRepository.getScmInfo(component);
73     if (scmInfoOpt.isEmpty()) {
74       return Optional.empty();
75     }
76
77     ScmInfo scmInfo = scmInfoOpt.get();
78     Changeset[] allChangesets = scmInfo.getAllChangesets();
79     Set<Integer> lines = new HashSet<>();
80
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)) {
85         lines.add(i + 1);
86       }
87     }
88
89     return Optional.of(lines);
90   }
91
92   private boolean useAnalysisDateAsReferenceDate() {
93     return analysisMetadataHolder.isPullRequest() || NewCodePeriodType.REFERENCE_BRANCH.name().equals(periodHolder.getPeriod().getMode());
94   }
95
96   /**
97    * A line belongs to a Period if its date is older than the SNAPSHOT's date of the period.
98    */
99   private static boolean isLineInPeriod(long lineDate, long referenceDate) {
100     return lineDate > referenceDate;
101   }
102
103   private Optional<Set<Integer>> getChangedLinesFromReport(Component file) {
104     if (analysisMetadataHolder.isPullRequest() || isReferenceBranch()) {
105       return reportChangedLinesCache.computeIfAbsent(file, this::readFromReport);
106     }
107
108     return Optional.empty();
109   }
110
111   private boolean isReferenceBranch() {
112     return periodHolder.hasPeriod() && periodHolder.getPeriod().getMode().equals(NewCodePeriodType.REFERENCE_BRANCH.name());
113   }
114
115   private Optional<Set<Integer>> readFromReport(Component file) {
116     return reportReader.readComponentChangedLines(file.getReportAttributes().getRef())
117       .map(c -> new HashSet<>(c.getLineList()));
118   }
119 }