*/
package org.sonar.plugins.core;
+import org.sonar.plugins.core.batch.PartialScanFilter;
+
+import org.sonar.plugins.core.utils.HashBuilder;
import com.google.common.collect.ImmutableList;
import org.sonar.api.*;
import org.sonar.api.checks.NoSonarFilter;
FilesDecorator.class,
IndexProjectPostJob.class,
ManualMeasureDecorator.class,
+ HashBuilder.class,
+ FileHashSensor.class,
+ PartialScanFilter.class,
// time machine
TendencyDecorator.class,
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.batch;
+
+import com.google.common.collect.Maps;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.picocontainer.Startable;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.config.Settings;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.scan.filesystem.FileSystemFilter;
+import org.sonar.api.scan.filesystem.PathResolver;
+import org.sonar.api.utils.SonarException;
+import org.sonar.batch.components.PastSnapshot;
+import org.sonar.batch.components.PastSnapshotFinder;
+import org.sonar.core.source.SnapshotDataType;
+import org.sonar.core.source.jdbc.SnapshotDataDao;
+import org.sonar.core.source.jdbc.SnapshotDataDto;
+import org.sonar.plugins.core.utils.HashBuilder;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * When enabled this filter will only allow modified files to be analyzed.
+ * @since 4.0
+ */
+public class PartialScanFilter implements FileSystemFilter, Startable {
+
+ private PathResolver pathResolver;
+ private HashBuilder hashBuilder;
+ private Settings settings;
+ private SnapshotDataDao snapshotDataDao;
+ private PastSnapshotFinder pastSnapshotFinder;
+ private Snapshot snapshot;
+ private ProjectDefinition module;
+
+ private Map<String, String> fileHashMap = Maps.newHashMap();
+
+ public PartialScanFilter(Settings settings, ProjectDefinition module, PathResolver pathResolver, HashBuilder hashBuilder,
+ Snapshot snapshot,
+ SnapshotDataDao snapshotDataDao,
+ PastSnapshotFinder pastSnapshotFinder) {
+ this.settings = settings;
+ this.module = module;
+ this.pathResolver = pathResolver;
+ this.hashBuilder = hashBuilder;
+ this.snapshot = snapshot;
+ this.snapshotDataDao = snapshotDataDao;
+ this.pastSnapshotFinder = pastSnapshotFinder;
+ }
+
+ @Override
+ public void start() {
+ // Extract previous checksum of all files of this module and store
+ // them in a map
+ if (settings.getBoolean(CoreProperties.PARTIAL_ANALYSIS)) {
+ if (!settings.getBoolean(CoreProperties.DRY_RUN)) {
+ throw new SonarException("Partial analysis is only supported with dry run mode");
+ }
+ PastSnapshot pastSnapshot = pastSnapshotFinder.findPreviousAnalysis(snapshot);
+ if (pastSnapshot.isRelatedToSnapshot()) {
+ Collection<SnapshotDataDto> selectSnapshotData = snapshotDataDao.selectSnapshotData(pastSnapshot.getProjectSnapshot().getId().longValue(),
+ Arrays.asList(SnapshotDataType.FILE_HASH.getValue()));
+ if (!selectSnapshotData.isEmpty()) {
+ SnapshotDataDto snapshotDataDto = selectSnapshotData.iterator().next();
+ String data = snapshotDataDto.getData();
+ try {
+ List<String> lines = IOUtils.readLines(new StringReader(data));
+ for (String line : lines) {
+ String[] keyValue = StringUtils.split(line, "=");
+ if (keyValue.length == 2) {
+ fileHashMap.put(keyValue[0], keyValue[1]);
+ }
+ }
+ } catch (IOException e) {
+ throw new SonarException("Unable to read previous file hashes", e);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void stop() {
+ }
+
+ @Override
+ public boolean accept(File file, Context context) {
+ if (!settings.getBoolean(CoreProperties.PARTIAL_ANALYSIS)) {
+ return true;
+ }
+ String relativePath = pathResolver.relativePath(module.getBaseDir(), file);
+ String previousHash = fileHashMap.get(relativePath);
+ if (previousHash == null) {
+ return true;
+ }
+ String currentHash = hashBuilder.computeHash(file);
+ return !currentHash.equals(previousHash);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.sensors;
+
+import org.sonar.api.batch.Sensor;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.resources.Project;
+import org.sonar.api.scan.filesystem.FileQuery;
+import org.sonar.api.scan.filesystem.FileType;
+import org.sonar.api.scan.filesystem.ModuleFileSystem;
+import org.sonar.api.scan.filesystem.PathResolver;
+import org.sonar.batch.index.ComponentDataCache;
+import org.sonar.core.source.SnapshotDataType;
+import org.sonar.plugins.core.utils.HashBuilder;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * This sensor will compute md5 checksum of each file of the current module and store it in DB
+ * in order to compare it during next analysis and see if the file was modified.
+ * This is used by the partial analysis mode.
+ * @see org.sonar.plugins.core.batch.PartialScanFilter
+ * @since 4.0
+ */
+public final class FileHashSensor implements Sensor {
+
+ private ModuleFileSystem moduleFileSystem;
+ private PathResolver pathResolver;
+ private HashBuilder hashBuilder;
+ private ComponentDataCache componentDataCache;
+
+ public FileHashSensor(ModuleFileSystem moduleFileSystem, PathResolver pathResolver, HashBuilder hashBuilder, ComponentDataCache componentDataCache) {
+ this.moduleFileSystem = moduleFileSystem;
+ this.pathResolver = pathResolver;
+ this.hashBuilder = hashBuilder;
+ this.componentDataCache = componentDataCache;
+ }
+
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @Override
+ public void analyse(Project project, SensorContext context) {
+ StringBuilder fileHashMap = new StringBuilder();
+ analyse(fileHashMap, project, FileType.SOURCE);
+ analyse(fileHashMap, project, FileType.TEST);
+ componentDataCache.setStringData(project.getKey(), SnapshotDataType.FILE_HASH.getValue(), fileHashMap.toString());
+ }
+
+ private void analyse(StringBuilder fileHashMap, Project project, FileType fileType) {
+ List<File> files = moduleFileSystem.files(FileQuery.on(fileType).onLanguage(project.getLanguageKey()));
+ for (File file : files) {
+ String md5 = hashBuilder.computeHash(file);
+ fileHashMap.append(pathResolver.relativePath(moduleFileSystem.baseDir(), file)).append("=").append(md5).append("\n");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.utils;
+
+import org.apache.commons.io.IOUtils;
+import org.sonar.api.BatchExtension;
+import org.sonar.api.utils.SonarException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * @since 4.0
+ */
+public final class HashBuilder implements BatchExtension {
+
+ public String computeHash(File file) {
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(file);
+ return org.apache.commons.codec.digest.DigestUtils.md5Hex(fis);
+ } catch (IOException e) {
+ throw new SonarException("Unable to compute file hash", e);
+ } finally {
+ IOUtils.closeQuietly(fis);
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.utils;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.batch;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.config.Settings;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.scan.filesystem.FileSystemFilter;
+import org.sonar.api.scan.filesystem.PathResolver;
+import org.sonar.api.utils.SonarException;
+import org.sonar.batch.components.PastSnapshot;
+import org.sonar.batch.components.PastSnapshotFinder;
+import org.sonar.core.source.SnapshotDataType;
+import org.sonar.core.source.jdbc.SnapshotDataDao;
+import org.sonar.core.source.jdbc.SnapshotDataDto;
+import org.sonar.plugins.core.utils.HashBuilder;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Date;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PartialScanFilterTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ private PartialScanFilter filter;
+
+ private Settings settings;
+
+ private PastSnapshotFinder pastSnapshotFinder;
+
+ private Snapshot snapshot;
+
+ private File baseDir;
+
+ private SnapshotDataDao snapshotDataDao;
+
+ @Before
+ public void prepare() throws Exception {
+ settings = new Settings();
+ pastSnapshotFinder = mock(PastSnapshotFinder.class);
+ snapshot = mock(Snapshot.class);
+ baseDir = temp.newFolder();
+ snapshotDataDao = mock(SnapshotDataDao.class);
+ filter = new PartialScanFilter(settings, ProjectDefinition.create().setBaseDir(baseDir), new PathResolver(), new HashBuilder(), snapshot,
+ snapshotDataDao, pastSnapshotFinder);
+ }
+
+ @Test
+ public void should_not_run_by_default() throws Exception {
+ filter.start();
+ assertThat(filter.accept(temp.newFile(), mock(FileSystemFilter.Context.class))).isTrue();
+ }
+
+ @Test
+ public void should_throw_if_partial_mode_and_not_in_dryrun() throws Exception {
+ settings.setProperty(CoreProperties.PARTIAL_ANALYSIS, true);
+
+ thrown.expect(SonarException.class);
+ thrown.expectMessage("Partial analysis is only supported with dry run mode");
+ filter.start();
+ }
+
+ @Test
+ public void should_include_if_no_previous_snapshot() throws Exception {
+ settings.setProperty(CoreProperties.PARTIAL_ANALYSIS, true);
+ settings.setProperty(CoreProperties.DRY_RUN, true);
+
+ when(pastSnapshotFinder.findPreviousAnalysis(snapshot)).thenReturn(new PastSnapshot("foo"));
+
+ filter.start();
+ assertThat(filter.accept(new File(baseDir, "src/main/java/foo/Bar.java"), mock(FileSystemFilter.Context.class))).isTrue();
+ }
+
+ @Test
+ public void should_include_if_no_previous_snapshot_data() throws Exception {
+ settings.setProperty(CoreProperties.PARTIAL_ANALYSIS, true);
+ settings.setProperty(CoreProperties.DRY_RUN, true);
+
+ Snapshot previousSnapshot = mock(Snapshot.class);
+ PastSnapshot pastSnapshot = new PastSnapshot("foo", new Date(), previousSnapshot);
+ when(pastSnapshotFinder.findPreviousAnalysis(snapshot)).thenReturn(pastSnapshot);
+
+ filter.start();
+ assertThat(filter.accept(new File(baseDir, "src/main/java/foo/Bar.java"), mock(FileSystemFilter.Context.class))).isTrue();
+ }
+
+ @Test
+ public void should_include_if_different_hash() throws Exception {
+ settings.setProperty(CoreProperties.PARTIAL_ANALYSIS, true);
+ settings.setProperty(CoreProperties.DRY_RUN, true);
+
+ Snapshot previousSnapshot = mock(Snapshot.class);
+ when(previousSnapshot.getId()).thenReturn(123);
+ PastSnapshot pastSnapshot = new PastSnapshot("foo", new Date(), previousSnapshot);
+ when(pastSnapshotFinder.findPreviousAnalysis(snapshot)).thenReturn(pastSnapshot);
+
+ SnapshotDataDto snapshotDataDto = new SnapshotDataDto();
+ snapshotDataDto.setData("src/main/java/foo/Bar.java=abcd1234\n");
+ when(snapshotDataDao.selectSnapshotData(123, Arrays.asList(SnapshotDataType.FILE_HASH.getValue())))
+ .thenReturn(Arrays.asList(snapshotDataDto));
+
+ filter.start();
+ File file = new File(baseDir, "src/main/java/foo/Bar.java");
+ FileUtils.write(file, "foo");
+ assertThat(filter.accept(file, mock(FileSystemFilter.Context.class))).isTrue();
+ }
+
+ @Test
+ public void should_exclude_if_same_hash() throws Exception {
+ settings.setProperty(CoreProperties.PARTIAL_ANALYSIS, true);
+ settings.setProperty(CoreProperties.DRY_RUN, true);
+
+ Snapshot previousSnapshot = mock(Snapshot.class);
+ when(previousSnapshot.getId()).thenReturn(123);
+ PastSnapshot pastSnapshot = new PastSnapshot("foo", new Date(), previousSnapshot);
+ when(pastSnapshotFinder.findPreviousAnalysis(snapshot)).thenReturn(pastSnapshot);
+
+ SnapshotDataDto snapshotDataDto = new SnapshotDataDto();
+ snapshotDataDto.setData("src/main/java/foo/Bar.java=acbd18db4cc2f85cedef654fccc4a4d8\n");
+ when(snapshotDataDao.selectSnapshotData(123, Arrays.asList(SnapshotDataType.FILE_HASH.getValue())))
+ .thenReturn(Arrays.asList(snapshotDataDto));
+
+ filter.start();
+ File file = new File(baseDir, "src/main/java/foo/Bar.java");
+ FileUtils.write(file, "foo");
+ assertThat(filter.accept(file, mock(FileSystemFilter.Context.class))).isFalse();
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.sensors;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.resources.Java;
+import org.sonar.api.resources.Project;
+import org.sonar.api.scan.filesystem.FileQuery;
+import org.sonar.api.scan.filesystem.ModuleFileSystem;
+import org.sonar.api.scan.filesystem.PathResolver;
+import org.sonar.batch.index.ComponentDataCache;
+import org.sonar.plugins.core.utils.HashBuilder;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class FileHashSensorTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ private FileHashSensor sensor;
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ private ModuleFileSystem fileSystem;
+
+ private ComponentDataCache componentDataCache;
+
+ private Project project;
+
+ @Before
+ public void prepare() {
+ fileSystem = mock(ModuleFileSystem.class);
+ componentDataCache = mock(ComponentDataCache.class);
+ sensor = new FileHashSensor(fileSystem, new PathResolver(), new HashBuilder(), componentDataCache);
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ conf.setProperty("sonar.language", "java");
+ project = new Project("java_project").setConfiguration(conf).setLanguage(Java.INSTANCE);
+ }
+
+ @Test
+ public void improve_code_coverage() throws Exception {
+ assertThat(sensor.shouldExecuteOnProject(project)).isTrue();
+ assertThat(sensor.toString()).isEqualTo("FileHashSensor");
+ }
+
+ @Test
+ public void computeHashes() throws Exception {
+ File baseDir = temp.newFolder();
+ File file1 = new File(baseDir, "src/com/foo/Bar.java");
+ FileUtils.write(file1, "Bar");
+ File file2 = new File(baseDir, "src/com/foo/Foo.java");
+ FileUtils.write(file2, "Foo");
+ when(fileSystem.baseDir()).thenReturn(baseDir);
+ when(fileSystem.files(any(FileQuery.class))).thenReturn(Arrays.asList(file1, file2)).thenReturn(Collections.<File> emptyList());
+ sensor.analyse(project, mock(SensorContext.class));
+
+ verify(componentDataCache).setStringData("java_project", "hash",
+ "src/com/foo/Bar.java=ddc35f88fa71b6ef142ae61f35364653\n"
+ + "src/com/foo/Foo.java=1356c67d7ad1638d816bfb822dd2c25d\n");
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.utils;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.utils.SonarException;
+
+import java.io.File;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class HashBuilderTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Test
+ public void should_compute_hash() throws Exception {
+ File tempFile = temp.newFile();
+ FileUtils.write(tempFile, "foobar");
+ assertThat(new HashBuilder().computeHash(tempFile)).isEqualTo("3858f62230ac3c915f300c664312c63f");
+ }
+
+ @Test(expected = SonarException.class)
+ public void should_throw_on_not_existing_file() throws Exception {
+ File tempFolder = temp.newFolder();
+ new HashBuilder().computeHash(new File(tempFolder, "unknowFile.txt"));
+ }
+}
import org.sonar.api.ServerComponent;
import org.sonar.api.issue.Issue;
import org.sonar.api.utils.SonarException;
+import org.sonar.core.source.SnapshotDataType;
import javax.annotation.Nullable;
import javax.sql.DataSource;
template.copyTable(source, dest, "events", "SELECT * FROM events WHERE resource_id=" + projectId);
- template.copyTable(source, dest, "snapshots", "SELECT * FROM snapshots WHERE project_id=" + projectId);
- template.copyTable(source, dest, "project_measures", "SELECT m.* FROM project_measures m INNER JOIN snapshots s on m.snapshot_id=s.id WHERE s.project_id=" + projectId);
+ StringBuilder snapshotQuery = new StringBuilder()
+ // All snapshots of root_project for alerts on differential periods
+ .append("SELECT * FROM snapshots WHERE project_id=")
+ .append(projectId)
+ // Plus all last snapshots of all modules having hash data for partial analysis
+ .append(" UNION SELECT snap.* FROM snapshots snap")
+ .append(" INNER JOIN (")
+ .append(projectQuery(projectId, true))
+ .append(") res")
+ .append(" ON snap.project_id=res.id")
+ .append(" INNER JOIN snapshot_data data")
+ .append(" ON snap.id=data.snapshot_id")
+ .append(" AND data.data_type='").append(SnapshotDataType.FILE_HASH.getValue()).append("'")
+ .append(" AND snap.islast=").append(database.getDialect().getTrueSqlValue());
+ template.copyTable(source, dest, "snapshots", snapshotQuery.toString());
+
+ StringBuilder snapshotDataQuery = new StringBuilder()
+ .append("SELECT data.* FROM snapshot_data data")
+ .append(" INNER JOIN snapshots s")
+ .append(" ON s.id=data.snapshot_id")
+ .append(" AND s.islast=").append(database.getDialect().getTrueSqlValue())
+ .append(" INNER JOIN (")
+ .append(projectQuery(projectId, true))
+ .append(") res")
+ .append(" ON data.resource_id=res.id")
+ .append(" AND data.data_type='").append(SnapshotDataType.FILE_HASH.getValue()).append("'");
+ template.copyTable(source, dest, "snapshot_data", snapshotDataQuery.toString());
+
+ // All measures of snapshots of root project for alerts on differential periods
+ template.copyTable(source, dest, "project_measures", "SELECT m.* FROM project_measures m INNER JOIN snapshots s on m.snapshot_id=s.id "
+ + "WHERE s.project_id=" + projectId);
StringBuilder issueQuery = new StringBuilder()
.append("SELECT issues.* FROM issues")
public enum SnapshotDataType {
SYNTAX_HIGHLIGHTING("highlight_syntax"),
- SYMBOL_HIGHLIGHTING("symbol");
+ SYMBOL_HIGHLIGHTING("symbol"),
+ FILE_HASH("hash");
private SnapshotDataType(String value) {
this.value = value;
dataSource = createDatabase(database);
assertThat(rowCount("issues")).isEqualTo(1);
assertThat(rowCount("projects")).isEqualTo(4);
- assertThat(rowCount("snapshots")).isEqualTo(1);
- assertThat(rowCount("project_measures")).isEqualTo(2);
+ assertThat(rowCount("snapshots")).isEqualTo(4);
+ assertThat(rowCount("snapshot_data")).isEqualTo(2);
+ assertThat(rowCount("project_measures")).isEqualTo(4);
}
@Test
dataSource = createDatabase(database);
assertThat(rowCount("issues")).isEqualTo(1);
assertThat(rowCount("projects")).isEqualTo(2);
- assertThat(rowCount("snapshots")).isEqualTo(1);
- assertThat(rowCount("project_measures")).isEqualTo(2);
+ assertThat(rowCount("snapshots")).isEqualTo(2);
+ assertThat(rowCount("project_measures")).isEqualTo(4);
}
@Test
<snapshots id="3002" project_id="302" root_project_id="300" root_snapshot_id="3000" path="3000." islast="[true]"/>
<snapshots id="3003" project_id="303" root_project_id="300" root_snapshot_id="3000" path="3000.3001." islast="[true]"/>
+ <snapshots id="3010" project_id="300" root_project_id="300" root_snapshot_id="[null]" path="" islast="[false]"/>
+ <snapshots id="3011" project_id="301" root_project_id="300" root_snapshot_id="3010" path="3010." islast="[false]"/>
+ <snapshots id="3012" project_id="302" root_project_id="300" root_snapshot_id="3010" path="3010." islast="[false]"/>
+ <snapshots id="3013" project_id="303" root_project_id="300" root_snapshot_id="3010" path="3010.3011." islast="[false]"/>
+
+ <snapshot_data id="1" snapshot_id="3001" resource_id="301" snapshot_data="foo=AB12" data_type="hash" />
+ <snapshot_data id="2" snapshot_id="3001" resource_id="301" snapshot_data="bar" data_type="other" />
+ <snapshot_data id="3" snapshot_id="3002" resource_id="302" snapshot_data="bar=DC12" data_type="hash" />
+
+ <snapshot_data id="4" snapshot_id="3011" resource_id="301" snapshot_data="foo=CD34" data_type="hash" />
+ <snapshot_data id="5" snapshot_id="3012" resource_id="302" snapshot_data="bar=EF78" data_type="hash" />
+
<project_measures id="1" value="12" metric_id="1" snapshot_id="3000" />
<project_measures id="2" value="5" metric_id="1" snapshot_id="3001" />
<project_measures id="3" value="7" metric_id="1" snapshot_id="3002" />
<project_measures id="4" value="5" metric_id="1" snapshot_id="3003" />
+
<project_measures id="5" value="35" metric_id="2" snapshot_id="3000" />
<project_measures id="6" value="20" metric_id="2" snapshot_id="3001" />
<project_measures id="7" value="30" metric_id="2" snapshot_id="3002" />
<project_measures id="8" value="20" metric_id="2" snapshot_id="3003" />
+ <project_measures id="11" value="112" metric_id="1" snapshot_id="3010" />
+ <project_measures id="12" value="15" metric_id="1" snapshot_id="3011" />
+ <project_measures id="13" value="17" metric_id="1" snapshot_id="3012" />
+ <project_measures id="14" value="15" metric_id="1" snapshot_id="3013" />
+
+ <project_measures id="15" value="135" metric_id="2" snapshot_id="3010" />
+ <project_measures id="16" value="120" metric_id="2" snapshot_id="3011" />
+ <project_measures id="17" value="130" metric_id="2" snapshot_id="3012" />
+ <project_measures id="18" value="120" metric_id="2" snapshot_id="3013" />
+
<rules id="500" plugin_rule_key="AvoidCycle" plugin_name="squid"/>
<rules id="501" plugin_rule_key="NullRef" plugin_name="squid"/>
*/
String CATEGORY_TECHNICAL_DEBT = "technicalDebt";
-
/* Global settings */
String SONAR_HOME = "SONAR_HOME";
String PROJECT_BRANCH_PROPERTY = "sonar.branch";
* @since 4.0
*/
String CORE_PREVENT_AUTOMATIC_PROJECT_CREATION = "sonar.preventAutoProjectCreation";
+
+ /**
+ * @since 4.0
+ */
+ String PARTIAL_ANALYSIS = "sonar.partialAnalysis";
}
/**
* Plugins must not implement this interface. It is provided at runtime.
*/
- interface Context {
+ public interface Context {
ModuleFileSystem fileSystem();
FileType type();