├── its
├── scm-repo
│ ├── 1.6
│ │ ├── repo-svn.zip
│ │ └── repo-svn-with-merge.zip
│ └── 1.8
│ │ ├── repo-svn.zip
│ │ └── repo-svn-with-merge.zip
├── pom.xml
└── src
│ └── test
│ └── java
│ └── com
│ └── sonarsource
│ └── it
│ └── scm
│ └── SvnTest.java
├── sonar-scm-svn-plugin
├── test-repos
│ ├── 1.6
│ │ ├── repo-svn.zip
│ │ └── repo-svn-with-merge.zip
│ ├── 1.7
│ │ ├── repo-svn.zip
│ │ └── repo-svn-with-merge.zip
│ ├── 1.8
│ │ ├── repo-svn.zip
│ │ └── repo-svn-with-merge.zip
│ └── 1.9
│ │ ├── repo-svn.zip
│ │ └── repo-svn-with-merge.zip
├── src
│ ├── test
│ │ ├── resources
│ │ │ ├── blame-with-uncomitted-changes.xml
│ │ │ ├── blame-with-anonymous-commit.xml
│ │ │ ├── blame.xml
│ │ │ └── blame-with-merge-history.xml
│ │ └── java
│ │ │ └── org
│ │ │ └── sonar
│ │ │ └── plugins
│ │ │ └── scm
│ │ │ └── svn
│ │ │ ├── SvnTesterTest.java
│ │ │ ├── SvnPluginTest.java
│ │ │ ├── SvnConfigurationTest.java
│ │ │ ├── FindForkTest.java
│ │ │ ├── SvnTester.java
│ │ │ ├── ChangedLinesComputerTest.java
│ │ │ ├── SvnScmProviderTest.java
│ │ │ └── SvnBlameCommandTest.java
│ └── main
│ │ └── java
│ │ └── org
│ │ └── sonar
│ │ └── plugins
│ │ └── scm
│ │ └── svn
│ │ ├── package-info.java
│ │ ├── ForkPoint.java
│ │ ├── AnnotationHandler.java
│ │ ├── SvnPlugin.java
│ │ ├── SvnConfiguration.java
│ │ ├── FindFork.java
│ │ ├── SvnBlameCommand.java
│ │ ├── ChangedLinesComputer.java
│ │ └── SvnScmProvider.java
└── pom.xml
├── third-party-licenses.sh
├── NOTICE.txt
├── .gitignore
├── .github
└── workflows
│ └── release.yml
├── README.md
├── pom.xml
├── .cirrus.yml
└── LICENSE.txt
/its/scm-repo/1.6/repo-svn.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SonarSource/sonar-scm-svn/HEAD/its/scm-repo/1.6/repo-svn.zip
--------------------------------------------------------------------------------
/its/scm-repo/1.8/repo-svn.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SonarSource/sonar-scm-svn/HEAD/its/scm-repo/1.8/repo-svn.zip
--------------------------------------------------------------------------------
/its/scm-repo/1.6/repo-svn-with-merge.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SonarSource/sonar-scm-svn/HEAD/its/scm-repo/1.6/repo-svn-with-merge.zip
--------------------------------------------------------------------------------
/its/scm-repo/1.8/repo-svn-with-merge.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SonarSource/sonar-scm-svn/HEAD/its/scm-repo/1.8/repo-svn-with-merge.zip
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/test-repos/1.6/repo-svn.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SonarSource/sonar-scm-svn/HEAD/sonar-scm-svn-plugin/test-repos/1.6/repo-svn.zip
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/test-repos/1.7/repo-svn.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SonarSource/sonar-scm-svn/HEAD/sonar-scm-svn-plugin/test-repos/1.7/repo-svn.zip
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/test-repos/1.8/repo-svn.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SonarSource/sonar-scm-svn/HEAD/sonar-scm-svn-plugin/test-repos/1.8/repo-svn.zip
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/test-repos/1.9/repo-svn.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SonarSource/sonar-scm-svn/HEAD/sonar-scm-svn-plugin/test-repos/1.9/repo-svn.zip
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/test-repos/1.6/repo-svn-with-merge.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SonarSource/sonar-scm-svn/HEAD/sonar-scm-svn-plugin/test-repos/1.6/repo-svn-with-merge.zip
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/test-repos/1.7/repo-svn-with-merge.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SonarSource/sonar-scm-svn/HEAD/sonar-scm-svn-plugin/test-repos/1.7/repo-svn-with-merge.zip
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/test-repos/1.8/repo-svn-with-merge.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SonarSource/sonar-scm-svn/HEAD/sonar-scm-svn-plugin/test-repos/1.8/repo-svn-with-merge.zip
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/test-repos/1.9/repo-svn-with-merge.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SonarSource/sonar-scm-svn/HEAD/sonar-scm-svn-plugin/test-repos/1.9/repo-svn-with-merge.zip
--------------------------------------------------------------------------------
/third-party-licenses.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | mvn org.codehaus.mojo:license-maven-plugin:aggregate-add-third-party -Dlicense.includedScopes=compile
3 |
4 | cat target/generated-sources/license/THIRD-PARTY.txt
5 |
--------------------------------------------------------------------------------
/NOTICE.txt:
--------------------------------------------------------------------------------
1 | SonarQube :: Plugins :: SCM :: SVN
2 | Copyright (C) 2014-2017 SonarSource SA
3 | mailto:info AT sonarsource DOT com
4 |
5 | This product includes software developed at
6 | SonarSource (http://www.sonarsource.com/).
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/test/resources/blame-with-uncomitted-changes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
9 | simon.brandhof
10 | 2009-04-18T10:29:59.077093Z
11 |
12 |
13 |
15 |
16 |
18 |
20 | david
21 | 2009-08-31T22:32:17.361675Z
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/test/resources/blame-with-anonymous-commit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
9 | simon.brandhof
10 | 2009-04-18T10:29:59.077093Z
11 |
12 |
13 |
15 |
17 | 2009-04-01T10:29:59.077093Z
18 |
19 |
20 |
22 |
24 | david
25 | 2009-08-31T22:32:17.361675Z
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/test/resources/blame.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
9 | simon.brandhof
10 | 2009-04-18T10:29:59.077093Z
11 |
12 |
13 |
15 |
17 | simon.brandhof
18 | 2009-04-18T10:29:59.077093Z
19 |
20 |
21 |
23 |
25 | david
26 | 2009-08-31T22:32:17.361675Z
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # The following should be moved in related sub-directories
2 | server/sonar-web/src/main/webapp/stylesheets/sonar-colorizer.css
3 | server/sonar-web/src/main/webapp/deploy/plugins
4 | server/sonar-web/src/main/webapp/deploy/bootstrap
5 | server/sonar-web/src/main/webapp/deploy/maven/org
6 | server/sonar-web/src/main/webapp/WEB-INF/log/
7 | server/sonar-web/src/main/webapp/deploy/*.jar
8 | server/sonar-web/src/main/webapp/deploy/jdbc-driver.txt
9 |
10 |
11 | # ---- Javadoc
12 | docs.tar
13 |
14 | # ---- Maven
15 | target/
16 | dependency-reduced-pom.xml
17 |
18 | # ---- IntelliJ IDEA
19 | *.iws
20 | *.iml
21 | *.ipr
22 | .idea/
23 |
24 | # ---- Eclipse
25 | .classpath
26 | .project
27 | .settings
28 | .externalToolBuilders
29 |
30 | # ---- Mac OS X
31 | .DS_Store
32 | Icon?
33 | # Thumbnails
34 | ._*
35 | # Files that might appear on external disk
36 | .Spotlight-V100
37 | .Trashes
38 |
39 | # ---- Windows
40 | # Windows image file caches
41 | Thumbs.db
42 | # Folder config file
43 | Desktop.ini
44 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SonarQube :: Plugins :: SCM :: SVN
3 | * Copyright (C) 2014-2021 SonarSource SA
4 | * mailto:info AT sonarsource DOT com
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 3 of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public License
17 | * along with this program; if not, write to the Free Software Foundation,
18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 | @ParametersAreNonnullByDefault
21 | package org.sonar.plugins.scm.svn;
22 |
23 | import javax.annotation.ParametersAreNonnullByDefault;
24 |
25 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/test/resources/blame-with-merge-history.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
9 | automatic-merge
10 | 2009-04-18T10:29:59.077093Z
11 |
12 |
14 |
16 | dgageot
17 | 2012-07-19T09:44:57.393222Z
18 |
19 |
20 |
21 |
23 |
25 | simon.brandhof
26 | 2009-04-18T10:29:59.077093Z
27 |
28 |
30 |
32 | simon.brandhof
33 | 2009-04-18T10:29:59.077093Z
34 |
35 |
36 |
37 |
39 |
41 | david
42 | 2009-08-31T22:32:17.361675Z
43 |
44 |
46 |
48 | david
49 | 2009-08-31T22:32:17.361675Z
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 | # This workflow is triggered when publishing a GitHub release
3 | on:
4 | release:
5 | types:
6 | - published
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-latest
11 | name: Start release process
12 | steps:
13 | # Not sure why this is needed... Fixes issue with running the action.
14 | - name: Checkout release action
15 | uses: actions/checkout@v2
16 | with:
17 | repository: SonarSource/gh-action_LT_release
18 | - name: Run release action
19 | id: run_release
20 | with:
21 | distribute: true
22 | uses: SonarSource/gh-action_LT_release@master
23 | env:
24 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
25 | - name: Log outputs
26 | if: always()
27 | run: |
28 | echo "${{ steps.run_release.outputs.releasability }}"
29 | echo "${{ steps.run_release.outputs.release }}"
30 | - name: Notify success on Slack
31 | uses: Ilshidur/action-slack@2.0.0
32 | env:
33 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
34 | with:
35 | args: "Release successful for {{ GITHUB_REPOSITORY }} by {{ GITHUB_ACTOR }}"
36 | - name: Notify failures on Slack
37 | uses: Ilshidur/action-slack@2.0.0
38 | if: failure()
39 | env:
40 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
41 | with:
42 | args: "Release failed, see the logs at https://github.com/{{ GITHUB_REPOSITORY }}/actions by {{ GITHUB_ACTOR }}"
43 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/ForkPoint.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SonarQube :: Plugins :: SCM :: SVN
3 | * Copyright (C) 2014-2021 SonarSource SA
4 | * mailto:info AT sonarsource DOT com
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 3 of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public License
17 | * along with this program; if not, write to the Free Software Foundation,
18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 | package org.sonar.plugins.scm.svn;
21 |
22 | import java.time.Instant;
23 |
24 | public class ForkPoint {
25 | private String commit;
26 | private Instant date;
27 |
28 | public ForkPoint(String commit, Instant date) {
29 | this.commit = commit;
30 | this.date = date;
31 | }
32 |
33 | public String commit() {
34 | return commit;
35 | }
36 |
37 | public Instant date() {
38 | return date;
39 | }
40 |
41 | @Override
42 | public String toString() {
43 | return "ForkPoint{" +
44 | "commit='" + commit + '\'' +
45 | ", date=" + date +
46 | '}';
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SonarQube SVN Plugin
2 |
3 | [](https://cirrus-ci.com/github/SonarSource/sonar-scm-svn)
4 |
5 | ### Enbedded since SonarQube 8.5
6 |
7 | This plugin is embedded in SonarQube starting 8.5, see [embedded sources](https://github.com/SonarSource/sonarqube/tree/master/sonar-scanner-engine/src/main/java/org/sonar/scm/svn).
8 |
9 | ### Have Question or Feedback?
10 |
11 | For support questions ("How do I?", "I got this error, why?", ...), please head to the [SonarSource forum](https://community.sonarsource.com/c/help). There are chances that a question similar to yours has already been answered.
12 |
13 | Be aware that this forum is a community, so the standard pleasantries ("Hi", "Thanks", ...) are expected. And if you don't get an answer to your thread, you should sit on your hands for at least three days before bumping it. Operators are not standing by. :-)
14 |
15 | ### Contributing
16 |
17 | If you would like to see a new feature, please create a new thread in the forum ["Suggest new features"](https://community.sonarsource.com/c/suggestions/features).
18 |
19 | Please be aware that we are not actively looking for feature contributions. The truth is that it's extremely difficult for someone outside SonarSource to comply with our roadmap and expectations. Therefore, we typically only accept minor cosmetic changes and typo fixes.
20 |
21 | With that in mind, if you would like to submit a code contribution, please create a pull request for this repository. Please explain your motives to contribute this change: what problem you are trying to fix, what improvement you are trying to make.
22 |
23 | Make sure that you follow our code style and all tests are passing (Travis build is executed for each pull request).
24 |
25 | ### License
26 |
27 | Copyright 2014-2017 SonarSource.
28 |
29 | Licensed under the [GNU Lesser General Public License, Version 3.0](http://www.gnu.org/licenses/lgpl.txt)
30 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/AnnotationHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SonarQube :: Plugins :: SCM :: SVN
3 | * Copyright (C) 2014-2021 SonarSource SA
4 | * mailto:info AT sonarsource DOT com
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 3 of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public License
17 | * along with this program; if not, write to the Free Software Foundation,
18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 | package org.sonar.plugins.scm.svn;
21 |
22 | import java.io.File;
23 | import java.util.ArrayList;
24 | import java.util.Date;
25 | import java.util.List;
26 | import org.sonar.api.batch.scm.BlameLine;
27 | import org.tmatesoft.svn.core.SVNException;
28 | import org.tmatesoft.svn.core.wc.ISVNAnnotateHandler;
29 |
30 | public class AnnotationHandler implements ISVNAnnotateHandler {
31 |
32 | private List lines = new ArrayList<>();
33 |
34 | @Override
35 | public void handleEOF() {
36 | // Not used
37 | }
38 |
39 | @Override
40 | public void handleLine(Date date, long revision, String author, String line) throws SVNException {
41 | // deprecated
42 | }
43 |
44 | @Override
45 | public void handleLine(Date date, long revision, String author, String line, Date mergedDate,
46 | long mergedRevision, String mergedAuthor, String mergedPath, int lineNumber) throws SVNException {
47 | lines.add(new BlameLine().date(mergedDate).revision(Long.toString(mergedRevision)).author(mergedAuthor));
48 | }
49 |
50 | @Override
51 | public boolean handleRevision(Date date, long revision, String author, File contents) throws SVNException {
52 | /*
53 | * We do not want our file to be annotated for each revision of the range, but only for the last
54 | * revision of it, so we return false
55 | */
56 | return false;
57 | }
58 |
59 | public List getLines() {
60 | return lines;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnPlugin.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SonarQube :: Plugins :: SCM :: SVN
3 | * Copyright (C) 2014-2021 SonarSource SA
4 | * mailto:info AT sonarsource DOT com
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 3 of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public License
17 | * along with this program; if not, write to the Free Software Foundation,
18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 | package org.sonar.plugins.scm.svn;
21 |
22 | import javax.annotation.CheckForNull;
23 | import javax.annotation.Nullable;
24 | import org.sonar.api.Plugin;
25 | import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
26 | import org.tmatesoft.svn.core.wc.ISVNOptions;
27 | import org.tmatesoft.svn.core.wc.SVNClientManager;
28 | import org.tmatesoft.svn.core.wc.SVNWCUtil;
29 |
30 | public final class SvnPlugin implements Plugin {
31 | static SVNClientManager newSvnClientManager(SvnConfiguration configuration) {
32 | ISVNOptions options = SVNWCUtil.createDefaultOptions(true);
33 | final char[] passwordValue = getCharsOrNull(configuration.password());
34 | final char[] passPhraseValue = getCharsOrNull(configuration.passPhrase());
35 | ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(
36 | null,
37 | configuration.username(),
38 | passwordValue,
39 | configuration.privateKey(),
40 | passPhraseValue,
41 | false);
42 | return SVNClientManager.newInstance(options, authManager);
43 | }
44 |
45 | @CheckForNull
46 | private static char[] getCharsOrNull(@Nullable String s) {
47 | return s != null ? s.toCharArray() : null;
48 | }
49 |
50 | @Override
51 | public void define(Context context) {
52 | context.addExtensions(SvnScmProvider.class,
53 | SvnBlameCommand.class,
54 | SvnConfiguration.class,
55 | FindFork.class);
56 | context.addExtensions(SvnConfiguration.getProperties());
57 |
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnTesterTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SonarQube :: Plugins :: SCM :: SVN
3 | * Copyright (C) 2014-2021 SonarSource SA
4 | * mailto:info AT sonarsource DOT com
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 3 of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public License
17 | * along with this program; if not, write to the Free Software Foundation,
18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 | package org.sonar.plugins.scm.svn;
21 |
22 | import java.io.IOException;
23 | import java.nio.file.Path;
24 | import org.junit.Before;
25 | import org.junit.Rule;
26 | import org.junit.Test;
27 | import org.junit.rules.TemporaryFolder;
28 | import org.tmatesoft.svn.core.SVNException;
29 |
30 | import static org.assertj.core.api.Assertions.assertThat;
31 |
32 | public class SvnTesterTest {
33 | @Rule
34 | public TemporaryFolder temp = new TemporaryFolder();
35 |
36 | private SvnTester tester;
37 |
38 | @Before
39 | public void before() throws IOException, SVNException {
40 | tester = new SvnTester(temp.newFolder().toPath());
41 | }
42 |
43 | @Test
44 | public void test_init() throws SVNException {
45 | assertThat(tester.list()).containsExactlyInAnyOrder("trunk", "branches");
46 | }
47 |
48 | @Test
49 | public void test_add_and_commit() throws IOException, SVNException {
50 | assertThat(tester.list("trunk")).isEmpty();
51 |
52 | Path worktree = temp.newFolder().toPath();
53 | tester.checkout(worktree, "trunk");
54 | tester.createFile(worktree, "file1");
55 |
56 | tester.add(worktree, "file1");
57 | tester.commit(worktree);
58 |
59 | assertThat(tester.list("trunk")).containsOnly("file1");
60 | }
61 |
62 | @Test
63 | public void test_createBranch() throws IOException, SVNException {
64 | tester.createBranch("b1");
65 | assertThat(tester.list()).containsExactlyInAnyOrder("trunk", "branches", "branches/b1");
66 | assertThat(tester.list("branches")).containsOnly("b1");
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnPluginTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SonarQube :: Plugins :: SCM :: SVN
3 | * Copyright (C) 2014-2021 SonarSource SA
4 | * mailto:info AT sonarsource DOT com
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 3 of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public License
17 | * along with this program; if not, write to the Free Software Foundation,
18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 | package org.sonar.plugins.scm.svn;
21 |
22 | import org.junit.Test;
23 | import org.sonar.api.Plugin;
24 | import org.sonar.api.SonarEdition;
25 | import org.sonar.api.SonarQubeSide;
26 | import org.sonar.api.SonarRuntime;
27 | import org.sonar.api.internal.SonarRuntimeImpl;
28 | import org.sonar.api.utils.Version;
29 |
30 | import static org.assertj.core.api.Assertions.assertThat;
31 | import static org.mockito.Mockito.mock;
32 | import static org.mockito.Mockito.when;
33 | import static org.sonar.plugins.scm.svn.SvnPlugin.newSvnClientManager;
34 |
35 | public class SvnPluginTest {
36 | @Test
37 | public void getExtensions() {
38 | SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(7, 9), SonarQubeSide.SCANNER, SonarEdition.DEVELOPER);
39 | Plugin.Context context = new Plugin.Context(runtime);
40 | new SvnPlugin().define(context);
41 | assertThat(context.getExtensions()).isNotEmpty();
42 | }
43 |
44 | @Test
45 | public void newSvnClientManager_with_auth() {
46 | SvnConfiguration config = mock(SvnConfiguration.class);
47 | when(config.password()).thenReturn("password");
48 | when(config.passPhrase()).thenReturn("passPhrase");
49 | assertThat(newSvnClientManager(config)).isNotNull();
50 | }
51 |
52 | @Test
53 | public void newSvnClientManager_without_auth() {
54 | SvnConfiguration config = mock(SvnConfiguration.class);
55 | assertThat(config.password()).isNull();
56 | assertThat(config.passPhrase()).isNull();
57 | assertThat(newSvnClientManager(config)).isNotNull();
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnConfigurationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SonarQube :: Plugins :: SCM :: SVN
3 | * Copyright (C) 2014-2021 SonarSource SA
4 | * mailto:info AT sonarsource DOT com
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 3 of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public License
17 | * along with this program; if not, write to the Free Software Foundation,
18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 | package org.sonar.plugins.scm.svn;
21 |
22 | import java.io.File;
23 | import org.junit.Rule;
24 | import org.junit.Test;
25 | import org.junit.rules.TemporaryFolder;
26 | import org.sonar.api.config.PropertyDefinitions;
27 | import org.sonar.api.config.internal.MapSettings;
28 |
29 | import static org.assertj.core.api.Assertions.assertThat;
30 | import static org.assertj.core.api.Assertions.fail;
31 |
32 | public class SvnConfigurationTest {
33 |
34 | @Rule
35 | public TemporaryFolder temp = new TemporaryFolder();
36 |
37 | @Test
38 | public void sanityCheck() throws Exception {
39 | MapSettings settings = new MapSettings(new PropertyDefinitions(SvnConfiguration.getProperties()));
40 | SvnConfiguration config = new SvnConfiguration(settings.asConfig());
41 |
42 | assertThat(config.username()).isNull();
43 | assertThat(config.password()).isNull();
44 |
45 | settings.setProperty(SvnConfiguration.USER_PROP_KEY, "foo");
46 | assertThat(config.username()).isEqualTo("foo");
47 |
48 | settings.setProperty(SvnConfiguration.PASSWORD_PROP_KEY, "pwd");
49 | assertThat(config.password()).isEqualTo("pwd");
50 |
51 | settings.setProperty(SvnConfiguration.PASSPHRASE_PROP_KEY, "pass");
52 | assertThat(config.passPhrase()).isEqualTo("pass");
53 |
54 | assertThat(config.privateKey()).isNull();
55 | File fakeKey = temp.newFile();
56 | settings.setProperty(SvnConfiguration.PRIVATE_KEY_PATH_PROP_KEY, fakeKey.getAbsolutePath());
57 | assertThat(config.privateKey()).isEqualTo(fakeKey);
58 |
59 | settings.setProperty(SvnConfiguration.PRIVATE_KEY_PATH_PROP_KEY, "/not/exists");
60 | try {
61 | config.privateKey();
62 | fail("Expected exception");
63 | } catch (Exception e) {
64 | assertThat(e).hasMessageContaining("Unable to read private key from ");
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.sonarsource.parent
6 | parent
7 | 59.0.29
8 |
9 | org.sonarsource.scm.svn
10 | svn
11 | 1.10-SNAPSHOT
12 | pom
13 | SonarQube :: Plugins :: SCM :: SVN
14 | Subversion SCM Provider for SonarQube
15 | http://redirect.sonarsource.com/plugins/scmsvn.html
16 | 2014
17 |
18 |
19 | SonarSource
20 | http://www.sonarsource.com
21 |
22 |
23 |
24 |
25 | GNU LGPL 3
26 | http://www.gnu.org/licenses/lgpl.txt
27 | repo
28 |
29 |
30 |
31 |
32 |
33 | henryju
34 | Julien Henry
35 | +1
36 |
37 |
38 |
39 |
40 | sonar-scm-svn-plugin
41 |
42 |
43 |
44 |
45 | scm:git:git@github.com:SonarSource/sonar-scm-svn.git
46 | scm:git:git@github.com:SonarSource/sonar-scm-svn.git
47 | https://github.com/SonarSource/sonar-scm-svn
48 | HEAD
49 |
50 |
51 |
52 | jira
53 | https://jira.sonarsource.com/browse/SONARSCSVN
54 |
55 |
56 |
57 | Travis
58 | https://travis-ci.org/SonarSource/sonar-scm-svn
59 |
60 |
61 |
62 |
63 | sonar-scm-svn
64 |
65 | ${project.groupId}:sonar-scm-svn-plugin:jar
66 |
67 |
68 |
69 |
70 |
71 |
72 | org.apache.maven.plugins
73 | maven-javadoc-plugin
74 |
75 | 8
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | its
85 |
86 | its
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/its/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | org.sonarsource.scm.svn
8 | svn
9 | 1.10-SNAPSHOT
10 |
11 |
12 | org.sonarsource.scm.svn
13 | it-scmsvn
14 | SVN :: Integration Tests
15 |
16 | 2014
17 |
18 |
19 |
20 | org.sonarsource.orchestrator
21 | sonar-orchestrator
22 | 3.22.0.1791
23 | test
24 |
25 |
26 | junit
27 | junit
28 | 4.13.1
29 | test
30 |
31 |
32 | org.assertj
33 | assertj-core
34 | 3.11.1
35 | test
36 |
37 |
38 | org.tmatesoft.svnkit
39 | svnkit
40 | 1.9.3
41 | test
42 |
43 |
44 |
45 |
46 |
47 | qa
48 |
49 |
50 | env.SONARSOURCE_QA
51 | true
52 |
53 |
54 |
55 |
56 |
57 | org.apache.maven.plugins
58 | maven-dependency-plugin
59 | 2.10
60 |
61 |
62 | copy-plugin
63 | generate-test-resources
64 |
65 | copy
66 |
67 |
68 |
69 |
70 | ${project.groupId}
71 | sonar-scm-svn-plugin
72 | ${project.version}
73 | sonar-plugin
74 | true
75 |
76 |
77 | ../sonar-scm-svn-plugin/target
78 | true
79 | true
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/.cirrus.yml:
--------------------------------------------------------------------------------
1 | # content of service-account-credentials.json, used to access to Google Cloud Platform
2 | gcp_credentials: ENCRYPTED[!e5f7207bd8d02d383733bef47e18296ac32e3b7d22eb480354e8dd8fdc0004be45a8a4e72c797bd66ee94eb3340fa363!]
3 |
4 | #
5 | # ENV VARIABLES
6 | #
7 | env:
8 | ### Shared variables
9 | ARTIFACTORY_URL: ENCRYPTED[!2f8fa307d3289faa0aa6791f18b961627ae44f1ef46b136e1a1e63b0b4c86454dbb25520d49b339e2d50a1e1e5f95c88!]
10 | ARTIFACTORY_PRIVATE_USERNAME: repox-private-reader
11 | ARTIFACTORY_PRIVATE_PASSWORD: ENCRYPTED[!35ca4446564213d4fd2d1a96e42a871d5de6e6aac4e1dd3e89342892a60a2badf74a966bcc8e885e9c9d09a775ffe4c0!]
12 | ARTIFACTORY_API_KEY: ENCRYPTED[!35ca4446564213d4fd2d1a96e42a871d5de6e6aac4e1dd3e89342892a60a2badf74a966bcc8e885e9c9d09a775ffe4c0!]
13 | ARTIFACTORY_DEPLOY_USERNAME: repox-qa-deployer
14 | ARTIFACTORY_DEPLOY_PASSWORD: ENCRYPTED[!d484e19f33c9ce63b165f70e414a33b1ac6c215a126791aacbf8059626caf0fd8a78e999a20af5c1a4ba01c0b0247921!]
15 | ARTIFACTORY_DEPLOY_REPO: sonarsource-public-qa
16 |
17 | GCF_ACCESS_TOKEN: ENCRYPTED[!1fb91961a5c01e06e38834e55755231d649dc62eca354593105af9f9d643d701ae4539ab6a8021278b8d9348ae2ce8be!]
18 | PROMOTE_URL: ENCRYPTED[!e22ed2e34a8f7a1aea5cff653585429bbd3d5151e7201022140218f9c5d620069ec2388f14f83971e3fd726215bc0f5e!]
19 |
20 | GITHUB_TOKEN: ENCRYPTED[!f272985ea5b49b3cf9c414b98de6a8e9096be47bfcee52f33311ba3131a2af637c1b956f49585b7757dd84b7c030233a!]
21 |
22 | BURGR_URL: ENCRYPTED[!c7e294da94762d7bac144abef6310c5db300c95979daed4454ca977776bfd5edeb557e1237e3aa8ed722336243af2d78!]
23 | BURGR_USERNAME: ENCRYPTED[!b29ddc7610116de511e74bec9a93ad9b8a20ac217a0852e94a96d0066e6e822b95e7bc1fe152afb707f16b70605fddd3!]
24 | BURGR_PASSWORD: ENCRYPTED[!83e130718e92b8c9de7c5226355f730e55fb46e45869149a9223e724bb99656878ef9684c5f8cfef434aa716e87f4cf2!]
25 |
26 | ### Project variables
27 | DEPLOY_PULL_REQUEST: true
28 | ARTIFACTS: org.sonarsource.scm.svn:sonar-scm-svn:jar
29 |
30 | #
31 | # RE-USABLE CONFIGS
32 | #
33 | container_definition: &CONTAINER_DEFINITION
34 | image: us.gcr.io/sonarqube-team/base:j11-m3-latest
35 | cluster_name: cirrus-ci-cluster
36 | zone: us-central1-a
37 | namespace: default
38 |
39 | only_sonarsource_qa: &ONLY_SONARSOURCE_QA
40 | only_if: $CIRRUS_USER_COLLABORATOR == 'true' && ($CIRRUS_PR != "" || $CIRRUS_BRANCH == "master" || $CIRRUS_BRANCH =~ "branch-.*" || $CIRRUS_BRANCH =~ "dogfood-on-.*")
41 |
42 | #
43 | # TASKS
44 | #
45 | build_task:
46 | gke_container:
47 | <<: *CONTAINER_DEFINITION
48 | cpu: 2
49 | memory: 2G
50 | env:
51 | SONAR_TOKEN: ENCRYPTED[!b6fd814826c51e64ee61b0b6f3ae621551f6413383f7170f73580e2e141ac78c4b134b506f6288c74faa0dd564c05a29!]
52 | SONAR_HOST_URL: https://next.sonarqube.com/sonarqube
53 | maven_cache:
54 | folder: ${CIRRUS_WORKING_DIR}/.m2/repository
55 | script:
56 | - source cirrus-env BUILD
57 | - regular_mvn_build_deploy_analyze
58 | cleanup_before_cache_script:
59 | - cleanup_maven_repository
60 |
61 | qa_task:
62 | depends_on:
63 | - build
64 | <<: *ONLY_SONARSOURCE_QA
65 | gke_container:
66 | <<: *CONTAINER_DEFINITION
67 | cpu: 1.7
68 | memory: 5Gb
69 | env:
70 | matrix:
71 | - SQ_VERSION: LATEST_RELEASE[7.9]
72 | maven_cache:
73 | folder: ${CIRRUS_WORKING_DIR}/.m2/repository
74 | qa_script:
75 | - source cirrus-env QA
76 | - source set_maven_build_version $BUILD_NUMBER
77 | - cd its
78 | - mvn -Dsonar.runtimeVersion="$SQ_VERSION" -Dmaven.test.redirectTestOutputToFile=false verify -B -e -V
79 | cleanup_before_cache_script:
80 | - cleanup_maven_repository
81 |
82 | promote_task:
83 | depends_on:
84 | - qa
85 | <<: *ONLY_SONARSOURCE_QA
86 | gke_container:
87 | <<: *CONTAINER_DEFINITION
88 | cpu: 0.5
89 | memory: 500M
90 | maven_cache:
91 | folder: $CIRRUS_WORKING_DIR/.m2/repository
92 | script:
93 | - cirrus_promote_maven
94 | cleanup_before_cache_script:
95 | - cleanup_maven_repository
96 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.sonarsource.scm.svn
7 | svn
8 | 1.10-SNAPSHOT
9 |
10 | sonar-scm-svn-plugin
11 | sonar-plugin
12 | SonarQube :: Plugins :: SCM :: SVN
13 | Subversion SCM Provider for SonarQube
14 | http://redirect.sonarsource.com/plugins/scmsvn.html
15 |
16 |
17 | https://github.com/SonarSource/sonar-scm-svn
18 |
19 |
20 |
21 |
22 | 7.9
23 | SVN
24 | org.sonar.plugins.scm.svn.SvnPlugin
25 |
26 |
27 |
28 |
29 | com.google.code.findbugs
30 | jsr305
31 | 3.0.2
32 | provided
33 |
34 |
35 | org.sonarsource.sonarqube
36 | sonar-plugin-api
37 | ${sonar.buildVersion}
38 | provided
39 |
40 |
41 | org.tmatesoft.svnkit
42 | svnkit
43 | 1.10.1
44 |
45 |
46 |
47 |
48 | junit
49 | junit
50 | 4.13.1
51 | test
52 |
53 |
54 | org.assertj
55 | assertj-core
56 | 3.13.2
57 | test
58 |
59 |
60 | org.mockito
61 | mockito-core
62 | 3.0.0
63 | test
64 |
65 |
66 |
67 |
68 |
69 |
70 | org.sonarsource.sonar-packaging-maven-plugin
71 | sonar-packaging-maven-plugin
72 | 1.18.0.372
73 |
74 | Svn
75 | true
76 | org.sonar.plugins.scm.svn.SvnPlugin
77 | 5.6
78 |
79 |
80 |
81 | maven-shade-plugin
82 |
83 |
84 | package
85 |
86 | shade
87 |
88 |
89 | false
90 | false
91 | false
92 |
93 |
94 | *:*
95 |
96 | META-INF/LICENSE*
97 | META-INF/NOTICE*
98 | META-INF/*.RSA
99 | META-INF/*.SF
100 | LICENSE*
101 | NOTICE*
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnConfiguration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SonarQube :: Plugins :: SCM :: SVN
3 | * Copyright (C) 2014-2021 SonarSource SA
4 | * mailto:info AT sonarsource DOT com
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 3 of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public License
17 | * along with this program; if not, write to the Free Software Foundation,
18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 | package org.sonar.plugins.scm.svn;
21 |
22 | import java.io.File;
23 | import java.util.Arrays;
24 | import java.util.List;
25 | import java.util.Optional;
26 | import javax.annotation.CheckForNull;
27 | import org.sonar.api.CoreProperties;
28 | import org.sonar.api.PropertyType;
29 | import org.sonar.api.config.Configuration;
30 | import org.sonar.api.config.PropertyDefinition;
31 | import org.sonar.api.resources.Qualifiers;
32 | import org.sonar.api.scanner.ScannerSide;
33 | import org.sonar.api.utils.MessageException;
34 |
35 | @ScannerSide
36 | public class SvnConfiguration {
37 |
38 | private static final String CATEGORY_SVN = "SVN";
39 | public static final String USER_PROP_KEY = "sonar.svn.username";
40 | public static final String PRIVATE_KEY_PATH_PROP_KEY = "sonar.svn.privateKeyPath";
41 | public static final String PASSWORD_PROP_KEY = "sonar.svn.password.secured";
42 | public static final String PASSPHRASE_PROP_KEY = "sonar.svn.passphrase.secured";
43 | private final Configuration config;
44 |
45 | public SvnConfiguration(Configuration config) {
46 | this.config = config;
47 | }
48 |
49 | public static List getProperties() {
50 | return Arrays.asList(
51 | PropertyDefinition.builder(USER_PROP_KEY)
52 | .name("Username")
53 | .description("Username to be used for SVN server or SVN+SSH authentication")
54 | .type(PropertyType.STRING)
55 | .onQualifiers(Qualifiers.PROJECT)
56 | .category(CoreProperties.CATEGORY_SCM)
57 | .subCategory(CATEGORY_SVN)
58 | .index(0)
59 | .build(),
60 | PropertyDefinition.builder(PASSWORD_PROP_KEY)
61 | .name("Password")
62 | .description("Password to be used for SVN server or SVN+SSH authentication")
63 | .type(PropertyType.PASSWORD)
64 | .onQualifiers(Qualifiers.PROJECT)
65 | .category(CoreProperties.CATEGORY_SCM)
66 | .subCategory(CATEGORY_SVN)
67 | .index(1)
68 | .build(),
69 | PropertyDefinition.builder(PRIVATE_KEY_PATH_PROP_KEY)
70 | .name("Path to private key file")
71 | .description("Can be used instead of password for SVN+SSH authentication")
72 | .type(PropertyType.STRING)
73 | .onQualifiers(Qualifiers.PROJECT)
74 | .category(CoreProperties.CATEGORY_SCM)
75 | .subCategory(CATEGORY_SVN)
76 | .index(2)
77 | .build(),
78 | PropertyDefinition.builder(PASSPHRASE_PROP_KEY)
79 | .name("Passphrase")
80 | .description("Optional passphrase of your private key file")
81 | .type(PropertyType.PASSWORD)
82 | .onQualifiers(Qualifiers.PROJECT)
83 | .category(CoreProperties.CATEGORY_SCM)
84 | .subCategory(CATEGORY_SVN)
85 | .index(3)
86 | .build());
87 | }
88 |
89 | @CheckForNull
90 | public String username() {
91 | return config.get(USER_PROP_KEY).orElse(null);
92 | }
93 |
94 | @CheckForNull
95 | public String password() {
96 | return config.get(PASSWORD_PROP_KEY).orElse(null);
97 | }
98 |
99 | @CheckForNull
100 | public File privateKey() {
101 | Optional privateKeyOpt = config.get(PRIVATE_KEY_PATH_PROP_KEY);
102 | if (privateKeyOpt.isPresent()) {
103 | File privateKeyFile = new File(privateKeyOpt.get());
104 | if (!privateKeyFile.exists() || !privateKeyFile.isFile() || !privateKeyFile.canRead()) {
105 | throw MessageException.of("Unable to read private key from '" + privateKeyFile + "'");
106 | }
107 | return privateKeyFile;
108 | }
109 | return null;
110 | }
111 |
112 | @CheckForNull
113 | public String passPhrase() {
114 | return config.get(PASSPHRASE_PROP_KEY).orElse(null);
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/FindFork.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SonarQube :: Plugins :: SCM :: SVN
3 | * Copyright (C) 2014-2021 SonarSource SA
4 | * mailto:info AT sonarsource DOT com
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 3 of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public License
17 | * along with this program; if not, write to the Free Software Foundation,
18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 | package org.sonar.plugins.scm.svn;
21 |
22 | import java.io.File;
23 | import java.nio.file.Path;
24 | import java.time.Instant;
25 | import java.util.Optional;
26 | import javax.annotation.CheckForNull;
27 | import org.sonar.api.scanner.ScannerSide;
28 | import org.sonar.api.utils.log.Logger;
29 | import org.sonar.api.utils.log.Loggers;
30 | import org.tmatesoft.svn.core.ISVNLogEntryHandler;
31 | import org.tmatesoft.svn.core.SVNException;
32 | import org.tmatesoft.svn.core.SVNLogEntry;
33 | import org.tmatesoft.svn.core.SVNLogEntryPath;
34 | import org.tmatesoft.svn.core.wc.SVNClientManager;
35 | import org.tmatesoft.svn.core.wc.SVNRevision;
36 | import org.tmatesoft.svn.core.wc.SVNStatus;
37 |
38 | import static org.sonar.plugins.scm.svn.SvnPlugin.newSvnClientManager;
39 |
40 | @ScannerSide
41 | public class FindFork {
42 | private static final Logger LOG = Loggers.get(FindFork.class);
43 |
44 | private final SvnConfiguration configuration;
45 |
46 | public FindFork(SvnConfiguration configuration) {
47 | this.configuration = configuration;
48 | }
49 |
50 | @CheckForNull
51 | public Instant findDate(Path location, String referenceBranch) throws SVNException {
52 | ForkPoint forkPoint = find(location, referenceBranch);
53 | if (forkPoint != null) {
54 | return forkPoint.date();
55 | }
56 | return null;
57 | }
58 |
59 | @CheckForNull
60 | public ForkPoint find(Path location, String referenceBranch) throws SVNException {
61 | SVNClientManager clientManager = newSvnClientManager(configuration);
62 | SVNRevision revision = getSvnRevision(location, clientManager);
63 | LOG.debug("latest revision is " + revision);
64 | String svnRefBranch = "/" + referenceBranch;
65 |
66 | SVNLogEntryHolder handler = new SVNLogEntryHolder();
67 | SVNRevision endRevision = SVNRevision.create(1);
68 | SVNRevision startRevision = SVNRevision.create(revision.getNumber());
69 |
70 | do {
71 | clientManager.getLogClient().doLog(new File[] {location.toFile()}, startRevision, endRevision, true, true, -1, handler);
72 | SVNLogEntry lastEntry = handler.getLastEntry();
73 | Optional copyFromReference = lastEntry.getChangedPaths().values().stream()
74 | .filter(e -> e.getCopyPath() != null && e.getCopyPath().equals(svnRefBranch))
75 | .findFirst();
76 |
77 | if (copyFromReference.isPresent()) {
78 | return new ForkPoint(String.valueOf(copyFromReference.get().getCopyRevision()), Instant.ofEpochMilli(lastEntry.getDate().getTime()));
79 | }
80 |
81 | if (lastEntry.getChangedPaths().isEmpty()) {
82 | // shouldn't happen since it should only stop in revisions with changed paths
83 | return null;
84 | }
85 |
86 | SVNLogEntryPath firstChangedPath = lastEntry.getChangedPaths().values().iterator().next();
87 | if (firstChangedPath.getCopyPath() == null) {
88 | // we walked the history to the root, and the last commit found had no copy reference. Must be the trunk, there is no fork point
89 | return null;
90 | }
91 |
92 | // TODO Looks like a revision can have multiple changed paths. Should we iterate through all of them?
93 | startRevision = SVNRevision.create(firstChangedPath.getCopyRevision());
94 | } while (true);
95 |
96 | }
97 |
98 | private SVNRevision getSvnRevision(Path location, SVNClientManager clientManager) throws SVNException {
99 | SVNStatus svnStatus = clientManager.getStatusClient().doStatus(location.toFile(), false);
100 | return svnStatus.getRevision();
101 | }
102 |
103 | /**
104 | * Handler keeping only the last entry, and count how many entries have been seen.
105 | */
106 | private static class SVNLogEntryHolder implements ISVNLogEntryHandler {
107 | SVNLogEntry value;
108 |
109 | public SVNLogEntry getLastEntry() {
110 | return value;
111 | }
112 |
113 | @Override
114 | public void handleLogEntry(SVNLogEntry svnLogEntry) {
115 | this.value = svnLogEntry;
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnBlameCommand.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SonarQube :: Plugins :: SCM :: SVN
3 | * Copyright (C) 2014-2021 SonarSource SA
4 | * mailto:info AT sonarsource DOT com
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 3 of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public License
17 | * along with this program; if not, write to the Free Software Foundation,
18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 | package org.sonar.plugins.scm.svn;
21 |
22 | import java.util.List;
23 | import org.sonar.api.batch.fs.FileSystem;
24 | import org.sonar.api.batch.fs.InputFile;
25 | import org.sonar.api.batch.scm.BlameCommand;
26 | import org.sonar.api.batch.scm.BlameLine;
27 | import org.sonar.api.utils.log.Logger;
28 | import org.sonar.api.utils.log.Loggers;
29 | import org.tmatesoft.svn.core.SVNErrorCode;
30 | import org.tmatesoft.svn.core.SVNException;
31 | import org.tmatesoft.svn.core.wc.SVNClientManager;
32 | import org.tmatesoft.svn.core.wc.SVNDiffOptions;
33 | import org.tmatesoft.svn.core.wc.SVNLogClient;
34 | import org.tmatesoft.svn.core.wc.SVNRevision;
35 | import org.tmatesoft.svn.core.wc.SVNStatus;
36 | import org.tmatesoft.svn.core.wc.SVNStatusClient;
37 | import org.tmatesoft.svn.core.wc.SVNStatusType;
38 |
39 | import static org.sonar.plugins.scm.svn.SvnPlugin.newSvnClientManager;
40 |
41 | public class SvnBlameCommand extends BlameCommand {
42 |
43 | private static final Logger LOG = Loggers.get(SvnBlameCommand.class);
44 | private final SvnConfiguration configuration;
45 |
46 | public SvnBlameCommand(SvnConfiguration configuration) {
47 | this.configuration = configuration;
48 | }
49 |
50 | @Override
51 | public void blame(final BlameInput input, final BlameOutput output) {
52 | FileSystem fs = input.fileSystem();
53 | LOG.debug("Working directory: " + fs.baseDir().getAbsolutePath());
54 | SVNClientManager clientManager = null;
55 | try {
56 | clientManager = newSvnClientManager(configuration);
57 | for (InputFile inputFile : input.filesToBlame()) {
58 | blame(clientManager, inputFile, output);
59 | }
60 | } finally {
61 | if (clientManager != null) {
62 | try {
63 | clientManager.dispose();
64 | } catch (Exception e) {
65 | LOG.warn("Unable to dispose SVN ClientManager", e);
66 | }
67 | }
68 | }
69 | }
70 |
71 | private static void blame(SVNClientManager clientManager, InputFile inputFile, BlameOutput output) {
72 | String filename = inputFile.relativePath();
73 |
74 | LOG.debug("Process file {}", filename);
75 |
76 | AnnotationHandler handler = new AnnotationHandler();
77 | try {
78 | if (!checkStatus(clientManager, inputFile)) {
79 | return;
80 | }
81 | SVNLogClient logClient = clientManager.getLogClient();
82 | logClient.setDiffOptions(new SVNDiffOptions(true, true, true));
83 | logClient.doAnnotate(inputFile.file(), SVNRevision.UNDEFINED, SVNRevision.create(1), SVNRevision.BASE, true, true, handler, null);
84 | } catch (SVNException e) {
85 | throw new IllegalStateException("Error when executing blame for file " + filename, e);
86 | }
87 |
88 | List lines = handler.getLines();
89 | if (lines.size() == inputFile.lines() - 1) {
90 | // SONARPLUGINS-3097 SVN do not report blame on last empty line
91 | lines.add(lines.get(lines.size() - 1));
92 | }
93 | output.blameResult(inputFile, lines);
94 | }
95 |
96 | private static boolean checkStatus(SVNClientManager clientManager, InputFile inputFile) throws SVNException {
97 | SVNStatusClient statusClient = clientManager.getStatusClient();
98 | try {
99 | SVNStatus status = statusClient.doStatus(inputFile.file(), false);
100 | if (status == null) {
101 | LOG.debug("File {} returns no svn state. Skipping it.", inputFile);
102 | return false;
103 | }
104 | if (status.getContentsStatus() != SVNStatusType.STATUS_NORMAL) {
105 | LOG.debug("File {} is not versionned or contains local modifications. Skipping it.", inputFile);
106 | return false;
107 | }
108 | } catch (SVNException e) {
109 | if (SVNErrorCode.WC_PATH_NOT_FOUND.equals(e.getErrorMessage().getErrorCode())
110 | || SVNErrorCode.WC_NOT_WORKING_COPY.equals(e.getErrorMessage().getErrorCode())) {
111 | LOG.debug("File {} is not versionned. Skipping it.", inputFile);
112 | return false;
113 | }
114 | throw e;
115 | }
116 | return true;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/FindForkTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SonarQube :: Plugins :: SCM :: SVN
3 | * Copyright (C) 2014-2021 SonarSource SA
4 | * mailto:info AT sonarsource DOT com
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 3 of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public License
17 | * along with this program; if not, write to the Free Software Foundation,
18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 | package org.sonar.plugins.scm.svn;
21 |
22 | import java.io.IOException;
23 | import java.nio.file.Path;
24 | import java.nio.file.Paths;
25 | import org.assertj.core.api.Assertions;
26 | import org.junit.Before;
27 | import org.junit.BeforeClass;
28 | import org.junit.ClassRule;
29 | import org.junit.Test;
30 | import org.junit.rules.TemporaryFolder;
31 | import org.mockito.Mockito;
32 | import org.mockito.internal.matchers.Find;
33 | import org.tmatesoft.svn.core.SVNException;
34 |
35 | import static org.assertj.core.api.Assertions.assertThat;
36 | import static org.mockito.Mockito.mock;
37 |
38 | public class FindForkTest {
39 |
40 | @ClassRule
41 | public static TemporaryFolder temp = new TemporaryFolder();
42 |
43 | private static SvnTester svnTester;
44 |
45 | private static Path trunk;
46 | private static Path b1;
47 | private static Path b2;
48 |
49 | private FindFork findFork;
50 |
51 | @BeforeClass
52 | public static void before() throws IOException, SVNException {
53 | svnTester = new SvnTester(temp.newFolder().toPath());
54 |
55 | trunk = temp.newFolder("trunk").toPath();
56 | svnTester.checkout(trunk, "trunk");
57 | createAndCommitFile(trunk, "file-1-commit-in-trunk.xoo");
58 | createAndCommitFile(trunk, "file-2-commit-in-trunk.xoo");
59 | createAndCommitFile(trunk, "file-3-commit-in-trunk.xoo");
60 | svnTester.checkout(trunk, "trunk");
61 |
62 | svnTester.createBranch("b1");
63 | b1 = temp.newFolder("branches", "b1").toPath();
64 | svnTester.checkout(b1, "branches/b1");
65 | createAndCommitFile(b1, "file-1-commit-in-b1.xoo");
66 | createAndCommitFile(b1, "file-2-commit-in-b1.xoo");
67 | createAndCommitFile(b1, "file-3-commit-in-b1.xoo");
68 | svnTester.checkout(b1, "branches/b1");
69 |
70 | svnTester.createBranch("branches/b1", "b2");
71 | b2 = temp.newFolder("branches", "b2").toPath();
72 | svnTester.checkout(b2, "branches/b2");
73 |
74 | createAndCommitFile(b2, "file-1-commit-in-b2.xoo");
75 | createAndCommitFile(b2, "file-2-commit-in-b2.xoo");
76 | createAndCommitFile(b2, "file-3-commit-in-b2.xoo");
77 | svnTester.checkout(b2, "branches/b2");
78 | }
79 |
80 | @Before
81 | public void setUp() {
82 | SvnConfiguration configurationMock = mock(SvnConfiguration.class);
83 | findFork = new FindFork(configurationMock);
84 | }
85 |
86 | @Test
87 | public void testEmptyBranch() throws SVNException, IOException {
88 | svnTester.createBranch("empty");
89 | Path empty = temp.newFolder("branches", "empty").toPath();
90 |
91 | svnTester.checkout(empty, "branches/empty");
92 | ForkPoint forkPoint = findFork.find(empty, "unknown");
93 | assertThat(forkPoint).isNull();
94 | }
95 |
96 | @Test
97 | public void returnNoDate() throws SVNException {
98 | FindFork findFork = new FindFork(mock(SvnConfiguration.class)) {
99 | @Override
100 | public ForkPoint find(Path location, String referenceBranch) {
101 | return null;
102 | }
103 | };
104 |
105 | assertThat(findFork.findDate(Paths.get(""), "branch")).isNull();
106 | }
107 |
108 | @Test
109 | public void testTrunk() throws SVNException {
110 | ForkPoint forkPoint = findFork.find(trunk, "unknown");
111 | assertThat(forkPoint).isNull();
112 | }
113 |
114 | @Test
115 | public void testB1() throws SVNException {
116 | ForkPoint forkPoint = findFork.find(b1, "trunk");
117 | assertThat(forkPoint.commit()).isEqualTo("5");
118 | }
119 |
120 | @Test
121 | public void testB2() throws SVNException {
122 | ForkPoint forkPoint = findFork.find(b2, "branches/b1");
123 | assertThat(forkPoint.commit()).isEqualTo("9");
124 | }
125 |
126 | @Test
127 | public void testB2Date() throws SVNException {
128 | assertThat(findFork.findDate(b2, "branches/b1")).isNotNull();
129 | }
130 |
131 | @Test
132 | public void testB2FromTrunk() throws SVNException {
133 | ForkPoint forkPoint = findFork.find(b2, "trunk");
134 | assertThat(forkPoint.commit()).isEqualTo("5");
135 | }
136 |
137 | private static void createAndCommitFile(Path worktree, String filename, String content) throws IOException, SVNException {
138 | svnTester.createFile(worktree, filename, content);
139 | svnTester.add(worktree, filename);
140 | svnTester.commit(worktree);
141 | }
142 |
143 | private static void createAndCommitFile(Path worktree, String filename) throws IOException, SVNException {
144 | createAndCommitFile(worktree, filename, filename + "\n");
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/ChangedLinesComputer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SonarQube :: Plugins :: SCM :: SVN
3 | * Copyright (C) 2014-2021 SonarSource SA
4 | * mailto:info AT sonarsource DOT com
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 3 of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public License
17 | * along with this program; if not, write to the Free Software Foundation,
18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 | package org.sonar.plugins.scm.svn;
21 |
22 | import java.io.OutputStream;
23 | import java.nio.file.Path;
24 | import java.nio.file.Paths;
25 | import java.util.HashMap;
26 | import java.util.HashSet;
27 | import java.util.Map;
28 | import java.util.Set;
29 | import java.util.regex.Matcher;
30 | import java.util.regex.Pattern;
31 |
32 | class ChangedLinesComputer {
33 |
34 | private final Tracker tracker;
35 |
36 | private final OutputStream receiver = new OutputStream() {
37 | StringBuilder sb = new StringBuilder();
38 |
39 | @Override
40 | public void write(int b) {
41 | sb.append((char) b);
42 | if (b == '\n') {
43 | tracker.parseLine(sb.toString());
44 | sb.setLength(0);
45 | }
46 | }
47 | };
48 |
49 | ChangedLinesComputer(Path rootBaseDir, Set included) {
50 | this.tracker = new Tracker(rootBaseDir, included);
51 | }
52 |
53 | /**
54 | * The OutputStream to pass to svnkit's diff command.
55 | */
56 | OutputStream receiver() {
57 | return receiver;
58 | }
59 |
60 | /**
61 | * From a stream of svn-style unified diff lines,
62 | * compute the line numbers that should be considered changed.
63 | *
64 | * Example input:
65 | *
66 | * Index: path/to/file
67 | * ===================================================================
68 | * --- lao 2002-02-21 23:30:39.942229878 -0800
69 | * +++ tzu 2002-02-21 23:30:50.442260588 -0800
70 | * @@ -1,7 +1,6 @@
71 | * -The Way that can be told of is not the eternal Way;
72 | * -The name that can be named is not the eternal name.
73 | * The Nameless is the origin of Heaven and Earth;
74 | * -The Named is the mother of all things.
75 | * +The named is the mother of all things.
76 | * +
77 | * Therefore let there always be non-being,
78 | * so we may see their subtlety,
79 | * And let there always be being,
80 | * @@ -9,3 +8,6 @@
81 | * The two are the same,
82 | * But after they are produced,
83 | * they have different names.
84 | * +They both may be called deep and profound.
85 | * +Deeper and more profound,
86 | * +The door of all subtleties!
87 | *
88 | *
89 | * See also: http://www.gnu.org/software/diffutils/manual/html_node/Example-Unified.html#Example-Unified
90 | */
91 | Map> changedLines() {
92 | return tracker.changedLines();
93 | }
94 |
95 | private static class Tracker {
96 |
97 | private static final Pattern START_LINE_IN_TARGET = Pattern.compile(" \\+(\\d+)");
98 | private static final String ENTRY_START_PREFIX = "Index: ";
99 |
100 | private final Map> changedLines = new HashMap<>();
101 | private final Set included;
102 | private final Path rootBaseDir;
103 |
104 | private int lineNumInTarget;
105 | private Path currentPath = null;
106 | private int skipCount = 0;
107 |
108 | Tracker(Path rootBaseDir, Set included) {
109 | this.rootBaseDir = rootBaseDir;
110 | this.included = included;
111 | }
112 |
113 | private void parseLine(String line) {
114 | if (line.startsWith(ENTRY_START_PREFIX)) {
115 | currentPath = Paths.get(line.substring(ENTRY_START_PREFIX.length()).trim());
116 | if (!currentPath.isAbsolute()) {
117 | currentPath = rootBaseDir.resolve(currentPath);
118 | }
119 | if (!included.contains(currentPath)) {
120 | return;
121 | }
122 | skipCount = 3;
123 | return;
124 | }
125 |
126 | if (!included.contains(currentPath)) {
127 | return;
128 | }
129 |
130 | if (skipCount > 0) {
131 | skipCount--;
132 | return;
133 | }
134 |
135 | if (line.startsWith("@@ ")) {
136 | Matcher matcher = START_LINE_IN_TARGET.matcher(line);
137 | if (!matcher.find()) {
138 | throw new IllegalStateException("Invalid block header: " + line);
139 | }
140 | lineNumInTarget = Integer.parseInt(matcher.group(1));
141 | return;
142 | }
143 |
144 | parseContent(line);
145 | }
146 |
147 | private void parseContent(String line) {
148 | char firstChar = line.charAt(0);
149 | if (firstChar == ' ') {
150 | lineNumInTarget++;
151 | } else if (firstChar == '+') {
152 | changedLines
153 | .computeIfAbsent(currentPath, path -> new HashSet<>())
154 | .add(lineNumInTarget);
155 | lineNumInTarget++;
156 | }
157 | }
158 |
159 | Map> changedLines() {
160 | return changedLines;
161 | }
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnTester.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SonarQube :: Plugins :: SCM :: SVN
3 | * Copyright (C) 2014-2021 SonarSource SA
4 | * mailto:info AT sonarsource DOT com
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 3 of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public License
17 | * along with this program; if not, write to the Free Software Foundation,
18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 | package org.sonar.plugins.scm.svn;
21 |
22 | import java.io.File;
23 | import java.io.IOException;
24 | import java.nio.file.Files;
25 | import java.nio.file.Path;
26 | import java.nio.file.StandardOpenOption;
27 | import java.util.Collection;
28 | import java.util.HashSet;
29 | import java.util.Set;
30 | import org.tmatesoft.svn.core.SVNDepth;
31 | import org.tmatesoft.svn.core.SVNException;
32 | import org.tmatesoft.svn.core.SVNURL;
33 | import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
34 | import org.tmatesoft.svn.core.wc.SVNClientManager;
35 | import org.tmatesoft.svn.core.wc.SVNCopyClient;
36 | import org.tmatesoft.svn.core.wc.SVNCopySource;
37 | import org.tmatesoft.svn.core.wc.SVNRevision;
38 | import org.tmatesoft.svn.core.wc.SVNUpdateClient;
39 | import org.tmatesoft.svn.core.wc2.SvnList;
40 | import org.tmatesoft.svn.core.wc2.SvnOperationFactory;
41 | import org.tmatesoft.svn.core.wc2.SvnRemoteMkDir;
42 | import org.tmatesoft.svn.core.wc2.SvnTarget;
43 |
44 | public class SvnTester {
45 | private final SVNClientManager manager = SVNClientManager.newInstance(new SvnOperationFactory());
46 |
47 | private final SVNURL localRepository;
48 |
49 | public SvnTester(Path root) throws SVNException, IOException {
50 | localRepository = SVNRepositoryFactory.createLocalRepository(root.toFile(), false, false);
51 | mkdir("trunk");
52 | mkdir("branches");
53 | }
54 |
55 | private void mkdir(String relpath) throws IOException, SVNException {
56 | SvnRemoteMkDir remoteMkDir = manager.getOperationFactory().createRemoteMkDir();
57 | remoteMkDir.addTarget(SvnTarget.fromURL(localRepository.appendPath(relpath, false)));
58 | remoteMkDir.run();
59 | }
60 |
61 | public void createBranch(String branchName) throws IOException, SVNException {
62 | SVNCopyClient copyClient = manager.getCopyClient();
63 | SVNCopySource source = new SVNCopySource(SVNRevision.HEAD, SVNRevision.HEAD, localRepository.appendPath("trunk", false));
64 | copyClient.doCopy(new SVNCopySource[] {source}, localRepository.appendPath("branches/" + branchName, false), false, false, true, "Create branch", null);
65 | }
66 |
67 | public void createBranch(String branchSource, String branchName) throws IOException, SVNException {
68 | SVNCopyClient copyClient = manager.getCopyClient();
69 | SVNCopySource source = new SVNCopySource(SVNRevision.HEAD, SVNRevision.HEAD, localRepository.appendPath(branchSource, false));
70 | copyClient.doCopy(new SVNCopySource[] {source}, localRepository.appendPath("branches/" + branchName, false), false, false, true, "Create branch", null);
71 | }
72 |
73 | public void checkout(Path worktree, String path) throws SVNException {
74 | SVNUpdateClient updateClient = manager.getUpdateClient();
75 | updateClient.doCheckout(localRepository.appendPath(path, false),
76 | worktree.toFile(), null, null, SVNDepth.INFINITY, false);
77 | }
78 |
79 | public void add(Path worktree, String filename) throws SVNException {
80 | manager.getWCClient().doAdd(worktree.resolve(filename).toFile(), true, false, false, SVNDepth.INFINITY, false, false, true);
81 | }
82 |
83 | public void copy(Path worktree, String src, String dst) throws SVNException {
84 | SVNCopyClient copyClient = manager.getCopyClient();
85 | SVNCopySource source = new SVNCopySource(SVNRevision.HEAD, SVNRevision.HEAD, worktree.resolve(src).toFile());
86 | copyClient.doCopy(new SVNCopySource[]{source}, worktree.resolve(dst).toFile(), false, false, true);
87 | }
88 |
89 | public void commit(Path worktree) throws SVNException {
90 | manager.getCommitClient().doCommit(new File[] {worktree.toFile()}, false, "commit " + worktree, null, null, false, false, SVNDepth.INFINITY);
91 | }
92 |
93 | public void update(Path worktree) throws SVNException {
94 | manager.getUpdateClient().doUpdate(new File[] {worktree.toFile()}, SVNRevision.HEAD, SVNDepth.INFINITY, false, false);
95 | }
96 |
97 | public Collection list(String... paths) throws SVNException {
98 | Set results = new HashSet<>();
99 |
100 | SvnList list = manager.getOperationFactory().createList();
101 | if (paths.length == 0) {
102 | list.addTarget(SvnTarget.fromURL(localRepository));
103 | } else {
104 | for (String path : paths) {
105 | list.addTarget(SvnTarget.fromURL(localRepository.appendPath(path, false)));
106 | }
107 | }
108 | list.setDepth(SVNDepth.INFINITY);
109 | list.setReceiver((svnTarget, svnDirEntry) -> {
110 | String path = svnDirEntry.getRelativePath();
111 | if (!path.isEmpty()) {
112 | results.add(path);
113 | }
114 | });
115 | list.run();
116 |
117 | return results;
118 | }
119 |
120 | public void createFile(Path worktree, String filename, String content) throws IOException {
121 | Files.write(worktree.resolve(filename), content.getBytes());
122 | }
123 |
124 | public void createFile(Path worktree, String filename) throws IOException {
125 | createFile(worktree, filename, filename + "\n");
126 | }
127 |
128 | public void appendToFile(Path worktree, String filename) throws IOException {
129 | Files.write(worktree.resolve(filename), (filename + "\n").getBytes(), StandardOpenOption.APPEND);
130 | }
131 |
132 | public void deleteFile(Path worktree, String filename) throws SVNException {
133 | manager.getWCClient().doDelete(worktree.resolve(filename).toFile(), false, false);
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/ChangedLinesComputerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SonarQube :: Plugins :: SCM :: SVN
3 | * Copyright (C) 2014-2021 SonarSource SA
4 | * mailto:info AT sonarsource DOT com
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 3 of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public License
17 | * along with this program; if not, write to the Free Software Foundation,
18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 | package org.sonar.plugins.scm.svn;
21 |
22 | import java.io.IOException;
23 | import java.io.OutputStreamWriter;
24 | import java.nio.file.Path;
25 | import java.nio.file.Paths;
26 | import org.junit.Test;
27 | import org.sonar.api.internal.google.common.collect.ImmutableMap;
28 | import org.sonar.api.internal.google.common.collect.ImmutableSet;
29 |
30 | import static java.util.Collections.singleton;
31 | import static org.assertj.core.api.Assertions.assertThat;
32 |
33 | public class ChangedLinesComputerTest {
34 |
35 | private final Path rootBaseDir = Paths.get("/foo");
36 | private final ChangedLinesComputer underTest = new ChangedLinesComputer(rootBaseDir, ImmutableSet.of(
37 | rootBaseDir.resolve("sample1"),
38 | rootBaseDir.resolve("sample2"),
39 | rootBaseDir.resolve("sample3"),
40 | rootBaseDir.resolve("sample4")));
41 |
42 | @Test
43 | public void do_not_count_deleted_line() throws IOException {
44 | String example = "Index: sample1\n"
45 | + "===================================================================\n"
46 | + "--- a/sample1\n"
47 | + "+++ b/sample1\n"
48 | + "@@ -1 +0,0 @@\n"
49 | + "-deleted line\n";
50 |
51 | printDiff(example);
52 | assertThat(underTest.changedLines()).isEmpty();
53 | }
54 |
55 | @Test
56 | public void count_single_added_line() throws IOException {
57 | String example = "Index: sample1\n"
58 | + "===================================================================\n"
59 | + "--- a/sample1\n"
60 | + "+++ b/sample1\n"
61 | + "@@ -0,0 +1 @@\n"
62 | + "+added line\n";
63 |
64 | printDiff(example);
65 | assertThat(underTest.changedLines()).isEqualTo(ImmutableMap.of(rootBaseDir.resolve("sample1"), singleton(1)));
66 | }
67 |
68 | @Test
69 | public void count_multiple_added_lines() throws IOException {
70 | String example = "Index: sample1\n"
71 | + "===================================================================\n"
72 | + "--- a/sample1\n"
73 | + "+++ b/sample1\n"
74 | + "@@ -1 +1,3 @@\n"
75 | + " same line\n"
76 | + "+added line 1\n"
77 | + "+added line 2\n";
78 |
79 | printDiff(example);
80 | assertThat(underTest.changedLines()).isEqualTo(ImmutableMap.of(rootBaseDir.resolve("sample1"), ImmutableSet.of(2, 3)));
81 | }
82 |
83 | @Test
84 | public void handle_index_using_absolute_paths() throws IOException {
85 | String example = "Index: /foo/sample1\n"
86 | + "===================================================================\n"
87 | + "--- a/sample1\n"
88 | + "+++ b/sample1\n"
89 | + "@@ -1 +1,3 @@\n"
90 | + " same line\n"
91 | + "+added line 1\n"
92 | + "+added line 2\n";
93 |
94 | printDiff(example);
95 | assertThat(underTest.changedLines()).isEqualTo(ImmutableMap.of(rootBaseDir.resolve("sample1"), ImmutableSet.of(2, 3)));
96 | }
97 |
98 | @Test
99 | public void compute_from_multiple_hunks() throws IOException {
100 | String example = "Index: sample1\n"
101 | + "===================================================================\n"
102 | + "--- lao\t2002-02-21 23:30:39.942229878 -0800\n"
103 | + "+++ tzu\t2002-02-21 23:30:50.442260588 -0800\n"
104 | + "@@ -1,7 +1,6 @@\n"
105 | + "-The Way that can be told of is not the eternal Way;\n"
106 | + "-The name that can be named is not the eternal name.\n"
107 | + " The Nameless is the origin of Heaven and Earth;\n"
108 | + "-The Named is the mother of all things.\n"
109 | + "+The named is the mother of all things.\n"
110 | + "+\n"
111 | + " Therefore let there always be non-being,\n"
112 | + " so we may see their subtlety,\n"
113 | + " And let there always be being,\n"
114 | + "@@ -9,3 +8,6 @@\n"
115 | + " The two are the same,\n"
116 | + " But after they are produced,\n"
117 | + " they have different names.\n"
118 | + "+They both may be called deep and profound.\n"
119 | + "+Deeper and more profound,\n"
120 | + "+The door of all subtleties!\n";
121 | printDiff(example);
122 | assertThat(underTest.changedLines()).isEqualTo(ImmutableMap.of(rootBaseDir.resolve("sample1"), ImmutableSet.of(2, 3, 11, 12, 13)));
123 | }
124 |
125 | @Test(expected = IllegalStateException.class)
126 | public void crash_on_invalid_start_line_format() throws IOException {
127 | String example = "Index: sample1\n"
128 | + "===================================================================\n"
129 | + "--- a/sample1\n"
130 | + "+++ b/sample1\n"
131 | + "@@ -1 +x1,3 @@\n"
132 | + " same line\n"
133 | + "+added line 1\n"
134 | + "+added line 2\n";
135 |
136 | printDiff(example);
137 | underTest.changedLines();
138 | }
139 |
140 | @Test
141 | public void parse_diff_with_multiple_files() throws IOException {
142 | String example = "Index: sample1\n"
143 | + "===================================================================\n"
144 | + "--- a/sample1\n"
145 | + "+++ b/sample1\n"
146 | + "@@ -1 +0,0 @@\n"
147 | + "-deleted line\n"
148 | + "Index: sample2\n"
149 | + "===================================================================\n"
150 | + "--- a/sample2\n"
151 | + "+++ b/sample2\n"
152 | + "@@ -0,0 +1 @@\n"
153 | + "+added line\n"
154 | + "Index: sample3\n"
155 | + "===================================================================\n"
156 | + "--- a/sample3\n"
157 | + "+++ b/sample3\n"
158 | + "@@ -0,0 +1,2 @@\n"
159 | + "+added line 1\n"
160 | + "+added line 2\n"
161 | + "Index: sample3-not-included\n"
162 | + "===================================================================\n"
163 | + "--- a/sample3-not-included\n"
164 | + "+++ b/sample3-not-included\n"
165 | + "@@ -0,0 +1,2 @@\n"
166 | + "+added line 1\n"
167 | + "+added line 2\n"
168 | + "Index: sample4\n"
169 | + "===================================================================\n"
170 | + "--- a/sample4\n"
171 | + "+++ b/sample4\n"
172 | + "@@ -1 +1,3 @@\n"
173 | + " same line\n"
174 | + "+added line 1\n"
175 | + "+added line 2\n";
176 |
177 | printDiff(example);
178 | assertThat(underTest.changedLines())
179 | .isEqualTo(
180 | ImmutableMap.of(
181 | rootBaseDir.resolve("sample2"), ImmutableSet.of(1),
182 | rootBaseDir.resolve("sample3"), ImmutableSet.of(1, 2),
183 | rootBaseDir.resolve("sample4"), ImmutableSet.of(2, 3)));
184 | }
185 |
186 | private void printDiff(String unifiedDiff) throws IOException {
187 | try (OutputStreamWriter writer = new OutputStreamWriter(underTest.receiver())) {
188 | writer.write(unifiedDiff);
189 | }
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/sonar-scm-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnScmProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SonarQube :: Plugins :: SCM :: SVN
3 | * Copyright (C) 2014-2021 SonarSource SA
4 | * mailto:info AT sonarsource DOT com
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 3 of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public License
17 | * along with this program; if not, write to the Free Software Foundation,
18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 | package org.sonar.plugins.scm.svn;
21 |
22 | import java.io.File;
23 | import java.net.MalformedURLException;
24 | import java.net.URISyntaxException;
25 | import java.net.URL;
26 | import java.nio.file.Path;
27 | import java.nio.file.Paths;
28 | import java.time.Instant;
29 | import java.util.HashSet;
30 | import java.util.Map;
31 | import java.util.Set;
32 | import javax.annotation.CheckForNull;
33 | import org.sonar.api.batch.scm.BlameCommand;
34 | import org.sonar.api.batch.scm.ScmProvider;
35 | import org.sonar.api.utils.log.Logger;
36 | import org.sonar.api.utils.log.Loggers;
37 | import org.tmatesoft.svn.core.SVNDepth;
38 | import org.tmatesoft.svn.core.SVNException;
39 | import org.tmatesoft.svn.core.SVNLogEntryPath;
40 | import org.tmatesoft.svn.core.SVNNodeKind;
41 | import org.tmatesoft.svn.core.SVNURL;
42 | import org.tmatesoft.svn.core.wc.SVNClientManager;
43 | import org.tmatesoft.svn.core.wc.SVNDiffClient;
44 | import org.tmatesoft.svn.core.wc.SVNInfo;
45 | import org.tmatesoft.svn.core.wc.SVNLogClient;
46 | import org.tmatesoft.svn.core.wc.SVNRevision;
47 | import org.tmatesoft.svn.core.wc.SVNWCClient;
48 |
49 | import static org.sonar.plugins.scm.svn.SvnPlugin.newSvnClientManager;
50 |
51 | public class SvnScmProvider extends ScmProvider {
52 |
53 | private static final Logger LOG = Loggers.get(SvnScmProvider.class);
54 |
55 | private final SvnConfiguration configuration;
56 | private final SvnBlameCommand blameCommand;
57 | private final FindFork findFork;
58 |
59 | public SvnScmProvider(SvnConfiguration configuration, SvnBlameCommand blameCommand, FindFork findFork) {
60 | this.configuration = configuration;
61 | this.blameCommand = blameCommand;
62 | this.findFork = findFork;
63 | }
64 |
65 | @Override
66 | public String key() {
67 | return "svn";
68 | }
69 |
70 | @Override
71 | public boolean supports(File baseDir) {
72 | File folder = baseDir;
73 | while (folder != null) {
74 | if (new File(folder, ".svn").exists()) {
75 | return true;
76 | }
77 | folder = folder.getParentFile();
78 | }
79 | return false;
80 | }
81 |
82 | @Override
83 | public BlameCommand blameCommand() {
84 | return blameCommand;
85 | }
86 |
87 | @CheckForNull
88 | @Override
89 | public Set branchChangedFiles(String targetBranchName, Path rootBaseDir) {
90 | SVNClientManager clientManager = null;
91 | try {
92 | clientManager = newSvnClientManager(configuration);
93 | return computeChangedPaths(rootBaseDir, clientManager);
94 | } catch (SVNException e) {
95 | LOG.warn(e.getMessage());
96 | } finally {
97 | if (clientManager != null) {
98 | try {
99 | clientManager.dispose();
100 | } catch (Exception e) {
101 | LOG.warn("Unable to dispose SVN ClientManager", e);
102 | }
103 | }
104 | }
105 |
106 | return null;
107 | }
108 |
109 | static Set computeChangedPaths(Path projectBasedir, SVNClientManager clientManager) throws SVNException {
110 | SVNWCClient wcClient = clientManager.getWCClient();
111 | SVNInfo svnInfo = wcClient.doInfo(projectBasedir.toFile(), null);
112 |
113 | // SVN path of the repo root, for example: /C:/Users/JANOSG~1/AppData/Local/Temp/x/y
114 | Path svnRootPath = toPath(svnInfo.getRepositoryRootURL());
115 |
116 | // the svn root path may be "" for urls like http://svnserver/
117 | // -> set it to "/" to avoid crashing when using Path.relativize later
118 | if (svnRootPath.equals(Paths.get(""))) {
119 | svnRootPath = Paths.get("/");
120 | }
121 |
122 | // SVN path of projectBasedir, for example: /C:/Users/JANOSG~1/AppData/Local/Temp/x/y/branches/b1
123 | Path svnProjectPath = toPath(svnInfo.getURL());
124 | // path of projectBasedir, as "absolute path within the SVN repo", for example: /branches/b1
125 | Path inRepoProjectPath = Paths.get("/").resolve(svnRootPath.relativize(svnProjectPath));
126 |
127 | // We inspect "svn log" from latest revision until copy-point.
128 | // The same path may appear in multiple commits, the ordering of changes and removals is important.
129 | Set paths = new HashSet<>();
130 | Set removed = new HashSet<>();
131 |
132 | SVNLogClient svnLogClient = clientManager.getLogClient();
133 | svnLogClient.doLog(new File[] {projectBasedir.toFile()}, null, null, null, true, true, 0, svnLogEntry -> {
134 | svnLogEntry.getChangedPaths().values().forEach(entry -> {
135 | if (entry.getKind().equals(SVNNodeKind.FILE)) {
136 | Path path = projectBasedir.resolve(inRepoProjectPath.relativize(Paths.get(entry.getPath())));
137 | if (isModified(entry)) {
138 | // Skip if the path is removed in a more recent commit
139 | if (!removed.contains(path)) {
140 | paths.add(path);
141 | }
142 | } else if (entry.getType() == SVNLogEntryPath.TYPE_DELETED) {
143 | removed.add(path);
144 | }
145 | }
146 | });
147 | });
148 | return paths;
149 | }
150 |
151 | private static Path toPath(SVNURL svnUrl) {
152 | if ("file".equals(svnUrl.getProtocol())) {
153 | try {
154 | return Paths.get(new URL("file", svnUrl.getHost(), svnUrl.getPath()).toURI());
155 | } catch (URISyntaxException | MalformedURLException e) {
156 | throw new IllegalStateException(e);
157 | }
158 | }
159 | return Paths.get(svnUrl.getURIEncodedPath());
160 | }
161 |
162 | private static boolean isModified(SVNLogEntryPath entry) {
163 | return entry.getType() == SVNLogEntryPath.TYPE_ADDED
164 | || entry.getType() == SVNLogEntryPath.TYPE_MODIFIED;
165 | }
166 |
167 | @CheckForNull
168 | @Override
169 | public Map> branchChangedLines(String targetBranchName, Path rootBaseDir, Set changedFiles) {
170 | SVNClientManager clientManager = null;
171 | try {
172 | clientManager = newSvnClientManager(configuration);
173 |
174 | // find reference revision number: the copy point
175 | SVNLogClient svnLogClient = clientManager.getLogClient();
176 | long[] revisionCounter = {0};
177 | svnLogClient.doLog(new File[] {rootBaseDir.toFile()}, null, null, null, true, true, 0,
178 | svnLogEntry -> revisionCounter[0] = svnLogEntry.getRevision());
179 |
180 | long startRev = revisionCounter[0];
181 |
182 | SVNDiffClient svnDiffClient = clientManager.getDiffClient();
183 | File path = rootBaseDir.toFile();
184 | ChangedLinesComputer computer = newChangedLinesComputer(rootBaseDir, changedFiles);
185 | svnDiffClient.doDiff(path, SVNRevision.create(startRev), path, SVNRevision.WORKING, SVNDepth.INFINITY, false, computer.receiver(), null);
186 | return computer.changedLines();
187 | } catch (Exception e) {
188 | LOG.warn("Failed to get changed lines from Subversion", e);
189 | } finally {
190 | if (clientManager != null) {
191 | try {
192 | clientManager.dispose();
193 | } catch (Exception e) {
194 | LOG.warn("Unable to dispose SVN ClientManager", e);
195 | }
196 | }
197 | }
198 |
199 | return null;
200 | }
201 |
202 | /**
203 | * It will override API in 8.4
204 | */
205 | @CheckForNull
206 | public Instant forkDate(Path rootBaseDir, String referenceBranch) {
207 | try {
208 | return findFork.findDate(rootBaseDir, referenceBranch);
209 | } catch (SVNException e) {
210 | LOG.warn("Unable to find fork date with '" + referenceBranch + "'", e);
211 | return null;
212 | }
213 | }
214 |
215 | ChangedLinesComputer newChangedLinesComputer(Path rootBaseDir, Set changedFiles) {
216 | return new ChangedLinesComputer(rootBaseDir, changedFiles);
217 | }
218 | }
219 |
--------------------------------------------------------------------------------
/its/src/test/java/com/sonarsource/it/scm/SvnTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SVN :: Integration Tests
3 | * Copyright (C) 2014-2021 SonarSource SA
4 | * mailto:info AT sonarsource DOT com
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 3 of the License, or (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public License
17 | * along with this program; if not, write to the Free Software Foundation,
18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | */
20 | package com.sonarsource.it.scm;
21 |
22 | import com.sonar.orchestrator.Orchestrator;
23 | import com.sonar.orchestrator.build.BuildResult;
24 | import com.sonar.orchestrator.build.MavenBuild;
25 | import com.sonar.orchestrator.locator.FileLocation;
26 | import com.sonar.orchestrator.locator.MavenLocation;
27 | import com.sonar.orchestrator.util.ZipUtils;
28 | import java.io.File;
29 | import java.io.IOException;
30 | import java.nio.charset.StandardCharsets;
31 | import java.nio.file.Files;
32 | import java.text.ParseException;
33 | import java.text.SimpleDateFormat;
34 | import java.util.Arrays;
35 | import java.util.Date;
36 | import java.util.HashMap;
37 | import java.util.Map;
38 | import org.apache.commons.io.FileUtils;
39 | import org.apache.commons.lang.StringUtils;
40 | import org.apache.commons.lang.builder.EqualsBuilder;
41 | import org.apache.commons.lang.builder.HashCodeBuilder;
42 | import org.apache.commons.lang.builder.ToStringBuilder;
43 | import org.apache.commons.lang.builder.ToStringStyle;
44 | import org.assertj.core.data.MapEntry;
45 | import org.junit.Before;
46 | import org.junit.ClassRule;
47 | import org.junit.Rule;
48 | import org.junit.Test;
49 | import org.junit.rules.ExpectedException;
50 | import org.junit.rules.TemporaryFolder;
51 | import org.junit.runner.RunWith;
52 | import org.junit.runners.Parameterized;
53 | import org.junit.runners.Parameterized.Parameters;
54 | import org.sonar.wsclient.jsonsimple.JSONArray;
55 | import org.sonar.wsclient.jsonsimple.JSONObject;
56 | import org.sonar.wsclient.jsonsimple.JSONValue;
57 | import org.tmatesoft.svn.core.SVNDepth;
58 | import org.tmatesoft.svn.core.SVNURL;
59 | import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
60 | import org.tmatesoft.svn.core.internal.wc2.compat.SvnCodec;
61 | import org.tmatesoft.svn.core.wc.ISVNOptions;
62 | import org.tmatesoft.svn.core.wc.SVNClientManager;
63 | import org.tmatesoft.svn.core.wc.SVNRevision;
64 | import org.tmatesoft.svn.core.wc.SVNUpdateClient;
65 | import org.tmatesoft.svn.core.wc.SVNWCUtil;
66 | import org.tmatesoft.svn.core.wc2.SvnCheckout;
67 | import org.tmatesoft.svn.core.wc2.SvnTarget;
68 |
69 | import static org.apache.commons.lang.StringUtils.substringAfter;
70 | import static org.assertj.core.api.Assertions.assertThat;
71 |
72 | @RunWith(Parameterized.class)
73 | public class SvnTest {
74 |
75 | @Rule
76 | public TemporaryFolder temp = new TemporaryFolder();
77 |
78 | public static final File REPO_DIR = new File("scm-repo");
79 |
80 | @ClassRule
81 | public static Orchestrator orchestrator = Orchestrator.builderEnv()
82 | .setSonarVersion(System.getProperty("sonar.runtimeVersion", "LATEST_RELEASE[7.9]"))
83 | .addPlugin(FileLocation.byWildcardMavenFilename(new File("../sonar-scm-svn-plugin/target"), "sonar-scm-svn-plugin-*.jar"))
84 | .addPlugin(MavenLocation.of("org.sonarsource.java", "sonar-java-plugin", "LATEST_RELEASE"))
85 | .build();
86 |
87 | @Rule
88 | public ExpectedException thrown = ExpectedException.none();
89 |
90 | private String serverVersion;
91 | private int wcVersion;
92 | private String wkSubPath;
93 | private String baseDirSubPath;
94 |
95 | @Parameters(name = "SVN server version {0}, WC version {1}, WC subPath \"{2}\", baseDir subPath \"{3}\"")
96 | public static Iterable