+++ /dev/null
-/*
- * 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.eclipse.jgit.api.errors.GitAPIException;
-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.BlameLine;
-import org.sonar.api.utils.command.Command;
-import org.sonar.api.utils.command.CommandExecutor;
-import org.sonar.api.utils.command.StreamConsumer;
-import org.sonar.api.utils.command.StringStreamConsumer;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-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;
-
-public class GitBlameCommand extends 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(BlameInput input, BlameOutput output) {
- FileSystem fs = input.fileSystem();
- LOG.debug("Working directory: " + fs.baseDir().getAbsolutePath());
- ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1);
- List<Future<Void>> tasks = new ArrayList<Future<Void>>();
- for (InputFile inputFile : input.filesToBlame()) {
- tasks.add(submitTask(fs.baseDir(), output, inputFile, executorService));
- }
- for (Future<Void> task : tasks) {
- try {
- task.get();
- } catch (ExecutionException e) {
- // Unwrap ExecutionException
- throw e.getCause() instanceof RuntimeException ? (RuntimeException) e.getCause() : new IllegalStateException(e.getCause());
- } catch (InterruptedException e) {
- throw new IllegalStateException(e);
- }
- }
- }
-
- private Future<Void> submitTask(final File baseDir, final BlameOutput output, final InputFile inputFile, ExecutorService executorService) {
- return executorService.submit(new Callable<Void>() {
- @Override
- public Void call() throws GitAPIException {
- blame(baseDir, output, inputFile);
- return null;
- }
-
- });
- }
-
- private void blame(File baseDir, BlameOutput output, InputFile inputFile) {
- String filename = inputFile.relativePath();
- Command cl = createCommandLine(baseDir, filename);
- GitBlameConsumer consumer = new GitBlameConsumer(filename);
- 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());
- }
- List<BlameLine> lines = consumer.getLines();
- if (lines.size() == inputFile.lines() - 1) {
- // SONARPLUGINS-3097 Git do not report blame on last empty line
- lines.add(lines.get(lines.size() - 1));
- }
- output.blameResult(inputFile, lines);
-
- }
-
- public int execute(Command cl, StreamConsumer consumer, StreamConsumer stderr) {
- LOG.debug("Executing: " + cl);
- return commandExecutor.execute(cl, consumer, stderr, -1);
- }
-
- 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;
- }
-
-}
+++ /dev/null
-/*
- * 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.apache.commons.lang.StringUtils;
-import org.sonar.api.batch.scm.BlameLine;
-import org.sonar.api.utils.command.StreamConsumer;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-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 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 Date time = null;
- private final String filename;
-
- public GitBlameConsumer(String filename) {
- this.filename = filename;
- }
-
- @Override
- 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_TIME)) {
- String timeStr = line.substring(GIT_COMMITTER_TIME.length());
- time = new Date(Long.parseLong(timeStr) * 1000L);
- return true;
- }
- return false;
- }
-
- 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().date(time).revision(revision).author(author);
- getLines().add(blameLine);
-
- // keep commitinfo for this sha-1
- commitInfo.put(revision, blameLine);
-
- expectRevisionLine = true;
- }
-
- private void consumeRevisionLine(String line) {
- String[] parts = line.split("\\s", 4);
-
- if (parts.length >= 1) {
- revision = parts[0];
-
- if (StringUtils.containsOnly(revision, "0")) {
- throw new IllegalStateException("Unable to blame file " + filename + ". No blame info at line " + (getLines().size() + 1) + ". Is file commited?");
- }
-
- BlameLine oldLine = commitInfo.get(revision);
-
- if (oldLine != null) {
- // restore the commit info
- author = oldLine.author();
- time = oldLine.date();
- }
-
- expectRevisionLine = false;
- }
- }
-
- public List<BlameLine> getLines() {
- return lines;
- }
-}
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;
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";
@Override
public List getExtensions() {
return ImmutableList.of(
GitScmProvider.class,
- GitBlameCommand.class,
- JGitBlameCommand.class,
-
- PropertyDefinition.builder(GIT_IMPLEMENTATION_PROP_KEY)
- .name("Git implementation")
- .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());
+ JGitBlameCommand.class);
}
-
}
import org.sonar.api.batch.scm.BlameCommand;
import org.sonar.api.batch.scm.ScmProvider;
-import org.sonar.api.config.Settings;
import java.io.File;
public class GitScmProvider extends ScmProvider {
- private final GitBlameCommand blameCommand;
private final JGitBlameCommand jgitBlameCommand;
- private final Settings settings;
- public GitScmProvider(Settings settings, GitBlameCommand blameCommand, JGitBlameCommand jgitBlameCommand) {
- this.settings = settings;
- this.blameCommand = blameCommand;
+ public GitScmProvider(JGitBlameCommand jgitBlameCommand) {
this.jgitBlameCommand = jgitBlameCommand;
}
@Override
public BlameCommand blameCommand() {
- String implem = settings.getString(GitPlugin.GIT_IMPLEMENTATION_PROP_KEY);
- if (GitPlugin.EXE.equals(implem)) {
- return this.blameCommand;
- } else if (GitPlugin.JGIT.equals(implem)) {
- return this.jgitBlameCommand;
- } else {
- throw new IllegalArgumentException("Illegal value for " + GitPlugin.GIT_IMPLEMENTATION_PROP_KEY + ": " + implem);
- }
+ return this.jgitBlameCommand;
}
}
List<Future<Void>> tasks = submitTasks(input, output, git, gitBaseDir, executorService);
waitForTaskToComplete(tasks);
} finally {
- if (repo != null) {
- repo.close();
- }
+ repo.close();
}
}
+++ /dev/null
-/*
- * 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.BlameInput;
-import org.sonar.api.batch.scm.BlameCommand.BlameOutput;
-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;
- private BlameInput input;
-
- @Before
- public void prepare() throws IOException {
- baseDir = temp.newFolder();
- fs = new DefaultFileSystem();
- fs.setBaseDir(baseDir);
- input = mock(BlameInput.class);
- when(input.fileSystem()).thenReturn(fs);
- }
-
- @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);
-
- BlameOutput result = mock(BlameOutput.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;
- }
- });
- when(input.filesToBlame()).thenReturn(Arrays.<InputFile>asList(inputFile));
- new GitBlameCommand(commandExecutor).blame(input, result);
- verify(result).blameResult(
- inputFile,
- Arrays.asList(
- new BlameLine().date(DateUtils.parseDateTime("2011-08-05T10:49:31+0200")).revision("2c68c473da7fc293e12ca50f19380c5118be7ead").author("simon.brandhof@gmail.com"),
- new BlameLine().date(DateUtils.parseDateTime("2011-08-05T10:49:31+0200")).revision("2c68c473da7fc293e12ca50f19380c5118be7ead").author("simon.brandhof@gmail.com")));
- }
-
- @Test
- public void shouldFailOnFileWithLocalModification() 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);
-
- BlameOutput result = mock(BlameOutput.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("000000000000000000000000000000000000000 68 54 1");
- outConsumer.consumeLine("author Not Committed Yet");
- outConsumer.consumeLine("author-mail <not.committed.yet>");
- outConsumer.consumeLine("author-time 1312534171");
- outConsumer.consumeLine("author-tz +0200");
- outConsumer.consumeLine("committer Not Committed Yet");
- outConsumer.consumeLine("committer-mail <not.committed.yet>");
- 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;
- }
- });
-
- thrown.expect(IllegalStateException.class);
- thrown.expectMessage("Unable to blame file src/foo.xoo. No blame info at line 1. Is file commited?");
- when(input.filesToBlame()).thenReturn(Arrays.<InputFile>asList(inputFile));
- new GitBlameCommand(commandExecutor).blame(input, result);
- }
-
- @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);
-
- BlameOutput result = mock(BlameOutput.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");
-
- when(input.filesToBlame()).thenReturn(Arrays.<InputFile>asList(inputFile));
- new GitBlameCommand(commandExecutor).blame(input, result);
- }
-
-}
@Test
public void getExtensions() {
- assertThat(new GitPlugin().getExtensions()).hasSize(4);
+ assertThat(new GitPlugin().getExtensions()).hasSize(2);
}
}
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
-import org.sonar.api.config.PropertyDefinitions;
-import org.sonar.api.config.Settings;
import org.sonar.api.scan.filesystem.PathResolver;
import java.io.File;
@Test
public void sanityCheck() {
- assertThat(new GitScmProvider(null, null, null).key()).isEqualTo("git");
+ assertThat(new GitScmProvider(null).key()).isEqualTo("git");
}
@Test
- public void selectImplem() {
- GitBlameCommand blameCommand = new GitBlameCommand();
+ public void returnImplem() {
JGitBlameCommand jblameCommand = new JGitBlameCommand(new PathResolver());
- Settings settings = new Settings(new PropertyDefinitions(new GitPlugin().getExtensions()));
- GitScmProvider gitScmProvider = new GitScmProvider(settings, blameCommand, jblameCommand);
+ GitScmProvider gitScmProvider = new GitScmProvider(jblameCommand);
assertThat(gitScmProvider.blameCommand()).isEqualTo(jblameCommand);
-
- settings.setProperty(GitPlugin.GIT_IMPLEMENTATION_PROP_KEY, GitPlugin.EXE);
- assertThat(gitScmProvider.blameCommand()).isEqualTo(blameCommand);
-
- settings.setProperty(GitPlugin.GIT_IMPLEMENTATION_PROP_KEY, GitPlugin.JGIT);
- assertThat(gitScmProvider.blameCommand()).isEqualTo(jblameCommand);
-
- settings.setProperty(GitPlugin.GIT_IMPLEMENTATION_PROP_KEY, "foo");
- thrown.expect(IllegalArgumentException.class);
- thrown.expectMessage("Illegal value for " + GitPlugin.GIT_IMPLEMENTATION_PROP_KEY + ": foo");
- gitScmProvider.blameCommand();
}
@Test
public void testAutodetection() throws IOException {
File baseDirEmpty = temp.newFolder();
- assertThat(new GitScmProvider(null, null, null).supports(baseDirEmpty)).isFalse();
+ assertThat(new GitScmProvider(null).supports(baseDirEmpty)).isFalse();
File gitBaseDir = temp.newFolder();
new File(gitBaseDir, ".git").mkdir();
- assertThat(new GitScmProvider(null, null, null).supports(gitBaseDir)).isTrue();
+ assertThat(new GitScmProvider(null).supports(gitBaseDir)).isTrue();
}
}