]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5644 Rework SCM API
authorJulien HENRY <julien.henry@sonarsource.com>
Thu, 25 Sep 2014 11:48:35 +0000 (13:48 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Thu, 2 Oct 2014 15:52:23 +0000 (17:52 +0200)
26 files changed:
plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitBlameCommand.java [new file with mode: 0644]
plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitBlameConsumer.java [new file with mode: 0644]
plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitPlugin.java
plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitScmProvider.java
plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/SonarGitBlameConsumer.java [deleted file]
plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/GitBlameCommandTest.java [new file with mode: 0644]
plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/GitPluginTest.java
plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/GitScmProviderTest.java [new file with mode: 0644]
plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/medium/GitMediumTest.java [deleted file]
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooScmProvider.java [deleted file]
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/package-info.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/XooBlameCommand.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/XooScmProvider.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/package-info.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/OneIssuePerLineSensorTest.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/scm/XooBlameCommandTest.java [new file with mode: 0644]
pom.xml
sonar-batch/src/main/java/org/sonar/batch/scm/ScmActivitySensor.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/BlameCommand.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/BlameLine.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/ScmProvider.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/internal/DefaultTestCase.java

diff --git a/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitBlameCommand.java b/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitBlameCommand.java
new file mode 100644 (file)
index 0000000..0d8b447
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.plugins.scm.git;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.scm.BlameCommand;
+import org.sonar.api.utils.command.Command;
+import org.sonar.api.utils.command.CommandExecutor;
+import org.sonar.api.utils.command.StreamConsumer;
+
+import java.io.File;
+
+public class GitBlameCommand implements BlameCommand {
+
+  private static final Logger LOG = LoggerFactory.getLogger(GitBlameCommand.class);
+  private final CommandExecutor commandExecutor;
+
+  public GitBlameCommand() {
+    this(CommandExecutor.create());
+  }
+
+  GitBlameCommand(CommandExecutor commandExecutor) {
+    this.commandExecutor = commandExecutor;
+  }
+
+  @Override
+  public void blame(FileSystem fs, Iterable<InputFile> files, BlameResult result) {
+    for (InputFile inputFile : files) {
+      String filename = inputFile.relativePath();
+      Command cl = createCommandLine(fs.baseDir(), filename);
+      GitBlameConsumer consumer = new GitBlameConsumer(LOG);
+      StringStreamConsumer stderr = new StringStreamConsumer();
+
+      int exitCode = execute(cl, consumer, stderr);
+      if (exitCode != 0) {
+        throw new IllegalStateException("The git blame command [" + cl.toString() + "] failed: " + stderr.getOutput());
+      }
+      result.add(inputFile, consumer.getLines());
+    }
+  }
+
+  public int execute(Command cl, StreamConsumer consumer, StreamConsumer stderr) {
+    LOG.info("Executing: " + cl);
+    LOG.info("Working directory: " + cl.getDirectory().getAbsolutePath());
+
+    return commandExecutor.execute(cl, consumer, stderr, 10 * 1000);
+  }
+
+  private Command createCommandLine(File workingDirectory, String filename) {
+    Command cl = Command.create("git");
+    cl.addArgument("blame");
+    if (workingDirectory != null) {
+      cl.setDirectory(workingDirectory);
+    }
+    cl.addArgument("--porcelain");
+    cl.addArgument(filename);
+    cl.addArgument("-w");
+    return cl;
+  }
+
+  private static class StringStreamConsumer implements StreamConsumer {
+    private StringBuffer string = new StringBuffer();
+
+    private String ls = System.getProperty("line.separator");
+
+    @Override
+    public void consumeLine(String line) {
+      string.append(line + ls);
+    }
+
+    public String getOutput() {
+      return string.toString();
+    }
+  }
+
+}
diff --git a/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitBlameConsumer.java b/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/GitBlameConsumer.java
new file mode 100644 (file)
index 0000000..1c7ef86
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.plugins.scm.git;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.slf4j.Logger;
+import org.sonar.api.batch.scm.BlameLine;
+import org.sonar.api.utils.command.StreamConsumer;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Plain copy of package org.apache.maven.scm.provider.git.gitexe.command.blame.GitBlameConsumer
+ * Patched to allow user email retrieval when parsing Git blame results.
+ *
+ * @Todo: hack - to be submitted as an update in maven-scm-api for a future release
+ *
+ * <p/>
+ * For more information, see:
+ * <a href="http://jira.sonarsource.com/browse/DEVACT-103">DEVACT-103</a>
+ *
+ * @since 1.5.1
+ */
+public class GitBlameConsumer implements StreamConsumer {
+
+  private static final String GIT_COMMITTER_PREFIX = "committer";
+  private static final String GIT_COMMITTER_TIME = GIT_COMMITTER_PREFIX + "-time ";
+  private static final String GIT_AUTHOR_EMAIL = "author-mail ";
+  private static final String GIT_COMMITTER_EMAIL = GIT_COMMITTER_PREFIX + "-mail ";
+  private static final String OPENING_EMAIL_FIELD = "<";
+  private static final String CLOSING_EMAIL_FIELD = ">";
+
+  private List<BlameLine> lines = new ArrayList<BlameLine>();
+
+  /**
+   * Since the porcelain format only contains the commit information
+   * the first time a specific sha-1 commit appears, we need to store
+   * this information somwehere.
+   * <p/>
+   * key: the sha-1 of the commit
+   * value: the {@link BlameLine} containing the full committer/author info
+   */
+  private Map<String, BlameLine> commitInfo = new HashMap<String, BlameLine>();
+
+  private boolean expectRevisionLine = true;
+
+  private String revision = null;
+  private String author = null;
+  private String committer = null;
+  private Date time = null;
+  private Logger logger;
+
+  public Logger getLogger() {
+    return logger;
+  }
+
+  public GitBlameConsumer(Logger logger) {
+    this.logger = logger;
+  }
+
+  public void consumeLine(String line) {
+    if (line == null) {
+      return;
+    }
+
+    if (expectRevisionLine) {
+      // this is the revision line
+      consumeRevisionLine(line);
+    } else {
+
+      if (extractCommitInfoFromLine(line)) {
+        return;
+      }
+
+      if (line.startsWith("\t")) {
+        // this is the content line.
+        // we actually don't need the content, but this is the right time to add the blame line
+        consumeContentLine();
+      }
+    }
+  }
+
+  @VisibleForTesting
+  protected boolean extractCommitInfoFromLine(String line) {
+    if (line.startsWith(GIT_AUTHOR_EMAIL)) {
+      author = extractEmail(line);
+      return true;
+    }
+
+    if (line.startsWith(GIT_COMMITTER_EMAIL)) {
+      committer = extractEmail(line);
+      return true;
+    }
+
+    if (line.startsWith(GIT_COMMITTER_TIME)) {
+      String timeStr = line.substring(GIT_COMMITTER_TIME.length());
+      time = new Date(Long.parseLong(timeStr) * 1000L);
+      return true;
+    }
+    return false;
+  }
+
+  @VisibleForTesting
+  protected String getAuthor() {
+    return author;
+  }
+
+  @VisibleForTesting
+  protected String getCommitter() {
+    return committer;
+  }
+
+  @VisibleForTesting
+  protected Date getTime() {
+    return time;
+  }
+
+  private String extractEmail(String line) {
+
+    int emailStartIndex = line.indexOf(OPENING_EMAIL_FIELD);
+    int emailEndIndex = line.indexOf(CLOSING_EMAIL_FIELD);
+
+    if (emailStartIndex == -1 || emailEndIndex == -1 || emailEndIndex <= emailStartIndex) {
+      return null;
+    }
+    return line.substring(emailStartIndex + 1, emailEndIndex);
+  }
+
+  private void consumeContentLine() {
+    BlameLine blameLine = new BlameLine(time, revision, author, committer);
+    getLines().add(blameLine);
+
+    // keep commitinfo for this sha-1
+    commitInfo.put(revision, blameLine);
+
+    if (getLogger().isDebugEnabled()) {
+      DateFormat df = SimpleDateFormat.getDateTimeInstance();
+      getLogger().debug(author + " " + df.format(time));
+    }
+
+    expectRevisionLine = true;
+  }
+
+  private void consumeRevisionLine(String line) {
+    String[] parts = line.split("\\s", 4);
+
+    if (parts.length >= 1) {
+      revision = parts[0];
+
+      BlameLine oldLine = commitInfo.get(revision);
+
+      if (oldLine != null) {
+        // restore the commit info
+        author = oldLine.getAuthor();
+        committer = oldLine.getCommitter();
+        time = oldLine.getDate();
+      }
+
+      expectRevisionLine = false;
+    }
+  }
+
+  public List<BlameLine> getLines() {
+    return lines;
+  }
+}
index 033cef7b7dec1bcf7bc33a4060695692d8bab46a..e5c12c42900d51f31285aad24e622b028267b815 100644 (file)
@@ -28,7 +28,8 @@ public final class GitPlugin extends SonarPlugin {
 
   public List getExtensions() {
     return ImmutableList.of(
-      GitScmProvider.class);
+      GitScmProvider.class,
+      GitBlameCommand.class);
   }
 
 }
index 7fbfe516c200ddbdeb6bb2d2068fbde19b0f606a..a0dfeb92601abad6eb5756b65716e366077f0c28 100644 (file)
  */
 package org.sonar.plugins.scm.git;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.batch.fs.FileSystem;
-import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.scm.BlameCommand;
 import org.sonar.api.batch.scm.ScmProvider;
-import org.sonar.api.utils.command.Command;
-import org.sonar.api.utils.command.CommandExecutor;
-import org.sonar.api.utils.command.StreamConsumer;
 
 import java.io.File;
 
-public class GitScmProvider implements ScmProvider {
+public class GitScmProvider extends ScmProvider {
 
-  private static final Logger LOG = LoggerFactory.getLogger(GitScmProvider.class);
+  private GitBlameCommand blameCommand;
+
+  public GitScmProvider(GitBlameCommand blameCommand) {
+    this.blameCommand = blameCommand;
+  }
 
   @Override
   public String key() {
@@ -45,53 +43,8 @@ public class GitScmProvider implements ScmProvider {
   }
 
   @Override
-  public void blame(FileSystem fs, Iterable<InputFile> files, BlameResult result) {
-    for (InputFile inputFile : files) {
-      String filename = inputFile.relativePath();
-      Command cl = createCommandLine(fs.baseDir(), filename);
-      SonarGitBlameConsumer consumer = new SonarGitBlameConsumer(LOG);
-      StringStreamConsumer stderr = new StringStreamConsumer();
-
-      int exitCode = execute(cl, consumer, stderr);
-      if (exitCode != 0) {
-        throw new IllegalStateException("The git blame command [" + cl.toString() + "] failed: " + stderr.getOutput());
-      }
-      result.add(inputFile, consumer.getLines());
-    }
-  }
-
-  public static int execute(Command cl, StreamConsumer consumer, StreamConsumer stderr) {
-    LOG.info("Executing: " + cl);
-    LOG.info("Working directory: " + cl.getDirectory().getAbsolutePath());
-
-    return CommandExecutor.create().execute(cl, consumer, stderr, 10 * 1000);
-  }
-
-  private static Command createCommandLine(File workingDirectory, String filename) {
-    Command cl = Command.create("git");
-    cl.addArgument("blame");
-    if (workingDirectory != null) {
-      cl.setDirectory(workingDirectory);
-    }
-    cl.addArgument("--porcelain");
-    cl.addArgument(filename);
-    cl.addArgument("-w");
-    return cl;
-  }
-
-  private static class StringStreamConsumer implements StreamConsumer {
-    private StringBuffer string = new StringBuffer();
-
-    private String ls = System.getProperty("line.separator");
-
-    @Override
-    public void consumeLine(String line) {
-      string.append(line + ls);
-    }
-
-    public String getOutput() {
-      return string.toString();
-    }
+  public BlameCommand blameCommand() {
+    return this.blameCommand;
   }
 
 }
diff --git a/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/SonarGitBlameConsumer.java b/plugins/sonar-git-plugin/src/main/java/org/sonar/plugins/scm/git/SonarGitBlameConsumer.java
deleted file mode 100644 (file)
index 3102b00..0000000
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Sonar SCM Activity Plugin
- * Copyright (C) 2010 SonarSource
- * dev@sonar.codehaus.org
- *
- * 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  02
- */
-
-package org.sonar.plugins.scm.git;
-
-import com.google.common.annotations.VisibleForTesting;
-import org.slf4j.Logger;
-import org.sonar.api.batch.scm.BlameLine;
-import org.sonar.api.utils.command.StreamConsumer;
-
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Plain copy of package org.apache.maven.scm.provider.git.gitexe.command.blame.GitBlameConsumer
- * Patched to allow user email retrieval when parsing Git blame results.
- *
- * @Todo: hack - to be submitted as an update in maven-scm-api for a future release
- *
- * <p/>
- * For more information, see:
- * <a href="http://jira.sonarsource.com/browse/DEVACT-103">DEVACT-103</a>
- *
- * @since 1.5.1
- */
-public class SonarGitBlameConsumer implements StreamConsumer {
-
-  private static final String GIT_COMMITTER_PREFIX = "committer";
-  private static final String GIT_COMMITTER_TIME = GIT_COMMITTER_PREFIX + "-time ";
-  private static final String GIT_AUTHOR_EMAIL = "author-mail ";
-  private static final String GIT_COMMITTER_EMAIL = GIT_COMMITTER_PREFIX + "-mail ";
-  private static final String OPENING_EMAIL_FIELD = "<";
-  private static final String CLOSING_EMAIL_FIELD = ">";
-
-  private List<BlameLine> lines = new ArrayList<BlameLine>();
-
-  /**
-   * Since the porcelain format only contains the commit information
-   * the first time a specific sha-1 commit appears, we need to store
-   * this information somwehere.
-   * <p/>
-   * key: the sha-1 of the commit
-   * value: the {@link BlameLine} containing the full committer/author info
-   */
-  private Map<String, BlameLine> commitInfo = new HashMap<String, BlameLine>();
-
-  private boolean expectRevisionLine = true;
-
-  private String revision = null;
-  private String author = null;
-  private String committer = null;
-  private Date time = null;
-  private Logger logger;
-
-  public Logger getLogger() {
-    return logger;
-  }
-
-  public SonarGitBlameConsumer(Logger logger) {
-    this.logger = logger;
-  }
-
-  public void consumeLine(String line) {
-    if (line == null) {
-      return;
-    }
-
-    if (expectRevisionLine) {
-      // this is the revision line
-      consumeRevisionLine(line);
-    } else {
-
-      if (extractCommitInfoFromLine(line)) {
-        return;
-      }
-
-      if (line.startsWith("\t")) {
-        // this is the content line.
-        // we actually don't need the content, but this is the right time to add the blame line
-        consumeContentLine();
-      }
-    }
-  }
-
-  @VisibleForTesting
-  protected boolean extractCommitInfoFromLine(String line) {
-    if (line.startsWith(GIT_AUTHOR_EMAIL)) {
-      author = extractEmail(line);
-      return true;
-    }
-
-    if (line.startsWith(GIT_COMMITTER_EMAIL)) {
-      committer = extractEmail(line);
-      return true;
-    }
-
-    if (line.startsWith(GIT_COMMITTER_TIME)) {
-      String timeStr = line.substring(GIT_COMMITTER_TIME.length());
-      time = new Date(Long.parseLong(timeStr) * 1000L);
-      return true;
-    }
-    return false;
-  }
-
-  @VisibleForTesting
-  protected String getAuthor() {
-    return author;
-  }
-
-  @VisibleForTesting
-  protected String getCommitter() {
-    return committer;
-  }
-
-  @VisibleForTesting
-  protected Date getTime() {
-    return time;
-  }
-
-  private String extractEmail(String line) {
-
-    int emailStartIndex = line.indexOf(OPENING_EMAIL_FIELD);
-    int emailEndIndex = line.indexOf(CLOSING_EMAIL_FIELD);
-
-    if (emailStartIndex == -1 || emailEndIndex == -1 || emailEndIndex <= emailStartIndex) {
-      return null;
-    }
-    return line.substring(emailStartIndex + 1, emailEndIndex);
-  }
-
-  private void consumeContentLine() {
-    BlameLine blameLine = new BlameLine(time, revision, author, committer);
-    getLines().add(blameLine);
-
-    // keep commitinfo for this sha-1
-    commitInfo.put(revision, blameLine);
-
-    if (getLogger().isDebugEnabled()) {
-      DateFormat df = SimpleDateFormat.getDateTimeInstance();
-      getLogger().debug(author + " " + df.format(time));
-    }
-
-    expectRevisionLine = true;
-  }
-
-  private void consumeRevisionLine(String line) {
-    String[] parts = line.split("\\s", 4);
-
-    if (parts.length >= 1) {
-      revision = parts[0];
-
-      BlameLine oldLine = commitInfo.get(revision);
-
-      if (oldLine != null) {
-        // restore the commit info
-        author = oldLine.getAuthor();
-        committer = oldLine.getCommitter();
-        time = oldLine.getDate();
-      }
-
-      expectRevisionLine = false;
-    }
-  }
-
-  public List<BlameLine> getLines() {
-    return lines;
-  }
-}
diff --git a/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/GitBlameCommandTest.java b/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/GitBlameCommandTest.java
new file mode 100644 (file)
index 0000000..805b022
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.plugins.scm.git;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultFileSystem;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.scm.BlameCommand.BlameResult;
+import org.sonar.api.batch.scm.BlameLine;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.command.Command;
+import org.sonar.api.utils.command.CommandExecutor;
+import org.sonar.api.utils.command.StreamConsumer;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class GitBlameCommandTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  private DefaultFileSystem fs;
+  private File baseDir;
+
+  @Before
+  public void prepare() throws IOException {
+    baseDir = temp.newFolder();
+    fs = new DefaultFileSystem();
+    fs.setBaseDir(baseDir);
+  }
+
+  @Test
+  public void testParsingOfOutput() throws IOException {
+    File source = new File(baseDir, "src/foo.xoo");
+    FileUtils.write(source, "sample content");
+    DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setAbsolutePath(new File(baseDir, "src/foo.xoo").getAbsolutePath());
+    fs.add(inputFile);
+
+    BlameResult result = mock(BlameResult.class);
+    CommandExecutor commandExecutor = mock(CommandExecutor.class);
+
+    when(commandExecutor.execute(any(Command.class), any(StreamConsumer.class), any(StreamConsumer.class), anyLong())).thenAnswer(new Answer<Integer>() {
+
+      @Override
+      public Integer answer(InvocationOnMock invocation) throws Throwable {
+        StreamConsumer outConsumer = (StreamConsumer) invocation.getArguments()[1];
+        outConsumer.consumeLine("2c68c473da7fc293e12ca50f19380c5118be7ead 68 54 1");
+        outConsumer.consumeLine("author Simon Brandhof");
+        outConsumer.consumeLine("author-mail <simon.brandhof@gmail.com>");
+        outConsumer.consumeLine("author-time 1312534171");
+        outConsumer.consumeLine("author-tz +0200");
+        outConsumer.consumeLine("committer Simon Brandhof");
+        outConsumer.consumeLine("committer-mail <simon.brandhof@gmail.com>");
+        outConsumer.consumeLine("committer-time 1312534171");
+        outConsumer.consumeLine("committer-tz +0200");
+        outConsumer.consumeLine("summary Move to nexus.codehaus.org + configuration of maven release plugin is back");
+        outConsumer.consumeLine("previous 1bec1c3a77f6957175be13e4433110f7fc8e387e pom.xml");
+        outConsumer.consumeLine("filename pom.xml");
+        outConsumer.consumeLine("\t<id>codehaus-nexus-staging</id>");
+        outConsumer.consumeLine("2c68c473da7fc293e12ca50f19380c5118be7ead 72 60 1");
+        outConsumer.consumeLine("\t<url>${sonar.snapshotRepository.url}</url>");
+        return 0;
+      }
+    });
+
+    new GitBlameCommand(commandExecutor).blame(fs, Arrays.<InputFile>asList(inputFile), result);
+    verify(result).add(inputFile,
+      Arrays.asList(new BlameLine(DateUtils.parseDateTime("2011-08-05T10:49:31+0200"), "2c68c473da7fc293e12ca50f19380c5118be7ead", "simon.brandhof@gmail.com"),
+        new BlameLine(DateUtils.parseDateTime("2011-08-05T10:49:31+0200"), "2c68c473da7fc293e12ca50f19380c5118be7ead", "simon.brandhof@gmail.com")));
+  }
+
+}
index d65e0eb5844265ae15b61a64b202f5065c9b950e..d175cdcbe918b72ea444e344384a682b68b00b1b 100644 (file)
@@ -27,6 +27,6 @@ public class GitPluginTest {
 
   @Test
   public void getExtensions() {
-    assertThat(new GitPlugin().getExtensions()).hasSize(1);
+    assertThat(new GitPlugin().getExtensions()).hasSize(2);
   }
 }
diff --git a/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/GitScmProviderTest.java b/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/GitScmProviderTest.java
new file mode 100644 (file)
index 0000000..9bc5801
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.plugins.scm.git;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class GitScmProviderTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Test
+  public void sanityCheck() {
+    assertThat(new GitScmProvider(null).key()).isEqualTo("git");
+    GitBlameCommand blameCommand = new GitBlameCommand();
+    assertThat(new GitScmProvider(blameCommand).blameCommand()).isEqualTo(blameCommand);
+  }
+
+  @Test
+  public void testAutodetection() throws IOException {
+    File baseDirEmpty = temp.newFolder();
+    assertThat(new GitScmProvider(null).supports(baseDirEmpty)).isFalse();
+
+    File gitBaseDir = temp.newFolder();
+    new File(gitBaseDir, ".git").mkdir();
+    assertThat(new GitScmProvider(null).supports(gitBaseDir)).isTrue();
+  }
+
+}
diff --git a/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/medium/GitMediumTest.java b/plugins/sonar-git-plugin/src/test/java/org/sonar/plugins/scm/git/medium/GitMediumTest.java
deleted file mode 100644 (file)
index a1f3459..0000000
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.plugins.scm.git.medium;
-
-import com.google.common.collect.ImmutableMap;
-import org.apache.commons.io.FileUtils;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.batch.fs.InputFile;
-import org.sonar.api.batch.sensor.duplication.DuplicationGroup;
-import org.sonar.batch.mediumtest.BatchMediumTester;
-import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult;
-import org.sonar.plugins.scm.git.GitPlugin;
-import org.sonar.xoo.XooPlugin;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-import static org.fest.assertions.Assertions.assertThat;
-
-public class GitMediumTest {
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  public BatchMediumTester tester = BatchMediumTester.builder()
-    .registerPlugin("xoo", new XooPlugin())
-    .registerPlugin("git", new GitPlugin())
-    .addDefaultQProfile("xoo", "Sonar Way")
-    .bootstrapProperties(ImmutableMap.of("sonar.analysis.mode", "sensor"))
-    .build();
-
-  private File baseDir;
-
-  private ImmutableMap.Builder<String, String> builder;
-
-  @Before
-  public void prepare() throws IOException {
-    tester.start();
-
-    baseDir = temp.newFolder();
-
-    builder = ImmutableMap.<String, String>builder()
-      .put("sonar.task", "scan")
-      .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
-      .put("sonar.projectKey", "com.foo.project")
-      .put("sonar.projectName", "Foo Project")
-      .put("sonar.projectVersion", "1.0-SNAPSHOT")
-      .put("sonar.projectDescription", "Description of Foo Project");
-  }
-
-  @After
-  public void stop() {
-    tester.stop();
-  }
-
-  @Test
-  public void testDuplications() throws IOException {
-    File srcDir = new File(baseDir, "src");
-    srcDir.mkdir();
-
-    String duplicatedStuff = "Sample xoo\ncontent\nfoo\nbar\ntoto\ntiti\nfoo\nbar\ntoto\ntiti\nbar\ntoto\ntiti\nfoo\nbar\ntoto\ntiti";
-
-    File xooFile1 = new File(srcDir, "sample1.xoo");
-    FileUtils.write(xooFile1, duplicatedStuff);
-
-    File xooFile2 = new File(srcDir, "sample2.xoo");
-    FileUtils.write(xooFile2, duplicatedStuff);
-
-    TaskResult result = tester.newTask()
-      .properties(builder
-        .put("sonar.sources", "src")
-        .put("sonar.cpd.xoo.minimumTokens", "10")
-        .put("sonar.verbose", "true")
-        .build())
-      .start();
-
-    assertThat(result.inputFiles()).hasSize(2);
-
-    // 4 measures per file
-    assertThat(result.measures()).hasSize(8);
-
-    InputFile inputFile = result.inputFiles().get(0);
-    // One clone group
-    List<DuplicationGroup> duplicationGroups = result.duplicationsFor(inputFile);
-    assertThat(duplicationGroups).hasSize(1);
-
-    DuplicationGroup cloneGroup = duplicationGroups.get(0);
-    assertThat(cloneGroup.duplicates()).hasSize(1);
-    assertThat(cloneGroup.originBlock().startLine()).isEqualTo(1);
-    assertThat(cloneGroup.originBlock().length()).isEqualTo(17);
-  }
-
-}
index e1988ac37ab97bef960c1b7c94568d107955a4d9..148a6df0b7c0ea7c6cc54ac4179f0f88fdcc2e94 100644 (file)
@@ -25,13 +25,14 @@ import org.sonar.xoo.lang.MeasureSensor;
 import org.sonar.xoo.lang.SymbolReferencesSensor;
 import org.sonar.xoo.lang.SyntaxHighlightingSensor;
 import org.sonar.xoo.lang.TestCaseSensor;
-import org.sonar.xoo.lang.XooScmProvider;
 import org.sonar.xoo.lang.XooTokenizerSensor;
 import org.sonar.xoo.rule.CreateIssueByInternalKeySensor;
 import org.sonar.xoo.rule.OneIssueOnDirPerFileSensor;
 import org.sonar.xoo.rule.OneIssuePerLineSensor;
 import org.sonar.xoo.rule.XooQualityProfile;
 import org.sonar.xoo.rule.XooRulesDefinition;
+import org.sonar.xoo.scm.XooBlameCommand;
+import org.sonar.xoo.scm.XooScmProvider;
 
 import java.util.Arrays;
 import java.util.List;
@@ -53,6 +54,7 @@ public class XooPlugin extends SonarPlugin {
 
       // SCM
       XooScmProvider.class,
+      XooBlameCommand.class,
 
       // sensors
       MeasureSensor.class,
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooScmProvider.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooScmProvider.java
deleted file mode 100644 (file)
index 895d47d..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.xoo.lang;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Charsets;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.lang.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.batch.fs.InputFile;
-import org.sonar.api.batch.scm.BlameLine;
-import org.sonar.api.batch.scm.ScmProvider;
-import org.sonar.api.config.Settings;
-import org.sonar.api.utils.DateUtils;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-public class XooScmProvider implements ScmProvider {
-
-  private static final Logger LOG = LoggerFactory.getLogger(XooScmProvider.class);
-
-  private static final String SCM_EXTENSION = ".scm";
-
-  private final Settings settings;
-
-  public XooScmProvider(Settings settings) {
-    this.settings = settings;
-  }
-
-  @Override
-  public String key() {
-    return "xoo";
-  }
-
-  @Override
-  public boolean supports(File baseDir) {
-    return false;
-  }
-
-  @Override
-  public void blame(Iterable<InputFile> files, BlameResult handler) {
-    for (InputFile inputFile : files) {
-      processFile(inputFile, handler);
-    }
-  }
-
-  @VisibleForTesting
-  protected void processFile(InputFile inputFile, BlameResult handler) {
-    File ioFile = inputFile.file();
-    File scmDataFile = new java.io.File(ioFile.getParentFile(), ioFile.getName() + SCM_EXTENSION);
-    if (!scmDataFile.exists()) {
-      throw new IllegalStateException("Missing file " + scmDataFile);
-    }
-
-    try {
-      List<String> lines = FileUtils.readLines(scmDataFile, Charsets.UTF_8.name());
-      List<BlameLine> blame = new ArrayList<BlameLine>(lines.size());
-      int lineNumber = 0;
-      for (String line : lines) {
-        lineNumber++;
-        if (StringUtils.isNotBlank(line)) {
-          // revision,author,dateTime
-          String[] fields = StringUtils.split(line, ',');
-          if (fields.length < 3) {
-            throw new IllegalStateException("Not enough fields on line " + lineNumber);
-          }
-          String revision = fields[0];
-          String author = fields[1];
-          // Will throw an exception, when date is not in format "yyyy-MM-dd"
-          Date date = DateUtils.parseDate(fields[2]);
-
-          blame.add(new BlameLine(date, revision, author));
-        }
-      }
-      handler.add(inputFile, blame);
-    } catch (IOException e) {
-      throw new IllegalStateException(e);
-    }
-  }
-}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/package-info.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/package-info.java
new file mode 100644 (file)
index 0000000..9963cc3
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.xoo.lang;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index 7a11c639e38d96b0fdea33135ebee38db61fbb04..31db08c01f201a6f57ad07e10b2d4955feac6f32 100644 (file)
@@ -32,7 +32,7 @@ public class OneIssuePerLineSensor implements Sensor {
 
   public static final String RULE_KEY = "OneIssuePerLine";
   private static final String EFFORT_TO_FIX_PROPERTY = "sonar.oneIssuePerLine.effortToFix";
-  private static final String FORCE_SEVERITY_PROPERTY = "sonar.oneIssuePerLine.forceSeverity";
+  public static final String FORCE_SEVERITY_PROPERTY = "sonar.oneIssuePerLine.forceSeverity";
 
   @Override
   public void describe(SensorDescriptor descriptor) {
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/XooBlameCommand.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/XooBlameCommand.java
new file mode 100644 (file)
index 0000000..7d492cc
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.xoo.scm;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Charsets;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.scm.BlameCommand;
+import org.sonar.api.batch.scm.BlameLine;
+import org.sonar.api.utils.DateUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class XooBlameCommand implements BlameCommand {
+
+  private static final String SCM_EXTENSION = ".scm";
+
+  @Override
+  public void blame(FileSystem fs, Iterable<InputFile> files, BlameResult result) {
+    for (InputFile inputFile : files) {
+      processFile(inputFile, result);
+    }
+  }
+
+  @VisibleForTesting
+  protected void processFile(InputFile inputFile, BlameResult result) {
+    File ioFile = inputFile.file();
+    File scmDataFile = new java.io.File(ioFile.getParentFile(), ioFile.getName() + SCM_EXTENSION);
+    if (!scmDataFile.exists()) {
+      throw new IllegalStateException("Missing file " + scmDataFile);
+    }
+
+    try {
+      List<String> lines = FileUtils.readLines(scmDataFile, Charsets.UTF_8.name());
+      List<BlameLine> blame = new ArrayList<BlameLine>(lines.size());
+      int lineNumber = 0;
+      for (String line : lines) {
+        lineNumber++;
+        if (StringUtils.isNotBlank(line)) {
+          // revision,author,dateTime
+          String[] fields = StringUtils.split(line, ',');
+          if (fields.length < 3) {
+            throw new IllegalStateException("Not enough fields on line " + lineNumber);
+          }
+          String revision = fields[0];
+          String author = fields[1];
+          // Will throw an exception, when date is not in format "yyyy-MM-dd"
+          Date date = DateUtils.parseDate(fields[2]);
+
+          blame.add(new BlameLine(date, revision, author));
+        }
+      }
+      result.add(inputFile, blame);
+    } catch (IOException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/XooScmProvider.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/XooScmProvider.java
new file mode 100644 (file)
index 0000000..5807d82
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.xoo.scm;
+
+import org.sonar.api.batch.scm.BlameCommand;
+import org.sonar.api.batch.scm.ScmProvider;
+
+public class XooScmProvider extends ScmProvider {
+
+  private final XooBlameCommand blame;
+
+  public XooScmProvider(XooBlameCommand blame) {
+    this.blame = blame;
+  }
+
+  @Override
+  public String key() {
+    return "xoo";
+  }
+
+  @Override
+  public BlameCommand blameCommand() {
+    return blame;
+  }
+
+}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/package-info.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/package-info.java
new file mode 100644 (file)
index 0000000..f5c68e3
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.xoo.scm;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index 96ea0ea923c7380db2fc28df9434c9233249c525..e8100740345f8eee92ff3c483677cd170956cfe6 100644 (file)
@@ -27,6 +27,6 @@ public class XooPluginTest {
 
   @Test
   public void provide_extensions() {
-    assertThat(new XooPlugin().getExtensions()).hasSize(13);
+    assertThat(new XooPlugin().getExtensions()).hasSize(14);
   }
 }
diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/OneIssuePerLineSensorTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/OneIssuePerLineSensorTest.java
new file mode 100644 (file)
index 0000000..31d109e
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.xoo.rule;
+
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.sonar.api.batch.fs.internal.DefaultFileSystem;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorStorage;
+import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor;
+import org.sonar.api.batch.sensor.issue.Issue;
+import org.sonar.api.batch.sensor.issue.Issue.Severity;
+import org.sonar.api.batch.sensor.issue.internal.DefaultIssue;
+import org.sonar.api.config.Settings;
+import org.sonar.xoo.Xoo;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class OneIssuePerLineSensorTest {
+
+  private OneIssuePerLineSensor sensor = new OneIssuePerLineSensor();
+
+  @Test
+  public void testDescriptor() {
+    DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor();
+    sensor.describe(descriptor);
+    assertThat(descriptor.ruleRepositories()).containsOnly(XooRulesDefinition.XOO_REPOSITORY);
+  }
+
+  @Test
+  public void testRule() {
+    DefaultFileSystem fs = new DefaultFileSystem();
+    DefaultInputFile inputFile = new DefaultInputFile("foo", "src/Foo.xoo").setLanguage(Xoo.KEY).setLines(10);
+    fs.add(inputFile);
+
+    SensorContext context = mock(SensorContext.class);
+    final SensorStorage sensorStorage = mock(SensorStorage.class);
+    when(context.settings()).thenReturn(new Settings());
+    when(context.fileSystem()).thenReturn(fs);
+    when(context.newIssue()).thenAnswer(new Answer<Issue>() {
+      @Override
+      public Issue answer(InvocationOnMock invocation) throws Throwable {
+        return new DefaultIssue(sensorStorage);
+      }
+    });
+    sensor.execute(context);
+
+    ArgumentCaptor<DefaultIssue> argCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(sensorStorage, times(10)).store(argCaptor.capture());
+    assertThat(argCaptor.getAllValues()).hasSize(10); // One issue per line
+    assertThat(argCaptor.getValue().overridenSeverity()).isNull();
+  }
+
+  @Test
+  public void testForceSeverity() {
+    DefaultFileSystem fs = new DefaultFileSystem();
+    DefaultInputFile inputFile = new DefaultInputFile("foo", "src/Foo.xoo").setLanguage(Xoo.KEY).setLines(10);
+    fs.add(inputFile);
+
+    SensorContext context = mock(SensorContext.class);
+    final SensorStorage sensorStorage = mock(SensorStorage.class);
+    Settings settings = new Settings();
+    settings.setProperty(OneIssuePerLineSensor.FORCE_SEVERITY_PROPERTY, "MINOR");
+    when(context.settings()).thenReturn(settings);
+    when(context.fileSystem()).thenReturn(fs);
+    when(context.newIssue()).thenAnswer(new Answer<Issue>() {
+      @Override
+      public Issue answer(InvocationOnMock invocation) throws Throwable {
+        return new DefaultIssue(sensorStorage);
+      }
+    });
+    sensor.execute(context);
+
+    ArgumentCaptor<DefaultIssue> argCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(sensorStorage, times(10)).store(argCaptor.capture());
+    assertThat(argCaptor.getAllValues()).hasSize(10); // One issue per line
+    assertThat(argCaptor.getValue().overridenSeverity()).isEqualTo(Severity.MINOR);
+  }
+
+}
diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/scm/XooBlameCommandTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/scm/XooBlameCommandTest.java
new file mode 100644 (file)
index 0000000..bba495d
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.xoo.scm;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultFileSystem;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.scm.BlameCommand.BlameResult;
+import org.sonar.api.batch.scm.BlameLine;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.xoo.Xoo;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class XooBlameCommandTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  private DefaultFileSystem fs;
+  private File baseDir;
+
+  @Before
+  public void prepare() throws IOException {
+    baseDir = temp.newFolder();
+    fs = new DefaultFileSystem();
+  }
+
+  @Test
+  public void testBlame() throws IOException {
+    File source = new File(baseDir, "src/foo.xoo");
+    FileUtils.write(source, "sample content");
+    File scm = new File(baseDir, "src/foo.xoo.scm");
+    FileUtils.write(scm, "123,julien,2014-12-12\n234,julien,2014-12-24");
+    DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setAbsolutePath(new File(baseDir, "src/foo.xoo").getAbsolutePath()).setLanguage(Xoo.KEY);
+    fs.add(inputFile);
+
+    BlameResult result = mock(BlameResult.class);
+    new XooBlameCommand().blame(fs, Arrays.<InputFile>asList(inputFile), result);
+    verify(result).add(inputFile, Arrays.asList(new BlameLine(DateUtils.parseDate("2014-12-12"), "123", "julien"),
+      new BlameLine(DateUtils.parseDate("2014-12-24"), "234", "julien")));
+  }
+
+}
diff --git a/pom.xml b/pom.xml
index e1bd848cda0476fa1c0e2120ea500c3ca31bf254..efd7f37738625655e2950ccbcacee04b19a5f02d 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -35,6 +35,7 @@
     <module>plugins/sonar-cpd-plugin</module>
     <module>plugins/sonar-l10n-en-plugin</module>
     <module>plugins/sonar-email-notifications-plugin</module>
+    <module>plugins/sonar-git-plugin</module>
     <module>plugins/sonar-xoo-plugin</module>
   </modules>
 
index 3a2cd2658170e40f05f6f91def7be45bb8887aa4..350135a07803afad71094b0a3f867e8441dde78c 100644 (file)
@@ -24,8 +24,8 @@ import org.slf4j.LoggerFactory;
 import org.sonar.api.batch.fs.FileSystem;
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.api.batch.fs.InputFile.Status;
+import org.sonar.api.batch.scm.BlameCommand.BlameResult;
 import org.sonar.api.batch.scm.BlameLine;
-import org.sonar.api.batch.scm.ScmProvider.BlameResult;
 import org.sonar.api.batch.sensor.Sensor;
 import org.sonar.api.batch.sensor.SensorContext;
 import org.sonar.api.batch.sensor.SensorDescriptor;
@@ -92,7 +92,7 @@ public final class ScmActivitySensor implements Sensor {
         filesToBlame.add(f);
       }
     }
-    configuration.provider().blame(fs, filesToBlame, new BlameResult() {
+    configuration.provider().blameCommand().blame(fs, filesToBlame, new BlameResult() {
 
       @Override
       public void add(InputFile file, List<BlameLine> lines) {
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/BlameCommand.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/BlameCommand.java
new file mode 100644 (file)
index 0000000..385b352
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.batch.scm;
+
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.fs.InputFile;
+
+import java.util.List;
+
+/**
+ * @since 5.0
+ */
+public interface BlameCommand {
+
+  /**
+   * Compute blame of the provided files. Computation can be done in parallel.
+   * If there is an error that prevent to blame a file then an exception should be raised.
+   */
+  void blame(FileSystem fs, Iterable<InputFile> files, BlameResult result);
+
+  /**
+   * Callback for the provider to report results of blame per file.
+   */
+  public static interface BlameResult {
+
+    void add(InputFile file, List<BlameLine> lines);
+
+  }
+
+}
index 2303b90f7949a87e7df50870d45a22fa3bb6bd87..c4362f05a9b756e9b56d82b1a9326b9d990eeee2 100644 (file)
  */
 package org.sonar.api.batch.scm;
 
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+
 import java.util.Date;
 
 /**
@@ -99,4 +104,41 @@ public class BlameLine {
       this.date = null;
     }
   }
+
+  // For testing purpose
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == null) {
+      return false;
+    }
+    if (obj == this) {
+      return true;
+    }
+    if (obj.getClass() != getClass()) {
+      return false;
+    }
+    BlameLine rhs = (BlameLine) obj;
+    return new EqualsBuilder()
+      .append(date, rhs.date)
+      .append(revision, rhs.revision)
+      .append(author, rhs.author)
+      .append(committer, rhs.committer)
+      .isEquals();
+  }
+
+  @Override
+  public int hashCode() {
+    return new HashCodeBuilder(27, 45).
+      append(date)
+      .append(revision)
+      .append(author)
+      .append(committer)
+      .toHashCode();
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+  }
 }
index 2630f95b5e69f545dd634bedf8ca835dcf9edfc1..a32bec98538b55501d24fdb6a9d53dd9a56c024f 100644 (file)
@@ -21,42 +21,31 @@ package org.sonar.api.batch.scm;
 
 import org.sonar.api.BatchExtension;
 import org.sonar.api.batch.InstantiationStrategy;
-import org.sonar.api.batch.fs.FileSystem;
-import org.sonar.api.batch.fs.InputFile;
 
 import java.io.File;
-import java.util.List;
 
 /**
  * @since 5.0
  */
 @InstantiationStrategy(InstantiationStrategy.PER_BATCH)
-public interface ScmProvider extends BatchExtension {
+public abstract class ScmProvider implements BatchExtension {
 
   /**
    * Unique identifier of the provider. Can be used in SCM URL to define the provider to use.
    */
-  String key();
+  public abstract String key();
 
   /**
    * Does this provider able to manage files located in this directory.
-   * Used by autodetection.
+   * Used by autodetection. Not considered if user has forced the provider key.
+   * @return false by default
    */
-  boolean supports(File baseDir);
-
-  /**
-   * Compute blame of the provided files. Computation can be done in parallel.
-   * If there is an error that prevent to blame a file then an exception should be raised.
-   */
-  void blame(FileSystem fs, Iterable<InputFile> files, BlameResult result);
-
-  /**
-   * Callback for the provider to save results of blame per file.
-   */
-  public static interface BlameResult {
-
-    void add(InputFile file, List<BlameLine> lines);
+  public boolean supports(File baseDir) {
+    return false;
+  }
 
+  public BlameCommand blameCommand() {
+    throw new UnsupportedOperationException("Blame command is not supported by " + key() + " provider");
   }
 
 }
index 23148378f35abfe3e29dfa98896ee917a57ec73f..42637fdd4cbc86de550c97eb7ad2dedc576abb76 100644 (file)
@@ -52,10 +52,12 @@ public class DefaultIssue implements Issue {
   private final SensorStorage storage;
 
   public DefaultIssue() {
+    this.key = UUID.randomUUID().toString();
     this.storage = null;
   }
 
   public DefaultIssue(SensorStorage storage) {
+    this.key = UUID.randomUUID().toString();
     this.storage = storage;
   }
 
@@ -155,9 +157,6 @@ public class DefaultIssue implements Issue {
   public void save() {
     Preconditions.checkNotNull(this.storage, "No persister on this object");
     Preconditions.checkNotNull(this.ruleKey, "ruleKey is mandatory on issue");
-    if (this.key == null) {
-      this.key = UUID.randomUUID().toString();
-    }
     Preconditions.checkState(!Strings.isNullOrEmpty(key), "Fail to generate issue key");
 
     storage.store(this);
index 7d05c23f25940c523d90d69f4bc94820f3aefba3..023adec810613ae93e2dd2266b639c3d5f6cf1e0 100644 (file)
@@ -43,6 +43,10 @@ public class DefaultTestCase implements TestCase {
   private TestCase.Type type = Type.UNIT;
   private String stackTrace;
 
+  public DefaultTestCase() {
+    this.storage = null;
+  }
+
   public DefaultTestCase(SensorStorage storage) {
     this.storage = storage;
   }