Skip to content

Commit

Permalink
SONAR-10257 Generate SCM Info for changed lines
Browse files Browse the repository at this point in the history
  • Loading branch information
dbmeneses committed Feb 7, 2018
1 parent d5481c2 commit ad83f03
Show file tree
Hide file tree
Showing 15 changed files with 927 additions and 189 deletions.
Expand Up @@ -114,6 +114,7 @@
import org.sonar.server.computation.task.projectanalysis.scm.ScmInfoRepositoryImpl; import org.sonar.server.computation.task.projectanalysis.scm.ScmInfoRepositoryImpl;
import org.sonar.server.computation.task.projectanalysis.source.LastCommitVisitor; import org.sonar.server.computation.task.projectanalysis.source.LastCommitVisitor;
import org.sonar.server.computation.task.projectanalysis.source.SourceHashRepositoryImpl; import org.sonar.server.computation.task.projectanalysis.source.SourceHashRepositoryImpl;
import org.sonar.server.computation.task.projectanalysis.source.SourceLinesDiffImpl;
import org.sonar.server.computation.task.projectanalysis.source.SourceLinesRepositoryImpl; import org.sonar.server.computation.task.projectanalysis.source.SourceLinesRepositoryImpl;
import org.sonar.server.computation.task.projectanalysis.step.ReportComputationSteps; import org.sonar.server.computation.task.projectanalysis.step.ReportComputationSteps;
import org.sonar.server.computation.task.projectanalysis.step.SmallChangesetQualityGateSpecialCase; import org.sonar.server.computation.task.projectanalysis.step.SmallChangesetQualityGateSpecialCase;
Expand Down Expand Up @@ -189,6 +190,7 @@ private static List<Object> componentClasses() {
EvaluationResultTextConverterImpl.class, EvaluationResultTextConverterImpl.class,
SourceLinesRepositoryImpl.class, SourceLinesRepositoryImpl.class,
SourceHashRepositoryImpl.class, SourceHashRepositoryImpl.class,
SourceLinesDiffImpl.class,
ScmInfoRepositoryImpl.class, ScmInfoRepositoryImpl.class,
ScmInfoDbLoader.class, ScmInfoDbLoader.class,
DuplicationRepositoryImpl.class, DuplicationRepositoryImpl.class,
Expand Down
Expand Up @@ -36,12 +36,14 @@
class DbScmInfo implements ScmInfo { class DbScmInfo implements ScmInfo {


private final ScmInfo delegate; private final ScmInfo delegate;
private final String fileHash;


private DbScmInfo(ScmInfo delegate) { private DbScmInfo(ScmInfo delegate, String fileHash) {
this.delegate = delegate; this.delegate = delegate;
this.fileHash = fileHash;
} }


static Optional<ScmInfo> create(Iterable<DbFileSources.Line> lines) { public static Optional<DbScmInfo> create(Iterable<DbFileSources.Line> lines, String fileHash) {
LineToChangeset lineToChangeset = new LineToChangeset(); LineToChangeset lineToChangeset = new LineToChangeset();
Map<Integer, Changeset> lineChanges = new LinkedHashMap<>(); Map<Integer, Changeset> lineChanges = new LinkedHashMap<>();


Expand All @@ -55,7 +57,11 @@ static Optional<ScmInfo> create(Iterable<DbFileSources.Line> lines) {
if (lineChanges.isEmpty()) { if (lineChanges.isEmpty()) {
return Optional.empty(); return Optional.empty();
} }
return Optional.of(new DbScmInfo(new ScmInfoImpl(lineChanges))); return Optional.of(new DbScmInfo(new ScmInfoImpl(lineChanges), fileHash));
}

public String fileHash() {
return fileHash;
} }


@Override @Override
Expand Down
@@ -0,0 +1,82 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.server.computation.task.projectanalysis.scm;

import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static com.google.common.base.Preconditions.checkState;

public class GeneratedScmInfo implements ScmInfo {
private final ScmInfoImpl delegate;

public GeneratedScmInfo(Map<Integer, Changeset> changesets) {
delegate = new ScmInfoImpl(changesets);
}

public static ScmInfo create(long analysisDate, Set<Integer> lines) {
checkState(!lines.isEmpty(), "No changesets");

Changeset changeset = Changeset.newChangesetBuilder()
.setDate(analysisDate)
.build();
Map<Integer, Changeset> changesets = lines.stream()
.collect(Collectors.toMap(x -> x, i -> changeset));
return new GeneratedScmInfo(changesets);
}

public static ScmInfo create(long analysisDate, Set<Integer> lines, ScmInfo dbScmInfo) {
checkState(!lines.isEmpty(), "No changesets");

Changeset changeset = Changeset.newChangesetBuilder()
.setDate(analysisDate)
.build();
Map<Integer, Changeset> changesets = lines.stream()
.collect(Collectors.toMap(x -> x, i -> changeset));

dbScmInfo.getAllChangesets().entrySet().stream()
.filter(e -> !lines.contains(e.getKey()))
.forEach(e -> changesets.put(e.getKey(), e.getValue()));

return new GeneratedScmInfo(changesets);
}

@Override
public Changeset getLatestChangeset() {
return delegate.getLatestChangeset();
}

@Override
public Changeset getChangesetForLine(int lineNumber) {
return delegate.getChangesetForLine(lineNumber);
}

@Override
public boolean hasChangesetForLine(int lineNumber) {
return delegate.hasChangesetForLine(lineNumber);
}

@Override
public Map<Integer, Changeset> getAllChangesets() {
return delegate.getAllChangesets();
}

}
Expand Up @@ -28,41 +28,34 @@
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder; import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.server.computation.task.projectanalysis.analysis.Branch; import org.sonar.server.computation.task.projectanalysis.analysis.Branch;
import org.sonar.server.computation.task.projectanalysis.component.Component; import org.sonar.server.computation.task.projectanalysis.component.Component;
import org.sonar.server.computation.task.projectanalysis.component.Component.Status;
import org.sonar.server.computation.task.projectanalysis.component.MergeBranchComponentUuids; import org.sonar.server.computation.task.projectanalysis.component.MergeBranchComponentUuids;
import org.sonar.server.computation.task.projectanalysis.scm.ScmInfoRepositoryImpl.NoScmInfo;
import org.sonar.server.computation.task.projectanalysis.source.SourceHashRepository;


public class ScmInfoDbLoader { public class ScmInfoDbLoader {
private static final Logger LOGGER = Loggers.get(ScmInfoDbLoader.class); private static final Logger LOGGER = Loggers.get(ScmInfoDbLoader.class);


private final AnalysisMetadataHolder analysisMetadataHolder; private final AnalysisMetadataHolder analysisMetadataHolder;
private final DbClient dbClient; private final DbClient dbClient;
private final SourceHashRepository sourceHashRepository;
private final MergeBranchComponentUuids mergeBranchComponentUuid; private final MergeBranchComponentUuids mergeBranchComponentUuid;


public ScmInfoDbLoader(AnalysisMetadataHolder analysisMetadataHolder, DbClient dbClient, public ScmInfoDbLoader(AnalysisMetadataHolder analysisMetadataHolder, DbClient dbClient, MergeBranchComponentUuids mergeBranchComponentUuid) {
SourceHashRepository sourceHashRepository, MergeBranchComponentUuids mergeBranchComponentUuid) {
this.analysisMetadataHolder = analysisMetadataHolder; this.analysisMetadataHolder = analysisMetadataHolder;
this.dbClient = dbClient; this.dbClient = dbClient;
this.sourceHashRepository = sourceHashRepository;
this.mergeBranchComponentUuid = mergeBranchComponentUuid; this.mergeBranchComponentUuid = mergeBranchComponentUuid;
} }


public ScmInfo getScmInfoFromDb(Component file) { public Optional<DbScmInfo> getScmInfo(Component file) {
Optional<String> uuid = getFileUUid(file); Optional<String> uuid = getFileUUid(file);

if (!uuid.isPresent()) { if (!uuid.isPresent()) {
return NoScmInfo.INSTANCE; return Optional.empty();
} }


LOGGER.trace("Reading SCM info from db for file '{}'", uuid.get()); LOGGER.trace("Reading SCM info from db for file '{}'", uuid.get());
try (DbSession dbSession = dbClient.openSession(false)) { try (DbSession dbSession = dbClient.openSession(false)) {
FileSourceDto dto = dbClient.fileSourceDao().selectSourceByFileUuid(dbSession, uuid.get()); FileSourceDto dto = dbClient.fileSourceDao().selectSourceByFileUuid(dbSession, uuid.get());
if (dto == null || !isDtoValid(file, dto)) { if (dto == null) {
return NoScmInfo.INSTANCE; return Optional.empty();
} }
return DbScmInfo.create(dto.getSourceData().getLinesList()).orElse(NoScmInfo.INSTANCE); return DbScmInfo.create(dto.getSourceData().getLinesList(), dto.getSrcHash());
} }
} }


Expand All @@ -80,10 +73,4 @@ private Optional<String> getFileUUid(Component file) {
return Optional.empty(); return Optional.empty();
} }


private boolean isDtoValid(Component file, FileSourceDto dto) {
if (file.getStatus() == Status.SAME) {
return true;
}
return sourceHashRepository.getRawSourceHash(file).equals(dto.getSrcHash());
}
} }
Expand Up @@ -24,6 +24,7 @@
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.annotation.CheckForNull; import javax.annotation.CheckForNull;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static com.google.common.base.Preconditions.checkState;


@Immutable @Immutable
public class ScmInfoImpl implements ScmInfo { public class ScmInfoImpl implements ScmInfo {
Expand All @@ -33,6 +34,7 @@ public class ScmInfoImpl implements ScmInfo {
private final Map<Integer, Changeset> lineChangesets; private final Map<Integer, Changeset> lineChangesets;


public ScmInfoImpl(Map<Integer, Changeset> lineChangesets) { public ScmInfoImpl(Map<Integer, Changeset> lineChangesets) {
checkState(!lineChangesets.isEmpty(), "A ScmInfo must have at least one Changeset and does not support any null one");
this.lineChangesets = lineChangesets; this.lineChangesets = lineChangesets;
this.latestChangeset = computeLatestChangeset(lineChangesets); this.latestChangeset = computeLatestChangeset(lineChangesets);
} }
Expand All @@ -54,7 +56,7 @@ public Changeset getChangesetForLine(int lineNumber) {
if (changeset != null) { if (changeset != null) {
return changeset; return changeset;
} }
throw new IllegalArgumentException("Line " + lineNumber + " doesn't have a changeset"); throw new IllegalArgumentException("There's no changeset on line " + lineNumber);
} }


@Override @Override
Expand Down
Expand Up @@ -19,102 +19,112 @@
*/ */
package org.sonar.server.computation.task.projectanalysis.scm; package org.sonar.server.computation.task.projectanalysis.scm;


import com.google.common.base.Optional;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.log.Loggers;
import org.sonar.scanner.protocol.output.ScannerReport; import org.sonar.scanner.protocol.output.ScannerReport;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.server.computation.task.projectanalysis.batch.BatchReportReader; 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.Component;
import org.sonar.server.computation.task.projectanalysis.component.Component.Status;
import org.sonar.server.computation.task.projectanalysis.source.SourceHashRepository;
import org.sonar.server.computation.task.projectanalysis.source.SourceLinesDiff;


import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;


public class ScmInfoRepositoryImpl implements ScmInfoRepository { public class ScmInfoRepositoryImpl implements ScmInfoRepository {


private static final Logger LOGGER = Loggers.get(ScmInfoRepositoryImpl.class); private static final Logger LOGGER = Loggers.get(ScmInfoRepositoryImpl.class);


private final BatchReportReader batchReportReader; private final BatchReportReader scannerReportReader;
private final Map<Component, ScmInfo> scmInfoCache = new HashMap<>(); private final Map<Component, Optional<ScmInfo>> scmInfoCache = new HashMap<>();
private final ScmInfoDbLoader scmInfoDbLoader; private final ScmInfoDbLoader scmInfoDbLoader;

private final AnalysisMetadataHolder analysisMetadata;
public ScmInfoRepositoryImpl(BatchReportReader batchReportReader, ScmInfoDbLoader scmInfoDbLoader) { private final SourceLinesDiff sourceLinesDiff;
this.batchReportReader = batchReportReader; private final SourceHashRepository sourceHashRepository;

public ScmInfoRepositoryImpl(BatchReportReader scannerReportReader, AnalysisMetadataHolder analysisMetadata, ScmInfoDbLoader scmInfoDbLoader,
SourceLinesDiff sourceLinesDiff, SourceHashRepository sourceHashRepository) {
this.scannerReportReader = scannerReportReader;
this.analysisMetadata = analysisMetadata;
this.scmInfoDbLoader = scmInfoDbLoader; this.scmInfoDbLoader = scmInfoDbLoader;
this.sourceLinesDiff = sourceLinesDiff;
this.sourceHashRepository = sourceHashRepository;
} }


@Override @Override
public Optional<ScmInfo> getScmInfo(Component component) { public com.google.common.base.Optional<ScmInfo> getScmInfo(Component component) {
requireNonNull(component, "Component cannot be null"); requireNonNull(component, "Component cannot be null");
return initializeScmInfoForComponent(component);
}


private Optional<ScmInfo> initializeScmInfoForComponent(Component component) {
if (component.getType() != Component.Type.FILE) { if (component.getType() != Component.Type.FILE) {
return Optional.absent(); return com.google.common.base.Optional.absent();
}
ScmInfo scmInfo = scmInfoCache.get(component);
if (scmInfo != null) {
return optionalOf(scmInfo);
} }


scmInfo = getScmInfoForComponent(component); return toGuavaOptional(scmInfoCache.computeIfAbsent(component, this::getScmInfoForComponent));
scmInfoCache.put(component, scmInfo);
return optionalOf(scmInfo);
} }


private static Optional<ScmInfo> optionalOf(ScmInfo scmInfo) { private static com.google.common.base.Optional<ScmInfo> toGuavaOptional(Optional<ScmInfo> scmInfo) {
if (scmInfo == NoScmInfo.INSTANCE) { return com.google.common.base.Optional.fromNullable(scmInfo.orElse(null));
return Optional.absent();
}
return Optional.of(scmInfo);
} }


private ScmInfo getScmInfoForComponent(Component component) { private Optional<ScmInfo> getScmInfoForComponent(Component component) {
ScannerReport.Changesets changesets = batchReportReader.readChangesets(component.getReportAttributes().getRef()); ScannerReport.Changesets changesets = scannerReportReader.readChangesets(component.getReportAttributes().getRef());
if (changesets == null) {
LOGGER.trace("No SCM info for file '{}'", component.getKey()); if (changesets != null) {
return NoScmInfo.INSTANCE; if (changesets.getChangesetCount() == 0) {
} return generateAndMergeDb(component, changesets.getCopyFromPrevious());
if (changesets.getCopyFromPrevious()) { }
return scmInfoDbLoader.getScmInfoFromDb(component); return getScmInfoFromReport(component, changesets);
} }
return getScmInfoFromReport(component, changesets);
LOGGER.trace("No SCM info for file '{}'", component.getKey());
return generateAndMergeDb(component, false);
} }


private static ScmInfo getScmInfoFromReport(Component file, ScannerReport.Changesets changesets) { private static Optional<ScmInfo> getScmInfoFromReport(Component file, ScannerReport.Changesets changesets) {
LOGGER.trace("Reading SCM info from report for file '{}'", file.getKey()); LOGGER.trace("Reading SCM info from report for file '{}'", file.getKey());
return new ReportScmInfo(changesets); return Optional.of(new ReportScmInfo(changesets));
} }


/** private Optional<ScmInfo> generateScmInfoForAllFile(Component file) {
* Internally used to populate cache when no ScmInfo exist. Set<Integer> newOrChangedLines = IntStream.rangeClosed(1, file.getFileAttributes().getLines()).boxed().collect(Collectors.toSet());
*/ return Optional.of(GeneratedScmInfo.create(analysisMetadata.getAnalysisDate(), newOrChangedLines));
enum NoScmInfo implements ScmInfo { }
INSTANCE {
@Override
public Changeset getLatestChangeset() {
return notImplemented();
}


@Override private ScmInfo removeAuthorAndRevision(ScmInfo info) {
public Changeset getChangesetForLine(int lineNumber) { Map<Integer, Changeset> cleanedScmInfo = info.getAllChangesets().entrySet().stream()
return notImplemented(); .collect(Collectors.toMap(Map.Entry::getKey, e -> removeAuthorAndRevision(e.getValue())));
} return new ScmInfoImpl(cleanedScmInfo);
}


@Override private static Changeset removeAuthorAndRevision(Changeset changeset) {
public boolean hasChangesetForLine(int lineNumber) { return Changeset.newChangesetBuilder().setDate(changeset.getDate()).build();
return notImplemented(); }
}


@Override private Optional<ScmInfo> generateAndMergeDb(Component file, boolean copyFromPrevious) {
public Iterable<Changeset> getAllChangesets() { Optional<DbScmInfo> dbInfoOpt = scmInfoDbLoader.getScmInfo(file);
return notImplemented(); if (!dbInfoOpt.isPresent()) {
} return generateScmInfoForAllFile(file);
}


private <T> T notImplemented() { ScmInfo scmInfo = copyFromPrevious ? dbInfoOpt.get() : removeAuthorAndRevision(dbInfoOpt.get());
throw new UnsupportedOperationException("NoScmInfo does not implement any method"); boolean fileUnchanged = file.getStatus() == Status.SAME && sourceHashRepository.getRawSourceHash(file).equals(dbInfoOpt.get().fileHash());
}
if (fileUnchanged) {
return Optional.of(scmInfo);
} }

// generate date for new/changed lines
Set<Integer> newOrChangedLines = sourceLinesDiff.getNewOrChangedLines(file);
if (newOrChangedLines.isEmpty()) {
return Optional.of(scmInfo);
}
return Optional.of(GeneratedScmInfo.create(analysisMetadata.getAnalysisDate(), newOrChangedLines, scmInfo));
} }

} }

0 comments on commit ad83f03

Please sign in to comment.