import java.util.List;
import org.sonar.core.issue.tracking.Tracker;
import org.sonar.core.platform.ContainerPopulator;
-import org.sonar.server.computation.queue.CeTask;
-import org.sonar.server.computation.step.ComputationStepExecutor;
-import org.sonar.server.computation.filesystem.ComputationTempFolderProvider;
import org.sonar.server.computation.analysis.ReportAnalysisMetadataHolder;
import org.sonar.server.computation.batch.BatchReportDirectoryHolderImpl;
import org.sonar.server.computation.batch.BatchReportReaderImpl;
import org.sonar.server.computation.component.SettingsRepositoryImpl;
import org.sonar.server.computation.debt.DebtModelHolderImpl;
import org.sonar.server.computation.event.EventRepositoryImpl;
+import org.sonar.server.computation.filesystem.ComputationTempFolderProvider;
import org.sonar.server.computation.issue.BaseIssuesLoader;
import org.sonar.server.computation.issue.CloseIssuesOnRemovedComponentsVisitor;
import org.sonar.server.computation.issue.ComponentIssuesRepositoryImpl;
import org.sonar.server.computation.qualitygate.QualityGateHolderImpl;
import org.sonar.server.computation.qualitygate.QualityGateServiceImpl;
import org.sonar.server.computation.qualityprofile.ActiveRulesHolderImpl;
+import org.sonar.server.computation.queue.CeTask;
+import org.sonar.server.computation.scm.ScmInfoRepositoryImpl;
import org.sonar.server.computation.source.LastCommitVisitor;
import org.sonar.server.computation.source.SourceLinesRepositoryImpl;
import org.sonar.server.computation.sqale.SqaleMeasuresVisitor;
-import org.sonar.server.computation.sqale.SqaleRatingSettings;
import org.sonar.server.computation.sqale.SqaleNewMeasuresVisitor;
+import org.sonar.server.computation.sqale.SqaleRatingSettings;
+import org.sonar.server.computation.step.ComputationStepExecutor;
import org.sonar.server.computation.step.ComputationSteps;
import org.sonar.server.computation.step.ReportComputationSteps;
import org.sonar.server.view.index.ViewIndex;
QualityGateServiceImpl.class,
EvaluationResultTextConverterImpl.class,
SourceLinesRepositoryImpl.class,
+ ScmInfoRepositoryImpl.class,
// issues
RuleCacheLoader.class,
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.scm;
+
+import java.util.Objects;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public final class Changeset {
+
+ private final String revision;
+ private final long date;
+ @CheckForNull
+ private final String author;
+
+ private Changeset(Builder builder) {
+ this.revision = builder.revision;
+ this.author = builder.author;
+ this.date = builder.date;
+ }
+
+ public static Builder newChangesetBuilder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String revision;
+ private Long date;
+ @CheckForNull
+ private String author;
+
+ private Builder() {
+ // prevents direct instantiation
+ }
+
+ public Builder setRevision(String revision) {
+ this.revision = checkRevision(revision);
+ return this;
+ }
+
+ public Builder setDate(Long date) {
+ this.date = checkDate(date);
+ return this;
+ }
+
+ public Builder setAuthor(@Nullable String author) {
+ this.author = author;
+ return this;
+ }
+
+ public Changeset build() {
+ checkRevision(revision);
+ checkDate(date);
+ return new Changeset(this);
+ }
+
+ private static String checkRevision(String revision){
+ return requireNonNull(revision, "Revision cannot be null");
+ }
+
+ private static long checkDate(Long date){
+ return requireNonNull(date, "Date cannot be null");
+ }
+
+ }
+
+ public String getRevision() {
+ return revision;
+ }
+
+ public long getDate() {
+ return date;
+ }
+
+ @CheckForNull
+ public String getAuthor() {
+ return author;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Changeset changeset = (Changeset) o;
+ if (date != changeset.date) {
+ return false;
+ }
+ if (!revision.equals(changeset.revision)) {
+ return false;
+ }
+ return Objects.equals(author, changeset.author);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(revision, author, date);
+ }
+
+ @Override
+ public String toString() {
+ return "Changeset{" +
+ "revision='" + revision + '\'' +
+ ", author='" + author + '\'' +
+ ", date=" + date +
+ '}';
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.scm;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.db.protobuf.DbFileSources;
+import org.sonar.server.computation.component.Component;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.collect.FluentIterable.from;
+import static com.google.common.collect.Iterables.isEmpty;
+import static java.lang.String.format;
+
+/**
+ * ScmInfo implementation based on the lines stored in DB
+ */
+@Immutable
+class DbScmInfo implements ScmInfo {
+
+ private final ScmInfo delegate;
+
+ private DbScmInfo(ScmInfo delegate) {
+ this.delegate = delegate;
+ }
+
+ static Optional<ScmInfo> create(Component component, Iterable<DbFileSources.Line> lines) {
+ LineToChangeset lineToChangeset = new LineToChangeset();
+ Iterable<Changeset> lineChangesets = from(lines)
+ .transform(lineToChangeset)
+ .filter(notNull())
+ .toList();
+ if (isEmpty(lineChangesets)) {
+ return Optional.absent();
+ }
+ checkState(!lineToChangeset.isEncounteredLineWithoutScmInfo(),
+ format("Partial scm information stored in DB for component '%s'. Not all lines have SCM info. Can not proceed", component));
+ return Optional.<ScmInfo>of(new DbScmInfo(new ScmInfoImpl(lineChangesets)));
+ }
+
+ @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 Iterable<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.
+ */
+ private static class LineToChangeset implements Function<DbFileSources.Line, Changeset> {
+ private boolean encounteredLineWithoutScmInfo = false;
+
+ @Override
+ @Nullable
+ public Changeset apply(@Nonnull DbFileSources.Line input) {
+ if (input.hasScmRevision() || input.hasScmAuthor() || input.hasScmDate()) {
+ return Changeset.newChangesetBuilder()
+ .setRevision(input.getScmRevision())
+ .setAuthor(input.getScmAuthor())
+ .setDate(input.getScmDate())
+ .build();
+ }
+
+ this.encounteredLineWithoutScmInfo = true;
+ return null;
+ }
+
+ public boolean isEncounteredLineWithoutScmInfo() {
+ return encounteredLineWithoutScmInfo;
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.scm;
+
+import com.google.common.base.Function;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.batch.protocol.output.BatchReport;
+
+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;
+
+/**
+ * ScmInfo implementation based on the changeset information from the Report
+ */
+@Immutable
+class ReportScmInfo implements ScmInfo {
+ private final ScmInfo delegate;
+
+ ReportScmInfo(BatchReport.Changesets changesets) {
+ requireNonNull(changesets);
+ this.delegate = convertToScmInfo(changesets);
+ }
+
+ private static ScmInfo convertToScmInfo(BatchReport.Changesets changesets) {
+ return new ScmInfoImpl(
+ from(new IntRangeIterable(changesets.getChangesetIndexByLineCount()))
+ .transform(new LineIndexToChangeset(changesets)));
+ }
+
+ private static Changeset convert(BatchReport.Changesets.Changeset changeset) {
+ checkState(changeset.hasRevision(), "Changeset must have a revision");
+ checkState(changeset.hasDate(), "Changeset must have a date");
+ return Changeset.newChangesetBuilder()
+ .setRevision(changeset.getRevision())
+ .setAuthor(changeset.hasAuthor() ? changeset.getAuthor() : null)
+ .setDate(changeset.getDate())
+ .build();
+ }
+
+ @Override
+ public Changeset getLatestChangeset() {
+ return this.delegate.getLatestChangeset();
+ }
+
+ @Override
+ public Changeset getChangesetForLine(int lineNumber) {
+ return this.delegate.getChangesetForLine(lineNumber);
+ }
+
+ @Override
+ public boolean hasChangesetForLine(int lineNumber) {
+ return delegate.hasChangesetForLine(lineNumber);
+ }
+
+ @Override
+ public Iterable<Changeset> getAllChangesets() {
+ return this.delegate.getAllChangesets();
+ }
+
+ private static class LineIndexToChangeset implements Function<Integer, Changeset> {
+ private final BatchReport.Changesets changesets;
+ private final Map<Integer, Changeset> changeSetCache;
+
+ public LineIndexToChangeset(BatchReport.Changesets changesets) {
+ this.changesets = changesets;
+ changeSetCache = new HashMap<>(changesets.getChangesetCount());
+ }
+
+ @Override
+ @Nonnull
+ public Changeset apply(@Nonnull Integer lineNumber) {
+ int changesetIndex = changesets.getChangesetIndexByLine(lineNumber - 1);
+ if (changeSetCache.containsKey(changesetIndex)) {
+ return changeSetCache.get(changesetIndex);
+ }
+ Changeset res = convert(changesets.getChangeset(changesetIndex));
+ changeSetCache.put(changesetIndex, res);
+ return res;
+ }
+ }
+
+ /**
+ * 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");
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.scm;
+
+/**
+ * Represents the Scm information for a specific file.
+ */
+public interface ScmInfo {
+
+ /**
+ * Get most recent ChangeSet of the file. Can never be null
+ */
+ Changeset getLatestChangeset();
+
+ /**
+ * 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)
+ */
+ Changeset getChangesetForLine(int lineNumber);
+
+ /**
+ * Check if there's a ChangeSet for given line
+ */
+ boolean hasChangesetForLine(int lineNumber);
+
+ /**
+ * Return all ChangeSets, in order, for all lines of the file
+ */
+ Iterable<Changeset> getAllChangesets();
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.scm;
+
+import com.google.common.base.Predicate;
+import java.util.Arrays;
+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;
+
+ 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);
+ 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;
+ }
+
+ @Override
+ public Changeset getLatestChangeset() {
+ return latestChangeset;
+ }
+
+ @Override
+ public Changeset getChangesetForLine(int lineNumber) {
+ checkArgument(lineNumber > 0 && lineNumber <= lineChangesets.length, String.format("There's no changeset on line %s", lineNumber));
+ return lineChangesets[lineNumber - 1];
+ }
+
+ @Override
+ public boolean hasChangesetForLine(int lineNumber) {
+ return lineNumber <= lineChangesets.length;
+ }
+
+ @Override
+ public Iterable<Changeset> getAllChangesets() {
+ return asList(lineChangesets);
+ }
+
+ @Override
+ public String toString() {
+ return "ScmInfoImpl{" +
+ "latestChangeset=" + latestChangeset +
+ ", lineChangesets=" + Arrays.toString(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;
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.scm;
+
+import com.google.common.base.Optional;
+import org.sonar.server.computation.component.Component;
+
+/**
+ * Return SCM information of components.
+ *
+ * It will always search in the db if there's nothing in the report.
+ */
+public interface ScmInfoRepository {
+
+ /**
+ * Returns Scm info for the specified component if there is any, first looking into the report, then into the database
+ * It there's nothing in the report and in the db (on first analysis for instance), then it return a {@link Optional#absent()}.
+ *
+ * @throws NullPointerException if argument is {@code null}
+ */
+ Optional<ScmInfo> getScmInfo(Component component);
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.scm;
+
+import com.google.common.base.Optional;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.protobuf.DbFileSources;
+import org.sonar.server.computation.batch.BatchReportReader;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.source.SourceService;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class ScmInfoRepositoryImpl implements ScmInfoRepository {
+
+ private static final Logger LOGGER = Loggers.get(ScmInfoRepositoryImpl.class);
+
+ private final BatchReportReader batchReportReader;
+ private final DbClient dbClient;
+ private final SourceService sourceService;
+
+ public ScmInfoRepositoryImpl(BatchReportReader batchReportReader, DbClient dbClient, SourceService sourceService) {
+ this.batchReportReader = batchReportReader;
+ this.dbClient = dbClient;
+ this.sourceService = sourceService;
+ }
+
+ @Override
+ public Optional<ScmInfo> getScmInfo(Component component) {
+ checkNotNull(component, "Component cannot be bull");
+ BatchReport.Changesets changesets = batchReportReader.readChangesets(component.getReportAttributes().getRef());
+ if (changesets == null) {
+ LOGGER.trace("Reading SCM info from db for file '{}'", component);
+ return getFromDb(component);
+ } else {
+ LOGGER.trace("Reading SCM info from report for file '{}'", component);
+ return Optional.<ScmInfo>of(new ReportScmInfo(changesets));
+ }
+ }
+
+ private Optional<ScmInfo> getFromDb(Component component){
+ DbSession dbSession = dbClient.openSession(false);
+ try {
+ Optional<Iterable<DbFileSources.Line>> linesOpt = sourceService.getLines(dbSession, component.getUuid(), 1, Integer.MAX_VALUE);
+ if (linesOpt.isPresent()) {
+ return DbScmInfo.create(component, linesOpt.get());
+ }
+ return Optional.absent();
+ } finally {
+ dbClient.closeSession(dbSession);
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.server.computation.scm;
+
+import javax.annotation.ParametersAreNonnullByDefault;
// load SCM blame information from report. It can be absent when the file was not touched
// since previous analysis (optimization to decrease execution of blame commands). In this case
// the date is loaded from database, as it did not change from previous analysis.
+
+ // TODO We should use ScmInfoRepository instead of reading the report
+ // (but should only be done when the repo is only used once per component,
+ // as it's done with ComponentIssuesRepository, to not increase number of calls to file_sources)
BatchReport.Changesets changesets = reportReader.readChangesets(file.getReportAttributes().getRef());
if (changesets == null) {
Optional<Measure> baseMeasure = measureRepository.getBaseMeasure(file, lastCommitDateMetric);
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.scm;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ChangesetTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void create_changeset() throws Exception {
+ Changeset underTest = Changeset.newChangesetBuilder()
+ .setAuthor("john")
+ .setDate(123456789L)
+ .setRevision("rev-1")
+ .build();
+
+ assertThat(underTest.getAuthor()).isEqualTo("john");
+ assertThat(underTest.getDate()).isEqualTo(123456789L);
+ assertThat(underTest.getRevision()).isEqualTo("rev-1");
+ }
+
+ @Test
+ public void create_changeset_with_minimum_fields() throws Exception {
+ Changeset underTest = Changeset.newChangesetBuilder()
+ .setDate(123456789L)
+ .setRevision("rev-1")
+ .build();
+
+ assertThat(underTest.getAuthor()).isNull();
+ assertThat(underTest.getDate()).isEqualTo(123456789L);
+ assertThat(underTest.getRevision()).isEqualTo("rev-1");
+ }
+
+ @Test
+ public void fail_with_NPE_when_setting_null_revision() throws Exception {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("Revision cannot be null");
+
+ Changeset.newChangesetBuilder().setRevision(null);
+ }
+
+ @Test
+ public void fail_with_NPE_when_building_without_revision() throws Exception {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("Revision cannot be null");
+
+ Changeset.newChangesetBuilder()
+ .setAuthor("john")
+ .setDate(123456789L)
+ .build();
+ }
+
+ @Test
+ public void fail_with_NPE_when_setting_null_date() throws Exception {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("Date cannot be null");
+
+ Changeset.newChangesetBuilder().setDate(null);
+ }
+
+ @Test
+ public void fail_with_NPE_when_building_without_date() throws Exception {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("Date cannot be null");
+
+ Changeset.newChangesetBuilder()
+ .setAuthor("john")
+ .setRevision("rev-1")
+ .build();
+ }
+
+ @Test
+ public void test_to_string() throws Exception {
+ Changeset underTest = Changeset.newChangesetBuilder()
+ .setAuthor("john")
+ .setDate(123456789L)
+ .setRevision("rev-1")
+ .build();
+
+ assertThat(underTest.toString()).isEqualTo("Changeset{revision='rev-1', author='john', date=123456789}");
+ }
+
+ @Test
+ public void test_equals_and_hash_code() throws Exception {
+ Changeset.Builder changesetBuilder = Changeset.newChangesetBuilder()
+ .setAuthor("john")
+ .setDate(123456789L)
+ .setRevision("rev-1");
+
+ Changeset changeset = changesetBuilder.build();
+ Changeset sameChangeset = changesetBuilder.build();
+
+ Changeset anotherChangeset = Changeset.newChangesetBuilder()
+ .setAuthor("henry")
+ .setDate(1234567810L)
+ .setRevision("rev-2")
+ .build();
+
+ assertThat(changeset).isEqualTo(sameChangeset);
+ assertThat(changeset).isEqualTo(changeset);
+ assertThat(changeset).isNotEqualTo(anotherChangeset);
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.scm;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.protobuf.DbFileSources;
+import org.sonar.server.computation.component.Component;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.guava.api.Assertions.assertThat;
+import static org.sonar.server.computation.component.ReportComponent.builder;
+import static org.sonar.server.source.index.FileSourceTesting.newFakeData;
+
+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();
+
+ assertThat(scmInfo.getAllChangesets()).hasSize(10);
+ }
+
+ @Test
+ public void return_changeset_for_a_given_line() throws Exception {
+ DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder();
+ addLine(fileDataBuilder, 1, "john", 123456789L, "rev-1");
+ addLine(fileDataBuilder, 2, "henry", 1234567810L, "rev-2");
+ addLine(fileDataBuilder, 3, "henry", 1234567810L, "rev-2");
+ addLine(fileDataBuilder, 4, "john", 123456789L, "rev-1");
+ fileDataBuilder.build();
+
+ ScmInfo scmInfo = DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get();
+
+ assertThat(scmInfo.getAllChangesets()).hasSize(4);
+
+ Changeset changeset = scmInfo.getChangesetForLine(4);
+ assertThat(changeset.getAuthor()).isEqualTo("john");
+ assertThat(changeset.getDate()).isEqualTo(123456789L);
+ assertThat(changeset.getRevision()).isEqualTo("rev-1");
+ }
+
+ @Test
+ public void return_latest_changeset() throws Exception {
+ DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder();
+ addLine(fileDataBuilder, 1, "john", 123456789L, "rev-1");
+ // Older changeset
+ addLine(fileDataBuilder, 2, "henry", 1234567810L, "rev-2");
+ addLine(fileDataBuilder, 3, "john", 123456789L, "rev-1");
+ fileDataBuilder.build();
+
+ ScmInfo scmInfo = DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get();
+
+ Changeset latestChangeset = scmInfo.getLatestChangeset();
+ assertThat(latestChangeset.getAuthor()).isEqualTo("henry");
+ assertThat(latestChangeset.getDate()).isEqualTo(1234567810L);
+ assertThat(latestChangeset.getRevision()).isEqualTo("rev-2");
+ }
+
+ @Test
+ public void return_absent_dsm_info_when_no_changeset() throws Exception {
+ DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder();
+ fileDataBuilder.addLinesBuilder().setLine(1);
+
+ assertThat(DbScmInfo.create(FILE, fileDataBuilder.getLinesList())).isAbsent();
+ }
+
+ @Test
+ public void fail_with_ISE_when_changeset_has_no_field() throws Exception {
+ 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");
+
+ DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder();
+ fileDataBuilder.addLinesBuilder().setLine(1);
+ fileDataBuilder.addLinesBuilder().setScmAuthor("John").setLine(2);
+ fileDataBuilder.build();
+
+ DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get().getAllChangesets();
+ }
+
+ private static void addLine(DbFileSources.Data.Builder dataBuilder, Integer line, String author, Long date, String revision) {
+ dataBuilder.addLinesBuilder()
+ .setLine(line)
+ .setScmAuthor(author)
+ .setScmDate(date)
+ .setScmRevision(revision);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.scm;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.batch.protocol.output.BatchReport;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ReportScmInfoTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ static final int FILE_REF = 1;
+
+ @Test
+ public void create_scm_info_with_some_changesets() throws Exception {
+ ScmInfo scmInfo = new ReportScmInfo(BatchReport.Changesets.newBuilder()
+ .setComponentRef(FILE_REF)
+ .addChangeset(BatchReport.Changesets.Changeset.newBuilder()
+ .setAuthor("john")
+ .setDate(123456789L)
+ .setRevision("rev-1")
+ .build())
+ .addChangeset(BatchReport.Changesets.Changeset.newBuilder()
+ .setAuthor("henry")
+ .setDate(1234567810L)
+ .setRevision("rev-2")
+ .build())
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(1)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .build());
+
+ assertThat(scmInfo.getAllChangesets()).hasSize(4);
+ }
+
+ @Test
+ public void return_changeset_for_a_given_line() throws Exception {
+ ScmInfo scmInfo = new ReportScmInfo(BatchReport.Changesets.newBuilder()
+ .setComponentRef(FILE_REF)
+ .addChangeset(BatchReport.Changesets.Changeset.newBuilder()
+ .setAuthor("john")
+ .setDate(123456789L)
+ .setRevision("rev-1")
+ .build())
+ .addChangeset(BatchReport.Changesets.Changeset.newBuilder()
+ .setAuthor("henry")
+ .setDate(1234567810L)
+ .setRevision("rev-2")
+ .build())
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(1)
+ .addChangesetIndexByLine(1)
+ .addChangesetIndexByLine(0)
+ .build());
+
+ assertThat(scmInfo.getAllChangesets()).hasSize(4);
+
+ Changeset changeset = scmInfo.getChangesetForLine(4);
+ assertThat(changeset.getAuthor()).isEqualTo("john");
+ assertThat(changeset.getDate()).isEqualTo(123456789L);
+ assertThat(changeset.getRevision()).isEqualTo("rev-1");
+ }
+
+ @Test
+ public void return_latest_changeset() throws Exception {
+ ScmInfo scmInfo = new ReportScmInfo(BatchReport.Changesets.newBuilder()
+ .setComponentRef(FILE_REF)
+ .addChangeset(BatchReport.Changesets.Changeset.newBuilder()
+ .setAuthor("john")
+ .setDate(123456789L)
+ .setRevision("rev-1")
+ .build())
+ // Older changeset
+ .addChangeset(BatchReport.Changesets.Changeset.newBuilder()
+ .setAuthor("henry")
+ .setDate(1234567810L)
+ .setRevision("rev-2")
+ .build())
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(1)
+ .addChangesetIndexByLine(0)
+ .build());
+
+ Changeset latestChangeset = scmInfo.getLatestChangeset();
+ assertThat(latestChangeset.getAuthor()).isEqualTo("henry");
+ assertThat(latestChangeset.getDate()).isEqualTo(1234567810L);
+ assertThat(latestChangeset.getRevision()).isEqualTo("rev-2");
+ }
+
+ @Test
+ public void fail_with_ISE_when_no_changeset() throws Exception {
+ thrown.expect(IllegalStateException.class);
+
+ new ReportScmInfo(BatchReport.Changesets.newBuilder().build());
+ }
+
+ @Test
+ public void fail_with_NPE_when_report_is_null() throws Exception {
+ thrown.expect(NullPointerException.class);
+
+ new ReportScmInfo(null);
+ }
+
+ @Test
+ public void fail_with_ISE_when_changeset_has_no_revision() throws Exception {
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("Changeset must have a revision");
+
+ new ReportScmInfo(BatchReport.Changesets.newBuilder()
+ .setComponentRef(FILE_REF)
+ .addChangeset(BatchReport.Changesets.Changeset.newBuilder()
+ .setAuthor("john")
+ .setDate(123456789L)
+ .build())
+ .addChangesetIndexByLine(0)
+ .build());
+ }
+
+ @Test
+ public void fail_with_ISE_when_changeset_has_no_date() throws Exception {
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("Changeset must have a date");
+
+ new ReportScmInfo(BatchReport.Changesets.newBuilder()
+ .setComponentRef(FILE_REF)
+ .addChangeset(BatchReport.Changesets.Changeset.newBuilder()
+ .setAuthor("john")
+ .setRevision("rev-1")
+ .build())
+ .addChangesetIndexByLine(0)
+ .build());
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.scm;
+
+import com.google.common.collect.Lists;
+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;
+
+public class ScmInfoImplTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ static final Changeset CHANGESET_1 = Changeset.newChangesetBuilder()
+ .setAuthor("john")
+ .setDate(123456789L)
+ .setRevision("rev-1")
+ .build();
+
+ static final Changeset CHANGESET_2 = Changeset.newChangesetBuilder()
+ .setAuthor("henry")
+ .setDate(1234567810L)
+ .setRevision("rev-2")
+ .build();
+
+ @Test
+ public void get_all_changesets() throws Exception {
+ ScmInfo scmInfo = createScmInfoWithTwoChangestOnFourLines();
+
+ assertThat(scmInfo.getAllChangesets()).containsOnly(CHANGESET_1, CHANGESET_2, CHANGESET_1, CHANGESET_1);
+ }
+
+ @Test
+ public void get_latest_changeset() throws Exception {
+ ScmInfo scmInfo = createScmInfoWithTwoChangestOnFourLines();
+
+ assertThat(scmInfo.getLatestChangeset()).isEqualTo(CHANGESET_2);
+ }
+
+ @Test
+ public void get_changeset_for_given_line() throws Exception {
+ ScmInfo scmInfo = createScmInfoWithTwoChangestOnFourLines();
+
+ assertThat(scmInfo.getChangesetForLine(1)).isEqualTo(CHANGESET_1);
+ assertThat(scmInfo.getChangesetForLine(2)).isEqualTo(CHANGESET_2);
+ assertThat(scmInfo.getChangesetForLine(3)).isEqualTo(CHANGESET_1);
+ assertThat(scmInfo.getChangesetForLine(4)).isEqualTo(CHANGESET_1);
+ }
+
+ @Test
+ public void exists_for_given_line() throws Exception {
+ ScmInfo scmInfo = createScmInfoWithTwoChangestOnFourLines();
+
+ assertThat(scmInfo.hasChangesetForLine(1)).isTrue();
+ assertThat(scmInfo.hasChangesetForLine(5)).isFalse();
+ }
+
+ @Test
+ public void fail_with_ISE_on_empty_changeset() throws Exception {
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("A ScmInfo must have at least one Changeset and does not support any null one");
+
+ new ScmInfoImpl(Lists.<Changeset>newArrayList());
+ }
+
+ @Test
+ public void fail_with_IAE_when_line_is_smaller_than_one() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("There's no changeset on line 0");
+
+ ScmInfo scmInfo = createScmInfoWithTwoChangestOnFourLines();
+ scmInfo.getChangesetForLine(0);
+ }
+
+ @Test
+ public void fail_with_IAE_when_line_is_bigger_than_changetset_size() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("There's no changeset on line 5");
+
+ ScmInfo scmInfo = createScmInfoWithTwoChangestOnFourLines();
+ scmInfo.getChangesetForLine(5);
+ }
+
+ @Test
+ public void test_to_string() throws Exception {
+ ScmInfo scmInfo = createScmInfoWithTwoChangestOnFourLines();
+
+ assertThat(scmInfo.toString()).isEqualTo("ScmInfoImpl{" +
+ "latestChangeset=Changeset{revision='rev-2', author='henry', date=1234567810}, " +
+ "lineChangesets=[" +
+ "Changeset{revision='rev-1', author='john', date=123456789}, " +
+ "Changeset{revision='rev-2', author='henry', date=1234567810}, " +
+ "Changeset{revision='rev-1', author='john', date=123456789}, " +
+ "Changeset{revision='rev-1', author='john', date=123456789}" +
+ "]}");
+ }
+
+ private static ScmInfo createScmInfoWithTwoChangestOnFourLines() {
+ Changeset changeset1 = Changeset.newChangesetBuilder()
+ .setAuthor("john")
+ .setDate(123456789L)
+ .setRevision("rev-1")
+ .build();
+ // Latest changeset
+ Changeset changeset2 = Changeset.newChangesetBuilder()
+ .setAuthor("henry")
+ .setDate(1234567810L)
+ .setRevision("rev-2")
+ .build();
+
+ ScmInfo scmInfo = new ScmInfoImpl(newArrayList(changeset1, changeset2, changeset1, changeset1));
+ return scmInfo;
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.scm;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.protobuf.DbFileSources;
+import org.sonar.db.source.FileSourceDto;
+import org.sonar.server.computation.batch.BatchReportReaderRule;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.source.SourceService;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.guava.api.Assertions.assertThat;
+import static org.sonar.server.computation.component.ReportComponent.builder;
+
+public class ScmInfoRepositoryImplTest {
+
+ @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();
+
+ @Rule
+ public BatchReportReaderRule reportReader = new BatchReportReaderRule();
+
+ @Rule
+ public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+ DbClient dbClient = dbTester.getDbClient();
+
+ ScmInfoRepositoryImpl underTest = new ScmInfoRepositoryImpl(reportReader, dbClient, new SourceService(dbClient, null));
+
+ @Test
+ public void read_from_report() throws Exception {
+ addChangesetInReport("john", 123456789L, "rev-1");
+
+ ScmInfo scmInfo = underTest.getScmInfo(FILE).get();
+ assertThat(scmInfo.getAllChangesets()).hasSize(1);
+ }
+
+ @Test
+ public void read_from_db() throws Exception {
+ addChangesetInDb("henry", 123456789L, "rev-1");
+
+ ScmInfo scmInfo = underTest.getScmInfo(FILE).get();
+ assertThat(scmInfo.getAllChangesets()).hasSize(1);
+ }
+
+ @Test
+ public void read_from_report_even_if_data_in_db_exists() throws Exception {
+ addChangesetInDb("henry", 123456789L, "rev-1");
+
+ addChangesetInReport("john", 1234567810L, "rev-2");
+
+ ScmInfo scmInfo = underTest.getScmInfo(FILE).get();
+
+ Changeset changeset = scmInfo.getChangesetForLine(1);
+ assertThat(changeset.getAuthor()).isEqualTo("john");
+ assertThat(changeset.getDate()).isEqualTo(1234567810L);
+ assertThat(changeset.getRevision()).isEqualTo("rev-2");
+ }
+
+ @Test
+ public void return_nothing_when_no_data_in_report_and_db() throws Exception {
+ assertThat(underTest.getScmInfo(FILE)).isAbsent();
+ }
+
+ @Test
+ public void return_nothing_when_nothing_in_erpot_and_db_has_no_scm() throws Exception {
+ DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder();
+ fileDataBuilder.addLinesBuilder()
+ .setLine(1);
+ dbTester.getDbClient().fileSourceDao().insert(new FileSourceDto()
+ .setFileUuid(FILE.getUuid())
+ .setProjectUuid("PROJECT_UUID")
+ .setSourceData(fileDataBuilder.build()));
+
+ assertThat(underTest.getScmInfo(FILE)).isAbsent();
+ }
+
+ @Test
+ public void fail_with_NPE_when_component_is_null() throws Exception {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("Component cannot be bull");
+
+ underTest.getScmInfo(null);
+ }
+
+ private void addChangesetInDb(String author, Long date, String revision) {
+ DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder();
+ fileDataBuilder.addLinesBuilder()
+ .setLine(1)
+ .setScmAuthor(author)
+ .setScmDate(date)
+ .setScmRevision(revision);
+ dbTester.getDbClient().fileSourceDao().insert(new FileSourceDto()
+ .setFileUuid(FILE.getUuid())
+ .setProjectUuid("PROJECT_UUID")
+ .setSourceData(fileDataBuilder.build()));
+ }
+
+ private void addChangesetInReport(String author, Long date, String revision) {
+ reportReader.putChangesets(BatchReport.Changesets.newBuilder()
+ .setComponentRef(FILE_REF)
+ .addChangeset(BatchReport.Changesets.Changeset.newBuilder()
+ .setAuthor(author)
+ .setDate(date)
+ .setRevision(revision)
+ .build())
+ .addChangesetIndexByLine(0)
+ .build());
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.scm;
+
+import com.google.common.base.Optional;
+import org.junit.rules.ExternalResource;
+import org.sonar.server.computation.component.Component;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class ScmInfoRepositoryRule extends ExternalResource implements ScmInfoRepository {
+
+ private Map<Integer, ScmInfo> scmInfoByFileRef = new HashMap<>();
+
+ @Override
+ protected void after() {
+ scmInfoByFileRef.clear();
+ }
+
+ @Override
+ public Optional<ScmInfo> getScmInfo(Component component) {
+ checkNotNull(component, "Component cannot be bull");
+ ScmInfo scmInfo = scmInfoByFileRef.get(component.getReportAttributes().getRef());
+ return Optional.fromNullable(scmInfo);
+ }
+
+ public ScmInfoRepositoryRule setScmInfo(int fileRef, Changeset... changesetList) {
+ scmInfoByFileRef.put(fileRef, new ScmInfoImpl(Arrays.asList(changesetList)));
+ return this;
+ }
+}