import java.util.Map;
/**
- * Represents the Scm information for a specific file.
+ * Represents changeset information for a file. If SCM information is present, it will be the author, revision and date fetched from SCM
+ * for every line. Otherwise, it's a date that corresponds the the analysis date in which the line was modified.
*/
public interface ScmInfo {
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
-import java.util.stream.Collectors;
-import javax.annotation.CheckForNull;
import javax.annotation.concurrent.Immutable;
import static com.google.common.base.Preconditions.checkState;
@Immutable
public class ScmInfoImpl implements ScmInfo {
-
- @CheckForNull
private final Changeset latestChangeset;
private final Map<Integer, Changeset> lineChangesets;
}
private static Changeset computeLatestChangeset(Map<Integer, Changeset> lineChangesets) {
- return lineChangesets.values().stream()
- .collect(Collectors.maxBy(Comparator.comparing(Changeset::getDate)))
- .orElse(null);
+ return lineChangesets.values().stream().max(Comparator.comparingLong(Changeset::getDate))
+ .orElseThrow(() -> new IllegalStateException("Expecting at least one Changeset to be present"));
}
@Override
private Optional<ScmInfo> getScmInfoForComponent(Component component) {
ScannerReport.Changesets changesets = scannerReportReader.readChangesets(component.getReportAttributes().getRef());
- if (changesets != null) {
- if (changesets.getChangesetCount() == 0) {
- return generateAndMergeDb(component, changesets.getCopyFromPrevious());
- }
- return getScmInfoFromReport(component, changesets);
+ if (changesets == null) {
+ LOGGER.trace("No SCM info for file '{}'", component.getKey());
+ // SCM not available. It might have been available before - don't keep author and revision.
+ return generateAndMergeDb(component, false);
}
- LOGGER.trace("No SCM info for file '{}'", component.getKey());
- return generateAndMergeDb(component, false);
+ // will be empty if the flag "copy from previous" is set, or if the file is empty.
+ if (changesets.getChangesetCount() == 0) {
+ return generateAndMergeDb(component, changesets.getCopyFromPrevious());
+ }
+ return getScmInfoFromReport(component, changesets);
}
private static Optional<ScmInfo> getScmInfoFromReport(Component file, ScannerReport.Changesets changesets) {
}
private Optional<ScmInfo> generateScmInfoForAllFile(Component file) {
+ if (file.getFileAttributes().getLines() == 0) {
+ return Optional.empty();
+ }
Set<Integer> newOrChangedLines = IntStream.rangeClosed(1, file.getFileAttributes().getLines()).boxed().collect(Collectors.toSet());
return Optional.of(GeneratedScmInfo.create(analysisMetadata.getAnalysisDate(), newOrChangedLines));
}
return Changeset.newChangesetBuilder().setDate(changeset.getDate()).build();
}
- private Optional<ScmInfo> generateAndMergeDb(Component file, boolean copyFromPrevious) {
+ private Optional<ScmInfo> generateAndMergeDb(Component file, boolean keepAuthorAndRevision) {
Optional<DbScmInfo> dbInfoOpt = scmInfoDbLoader.getScmInfo(file);
if (!dbInfoOpt.isPresent()) {
return generateScmInfoForAllFile(file);
}
- ScmInfo scmInfo = copyFromPrevious ? dbInfoOpt.get() : removeAuthorAndRevision(dbInfoOpt.get());
+ ScmInfo scmInfo = keepAuthorAndRevision ? dbInfoOpt.get() : removeAuthorAndRevision(dbInfoOpt.get());
boolean fileUnchanged = file.getStatus() == Status.SAME && sourceHashRepository.getRawSourceHash(file).equals(dbInfoOpt.get().fileHash());
if (fileUnchanged) {
import org.sonar.server.computation.task.projectanalysis.component.Component;
public interface SourceLinesDiff {
+ /**
+ * Creates a diff between the file in the database and the file in the report using Myers' algorithm, and links matching lines between
+ * both files.
+ * @return an array with one entry for each line in the left side. Those entries point either to a line in the right side, or to 0,
+ * in which case it means the line was added.
+ */
int[] getMatchingLines(Component component);
}
*/
package org.sonar.server.computation.task.projectanalysis.source;
+import difflib.myers.DifferentiationFailedException;
import difflib.myers.MyersDiff;
import difflib.myers.PathNode;
import java.util.List;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
public class SourceLinesDiffFinder {
+ private static final Logger LOG = Loggers.get(SourceLinesDiffFinder.class);
+ private final List<String> left;
+ private final List<String> right;
- private final List<String> database;
- private final List<String> report;
-
- public SourceLinesDiffFinder(List<String> database, List<String> report) {
- this.database = database;
- this.report = report;
+ public SourceLinesDiffFinder(List<String> left, List<String> right) {
+ this.left = left;
+ this.right = right;
}
- /**
- * Creates a diff between the file in the database and the file in the report using Myers' algorithm, and links matching lines between
- * both files.
- * @return an array with one entry for each line in the report. Those entries point either to a line in the database, or to 0,
- * in which case it means the line was added.
- */
public int[] findMatchingLines() {
- int[] index = new int[report.size()];
+ int[] index = new int[right.size()];
- int dbLine = database.size();
- int reportLine = report.size();
+ int dbLine = left.size();
+ int reportLine = right.size();
try {
- PathNode node = MyersDiff.buildPath(database.toArray(), report.toArray());
+ PathNode node = MyersDiff.buildPath(left.toArray(), right.toArray());
while (node.prev != null) {
PathNode prevNode = node.prev;
}
node = prevNode;
}
- } catch (Exception e) {
+ } catch (DifferentiationFailedException e) {
+ LOG.error("Error finding matching lines", e);
return index;
}
return index;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
-import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.indices.TermsLookup;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
.format(DateUtils.DATETIME_FORMAT)
.timeZone(DateTimeZone.forOffsetMillis(system.getDefaultTimeZone().getRawOffset()))
// ES dateHistogram bounds are inclusive while createdBefore parameter is exclusive
- .extendedBounds(new ExtendedBounds(startInclusive ? startTime : startTime + 1, endTime - 1L));
+ .extendedBounds(new ExtendedBounds(startInclusive ? startTime : (startTime + 1), endTime - 1L));
addEffortAggregationIfNeeded(query, dateHistogram);
return Optional.of(dateHistogram);
}
@Test
public void shouldFindNothingWhenContentAreIdentical() {
-
List<String> database = new ArrayList<>();
database.add("line - 0");
database.add("line - 1");
int[] diff = new SourceLinesDiffFinder(database, report).findMatchingLines();
assertThat(diff).containsExactly(1, 2, 3, 4, 5);
-
}
@Test
public void shouldFindNothingWhenContentAreIdentical2() {
-
List<String> database = new ArrayList<>();
database.add("package sample;\n");
database.add("\n");
int[] diff = new SourceLinesDiffFinder(database, report).findMatchingLines();
assertThat(diff).containsExactly(1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 5, 6, 7);
-
}
@Test
public void shouldDetectWhenStartingWithModifiedLines() {
-
List<String> database = new ArrayList<>();
database.add("line - 0");
database.add("line - 1");
int[] diff = new SourceLinesDiffFinder(database, report).findMatchingLines();
assertThat(diff).containsExactly(0, 0, 3, 4);
-
}
@Test
public void shouldDetectWhenEndingWithModifiedLines() {
-
List<String> database = new ArrayList<>();
database.add("line - 0");
database.add("line - 1");
int[] diff = new SourceLinesDiffFinder(database, report).findMatchingLines();
assertThat(diff).containsExactly(1, 2, 0, 0);
-
}
@Test
public void shouldDetectModifiedLinesInMiddleOfTheFile() {
-
List<String> database = new ArrayList<>();
database.add("line - 0");
database.add("line - 1");
int[] diff = new SourceLinesDiffFinder(database, report).findMatchingLines();
assertThat(diff).containsExactly(1, 2, 0, 0, 5, 6);
-
}
@Test
public void shouldDetectNewLinesAtBeginningOfFile() {
-
List<String> database = new ArrayList<>();
database.add("line - 0");
database.add("line - 1");
int[] diff = new SourceLinesDiffFinder(database, report).findMatchingLines();
assertThat(diff).containsExactly(0, 0, 1, 2, 3);
-
}
@Test
public void shouldDetectNewLinesInMiddleOfFile() {
-
List<String> database = new ArrayList<>();
database.add("line - 0");
database.add("line - 1");
int[] diff = new SourceLinesDiffFinder(database, report).findMatchingLines();
assertThat(diff).containsExactly(1, 2, 0, 0, 3, 4);
-
}
@Test
public void shouldDetectNewLinesAtEndOfFile() {
-
List<String> database = new ArrayList<>();
database.add("line - 0");
database.add("line - 1");
int[] diff = new SourceLinesDiffFinder(database, report).findMatchingLines();
assertThat(diff).containsExactly(1, 2, 3, 0, 0);
-
}
@Test
public void shouldIgnoreDeletedLinesAtEndOfFile() {
-
List<String> database = new ArrayList<>();
database.add("line - 0");
database.add("line - 1");
int[] diff = new SourceLinesDiffFinder(database, report).findMatchingLines();
assertThat(diff).containsExactly(1, 2, 3);
-
}
@Test
public void shouldIgnoreDeletedLinesInTheMiddleOfFile() {
-
List<String> database = new ArrayList<>();
database.add("line - 0");
database.add("line - 1");
@Test
public void shouldIgnoreDeletedLinesAtTheStartOfTheFile() {
-
List<String> database = new ArrayList<>();
database.add("line - 0");
database.add("line - 1");
int[] diff = new SourceLinesDiffFinder(database, report).findMatchingLines();
assertThat(diff).containsExactly(3, 4);
-
}
-
}
import java.text.ParseException;
import java.util.Date;
import java.util.Map;
+import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
@ClassRule
public static Orchestrator orchestrator = ORCHESTRATOR;
- private SourceScmWS ws = new SourceScmWS(orchestrator);
+ private SourceScmWS ws;
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Rule
public Tester tester = new Tester(orchestrator);
+ @Before
+ public void setUp() {
+ ws = new SourceScmWS(tester);
+ }
+
@Test
public void two_analysis_without_scm_on_same_file() throws ParseException, IOException {
import java.text.ParseException;
import java.util.Date;
import java.util.Map;
+import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
@ClassRule
public static Orchestrator orchestrator = ORCHESTRATOR;
- private SourceScmWS ws = new SourceScmWS(orchestrator);
+ private SourceScmWS ws;
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Rule
public Tester tester = new Tester(orchestrator);
+ @Before
+ public void setUp() {
+ ws = new SourceScmWS(tester);
+ }
+
@Test
public void without_and_then_with_scm_on_same_file() throws ParseException, IOException {
import java.time.ZoneOffset;
import java.util.Date;
import java.util.Map;
+import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
@ClassRule
public static Orchestrator orchestrator = ORCHESTRATOR;
- private SourceScmWS ws = new SourceScmWS(orchestrator);
+ private SourceScmWS ws;
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Rule
public Tester tester = new Tester(orchestrator);
+ @Before
+ public void setUp() {
+ ws = new SourceScmWS(tester);
+ }
+
@Test
public void with_and_then_without_scm_on_same_file() throws ParseException, IOException {
*/
package org.sonarqube.tests.source;
-import com.sonar.orchestrator.Orchestrator;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import org.sonar.wsclient.jsonsimple.JSONArray;
import org.sonar.wsclient.jsonsimple.JSONObject;
import org.sonar.wsclient.jsonsimple.JSONValue;
+import org.sonarqube.qa.util.Tester;
+import org.sonarqube.ws.client.GetRequest;
public class SourceScmWS {
+ public final Tester tester;
- private final Orchestrator orchestrator;
-
- public SourceScmWS(Orchestrator orchestrator) {
- this.orchestrator = orchestrator;
+ public SourceScmWS(Tester tester) {
+ this.tester = tester;
}
public Map<Integer, LineData> getScmData(String fileKey) throws ParseException {
Map<Integer, LineData> result = new HashMap<>();
- String json = orchestrator.getServer().adminWsClient().get("api/sources/scm", "key", fileKey);
+ String json = tester.asAnonymous().wsClient().wsConnector().call(new GetRequest("api/sources/scm").setParam("key", fileKey)).content();
JSONObject obj = (JSONObject) JSONValue.parse(json);
JSONArray array = (JSONArray) obj.get("scm");
for (Object anArray : array) {
return result;
}
-
}