]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10257 Support lines without SCM info
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Fri, 12 Jan 2018 08:42:31 +0000 (09:42 +0100)
committerDuarte Meneses <duarte.meneses@sonarsource.com>
Wed, 7 Feb 2018 13:33:55 +0000 (14:33 +0100)
17 files changed:
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCreationDateCalculator.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/DbScmInfo.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ReportScmInfo.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfo.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoDbLoader.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoImpl.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/source/ScmLineReader.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/NewCoverageMeasuresStep.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/NewSizeMeasuresStep.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCreationDateCalculatorTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/scm/DbScmInfoTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoImplTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoRepositoryRule.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/source/ScmLineReaderTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistFileSourcesStepTest.java
sonar-core/src/main/java/org/sonar/core/util/stream/MoreCollectors.java

index f34d3d0fc73eb1a6b89ab22455a799c5f86a97c4..4c3c173109c16b0a0fde555c20549fea5575a639 100644 (file)
@@ -151,17 +151,13 @@ public class IssueCreationDateCalculator extends IssueVisitor {
       }
       if (!involvedLines.isEmpty()) {
         return involvedLines.stream()
+          .filter(scmInfo::hasChangesetForLine)
           .map(scmInfo::getChangesetForLine)
           .max(Comparator.comparingLong(Changeset::getDate));
       }
     }
 
-    Changeset latestChangeset = scmInfo.getLatestChangeset();
-    if (latestChangeset != null) {
-      return Optional.of(latestChangeset);
-    }
-
-    return Optional.empty();
+    return Optional.ofNullable(scmInfo.getLatestChangeset());
   }
 
   private static void addLines(Set<Integer> involvedLines, TextRange range) {
index ff993ee893237fb82d7b030d55aa1e13fb739c2f..86df58c3fe95c531fece8dd3881d836dacea8bef 100644 (file)
@@ -180,11 +180,13 @@ public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<N
 
     long lineDevCost = ratingSettings.getDevCost(file.getFileAttributes().getLanguageKey());
     for (Integer nclocLineIndex : nclocLineIndexes(nclocDataMeasure)) {
-      Changeset changeset = scmInfo.getChangesetForLine(nclocLineIndex);
-      Period period = periodHolder.getPeriod();
-      if (isLineInPeriod(changeset.getDate(), period)) {
-        devCostCounter.incrementDevCost(lineDevCost);
-        hasDevCost = true;
+      if (scmInfo.hasChangesetForLine(nclocLineIndex)) {
+        Changeset changeset = scmInfo.getChangesetForLine(nclocLineIndex);
+        Period period = periodHolder.getPeriod();
+        if (isLineInPeriod(changeset.getDate(), period)) {
+          devCostCounter.incrementDevCost(lineDevCost);
+          hasDevCost = true;
+        }
       }
     }
     if (hasDevCost) {
index a0051e80b9cc74053582ce3ac070788ec22b41c5..c8220e929cdc643cb6864b4d13aee425f873caec 100644 (file)
  */
 package org.sonar.server.computation.task.projectanalysis.scm;
 
-import com.google.common.base.Optional;
 import java.util.HashMap;
-import java.util.List;
+import java.util.LinkedHashMap;
 import java.util.Map;
-import java.util.Objects;
+import java.util.Optional;
 import java.util.function.Function;
-import java.util.stream.StreamSupport;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
-import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.protobuf.DbFileSources;
-import org.sonar.server.computation.task.projectanalysis.component.Component;
-
-import static com.google.common.base.Preconditions.checkState;
 
 /**
  * ScmInfo implementation based on the lines stored in DB
@@ -47,18 +41,21 @@ class DbScmInfo implements ScmInfo {
     this.delegate = delegate;
   }
 
-  static Optional<ScmInfo> create(Component component, Iterable<DbFileSources.Line> lines) {
+  static Optional<ScmInfo> create(Iterable<DbFileSources.Line> lines) {
     LineToChangeset lineToChangeset = new LineToChangeset();
-    List<Changeset> lineChangesets = StreamSupport.stream(lines.spliterator(), false)
-      .map(lineToChangeset)
-      .filter(Objects::nonNull)
-      .collect(MoreCollectors.toList());
-    if (lineChangesets.isEmpty()) {
-      return Optional.absent();
+    Map<Integer, Changeset> lineChanges = new LinkedHashMap<>();
+
+    for (DbFileSources.Line line : lines) {
+      Changeset changeset = lineToChangeset.apply(line);
+      if (changeset == null) {
+        continue;
+      }
+      lineChanges.put(line.getLine(), changeset);
     }
-    checkState(!lineToChangeset.isEncounteredLineWithoutScmInfo(),
-      "Partial scm information stored in DB for component '%s'. Not all lines have SCM info. Can not proceed", component);
-    return Optional.of(new DbScmInfo(new ScmInfoImpl(lineChangesets)));
+    if (lineChanges.isEmpty()) {
+      return Optional.empty();
+    }
+    return Optional.of(new DbScmInfo(new ScmInfoImpl(lineChanges)));
   }
 
   @Override
@@ -77,37 +74,34 @@ class DbScmInfo implements ScmInfo {
   }
 
   @Override
-  public Iterable<Changeset> getAllChangesets() {
+  public Map<Integer, Changeset> getAllChangesets() {
     return delegate.getAllChangesets();
   }
 
   /**
-   * Transforms {@link org.sonar.db.protobuf.DbFileSources.Line} into {@link Changeset} and keep a flag if it encountered
-   * at least one which did not have any SCM information.
+   * Transforms {@link org.sonar.db.protobuf.DbFileSources.Line} into {@link Changeset} 
    */
   private static class LineToChangeset implements Function<DbFileSources.Line, Changeset> {
-    private boolean encounteredLineWithoutScmInfo = false;
-    private final Map<String, Changeset> cache = new HashMap<>();
     private final Changeset.Builder builder = Changeset.newChangesetBuilder();
+    private final HashMap<Changeset, Changeset> cache = new HashMap<>();
 
     @Override
     @Nullable
     public Changeset apply(@Nonnull DbFileSources.Line input) {
-      if (input.hasScmRevision() && input.hasScmDate()) {
-        String revision = input.getScmRevision();
-        return cache.computeIfAbsent(revision, k -> builder
-          .setRevision(revision)
+      if (input.hasScmDate()) {
+        Changeset cs = builder
+          .setRevision(input.hasScmRevision() ? input.getScmRevision() : null)
           .setAuthor(input.hasScmAuthor() ? input.getScmAuthor() : null)
           .setDate(input.getScmDate())
-          .build());
+          .build();
+        if (cache.containsKey(cs)) {
+          return cache.get(cs);
+        }
+        cache.put(cs, cs);
+        return cs;
       }
 
-      this.encounteredLineWithoutScmInfo = true;
       return null;
     }
-
-    boolean isEncounteredLineWithoutScmInfo() {
-      return encounteredLineWithoutScmInfo;
-    }
   }
 }
index a44e4c215fecfb92c09f7b0ad8a9a64ea327ddb6..d117f344e7060be2eef9ac4a5b0339b97333535a 100644 (file)
  */
 package org.sonar.server.computation.task.projectanalysis.scm;
 
-import com.google.common.base.Function;
 import java.util.HashMap;
-import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.Map;
-import java.util.NoSuchElementException;
-import javax.annotation.Nonnull;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 import javax.annotation.concurrent.Immutable;
+import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.scanner.protocol.output.ScannerReport;
 
-import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.collect.FluentIterable.from;
 import static java.util.Objects.requireNonNull;
 import static org.apache.commons.lang.StringUtils.isNotEmpty;
 
@@ -44,12 +43,13 @@ class ReportScmInfo implements ScmInfo {
   ReportScmInfo(ScannerReport.Changesets changesets) {
     requireNonNull(changesets);
     this.delegate = convertToScmInfo(changesets);
+    checkState(!delegate.getAllChangesets().isEmpty(), "Report has no changesets");
   }
 
   private static ScmInfo convertToScmInfo(ScannerReport.Changesets changesets) {
-    return new ScmInfoImpl(
-      from(new IntRangeIterable(changesets.getChangesetIndexByLineCount()))
-        .transform(new LineIndexToChangeset(changesets)));
+    return new ScmInfoImpl(IntStream.rangeClosed(1, changesets.getChangesetIndexByLineCount())
+      .boxed()
+      .collect(Collectors.toMap(x -> x, new LineIndexToChangeset(changesets), MoreCollectors.mergeNotSupportedMerger(), LinkedHashMap::new)));
   }
 
   @Override
@@ -68,7 +68,7 @@ class ReportScmInfo implements ScmInfo {
   }
 
   @Override
-  public Iterable<Changeset> getAllChangesets() {
+  public Map<Integer, Changeset> getAllChangesets() {
     return this.delegate.getAllChangesets();
   }
 
@@ -83,16 +83,9 @@ class ReportScmInfo implements ScmInfo {
     }
 
     @Override
-    @Nonnull
-    public Changeset apply(@Nonnull Integer lineNumber) {
+    public Changeset apply(Integer lineNumber) {
       int changesetIndex = changesets.getChangesetIndexByLine(lineNumber - 1);
-      Changeset changeset = changesetCache.get(changesetIndex);
-      if (changeset != null) {
-        return changeset;
-      }
-      Changeset res = convert(changesets.getChangeset(changesetIndex), lineNumber);
-      changesetCache.put(changesetIndex, res);
-      return res;
+      return changesetCache.computeIfAbsent(changesetIndex, idx -> convert(changesets.getChangeset(changesetIndex), lineNumber));
     }
 
     private Changeset convert(ScannerReport.Changesets.Changeset changeset, int line) {
@@ -105,50 +98,4 @@ class ReportScmInfo implements ScmInfo {
         .build();
     }
   }
-
-  /**
-   * A simple Iterable which generate integer from 0 (included) to a specific value (excluded).
-   */
-  private static final class IntRangeIterable implements Iterable<Integer> {
-    private final int max;
-
-    private IntRangeIterable(int max) {
-      checkArgument(max >= 0, "Max value must be >= 0");
-      this.max = max;
-    }
-
-    @Override
-    public Iterator<Integer> iterator() {
-      return new IntRangeIterator(max);
-    }
-  }
-
-  private static class IntRangeIterator implements Iterator<Integer> {
-    private final int max;
-
-    private int current = 0;
-
-    public IntRangeIterator(int max) {
-      this.max = max;
-    }
-
-    @Override
-    public boolean hasNext() {
-      return current < max;
-    }
-
-    @Override
-    public Integer next() {
-      if (!hasNext()) {
-        throw new NoSuchElementException();
-      }
-      current++;
-      return current;
-    }
-
-    @Override
-    public void remove() {
-      throw new UnsupportedOperationException("Remove cannot be called");
-    }
-  }
 }
index ba67f742b84192c883d5374e529cc279ace85552..1297b674699ddbee96c993e4f262d42c8ab86aa1 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.server.computation.task.projectanalysis.scm;
 
+import java.util.Map;
+
 /**
  * Represents the Scm information for a specific file.
  */
@@ -32,7 +34,7 @@ public interface ScmInfo {
   /**
    * Get ChangeSet of the file for given line
    *
-   * @throws IllegalArgumentException if there is no Changeset for the specified line, ie. the line number in invalid (either less than 1 or > linecount of the file)
+   * @throws IllegalArgumentException if there is no Changeset for the specified line
    */
   Changeset getChangesetForLine(int lineNumber);
 
@@ -42,8 +44,8 @@ public interface ScmInfo {
   boolean hasChangesetForLine(int lineNumber);
 
   /**
-   * Return all ChangeSets, in order, for all lines of the file
+   * Return all ChangeSets, in order, for all lines that have changesets.
    */
-  Iterable<Changeset> getAllChangesets();
+  Map<Integer, Changeset> getAllChangesets();
 
 }
index b79950b4484d9400af1b296e4dadb78d9ae938d9..de56432ae5b6f3debfd4d0f6d344668181ac0075 100644 (file)
@@ -62,7 +62,7 @@ public class ScmInfoDbLoader {
       if (dto == null || !isDtoValid(file, dto)) {
         return NoScmInfo.INSTANCE;
       }
-      return DbScmInfo.create(file, dto.getSourceData().getLinesList()).or(NoScmInfo.INSTANCE);
+      return DbScmInfo.create(dto.getSourceData().getLinesList()).orElse(NoScmInfo.INSTANCE);
     }
   }
 
index c4f2afc093b285d66702c7d3df3c3247b108225b..df006eaf2fe546f41d51d68c3cd322ea56e1b7e1 100644 (file)
  */
 package org.sonar.server.computation.task.projectanalysis.scm;
 
-import com.google.common.base.Predicate;
-import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.stream.Collectors;
 import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.collect.FluentIterable.from;
-import static com.google.common.collect.Iterables.isEmpty;
-import static java.util.Arrays.asList;
-
 @Immutable
 public class ScmInfoImpl implements ScmInfo {
 
   @CheckForNull
   private final Changeset latestChangeset;
-  private final Changeset[] lineChangesets;
+  private final Map<Integer, Changeset> lineChangesets;
 
-  public ScmInfoImpl(Iterable<Changeset> lineChangesets) {
-    checkState(!isEmpty(lineChangesets), "A ScmInfo must have at least one Changeset and does not support any null one");
-    this.lineChangesets = from(lineChangesets)
-      .filter(CheckNotNull.INSTANCE)
-      .toArray(Changeset.class);
+  public ScmInfoImpl(Map<Integer, Changeset> lineChangesets) {
+    this.lineChangesets = lineChangesets;
     this.latestChangeset = computeLatestChangeset(lineChangesets);
   }
 
-  private static Changeset computeLatestChangeset(Iterable<Changeset> lineChangesets) {
-    Changeset latestChangeset = null;
-    for (Changeset lineChangeset : lineChangesets) {
-      if (latestChangeset == null || lineChangeset.getDate() > latestChangeset.getDate()) {
-        latestChangeset = lineChangeset;
-      }
-    }
-    return latestChangeset;
+  private static Changeset computeLatestChangeset(Map<Integer, Changeset> lineChangesets) {
+    return lineChangesets.values().stream()
+      .collect(Collectors.maxBy(Comparator.comparing(Changeset::getDate)))
+      .orElse(null);
   }
 
   @Override
@@ -63,35 +50,28 @@ public class ScmInfoImpl implements ScmInfo {
 
   @Override
   public Changeset getChangesetForLine(int lineNumber) {
-    checkArgument(lineNumber > 0 && lineNumber <= lineChangesets.length, "There's no changeset on line %s", lineNumber);
-    return lineChangesets[lineNumber - 1];
+    Changeset changeset = lineChangesets.get(lineNumber);
+    if (changeset != null) {
+      return changeset;
+    }
+    throw new IllegalArgumentException("Line " + lineNumber + " doesn't have a changeset");
   }
 
   @Override
   public boolean hasChangesetForLine(int lineNumber) {
-    return lineNumber <= lineChangesets.length;
+    return lineChangesets.containsKey(lineNumber);
   }
 
   @Override
-  public Iterable<Changeset> getAllChangesets() {
-    return asList(lineChangesets);
+  public Map<Integer, Changeset> getAllChangesets() {
+    return lineChangesets;
   }
 
   @Override
   public String toString() {
     return "ScmInfoImpl{" +
       "latestChangeset=" + latestChangeset +
-      ", lineChangesets=" + Arrays.toString(lineChangesets) +
+      ", lineChangesets=" + lineChangesets +
       '}';
   }
-
-  private enum CheckNotNull implements Predicate<Changeset> {
-    INSTANCE;
-
-    @Override
-    public boolean apply(@Nullable Changeset input) {
-      checkState(input != null, "Null changeset are not allowed");
-      return true;
-    }
-  }
 }
index a120a4882a3a739b6b5a99a645db5246aa8a5f65..e2a751615c3475e0ac5e3ec5d6254b2ebbafce95 100644 (file)
@@ -38,20 +38,22 @@ public class ScmLineReader implements LineReader {
 
   @Override
   public void read(DbFileSources.Line.Builder lineBuilder) {
-    Changeset changeset = scmReport.getChangesetForLine(lineBuilder.getLine());
-    String author = changeset.getAuthor();
-    if (author != null) {
-      lineBuilder.setScmAuthor(author);
-    }
-    String revision = changeset.getRevision();
-    if (revision != null) {
-      lineBuilder.setScmRevision(revision);
-    }
-    lineBuilder.setScmDate(changeset.getDate());
-    updateLatestChange(changeset);
+    if (scmReport.hasChangesetForLine(lineBuilder.getLine())) {
+      Changeset changeset = scmReport.getChangesetForLine(lineBuilder.getLine());
+      String author = changeset.getAuthor();
+      if (author != null) {
+        lineBuilder.setScmAuthor(author);
+      }
+      String revision = changeset.getRevision();
+      if (revision != null) {
+        lineBuilder.setScmRevision(revision);
+      }
+      lineBuilder.setScmDate(changeset.getDate());
+      updateLatestChange(changeset);
 
-    if (revision != null) {
-      updateLatestChangeWithRevision(changeset);
+      if (revision != null) {
+        updateLatestChangeWithRevision(changeset);
+      }
     }
   }
 
index e708f4fc23271851d7e4559fa9ca69c32688809b..5e7b8d74bc7ff4004244071e8ff1add24d394ef7 100644 (file)
@@ -35,6 +35,7 @@ import org.sonar.server.computation.task.projectanalysis.batch.BatchReportReader
 import org.sonar.server.computation.task.projectanalysis.component.Component;
 import org.sonar.server.computation.task.projectanalysis.component.PathAwareCrawler;
 import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
+import org.sonar.server.computation.task.projectanalysis.formula.Counter;
 import org.sonar.server.computation.task.projectanalysis.formula.CounterInitializationContext;
 import org.sonar.server.computation.task.projectanalysis.formula.CreateMeasureContext;
 import org.sonar.server.computation.task.projectanalysis.formula.Formula;
@@ -226,7 +227,7 @@ public class NewCoverageMeasuresStep implements ComputationStep {
     }
   }
 
-  public static final class NewCoverageCounter implements org.sonar.server.computation.task.projectanalysis.formula.Counter<NewCoverageCounter> {
+  public static final class NewCoverageCounter implements Counter<NewCoverageCounter> {
     private final IntValue newLines = new IntValue();
     private final IntValue newCoveredLines = new IntValue();
     private final IntValue newConditions = new IntValue();
@@ -271,8 +272,9 @@ public class NewCoverageMeasuresStep implements ComputationStep {
         int hits = entry.getValue();
         int conditions = (Integer) ObjectUtils.defaultIfNull(conditionsByLine.get(lineId), 0);
         int coveredConditions = (Integer) ObjectUtils.defaultIfNull(coveredConditionsByLine.get(lineId), 0);
-        long date = componentScm.getChangesetForLine(lineId).getDate();
-        analyze(context.getPeriod(), date, hits, conditions, coveredConditions);
+        if (componentScm.hasChangesetForLine(lineId)) {
+          analyze(context.getPeriod(), componentScm.getChangesetForLine(lineId).getDate(), hits, conditions, coveredConditions);
+        }
       }
     }
 
@@ -283,10 +285,7 @@ public class NewCoverageMeasuresStep implements ComputationStep {
       return Collections.emptyMap();
     }
 
-    void analyze(Period period, @Nullable Long lineDate, int hits, int conditions, int coveredConditions) {
-      if (lineDate == null) {
-        return;
-      }
+    void analyze(Period period, long lineDate, int hits, int conditions, int coveredConditions) {
       if (isLineInPeriod(lineDate, period)) {
         incrementLines(hits);
         incrementConditions(conditions, coveredConditions);
index 4b0e0c92478227d4ab1468d0151a259160c3b583..94881754f28732ddc4b8b7bdcc3054ed9ccad989 100644 (file)
@@ -21,11 +21,9 @@ package org.sonar.server.computation.task.projectanalysis.step;
 
 import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.stream.IntStream;
-import java.util.stream.StreamSupport;
 import org.sonar.server.computation.task.projectanalysis.component.Component;
 import org.sonar.server.computation.task.projectanalysis.component.PathAwareCrawler;
 import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
@@ -66,7 +64,7 @@ public class NewSizeMeasuresStep implements ComputationStep {
   private final NewDuplicationFormula duplicationFormula;
 
   public NewSizeMeasuresStep(TreeRootHolder treeRootHolder, PeriodHolder periodHolder, MetricRepository metricRepository, MeasureRepository measureRepository,
-                             ScmInfoRepository scmInfoRepository, DuplicationRepository duplicationRepository) {
+    ScmInfoRepository scmInfoRepository, DuplicationRepository duplicationRepository) {
     this.treeRootHolder = treeRootHolder;
     this.periodHolder = periodHolder;
     this.metricRepository = metricRepository;
@@ -128,7 +126,7 @@ public class NewSizeMeasuresStep implements ComputationStep {
     }
 
     private void initNewLines(ScmInfo scmInfo, Period period) {
-      StreamSupport.stream(scmInfo.getAllChangesets().spliterator(), false)
+      scmInfo.getAllChangesets().values().stream()
         .filter(changeset -> isLineInPeriod(changeset, period))
         .forEach(changeset -> newLines.increment(1));
     }
@@ -162,7 +160,7 @@ public class NewSizeMeasuresStep implements ComputationStep {
     private DuplicationCounters(ScmInfo scmInfo, Period period) {
       this.scmInfo = scmInfo;
       this.period = period;
-      this.lineCounts = new HashSet<>(Iterables.size(scmInfo.getAllChangesets()));
+      this.lineCounts = new HashSet<>();
     }
 
     void addBlock(TextBlock textBlock) {
index ad42fdf07aa03b87c001a1b74ee8a410beeeed37..b4c98156d1f78dadb4deeb8f57db1449099d51b8 100644 (file)
@@ -357,6 +357,7 @@ public class IssueCreationDateCalculatorTest {
   private void withScmAt(int line, long blame) {
     createMockScmInfo();
     Changeset changeset = Changeset.newChangesetBuilder().setDate(blame).setRevision("1").build();
+    when(scmInfo.hasChangesetForLine(line)).thenReturn(true);
     when(scmInfo.getChangesetForLine(line)).thenReturn(changeset);
   }
 
index ed8467252f7986d330209a357dbc58423707f78d..a3b9865287420d65afbbe8a57285c9be0a0b5f57 100644 (file)
@@ -23,11 +23,8 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.sonar.db.protobuf.DbFileSources;
-import org.sonar.server.computation.task.projectanalysis.component.Component;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.guava.api.Assertions.assertThat;
-import static org.sonar.server.computation.task.projectanalysis.component.ReportComponent.builder;
 import static org.sonar.server.source.index.FileSourceTesting.newFakeData;
 
 public class DbScmInfoTest {
@@ -35,12 +32,9 @@ public class DbScmInfoTest {
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
-  static final int FILE_REF = 1;
-  static final Component FILE = builder(Component.Type.FILE, FILE_REF).setKey("FILE_KEY").setUuid("FILE_UUID").build();
-
   @Test
   public void create_scm_info_with_some_changesets() throws Exception {
-    ScmInfo scmInfo = DbScmInfo.create(FILE, newFakeData(10).build().getLinesList()).get();
+    ScmInfo scmInfo = DbScmInfo.create(newFakeData(10).build().getLinesList()).get();
 
     assertThat(scmInfo.getAllChangesets()).hasSize(10);
   }
@@ -54,7 +48,7 @@ public class DbScmInfoTest {
     addLine(fileDataBuilder, 4, "john", 123456789L, "rev-1");
     fileDataBuilder.build();
 
-    ScmInfo scmInfo = DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get();
+    ScmInfo scmInfo = DbScmInfo.create(fileDataBuilder.getLinesList()).get();
 
     assertThat(scmInfo.getAllChangesets()).hasSize(4);
 
@@ -65,14 +59,14 @@ public class DbScmInfoTest {
   }
 
   @Test
-  public void return_same_changeset_objects_for_lines_with_same_revision() {
+  public void return_same_changeset_objects_for_lines_with_same_fields() throws Exception {
     DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder();
     fileDataBuilder.addLinesBuilder().setScmRevision("rev").setScmDate(65L).setLine(1);
     fileDataBuilder.addLinesBuilder().setScmRevision("rev2").setScmDate(6541L).setLine(2);
     fileDataBuilder.addLinesBuilder().setScmRevision("rev1").setScmDate(6541L).setLine(3);
-    fileDataBuilder.addLinesBuilder().setScmRevision("rev").setScmDate(6542L).setLine(4);
+    fileDataBuilder.addLinesBuilder().setScmRevision("rev").setScmDate(65L).setLine(4);
 
-    ScmInfo scmInfo = DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get();
+    ScmInfo scmInfo = DbScmInfo.create(fileDataBuilder.getLinesList()).get();
 
     assertThat(scmInfo.getAllChangesets()).hasSize(4);
 
@@ -88,7 +82,7 @@ public class DbScmInfoTest {
     addLine(fileDataBuilder, 3, "john", 123456789L, "rev-1");
     fileDataBuilder.build();
 
-    ScmInfo scmInfo = DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get();
+    ScmInfo scmInfo = DbScmInfo.create(fileDataBuilder.getLinesList()).get();
 
     Changeset latestChangeset = scmInfo.getLatestChangeset();
     assertThat(latestChangeset.getAuthor()).isEqualTo("henry");
@@ -101,60 +95,40 @@ public class DbScmInfoTest {
     DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder();
     fileDataBuilder.addLinesBuilder().setLine(1);
 
-    assertThat(DbScmInfo.create(FILE, fileDataBuilder.getLinesList())).isAbsent();
-  }
-
-  @Test
-  public void return_absent_dsm_info_when_changeset_line_has_both_revision_and_date() {
-    DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder();
-    fileDataBuilder.addLinesBuilder().setLine(1);
-    fileDataBuilder.addLinesBuilder().setScmDate(6541L).setLine(2);
-    fileDataBuilder.addLinesBuilder().setScmRevision("rev").setLine(3);
-    fileDataBuilder.addLinesBuilder().setScmAuthor("author").setLine(4);
-
-    assertThat(DbScmInfo.create(FILE, fileDataBuilder.getLinesList())).isAbsent();
+    assertThat(DbScmInfo.create(fileDataBuilder.getLinesList())).isNotPresent();
   }
 
   @Test
-  public void fail_with_ISE_when_changeset_has_no_field() {
-    thrown.expect(IllegalStateException.class);
-    thrown.expectMessage("Partial scm information stored in DB for component 'ReportComponent{ref=1, key='FILE_KEY', type=FILE}'. " +
-      "Not all lines have SCM info. Can not proceed");
-
+  public void should_support_some_lines_not_having_scm_info() throws Exception {
     DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder();
     fileDataBuilder.addLinesBuilder().setScmRevision("rev").setScmDate(543L).setLine(1);
     fileDataBuilder.addLinesBuilder().setLine(2);
     fileDataBuilder.build();
 
-    DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get().getAllChangesets();
+    assertThat(DbScmInfo.create(fileDataBuilder.getLinesList()).get().getAllChangesets()).hasSize(1);
   }
 
   @Test
-  public void fail_with_ISE_when_changeset_has_only_revision_field() {
-    thrown.expect(IllegalStateException.class);
-    thrown.expectMessage("Partial scm information stored in DB for component 'ReportComponent{ref=1, key='FILE_KEY', type=FILE}'. " +
-      "Not all lines have SCM info. Can not proceed");
-
+  public void filter_out_entries_without_date() throws Exception {
     DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder();
     fileDataBuilder.addLinesBuilder().setScmRevision("rev").setScmDate(555L).setLine(1);
     fileDataBuilder.addLinesBuilder().setScmRevision("rev-1").setLine(2);
     fileDataBuilder.build();
 
-    DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get().getAllChangesets();
+    assertThat(DbScmInfo.create(fileDataBuilder.getLinesList()).get().getAllChangesets()).hasSize(1);
+    assertThat(DbScmInfo.create(fileDataBuilder.getLinesList()).get().getChangesetForLine(1).getRevision()).isEqualTo("rev");
   }
 
   @Test
-  public void fail_with_ISE_when_changeset_has_only_author_field() {
-    thrown.expect(IllegalStateException.class);
-    thrown.expectMessage("Partial scm information stored in DB for component 'ReportComponent{ref=1, key='FILE_KEY', type=FILE}'. " +
-      "Not all lines have SCM info. Can not proceed");
-
+  public void should_support_having_no_author() throws Exception {
     DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder();
+    // gets filtered out
     fileDataBuilder.addLinesBuilder().setScmAuthor("John").setLine(1);
     fileDataBuilder.addLinesBuilder().setScmRevision("rev").setScmDate(555L).setLine(2);
     fileDataBuilder.build();
 
-    DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get().getAllChangesets();
+    assertThat(DbScmInfo.create(fileDataBuilder.getLinesList()).get().getAllChangesets()).hasSize(1);
+    assertThat(DbScmInfo.create(fileDataBuilder.getLinesList()).get().getChangesetForLine(2).getAuthor()).isNull();
   }
 
   private static void addLine(DbFileSources.Data.Builder dataBuilder, Integer line, String author, Long date, String revision) {
index 36ad61303dddd63d0dfc3c4166f3e9b7b16c0d4b..d5efe051080684bfa0de5f9326640f8a118b41f7 100644 (file)
  */
 package org.sonar.server.computation.task.projectanalysis.scm;
 
-import com.google.common.collect.Lists;
+import com.google.common.collect.ImmutableMap;
+import java.util.Collections;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
-import static com.google.common.collect.Lists.newArrayList;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
 
 public class ScmInfoImplTest {
 
@@ -48,7 +49,7 @@ public class ScmInfoImplTest {
   public void get_all_changesets() {
     ScmInfo scmInfo = createScmInfoWithTwoChangestOnFourLines();
 
-    assertThat(scmInfo.getAllChangesets()).containsOnly(CHANGESET_1, CHANGESET_2, CHANGESET_1, CHANGESET_1);
+    assertThat(scmInfo.getAllChangesets()).containsOnly(entry(1, CHANGESET_1), entry(2, CHANGESET_2), entry(3, CHANGESET_1), entry(4, CHANGESET_1));
   }
 
   @Test
@@ -81,7 +82,7 @@ public class ScmInfoImplTest {
     thrown.expect(IllegalStateException.class);
     thrown.expectMessage("A ScmInfo must have at least one Changeset and does not support any null one");
 
-    new ScmInfoImpl(Lists.newArrayList());
+    new ScmInfoImpl(Collections.emptyMap());
   }
 
   @Test
@@ -129,7 +130,7 @@ public class ScmInfoImplTest {
       .setRevision("rev-2")
       .build();
 
-    ScmInfo scmInfo = new ScmInfoImpl(newArrayList(changeset1, changeset2, changeset1, changeset1));
+    ScmInfo scmInfo = new ScmInfoImpl(ImmutableMap.of(1, changeset1, 2, changeset2, 3, changeset1, 4, changeset1));
     return scmInfo;
   }
 }
index 6f1cc9f7dc5ec753de2ef638072d886b102831a3..516e780c50b84366f33270a001d6ff4098520cf5 100644 (file)
 package org.sonar.server.computation.task.projectanalysis.scm;
 
 import com.google.common.base.Optional;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 import org.junit.rules.ExternalResource;
 import org.sonar.server.computation.task.projectanalysis.component.Component;
 
@@ -45,7 +46,8 @@ public class ScmInfoRepositoryRule extends ExternalResource implements ScmInfoRe
   }
 
   public ScmInfoRepositoryRule setScmInfo(int fileRef, Changeset... changesetList) {
-    scmInfoByFileRef.put(fileRef, new ScmInfoImpl(Arrays.asList(changesetList)));
+    Map<Integer, Changeset> changeset = IntStream.rangeClosed(1, changesetList.length).boxed().collect(Collectors.toMap(x -> x, x -> changesetList[x-1]));
+    scmInfoByFileRef.put(fileRef, new ScmInfoImpl(changeset));
     return this;
   }
 }
index 769d8d3f6dd7f363666039329ccb10d276ed4fe6..d22734747387d555ed5e94179e1a55fd19efe9ae 100644 (file)
  */
 package org.sonar.server.computation.task.projectanalysis.source;
 
-import com.google.common.collect.ImmutableList;
-import java.util.List;
+import com.google.common.collect.ImmutableMap;
+import java.util.Collections;
+import java.util.Map;
 import org.junit.Test;
 import org.sonar.db.protobuf.DbFileSources;
 import org.sonar.server.computation.task.projectanalysis.scm.Changeset;
 import org.sonar.server.computation.task.projectanalysis.scm.ScmInfo;
 import org.sonar.server.computation.task.projectanalysis.scm.ScmInfoImpl;
 
-import static com.google.common.collect.Lists.newArrayList;
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class ScmLineReaderTest {
 
   @Test
   public void set_scm() {
-    ScmInfo scmInfo = new ScmInfoImpl(newArrayList(
+    ScmInfo scmInfo = new ScmInfoImpl(Collections.singletonMap(1,
       Changeset.newChangesetBuilder()
         .setAuthor("john")
         .setDate(123_456_789L)
         .setRevision("rev-1")
-        .build()
-      ));
+        .build()));
 
     ScmLineReader lineScm = new ScmLineReader(scmInfo);
 
@@ -54,12 +53,11 @@ public class ScmLineReaderTest {
 
   @Test
   public void set_scm_with_minim_fields() {
-    ScmInfo scmInfo = new ScmInfoImpl(newArrayList(
+    ScmInfo scmInfo = new ScmInfoImpl(Collections.singletonMap(1,
       Changeset.newChangesetBuilder()
         .setDate(123_456_789L)
         .setRevision("rev-1")
-        .build()
-      ));
+        .build()));
 
     ScmLineReader lineScm = new ScmLineReader(scmInfo);
 
@@ -103,8 +101,16 @@ public class ScmLineReaderTest {
     readLineAndAssertLatestChangeDate(lineScm, 8, changeset2);
   }
 
-  private static List<Changeset> setup8LinesChangeset(Changeset changeset0, Changeset changeset1, Changeset changeset2) {
-    return ImmutableList.of(changeset0, changeset1, changeset1, changeset2, changeset0, changeset1, changeset0, changeset0);
+  private static Map<Integer, Changeset> setup8LinesChangeset(Changeset changeset0, Changeset changeset1, Changeset changeset2) {
+    return ImmutableMap.<Integer, Changeset>builder()
+      .put(1, changeset0)
+      .put(2, changeset1)
+      .put(3, changeset1)
+      .put(4, changeset2)
+      .put(5, changeset0)
+      .put(6, changeset1)
+      .put(7, changeset0)
+      .put(8, changeset0).build();
   }
 
   private void readLineAndAssertLatestChangeDate(ScmLineReader lineScm, int line, Changeset expectedChangeset) {
index 531f66c6b181fa2120d193b9863fabe0694398ac..fbe3fc3d10563c5cc617f393d606d842914f0ac7 100644 (file)
@@ -183,6 +183,39 @@ public class PersistFileSourcesStepTest extends BaseStepTest {
     assertThat(data.getLines(0).getScmRevision()).isEqualTo("rev-1");
   }
 
+  @Test
+  public void persist_scm_some_lines() {
+    initBasicReport(3);
+    scmInfoRepository.setScmInfo(FILE1_REF, Changeset.newChangesetBuilder()
+      .setAuthor("john")
+      .setDate(123456789L)
+      .setRevision("rev-1")
+      .build());
+
+    underTest.execute();
+
+    assertThat(dbTester.countRowsOfTable("file_sources")).isEqualTo(1);
+    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectSourceByFileUuid(session, FILE1_UUID);
+
+    assertThat(fileSourceDto.getRevision()).isEqualTo("rev-1");
+
+    DbFileSources.Data data = fileSourceDto.getSourceData();
+
+    assertThat(data.getLinesList()).hasSize(3);
+
+    assertThat(data.getLines(0).getScmAuthor()).isEqualTo("john");
+    assertThat(data.getLines(0).getScmDate()).isEqualTo(123456789L);
+    assertThat(data.getLines(0).getScmRevision()).isEqualTo("rev-1");
+
+    assertThat(data.getLines(1).getScmAuthor()).isEmpty();
+    assertThat(data.getLines(1).getScmDate()).isEqualTo(0);
+    assertThat(data.getLines(1).getScmRevision()).isEmpty();
+
+    assertThat(data.getLines(2).getScmAuthor()).isEmpty();
+    assertThat(data.getLines(2).getScmDate()).isEqualTo(0);
+    assertThat(data.getLines(2).getScmRevision()).isEmpty();
+  }
+
   @Test
   public void persist_highlighting() {
     initBasicReport(1);
index d9d2e512908b0b5e9026e493ce0ef6ed8034e01f..649b92311cb247f13517e501cfac54013bc8b082 100644 (file)
@@ -365,7 +365,7 @@ public final class MoreCollectors {
       joiner::join);
   }
 
-  private static <R> BinaryOperator<R> mergeNotSupportedMerger() {
+  public static <R> BinaryOperator<R> mergeNotSupportedMerger() {
     return (m1, m2) -> {
       throw new IllegalStateException("Parallel processing is not supported");
     };