aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-scanner-engine/src
diff options
context:
space:
mode:
authorKlaudio Sinani <klaudio.sinani@sonarsource.com>2022-08-10 13:37:31 +0200
committersonartech <sonartech@sonarsource.com>2022-10-26 20:03:10 +0000
commit84f0224faf4e45999e793f8d06b66c065dfc6399 (patch)
treebeb055ee1d93aa0966ca01d8cbed088202ae59ef /sonar-scanner-engine/src
parent30e6c8d94430d7087f14196032d77e3034262e83 (diff)
downloadsonarqube-84f0224faf4e45999e793f8d06b66c065dfc6399.tar.gz
sonarqube-84f0224faf4e45999e793f8d06b66c065dfc6399.zip
SONAR-13579 Detect files moves in Pull Request scope
SONAR-13579 Get database files from target branch instead of snapshot SONAR-13579 Store old relative file path to `FileAttributes` class
Diffstat (limited to 'sonar-scanner-engine/src')
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ChangedLinesPublisher.java3
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ComponentsPublisher.java4
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java17
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFiles.java36
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFilesProvider.java30
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scm/git/ChangedFile.java59
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scm/git/GitScmProvider.java178
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/StatusDetectionTest.java8
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesProviderTest.java5
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesTest.java10
10 files changed, 325 insertions, 25 deletions
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ChangedLinesPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ChangedLinesPublisher.java
index 19bb51e1695..56737d1e068 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ChangedLinesPublisher.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ChangedLinesPublisher.java
@@ -39,6 +39,7 @@ import org.sonar.scanner.repository.ReferenceBranchSupplier;
import org.sonar.scanner.scan.branch.BranchConfiguration;
import org.sonar.scanner.scan.filesystem.InputComponentStore;
import org.sonar.scanner.scm.ScmConfiguration;
+import org.sonar.scm.git.GitScmProvider;
import static java.util.Optional.empty;
@@ -89,7 +90,7 @@ public class ChangedLinesPublisher implements ReportPublisherStep {
Map<Path, DefaultInputFile> changedFiles = StreamSupport.stream(inputComponentStore.allChangedFilesToPublish().spliterator(), false)
.collect(Collectors.toMap(DefaultInputFile::path, f -> f));
- Map<Path, Set<Integer>> pathSetMap = provider.branchChangedLines(targetScmBranch, rootBaseDir, changedFiles.keySet());
+ Map<Path, Set<Integer>> pathSetMap = ((GitScmProvider) provider).branchChangedLines(targetScmBranch, rootBaseDir, changedFiles); // TODO: Extend ScmProvider abstract
int count = 0;
if (pathSetMap == null) {
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ComponentsPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ComponentsPublisher.java
index 4ca7c8be780..b21da5f2f94 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ComponentsPublisher.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ComponentsPublisher.java
@@ -68,6 +68,10 @@ public class ComponentsPublisher implements ReportPublisherStep {
fileBuilder.setStatus(convert(file.status()));
fileBuilder.setMarkedAsUnchanged(file.isMarkedAsUnchanged());
+ if (file.isMovedFile()) {
+ fileBuilder.setOldRelativeFilePath(file.oldPath());
+ }
+
String lang = getLanguageKey(file);
if (lang != null) {
fileBuilder.setLanguage(lang);
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java
index 7ea8799c855..4653c24d171 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java
@@ -43,6 +43,7 @@ import org.sonar.api.utils.log.Loggers;
import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader;
import org.sonar.scanner.repository.language.Language;
import org.sonar.scanner.scan.ScanProperties;
+import org.sonar.scanner.scm.ScmChangedFiles;
import org.sonar.scanner.util.ProgressReport;
import static java.lang.String.format;
@@ -66,6 +67,7 @@ public class FileIndexer {
private final InputComponentStore componentStore;
private final SensorStrategy sensorStrategy;
private final LanguageDetection langDetection;
+ private final ScmChangedFiles scmChangedFiles;
private boolean warnInclusionsAlreadyLogged;
private boolean warnExclusionsAlreadyLogged;
@@ -75,7 +77,7 @@ public class FileIndexer {
public FileIndexer(DefaultInputProject project, ScannerComponentIdGenerator scannerComponentIdGenerator, InputComponentStore componentStore,
ProjectExclusionFilters projectExclusionFilters, ProjectCoverageAndDuplicationExclusions projectCoverageAndDuplicationExclusions, IssueExclusionsLoader issueExclusionsLoader,
MetadataGenerator metadataGenerator, SensorStrategy sensorStrategy, LanguageDetection languageDetection, AnalysisWarnings analysisWarnings, ScanProperties properties,
- InputFileFilter[] filters) {
+ InputFileFilter[] filters, ScmChangedFiles scmChangedFiles) {
this.project = project;
this.scannerComponentIdGenerator = scannerComponentIdGenerator;
this.componentStore = componentStore;
@@ -88,6 +90,7 @@ public class FileIndexer {
this.properties = properties;
this.filters = filters;
this.projectExclusionFilters = projectExclusionFilters;
+ this.scmChangedFiles = scmChangedFiles;
}
void indexFile(DefaultInputModule module, ModuleExclusionFilters moduleExclusionFilters, ModuleCoverageAndDuplicationExclusions moduleCoverageAndDuplicationExclusions,
@@ -124,10 +127,18 @@ public class FileIndexer {
return;
}
- DefaultIndexedFile indexedFile = new DefaultIndexedFile(realAbsoluteFile, project.key(),
+ DefaultIndexedFile indexedFile = new DefaultIndexedFile(
+ realAbsoluteFile,
+ project.key(),
projectRelativePath.toString(),
moduleRelativePath.toString(),
- type, language != null ? language.key() : null, scannerComponentIdGenerator.getAsInt(), sensorStrategy);
+ type,
+ language != null ? language.key() : null,
+ scannerComponentIdGenerator.getAsInt(),
+ sensorStrategy,
+ scmChangedFiles.getFileOldPath(realAbsoluteFile)
+ );
+
DefaultInputFile inputFile = new DefaultInputFile(indexedFile, f -> metadataGenerator.setMetadata(module.key(), f, module.getEncoding()));
if (language != null && language.isPublishAllFiles()) {
inputFile.setPublished(true);
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFiles.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFiles.java
index 0491d6b4281..010f2de4cbf 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFiles.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFiles.java
@@ -21,17 +21,21 @@ package org.sonar.scanner.scm;
import java.nio.file.Path;
import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Predicate;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
+import org.sonar.scm.git.ChangedFile;
@Immutable
public class ScmChangedFiles {
@Nullable
- private final Collection<Path> fileCollection;
+ private final Collection<ChangedFile> changedFiles;
- public ScmChangedFiles(@Nullable Collection<Path> changedFiles) {
- this.fileCollection = changedFiles;
+ public ScmChangedFiles(@Nullable Collection<ChangedFile> changedFiles) {
+ this.changedFiles = changedFiles;
}
public boolean isChanged(Path file) {
@@ -39,15 +43,33 @@ public class ScmChangedFiles {
throw new IllegalStateException("Scm didn't provide valid data");
}
- return fileCollection.contains(file);
+ return this.findFile(file).isPresent();
}
public boolean isValid() {
- return fileCollection != null;
+ return changedFiles != null;
}
@CheckForNull
- Collection<Path> get() {
- return fileCollection;
+ public Collection<ChangedFile> get() {
+ return changedFiles;
+ }
+
+ @CheckForNull
+ public String getFileOldPath(Path absoluteFilePath) {
+ return this.findFile(absoluteFilePath)
+ .filter(ChangedFile::isMoved)
+ .map(ChangedFile::getOldFilePath)
+ .orElse(null);
+ }
+
+ private Optional<ChangedFile> findFile(Path absoluteFilePath) {
+ Predicate<ChangedFile> isTargetFile = file -> file.getAbsolutFilePath().equals(absoluteFilePath);
+
+ return Optional.ofNullable(this.get())
+ .orElseGet(List::of)
+ .stream()
+ .filter(isTargetFile)
+ .findFirst();
}
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFilesProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFilesProvider.java
index 5a46e41a90f..392996d7e74 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFilesProvider.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFilesProvider.java
@@ -21,14 +21,17 @@ package org.sonar.scanner.scm;
import java.nio.file.Path;
import java.util.Collection;
+import java.util.Set;
+import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
import org.sonar.api.batch.fs.internal.DefaultInputProject;
-import org.sonar.api.batch.scm.ScmProvider;
import org.sonar.api.impl.utils.ScannerUtils;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.api.utils.log.Profiler;
import org.sonar.scanner.scan.branch.BranchConfiguration;
+import org.sonar.scm.git.ChangedFile;
+import org.sonar.scm.git.GitScmProvider;
import org.springframework.context.annotation.Bean;
public class ScmChangedFilesProvider {
@@ -38,25 +41,36 @@ public class ScmChangedFilesProvider {
@Bean("ScmChangedFiles")
public ScmChangedFiles provide(ScmConfiguration scmConfiguration, BranchConfiguration branchConfiguration, DefaultInputProject project) {
Path rootBaseDir = project.getBaseDir();
- Collection<Path> changedFiles = loadChangedFilesIfNeeded(scmConfiguration, branchConfiguration, rootBaseDir);
- validatePaths(changedFiles);
+ Collection<ChangedFile> changedFiles = loadChangedFilesIfNeeded(scmConfiguration, branchConfiguration, rootBaseDir);
+
+ if (changedFiles != null) {
+ validatePaths(getFilePaths(changedFiles));
+ }
+
return new ScmChangedFiles(changedFiles);
}
- private static void validatePaths(@javax.annotation.Nullable Collection<Path> paths) {
- if (paths != null && paths.stream().anyMatch(p -> !p.isAbsolute())) {
+ private static void validatePaths(Set<Path> changedFilePaths) {
+ if (changedFilePaths != null && changedFilePaths.stream().anyMatch(p -> !p.isAbsolute())) {
throw new IllegalStateException("SCM provider returned a changed file with a relative path but paths must be absolute. Please fix the provider.");
}
}
+ private static Set<Path> getFilePaths(Collection<ChangedFile> changedFiles) {
+ return changedFiles
+ .stream()
+ .map(ChangedFile::getAbsolutFilePath)
+ .collect(Collectors.toSet());
+ }
+
@CheckForNull
- private static Collection<Path> loadChangedFilesIfNeeded(ScmConfiguration scmConfiguration, BranchConfiguration branchConfiguration, Path rootBaseDir) {
+ private static Collection<ChangedFile> loadChangedFilesIfNeeded(ScmConfiguration scmConfiguration, BranchConfiguration branchConfiguration, Path rootBaseDir) {
final String targetBranchName = branchConfiguration.targetBranchName();
if (branchConfiguration.isPullRequest() && targetBranchName != null) {
- ScmProvider scmProvider = scmConfiguration.provider();
+ GitScmProvider scmProvider = (GitScmProvider) scmConfiguration.provider();
if (scmProvider != null) {
Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG);
- Collection<Path> changedFiles = scmProvider.branchChangedFiles(targetBranchName, rootBaseDir);
+ Collection<ChangedFile> changedFiles = scmProvider.branchModifiedFiles(targetBranchName, rootBaseDir);
profiler.stopInfo();
if (changedFiles != null) {
LOG.debug("SCM reported {} {} changed in the branch", changedFiles.size(), ScannerUtils.pluralize("file", changedFiles.size()));
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/ChangedFile.java b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/ChangedFile.java
new file mode 100644
index 00000000000..6f30d24e744
--- /dev/null
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/ChangedFile.java
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.scm.git;
+
+import java.nio.file.Path;
+import java.util.Objects;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public class ChangedFile {
+ @Nullable
+ private final String oldFilePath;
+ private final String filePath;
+ private final Path absoluteFilePath;
+
+ public ChangedFile(String filePath, Path absoluteFilePath) {
+ this(filePath, absoluteFilePath, null);
+ }
+
+ public ChangedFile(String filePath, Path absoluteFilePath, @Nullable String oldFilePath) {
+ this.filePath = filePath;
+ this.oldFilePath = oldFilePath;
+ this.absoluteFilePath = absoluteFilePath;
+ }
+
+ @CheckForNull
+ public String getOldFilePath() {
+ return oldFilePath;
+ }
+
+ public boolean isMoved() {
+ return Objects.nonNull(this.getOldFilePath());
+ }
+
+ public String getFilePath() {
+ return filePath;
+ }
+
+ public Path getAbsolutFilePath() {
+ return absoluteFilePath;
+ }
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/GitScmProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/GitScmProvider.java
index bef15b669bc..c51035ee8f1 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/GitScmProvider.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/GitScmProvider.java
@@ -24,11 +24,17 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
@@ -36,8 +42,10 @@ import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffAlgorithm;
import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.RawTextComparator;
+import org.eclipse.jgit.diff.RenameDetector;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -52,6 +60,9 @@ import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.filter.PathFilter;
+import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.scm.BlameCommand;
import org.sonar.api.batch.scm.ScmProvider;
import org.sonar.api.notifications.AnalysisWarnings;
@@ -60,6 +71,10 @@ import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
+import static org.eclipse.jgit.diff.DiffEntry.ChangeType.ADD;
+import static org.eclipse.jgit.diff.DiffEntry.ChangeType.MODIFY;
+import static org.eclipse.jgit.diff.DiffEntry.ChangeType.RENAME;
+
public class GitScmProvider extends ScmProvider {
private static final Logger LOG = Loggers.get(GitScmProvider.class);
@@ -141,6 +156,87 @@ public class GitScmProvider extends ScmProvider {
return null;
}
+ // TODO: Adjust ScmProvider abstract
+ @CheckForNull
+ public Collection<ChangedFile> branchModifiedFiles(String targetBranchName, Path rootBaseDir) {
+ try (Repository repo = buildRepo(rootBaseDir)) {
+ Ref targetRef = resolveTargetRef(targetBranchName, repo);
+ if (targetRef == null) {
+ addWarningTargetNotFound(targetBranchName);
+ return null;
+ }
+
+ if (isDiffAlgoInvalid(repo.getConfig())) {
+ LOG.warn("The diff algorithm configured in git is not supported. "
+ + "No information regarding changes in the branch will be collected, which can lead to unexpected results.");
+ return null;
+ }
+
+ Optional<RevCommit> mergeBaseCommit = findMergeBase(repo, targetRef);
+ if (mergeBaseCommit.isEmpty()) {
+ LOG.warn("No merge base found between HEAD and " + targetRef.getName());
+ return null;
+ }
+ AbstractTreeIterator mergeBaseTree = prepareTreeParser(repo, mergeBaseCommit.get());
+
+ // we compare a commit with HEAD, so no point ignoring line endings (it will be whatever is committed)
+ try (Git git = newGit(repo)) {
+ List<DiffEntry> diffEntries = git.diff()
+ .setShowNameAndStatusOnly(true)
+ .setOldTree(mergeBaseTree)
+ .setNewTree(prepareNewTree(repo))
+ .call();
+
+ return computeChangedFiles(repo, diffEntries);
+ }
+ } catch (IOException | GitAPIException e) {
+ LOG.warn(e.getMessage(), e);
+ }
+ return null;
+ }
+
+ private static List<ChangedFile> computeChangedFiles(Repository repository, List<DiffEntry> diffEntries) throws IOException {
+ Path workingDirectory = repository.getWorkTree().toPath();
+
+ Map<String, String> renamedFilePaths = computeRenamedFilePaths(repository, diffEntries);
+ Set<String> changedFilePaths = computeChangedFilePaths(diffEntries);
+
+ List<ChangedFile> changedFiles = new LinkedList<>();
+
+ Consumer<String> collectChangedFiles = filePath -> changedFiles.add(new ChangedFile(filePath, workingDirectory.resolve(filePath), renamedFilePaths.getOrDefault(filePath, null)));
+ changedFilePaths.forEach(collectChangedFiles);
+
+ return changedFiles;
+ }
+
+ private static Map<String, String> computeRenamedFilePaths(Repository repository, List<DiffEntry> diffEntries) throws IOException {
+ RenameDetector renameDetector = new RenameDetector(repository);
+ renameDetector.addAll(diffEntries);
+
+ return renameDetector
+ .compute()
+ .stream()
+ .filter(entry -> RENAME.equals(entry.getChangeType()))
+ .collect(Collectors.toUnmodifiableMap(DiffEntry::getNewPath, DiffEntry::getOldPath));
+ }
+
+ private static Set<String> computeChangedFilePaths(List<DiffEntry> diffEntries) {
+ return diffEntries
+ .stream()
+ .filter(isAllowedChangeType(ADD, MODIFY))
+ .map(DiffEntry::getNewPath)
+ .collect(Collectors.toSet());
+ }
+
+ private static Predicate<DiffEntry> isAllowedChangeType(ChangeType ...changeTypes) {
+ Function<ChangeType, Predicate<DiffEntry>> isChangeType = type -> entry -> type.equals(entry.getChangeType());
+
+ return Arrays
+ .stream(changeTypes)
+ .map(isChangeType)
+ .reduce(x -> false, Predicate::or);
+ }
+
@CheckForNull
@Override
public Map<Path, Set<Integer>> branchChangedLines(String targetBranchName, Path projectBaseDir, Set<Path> changedFiles) {
@@ -177,6 +273,43 @@ public class GitScmProvider extends ScmProvider {
return null;
}
+ // TODO: Adjust ScmProvider abstract
+ public Map<Path, Set<Integer>> branchChangedLines(String targetBranchName, Path projectBaseDir, Map<Path, DefaultInputFile> changedFiles) {
+ try (Repository repo = buildRepo(projectBaseDir)) {
+ Ref targetRef = resolveTargetRef(targetBranchName, repo);
+ if (targetRef == null) {
+ addWarningTargetNotFound(targetBranchName);
+ return null;
+ }
+
+ if (isDiffAlgoInvalid(repo.getConfig())) {
+ // we already print a warning when branchChangedFiles is called
+ return null;
+ }
+
+ // force ignore different line endings when comparing a commit with the workspace
+ repo.getConfig().setBoolean("core", null, "autocrlf", true);
+
+ Optional<RevCommit> mergeBaseCommit = findMergeBase(repo, targetRef);
+
+ if (mergeBaseCommit.isEmpty()) {
+ LOG.warn("No merge base found between HEAD and " + targetRef.getName());
+ return null;
+ }
+
+ Map<Path, Set<Integer>> changedLines = new HashMap<>();
+
+ for (Map.Entry<Path, DefaultInputFile> entry : changedFiles.entrySet()) {
+ collectChangedLines(repo, mergeBaseCommit.get(), changedLines, entry.getKey(), entry.getValue());
+ }
+
+ return changedLines;
+ } catch (Exception e) {
+ LOG.warn("Failed to get changed lines from git", e);
+ }
+ return null;
+ }
+
private void addWarningTargetNotFound(String targetBranchName) {
analysisWarnings.addUnique(String.format(COULD_NOT_FIND_REF
+ ". You may see unexpected issues and changes. "
@@ -211,6 +344,36 @@ public class GitScmProvider extends ScmProvider {
}
}
+ // TODO: Adjust ScmProvider abstract
+ private void collectChangedLines(Repository repo, RevCommit mergeBaseCommit, Map<Path, Set<Integer>> changedLines, Path changedFilePath, DefaultInputFile changedFileData) {
+ ChangedLinesComputer computer = new ChangedLinesComputer();
+
+ try (DiffFormatter diffFmt = new DiffFormatter(new BufferedOutputStream(computer.receiver()))) {
+ diffFmt.setRepository(repo);
+ diffFmt.setProgressMonitor(NullProgressMonitor.INSTANCE);
+ diffFmt.setDiffComparator(RawTextComparator.WS_IGNORE_ALL);
+
+ diffFmt.setDetectRenames(changedFileData.isMovedFile());
+
+ Path workTree = repo.getWorkTree().toPath();
+ TreeFilter treeFilter = getTreeFilter(changedFileData, workTree);
+ diffFmt.setPathFilter(treeFilter);
+
+ AbstractTreeIterator mergeBaseTree = prepareTreeParser(repo, mergeBaseCommit);
+ List<DiffEntry> diffEntries = diffFmt.scan(mergeBaseTree, new FileTreeIterator(repo));
+
+ diffFmt.format(diffEntries);
+ diffFmt.flush();
+
+ diffEntries.stream()
+ .filter(isAllowedChangeType(ADD, MODIFY, RENAME))
+ .findAny()
+ .ifPresent(diffEntry -> changedLines.put(changedFilePath, computer.changedLines()));
+ } catch (Exception e) {
+ LOG.warn("Failed to get changed lines from git for file " + changedFilePath, e);
+ }
+ }
+
@Override
@CheckForNull
public Instant forkDate(String referenceBranchName, Path projectBaseDir) {
@@ -221,6 +384,17 @@ public class GitScmProvider extends ScmProvider {
return path.replaceAll(Pattern.quote(File.separator), "/");
}
+ private TreeFilter getTreeFilter(DefaultInputFile changedFile, Path baseDir) {
+ String oldPath = toGitPath(changedFile.oldPath());
+ String path = toGitPath(relativizeFilePath(baseDir, changedFile.path()));
+
+ if (changedFile.isMovedFile()) {
+ return PathFilterGroup.createFromStrings(path, oldPath);
+ }
+
+ return PathFilter.create(path);
+ }
+
@CheckForNull
private Ref resolveTargetRef(String targetBranchName, Repository repo) throws IOException {
String localRef = "refs/heads/" + targetBranchName;
@@ -332,6 +506,10 @@ public class GitScmProvider extends ScmProvider {
}
}
+ private static String relativizeFilePath(Path baseDirectory, Path filePath) {
+ return baseDirectory.relativize(filePath).toString();
+ }
+
AbstractTreeIterator prepareTreeParser(Repository repo, RevCommit commit) throws IOException {
CanonicalTreeParser treeParser = new CanonicalTreeParser();
try (ObjectReader objectReader = repo.newObjectReader()) {
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/StatusDetectionTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/StatusDetectionTest.java
index 73c736b224b..94629f59b60 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/StatusDetectionTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/StatusDetectionTest.java
@@ -19,9 +19,11 @@
*/
package org.sonar.scanner.scan.filesystem;
+import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.sonar.api.batch.fs.InputFile;
@@ -31,6 +33,7 @@ import org.sonar.scanner.repository.FileData;
import org.sonar.scanner.repository.ProjectRepositories;
import org.sonar.scanner.repository.SingleProjectRepository;
import org.sonar.scanner.scm.ScmChangedFiles;
+import org.sonar.scm.git.ChangedFile;
import static org.assertj.core.api.Assertions.assertThat;
@@ -61,9 +64,10 @@ public class StatusDetectionTest {
@Test
public void detect_status_branches_confirm() {
- ScmChangedFiles changedFiles = new ScmChangedFiles(Collections.singletonList(Paths.get("module", "src", "Foo.java")));
- StatusDetection statusDetection = new StatusDetection(projectRepositories, changedFiles);
+ Path filePath = Paths.get("module", "src", "Foo.java");
+ ScmChangedFiles changedFiles = new ScmChangedFiles(List.of(new ChangedFile(filePath.toString(), filePath)));
+ StatusDetection statusDetection = new StatusDetection(projectRepositories, changedFiles);
assertThat(statusDetection.status("foo", createFile("src/Foo.java"), "XXXXX")).isEqualTo(InputFile.Status.CHANGED);
}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesProviderTest.java
index ec319e1c931..abbc78a3819 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesProviderTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesProviderTest.java
@@ -30,6 +30,7 @@ import org.sonar.api.batch.fs.internal.DefaultInputProject;
import org.sonar.api.batch.scm.ScmProvider;
import org.sonar.scanner.fs.InputModuleHierarchy;
import org.sonar.scanner.scan.branch.BranchConfiguration;
+import org.sonar.scm.git.ChangedFile;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -130,7 +131,9 @@ public class ScmChangedFilesProviderTest {
when(scmProvider.branchChangedFiles("target", rootBaseDir)).thenReturn(Collections.singleton(Paths.get("changedFile").toAbsolutePath()));
ScmChangedFiles scmChangedFiles = provider.provide(scmConfiguration, branchConfiguration, project);
- assertThat(scmChangedFiles.get()).containsOnly(Paths.get("changedFile").toAbsolutePath());
+ Path filePath = Paths.get("changedFile").toAbsolutePath();
+ ChangedFile changedFile = new ChangedFile(filePath.toString(), filePath);
+ assertThat(scmChangedFiles.get()).containsOnly(changedFile);
verify(scmProvider).branchChangedFiles("target", rootBaseDir);
}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesTest.java
index 40d3930713f..856d0573124 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesTest.java
@@ -24,6 +24,7 @@ import java.nio.file.Paths;
import java.util.Collection;
import java.util.Collections;
import org.junit.Test;
+import org.sonar.scm.git.ChangedFile;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -33,9 +34,11 @@ public class ScmChangedFilesTest {
@Test
public void testGetter() {
- Collection<Path> files = Collections.singletonList(Paths.get("files"));
+ Path filePath = Paths.get("files");
+ ChangedFile file = new ChangedFile(filePath.toString(), filePath);
+ Collection<ChangedFile> files = Collections.singletonList(file);
scmChangedFiles = new ScmChangedFiles(files);
- assertThat(scmChangedFiles.get()).containsOnly(Paths.get("files"));
+ assertThat(scmChangedFiles.get()).containsOnly(file);
}
@Test
@@ -50,7 +53,8 @@ public class ScmChangedFilesTest {
@Test
public void testConfirm() {
- Collection<Path> files = Collections.singletonList(Paths.get("files"));
+ Path filePath = Paths.get("files");
+ Collection<ChangedFile> files = Collections.singletonList(new ChangedFile(filePath.toString(), filePath));
scmChangedFiles = new ScmChangedFiles(files);
assertThat(scmChangedFiles.isValid()).isTrue();
assertThat(scmChangedFiles.isChanged(Paths.get("files"))).isTrue();