SONAR-5643 Provide a default SVN SCM Provider

This commit is contained in:
Julien HENRY 2014-09-30 16:49:55 +02:00
parent 327799ec3e
commit 2d60412e14
21 changed files with 875 additions and 68 deletions

View File

@ -47,23 +47,6 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-batch</artifactId>
<version>${project.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>sonar-deprecated</artifactId>
<groupId>org.codehaus.sonar</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.codehaus.sonar.plugins</groupId>
<artifactId>sonar-xoo-plugin</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -48,10 +48,11 @@ public class GitBlameCommand implements BlameCommand, BatchComponent {
@Override
public void blame(FileSystem fs, Iterable<InputFile> files, BlameResult result) {
LOG.info("Working directory: " + fs.baseDir().getAbsolutePath());
for (InputFile inputFile : files) {
String filename = inputFile.relativePath();
Command cl = createCommandLine(fs.baseDir(), filename);
GitBlameConsumer consumer = new GitBlameConsumer(LOG);
GitBlameConsumer consumer = new GitBlameConsumer();
StringStreamConsumer stderr = new StringStreamConsumer();
int exitCode = execute(cl, consumer, stderr);
@ -64,9 +65,7 @@ public class GitBlameCommand implements BlameCommand, BatchComponent {
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);
return commandExecutor.execute(cl, consumer, stderr, 0);
}
private Command createCommandLine(File workingDirectory, String filename) {

View File

@ -20,30 +20,15 @@
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";
@ -71,15 +56,6 @@ public class GitBlameConsumer implements StreamConsumer {
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) {
@ -123,21 +99,6 @@ public class GitBlameConsumer implements StreamConsumer {
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);
@ -156,11 +117,6 @@ public class GitBlameConsumer implements StreamConsumer {
// 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;
}

View File

@ -20,6 +20,7 @@
package org.sonar.plugins.scm.git;
import com.google.common.collect.ImmutableList;
import org.sonar.api.CoreProperties;
import org.sonar.api.PropertyType;
import org.sonar.api.SonarPlugin;
import org.sonar.api.config.PropertyDefinition;
@ -28,6 +29,7 @@ import java.util.List;
public final class GitPlugin extends SonarPlugin {
static final String CATEGORY_GIT = "Git";
static final String GIT_IMPLEMENTATION_PROP_KEY = "sonar.git.implementation";
static final String JGIT = "jgit";
static final String EXE = "exe";
@ -40,10 +42,12 @@ public final class GitPlugin extends SonarPlugin {
PropertyDefinition.builder(GIT_IMPLEMENTATION_PROP_KEY)
.name("Git implementation")
.description("Use pure Java implementation by default but you can use command line git executable in case of issue.")
.description("By default pure Java implementation is used. You can force use of command line git executable in case of issue.")
.defaultValue(JGIT)
.type(PropertyType.SINGLE_SELECT_LIST)
.options(EXE, JGIT)
.category(CoreProperties.CATEGORY_SCM)
.subCategory(CATEGORY_GIT)
.build());
}

View File

@ -105,4 +105,30 @@ public class GitBlameCommandTest {
new BlameLine(DateUtils.parseDateTime("2011-08-05T10:49:31+0200"), "2c68c473da7fc293e12ca50f19380c5118be7ead", "simon.brandhof@gmail.com")));
}
@Test
public void testExecutionError() 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 errConsumer = (StreamConsumer) invocation.getArguments()[2];
errConsumer.consumeLine("My error");
return 1;
}
});
thrown.expect(IllegalStateException.class);
thrown.expectMessage("The git blame command [git blame --porcelain src/foo.xoo -w] failed: My error");
new GitBlameCommand(commandExecutor).blame(fs, Arrays.<InputFile>asList(inputFile), result);
}
}

View File

@ -1,3 +1,22 @@
/*
* 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.io.Closeables;

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar</artifactId>
<version>5.0-SNAPSHOT</version>
<relativePath>../..</relativePath>
</parent>
<groupId>org.codehaus.sonar.plugins</groupId>
<artifactId>sonar-svn-plugin</artifactId>
<name>SonarQube :: Plugins :: SVN</name>
<packaging>sonar-plugin</packaging>
<description>SVN SCM Provider.</description>
<dependencies>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-plugin-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- unit tests -->
<dependency>
<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-testing-harness</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-packaging-maven-plugin</artifactId>
<configuration>
<pluginKey>svn</pluginKey>
<pluginName>SVN</pluginName>
<pluginClass>org.sonar.plugins.scm.svn.SvnPlugin</pluginClass>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,152 @@
/*
* 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.svn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.BatchComponent;
import org.sonar.api.batch.InstantiationStrategy;
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;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
public class SvnBlameCommand implements BlameCommand, BatchComponent {
private static final Logger LOG = LoggerFactory.getLogger(SvnBlameCommand.class);
private final CommandExecutor commandExecutor;
private final SvnConfiguration configuration;
public SvnBlameCommand(SvnConfiguration configuration) {
this(CommandExecutor.create(), configuration);
}
SvnBlameCommand(CommandExecutor commandExecutor, SvnConfiguration configuration) {
this.commandExecutor = commandExecutor;
this.configuration = configuration;
}
@Override
public void blame(final FileSystem fs, Iterable<InputFile> files, final BlameResult result) {
LOG.info("Working directory: " + fs.baseDir().getAbsolutePath());
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1);
List<Future<Void>> tasks = new ArrayList<Future<Void>>();
for (InputFile inputFile : files) {
tasks.add(submitTask(fs, result, executorService, inputFile));
}
for (Future<Void> task : tasks) {
try {
task.get();
} catch (ExecutionException e) {
throw e.getCause() instanceof RuntimeException ? (RuntimeException) e.getCause() : new IllegalStateException(e.getCause());
} catch (InterruptedException e) {
// Ignore
}
}
}
private Future<Void> submitTask(final FileSystem fs, final BlameResult result, ExecutorService executorService, final InputFile inputFile) {
return executorService.submit(new Callable<Void>() {
public Void call() {
String filename = inputFile.relativePath();
Command cl = createCommandLine(fs.baseDir(), filename);
SvnBlameConsumer consumer = new SvnBlameConsumer();
StringStreamConsumer stderr = new StringStreamConsumer();
int exitCode = execute(cl, consumer, stderr);
if (exitCode != 0) {
throw new IllegalStateException("The svn blame command [" + cl.toString() + "] failed: " + stderr.getOutput());
}
result.add(inputFile, consumer.getLines());
return null;
}
});
}
public int execute(Command cl, StreamConsumer consumer, StreamConsumer stderr) {
LOG.info("Executing: " + cl);
return commandExecutor.execute(cl, consumer, stderr, 0);
}
public Command createCommandLine(File workingDirectory, String filename) {
Command cl = Command.create("svn");
for (Entry<String, String> env : System.getenv().entrySet()) {
cl.setEnvironmentVariable(env.getKey(), env.getValue());
}
cl.setEnvironmentVariable("LC_MESSAGES", "en");
if (workingDirectory != null) {
cl.setDirectory(workingDirectory);
}
cl.addArgument("blame");
cl.addArgument("--xml");
cl.addArgument(filename);
cl.addArgument("--non-interactive");
String configDir = configuration.configDir();
if (configDir != null) {
cl.addArgument("--config-dir");
cl.addArgument(configDir);
}
String username = configuration.username();
if (username != null) {
cl.addArgument("--username");
cl.addArgument(username);
String password = configuration.password();
if (password != null) {
cl.addArgument("--password");
cl.addArgument(password);
}
}
if (configuration.trustServerCert()) {
cl.addArgument("--trust-server-cert");
}
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();
}
}
}

View File

@ -0,0 +1,117 @@
/*
* 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.svn;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.scm.BlameLine;
import org.sonar.api.utils.command.StreamConsumer;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SvnBlameConsumer implements StreamConsumer {
private static final Logger LOG = LoggerFactory.getLogger(SvnBlameConsumer.class);
private static final String SVN_TIMESTAMP_PATTERN = "yyyy-MM-dd HH:mm:ss";
private static final Pattern LINE_PATTERN = Pattern.compile("line-number=\"(.*)\"");
private static final Pattern REVISION_PATTERN = Pattern.compile("revision=\"(.*)\"");
private static final Pattern AUTHOR_PATTERN = Pattern.compile("<author>(.*)</author>");
private static final Pattern DATE_PATTERN = Pattern.compile("<date>(.*)T(.*)\\.(.*)Z</date>");
private SimpleDateFormat dateFormat;
private List<BlameLine> lines = new ArrayList<BlameLine>();
public SvnBlameConsumer() {
dateFormat = new SimpleDateFormat(SVN_TIMESTAMP_PATTERN);
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private int lineNumber;
private String revision;
private String author;
@Override
public void consumeLine(String line) {
Matcher matcher;
if ((matcher = LINE_PATTERN.matcher(line)).find()) {
String lineNumberStr = matcher.group(1);
lineNumber = Integer.parseInt(lineNumberStr);
}
else if ((matcher = REVISION_PATTERN.matcher(line)).find()) {
revision = matcher.group(1);
}
else if ((matcher = AUTHOR_PATTERN.matcher(line)).find()) {
author = matcher.group(1);
}
else if ((matcher = DATE_PATTERN.matcher(line)).find()) {
String date = matcher.group(1);
String time = matcher.group(2);
Date dateTime = parseDateTime(date + " " + time);
lines.add(new BlameLine(dateTime, revision, author));
}
}
protected Date parseDateTime(String dateTimeStr) {
try {
return dateFormat.parse(dateTimeStr);
} catch (ParseException e) {
LOG.error("skip ParseException: " + e.getMessage() + " during parsing date " + dateTimeStr, e);
return null;
}
}
public List<BlameLine> getLines() {
return lines;
}
}

View File

@ -0,0 +1,109 @@
/*
* 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.svn;
import com.google.common.collect.ImmutableList;
import org.sonar.api.BatchComponent;
import org.sonar.api.CoreProperties;
import org.sonar.api.PropertyType;
import org.sonar.api.batch.InstantiationStrategy;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.config.Settings;
import org.sonar.api.resources.Qualifiers;
import javax.annotation.CheckForNull;
import java.util.List;
@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
public class SvnConfiguration implements BatchComponent {
private static final String CATEGORY_SVN = "SVN";
private static final String USER_PROP_KEY = "sonar.svn.username";
private static final String PASSWORD_PROP_KEY = "sonar.svn.password";
private static final String CONFIG_DIR_PROP_KEY = "sonar.svn.config_dir";
private static final String TRUST_SERVER_PROP_KEY = "sonar.svn.trust_server_cert";
private final Settings settings;
public SvnConfiguration(Settings settings) {
this.settings = settings;
}
public static List<PropertyDefinition> getProperties() {
return ImmutableList.of(
PropertyDefinition.builder(USER_PROP_KEY)
.name("Username")
.description("Username to be used for SVN authentication")
.type(PropertyType.STRING)
.onQualifiers(Qualifiers.PROJECT)
.category(CoreProperties.CATEGORY_SCM)
.subCategory(CATEGORY_SVN)
.index(0)
.build(),
PropertyDefinition.builder(PASSWORD_PROP_KEY)
.name("Password")
.description("Password to be used for SVN authentication")
.type(PropertyType.STRING)
.onQualifiers(Qualifiers.PROJECT)
.category(CoreProperties.CATEGORY_SCM)
.subCategory(CATEGORY_SVN)
.index(1)
.build(),
PropertyDefinition.builder(CONFIG_DIR_PROP_KEY)
.name("Configuration directory")
.description("Folder containing configuration files (see --config-dir)")
.type(PropertyType.STRING)
.onQualifiers(Qualifiers.PROJECT)
.category(CoreProperties.CATEGORY_SCM)
.subCategory(CATEGORY_SVN)
.index(2)
.build(),
PropertyDefinition.builder(TRUST_SERVER_PROP_KEY)
.name("Trust server certificate")
.description("Accept unknown SSL certificates (like self-signed)")
.type(PropertyType.BOOLEAN)
.defaultValue("false")
.onQualifiers(Qualifiers.PROJECT)
.category(CoreProperties.CATEGORY_SCM)
.subCategory(CATEGORY_SVN)
.index(3)
.build());
}
@CheckForNull
public String username() {
return settings.getString(USER_PROP_KEY);
}
@CheckForNull
public String password() {
return settings.getString(PASSWORD_PROP_KEY);
}
@CheckForNull
public String configDir() {
return settings.getString(CONFIG_DIR_PROP_KEY);
}
public boolean trustServerCert() {
return settings.getBoolean(TRUST_SERVER_PROP_KEY);
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.svn;
import com.google.common.collect.ImmutableList;
import org.sonar.api.SonarPlugin;
import java.util.ArrayList;
import java.util.List;
public final class SvnPlugin extends SonarPlugin {
public List getExtensions() {
ArrayList result = new ArrayList();
result.addAll(ImmutableList.of(
SvnScmProvider.class,
SvnBlameCommand.class,
SvnConfiguration.class));
result.addAll(SvnConfiguration.getProperties());
return result;
}
}

View File

@ -0,0 +1,49 @@
/*
* 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.svn;
import org.sonar.api.batch.scm.BlameCommand;
import org.sonar.api.batch.scm.ScmProvider;
import java.io.File;
public class SvnScmProvider extends ScmProvider {
private final SvnBlameCommand blameCommand;
public SvnScmProvider(SvnBlameCommand blameCommand) {
this.blameCommand = blameCommand;
}
@Override
public String key() {
return "svn";
}
@Override
public boolean supports(File baseDir) {
return new File(baseDir, ".svn").exists();
}
@Override
public BlameCommand blameCommand() {
return blameCommand;
}
}

View File

@ -0,0 +1,25 @@
/*
* 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.plugins.scm.svn;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@ -0,0 +1,126 @@
/*
* 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.svn;
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 java.util.List;
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 SvnBlameCommandTest {
@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];
List<String> lines = FileUtils.readLines(new File("src/test/resources/blame.xml"), "UTF-8");
for (String line : lines) {
outConsumer.consumeLine(line);
}
return 0;
}
});
new SvnBlameCommand(commandExecutor, mock(SvnConfiguration.class)).blame(fs, Arrays.<InputFile>asList(inputFile), result);
verify(result).add(inputFile,
Arrays.asList(
new BlameLine(DateUtils.parseDateTime("2009-04-18T10:29:59+0000"), "9491", "simon.brandhof"),
new BlameLine(DateUtils.parseDateTime("2009-04-18T10:29:59+0000"), "9491", "simon.brandhof"),
new BlameLine(DateUtils.parseDateTime("2009-08-31T22:32:17+0000"), "10558", "david")));
}
@Test
public void testExecutionError() 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 errConsumer = (StreamConsumer) invocation.getArguments()[2];
errConsumer.consumeLine("My error");
return 1;
}
});
thrown.expect(IllegalStateException.class);
thrown.expectMessage("The svn blame command [svn blame --xml src/foo.xoo --non-interactive] failed: My error");
new SvnBlameCommand(commandExecutor, mock(SvnConfiguration.class)).blame(fs, Arrays.<InputFile>asList(inputFile), result);
}
}

View File

@ -0,0 +1,32 @@
/*
* 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.svn;
import org.junit.Test;
import static org.fest.assertions.Assertions.assertThat;
public class SvnPluginTest {
@Test
public void getExtensions() {
assertThat(new SvnPlugin().getExtensions()).hasSize(7);
}
}

View File

@ -0,0 +1,58 @@
/*
* 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.svn;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.mock;
public class SvnScmProviderTest {
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void sanityCheck() {
SvnBlameCommand blameCommand = new SvnBlameCommand(mock(SvnConfiguration.class));
SvnScmProvider svnScmProvider = new SvnScmProvider(blameCommand);
assertThat(svnScmProvider.key()).isEqualTo("svn");
assertThat(svnScmProvider.blameCommand()).isEqualTo(blameCommand);
}
@Test
public void testAutodetection() throws IOException {
File baseDirEmpty = temp.newFolder();
assertThat(new SvnScmProvider(null).supports(baseDirEmpty)).isFalse();
File svnBaseDir = temp.newFolder();
new File(svnBaseDir, ".svn").mkdir();
assertThat(new SvnScmProvider(null).supports(svnBaseDir)).isTrue();
}
}

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<blame>
<target
path="pom.xml">
<entry
line-number="1">
<commit
revision="9491">
<author>simon.brandhof</author>
<date>2009-04-18T10:29:59.077093Z</date>
</commit>
</entry>
<entry
line-number="2">
<commit
revision="9491">
<author>simon.brandhof</author>
<date>2009-04-18T10:29:59.077093Z</date>
</commit>
</entry>
<entry
line-number="3">
<commit
revision="10558">
<author>david</author>
<date>2009-08-31T22:32:17.361675Z</date>
</commit>
</entry>
</target>
</blame>

Binary file not shown.

View File

@ -36,6 +36,7 @@
<module>plugins/sonar-l10n-en-plugin</module>
<module>plugins/sonar-email-notifications-plugin</module>
<module>plugins/sonar-git-plugin</module>
<module>plugins/sonar-svn-plugin</module>
<module>plugins/sonar-xoo-plugin</module>
</modules>
@ -597,6 +598,12 @@
<version>${project.version}</version>
<type>sonar-plugin</type>
</dependency>
<dependency>
<groupId>org.codehaus.sonar.plugins</groupId>
<artifactId>sonar-svn-plugin</artifactId>
<version>${project.version}</version>
<type>sonar-plugin</type>
</dependency>
<dependency>
<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-squid</artifactId>

View File

@ -137,6 +137,12 @@
<type>sonar-plugin</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.sonar.plugins</groupId>
<artifactId>sonar-svn-plugin</artifactId>
<type>sonar-plugin</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.sonatype.jsw-binaries</groupId>
<artifactId>jsw-binaries</artifactId>

View File

@ -27,7 +27,11 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.concurrent.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* Synchronously execute a native command line. It's much more limited than the Apache Commons Exec library.
@ -52,6 +56,7 @@ public class CommandExecutor {
/**
* @throws org.sonar.api.utils.command.TimeoutException on timeout, since 4.4
* @throws CommandException on any other error
* @param timeoutMilliseconds set it to 0 for no timeout.
* @since 3.0
*/
public int execute(Command command, StreamConsumer stdOut, StreamConsumer stdErr, long timeoutMilliseconds) {
@ -80,7 +85,12 @@ public class CommandExecutor {
return finalProcess.waitFor();
}
});
int exitCode = ft.get(timeoutMilliseconds, TimeUnit.MILLISECONDS);
int exitCode;
if (timeoutMilliseconds == 0) {
exitCode = ft.get();
} else {
exitCode = ft.get(timeoutMilliseconds, TimeUnit.MILLISECONDS);
}
waitUntilFinish(outputGobbler);
waitUntilFinish(errorGobbler);
verifyGobbler(command, outputGobbler, "stdOut");