From 981c3bd653a209ae76f439a870e975b70082a3e4 Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Thu, 6 Nov 2014 09:42:22 +0100 Subject: [PATCH] SONAR-5843 Use the original SVN author on merges --- .../plugins/scm/svn/SvnBlameCommand.java | 3 ++ .../plugins/scm/svn/SvnBlameConsumer.java | 53 ++++++++++++++---- .../plugins/scm/svn/SvnConfiguration.java | 17 ++++++ .../plugins/scm/svn/SvnBlameCommandTest.java | 42 +++++++++++++-- .../sonar/plugins/scm/svn/SvnPluginTest.java | 2 +- .../resources/blame-with-merge-history.xml | 54 +++++++++++++++++++ 6 files changed, 157 insertions(+), 14 deletions(-) create mode 100644 plugins/sonar-svn-plugin/src/test/resources/blame-with-merge-history.xml diff --git a/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnBlameCommand.java b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnBlameCommand.java index 2e1038f162b..f088967589a 100644 --- a/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnBlameCommand.java +++ b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnBlameCommand.java @@ -128,6 +128,9 @@ public class SvnBlameCommand extends BlameCommand { cl.setDirectory(baseDir); cl.addArgument("blame"); cl.addArgument("--xml"); + if (configuration.useMergeHistory()) { + cl.addArgument("--use-merge-history"); + } cl.addArgument("--non-interactive"); cl.addArgument("-x"); cl.addArgument("-w"); diff --git a/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnBlameConsumer.java b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnBlameConsumer.java index 8224fab347d..3bf42c756b3 100644 --- a/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnBlameConsumer.java +++ b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnBlameConsumer.java @@ -68,6 +68,9 @@ public class SvnBlameConsumer implements StreamConsumer { private static final Pattern DATE_PATTERN = Pattern.compile("(.*)T(.*)\\.(.*)Z"); + private boolean insideCommitSection = false; + private boolean insideMergedSection = false; + private SimpleDateFormat dateFormat; private List lines = new ArrayList(); @@ -82,31 +85,61 @@ public class SvnBlameConsumer implements StreamConsumer { private int lineNumber = 0; - private String revision; - + private String committerRevision; + private String committer; + private Date committerDate; + private String authorRevision; private String author; + private Date authorDate; @Override public void consumeLine(String line) { Matcher matcher; if ((matcher = LINE_PATTERN.matcher(line)).find()) { - if (lineNumber != 0) { - throw new IllegalStateException("Unable to blame file " + filename + ". No blame info at line " + lineNumber + ". Is file commited?"); - } String lineNumberStr = matcher.group(1); lineNumber = Integer.parseInt(lineNumberStr); + insideCommitSection = false; + insideMergedSection = false; + } else if (line.contains("")) { + if (authorRevision != null) { + lines.add(new BlameLine().revision(authorRevision).author(author).date(authorDate)); + } else if (committerRevision != null) { + lines.add(new BlameLine().revision(committerRevision).author(committer).date(committerDate)); + } else { + throw new IllegalStateException("Unable to blame file " + filename + ". No blame info at line " + lineNumber + ". Is file commited?"); + } + insideCommitSection = false; + insideMergedSection = false; author = null; + committer = null; + committerRevision = null; + authorRevision = null; } } diff --git a/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnConfiguration.java b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnConfiguration.java index 16942c8a0f8..8c7db3efb6d 100644 --- a/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnConfiguration.java +++ b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnConfiguration.java @@ -40,6 +40,7 @@ public class SvnConfiguration implements BatchComponent { public static final String PASSWORD_PROP_KEY = "sonar.svn.password.secured"; public static final String CONFIG_DIR_PROP_KEY = "sonar.svn.config_dir"; public static final String TRUST_SERVER_PROP_KEY = "sonar.svn.trust_server_cert"; + public static final String USE_MERGE_HISTORY_KEY = "sonar.svn.use_merge_history"; private final Settings settings; public SvnConfiguration(Settings settings) { @@ -84,6 +85,18 @@ public class SvnConfiguration implements BatchComponent { .category(CoreProperties.CATEGORY_SCM) .subCategory(CATEGORY_SVN) .index(3) + .build(), + PropertyDefinition + .builder(USE_MERGE_HISTORY_KEY) + .name("Use merge history for blame") + .description( + "Use merge history (--use-merge-history) to get real author of a modification instead of commiter of the merge. May not be supported by your SVN server/client.") + .type(PropertyType.BOOLEAN) + .defaultValue("false") + .onQualifiers(Qualifiers.PROJECT) + .category(CoreProperties.CATEGORY_SCM) + .subCategory(CATEGORY_SVN) + .index(4) .build()); } @@ -106,4 +119,8 @@ public class SvnConfiguration implements BatchComponent { return settings.getBoolean(TRUST_SERVER_PROP_KEY); } + public boolean useMergeHistory() { + return settings.getBoolean(USE_MERGE_HISTORY_KEY); + } + } diff --git a/plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnBlameCommandTest.java b/plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnBlameCommandTest.java index 6bc94b51ddd..f306fe3fc60 100644 --- a/plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnBlameCommandTest.java +++ b/plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnBlameCommandTest.java @@ -140,6 +140,39 @@ public class SvnBlameCommandTest { new BlameLine().date(DateUtils.parseDateTime("2009-08-31T22:32:17+0000")).revision("10558").author("david"))); } + @Test + public void testParsingOfOutputWithMergeHistory() 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() { + + @Override + public Integer answer(InvocationOnMock invocation) throws Throwable { + StreamConsumer outConsumer = (StreamConsumer) invocation.getArguments()[1]; + List lines = IOUtils.readLines(getClass().getResourceAsStream("/blame-with-merge-history.xml"), "UTF-8"); + for (String line : lines) { + outConsumer.consumeLine(line); + } + return 0; + } + }); + + when(input.filesToBlame()).thenReturn(Arrays.asList(inputFile)); + + new SvnBlameCommand(commandExecutor, mock(SvnConfiguration.class)).blame(input, result); + verify(result).blameResult(inputFile, + Arrays.asList( + new BlameLine().date(DateUtils.parseDateTime("2012-07-19T11:44:57+0200")).revision("9490").author("dgageot"), + new BlameLine().date(DateUtils.parseDateTime("2009-04-18T10:29:59+0000")).revision("9491").author("simon.brandhof"), + new BlameLine().date(DateUtils.parseDateTime("2009-08-31T22:32:17+0000")).revision("10558").author("david"))); + } + @Test public void shouldFailIfFileContainsLocalModification() throws IOException { File source = new File(baseDir, "src/foo.xoo"); @@ -215,10 +248,13 @@ public class SvnBlameCommandTest { settings.setProperty(SvnConfiguration.CONFIG_DIR_PROP_KEY, "/home/julien/.svn"); settings.setProperty(SvnConfiguration.TRUST_SERVER_PROP_KEY, "true"); + settings.setProperty(SvnConfiguration.USE_MERGE_HISTORY_KEY, "true"); commandLine = svnBlameCommand.createCommandLine(baseDir, "src/main/java/Foo.java"); assertThat(commandLine.toCommandLine()) - .isEqualTo("svn blame --xml --non-interactive -x -w --config-dir /home/julien/.svn --username myUser --password myPass --trust-server-cert src/main/java/Foo.java"); - assertThat(commandLine.toString()).isEqualTo( - "svn blame --xml --non-interactive -x -w --config-dir /home/julien/.svn --username ******** --password ******** --trust-server-cert src/main/java/Foo.java"); + .isEqualTo( + "svn blame --xml --use-merge-history --non-interactive -x -w --config-dir /home/julien/.svn --username myUser --password myPass --trust-server-cert src/main/java/Foo.java"); + assertThat(commandLine.toString()) + .isEqualTo( + "svn blame --xml --use-merge-history --non-interactive -x -w --config-dir /home/julien/.svn --username ******** --password ******** --trust-server-cert src/main/java/Foo.java"); } } diff --git a/plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnPluginTest.java b/plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnPluginTest.java index 7c8ea23a91a..745067f838e 100644 --- a/plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnPluginTest.java +++ b/plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnPluginTest.java @@ -27,6 +27,6 @@ public class SvnPluginTest { @Test public void getExtensions() { - assertThat(new SvnPlugin().getExtensions()).hasSize(7); + assertThat(new SvnPlugin().getExtensions()).hasSize(8); } } diff --git a/plugins/sonar-svn-plugin/src/test/resources/blame-with-merge-history.xml b/plugins/sonar-svn-plugin/src/test/resources/blame-with-merge-history.xml new file mode 100644 index 00000000000..efe87073a7a --- /dev/null +++ b/plugins/sonar-svn-plugin/src/test/resources/blame-with-merge-history.xml @@ -0,0 +1,54 @@ + + + + + +automatic-merge +2009-04-18T10:29:59.077093Z + + + +dgageot +2012-07-19T09:44:57.393222Z + + + + + +simon.brandhof +2009-04-18T10:29:59.077093Z + + + +simon.brandhof +2009-04-18T10:29:59.077093Z + + + + + +david +2009-08-31T22:32:17.361675Z + + + +david +2009-08-31T22:32:17.361675Z + + + + + -- 2.39.5