aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-scanner-engine/src
diff options
context:
space:
mode:
authorLéo Geoffroy <99647462+leo-geoffroy-sonarsource@users.noreply.github.com>2023-03-14 16:22:08 +0100
committersonartech <sonartech@sonarsource.com>2023-03-14 20:03:28 +0000
commitd8a3797bd65b1b04f4ccfa9b7a0fc1c59dd0d6eb (patch)
tree4637f1fc583d24d0ac9be36380da86223e0fc1e9 /sonar-scanner-engine/src
parentc1325337e923a28c14f0e08d0bb8e6c2393d9582 (diff)
downloadsonarqube-d8a3797bd65b1b04f4ccfa9b7a0fc1c59dd0d6eb.tar.gz
sonarqube-d8a3797bd65b1b04f4ccfa9b7a0fc1c59dd0d6eb.zip
SONAR-18408 Integrate git-files-blame algorithm to scanner-engine
Co-authored-by: lukasz-jarocki-sonarsource <lukasz.jarocki@sonarsource.com> Co-authored-by: leo-geoffroy-sonarsource <leo.geoffroy@sonarsource.com> Co-authored-by: benjamin-campomenosi-sonarsource <benjamin.campomenosi@sonarsource.com> Co-authored-by: Duarte Meneses <duarte.meneses@sonarsource.com>
Diffstat (limited to 'sonar-scanner-engine/src')
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scm/git/CompositeBlameCommand.java103
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scm/git/GitScmSupport.java4
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scm/git/NativeGitBlameCommand.java (renamed from sonar-scanner-engine/src/main/java/org/sonar/scm/git/GitBlameCommand.java)8
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scm/git/strategy/BlameStrategy.java25
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scm/git/strategy/DefaultBlameStrategy.java80
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scm/git/CompositeBlameCommandIT.java184
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scm/git/CompositeBlameCommandTest.java150
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitScmProviderTest.java6
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitUtils.java24
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scm/git/JGitBlameCommandTest.java30
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scm/git/NativeGitBlameCommandTest.java (renamed from sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitBlameCommandTest.java)41
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scm/git/strategy/DefaultBlameStrategyTest.java82
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/fifth-file.js1
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/first-file.js1
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/fourth-file.js1
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/second-file.js1
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/third-file.js1
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5lines-5commits/5lines.js5
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/ReadMe.txt6
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git-few-comitters/pom.xml52
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git-few-comitters/src/main/java/org/dummy/AnotherDummy.java29
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git-few-comitters/src/main/java/org/dummy/Dummy.java29
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git-few-comitters/src/test/java/org/dummy/AnotherDummyTest.java31
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git/pom.xml52
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git/src/main/java/org/dummy/AnotherDummy.java29
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git/src/main/java/org/dummy/Dummy.java29
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git/src/test/java/org/dummy/AnotherDummyTest.java31
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/merge-commits/merge-commit.js3
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/one-file-many-merges-and-renames/file1.js10
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/one-file-one-commit/one-commit.js1
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/one-file-renamed-many-times/renamed-many-times.js5
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/one-file-two-commits/two-commits.js2
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-files-moved-around-with-conflicts/firstcopy.js1
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-files-moved-around-with-conflicts/secondcopy.js1
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-files-one-commit/firstfile.js1
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-files-one-commit/secondfile.js1
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-merge-commits/two-merge-commits.js4
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/5files-5commits.zipbin0 -> 27239 bytes
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/5lines-5commits.zipbin0 -> 28514 bytes
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/dummy-git-few-comitters.zipbin0 -> 64004 bytes
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/merge-commits.zipbin0 -> 21206 bytes
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/one-file-many-merges-and-renames.zipbin0 -> 40675 bytes
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/one-file-one-commit.zipbin0 -> 17336 bytes
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/one-file-renamed-many-times.zipbin0 -> 23835 bytes
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/one-file-two-commits.zipbin0 -> 18942 bytes
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/two-files-moved-around-with-conflicts.zipbin0 -> 25956 bytes
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/two-files-one-commit.zipbin0 -> 18077 bytes
-rw-r--r--sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/two-merge-commits.zipbin0 -> 26802 bytes
48 files changed, 942 insertions, 122 deletions
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/CompositeBlameCommand.java b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/CompositeBlameCommand.java
index 1ea3b4a4f39..69c02674fd3 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/CompositeBlameCommand.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/CompositeBlameCommand.java
@@ -22,14 +22,19 @@ package org.sonar.scm.git;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
@@ -44,8 +49,13 @@ import org.sonar.api.scan.filesystem.PathResolver;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.api.utils.log.Profiler;
+import org.sonar.scm.git.blame.BlameResult;
+import org.sonar.scm.git.blame.RepositoryBlameCommand;
+import org.sonar.scm.git.strategy.BlameStrategy;
+import org.sonar.scm.git.strategy.DefaultBlameStrategy.BlameAlgorithmEnum;
import static java.util.Optional.ofNullable;
+import static org.sonar.scm.git.strategy.DefaultBlameStrategy.BlameAlgorithmEnum.GIT_FILES_BLAME;
public class CompositeBlameCommand extends BlameCommand {
private static final Logger LOG = Loggers.get(CompositeBlameCommand.class);
@@ -53,12 +63,16 @@ public class CompositeBlameCommand extends BlameCommand {
private final AnalysisWarnings analysisWarnings;
private final PathResolver pathResolver;
private final JGitBlameCommand jgitCmd;
- private final GitBlameCommand nativeCmd;
+ private final NativeGitBlameCommand nativeCmd;
private boolean nativeGitEnabled = false;
- public CompositeBlameCommand(AnalysisWarnings analysisWarnings, PathResolver pathResolver, JGitBlameCommand jgitCmd, GitBlameCommand nativeCmd) {
+ private final BlameStrategy blameStrategy;
+
+ public CompositeBlameCommand(AnalysisWarnings analysisWarnings, PathResolver pathResolver, JGitBlameCommand jgitCmd,
+ NativeGitBlameCommand nativeCmd, BlameStrategy blameStrategy) {
this.analysisWarnings = analysisWarnings;
this.pathResolver = pathResolver;
+ this.blameStrategy = blameStrategy;
this.jgitCmd = jgitCmd;
this.nativeCmd = nativeCmd;
}
@@ -66,23 +80,48 @@ public class CompositeBlameCommand extends BlameCommand {
@Override
public void blame(BlameInput input, BlameOutput output) {
File basedir = input.fileSystem().baseDir();
- try (Repository repo = JGitUtils.buildRepository(basedir.toPath()); Git git = Git.wrap(repo)) {
+ try (Repository repo = JGitUtils.buildRepository(basedir.toPath())) {
+
File gitBaseDir = repo.getWorkTree();
if (cloneIsInvalid(gitBaseDir)) {
return;
}
Profiler profiler = Profiler.create(LOG);
profiler.startDebug("Collecting committed files");
- Set<String> committedFiles = collectAllCommittedFiles(repo);
+ Map<String, InputFile> inputFileByGitRelativePath = getCommittedFilesToBlame(repo, gitBaseDir, input);
profiler.stopDebug();
+
+ BlameAlgorithmEnum blameAlgorithmEnum = this.blameStrategy.getBlameAlgorithm(Runtime.getRuntime().availableProcessors(), inputFileByGitRelativePath.size());
+ LOG.debug("Using {} strategy to blame files", blameAlgorithmEnum);
+ if (blameAlgorithmEnum == GIT_FILES_BLAME) {
+ blameWithFilesGitCommand(output, repo, inputFileByGitRelativePath);
+ } else {
+ blameWithNativeGitCommand(output, repo, inputFileByGitRelativePath, gitBaseDir);
+ }
+ }
+ }
+
+ private Map<String, InputFile> getCommittedFilesToBlame(Repository repo, File gitBaseDir, BlameInput input) {
+ Set<String> committedFiles = collectAllCommittedFiles(repo);
+ Map<String, InputFile> inputFileByGitRelativePath = new HashMap<>();
+ for (InputFile inputFile : input.filesToBlame()) {
+ String relative = pathResolver.relativePath(gitBaseDir, inputFile.file());
+ if (relative == null || !committedFiles.contains(relative)) {
+ continue;
+ }
+ inputFileByGitRelativePath.put(relative, inputFile);
+ }
+ return inputFileByGitRelativePath;
+ }
+
+ private void blameWithNativeGitCommand(BlameOutput output, Repository repo, Map<String, InputFile> inputFileByGitRelativePath, File gitBaseDir) {
+ try (Git git = Git.wrap(repo)) {
nativeGitEnabled = nativeCmd.checkIfEnabled();
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new GitThreadFactory());
- for (InputFile inputFile : input.filesToBlame()) {
- String filename = pathResolver.relativePath(gitBaseDir, inputFile.file());
- if (filename == null || !committedFiles.contains(filename)) {
- continue;
- }
+ for (Map.Entry<String, InputFile> e : inputFileByGitRelativePath.entrySet()) {
+ InputFile inputFile = e.getValue();
+ String filename = e.getKey();
// exceptions thrown by the blame method will be ignored
executorService.submit(() -> blame(output, git, gitBaseDir, inputFile, filename));
}
@@ -151,6 +190,29 @@ public class CompositeBlameCommand extends BlameCommand {
}
}
+ private static void blameWithFilesGitCommand(BlameOutput output, Repository repo, Map<String, InputFile> inputFileByGitRelativePath) {
+ RepositoryBlameCommand blameCommand = new RepositoryBlameCommand(repo)
+ .setTextComparator(RawTextComparator.WS_IGNORE_ALL)
+ .setMultithreading(true)
+ .setFilePaths(inputFileByGitRelativePath.keySet());
+ try {
+ BlameResult blameResult = blameCommand.call();
+
+ for (Map.Entry<String, InputFile> e : inputFileByGitRelativePath.entrySet()) {
+ BlameResult.FileBlame fileBlameResult = blameResult.getFileBlameByPath().get(e.getKey());
+
+ if (fileBlameResult == null) {
+ LOG.debug("Unable to blame file {}.", e.getValue().filename());
+ continue;
+ }
+
+ saveBlameInformationForFileInTheOutput(fileBlameResult, e.getValue(), output);
+ }
+ } catch (GitAPIException e) {
+ LOG.warn("There was an issue when interacting with git repository: " + e.getMessage(), e);
+ }
+ }
+
private boolean cloneIsInvalid(File gitBaseDir) {
if (Files.isRegularFile(gitBaseDir.toPath().resolve(".git/objects/info/alternates"))) {
LOG.info("This git repository references another local repository which is not well supported. SCM information might be missing for some files. "
@@ -166,4 +228,27 @@ public class CompositeBlameCommand extends BlameCommand {
return false;
}
+
+ private static void saveBlameInformationForFileInTheOutput(BlameResult.FileBlame fileBlame, InputFile file, BlameOutput output) {
+ List<BlameLine> linesList = new ArrayList<>();
+ for (int i = 0; i < fileBlame.lines(); i++) {
+ if (fileBlame.getAuthorEmails()[i] == null || fileBlame.getCommitHashes()[i] == null || fileBlame.getCommitDates()[i] == null) {
+ LOG.debug("Unable to blame file {}. No blame info at line {}. Is file committed? [Author: {} Source commit: {}]", file.filename());
+ linesList.clear();
+ break;
+ }
+ linesList.add(new BlameLine()
+ .date(fileBlame.getCommitDates()[i])
+ .revision(fileBlame.getCommitHashes()[i])
+ .author(fileBlame.getAuthorEmails()[i]));
+ }
+ if (!linesList.isEmpty()) {
+ if (linesList.size() == file.lines() - 1) {
+ // SONARPLUGINS-3097 Git does not report blame on last empty line
+ linesList.add(linesList.get(linesList.size() - 1));
+ }
+ output.blameResult(file, linesList);
+ }
+ }
+
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/GitScmSupport.java b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/GitScmSupport.java
index 70f7e2918d0..e06c95c7c48 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/GitScmSupport.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/GitScmSupport.java
@@ -22,6 +22,7 @@ package org.sonar.scm.git;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jgit.util.FS;
+import org.sonar.scm.git.strategy.DefaultBlameStrategy;
public final class GitScmSupport {
private GitScmSupport() {
@@ -33,8 +34,9 @@ public final class GitScmSupport {
return Arrays.asList(
JGitBlameCommand.class,
CompositeBlameCommand.class,
+ NativeGitBlameCommand.class,
+ DefaultBlameStrategy.class,
ProcessWrapperFactory.class,
- GitBlameCommand.class,
GitScmProvider.class,
GitIgnoreCommand.class);
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/GitBlameCommand.java b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/NativeGitBlameCommand.java
index 9469ec15c38..0dd2e1620ef 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/GitBlameCommand.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/NativeGitBlameCommand.java
@@ -39,13 +39,13 @@ import org.springframework.beans.factory.annotation.Autowired;
import static java.util.Collections.emptyList;
import static org.sonar.api.utils.Preconditions.checkState;
-public class GitBlameCommand {
+public class NativeGitBlameCommand {
protected static final String BLAME_COMMAND = "blame";
protected static final String GIT_DIR_FLAG = "--git-dir";
protected static final String GIT_DIR_ARGUMENT = "%s/.git";
protected static final String GIT_DIR_FORCE_FLAG = "-C";
- private static final Logger LOG = Loggers.get(GitBlameCommand.class);
+ private static final Logger LOG = Loggers.get(NativeGitBlameCommand.class);
private static final Pattern EMAIL_PATTERN = Pattern.compile("<(.*?)>");
private static final String COMMITTER_TIME = "committer-time ";
private static final String AUTHOR_MAIL = "author-mail ";
@@ -64,12 +64,12 @@ public class GitBlameCommand {
private String gitCommand;
@Autowired
- public GitBlameCommand(System2 system, ProcessWrapperFactory processWrapperFactory) {
+ public NativeGitBlameCommand(System2 system, ProcessWrapperFactory processWrapperFactory) {
this.system = system;
this.processWrapperFactory = processWrapperFactory;
}
- GitBlameCommand(String gitCommand, System2 system, ProcessWrapperFactory processWrapperFactory) {
+ NativeGitBlameCommand(String gitCommand, System2 system, ProcessWrapperFactory processWrapperFactory) {
this.gitCommand = gitCommand;
this.system = system;
this.processWrapperFactory = processWrapperFactory;
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/strategy/BlameStrategy.java b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/strategy/BlameStrategy.java
new file mode 100644
index 00000000000..7092d5e5e05
--- /dev/null
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/strategy/BlameStrategy.java
@@ -0,0 +1,25 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.strategy;
+
+public interface BlameStrategy {
+
+ DefaultBlameStrategy.BlameAlgorithmEnum getBlameAlgorithm(int availableProcessors, int numberOfFiles);
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/strategy/DefaultBlameStrategy.java b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/strategy/DefaultBlameStrategy.java
new file mode 100644
index 00000000000..d52aed22d0d
--- /dev/null
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/strategy/DefaultBlameStrategy.java
@@ -0,0 +1,80 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.strategy;
+
+import org.sonar.api.config.Configuration;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import static org.sonar.scm.git.strategy.DefaultBlameStrategy.BlameAlgorithmEnum.*;
+
+/**
+ * The blame strategy defines when the files-blame algorithm is used over jgit or native git algorithm.
+ * It has been found that the JGit/Git native algorithm performs better in certain circumstances, such as:
+ * - When we have many cores available for multi-threading
+ * - The number of files to be blame by the algorithm
+ * This two metrics are correlated:
+ * - The more threads available, the more it is favorable to use the JGit/Git native algorithm
+ * - The more files available, the more it is favorable to use the git-files-blame algorithm
+ */
+public class DefaultBlameStrategy implements BlameStrategy {
+
+ private static final Logger LOG = Loggers.get(DefaultBlameStrategy.class);
+ private static final int FILES_GIT_BLAME_TRIGGER = 10;
+ public static final String PROP_SONAR_SCM_USE_BLAME_ALGORITHM = "sonar.scm.use.blame.algorithm";
+ private final Configuration configuration;
+
+ public DefaultBlameStrategy(Configuration configuration) {
+ this.configuration = configuration;
+ }
+
+ @Override
+ public BlameAlgorithmEnum getBlameAlgorithm(int availableProcessors, int numberOfFiles) {
+ BlameAlgorithmEnum forcedStrategy = configuration.get(PROP_SONAR_SCM_USE_BLAME_ALGORITHM)
+ .map(BlameAlgorithmEnum::valueOf)
+ .orElse(null);
+
+ if (forcedStrategy != null) {
+ return forcedStrategy;
+ }
+
+ if (availableProcessors == 0) {
+ LOG.warn("Available processors are 0. Falling back to native git blame");
+ return GIT_NATIVE_BLAME;
+ }
+ if (numberOfFiles / availableProcessors > FILES_GIT_BLAME_TRIGGER) {
+ return GIT_FILES_BLAME;
+ }
+
+ return GIT_NATIVE_BLAME;
+ }
+
+ public enum BlameAlgorithmEnum {
+ /**
+ * Strategy using native git for the blame, or JGit on single file as a fallback
+ */
+ GIT_NATIVE_BLAME,
+ /**
+ * Strategy using git-files-blame algorithm
+ */
+ GIT_FILES_BLAME;
+ }
+
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/git/CompositeBlameCommandIT.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/CompositeBlameCommandIT.java
new file mode 100644
index 00000000000..3178cf68327
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/CompositeBlameCommandIT.java
@@ -0,0 +1,184 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultFileSystem;
+import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
+import org.sonar.api.batch.scm.BlameCommand;
+import org.sonar.api.batch.scm.BlameLine;
+import org.sonar.api.notifications.AnalysisWarnings;
+import org.sonar.api.scan.filesystem.PathResolver;
+import org.sonar.api.utils.System2;
+import org.sonar.scm.git.strategy.DefaultBlameStrategy.BlameAlgorithmEnum;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(DataProviderRunner.class)
+public class CompositeBlameCommandIT {
+
+ private final AnalysisWarnings analysisWarnings = mock(AnalysisWarnings.class);
+
+ private final BlameCommand.BlameInput input = mock(BlameCommand.BlameInput.class);
+ private final JGitBlameCommand jGitBlameCommand = new JGitBlameCommand();
+
+ private final ProcessWrapperFactory processWrapperFactory = new ProcessWrapperFactory();
+ private final NativeGitBlameCommand nativeGitBlameCommand = new NativeGitBlameCommand(System2.INSTANCE, processWrapperFactory);
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Test
+ @UseDataProvider("namesOfTheTestRepositoriesWithBlameAlgorithm")
+ public void testThatBlameAlgorithmOutputsTheSameDataAsGitNativeBlame(String folder, BlameAlgorithmEnum blameAlgorithm) throws Exception {
+ CompositeBlameCommand underTest = new CompositeBlameCommand(analysisWarnings, new PathResolver(), jGitBlameCommand, nativeGitBlameCommand, (p, f) -> blameAlgorithm);
+
+ TestBlameOutput output = new TestBlameOutput();
+ File gitFolder = unzipGitRepository(folder);
+
+ setUpBlameInputWithFile(gitFolder.toPath());
+
+ underTest.blame(input, output);
+
+ assertBlameMatchesExpectedBlame(output.blame, gitFolder);
+ }
+
+ @DataProvider
+ public static Object[][] namesOfTheTestRepositoriesWithBlameAlgorithm() {
+ List<String> testCases = List.of("one-file-one-commit",
+ "one-file-two-commits",
+ "two-files-one-commit",
+ "merge-commits",
+ "5lines-5commits",
+ "5files-5commits",
+ "two-files-moved-around-with-conflicts",
+ "one-file-renamed-many-times",
+ "one-file-many-merges-and-renames",
+ "two-merge-commits",
+ "dummy-git",
+ "dummy-git-few-comitters"
+ );
+
+ List<BlameAlgorithmEnum> blameStrategies = Arrays.stream(BlameAlgorithmEnum.values()).collect(Collectors.toList());
+ return testCases.stream()
+ .flatMap(t -> blameStrategies.stream().map(b -> new Object[]{t, b}))
+ .toArray(Object[][]::new);
+ }
+
+
+ private void assertBlameMatchesExpectedBlame(Map<InputFile, List<BlameLine>> blame, File gitFolder) throws Exception {
+ Map<Path, List<BlameLine>> expectedBlame = readExpectedBlame(gitFolder.getName());
+
+ assertThat(blame.entrySet())
+ .as("Blamed files: " + blame.keySet() + ". Expected blamed files " + expectedBlame.keySet())
+ .hasSize(expectedBlame.size());
+
+ for (Map.Entry<InputFile, List<BlameLine>> actualBlame : blame.entrySet()) {
+ Path relativeFilePath = gitFolder.toPath().relativize(actualBlame.getKey().path());
+ assertThat(actualBlame.getValue())
+ .as("A difference is found in file " + relativeFilePath)
+ .isEqualTo(expectedBlame.get(relativeFilePath));
+ }
+ }
+
+ // --- helper methods --- //
+
+ private Map<Path, List<BlameLine>> readExpectedBlame(String expectedBlameFolder) throws Exception {
+ Path expectedBlameFiles = new File(Utils.class.getResource("expected-blame/" + expectedBlameFolder).toURI()).toPath();
+ Map<Path, List<BlameLine>> expectedBlame = new HashMap<>();
+
+ List<Path> filesInExpectedBlameFolder = Files.walk(expectedBlameFiles).filter(Files::isRegularFile).collect(Collectors.toList());
+ for (Path expectedFileBlamePath : filesInExpectedBlameFolder) {
+ List<BlameLine> blameLines = new ArrayList<>();
+ List<String> expectedBlameStrings = Files.readAllLines(expectedFileBlamePath);
+ for (String line : expectedBlameStrings) {
+ String revision = line.substring(0, 40);
+
+ int beginningEmail = line.indexOf("<") + 1, endEmail = line.indexOf(">");
+ String email = line.substring(beginningEmail, endEmail);
+
+ int beginningDate = line.indexOf("2", endEmail), dateLength = 25;
+ String sDate = line.substring(beginningDate, beginningDate + dateLength);
+ Date parsedDate = new Date(OffsetDateTime.parse(sDate).toInstant().toEpochMilli());
+
+ BlameLine blameLine = new BlameLine()
+ .revision(revision)
+ .author(email)
+ .date(parsedDate);
+
+ blameLines.add(blameLine);
+ }
+ expectedBlame.put(expectedBlameFiles.relativize(expectedFileBlamePath), blameLines);
+ }
+ return expectedBlame;
+ }
+
+ private File unzipGitRepository(String repositoryName) throws IOException {
+ File gitFolderForEachTest = temp.newFolder().toPath().toRealPath(LinkOption.NOFOLLOW_LINKS).toFile();
+ Utils.javaUnzip(repositoryName + ".zip", gitFolderForEachTest);
+ return gitFolderForEachTest.toPath().resolve(repositoryName).toFile();
+ }
+
+ private static class TestBlameOutput implements BlameCommand.BlameOutput {
+ private final Map<InputFile, List<BlameLine>> blame = new ConcurrentHashMap<>();
+
+ @Override
+ public void blameResult(InputFile inputFile, List<BlameLine> list) {
+ blame.put(inputFile, list);
+ }
+ }
+
+ private void setUpBlameInputWithFile(Path baseDir) throws IOException {
+ DefaultFileSystem fs = new DefaultFileSystem(baseDir);
+ when(input.fileSystem()).thenReturn(fs);
+
+ try (Stream<Path> stream = Files.walk(baseDir)) {
+ List<InputFile> inputFiles = stream.filter(Files::isRegularFile)
+ .map(f -> new TestInputFileBuilder("foo", baseDir.toFile(), f.toFile()).build())
+ .filter(f -> !f.toString().startsWith(".git") && !f.toString().endsWith(".class"))
+ .collect(Collectors.toList());
+ when(input.filesToBlame()).thenReturn(inputFiles);
+ }
+ }
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/git/CompositeBlameCommandTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/CompositeBlameCommandTest.java
index 622196549bd..088ee40d541 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scm/git/CompositeBlameCommandTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/CompositeBlameCommandTest.java
@@ -19,6 +19,9 @@
*/
package org.sonar.scm.git;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@@ -30,10 +33,14 @@ import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
import org.apache.commons.io.FileUtils;
+import org.assertj.core.api.Condition;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DefaultFileSystem;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
@@ -46,26 +53,33 @@ import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.MessageException;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.scm.git.strategy.BlameStrategy;
+import org.sonar.scm.git.strategy.DefaultBlameStrategy.BlameAlgorithmEnum;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.intThat;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.scm.git.Utils.javaUnzip;
+import static org.sonar.scm.git.strategy.DefaultBlameStrategy.BlameAlgorithmEnum.GIT_FILES_BLAME;
+import static org.sonar.scm.git.strategy.DefaultBlameStrategy.BlameAlgorithmEnum.GIT_NATIVE_BLAME;
+@RunWith(DataProviderRunner.class)
public class CompositeBlameCommandTest {
private static final String DUMMY_JAVA = "src/main/java/org/dummy/Dummy.java";
- private final PathResolver pathResolver = new PathResolver();
private final ProcessWrapperFactory processWrapperFactory = new ProcessWrapperFactory();
private final JGitBlameCommand jGitBlameCommand = new JGitBlameCommand();
- private final GitBlameCommand gitBlameCommand = new GitBlameCommand(System2.INSTANCE, processWrapperFactory);
+ private final NativeGitBlameCommand nativeGitBlameCommand = new NativeGitBlameCommand(System2.INSTANCE, processWrapperFactory);
private final AnalysisWarnings analysisWarnings = mock(AnalysisWarnings.class);
- private final CompositeBlameCommand blameCommand = new CompositeBlameCommand(analysisWarnings, pathResolver, jGitBlameCommand, gitBlameCommand);
+
+ private final PathResolver pathResolver = new PathResolver();
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@@ -74,10 +88,15 @@ public class CompositeBlameCommandTest {
public LogTester logTester = new LogTester();
private final BlameCommand.BlameInput input = mock(BlameCommand.BlameInput.class);
+ @DataProvider
+ public static List<Object> blameAlgorithms() {
+ return Arrays.stream(BlameAlgorithmEnum.values()).collect(Collectors.toList());
+ }
+
@Test
public void use_jgit_if_native_git_disabled() throws IOException {
- GitBlameCommand gitCmd = new GitBlameCommand("invalidcommandnotfound", System2.INSTANCE, processWrapperFactory);
- BlameCommand blameCmd = new CompositeBlameCommand(analysisWarnings, pathResolver, jGitBlameCommand, gitCmd);
+ NativeGitBlameCommand gitCmd = new NativeGitBlameCommand("invalidcommandnotfound", System2.INSTANCE, processWrapperFactory);
+ BlameCommand blameCmd = new CompositeBlameCommand(analysisWarnings, pathResolver, jGitBlameCommand, gitCmd, (p, f) -> GIT_NATIVE_BLAME);
File projectDir = createNewTempFolder();
javaUnzip("dummy-git.zip", projectDir);
@@ -85,14 +104,33 @@ public class CompositeBlameCommandTest {
setUpBlameInputWithFile(baseDir.toPath());
TestBlameOutput output = new TestBlameOutput();
blameCmd.blame(input, output);
+
+ assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Using GIT_NATIVE_BLAME strategy to blame files");
assertThat(output.blame).hasSize(1);
assertThat(output.blame.get(input.filesToBlame().iterator().next())).hasSize(29);
}
@Test
+ public void blame_shouldCallStrategyWithCorrectSpecifications() throws IOException {
+
+ BlameStrategy strategyMock = mock(BlameStrategy.class);
+ BlameCommand blameCmd = new CompositeBlameCommand(analysisWarnings, pathResolver, jGitBlameCommand, nativeGitBlameCommand, strategyMock);
+
+ File projectDir = createNewTempFolder();
+ javaUnzip("dummy-git.zip", projectDir);
+
+ File baseDir = new File(projectDir, "dummy-git");
+ setUpBlameInputWithFile(baseDir.toPath());
+ TestBlameOutput output = new TestBlameOutput();
+ blameCmd.blame(input, output);
+
+ verify(strategyMock).getBlameAlgorithm(intThat((i) -> i > 0), intThat(i -> i == 1));
+ }
+
+ @Test
public void fallback_to_jgit_if_native_git_fails() throws Exception {
- GitBlameCommand gitCmd = mock(GitBlameCommand.class);
- BlameCommand blameCmd = new CompositeBlameCommand(analysisWarnings, pathResolver, jGitBlameCommand, gitCmd);
+ NativeGitBlameCommand gitCmd = mock(NativeGitBlameCommand.class);
+ BlameCommand blameCmd = new CompositeBlameCommand(analysisWarnings, pathResolver, jGitBlameCommand, gitCmd, (p, f) -> GIT_NATIVE_BLAME);
File projectDir = createNewTempFolder();
javaUnzip("dummy-git.zip", projectDir);
@@ -102,6 +140,8 @@ public class CompositeBlameCommandTest {
setUpBlameInputWithFile(baseDir.toPath());
TestBlameOutput output = new TestBlameOutput();
blameCmd.blame(input, output);
+
+ assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Using GIT_NATIVE_BLAME strategy to blame files");
assertThat(output.blame).hasSize(1);
assertThat(output.blame.get(input.filesToBlame().iterator().next())).hasSize(29);
@@ -111,12 +151,15 @@ public class CompositeBlameCommandTest {
}
@Test
- public void skip_files_not_committed() throws Exception {
+ @UseDataProvider("blameAlgorithms")
+ public void skip_files_not_committed(BlameAlgorithmEnum strategy) throws Exception {
// skip if git not installed
- assumeTrue(gitBlameCommand.checkIfEnabled());
+ if (strategy == GIT_NATIVE_BLAME) {
+ assumeTrue(nativeGitBlameCommand.checkIfEnabled());
+ }
JGitBlameCommand jgit = mock(JGitBlameCommand.class);
- BlameCommand blameCmd = new CompositeBlameCommand(analysisWarnings, pathResolver, jgit, gitBlameCommand);
+ BlameCommand blameCmd = new CompositeBlameCommand(analysisWarnings, pathResolver, jgit, nativeGitBlameCommand, (p, f) -> strategy);
File projectDir = createNewTempFolder();
javaUnzip("dummy-git.zip", projectDir);
@@ -126,18 +169,16 @@ public class CompositeBlameCommandTest {
blameCmd.blame(input, output);
assertThat(output.blame).hasSize(1);
assertThat(output.blame.get(input.filesToBlame().iterator().next())).hasSize(29);
-
- // never had to fall back to jgit
- verifyNoInteractions(jgit);
}
@Test
- public void skip_files_when_head_commit_is_missing() throws IOException {
+ @UseDataProvider("blameAlgorithms")
+ public void skip_files_when_head_commit_is_missing(BlameAlgorithmEnum strategy) throws IOException {
// skip if git not installed
- assumeTrue(gitBlameCommand.checkIfEnabled());
+ assumeTrue(nativeGitBlameCommand.checkIfEnabled());
JGitBlameCommand jgit = mock(JGitBlameCommand.class);
- BlameCommand blameCmd = new CompositeBlameCommand(analysisWarnings, pathResolver, jgit, gitBlameCommand);
+ BlameCommand blameCmd = new CompositeBlameCommand(analysisWarnings, pathResolver, jgit, nativeGitBlameCommand, (p, f) -> strategy);
File projectDir = createNewTempFolder();
javaUnzip("no-head-git.zip", projectDir);
@@ -149,31 +190,15 @@ public class CompositeBlameCommandTest {
assertThat(output.blame).isEmpty();
verifyNoInteractions(jgit);
- assertThat(logTester.logs())
+ assertThat(logTester.logs(LoggerLevel.WARN))
.contains("Could not find HEAD commit");
}
@Test
- public void use_native_git_by_default() throws IOException {
- // skip test if git is not installed
- assumeTrue(gitBlameCommand.checkIfEnabled());
- File projectDir = createNewTempFolder();
- javaUnzip("dummy-git.zip", projectDir);
- File baseDir = new File(projectDir, "dummy-git");
+ @UseDataProvider("blameAlgorithms")
+ public void return_early_when_shallow_clone_detected(BlameAlgorithmEnum strategy) throws IOException {
+ CompositeBlameCommand blameCommand = new CompositeBlameCommand(analysisWarnings, pathResolver, jGitBlameCommand, nativeGitBlameCommand, (p, f) -> strategy);
- JGitBlameCommand jgit = mock(JGitBlameCommand.class);
- BlameCommand blameCmd = new CompositeBlameCommand(analysisWarnings, pathResolver, jgit, gitBlameCommand);
-
- setUpBlameInputWithFile(baseDir.toPath());
- TestBlameOutput output = new TestBlameOutput();
- blameCmd.blame(input, output);
- assertThat(output.blame).hasSize(1);
- assertThat(output.blame.get(input.filesToBlame().iterator().next())).hasSize(29);
- verifyNoInteractions(jgit);
- }
-
- @Test
- public void return_early_when_shallow_clone_detected() throws IOException {
File projectDir = createNewTempFolder();
javaUnzip("shallow-git.zip", projectDir);
@@ -189,6 +214,8 @@ public class CompositeBlameCommandTest {
@Test
public void fail_if_not_git_project() throws IOException {
+ CompositeBlameCommand blameCommand = new CompositeBlameCommand(analysisWarnings, pathResolver, jGitBlameCommand, nativeGitBlameCommand, (p, f) -> GIT_FILES_BLAME);
+
File projectDir = createNewTempFolder();
javaUnzip("dummy-git.zip", projectDir);
@@ -207,7 +234,10 @@ public class CompositeBlameCommandTest {
}
@Test
- public void dont_fail_with_symlink() throws IOException {
+ @UseDataProvider("blameAlgorithms")
+ public void dont_fail_with_symlink(BlameAlgorithmEnum strategy) throws IOException {
+ CompositeBlameCommand blameCommand = new CompositeBlameCommand(analysisWarnings, pathResolver, jGitBlameCommand, nativeGitBlameCommand, (p, f) -> strategy);
+
assumeTrue(!System2.INSTANCE.isOsWindows());
File projectDir = createNewTempFolder();
javaUnzip("dummy-git.zip", projectDir);
@@ -229,10 +259,13 @@ public class CompositeBlameCommandTest {
when(input.filesToBlame()).thenReturn(Arrays.asList(inputFile, inputFile2));
TestBlameOutput output = new TestBlameOutput();
blameCommand.blame(input, output);
+ assertThat(output.blame).isNotEmpty();
}
@Test
public void return_early_when_clone_with_reference_detected() throws IOException {
+ CompositeBlameCommand blameCommand = new CompositeBlameCommand(analysisWarnings, pathResolver, jGitBlameCommand, nativeGitBlameCommand, (p, f) -> GIT_FILES_BLAME);
+
File projectDir = createNewTempFolder();
javaUnzip("dummy-git-reference-clone.zip", projectDir);
@@ -248,8 +281,9 @@ public class CompositeBlameCommandTest {
TestBlameOutput output = new TestBlameOutput();
blameCommand.blame(input, output);
- assertThat(logTester.logs()).first()
- .matches(s -> s.contains("This git repository references another local repository which is not well supported"));
+ assertThat(logTester.logs())
+ .haveAtLeastOne(new Condition<>(s-> s.startsWith("This git repository references another local repository which is not well supported"),
+ "log for reference detected"));
// contains commits referenced from the old clone and commits in the new clone
assertThat(output.blame).containsKey(inputFile);
@@ -259,7 +293,9 @@ public class CompositeBlameCommandTest {
}
@Test
- public void blame_on_nested_module() throws IOException {
+ @UseDataProvider("blameAlgorithms")
+ public void blame_on_nested_module(BlameAlgorithmEnum strategy) throws IOException {
+ CompositeBlameCommand blameCommand = new CompositeBlameCommand(analysisWarnings, pathResolver, jGitBlameCommand, nativeGitBlameCommand, (p, f) -> strategy);
File projectDir = createNewTempFolder();
javaUnzip("dummy-git-nested.zip", projectDir);
File baseDir = new File(projectDir, "dummy-git-nested/dummy-project");
@@ -271,40 +307,16 @@ public class CompositeBlameCommandTest {
fs.add(inputFile);
BlameCommand.BlameOutput blameResult = mock(BlameCommand.BlameOutput.class);
- when(input.filesToBlame()).thenReturn(Arrays.asList(inputFile));
+ when(input.filesToBlame()).thenReturn(List.of(inputFile));
blameCommand.blame(input, blameResult);
Date revisionDate = DateUtils.parseDateTime("2012-07-17T16:12:48+0200");
String revision = "6b3aab35a3ea32c1636fee56f996e677653c48ea";
String author = "david@gageot.net";
verify(blameResult).blameResult(inputFile,
- Arrays.asList(
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author),
- new BlameLine().revision(revision).date(revisionDate).author(author)));
+ IntStream.range(0, 26)
+ .mapToObj(i -> new BlameLine().revision(revision).date(revisionDate).author(author))
+ .collect(Collectors.toList()));
}
private BlameCommand.BlameInput setUpBlameInputWithFile(Path baseDir) {
@@ -317,7 +329,7 @@ public class CompositeBlameCommandTest {
}
private File createNewTempFolder() throws IOException {
- // This is needed for Windows, otherwise the created File point to invalid (shortened by Windows) temp folder path
+ // This is needed for Windows, otherwise the created File points to invalid (shortened by Windows) temp folder path
return temp.newFolder().toPath().toRealPath(LinkOption.NOFOLLOW_LINKS).toFile();
}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitScmProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitScmProviderTest.java
index 242b31fe372..cb7d1f68aec 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitScmProviderTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitScmProviderTest.java
@@ -54,6 +54,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import org.sonar.api.config.Configuration;
import org.sonar.api.notifications.AnalysisWarnings;
import org.sonar.api.scan.filesystem.PathResolver;
import org.sonar.api.utils.MessageException;
@@ -61,6 +62,7 @@ import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.LogAndArguments;
import org.sonar.api.utils.log.LogTester;
import org.sonar.core.documentation.DocumentationLinkGenerator;
+import org.sonar.scm.git.strategy.DefaultBlameStrategy;
import static java.lang.String.format;
import static java.util.Collections.emptySet;
@@ -145,8 +147,8 @@ public class GitScmProviderTest {
@Test
public void returnImplem() {
JGitBlameCommand jblameCommand = new JGitBlameCommand();
- GitBlameCommand nativeBlameCommand = new GitBlameCommand(System2.INSTANCE, new ProcessWrapperFactory());
- CompositeBlameCommand compositeBlameCommand = new CompositeBlameCommand(analysisWarnings, new PathResolver(), jblameCommand, nativeBlameCommand);
+ NativeGitBlameCommand nativeBlameCommand = new NativeGitBlameCommand(System2.INSTANCE, new ProcessWrapperFactory());
+ CompositeBlameCommand compositeBlameCommand = new CompositeBlameCommand(analysisWarnings, new PathResolver(), jblameCommand, nativeBlameCommand, new DefaultBlameStrategy(mock(Configuration.class)));
GitScmProvider gitScmProvider = new GitScmProvider(compositeBlameCommand, analysisWarnings, gitIgnoreCommand, system2, documentationLinkGenerator);
assertThat(gitScmProvider.blameCommand()).isEqualTo(compositeBlameCommand);
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitUtils.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitUtils.java
index 6257b1bb512..13b152994fb 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitUtils.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitUtils.java
@@ -40,5 +40,29 @@ public class GitUtils {
Files.write(newFile, content.getBytes(), StandardOpenOption.CREATE);
}
+ public static void createFile(Path worktree, String relativePath, String... lines) throws IOException {
+ Path newFile = worktree.resolve(relativePath);
+ Files.createDirectories(newFile.getParent());
+ String content = String.join(System.lineSeparator(), lines) + System.lineSeparator();
+ Files.write(newFile, content.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
+ }
+
+ public static void deleteFile(Path worktree, String relativePath) throws IOException {
+ Path fileToDelete = worktree.resolve(relativePath);
+ Files.delete(fileToDelete);
+ }
+
+ public static void copyFile(Path worktree, String origin, String dest) throws IOException {
+ Path originPath = worktree.resolve(origin);
+ Path destPath = worktree.resolve(dest);
+ Files.copy(originPath, destPath);
+ }
+
+ public static void moveFile(Path worktree, String origin, String dest) throws IOException {
+ Path originPath = worktree.resolve(origin);
+ Path destPath = worktree.resolve(dest);
+ Files.move(originPath, destPath);
+ }
+
}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/git/JGitBlameCommandTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/JGitBlameCommandTest.java
index 2100475da9d..af8a102f367 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scm/git/JGitBlameCommandTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/JGitBlameCommandTest.java
@@ -30,6 +30,7 @@ import java.util.List;
import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.Repository;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -53,15 +54,20 @@ public class JGitBlameCommandTest {
public LogTester logTester = new LogTester();
private final JGitBlameCommand jGitBlameCommand = new JGitBlameCommand();
+ private Path baseDir;
- @Test
- public void blame_returns_all_lines() throws IOException {
+ @Before
+ public void before() throws IOException {
File projectDir = createNewTempFolder();
javaUnzip("dummy-git.zip", projectDir);
+ baseDir = projectDir.toPath().resolve("dummy-git");
+ }
+
+ @Test
+ public void blame_returns_all_lines() {
- File baseDir = new File(projectDir, "dummy-git");
- try (Git git = loadRepository(baseDir.toPath())) {
+ try (Git git = loadRepository(baseDir)) {
List<BlameLine> blameLines = jGitBlameCommand.blame(git, DUMMY_JAVA);
Date revisionDate1 = DateUtils.parseDateTime("2012-07-17T16:12:48+0200");
@@ -90,10 +96,6 @@ public class JGitBlameCommandTest {
@Test
public void modified_file_returns_no_blame() throws IOException {
- File projectDir = createNewTempFolder();
- javaUnzip("dummy-git.zip", projectDir);
-
- Path baseDir = projectDir.toPath().resolve("dummy-git");
// Emulate a modification
Files.write(baseDir.resolve(DUMMY_JAVA), "modification and \n some new line".getBytes());
@@ -105,16 +107,12 @@ public class JGitBlameCommandTest {
@Test
public void new_file_returns_no_blame() throws IOException {
- File projectDir = createNewTempFolder();
- javaUnzip("dummy-git.zip", projectDir);
-
- File baseDir = new File(projectDir, "dummy-git");
String relativePath2 = "src/main/java/org/dummy/Dummy2.java";
// Emulate a new file
- FileUtils.copyFile(new File(baseDir, DUMMY_JAVA), new File(baseDir, relativePath2));
+ FileUtils.copyFile(new File(baseDir.toFile(), DUMMY_JAVA), new File(baseDir.toFile(), relativePath2));
- try (Git git = loadRepository(baseDir.toPath())) {
+ try (Git git = loadRepository(baseDir)) {
assertThat(jGitBlameCommand.blame(git, DUMMY_JAVA)).hasSize(29);
assertThat(jGitBlameCommand.blame(git, relativePath2)).isEmpty();
}
@@ -123,10 +121,6 @@ public class JGitBlameCommandTest {
@Test
public void symlink_doesnt_fail() throws IOException {
assumeTrue(!System2.INSTANCE.isOsWindows());
- File projectDir = temp.newFolder();
- javaUnzip("dummy-git.zip", projectDir);
-
- Path baseDir = projectDir.toPath().resolve("dummy-git");
String relativePath2 = "src/main/java/org/dummy/Dummy2.java";
// Create symlink
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitBlameCommandTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/NativeGitBlameCommandTest.java
index 834f02bd837..8bebca19a87 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scm/git/GitBlameCommandTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/NativeGitBlameCommandTest.java
@@ -19,6 +19,7 @@
*/
package org.sonar.scm.git;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@@ -35,6 +36,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
import org.sonar.api.batch.scm.BlameLine;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.System2;
@@ -51,15 +53,16 @@ import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static org.sonar.scm.git.GitBlameCommand.BLAME_COMMAND;
-import static org.sonar.scm.git.GitBlameCommand.GIT_DIR_ARGUMENT;
-import static org.sonar.scm.git.GitBlameCommand.GIT_DIR_FLAG;
-import static org.sonar.scm.git.GitBlameCommand.GIT_DIR_FORCE_FLAG;
+import static org.sonar.scm.git.NativeGitBlameCommand.BLAME_COMMAND;
+import static org.sonar.scm.git.NativeGitBlameCommand.GIT_DIR_ARGUMENT;
+import static org.sonar.scm.git.NativeGitBlameCommand.GIT_DIR_FLAG;
+import static org.sonar.scm.git.NativeGitBlameCommand.GIT_DIR_FORCE_FLAG;
import static org.sonar.scm.git.GitUtils.createFile;
import static org.sonar.scm.git.GitUtils.createRepository;
import static org.sonar.scm.git.Utils.javaUnzip;
-public class GitBlameCommandTest {
+@RunWith(DataProviderRunner.class)
+public class NativeGitBlameCommandTest {
private static final String DUMMY_JAVA = "src/main/java/org/dummy/Dummy.java";
@Rule
@@ -67,7 +70,7 @@ public class GitBlameCommandTest {
@Rule
public LogTester logTester = new LogTester();
private final ProcessWrapperFactory processWrapperFactory = new ProcessWrapperFactory();
- private final GitBlameCommand blameCommand = new GitBlameCommand(System2.INSTANCE, processWrapperFactory);
+ private final NativeGitBlameCommand blameCommand = new NativeGitBlameCommand(System2.INSTANCE, processWrapperFactory);
@Before
public void skipTestsIfNoGitFound() {
@@ -161,7 +164,7 @@ public class GitBlameCommandTest {
anyString(), anyString(), anyString(), anyString(), anyString(), anyString()))
.then(invocation -> mockProcess);
- GitBlameCommand blameCommand = new GitBlameCommand(gitCommand, System2.INSTANCE, mockFactory);
+ NativeGitBlameCommand blameCommand = new NativeGitBlameCommand(gitCommand, System2.INSTANCE, mockFactory);
blameCommand.blame(baseDir.toPath(), DUMMY_JAVA);
verify(mockFactory).create(any(), any(), eq(gitCommand),
@@ -204,27 +207,27 @@ public class GitBlameCommandTest {
@Test
public void git_should_be_detected() {
- GitBlameCommand blameCommand = new GitBlameCommand(System2.INSTANCE, processWrapperFactory);
+ NativeGitBlameCommand blameCommand = new NativeGitBlameCommand(System2.INSTANCE, processWrapperFactory);
assertThat(blameCommand.checkIfEnabled()).isTrue();
}
@Test
public void git_should_not_be_detected() {
- GitBlameCommand blameCommand = new GitBlameCommand("randomcmdthatwillneverbefound", System2.INSTANCE, processWrapperFactory);
+ NativeGitBlameCommand blameCommand = new NativeGitBlameCommand("randomcmdthatwillneverbefound", System2.INSTANCE, processWrapperFactory);
assertThat(blameCommand.checkIfEnabled()).isFalse();
}
@Test
public void git_should_not_be_enabled_if_version_command_is_not_found() {
ProcessWrapperFactory mockedCmd = mockGitVersionCommand("error: unknown option `version'");
- GitBlameCommand blameCommand = new GitBlameCommand(System2.INSTANCE, mockedCmd);
+ NativeGitBlameCommand blameCommand = new NativeGitBlameCommand(System2.INSTANCE, mockedCmd);
assertThat(blameCommand.checkIfEnabled()).isFalse();
}
@Test
public void git_should_not_be_enabled_if_version_command_does_not_return_string_output() {
ProcessWrapperFactory mockedCmd = mockGitVersionCommand(null);
- GitBlameCommand blameCommand = new GitBlameCommand(System2.INSTANCE, mockedCmd);
+ NativeGitBlameCommand blameCommand = new NativeGitBlameCommand(System2.INSTANCE, mockedCmd);
assertThat(blameCommand.checkIfEnabled()).isFalse();
}
@@ -244,7 +247,7 @@ public class GitBlameCommandTest {
return mock(ProcessWrapper.class);
});
- GitBlameCommand blameCommand = new GitBlameCommand(System2.INSTANCE, mockedCmd);
+ NativeGitBlameCommand blameCommand = new NativeGitBlameCommand(System2.INSTANCE, mockedCmd);
assertThat(blameCommand.checkIfEnabled()).isTrue();
});
}
@@ -252,14 +255,14 @@ public class GitBlameCommandTest {
@Test
public void git_should_not_be_enabled_if_version_is_less_than_required_minimum() {
ProcessWrapperFactory mockFactory = mockGitVersionCommand("git version 1.9.0");
- GitBlameCommand blameCommand = new GitBlameCommand(System2.INSTANCE, mockFactory);
+ NativeGitBlameCommand blameCommand = new NativeGitBlameCommand(System2.INSTANCE, mockFactory);
assertThat(blameCommand.checkIfEnabled()).isFalse();
}
@Test
public void throw_exception_if_command_fails() throws Exception {
Path baseDir = temp.newFolder().toPath();
- GitBlameCommand blameCommand = new GitBlameCommand("randomcmdthatwillneverbefound", System2.INSTANCE, processWrapperFactory);
+ NativeGitBlameCommand blameCommand = new NativeGitBlameCommand("randomcmdthatwillneverbefound", System2.INSTANCE, processWrapperFactory);
assertThatThrownBy(() -> blameCommand.blame(baseDir, "file")).isInstanceOf(IOException.class);
}
@@ -271,7 +274,7 @@ public class GitBlameCommandTest {
createFile(filePath, "line", baseDir);
commitWithNoEmail(git, filePath);
- GitBlameCommand blameCommand = new GitBlameCommand(System2.INSTANCE, processWrapperFactory);
+ NativeGitBlameCommand blameCommand = new NativeGitBlameCommand(System2.INSTANCE, processWrapperFactory);
assertThat(blameCommand.checkIfEnabled()).isTrue();
List<BlameLine> blame = blameCommand.blame(baseDir, filePath);
assertThat(blame).hasSize(1);
@@ -289,7 +292,7 @@ public class GitBlameCommandTest {
createFile(filePath, "line", baseDir);
commit(git, filePath, "my DOT name AT server DOT com");
- GitBlameCommand blameCommand = new GitBlameCommand(System2.INSTANCE, processWrapperFactory);
+ NativeGitBlameCommand blameCommand = new NativeGitBlameCommand(System2.INSTANCE, processWrapperFactory);
assertThat(blameCommand.checkIfEnabled()).isTrue();
List<BlameLine> blame = blameCommand.blame(baseDir, filePath);
assertThat(blame).hasSize(1);
@@ -304,7 +307,7 @@ public class GitBlameCommandTest {
createFile(filePath, "line", baseDir);
commitWithNoEmail(git, filePath);
- GitBlameCommand blameCommand = new GitBlameCommand(System2.INSTANCE, processWrapperFactory);
+ NativeGitBlameCommand blameCommand = new NativeGitBlameCommand(System2.INSTANCE, processWrapperFactory);
assertThat(blameCommand.checkIfEnabled()).isTrue();
List<BlameLine> blame = blameCommand.blame(baseDir, filePath);
assertThat(blame).hasSize(1);
@@ -329,7 +332,7 @@ public class GitBlameCommandTest {
return mockProcess;
});
- GitBlameCommand blameCommand = new GitBlameCommand(system2, mockFactory);
+ NativeGitBlameCommand blameCommand = new NativeGitBlameCommand(system2, mockFactory);
assertThat(blameCommand.checkIfEnabled()).isTrue();
assertThat(logTester.logs()).contains("Found git.exe at C:\\mockGit.exe");
}
@@ -343,7 +346,7 @@ public class GitBlameCommandTest {
ProcessWrapperFactory mockFactory = mock(ProcessWrapperFactory.class);
mockGitWhereOnWindows(mockFactory);
- GitBlameCommand blameCommand = new GitBlameCommand(system2, mockFactory);
+ NativeGitBlameCommand blameCommand = new NativeGitBlameCommand(system2, mockFactory);
assertThat(blameCommand.checkIfEnabled()).isFalse();
}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/git/strategy/DefaultBlameStrategyTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/strategy/DefaultBlameStrategyTest.java
new file mode 100644
index 00000000000..2cf351e5e33
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/strategy/DefaultBlameStrategyTest.java
@@ -0,0 +1,82 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.strategy;
+
+import java.util.Optional;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.scm.git.strategy.DefaultBlameStrategy.BlameAlgorithmEnum.GIT_FILES_BLAME;
+import static org.sonar.scm.git.strategy.DefaultBlameStrategy.BlameAlgorithmEnum.GIT_NATIVE_BLAME;
+
+public class DefaultBlameStrategyTest {
+
+ private final Configuration configuration = mock(Configuration.class);
+ private final BlameStrategy underTest = new DefaultBlameStrategy(configuration);
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ @Test
+ public void useRepositoryBlame_whenFileBlamePropsEnabled_shouldDisableRepoBlame() {
+ when(configuration.get(DefaultBlameStrategy.PROP_SONAR_SCM_USE_BLAME_ALGORITHM)).thenReturn(Optional.of(GIT_FILES_BLAME.name()));
+
+ assertThat(underTest.getBlameAlgorithm(1, 1)).isEqualTo(GIT_FILES_BLAME);
+
+ }
+
+ @Test
+ public void useRepositoryBlame_whenFileBlamePropsDisableOrUnspecified_shouldEnableRepoBlame() {
+ when(configuration.get(DefaultBlameStrategy.PROP_SONAR_SCM_USE_BLAME_ALGORITHM)).thenReturn(Optional.of(GIT_NATIVE_BLAME.name()));
+
+ assertThat(underTest.getBlameAlgorithm(1, 10000)).isEqualTo(GIT_NATIVE_BLAME);
+
+ }
+
+ @Test
+ public void useRepositoryBlame_whenFileBlamePropsInvalid_shouldThrowException() {
+ when(configuration.get(DefaultBlameStrategy.PROP_SONAR_SCM_USE_BLAME_ALGORITHM)).thenReturn(Optional.of("unknown"));
+
+ assertThatThrownBy(() -> underTest.getBlameAlgorithm(1, 1)).isInstanceOf(IllegalArgumentException.class);
+
+ }
+
+ @Test
+ public void useRepositoryBlame_whenProcessorsCountAndFileSizeSpecified_shouldEnableRepoBlame() {
+ when(configuration.getBoolean(DefaultBlameStrategy.PROP_SONAR_SCM_USE_BLAME_ALGORITHM)).thenReturn(Optional.empty());
+
+ assertThat(underTest.getBlameAlgorithm(1, 10000)).isEqualTo(GIT_FILES_BLAME);
+ assertThat(underTest.getBlameAlgorithm(8, 10)).isEqualTo(GIT_NATIVE_BLAME);
+
+ assertThat(underTest.getBlameAlgorithm(1, 10)).isEqualTo(GIT_NATIVE_BLAME);
+ assertThat(underTest.getBlameAlgorithm(1, 11)).isEqualTo(GIT_FILES_BLAME);
+
+
+ assertThat(underTest.getBlameAlgorithm(0, 10)).isEqualTo(GIT_NATIVE_BLAME);
+ assertThat(logTester.logs(LoggerLevel.WARN)).contains("Available processors are 0. Falling back to native git blame");
+ }
+}
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/fifth-file.js b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/fifth-file.js
new file mode 100644
index 00000000000..f87f63f495e
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/fifth-file.js
@@ -0,0 +1 @@
+37463a67cb907766a8fb2178fd4fc9a1b6f4f14f (<lukasz.jarocki@sonarsource.com> 2023-02-24T09:51:22+01:00 1) fifth-file - changed
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/first-file.js b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/first-file.js
new file mode 100644
index 00000000000..b2ad74c5667
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/first-file.js
@@ -0,0 +1 @@
+5c2578e72d07e95781682a3ce1be61a954e39df8 (<lukasz.jarocki@sonarsource.com> 2023-02-24T09:49:41+01:00 1) first-file
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/fourth-file.js b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/fourth-file.js
new file mode 100644
index 00000000000..cdb59898f57
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/fourth-file.js
@@ -0,0 +1 @@
+a7015bf40760ca91ae7d73d9102958fc870b30b9 (<lukasz.jarocki@sonarsource.com> 2023-02-24T09:51:08+01:00 1) fourth-file - changed
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/second-file.js b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/second-file.js
new file mode 100644
index 00000000000..1cf8ae4a139
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/second-file.js
@@ -0,0 +1 @@
+8bda33b1296875441f08c157140eb8504e13ebb2 (<lukasz.jarocki@sonarsource.com> 2023-02-24T09:49:59+01:00 1) second-file - changed
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/third-file.js b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/third-file.js
new file mode 100644
index 00000000000..9e9bcd22016
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5files-5commits/third-file.js
@@ -0,0 +1 @@
+442ca0c2c76fe6eee3246cae40fe36c077f4d4a0 (<lukasz.jarocki@sonarsource.com> 2023-02-24T09:50:13+01:00 1) third-file - changed
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5lines-5commits/5lines.js b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5lines-5commits/5lines.js
new file mode 100644
index 00000000000..eaa6ca66feb
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/5lines-5commits/5lines.js
@@ -0,0 +1,5 @@
+d0da62499e8476f7d7599b37d2f2201770cf6790 (<lukasz.jarocki@sonarsource.com> 2023-03-01T10:31:46+01:00 1) first line
+bed9b1f0ae87ecda5ef1c805e303e2295676317b (<lukasz.jarocki@sonarsource.com> 2023-03-01T10:32:16+01:00 2) second line
+939282feaabde0d049e739fd3e30937836c6fc13 (<lukasz.jarocki@sonarsource.com> 2023-03-01T10:32:33+01:00 3) third line
+f4cfbd0b9fe74daea9dd588ec41e8da71505e959 (<lukasz.jarocki@sonarsource.com> 2023-03-01T10:32:46+01:00 4) fourth line
+5fa3e610adad109fdc89cdfa68592ddd7a0ba3c6 (<lukasz.jarocki@sonarsource.com> 2023-03-01T10:33:03+01:00 5) fifth line
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/ReadMe.txt b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/ReadMe.txt
new file mode 100644
index 00000000000..9949224313b
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/ReadMe.txt
@@ -0,0 +1,6 @@
+This folder contains expected blame data for corresponding repositories (stored in test-repos folder). The data in expected-blame folder is
+produced by blaming each file with
+git blame {file_name_from_the_repository} --date=iso-strict --show-email -l --root > expected-blame/repository-name/{file_name_from_the_repository}
+
+The expected blame data is then used in the corresponding unit and integration tests to assert correctness of the new git blame algorithm
+implemented in the scanner engine. \ No newline at end of file
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git-few-comitters/pom.xml b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git-few-comitters/pom.xml
new file mode 100644
index 00000000000..2424e4faa60
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git-few-comitters/pom.xml
@@ -0,0 +1,52 @@
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 1) <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 2) <modelVersion>4.0.0</modelVersion>
+783905e3d8b3e6f89a63c573d5fcf60af7845ebe (<david@gageot.net> 2012-07-20T18:52:36+02:00 3) <groupId>dummy-git</groupId>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 4) <artifactId>dummy</artifactId>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 5) <version>1.0</version>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 6) <packaging>jar</packaging>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 7) <name>Dummy</name>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 8)
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 9) <properties>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 10) <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 11) </properties>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 12)
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 13) <build>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 14) <pluginManagement>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 15) <plugins>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 16) <plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 17) <artifactId>maven-clean-plugin</artifactId>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 18) <version>2.4.1</version>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 19) </plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 20) <plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 21) <artifactId>maven-compiler-plugin</artifactId>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 22) <version>2.3.2</version>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 23) </plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 24) <plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 25) <artifactId>maven-resources-plugin</artifactId>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 26) <version>2.5</version>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 27) </plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 28) <plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 29) <artifactId>maven-source-plugin</artifactId>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 30) <version>2.1.2</version>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 31) </plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 32) </plugins>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 33) </pluginManagement>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 34) <plugins>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 35) <plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 36) <artifactId>maven-compiler-plugin</artifactId>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 37) <configuration>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 38) <source>1.5</source>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 39) <target>1.5</target>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 40) </configuration>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 41) </plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 42) </plugins>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 43) </build>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 44)
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 45) <dependencies>
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 46) <dependency>
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 47) <groupId>junit</groupId>
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 48) <artifactId>junit</artifactId>
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 49) <version>4.10</version>
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 50) </dependency>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 51) </dependencies>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 52) </project>
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git-few-comitters/src/main/java/org/dummy/AnotherDummy.java b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git-few-comitters/src/main/java/org/dummy/AnotherDummy.java
new file mode 100644
index 00000000000..ffec5c679d1
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git-few-comitters/src/main/java/org/dummy/AnotherDummy.java
@@ -0,0 +1,29 @@
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 1) /*
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 2) * Sonar, open source software quality management tool.
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 3) * Copyright (C) 2008-2012 SonarSource
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 4) * mailto:contact AT sonarsource DOT com
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 5) *
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 6) * Sonar is free software; you can redistribute it and/or
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 7) * modify it under the terms of the GNU Lesser General Public
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 8) * License as published by the Free Software Foundation; either
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 9) * version 3 of the License, or (at your option) any later version.
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 10) *
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 11) * Sonar is distributed in the hope that it will be useful,
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 12) * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 13) * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 14) * Lesser General Public License for more details.
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 15) *
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 16) * You should have received a copy of the GNU Lesser General Public
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 17) * License along with Sonar; if not, write to the Free Software
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 18) * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 19) */
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 20) package org.dummy;
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 21)
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 22) public class AnotherDummy {
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 23) public String say(boolean hello) {
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 24) if (hello) {
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 25) return "Hello";
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 26) }
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 27) return new String("GoodBye");
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 28) }
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 29) }
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git-few-comitters/src/main/java/org/dummy/Dummy.java b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git-few-comitters/src/main/java/org/dummy/Dummy.java
new file mode 100644
index 00000000000..d54958921de
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git-few-comitters/src/main/java/org/dummy/Dummy.java
@@ -0,0 +1,29 @@
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 1) /*
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 2) * Sonar, open source software quality management tool.
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 3) * Copyright (C) 2008-2012 SonarSource
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 4) * mailto:contact AT sonarsource DOT com
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 5) *
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 6) * Sonar is free software; you can redistribute it and/or
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 7) * modify it under the terms of the GNU Lesser General Public
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 8) * License as published by the Free Software Foundation; either
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 9) * version 3 of the License, or (at your option) any later version.
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 10) *
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 11) * Sonar is distributed in the hope that it will be useful,
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 12) * but WITHOUT ANY WARRANTY; without even the implied warranty of
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 13) * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 14) * Lesser General Public License for more details.
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 15) *
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 16) * You should have received a copy of the GNU Lesser General Public
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 17) * License along with Sonar; if not, write to the Free Software
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 18) * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 19) */
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 20) package org.dummy;
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 21)
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 22) public class Dummy {
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 23) public String sayHello() {
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 24) return "Hello";
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 25) }
+7609f824d5ff7018bebf107cdbe4edcc901b574f (<duarte.meneses@sonarsource.com> 2022-10-11T14:14:26+02:00 26) public String sayBye() {
+7609f824d5ff7018bebf107cdbe4edcc901b574f (<duarte.meneses@sonarsource.com> 2022-10-11T14:14:26+02:00 27) return "Bye";
+7609f824d5ff7018bebf107cdbe4edcc901b574f (<duarte.meneses@sonarsource.com> 2022-10-11T14:14:26+02:00 28) }
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 29) }
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git-few-comitters/src/test/java/org/dummy/AnotherDummyTest.java b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git-few-comitters/src/test/java/org/dummy/AnotherDummyTest.java
new file mode 100644
index 00000000000..92c55be754a
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git-few-comitters/src/test/java/org/dummy/AnotherDummyTest.java
@@ -0,0 +1,31 @@
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 1) /*
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 2) * Sonar, open source software quality management tool.
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 3) * Copyright (C) 2008-2012 SonarSource
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 4) * mailto:contact AT sonarsource DOT com
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 5) *
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 6) * Sonar is free software; you can redistribute it and/or
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 7) * modify it under the terms of the GNU Lesser General Public
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 8) * License as published by the Free Software Foundation; either
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 9) * version 3 of the License, or (at your option) any later version.
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 10) *
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 11) * Sonar is distributed in the hope that it will be useful,
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 12) * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 13) * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 14) * Lesser General Public License for more details.
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 15) *
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 16) * You should have received a copy of the GNU Lesser General Public
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 17) * License along with Sonar; if not, write to the Free Software
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 18) * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 19) */
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 20) package org.dummy;
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 21)
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 22) import org.junit.Test;
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 23)
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 24) import static org.junit.Assert.assertEquals;
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 25)
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 26) public class AnotherDummyTest {
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 27) @Test
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 28) public void should_say_hello() {
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 29) assertEquals("Hello", new AnotherDummy().say(true));
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 30) }
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 31) }
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git/pom.xml b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git/pom.xml
new file mode 100644
index 00000000000..d770317afb6
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git/pom.xml
@@ -0,0 +1,52 @@
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 1) <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 2) <modelVersion>4.0.0</modelVersion>
+783905e3d8b3e6f89a63c573d5fcf60af7845ebe (<david@gageot.net> 2012-07-20T18:52:36+02:00 3) <groupId>dummy-git</groupId>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 4) <artifactId>dummy</artifactId>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 5) <version>1.0</version>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 6) <packaging>jar</packaging>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 7) <name>Dummy</name>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 8)
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 9) <properties>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 10) <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 11) </properties>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 12)
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 13) <build>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 14) <pluginManagement>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 15) <plugins>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 16) <plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 17) <artifactId>maven-clean-plugin</artifactId>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 18) <version>2.4.1</version>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 19) </plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 20) <plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 21) <artifactId>maven-compiler-plugin</artifactId>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 22) <version>2.3.2</version>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 23) </plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 24) <plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 25) <artifactId>maven-resources-plugin</artifactId>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 26) <version>2.5</version>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 27) </plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 28) <plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 29) <artifactId>maven-source-plugin</artifactId>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 30) <version>2.1.2</version>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 31) </plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 32) </plugins>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 33) </pluginManagement>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 34) <plugins>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 35) <plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 36) <artifactId>maven-compiler-plugin</artifactId>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 37) <configuration>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 38) <source>1.5</source>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 39) <target>1.5</target>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 40) </configuration>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 41) </plugin>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 42) </plugins>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 43) </build>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 44)
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 45) <dependencies>
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 46) <dependency>
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 47) <groupId>junit</groupId>
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 48) <artifactId>junit</artifactId>
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 49) <version>4.10</version>
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 50) </dependency>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 51) </dependencies>
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 52) </project>
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git/src/main/java/org/dummy/AnotherDummy.java b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git/src/main/java/org/dummy/AnotherDummy.java
new file mode 100644
index 00000000000..bf0da1e1e2d
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git/src/main/java/org/dummy/AnotherDummy.java
@@ -0,0 +1,29 @@
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 1) /*
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 2) * Sonar, open source software quality management tool.
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 3) * Copyright (C) 2008-2012 SonarSource
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 4) * mailto:contact AT sonarsource DOT com
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 5) *
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 6) * Sonar is free software; you can redistribute it and/or
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 7) * modify it under the terms of the GNU Lesser General Public
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 8) * License as published by the Free Software Foundation; either
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 9) * version 3 of the License, or (at your option) any later version.
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 10) *
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 11) * Sonar is distributed in the hope that it will be useful,
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 12) * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 13) * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 14) * Lesser General Public License for more details.
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 15) *
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 16) * You should have received a copy of the GNU Lesser General Public
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 17) * License along with Sonar; if not, write to the Free Software
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 18) * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 19) */
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 20) package org.dummy;
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 21)
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 22) public class AnotherDummy {
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 23) public String say(boolean hello) {
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 24) if (hello) {
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 25) return "Hello";
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 26) }
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 27) return new String("GoodBye");
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 28) }
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 29) }
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git/src/main/java/org/dummy/Dummy.java b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git/src/main/java/org/dummy/Dummy.java
new file mode 100644
index 00000000000..8245e31e6cc
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git/src/main/java/org/dummy/Dummy.java
@@ -0,0 +1,29 @@
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 1) /*
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 2) * Sonar, open source software quality management tool.
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 3) * Copyright (C) 2008-2012 SonarSource
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 4) * mailto:contact AT sonarsource DOT com
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 5) *
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 6) * Sonar is free software; you can redistribute it and/or
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 7) * modify it under the terms of the GNU Lesser General Public
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 8) * License as published by the Free Software Foundation; either
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 9) * version 3 of the License, or (at your option) any later version.
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 10) *
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 11) * Sonar is distributed in the hope that it will be useful,
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 12) * but WITHOUT ANY WARRANTY; without even the implied warranty of
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 13) * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 14) * Lesser General Public License for more details.
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 15) *
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 16) * You should have received a copy of the GNU Lesser General Public
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 17) * License along with Sonar; if not, write to the Free Software
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 18) * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 19) */
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 20) package org.dummy;
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 21)
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 22) public class Dummy {
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 23) public String sayHello() {
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 24) return "Hello";
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 25) }
+0d269c1acfb8e6d4d33f3c43041eb87e0df0f5e7 (<duarte.meneses@sonarsource.com> 2015-05-19T13:31:09+02:00 26) public String sayBye() {
+0d269c1acfb8e6d4d33f3c43041eb87e0df0f5e7 (<duarte.meneses@sonarsource.com> 2015-05-19T13:31:09+02:00 27) return "Bye";
+0d269c1acfb8e6d4d33f3c43041eb87e0df0f5e7 (<duarte.meneses@sonarsource.com> 2015-05-19T13:31:09+02:00 28) }
+6b3aab35a3ea32c1636fee56f996e677653c48ea (<david@gageot.net> 2012-07-17T16:12:48+02:00 29) }
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git/src/test/java/org/dummy/AnotherDummyTest.java b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git/src/test/java/org/dummy/AnotherDummyTest.java
new file mode 100644
index 00000000000..c7df309c4a2
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/dummy-git/src/test/java/org/dummy/AnotherDummyTest.java
@@ -0,0 +1,31 @@
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 1) /*
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 2) * Sonar, open source software quality management tool.
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 3) * Copyright (C) 2008-2012 SonarSource
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 4) * mailto:contact AT sonarsource DOT com
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 5) *
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 6) * Sonar is free software; you can redistribute it and/or
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 7) * modify it under the terms of the GNU Lesser General Public
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 8) * License as published by the Free Software Foundation; either
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 9) * version 3 of the License, or (at your option) any later version.
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 10) *
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 11) * Sonar is distributed in the hope that it will be useful,
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 12) * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 13) * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 14) * Lesser General Public License for more details.
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 15) *
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 16) * You should have received a copy of the GNU Lesser General Public
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 17) * License along with Sonar; if not, write to the Free Software
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 18) * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 19) */
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 20) package org.dummy;
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 21)
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 22) import org.junit.Test;
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 23)
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 24) import static org.junit.Assert.assertEquals;
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 25)
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 26) public class AnotherDummyTest {
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 27) @Test
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 28) public void should_say_hello() {
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 29) assertEquals("Hello", new AnotherDummy().say(true));
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 30) }
+ffb55a516440d069145dd9199e4ac67976b2338b (<david@gageot.net> 2012-07-20T15:26:23+02:00 31) }
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/merge-commits/merge-commit.js b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/merge-commits/merge-commit.js
new file mode 100644
index 00000000000..3fca0bd063a
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/merge-commits/merge-commit.js
@@ -0,0 +1,3 @@
+2b6ccf6d99dea2840613563aa54fdc5c8efc05cb (<lukasz.jarocki@sonarsource.com> 2023-03-01T14:43:49+01:00 1) first commit
+5a729e03b90e11d3d4f582a63cd2df93a7db41e0 (<lukasz.jarocki@sonarsource.com> 2023-03-01T14:44:47+01:00 2) second commit on master
+e62f234121479b8aa3bf60481324784c6ed6ba40 (<lukasz.jarocki@sonarsource.com> 2023-03-01T14:45:59+01:00 3) commit on branch \ No newline at end of file
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/one-file-many-merges-and-renames/file1.js b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/one-file-many-merges-and-renames/file1.js
new file mode 100644
index 00000000000..1a868d1d919
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/one-file-many-merges-and-renames/file1.js
@@ -0,0 +1,10 @@
+691242bf1b8319a7398be257c206203f7d6b8743 file1.js (<lukasz.jarocki@sonarsource.com> 2023-02-24T10:28:35+01:00 1) 111 - changed
+cb5f118c653af2e6d4d30f798714f45f5338c11c file.js (<lukasz.jarocki@sonarsource.com> 2023-02-24T10:29:03+01:00 2) 222 - changed
+84dd29d9b45487fecae9d952f1e71c5dea4cd356 file.js (<lukasz.jarocki@sonarsource.com> 2023-02-24T10:26:31+01:00 3) 333
+ea53e21a12061a733434454cf6cb8c6c29094fd0 file1.js (<lukasz.jarocki@sonarsource.com> 2023-02-24T10:33:06+01:00 4) 444 - changed in master
+84dd29d9b45487fecae9d952f1e71c5dea4cd356 file.js (<lukasz.jarocki@sonarsource.com> 2023-02-24T10:26:31+01:00 5) 555
+223e2f4e80d72199dae13b94690099dccc20eeea file1.js (<lukasz.jarocki@sonarsource.com> 2023-03-01T10:52:44+01:00 6) 666 - changed on branch1
+84dd29d9b45487fecae9d952f1e71c5dea4cd356 file.js (<lukasz.jarocki@sonarsource.com> 2023-02-24T10:26:31+01:00 7) 777
+84dd29d9b45487fecae9d952f1e71c5dea4cd356 file.js (<lukasz.jarocki@sonarsource.com> 2023-02-24T10:26:31+01:00 8) 888
+84dd29d9b45487fecae9d952f1e71c5dea4cd356 file.js (<lukasz.jarocki@sonarsource.com> 2023-02-24T10:26:31+01:00 9) 999
+c700a4b511aeecb225b4bd86dbe964f29730cffd file.js (<lukasz.jarocki@sonarsource.com> 2023-02-24T10:27:59+01:00 10) 100 - changed
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/one-file-one-commit/one-commit.js b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/one-file-one-commit/one-commit.js
new file mode 100644
index 00000000000..348567b955e
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/one-file-one-commit/one-commit.js
@@ -0,0 +1 @@
+23628a3b878ae8dd5e51042c7a39057a3e4ce45c (<lukasz.jarocki@sonarsource.com> 2023-02-23T14:29:45+01:00 1) hello world
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/one-file-renamed-many-times/renamed-many-times.js b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/one-file-renamed-many-times/renamed-many-times.js
new file mode 100644
index 00000000000..d9ca90d398a
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/one-file-renamed-many-times/renamed-many-times.js
@@ -0,0 +1,5 @@
+13bc62d2a546d41696e25e26ae44b3671f5ddf89 c.js (<lukasz.jarocki@sonarsource.com> 2023-02-24T10:16:08+01:00 1) initial-change
+13bc62d2a546d41696e25e26ae44b3671f5ddf89 c.js (<lukasz.jarocki@sonarsource.com> 2023-02-24T10:16:08+01:00 2) second-change
+13bc62d2a546d41696e25e26ae44b3671f5ddf89 c.js (<lukasz.jarocki@sonarsource.com> 2023-02-24T10:16:08+01:00 3) third-change
+3561639d487376f1d8c0297838fd2a94c5563d61 d.js (<lukasz.jarocki@sonarsource.com> 2023-02-24T10:16:39+01:00 4) fourth-change
+5123767d66854a431e9f6a05a88cf7db6e73aabc e.js (<lukasz.jarocki@sonarsource.com> 2023-02-24T10:17:15+01:00 5) fifth-change \ No newline at end of file
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/one-file-two-commits/two-commits.js b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/one-file-two-commits/two-commits.js
new file mode 100644
index 00000000000..4d40b8eadd9
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/one-file-two-commits/two-commits.js
@@ -0,0 +1,2 @@
+51c8ead53b248e93e66e5038c826524c0730f271 (<lukasz.jarocki@sonarsource.com> 2023-02-23T14:39:44+01:00 1) first line first commit
+b497efbe6a7ff547dee5dcc368b8920dba0fd908 (<lukasz.jarocki@sonarsource.com> 2023-02-23T14:40:18+01:00 2) second line second commit
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-files-moved-around-with-conflicts/firstcopy.js b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-files-moved-around-with-conflicts/firstcopy.js
new file mode 100644
index 00000000000..167d039fec9
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-files-moved-around-with-conflicts/firstcopy.js
@@ -0,0 +1 @@
+5ee03833d69a38d440a8685eb33dd96b0bea24e6 first.js (<lukasz.jarocki@sonarsource.com> 2023-03-02T11:47:42+01:00 1) commit on master, file: first.js \ No newline at end of file
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-files-moved-around-with-conflicts/secondcopy.js b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-files-moved-around-with-conflicts/secondcopy.js
new file mode 100644
index 00000000000..89de99f0a0a
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-files-moved-around-with-conflicts/secondcopy.js
@@ -0,0 +1 @@
+dab8b2a7c229b1111fbfe7c7dda7f9913ebb71f9 (<lukasz.jarocki@sonarsource.com> 2023-03-02T11:46:47+01:00 1) second commit, file: second.js \ No newline at end of file
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-files-one-commit/firstfile.js b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-files-one-commit/firstfile.js
new file mode 100644
index 00000000000..5ce08eaf95c
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-files-one-commit/firstfile.js
@@ -0,0 +1 @@
+a66e868e71a5e871ca9d9d74035adc0a743ac537 (<lukasz.jarocki@sonarsource.com> 2023-02-23T16:45:23+01:00 1) first file
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-files-one-commit/secondfile.js b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-files-one-commit/secondfile.js
new file mode 100644
index 00000000000..927595f0883
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-files-one-commit/secondfile.js
@@ -0,0 +1 @@
+a66e868e71a5e871ca9d9d74035adc0a743ac537 (<lukasz.jarocki@sonarsource.com> 2023-02-23T16:45:23+01:00 1) second file
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-merge-commits/two-merge-commits.js b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-merge-commits/two-merge-commits.js
new file mode 100644
index 00000000000..06935cc60fb
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/expected-blame/two-merge-commits/two-merge-commits.js
@@ -0,0 +1,4 @@
+48559ab7c378fb7181dc515967bd0dcfc71a8a57 (<lukasz.jarocki@sonarsource.com> 2023-03-01T14:54:30+01:00 1) first commit
+b5767c89ce517009ede7a6c7e69f46f4d2270fb5 (<lukasz.jarocki@sonarsource.com> 2023-03-01T14:56:53+01:00 2) fifth commit on master
+69cf0175d31c5a2c1517ac2d3515864324c436e9 (<lukasz.jarocki@sonarsource.com> 2023-03-01T14:55:02+01:00 3) second commit on branch
+c65549591670241841ae5882453ea05e51e3df20 (<lukasz.jarocki@sonarsource.com> 2023-03-01T14:56:33+01:00 4) fourth commit on branch \ No newline at end of file
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/5files-5commits.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/5files-5commits.zip
new file mode 100644
index 00000000000..2e5068acc6f
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/5files-5commits.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/5lines-5commits.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/5lines-5commits.zip
new file mode 100644
index 00000000000..0c441f8c674
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/5lines-5commits.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/dummy-git-few-comitters.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/dummy-git-few-comitters.zip
new file mode 100644
index 00000000000..fcef9cdf47c
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/dummy-git-few-comitters.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/merge-commits.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/merge-commits.zip
new file mode 100644
index 00000000000..60e9d2a1502
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/merge-commits.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/one-file-many-merges-and-renames.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/one-file-many-merges-and-renames.zip
new file mode 100644
index 00000000000..ce7e7b993ca
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/one-file-many-merges-and-renames.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/one-file-one-commit.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/one-file-one-commit.zip
new file mode 100644
index 00000000000..becfbf720e4
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/one-file-one-commit.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/one-file-renamed-many-times.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/one-file-renamed-many-times.zip
new file mode 100644
index 00000000000..2efd3dd0004
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/one-file-renamed-many-times.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/one-file-two-commits.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/one-file-two-commits.zip
new file mode 100644
index 00000000000..81b25b8b288
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/one-file-two-commits.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/two-files-moved-around-with-conflicts.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/two-files-moved-around-with-conflicts.zip
new file mode 100644
index 00000000000..1999eb6d207
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/two-files-moved-around-with-conflicts.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/two-files-one-commit.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/two-files-one-commit.zip
new file mode 100644
index 00000000000..d90349a6343
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/two-files-one-commit.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/two-merge-commits.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/two-merge-commits.zip
new file mode 100644
index 00000000000..3f8c40fe8ca
--- /dev/null
+++ b/sonar-scanner-engine/src/test/resources/org/sonar/scm/git/test-repos/two-merge-commits.zip
Binary files differ