aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-scanner-engine/src/main/java/org/sonar
diff options
context:
space:
mode:
authorJacek <jacek.poreda@sonarsource.com>2022-05-25 13:05:58 +0200
committersonartech <sonartech@sonarsource.com>2022-05-27 20:03:00 +0000
commitf82dbec738e0bc90fe652d144baf91bb73414433 (patch)
tree46a5a924654411dded8c6fe5c7eb55594b72c0f0 /sonar-scanner-engine/src/main/java/org/sonar
parente7870657ed000c2621fe111cc05dfafd47c2104c (diff)
downloadsonarqube-f82dbec738e0bc90fe652d144baf91bb73414433.tar.gz
sonarqube-f82dbec738e0bc90fe652d144baf91bb73414433.zip
SONAR-16416 Fix SSF-266
Diffstat (limited to 'sonar-scanner-engine/src/main/java/org/sonar')
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scm/git/CompositeBlameCommand.java2
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scm/git/GitBlameCommand.java100
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scm/git/GitScmSupport.java1
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scm/git/ProcessWrapperFactory.java88
4 files changed, 145 insertions, 46 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 762e20b65d0..a39b6401767 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
@@ -71,7 +71,7 @@ public class CompositeBlameCommand extends BlameCommand {
profiler.startDebug("Collecting committed files");
Set<String> committedFiles = collectAllCommittedFiles(repo);
profiler.stopDebug();
- nativeGitEnabled = nativeCmd.isEnabled();
+ nativeGitEnabled = nativeCmd.checkIfEnabled();
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new GitThreadFactory());
for (InputFile inputFile : input.filesToBlame()) {
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/GitBlameCommand.java
index 06a216d51ab..10733386574 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/GitBlameCommand.java
@@ -19,53 +19,60 @@
*/
package org.sonar.scm.git;
-import java.io.InputStream;
-import java.io.InputStreamReader;
+import java.io.IOException;
import java.nio.file.Path;
import java.time.Instant;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
-import java.util.Scanner;
-import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import javax.annotation.Nullable;
import org.sonar.api.batch.scm.BlameLine;
+import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.springframework.beans.factory.annotation.Autowired;
-import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptyList;
import static org.sonar.api.utils.Preconditions.checkState;
public class GitBlameCommand {
private static final Logger LOG = Loggers.get(GitBlameCommand.class);
private static final Pattern EMAIL_PATTERN = Pattern.compile("<(\\S*?)>");
- private static final String COMITTER_TIME = "committer-time ";
- private static final String COMITTER_MAIL = "committer-mail ";
+ private static final String COMMITTER_TIME = "committer-time ";
+ private static final String COMMITTER_MAIL = "committer-mail ";
- private static final String GIT_COMMAND = "git";
+ private static final String DEFAULT_GIT_COMMAND = "git";
private static final String BLAME_COMMAND = "blame";
private static final String BLAME_LINE_PORCELAIN_FLAG = "--line-porcelain";
private static final String IGNORE_WHITESPACES = "-w";
- private final String gitCommand;
+ private final System2 system;
+ private final ProcessWrapperFactory processWrapperFactory;
+ private String gitCommand;
@Autowired
- public GitBlameCommand() {
- this(GIT_COMMAND);
+ public GitBlameCommand(System2 system, ProcessWrapperFactory processWrapperFactory) {
+ this.system = system;
+ this.processWrapperFactory = processWrapperFactory;
}
- public GitBlameCommand(String gitCommand) {
+ GitBlameCommand(String gitCommand, System2 system, ProcessWrapperFactory processWrapperFactory) {
this.gitCommand = gitCommand;
+ this.system = system;
+ this.processWrapperFactory = processWrapperFactory;
}
- public boolean isEnabled() {
+ /**
+ * This method must be executed before org.sonar.scm.git.GitBlameCommand#blame
+ *
+ * @return true, if native git is installed
+ */
+ public boolean checkIfEnabled() {
try {
+ this.gitCommand = locateDefaultGit();
MutableString stdOut = new MutableString();
- executeCommand(null, l -> stdOut.string = l, gitCommand, "--version");
+ this.processWrapperFactory.create(null, l -> stdOut.string = l, gitCommand, "--version").execute();
return stdOut.string != null && stdOut.string.startsWith("git version");
} catch (Exception e) {
LOG.debug("Failed to find git native client", e);
@@ -73,10 +80,38 @@ public class GitBlameCommand {
}
}
+ private String locateDefaultGit() throws IOException {
+ if (this.gitCommand != null) {
+ return this.gitCommand;
+ }
+ // if not set fall back to defaults
+ if (system.isOsWindows()) {
+ return locateGitOnWindows();
+ }
+ return DEFAULT_GIT_COMMAND;
+ }
+
+ private String locateGitOnWindows() throws IOException {
+ // Windows will search current directory in addition to the PATH variable, which is unsecure.
+ // To avoid it we use where.exe to find git binary only in PATH.
+ LOG.debug("Looking for git command in the PATH using where.exe (Windows)");
+ List<String> whereCommandResult = new LinkedList<>();
+ this.processWrapperFactory.create(null, whereCommandResult::add, "C:\\Windows\\System32\\where.exe", "$PATH:git.exe")
+ .execute();
+
+ if (!whereCommandResult.isEmpty()) {
+ String out = whereCommandResult.get(0).trim();
+ LOG.debug("Found git.exe at {}", out);
+ return out;
+ }
+ throw new IllegalStateException("git.exe not found in PATH. PATH value was: " + system.property("PATH"));
+ }
+
public List<BlameLine> blame(Path baseDir, String fileName) throws Exception {
BlameOutputProcessor outputProcessor = new BlameOutputProcessor();
try {
- executeCommand(baseDir, outputProcessor::process, gitCommand, BLAME_COMMAND, BLAME_LINE_PORCELAIN_FLAG, IGNORE_WHITESPACES, fileName);
+ this.processWrapperFactory.create(baseDir, outputProcessor::process, gitCommand, BLAME_COMMAND, BLAME_LINE_PORCELAIN_FLAG, IGNORE_WHITESPACES, fileName)
+ .execute();
} catch (UncommittedLineException e) {
LOG.debug("Unable to blame file '{}' - it has uncommitted changes", fileName);
return emptyList();
@@ -84,31 +119,6 @@ public class GitBlameCommand {
return outputProcessor.getBlameLines();
}
- private static void executeCommand(@Nullable Path baseDir, Consumer<String> stdOutLineConsumer, String... command) throws Exception {
- ProcessBuilder pb = new ProcessBuilder()
- .command(command)
- .directory(baseDir != null ? baseDir.toFile() : null);
-
- Process p = pb.start();
- try {
- InputStream processStdOutput = p.getInputStream();
- // don't use BufferedReader#readLine because it will also parse CR, which may be part of the actual source code line
- try (Scanner scanner = new Scanner(new InputStreamReader(processStdOutput, UTF_8))) {
- scanner.useDelimiter("\n");
- while (scanner.hasNext()) {
- stdOutLineConsumer.accept(scanner.next());
- }
- }
-
- int exit = p.waitFor();
- if (exit != 0) {
- throw new IllegalStateException(String.format("Command execution exited with code: %d", exit));
- }
- } finally {
- p.destroy();
- }
- }
-
private static class BlameOutputProcessor {
private final List<BlameLine> blameLines = new LinkedList<>();
private String sha1 = null;
@@ -124,11 +134,11 @@ public class GitBlameCommand {
sha1 = line.split(" ")[0];
} else if (line.startsWith("\t")) {
saveEntry();
- } else if (line.startsWith(COMITTER_TIME)) {
- committerTime = line.substring(COMITTER_TIME.length());
- } else if (line.startsWith(COMITTER_MAIL)) {
+ } else if (line.startsWith(COMMITTER_TIME)) {
+ committerTime = line.substring(COMMITTER_TIME.length());
+ } else if (line.startsWith(COMMITTER_MAIL)) {
Matcher matcher = EMAIL_PATTERN.matcher(line);
- if (!matcher.find(COMITTER_MAIL.length()) || matcher.groupCount() != 1) {
+ if (!matcher.find(COMMITTER_MAIL.length()) || matcher.groupCount() != 1) {
throw new IllegalStateException("Couldn't parse committer email from: " + line);
}
committerMail = matcher.group(1);
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 a67bd56320d..65a8044dfbe 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
@@ -33,6 +33,7 @@ public final class GitScmSupport {
return Arrays.asList(
JGitBlameCommand.class,
CompositeBlameCommand.class,
+ ProcessWrapperFactory.class,
GitBlameCommand.class,
GitScmProvider.class,
GitIgnoreCommand.class);
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/ProcessWrapperFactory.java b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/ProcessWrapperFactory.java
new file mode 100644
index 00000000000..6d9c602d1c2
--- /dev/null
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/ProcessWrapperFactory.java
@@ -0,0 +1,88 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scm.git;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.file.Path;
+import java.util.Scanner;
+import java.util.function.Consumer;
+import javax.annotation.Nullable;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import static java.lang.String.format;
+import static java.lang.String.join;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public class ProcessWrapperFactory {
+ private static final Logger LOG = Loggers.get(ProcessWrapperFactory.class);
+
+ public ProcessWrapperFactory() {
+ // nothing to do
+ }
+
+ public ProcessWrapper create(@Nullable Path baseDir, Consumer<String> stdOutLineConsumer, String... command) {
+ return new ProcessWrapper(baseDir, stdOutLineConsumer, command);
+ }
+
+ static class ProcessWrapper {
+
+ private final Path baseDir;
+ private final Consumer<String> stdOutLineConsumer;
+ private final String[] command;
+
+ ProcessWrapper(@Nullable Path baseDir, Consumer<String> stdOutLineConsumer, String... command) {
+ this.baseDir = baseDir;
+ this.stdOutLineConsumer = stdOutLineConsumer;
+ this.command = command;
+ }
+
+ public void execute() throws IOException {
+ ProcessBuilder pb = new ProcessBuilder()
+ .command(command)
+ .directory(baseDir != null ? baseDir.toFile() : null);
+
+ Process p = pb.start();
+ try {
+ InputStream processStdOutput = p.getInputStream();
+ // don't use BufferedReader#readLine because it will also parse CR, which may be part of the actual source code line
+ try (Scanner scanner = new Scanner(new InputStreamReader(processStdOutput, UTF_8))) {
+ scanner.useDelimiter("\n");
+ while (scanner.hasNext()) {
+ stdOutLineConsumer.accept(scanner.next());
+ }
+ }
+
+ int exit = p.waitFor();
+ if (exit != 0) {
+ throw new IllegalStateException(format("Command execution exited with code: %d", exit));
+ }
+ } catch (InterruptedException e) {
+ LOG.warn(format("Command [%s] interrupted", join(" ", command)), e);
+ Thread.currentThread().interrupt();
+ } finally {
+ p.destroy();
+ }
+ }
+ }
+
+}