]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11950 autoconfig of SCM revision on Cirrus and Bitbucket
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Sun, 12 May 2019 13:12:42 +0000 (15:12 +0200)
committerSonarTech <sonartech@sonarsource.com>
Wed, 22 May 2019 18:21:18 +0000 (20:21 +0200)
19 files changed:
sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/CiConfiguration.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/CiConfigurationImpl.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/CiConfigurationProvider.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/CiVendor.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/package-info.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/vendors/BitbucketPipelines.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/vendors/CirrusCi.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/vendors/package-info.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ScanProperties.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmRevision.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmRevisionImpl.java [new file with mode: 0644]
sonar-scanner-engine/src/test/java/org/sonar/scanner/ci/CiConfigurationImplTest.java [new file with mode: 0644]
sonar-scanner-engine/src/test/java/org/sonar/scanner/ci/CiConfigurationProviderTest.java [new file with mode: 0644]
sonar-scanner-engine/src/test/java/org/sonar/scanner/ci/vendors/BitbucketPipelinesTest.java [new file with mode: 0644]
sonar-scanner-engine/src/test/java/org/sonar/scanner/ci/vendors/CirrusCiTest.java [new file with mode: 0644]
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MetadataPublisherTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmRevisionImplTest.java [new file with mode: 0644]

diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/CiConfiguration.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/CiConfiguration.java
new file mode 100644 (file)
index 0000000..fc1d3c6
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.scanner.ci;
+
+import java.util.Optional;
+
+/**
+ * Configuration provided by CI environments like TravisCI or Jenkins.
+ *
+ * @see CiVendor
+ */
+public interface CiConfiguration {
+
+  /**
+   * The revision that triggered the analysis. It should
+   * be the revision as seen by end-user, but not the necessarily
+   * the effective revision of the clone on disk (merge commit with
+   * base branch for instance).
+   */
+  Optional<String> getScmRevision();
+
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/CiConfigurationImpl.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/CiConfigurationImpl.java
new file mode 100644 (file)
index 0000000..aabd92b
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.scanner.ci;
+
+import java.util.Optional;
+import javax.annotation.Nullable;
+
+import static org.apache.commons.lang.StringUtils.defaultIfBlank;
+
+public class CiConfigurationImpl implements CiConfiguration {
+
+  @Nullable
+  private final String scmRevision;
+
+  public CiConfigurationImpl(@Nullable String scmRevision) {
+    this.scmRevision = defaultIfBlank(scmRevision, null);
+  }
+
+  @Override
+  public Optional<String> getScmRevision() {
+    return Optional.ofNullable(scmRevision);
+  }
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/CiConfigurationProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/CiConfigurationProvider.java
new file mode 100644 (file)
index 0000000..3803297
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.scanner.ci;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import org.picocontainer.injectors.ProviderAdapter;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+public class CiConfigurationProvider extends ProviderAdapter {
+
+  private static final Logger LOG = Loggers.get(CiConfigurationProvider.class);
+  private static final String PROP_DISABLED = "sonar.ci.autoconfig.disabled";
+
+  public CiConfiguration provide(Configuration configuration, CiVendor[] ciVendors) {
+    boolean disabled = configuration.getBoolean(PROP_DISABLED).orElse(false);
+    if (disabled) {
+      return new EmptyCiConfiguration();
+    }
+
+    List<CiVendor> detectedVendors = Arrays.stream(ciVendors)
+      .filter(CiVendor::isDetected)
+      .collect(Collectors.toList());
+
+    if (detectedVendors.size() > 1) {
+      List<String> names = detectedVendors.stream().map(CiVendor::getName).collect(Collectors.toList());
+      throw MessageException.of("Multiple CI environments are detected: " + names + ". Please check environment variables or set property " + PROP_DISABLED + " to true.");
+    }
+
+    if (detectedVendors.size() == 1) {
+      CiVendor vendor = detectedVendors.get(0);
+      LOG.info("Detected {}", vendor.getName());
+      return vendor.loadConfiguration();
+    }
+    return new EmptyCiConfiguration();
+  }
+
+  private static class EmptyCiConfiguration implements CiConfiguration {
+    @Override
+    public Optional<String> getScmRevision() {
+      return Optional.empty();
+    }
+  }
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/CiVendor.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/CiVendor.java
new file mode 100644 (file)
index 0000000..30aefcd
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.scanner.ci;
+
+import org.sonar.api.scanner.ScannerSide;
+
+@ScannerSide
+public interface CiVendor {
+
+  /**
+   * The display name, for example "Jenkins"
+   */
+  String getName();
+
+  /**
+   * Whether the analyser runs in the CI vendor or not.
+   */
+  boolean isDetected();
+
+  /**
+   * The configuration guessed by CI vendor. Called only
+   * if {@link #isDetected()} is true.
+   */
+  CiConfiguration loadConfiguration();
+
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/package-info.java
new file mode 100644 (file)
index 0000000..d7a896d
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.scanner.ci;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/vendors/BitbucketPipelines.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/vendors/BitbucketPipelines.java
new file mode 100644 (file)
index 0000000..7e86de9
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.scanner.ci.vendors;
+
+import org.sonar.api.utils.System2;
+import org.sonar.scanner.ci.CiConfiguration;
+import org.sonar.scanner.ci.CiConfigurationImpl;
+import org.sonar.scanner.ci.CiVendor;
+
+import static org.apache.commons.lang.StringUtils.isNotEmpty;
+
+public class BitbucketPipelines implements CiVendor {
+
+  private final System2 system;
+
+  public BitbucketPipelines(System2 system) {
+    this.system = system;
+  }
+
+  @Override
+  public String getName() {
+    return "Bitbucket Pipelines";
+  }
+
+  @Override
+  public boolean isDetected() {
+    String ci = system.envVariable("CI");
+    String revision = system.envVariable("BITBUCKET_COMMIT");
+    return "true".equals(ci) && isNotEmpty(revision);
+  }
+
+  @Override
+  public CiConfiguration loadConfiguration() {
+    String revision = system.envVariable("BITBUCKET_COMMIT");
+    return new CiConfigurationImpl(revision);
+  }
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/vendors/CirrusCi.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/vendors/CirrusCi.java
new file mode 100644 (file)
index 0000000..9e2bc92
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.scanner.ci.vendors;
+
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.scanner.ci.CiConfiguration;
+import org.sonar.scanner.ci.CiConfigurationImpl;
+import org.sonar.scanner.ci.CiVendor;
+
+import static org.apache.commons.lang.StringUtils.isEmpty;
+
+/**
+ * Support https://cirrus-ci.org/
+ *
+ * Environment variables are documented at https://cirrus-ci.org/guide/writing-tasks/#environment-variables
+ */
+public class CirrusCi implements CiVendor {
+
+  private static final String PROPERTY_COMMIT = "CIRRUS_CHANGE_IN_REPO";
+  private final System2 system;
+
+  public CirrusCi(System2 system) {
+    this.system = system;
+  }
+
+  @Override
+  public String getName() {
+    return "CirrusCI";
+  }
+
+  @Override
+  public boolean isDetected() {
+    return "true".equals(system.envVariable("CIRRUS_CI"));
+  }
+
+  @Override
+  public CiConfiguration loadConfiguration() {
+    String revision = system.envVariable(PROPERTY_COMMIT);
+    if (isEmpty(revision)) {
+      Loggers.get(getClass()).warn("Missing environment variable " + PROPERTY_COMMIT);
+    }
+    return new CiConfigurationImpl(revision);
+  }
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/vendors/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/ci/vendors/package-info.java
new file mode 100644 (file)
index 0000000..daae39d
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.scanner.ci.vendors;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index dc79f7262f453ceb901e5b78e9d6433096b7117b..06f20e70e95d20e4ddb1e81ef053054033e90de7 100644 (file)
@@ -23,7 +23,6 @@ import java.io.File;
 import java.nio.file.Path;
 import java.util.LinkedList;
 import java.util.Map.Entry;
-import java.util.Optional;
 import java.util.regex.Pattern;
 import javax.annotation.Nullable;
 import org.sonar.api.batch.fs.internal.AbstractProjectOrModule;
@@ -44,6 +43,7 @@ import org.sonar.scanner.rule.QualityProfiles;
 import org.sonar.scanner.scan.ScanProperties;
 import org.sonar.scanner.scan.branch.BranchConfiguration;
 import org.sonar.scanner.scm.ScmConfiguration;
+import org.sonar.scanner.scm.ScmRevision;
 
 import static java.util.Optional.ofNullable;
 
@@ -58,13 +58,14 @@ public class MetadataPublisher implements ReportPublisherStep {
   private final CpdSettings cpdSettings;
   private final ScannerPluginRepository pluginRepository;
   private final BranchConfiguration branchConfiguration;
+  private final ScmRevision scmRevision;
 
   @Nullable
   private final ScmConfiguration scmConfiguration;
 
   public MetadataPublisher(ProjectInfo projectInfo, InputModuleHierarchy moduleHierarchy, ScanProperties properties,
     QualityProfiles qProfiles, CpdSettings cpdSettings, ScannerPluginRepository pluginRepository, BranchConfiguration branchConfiguration,
-    @Nullable ScmConfiguration scmConfiguration) {
+    ScmRevision scmRevision, @Nullable ScmConfiguration scmConfiguration) {
     this.projectInfo = projectInfo;
     this.moduleHierarchy = moduleHierarchy;
     this.properties = properties;
@@ -72,12 +73,13 @@ public class MetadataPublisher implements ReportPublisherStep {
     this.cpdSettings = cpdSettings;
     this.pluginRepository = pluginRepository;
     this.branchConfiguration = branchConfiguration;
+    this.scmRevision = scmRevision;
     this.scmConfiguration = scmConfiguration;
   }
 
   public MetadataPublisher(ProjectInfo projectInfo, InputModuleHierarchy moduleHierarchy, ScanProperties properties,
-    QualityProfiles qProfiles, CpdSettings cpdSettings, ScannerPluginRepository pluginRepository, BranchConfiguration branchConfiguration) {
-    this(projectInfo, moduleHierarchy, properties, qProfiles, cpdSettings, pluginRepository, branchConfiguration, null);
+    QualityProfiles qProfiles, CpdSettings cpdSettings, ScannerPluginRepository pluginRepository, BranchConfiguration branchConfiguration, ScmRevision scmRevision) {
+    this(projectInfo, moduleHierarchy, properties, qProfiles, cpdSettings, pluginRepository, branchConfiguration, scmRevision, null);
   }
 
   @Override
@@ -100,9 +102,7 @@ public class MetadataPublisher implements ReportPublisherStep {
 
     ofNullable(rootProject.getBranch()).ifPresent(builder::setDeprecatedBranch);
 
-    if (scmConfiguration != null) {
-      addScmInformation(builder);
-    }
+    addScmInformation(builder);
 
     for (QProfile qp : qProfiles.findAll()) {
       builder.putQprofilesPerLanguage(qp.getLanguage(), ScannerReport.Metadata.QProfile.newBuilder()
@@ -134,33 +134,26 @@ public class MetadataPublisher implements ReportPublisherStep {
         builder.putModulesProjectRelativePathByKey(module.key(), relativePath);
       }
     }
-  }
 
-  private void addScmInformation(ScannerReport.Metadata.Builder builder) {
-    ScmProvider scmProvider = scmConfiguration.provider();
-    if (scmProvider != null) {
-      Path projectBasedir = moduleHierarchy.root().getBaseDir();
-      try {
-        builder.setRelativePathFromScmRoot(toSonarQubePath(scmProvider.relativePathFromScmRoot(projectBasedir)));
-      } catch (UnsupportedOperationException e) {
-        LOG.debug(e.getMessage());
-      }
-      try {
-        computeScmRevision().ifPresent(builder::setScmRevisionId);
-      } catch (UnsupportedOperationException e) {
-        LOG.debug(e.getMessage());
+    if (scmConfiguration != null) {
+      ScmProvider scmProvider = scmConfiguration.provider();
+      if (scmProvider != null) {
+        Path projectBasedir = moduleHierarchy.root().getBaseDir();
+        try {
+          builder.setRelativePathFromScmRoot(toSonarQubePath(scmProvider.relativePathFromScmRoot(projectBasedir)));
+        } catch (UnsupportedOperationException e) {
+          LOG.debug(e.getMessage());
+        }
       }
     }
   }
 
-  private Optional<String> computeScmRevision() {
-    Optional<String> scmRevision = properties.getScmRevision();
-    ScmProvider scmProvider = scmConfiguration.provider();
-    if (!scmRevision.isPresent() && scmProvider != null) {
-      scmRevision = Optional.ofNullable(scmProvider.revisionId(moduleHierarchy.root().getBaseDir()));
+  private void addScmInformation(ScannerReport.Metadata.Builder builder) {
+    try {
+      scmRevision.get().ifPresent(builder::setScmRevisionId);
+    } catch (UnsupportedOperationException e) {
+      LOG.debug(e.getMessage());
     }
-
-    return scmRevision;
   }
 
   private void addBranchInformation(ScannerReport.Metadata.Builder builder) {
index d31dfe56c1b8e883fa8e33e169d132449129edfd..edb43371cd2bc0e4d9faf819465ff69e14efd66c 100644 (file)
@@ -46,6 +46,9 @@ import org.sonar.scanner.bootstrap.GlobalAnalysisMode;
 import org.sonar.scanner.bootstrap.MetricProvider;
 import org.sonar.scanner.bootstrap.PostJobExtensionDictionnary;
 import org.sonar.scanner.bootstrap.ProcessedScannerProperties;
+import org.sonar.scanner.ci.CiConfigurationProvider;
+import org.sonar.scanner.ci.vendors.BitbucketPipelines;
+import org.sonar.scanner.ci.vendors.CirrusCi;
 import org.sonar.scanner.cpd.CpdExecutor;
 import org.sonar.scanner.cpd.CpdSettings;
 import org.sonar.scanner.cpd.index.SonarCpdBlockIndex;
@@ -109,6 +112,7 @@ import org.sonar.scanner.scan.measure.DefaultMetricFinder;
 import org.sonar.scanner.scm.ScmChangedFilesProvider;
 import org.sonar.scanner.scm.ScmConfiguration;
 import org.sonar.scanner.scm.ScmPublisher;
+import org.sonar.scanner.scm.ScmRevisionImpl;
 import org.sonar.scanner.sensor.DefaultSensorStorage;
 import org.sonar.scanner.sensor.ProjectSensorContext;
 import org.sonar.scanner.sensor.ProjectSensorExtensionDictionnary;
@@ -248,6 +252,7 @@ public class ProjectScanContainer extends ComponentContainer {
       // SCM
       ScmConfiguration.class,
       ScmPublisher.class,
+      ScmRevisionImpl.class,
 
       // Sensors
       DefaultSensorStorage.class,
@@ -260,6 +265,11 @@ public class ProjectScanContainer extends ComponentContainer {
       // Filesystem
       DefaultProjectFileSystem.class,
 
+      // CI
+      new CiConfigurationProvider(),
+      BitbucketPipelines.class,
+      CirrusCi.class,
+
       AnalysisObservers.class);
 
     addIfMissing(DefaultProjectSettingsLoader.class, ProjectSettingsLoader.class);
index 36fd9b5d41ede30b5710e0b0b53d0995bda76628..1033292a4b863004a9a98d6816aca06b39651932 100644 (file)
@@ -79,10 +79,6 @@ public class ScanProperties {
     }
   }
 
-  public Optional<String> getScmRevision() {
-    return configuration.get(SCM_REVISION);
-  }
-
   /**
    * This should be called in the beginning of the analysis to fail fast
    */
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmRevision.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmRevision.java
new file mode 100644 (file)
index 0000000..28af012
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.scanner.scm;
+
+import java.util.Optional;
+import org.sonar.api.scanner.ScannerSide;
+
+/**
+ * The SCM revision that triggered the analysis. It may be different than
+ * the effective revision checked-out on disk, as provided by {@link org.sonar.api.batch.scm.ScmProvider}.
+ *
+ * For instance on pull requests it's not the merge-commit that some CI services check out. It is
+ * the commit that was pushed by user to the branch.
+ */
+@ScannerSide
+public interface ScmRevision {
+
+  Optional<String> get();
+
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmRevisionImpl.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmRevisionImpl.java
new file mode 100644 (file)
index 0000000..6eb15be
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.scanner.scm;
+
+import java.util.Optional;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
+import org.sonar.api.batch.scm.ScmProvider;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.scanner.bootstrap.RawScannerProperties;
+import org.sonar.scanner.ci.CiConfiguration;
+
+import static org.apache.commons.lang.StringUtils.isBlank;
+import static org.sonar.scanner.scan.ScanProperties.SCM_REVISION;
+
+public class ScmRevisionImpl implements ScmRevision {
+
+  private static final Logger LOG = Loggers.get(ScmRevisionImpl.class);
+
+  private final CiConfiguration ciConfiguration;
+  private final RawScannerProperties scannerConfiguration;
+  private final ScmConfiguration scmConfiguration;
+  private final InputModuleHierarchy moduleHierarchy;
+
+  public ScmRevisionImpl(CiConfiguration ciConfiguration, RawScannerProperties scannerConfiguration, ScmConfiguration scmConfiguration, InputModuleHierarchy moduleHierarchy) {
+    this.ciConfiguration = ciConfiguration;
+    this.scannerConfiguration = scannerConfiguration;
+    this.scmConfiguration = scmConfiguration;
+    this.moduleHierarchy = moduleHierarchy;
+  }
+
+  @Override
+  public Optional<String> get() {
+    Optional<String> revision = Optional.ofNullable(scannerConfiguration.property(SCM_REVISION));
+    if (isSet(revision)) {
+      return revision;
+    }
+    revision = ciConfiguration.getScmRevision();
+    if (isSet(revision)) {
+      return revision;
+    }
+    ScmProvider scmProvider = scmConfiguration.provider();
+    if (scmProvider != null) {
+      try {
+        revision = Optional.ofNullable(scmProvider.revisionId(moduleHierarchy.root().getBaseDir()));
+      } catch (UnsupportedOperationException e) {
+        LOG.debug(e.getMessage());
+        revision = Optional.empty();
+      }
+    }
+    if (isSet(revision)) {
+      return revision;
+    }
+    return Optional.empty();
+  }
+
+  private static boolean isSet(Optional<String> opt) {
+    return opt.isPresent() && !isBlank(opt.get());
+  }
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/ci/CiConfigurationImplTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/ci/CiConfigurationImplTest.java
new file mode 100644 (file)
index 0000000..3b17b86
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.scanner.ci;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CiConfigurationImplTest {
+
+  @Test
+  public void getScmRevision() {
+    assertThat(new CiConfigurationImpl(null).getScmRevision()).isEmpty();
+    assertThat(new CiConfigurationImpl("").getScmRevision()).isEmpty();
+    assertThat(new CiConfigurationImpl("   ").getScmRevision()).isEmpty();
+    assertThat(new CiConfigurationImpl("a7bdf2d").getScmRevision()).hasValue("a7bdf2d");
+  }
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/ci/CiConfigurationProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/ci/CiConfigurationProviderTest.java
new file mode 100644 (file)
index 0000000..57e1aa0
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.scanner.ci;
+
+import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.utils.MessageException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.catchThrowable;
+
+public class CiConfigurationProviderTest {
+
+  private MapSettings cli = new MapSettings();
+  private CiConfigurationProvider underTest = new CiConfigurationProvider();
+
+  @Test
+  public void empty_configuration_if_no_ci_vendors() {
+    CiConfiguration CiConfiguration = underTest.provide(cli.asConfig(), new CiVendor[0]);
+
+    assertThat(CiConfiguration.getScmRevision()).isEmpty();
+  }
+
+  @Test
+  public void empty_configuration_if_no_ci_detected() {
+    CiConfiguration ciConfiguration = underTest.provide(cli.asConfig(), new CiVendor[] {new DisabledCiVendor("vendor1"), new DisabledCiVendor("vendor2")});
+
+    assertThat(ciConfiguration.getScmRevision()).isEmpty();
+  }
+
+  @Test
+  public void configuration_defined_by_ci_vendor() {
+    CiConfiguration ciConfiguration = underTest.provide(cli.asConfig(), new CiVendor[] {new DisabledCiVendor("vendor1"), new EnabledCiVendor("vendor2")});
+
+    assertThat(ciConfiguration.getScmRevision()).hasValue(EnabledCiVendor.SHA);
+  }
+
+  @Test
+  public void fail_if_multiple_ci_vendor_are_detected() {
+    Throwable thrown = catchThrowable(() -> underTest.provide(cli.asConfig(), new CiVendor[] {new EnabledCiVendor("vendor1"), new EnabledCiVendor("vendor2")}));
+
+    assertThat(thrown)
+      .isInstanceOf(MessageException.class)
+      .hasMessage("Multiple CI environments are detected: [vendor1, vendor2]. Please check environment variables or set property sonar.ci.autoconfig.disabled to true.");
+  }
+
+  @Test
+  public void empty_configuration_if_auto_configuration_is_disabled() {
+    cli.setProperty("sonar.ci.autoconfig.disabled", true);
+    CiConfiguration ciConfiguration = underTest.provide(cli.asConfig(), new CiVendor[] {new EnabledCiVendor("vendor1")});
+
+    assertThat(ciConfiguration.getScmRevision()).isEmpty();
+  }
+
+  private static class DisabledCiVendor implements CiVendor {
+    private final String name;
+
+    private DisabledCiVendor(String name) {
+      this.name = name;
+    }
+
+    @Override
+    public String getName() {
+      return name;
+    }
+
+    @Override
+    public boolean isDetected() {
+      return false;
+    }
+
+    @Override
+    public CiConfiguration loadConfiguration() {
+      throw new IllegalStateException("should not be called");
+    }
+  }
+
+  private static class EnabledCiVendor implements CiVendor {
+    private static final String SHA = "abc12df";
+    private final String name;
+
+    private EnabledCiVendor(String name) {
+      this.name = name;
+    }
+
+    @Override
+    public String getName() {
+      return name;
+    }
+
+    @Override
+    public boolean isDetected() {
+      return true;
+    }
+
+    @Override
+    public CiConfiguration loadConfiguration() {
+      return new CiConfigurationImpl(SHA);
+    }
+  }
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/ci/vendors/BitbucketPipelinesTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/ci/vendors/BitbucketPipelinesTest.java
new file mode 100644 (file)
index 0000000..397c1c4
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.scanner.ci.vendors;
+
+import javax.annotation.Nullable;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.scanner.ci.CiVendor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class BitbucketPipelinesTest {
+
+  private System2 system = mock(System2.class);
+  private CiVendor underTest = new BitbucketPipelines(system);
+
+  @Test
+  public void getName() {
+    assertThat(underTest.getName()).isEqualTo("Bitbucket Pipelines");
+  }
+
+  @Test
+  public void isDetected() {
+    setEnvVariable("CI", "true");
+    setEnvVariable("BITBUCKET_COMMIT", "bdf12fe");
+    assertThat(underTest.isDetected()).isTrue();
+
+    setEnvVariable("CI", "true");
+    setEnvVariable("BITBUCKET_COMMIT", null);
+    assertThat(underTest.isDetected()).isFalse();
+  }
+
+  @Test
+  public void configuration_of_pull_request() {
+    setEnvVariable("CI", "true");
+    setEnvVariable("BITBUCKET_COMMIT", "abd12fc");
+    setEnvVariable("BITBUCKET_PR_ID", "1234");
+
+    assertThat(underTest.loadConfiguration().getScmRevision()).hasValue("abd12fc");
+  }
+
+  @Test
+  public void configuration_of_branch() {
+    setEnvVariable("CI", "true");
+    setEnvVariable("BITBUCKET_COMMIT", "abd12fc");
+    setEnvVariable("BITBUCKET_PR_ID", null);
+
+    assertThat(underTest.loadConfiguration().getScmRevision()).hasValue("abd12fc");
+  }
+
+  private void setEnvVariable(String key, @Nullable String value) {
+    when(system.envVariable(key)).thenReturn(value);
+  }
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/ci/vendors/CirrusCiTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/ci/vendors/CirrusCiTest.java
new file mode 100644 (file)
index 0000000..6e7f988
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.scanner.ci.vendors;
+
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.scanner.ci.CiConfiguration;
+import org.sonar.scanner.ci.CiVendor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CirrusCiTest {
+
+  private System2 system = mock(System2.class);
+  private CiVendor underTest = new CirrusCi(system);
+
+  @Rule
+  public LogTester logs = new LogTester();
+
+  @Test
+  public void getName() {
+    assertThat(underTest.getName()).isEqualTo("CirrusCI");
+  }
+
+  @Test
+  public void isDetected() {
+    setEnvVariable("CIRRUS_CI", "true");
+    assertThat(underTest.isDetected()).isTrue();
+
+    setEnvVariable("CIRRUS_CI", null);
+    assertThat(underTest.isDetected()).isFalse();
+  }
+
+  @Test
+  public void configuration_of_pull_request() {
+    setEnvVariable("CIRRUS_PR", "1234");
+    setEnvVariable("CIRRUS_BASE_SHA", "abd12fc");
+    setEnvVariable("CIRRUS_CHANGE_IN_REPO", "fd355db");
+
+    assertThat(underTest.loadConfiguration().getScmRevision()).hasValue("fd355db");
+  }
+
+  @Test
+  public void configuration_of_branch() {
+    setEnvVariable("CIRRUS_CHANGE_IN_REPO", "abd12fc");
+
+    assertThat(underTest.loadConfiguration().getScmRevision()).hasValue("abd12fc");
+  }
+
+  @Test
+  public void log_warning_if_missing_commit_variable() {
+    setEnvVariable("CIRRUS_PR", "1234");
+
+    CiConfiguration configuration = underTest.loadConfiguration();
+
+    assertThat(configuration.getScmRevision()).isEmpty();
+    assertThat(logs.logs(LoggerLevel.WARN)).contains("Missing environment variable CIRRUS_CHANGE_IN_REPO");
+  }
+
+  private void setEnvVariable(String key, @Nullable String value) {
+    when(system.envVariable(key)).thenReturn(value);
+  }
+}
index cf83df2448d8cd28d890cba7b03bf529928fbaac..3c1d845f7856b1aaaf4c74770d268e877352dc96 100644 (file)
@@ -56,6 +56,7 @@ import org.sonar.scanner.scan.ScanProperties;
 import org.sonar.scanner.scan.branch.BranchConfiguration;
 import org.sonar.scanner.scan.branch.BranchType;
 import org.sonar.scanner.scm.ScmConfiguration;
+import org.sonar.scanner.scm.ScmRevision;
 
 import static java.util.Arrays.asList;
 import static java.util.Collections.emptyMap;
@@ -83,6 +84,7 @@ public class MetadataPublisherTest {
   private BranchConfiguration branches;
   private ScmConfiguration scmConfiguration;
   private ScmProvider scmProvider = mock(ScmProvider.class);
+  private ScmRevision scmRevision = mock(ScmRevision.class);
 
   @Before
   public void prepare() throws IOException {
@@ -115,7 +117,7 @@ public class MetadataPublisherTest {
     scmConfiguration = mock(ScmConfiguration.class);
     when(scmConfiguration.provider()).thenReturn(scmProvider);
     underTest = new MetadataPublisher(projectInfo, inputModuleHierarchy, properties, qProfiles, cpdSettings,
-      pluginRepository, branches, scmConfiguration);
+      pluginRepository, branches, scmRevision, scmConfiguration);
   }
 
   @Test
@@ -294,7 +296,7 @@ public class MetadataPublisherTest {
   @Test
   public void write_revision_id() throws Exception {
     String revisionId = "some-sha1";
-    when(scmProvider.revisionId(any(Path.class))).thenReturn(revisionId);
+    when(scmRevision.get()).thenReturn(Optional.of(revisionId));
 
     File outputDir = temp.newFolder();
     underTest.publish(new ScannerReportWriter(outputDir));
@@ -304,58 +306,13 @@ public class MetadataPublisherTest {
     assertThat(metadata.getScmRevisionId()).isEqualTo(revisionId);
   }
 
-  public void revision_from_scanner_props_overrides_scm_provider_revision_if_specified() throws IOException {
-    String revisionId = "some-sha1";
-    when(scmProvider.revisionId(any(Path.class))).thenReturn(revisionId);
-    when(properties.getScmRevision()).thenReturn(Optional.of("123"));
-
-    File outputDir = temp.newFolder();
-    underTest.publish(new ScannerReportWriter(outputDir));
-
-    ScannerReportReader reader = new ScannerReportReader(outputDir);
-    ScannerReport.Metadata metadata = reader.readMetadata();
-    assertThat(metadata.getScmRevisionId()).isEqualTo("123");
-  }
-
   @Test
   public void should_not_crash_when_scm_provider_does_not_support_relativePathFromScmRoot() throws IOException {
-    String revisionId = "some-sha1";
-
     ScmProvider fakeScmProvider = new ScmProvider() {
       @Override
       public String key() {
         return "foo";
       }
-
-      @Override
-      public String revisionId(Path path) {
-        return revisionId;
-      }
-    };
-    when(scmConfiguration.provider()).thenReturn(fakeScmProvider);
-
-    File outputDir = temp.newFolder();
-    underTest.publish(new ScannerReportWriter(outputDir));
-
-    ScannerReportReader reader = new ScannerReportReader(outputDir);
-    ScannerReport.Metadata metadata = reader.readMetadata();
-    assertThat(metadata.getScmRevisionId()).isEqualTo(revisionId);
-  }
-
-  @Test
-  public void should_not_crash_when_scm_provider_does_not_support_revisionId() throws IOException {
-    String relativePathFromScmRoot = "some/path";
-
-    ScmProvider fakeScmProvider = new ScmProvider() {
-      @Override
-      public String key() {
-        return "foo";
-      }
-
-      @Override
-      public Path relativePathFromScmRoot(Path path) {
-        return Paths.get(relativePathFromScmRoot);
-      }
     };
     when(scmConfiguration.provider()).thenReturn(fakeScmProvider);
 
@@ -364,6 +321,6 @@ public class MetadataPublisherTest {
 
     ScannerReportReader reader = new ScannerReportReader(outputDir);
     ScannerReport.Metadata metadata = reader.readMetadata();
-    assertThat(metadata.getRelativePathFromScmRoot()).isEqualTo(relativePathFromScmRoot);
+    assertThat(metadata.getRelativePathFromScmRoot()).isEmpty();
   }
 }
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmRevisionImplTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmRevisionImplTest.java
new file mode 100644 (file)
index 0000000..ff6ca35
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.scanner.scm;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.junit.Test;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
+import org.sonar.scanner.bootstrap.RawScannerProperties;
+import org.sonar.scanner.ci.CiConfiguration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ScmRevisionImplTest {
+
+  @Test
+  public void test_priority_of_revision_sources() {
+    assertThat(testGet(null, null, null)).isEmpty();
+    assertThat(testGet("1b34d77", null, null)).hasValue("1b34d77");
+    assertThat(testGet(null, "1b34d77", null)).hasValue("1b34d77");
+    assertThat(testGet(null, null, "1b34d77")).hasValue("1b34d77");
+    assertThat(testGet("1b34d77", "f6e62c5", "f6e62c5")).hasValue("1b34d77");
+    assertThat(testGet(null, "1b34d77", "f6e62c5")).hasValue("1b34d77");
+  }
+
+  @Test
+  public void test_empty_values() {
+    assertThat(testGet("", "", "")).isEmpty();
+    assertThat(testGet("1b34d77", "", "")).hasValue("1b34d77");
+    assertThat(testGet("", "1b34d77", "")).hasValue("1b34d77");
+    assertThat(testGet("", "", "1b34d77")).hasValue("1b34d77");
+    assertThat(testGet("1b34d77", "f6e62c5", "f6e62c5")).hasValue("1b34d77");
+    assertThat(testGet("", "1b34d77", "f6e62c5")).hasValue("1b34d77");
+  }
+
+  @Test
+  public void ignore_failure_if_scm_does_not_support_revisions() {
+    CiConfiguration ciConfiguration = mock(CiConfiguration.class);
+    when(ciConfiguration.getScmRevision()).thenReturn(Optional.empty());
+    Map<String,String> scannerConfiguration = new HashMap<>();
+    ScmConfiguration scmConfiguration = mock(ScmConfiguration.class, RETURNS_DEEP_STUBS);
+    when(scmConfiguration.provider().revisionId(any())).thenThrow(new UnsupportedOperationException("BOOM"));
+    InputModuleHierarchy moduleHierarchy = mock(InputModuleHierarchy.class, RETURNS_DEEP_STUBS);
+
+    ScmRevisionImpl underTest = new ScmRevisionImpl(ciConfiguration, new RawScannerProperties(scannerConfiguration), scmConfiguration, moduleHierarchy);
+
+    assertThat(underTest.get()).isEmpty();
+  }
+
+  private Optional<String> testGet(@Nullable String cliValue, @Nullable String ciValue, @Nullable String scmValue) {
+    CiConfiguration ciConfiguration = mock(CiConfiguration.class);
+    when(ciConfiguration.getScmRevision()).thenReturn(Optional.ofNullable(ciValue));
+    Map<String,String> scannerConfiguration = new HashMap<>();
+    scannerConfiguration.put("sonar.scm.revision", cliValue);
+    ScmConfiguration scmConfiguration = mock(ScmConfiguration.class, RETURNS_DEEP_STUBS);
+    when(scmConfiguration.provider().revisionId(any())).thenReturn(scmValue);
+    InputModuleHierarchy moduleHierarchy = mock(InputModuleHierarchy.class, RETURNS_DEEP_STUBS);
+
+    ScmRevisionImpl underTest = new ScmRevisionImpl(ciConfiguration, new RawScannerProperties(scannerConfiguration), scmConfiguration, moduleHierarchy);
+    return underTest.get();
+  }
+}