mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-13 08:55:44 +01:00
Merge branch 'develop' into feature/packaging
This commit is contained in:
10
CHANGELOG.md
10
CHANGELOG.md
@@ -4,7 +4,14 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
##Unreleased
|
## Unreleased
|
||||||
|
### Added
|
||||||
|
- Detect renamed files in git and hg diffs ([#1157](https://github.com/scm-manager/scm-manager/pull/1157))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Correctly resolve Links in markdown files ([#1152](https://github.com/scm-manager/scm-manager/pull/1152))
|
||||||
|
- Missing copy on write in the data store ([#1155](https://github.com/scm-manager/scm-manager/pull/1155))
|
||||||
|
- Resolved conflicting dependencies for scm-webapp ([#1159](https://github.com/scm-manager/scm-manager/pull/1159))
|
||||||
|
|
||||||
## [2.0.0-rc8] - 2020-05-08
|
## [2.0.0-rc8] - 2020-05-08
|
||||||
### Added
|
### Added
|
||||||
@@ -26,7 +33,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Handle obscure line breaks in diff viewer ([#1129](https://github.com/scm-manager/scm-manager/pull/1129))
|
- Handle obscure line breaks in diff viewer ([#1129](https://github.com/scm-manager/scm-manager/pull/1129))
|
||||||
- Validate subversion client checksum ([#1113](https://github.com/scm-manager/scm-manager/issues/1113))
|
- Validate subversion client checksum ([#1113](https://github.com/scm-manager/scm-manager/issues/1113))
|
||||||
- Fix plugin manage permission ([#1135](https://github.com/scm-manager/scm-manager/pull/1135))
|
- Fix plugin manage permission ([#1135](https://github.com/scm-manager/scm-manager/pull/1135))
|
||||||
- Missing copy on write in the data store ([#1155](https://github.com/scm-manager/scm-manager/pull/1155))
|
|
||||||
|
|
||||||
## [2.0.0-rc7] - 2020-04-09
|
## [2.0.0-rc7] - 2020-04-09
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
3
Jenkinsfile
vendored
3
Jenkinsfile
vendored
@@ -194,6 +194,7 @@ Maven setupMavenBuild() {
|
|||||||
def logConf = "scm-webapp/src/main/resources/logback.ci.xml"
|
def logConf = "scm-webapp/src/main/resources/logback.ci.xml"
|
||||||
mvn.additionalArgs += " -Dlogback.configurationFile=${logConf}"
|
mvn.additionalArgs += " -Dlogback.configurationFile=${logConf}"
|
||||||
mvn.additionalArgs += " -Dscm-it.logbackConfiguration=${logConf}"
|
mvn.additionalArgs += " -Dscm-it.logbackConfiguration=${logConf}"
|
||||||
|
mvn.additionalArgs += " -Dsonar.coverage.exclusions=**/*.test.ts,**/*.test.tsx,**/*.stories.tsx"
|
||||||
|
|
||||||
if (isMainBranch() || isReleaseBranch()) {
|
if (isMainBranch() || isReleaseBranch()) {
|
||||||
// Release starts javadoc, which takes very long, so do only for certain branches
|
// Release starts javadoc, which takes very long, so do only for certain branches
|
||||||
@@ -218,7 +219,7 @@ boolean isMainBranch() {
|
|||||||
|
|
||||||
boolean waitForQualityGateWebhookToBeCalled() {
|
boolean waitForQualityGateWebhookToBeCalled() {
|
||||||
boolean isQualityGateSucceeded = true
|
boolean isQualityGateSucceeded = true
|
||||||
timeout(time: 5, unit: 'MINUTES') { // Needed when there is no webhook for example
|
timeout(time: 10, unit: 'MINUTES') { // Needed when there is no webhook for example
|
||||||
def qGate = waitForQualityGate()
|
def qGate = waitForQualityGate()
|
||||||
echo "SonarQube Quality Gate status: ${qGate.status}"
|
echo "SonarQube Quality Gate status: ${qGate.status}"
|
||||||
if (qGate.status != 'OK') {
|
if (qGate.status != 'OK') {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"babel-core": "7.0.0-bridge.0",
|
"babel-core": "7.0.0-bridge.0",
|
||||||
"gitdiff-parser": "https://github.com/scm-manager/gitdiff-parser#ed3fe7de73dbb0a06c3e6adbbdf22dbae6e66351",
|
"gitdiff-parser": "https://github.com/scm-manager/gitdiff-parser#617747460280bf4522bb84d217a9064ac8eb6d3d",
|
||||||
"lowlight": "1.13.1"
|
"lowlight": "1.13.1"
|
||||||
},
|
},
|
||||||
"babel": {
|
"babel": {
|
||||||
|
|||||||
83
pom.xml
83
pom.xml
@@ -41,7 +41,6 @@
|
|||||||
|
|
||||||
<url>https://github.com/scm-manager/scm-manager</url>
|
<url>https://github.com/scm-manager/scm-manager</url>
|
||||||
|
|
||||||
|
|
||||||
<licenses>
|
<licenses>
|
||||||
<license>
|
<license>
|
||||||
<name>MIT License</name>
|
<name>MIT License</name>
|
||||||
@@ -82,7 +81,7 @@
|
|||||||
|
|
||||||
<ciManagement>
|
<ciManagement>
|
||||||
<system>Jenkins</system>
|
<system>Jenkins</system>
|
||||||
<url>https://scm-manager.ci.cloudbees.com/</url>
|
<url>https://oss.cloudogu.com/jenkins/</url>
|
||||||
</ciManagement>
|
</ciManagement>
|
||||||
|
|
||||||
<prerequisites>
|
<prerequisites>
|
||||||
@@ -153,11 +152,6 @@
|
|||||||
<artifactId>junit-vintage-engine</artifactId>
|
<artifactId>junit-vintage-engine</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.junit-pioneer</groupId>
|
|
||||||
<artifactId>junit-pioneer</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.hamcrest</groupId>
|
<groupId>org.hamcrest</groupId>
|
||||||
<artifactId>hamcrest-core</artifactId>
|
<artifactId>hamcrest-core</artifactId>
|
||||||
@@ -242,6 +236,16 @@
|
|||||||
<groupId>org.jboss.resteasy</groupId>
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
<artifactId>resteasy-core</artifactId>
|
<artifactId>resteasy-core</artifactId>
|
||||||
<version>${resteasy.version}</version>
|
<version>${resteasy.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jboss.spec.javax.ws.rs</groupId>
|
||||||
|
<artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jboss.spec.javax.xml.bind</groupId>
|
||||||
|
<artifactId>jboss-jaxb-api_2.3_spec</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -254,6 +258,12 @@
|
|||||||
<groupId>org.jboss.resteasy</groupId>
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
<artifactId>resteasy-jaxb-provider</artifactId>
|
<artifactId>resteasy-jaxb-provider</artifactId>
|
||||||
<version>${resteasy.version}</version>
|
<version>${resteasy.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jboss.spec.javax.xml.bind</groupId>
|
||||||
|
<artifactId>jboss-jaxb-api_2.3_spec</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -268,6 +278,30 @@
|
|||||||
<version>${resteasy.version}</version>
|
<version>${resteasy.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>resteasy-client-api</artifactId>
|
||||||
|
<version>${resteasy.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jboss.spec.javax.ws.rs</groupId>
|
||||||
|
<artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>resteasy-client</artifactId>
|
||||||
|
<version>${resteasy.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jboss.spec.javax.ws.rs</groupId>
|
||||||
|
<artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
<artifactId>resteasy-guice</artifactId>
|
<artifactId>resteasy-guice</artifactId>
|
||||||
@@ -278,6 +312,12 @@
|
|||||||
<groupId>org.jboss.resteasy</groupId>
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
<artifactId>resteasy-servlet-initializer</artifactId>
|
<artifactId>resteasy-servlet-initializer</artifactId>
|
||||||
<version>${resteasy.version}</version>
|
<version>${resteasy.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jboss.spec.javax.ws.rs</groupId>
|
||||||
|
<artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@@ -357,13 +397,6 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.junit-pioneer</groupId>
|
|
||||||
<artifactId>junit-pioneer</artifactId>
|
|
||||||
<version>0.5.6</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
@@ -419,6 +452,12 @@
|
|||||||
<groupId>org.apache.httpcomponents</groupId>
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
<artifactId>httpclient</artifactId>
|
<artifactId>httpclient</artifactId>
|
||||||
<version>4.5.5</version>
|
<version>4.5.5</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-logging</groupId>
|
||||||
|
<artifactId>commons-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- logging -->
|
<!-- logging -->
|
||||||
@@ -438,8 +477,8 @@
|
|||||||
<!-- xml -->
|
<!-- xml -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.xml.bind</groupId>
|
<groupId>jakarta.xml.bind</groupId>
|
||||||
<artifactId>jaxb-api</artifactId>
|
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||||
<version>${jaxb.version}</version>
|
<version>${jaxb.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -449,12 +488,6 @@
|
|||||||
<version>${jaxb.version}</version>
|
<version>${jaxb.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>javax.activation</groupId>
|
|
||||||
<artifactId>activation</artifactId>
|
|
||||||
<version>1.1.1</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- utils -->
|
<!-- utils -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -821,20 +854,20 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<!-- test libraries -->
|
<!-- test libraries -->
|
||||||
<mockito.version>2.28.2</mockito.version>
|
<mockito.version>2.28.2</mockito.version>
|
||||||
<hamcrest.version>1.3</hamcrest.version>
|
<hamcrest.version>2.1</hamcrest.version>
|
||||||
<junit.version>5.6.2</junit.version>
|
<junit.version>5.6.2</junit.version>
|
||||||
|
|
||||||
<!-- logging libraries -->
|
<!-- logging libraries -->
|
||||||
<slf4j.version>1.7.30</slf4j.version>
|
<slf4j.version>1.7.30</slf4j.version>
|
||||||
<logback.version>1.2.3</logback.version>
|
<logback.version>1.2.3</logback.version>
|
||||||
<servlet.version>3.0.1</servlet.version>
|
<servlet.version>3.1.0</servlet.version>
|
||||||
|
|
||||||
<jaxrs.version>2.1.1</jaxrs.version>
|
<jaxrs.version>2.1.1</jaxrs.version>
|
||||||
<resteasy.version>4.5.3.Final</resteasy.version>
|
<resteasy.version>4.5.3.Final</resteasy.version>
|
||||||
<jersey-client.version>1.19.4</jersey-client.version>
|
<jersey-client.version>1.19.4</jersey-client.version>
|
||||||
<jackson.version>2.11.0</jackson.version>
|
<jackson.version>2.11.0</jackson.version>
|
||||||
<guice.version>4.2.3</guice.version>
|
<guice.version>4.2.3</guice.version>
|
||||||
<jaxb.version>2.3.1</jaxb.version>
|
<jaxb.version>2.3.3</jaxb.version>
|
||||||
<hibernate-validator.version>6.1.4.Final</hibernate-validator.version>
|
<hibernate-validator.version>6.1.4.Final</hibernate-validator.version>
|
||||||
|
|
||||||
<!-- event bus -->
|
<!-- event bus -->
|
||||||
|
|||||||
@@ -173,8 +173,8 @@
|
|||||||
<!-- xml -->
|
<!-- xml -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.xml.bind</groupId>
|
<groupId>jakarta.xml.bind</groupId>
|
||||||
<artifactId>jaxb-api</artifactId>
|
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -182,11 +182,6 @@
|
|||||||
<artifactId>jaxb-runtime</artifactId>
|
<artifactId>jaxb-runtime</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>javax.activation</groupId>
|
|
||||||
<artifactId>activation</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- validation -->
|
<!-- validation -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import java.io.IOException;
|
|||||||
import java.util.zip.GZIPOutputStream;
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
|
||||||
import javax.servlet.ServletOutputStream;
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.WriteListener;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -245,6 +246,20 @@ public class GZipResponseStream extends ServletOutputStream
|
|||||||
return closed;
|
return closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReady() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWriteListener(WriteListener writeListener) {
|
||||||
|
try {
|
||||||
|
writeListener.onWritePossible();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.debug("could not call writeListener.onWritePossible()", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
|
|||||||
41
scm-core/src/main/java/sonia/scm/repository/Added.java
Normal file
41
scm-core/src/main/java/sonia/scm/repository/Added.java
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static java.util.stream.Stream.of;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public class Added extends Modification {
|
||||||
|
private final String path;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Stream<String> getEffectedPaths() {
|
||||||
|
return of(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
scm-core/src/main/java/sonia/scm/repository/Copied.java
Normal file
42
scm-core/src/main/java/sonia/scm/repository/Copied.java
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static java.util.stream.Stream.of;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public class Copied extends Modification {
|
||||||
|
private final String sourcePath;
|
||||||
|
private final String targetPath;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Stream<String> getEffectedPaths() {
|
||||||
|
return of(targetPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
abstract class EffectedPath {
|
||||||
|
abstract Stream<String> getEffectedPaths();
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public abstract class Modification extends EffectedPath implements Serializable {
|
||||||
|
}
|
||||||
@@ -24,71 +24,78 @@
|
|||||||
|
|
||||||
package sonia.scm.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.ImmutableList;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Setter;
|
import lombok.Getter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
@ToString
|
@ToString
|
||||||
@Setter
|
@Getter
|
||||||
public class Modifications implements Serializable {
|
public class Modifications implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = -8902033326668658140L;
|
private static final long serialVersionUID = -8902033326668658140L;
|
||||||
private String revision;
|
|
||||||
|
|
||||||
/**
|
private final String revision;
|
||||||
* lists of changed files
|
private final Collection<Modification> modifications;
|
||||||
*/
|
|
||||||
private List<String> added;
|
|
||||||
private List<String> modified;
|
|
||||||
private List<String> removed;
|
|
||||||
|
|
||||||
public Modifications() {
|
public Modifications(String revision, Modification... modifications) {
|
||||||
|
this(revision, asList(modifications));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Modifications(List<String> added) {
|
public Modifications(String revision, Collection<Modification> modifications) {
|
||||||
this(added, null, null);
|
this.revision = revision;
|
||||||
|
this.modifications = ImmutableList.copyOf(modifications);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Modifications(List<String> added, List<String> modified) {
|
public List<String> getEffectedPaths() {
|
||||||
this(added, modified, null);
|
return effectedPathsStream().collect(toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Modifications(List<String> added, List<String> modified, List<String> removed) {
|
public Stream<String> effectedPathsStream() {
|
||||||
this.added = added;
|
return modifications.stream().flatMap(Modification::getEffectedPaths);
|
||||||
this.modified = modified;
|
|
||||||
this.removed = removed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getAdded() {
|
public List<Added> getAdded() {
|
||||||
if (added == null) {
|
return modifications.stream()
|
||||||
added = Lists.newArrayList();
|
.filter(m -> m instanceof Added)
|
||||||
}
|
.map(m -> (Added) m)
|
||||||
|
.collect(toList());
|
||||||
return added;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getModified() {
|
public List<Removed> getRemoved() {
|
||||||
if (modified == null) {
|
return modifications.stream()
|
||||||
modified = Lists.newArrayList();
|
.filter(m -> m instanceof Removed)
|
||||||
}
|
.map(m -> (Removed) m)
|
||||||
|
.collect(toList());
|
||||||
return modified;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getRemoved() {
|
public List<Modified> getModified() {
|
||||||
if (removed == null) {
|
return modifications.stream()
|
||||||
removed = Lists.newArrayList();
|
.filter(m -> m instanceof Modified)
|
||||||
}
|
.map(m -> (Modified) m)
|
||||||
|
.collect(toList());
|
||||||
return removed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRevision() {
|
public List<Renamed> getRenamed() {
|
||||||
return revision;
|
return modifications.stream()
|
||||||
|
.filter(m -> m instanceof Renamed)
|
||||||
|
.map(m -> (Renamed) m)
|
||||||
|
.collect(toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Copied> getCopied() {
|
||||||
|
return modifications.stream()
|
||||||
|
.filter(m -> m instanceof Copied)
|
||||||
|
.map(m -> (Copied) m)
|
||||||
|
.collect(toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
41
scm-core/src/main/java/sonia/scm/repository/Modified.java
Normal file
41
scm-core/src/main/java/sonia/scm/repository/Modified.java
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static java.util.stream.Stream.of;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public class Modified extends Modification {
|
||||||
|
private final String path;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Stream<String> getEffectedPaths() {
|
||||||
|
return of(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
scm-core/src/main/java/sonia/scm/repository/Removed.java
Normal file
41
scm-core/src/main/java/sonia/scm/repository/Removed.java
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static java.util.stream.Stream.of;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public class Removed extends Modification {
|
||||||
|
private final String path;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Stream<String> getEffectedPaths() {
|
||||||
|
return of(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
scm-core/src/main/java/sonia/scm/repository/Renamed.java
Normal file
42
scm-core/src/main/java/sonia/scm/repository/Renamed.java
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static java.util.stream.Stream.of;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public class Renamed extends Modification {
|
||||||
|
private final String oldPath;
|
||||||
|
private final String newPath;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Stream<String> getEffectedPaths() {
|
||||||
|
return of(oldPath, newPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,4 +33,10 @@ public interface DiffFile extends Iterable<Hunk> {
|
|||||||
String getOldPath();
|
String getOldPath();
|
||||||
|
|
||||||
String getNewPath();
|
String getNewPath();
|
||||||
|
|
||||||
|
ChangeType getChangeType();
|
||||||
|
|
||||||
|
enum ChangeType {
|
||||||
|
ADD, MODIFY, DELETE, RENAME, COPY
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.servlet.ReadListener;
|
||||||
import javax.servlet.ServletInputStream;
|
import javax.servlet.ServletInputStream;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletRequestWrapper;
|
import javax.servlet.http.HttpServletRequestWrapper;
|
||||||
@@ -197,6 +198,25 @@ public class BufferedHttpServletRequest extends HttpServletRequestWrapper
|
|||||||
return bais.read(buf, off, len);
|
return bais.read(buf, off, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished() {
|
||||||
|
return bais.available() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReady() {
|
||||||
|
return bais.available() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReadListener(ReadListener readListener) {
|
||||||
|
try {
|
||||||
|
readListener.onDataAvailable();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.debug("could not call readListener.onDataAvailable()", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//~--- fields -------------------------------------------------------------
|
//~--- fields -------------------------------------------------------------
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ package sonia.scm.web.filter;
|
|||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
@@ -37,6 +40,7 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.servlet.ServletOutputStream;
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.WriteListener;
|
||||||
import javax.servlet.http.Cookie;
|
import javax.servlet.http.Cookie;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpServletResponseWrapper;
|
import javax.servlet.http.HttpServletResponseWrapper;
|
||||||
@@ -48,6 +52,8 @@ import javax.servlet.http.HttpServletResponseWrapper;
|
|||||||
public class BufferedHttpServletResponse extends HttpServletResponseWrapper
|
public class BufferedHttpServletResponse extends HttpServletResponseWrapper
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(BufferedHttpServletResponse.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs ...
|
* Constructs ...
|
||||||
*
|
*
|
||||||
@@ -445,6 +451,20 @@ public class BufferedHttpServletResponse extends HttpServletResponseWrapper
|
|||||||
baos.write(param);
|
baos.write(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReady() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWriteListener(WriteListener writeListener) {
|
||||||
|
try {
|
||||||
|
writeListener.onWritePossible();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.debug("could not call writeListener.onWritePossible()", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//~--- fields -------------------------------------------------------------
|
//~--- fields -------------------------------------------------------------
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
|
|||||||
@@ -27,15 +27,13 @@ package sonia.scm;
|
|||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
class BasicContextProviderTest {
|
class BasicContextProviderTest {
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
@@ -68,13 +66,13 @@ class BasicContextProviderTest {
|
|||||||
private BasicContextProvider context;
|
private BasicContextProvider context;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUpContext(@TempDirectory.TempDir Path baseDirectory) {
|
void setUpContext(@TempDir Path baseDirectory) {
|
||||||
this.baseDirectory = baseDirectory;
|
this.baseDirectory = baseDirectory;
|
||||||
context = new BasicContextProvider(baseDirectory.toFile(), "x.y.z", Stage.PRODUCTION);
|
context = new BasicContextProvider(baseDirectory.toFile(), "x.y.z", Stage.PRODUCTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnAbsolutePathAsIs(@TempDirectory.TempDir Path path) {
|
void shouldReturnAbsolutePathAsIs(@TempDir Path path) {
|
||||||
Path absolutePath = path.toAbsolutePath();
|
Path absolutePath = path.toAbsolutePath();
|
||||||
Path resolved = context.resolve(absolutePath);
|
Path resolved = context.resolve(absolutePath);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class ModificationsTest {
|
||||||
|
|
||||||
|
public static final Modifications MODIFICATIONS = new Modifications("123",
|
||||||
|
new Added("added"),
|
||||||
|
new Removed("removed"),
|
||||||
|
new Modified("modified"),
|
||||||
|
new Renamed("rename from", "rename to"),
|
||||||
|
new Copied("copy from", "copy to")
|
||||||
|
);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFindAddedFilesAsEffected() {
|
||||||
|
assertThat(MODIFICATIONS.getEffectedPaths())
|
||||||
|
.contains("added");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFindRemovedFilesAsEffected() {
|
||||||
|
assertThat(MODIFICATIONS.getEffectedPaths())
|
||||||
|
.contains("removed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFindModifiedFilesAsEffected() {
|
||||||
|
assertThat(MODIFICATIONS.getEffectedPaths())
|
||||||
|
.contains("modified");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFindRenamedFilesAsEffected() {
|
||||||
|
assertThat(MODIFICATIONS.getEffectedPaths())
|
||||||
|
.contains("rename from", "rename to");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFindTargetOfCopiedFilesAsEffected() {
|
||||||
|
assertThat(MODIFICATIONS.getEffectedPaths())
|
||||||
|
.contains("copy to")
|
||||||
|
.doesNotContain("copy from");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ import com.google.common.io.ByteSource;
|
|||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
@@ -57,7 +57,6 @@ import static org.mockito.Mockito.verify;
|
|||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
class ModifyCommandBuilderTest {
|
class ModifyCommandBuilderTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
@@ -71,7 +70,7 @@ class ModifyCommandBuilderTest {
|
|||||||
Path workdir;
|
Path workdir;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void initWorkdir(@TempDirectory.TempDir Path temp) throws IOException {
|
void initWorkdir(@TempDir Path temp) throws IOException {
|
||||||
workdir = Files.createDirectory(temp.resolve("workdir"));
|
workdir = Files.createDirectory(temp.resolve("workdir"));
|
||||||
lenient().when(workdirProvider.createNewWorkdir()).thenReturn(workdir.toFile());
|
lenient().when(workdirProvider.createNewWorkdir()).thenReturn(workdir.toFile());
|
||||||
commandBuilder = new ModifyCommandBuilder(command, workdirProvider);
|
commandBuilder = new ModifyCommandBuilder(command, workdirProvider);
|
||||||
@@ -207,7 +206,7 @@ class ModifyCommandBuilderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldDeleteTemporaryFiles(@TempDirectory.TempDir Path temp) throws IOException {
|
void shouldDeleteTemporaryFiles(@TempDir Path temp) throws IOException {
|
||||||
ArgumentCaptor<String> nameCaptor = ArgumentCaptor.forClass(String.class);
|
ArgumentCaptor<String> nameCaptor = ArgumentCaptor.forClass(String.class);
|
||||||
ArgumentCaptor<File> fileCaptor = ArgumentCaptor.forClass(File.class);
|
ArgumentCaptor<File> fileCaptor = ArgumentCaptor.forClass(File.class);
|
||||||
doNothing().when(worker).modify(nameCaptor.capture(), fileCaptor.capture());
|
doNothing().when(worker).modify(nameCaptor.capture(), fileCaptor.capture());
|
||||||
|
|||||||
@@ -25,8 +25,7 @@
|
|||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -36,11 +35,10 @@ import java.nio.file.Path;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
class ModifyWorkerHelperTest {
|
class ModifyWorkerHelperTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldKeepExecutableFlag(@TempDirectory.TempDir Path temp) throws IOException {
|
void shouldKeepExecutableFlag(@TempDir Path temp) throws IOException {
|
||||||
|
|
||||||
File target = createFile(temp, "executable.sh");
|
File target = createFile(temp, "executable.sh");
|
||||||
File newFile = createFile(temp, "other");
|
File newFile = createFile(temp, "other");
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ package sonia.scm.security;
|
|||||||
import com.google.common.io.Files;
|
import com.google.common.io.Files;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import sonia.scm.SCMContextProvider;
|
import sonia.scm.SCMContextProvider;
|
||||||
@@ -45,7 +45,7 @@ import static org.mockito.Mockito.when;
|
|||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@ExtendWith({MockitoExtension.class, TempDirectory.class})
|
@ExtendWith({MockitoExtension.class})
|
||||||
public class DefaultCipherHandlerTest {
|
public class DefaultCipherHandlerTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
@@ -58,7 +58,7 @@ public class DefaultCipherHandlerTest {
|
|||||||
* Tests loading and storing default key.
|
* Tests loading and storing default key.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void shouldLoadAndStoreDefaultKey(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldLoadAndStoreDefaultKey(@TempDir Path tempDir) throws IOException {
|
||||||
File baseDirectory = tempDir.toFile();
|
File baseDirectory = tempDir.toFile();
|
||||||
|
|
||||||
when(context.getBaseDirectory()).thenReturn(baseDirectory);
|
when(context.getBaseDirectory()).thenReturn(baseDirectory);
|
||||||
@@ -84,7 +84,7 @@ public class DefaultCipherHandlerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("UnstableApiUsage") // is ok for unit test
|
@SuppressWarnings("UnstableApiUsage") // is ok for unit test
|
||||||
void shouldReEncodeOldFormattedDefaultKey(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldReEncodeOldFormattedDefaultKey(@TempDir Path tempDir) throws IOException {
|
||||||
String oldKey = "17eXopruTtX3S4dJ9KTEmbZ-vfZztw==";
|
String oldKey = "17eXopruTtX3S4dJ9KTEmbZ-vfZztw==";
|
||||||
String encryptedValue = "A11kQF7wytpWCkjPflxJB-zUWJ1CVKU3qhwhRFq4Pvl6XqiS9V2w-gqNktqMX6YNDw==";
|
String encryptedValue = "A11kQF7wytpWCkjPflxJB-zUWJ1CVKU3qhwhRFq4Pvl6XqiS9V2w-gqNktqMX6YNDw==";
|
||||||
String plainValue = "Marvin The Paranoid Android - RAM";
|
String plainValue = "Marvin The Paranoid Android - RAM";
|
||||||
|
|||||||
@@ -25,8 +25,7 @@
|
|||||||
package sonia.scm.xml;
|
package sonia.scm.xml;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
|
||||||
|
|
||||||
import javax.xml.bind.JAXB;
|
import javax.xml.bind.JAXB;
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
@@ -36,13 +35,12 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
class XmlInstantAdapterTest {
|
class XmlInstantAdapterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldMarshalAndUnmarshalInstant(@TempDirectory.TempDir Path tempDirectory) {
|
void shouldMarshalAndUnmarshalInstant(@TempDir Path tempDirectory) {
|
||||||
Path path = tempDirectory.resolve("instant.xml");
|
Path path = tempDirectory.resolve("instant.xml");
|
||||||
|
|
||||||
Instant instant = Instant.now();
|
Instant instant = Instant.now();
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.mockito.junit.jupiter.MockitoSettings;
|
import org.mockito.junit.jupiter.MockitoSettings;
|
||||||
@@ -52,7 +52,6 @@ import static org.mockito.ArgumentMatchers.anyString;
|
|||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||||
class PathBasedRepositoryLocationResolverTest {
|
class PathBasedRepositoryLocationResolverTest {
|
||||||
|
|
||||||
@@ -74,7 +73,7 @@ class PathBasedRepositoryLocationResolverTest {
|
|||||||
private PathBasedRepositoryLocationResolver resolver;
|
private PathBasedRepositoryLocationResolver resolver;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void beforeEach(@TempDirectory.TempDir Path temp) {
|
void beforeEach(@TempDir Path temp) {
|
||||||
this.basePath = temp;
|
this.basePath = temp;
|
||||||
when(contextProvider.getBaseDirectory()).thenReturn(temp.toFile());
|
when(contextProvider.getBaseDirectory()).thenReturn(temp.toFile());
|
||||||
when(contextProvider.resolve(any(Path.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
when(contextProvider.resolve(any(Path.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
@@ -61,7 +61,7 @@ import static org.mockito.Mockito.never;
|
|||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith({MockitoExtension.class, TempDirectory.class})
|
@ExtendWith({MockitoExtension.class})
|
||||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||||
class XmlRepositoryDAOTest {
|
class XmlRepositoryDAOTest {
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ class XmlRepositoryDAOTest {
|
|||||||
private XmlRepositoryDAO dao;
|
private XmlRepositoryDAO dao;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void createDAO(@TempDirectory.TempDir Path basePath) {
|
void createDAO(@TempDir Path basePath) {
|
||||||
when(locationResolver.create(Path.class)).thenReturn(
|
when(locationResolver.create(Path.class)).thenReturn(
|
||||||
new RepositoryLocationResolver.RepositoryLocationResolverInstance<Path>() {
|
new RepositoryLocationResolver.RepositoryLocationResolverInstance<Path>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -103,7 +103,7 @@ class XmlRepositoryDAOTest {
|
|||||||
when(locationResolver.remove(anyString())).thenAnswer(invocation -> basePath.resolve(invocation.getArgument(0).toString()));
|
when(locationResolver.remove(anyString())).thenAnswer(invocation -> basePath.resolve(invocation.getArgument(0).toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path createMockedRepoPath(@TempDirectory.TempDir Path basePath, InvocationOnMock invocation) {
|
private Path createMockedRepoPath(@TempDir Path basePath, InvocationOnMock invocation) {
|
||||||
Path resolvedPath = basePath.resolve(invocation.getArgument(0).toString());
|
Path resolvedPath = basePath.resolve(invocation.getArgument(0).toString());
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(resolvedPath);
|
Files.createDirectories(resolvedPath);
|
||||||
@@ -337,7 +337,7 @@ class XmlRepositoryDAOTest {
|
|||||||
private Path repositoryPath;
|
private Path repositoryPath;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void createMetadataFileForRepository(@TempDirectory.TempDir Path basePath) throws IOException {
|
void createMetadataFileForRepository(@TempDir Path basePath) throws IOException {
|
||||||
repositoryPath = basePath.resolve("existing");
|
repositoryPath = basePath.resolve("existing");
|
||||||
|
|
||||||
Files.createDirectories(repositoryPath);
|
Files.createDirectories(repositoryPath);
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ package sonia.scm.store;
|
|||||||
|
|
||||||
import org.assertj.core.api.Assertions;
|
import org.assertj.core.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
|
||||||
|
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -38,11 +37,10 @@ import java.nio.file.Paths;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static sonia.scm.store.CopyOnWrite.withTemporaryFile;
|
import static sonia.scm.store.CopyOnWrite.withTemporaryFile;
|
||||||
|
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
class CopyOnWriteTest {
|
class CopyOnWriteTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCreateNewFile(@TempDirectory.TempDir Path tempDir) {
|
void shouldCreateNewFile(@TempDir Path tempDir) {
|
||||||
Path expectedFile = tempDir.resolve("toBeCreated.txt");
|
Path expectedFile = tempDir.resolve("toBeCreated.txt");
|
||||||
|
|
||||||
withTemporaryFile(
|
withTemporaryFile(
|
||||||
@@ -53,7 +51,7 @@ class CopyOnWriteTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldOverwriteExistingFile(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldOverwriteExistingFile(@TempDir Path tempDir) throws IOException {
|
||||||
Path expectedFile = tempDir.resolve("toBeOverwritten.txt");
|
Path expectedFile = tempDir.resolve("toBeOverwritten.txt");
|
||||||
Files.createFile(expectedFile);
|
Files.createFile(expectedFile);
|
||||||
|
|
||||||
@@ -65,7 +63,7 @@ class CopyOnWriteTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldFailForDirectory(@TempDirectory.TempDir Path tempDir) {
|
void shouldFailForDirectory(@TempDir Path tempDir) {
|
||||||
assertThrows(IllegalArgumentException.class,
|
assertThrows(IllegalArgumentException.class,
|
||||||
() -> withTemporaryFile(
|
() -> withTemporaryFile(
|
||||||
file -> new FileOutputStream(file.toFile()).write("should not be written".getBytes()),
|
file -> new FileOutputStream(file.toFile()).write("should not be written".getBytes()),
|
||||||
@@ -82,7 +80,7 @@ class CopyOnWriteTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldKeepBackupIfTemporaryFileCouldNotBeWritten(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldKeepBackupIfTemporaryFileCouldNotBeWritten(@TempDir Path tempDir) throws IOException {
|
||||||
Path unchangedOriginalFile = tempDir.resolve("notToBeDeleted.txt");
|
Path unchangedOriginalFile = tempDir.resolve("notToBeDeleted.txt");
|
||||||
new FileOutputStream(unchangedOriginalFile.toFile()).write("this should be kept".getBytes());
|
new FileOutputStream(unchangedOriginalFile.toFile()).write("this should be kept".getBytes());
|
||||||
|
|
||||||
@@ -98,7 +96,7 @@ class CopyOnWriteTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldNotWrapRuntimeExceptions(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldNotWrapRuntimeExceptions(@TempDir Path tempDir) throws IOException {
|
||||||
Path someFile = tempDir.resolve("something.txt");
|
Path someFile = tempDir.resolve("something.txt");
|
||||||
|
|
||||||
assertThrows(
|
assertThrows(
|
||||||
@@ -111,7 +109,7 @@ class CopyOnWriteTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldKeepBackupIfTemporaryFileIsMissing(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldKeepBackupIfTemporaryFileIsMissing(@TempDir Path tempDir) throws IOException {
|
||||||
Path backedUpFile = tempDir.resolve("notToBeDeleted.txt");
|
Path backedUpFile = tempDir.resolve("notToBeDeleted.txt");
|
||||||
new FileOutputStream(backedUpFile.toFile()).write("this should be kept".getBytes());
|
new FileOutputStream(backedUpFile.toFile()).write("this should be kept".getBytes());
|
||||||
|
|
||||||
@@ -125,7 +123,7 @@ class CopyOnWriteTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldDeleteExistingFile(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldDeleteExistingFile(@TempDir Path tempDir) throws IOException {
|
||||||
Path expectedFile = tempDir.resolve("toBeReplaced.txt");
|
Path expectedFile = tempDir.resolve("toBeReplaced.txt");
|
||||||
new FileOutputStream(expectedFile.toFile()).write("this should be removed".getBytes());
|
new FileOutputStream(expectedFile.toFile()).write("this should be removed".getBytes());
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import sonia.scm.SCMContextProvider;
|
import sonia.scm.SCMContextProvider;
|
||||||
@@ -48,7 +48,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.lenient;
|
import static org.mockito.Mockito.lenient;
|
||||||
|
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class JAXBPropertyFileAccessTest {
|
class JAXBPropertyFileAccessTest {
|
||||||
|
|
||||||
@@ -63,7 +62,7 @@ class JAXBPropertyFileAccessTest {
|
|||||||
JAXBPropertyFileAccess fileAccess;
|
JAXBPropertyFileAccess fileAccess;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void initTempDir(@TempDirectory.TempDir Path tempDir) {
|
void initTempDir(@TempDir Path tempDir) {
|
||||||
lenient().when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
|
lenient().when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
|
||||||
lenient().when(contextProvider.resolve(any())).thenAnswer(invocation -> tempDir.resolve(invocation.getArgument(0).toString()));
|
lenient().when(contextProvider.resolve(any())).thenAnswer(invocation -> tempDir.resolve(invocation.getArgument(0).toString()));
|
||||||
|
|
||||||
@@ -99,7 +98,7 @@ class JAXBPropertyFileAccessTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldMoveStoreFileToRepositoryBasedLocation(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldMoveStoreFileToRepositoryBasedLocation(@TempDir Path tempDir) throws IOException {
|
||||||
createV1StoreFile(tempDir, "myStore.xml");
|
createV1StoreFile(tempDir, "myStore.xml");
|
||||||
|
|
||||||
fileAccess.forStoreName(STORE_NAME).moveAsRepositoryStore(Paths.get("myStore.xml"), REPOSITORY_ID);
|
fileAccess.forStoreName(STORE_NAME).moveAsRepositoryStore(Paths.get("myStore.xml"), REPOSITORY_ID);
|
||||||
@@ -108,7 +107,7 @@ class JAXBPropertyFileAccessTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldMoveAllStoreFilesToRepositoryBasedLocations(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldMoveAllStoreFilesToRepositoryBasedLocations(@TempDir Path tempDir) throws IOException {
|
||||||
locationResolver.forClass(Path.class).createLocation("repoId2");
|
locationResolver.forClass(Path.class).createLocation("repoId2");
|
||||||
|
|
||||||
createV1StoreFile(tempDir, REPOSITORY_ID + ".xml");
|
createV1StoreFile(tempDir, REPOSITORY_ID + ".xml");
|
||||||
@@ -122,7 +121,7 @@ class JAXBPropertyFileAccessTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createV1StoreFile(@TempDirectory.TempDir Path tempDir, String name) throws IOException {
|
private void createV1StoreFile(@TempDir Path tempDir, String name) throws IOException {
|
||||||
Path v1Dir = tempDir.resolve("var").resolve("data").resolve(STORE_NAME);
|
Path v1Dir = tempDir.resolve("var").resolve("data").resolve(STORE_NAME);
|
||||||
IOUtil.mkdirs(v1Dir.toFile());
|
IOUtil.mkdirs(v1Dir.toFile());
|
||||||
Files.createFile(v1Dir.resolve(name));
|
Files.createFile(v1Dir.resolve(name));
|
||||||
@@ -132,7 +131,7 @@ class JAXBPropertyFileAccessTest {
|
|||||||
class ForMissingRepository {
|
class ForMissingRepository {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldIgnoreStoreFile(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldIgnoreStoreFile(@TempDir Path tempDir) throws IOException {
|
||||||
createV1StoreFile(tempDir, "myStore.xml");
|
createV1StoreFile(tempDir, "myStore.xml");
|
||||||
|
|
||||||
fileAccess.forStoreName(STORE_NAME).moveAsRepositoryStore(Paths.get("myStore.xml"), REPOSITORY_ID);
|
fileAccess.forStoreName(STORE_NAME).moveAsRepositoryStore(Paths.get("myStore.xml"), REPOSITORY_ID);
|
||||||
|
|||||||
@@ -102,7 +102,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.rest-assured</groupId>
|
<groupId>io.rest-assured</groupId>
|
||||||
<artifactId>rest-assured</artifactId>
|
<artifactId>rest-assured</artifactId>
|
||||||
<version>3.1.0</version>
|
<version>4.3.0</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|||||||
@@ -31,14 +31,9 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.TestInstance;
|
import org.junit.jupiter.api.TestInstance;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
|
||||||
import org.junit.jupiter.params.provider.ArgumentsProvider;
|
|
||||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
|
||||||
import sonia.scm.it.utils.RepositoryUtil;
|
import sonia.scm.it.utils.RepositoryUtil;
|
||||||
import sonia.scm.it.utils.RestUtil;
|
import sonia.scm.it.utils.RestUtil;
|
||||||
import sonia.scm.it.utils.ScmRequests;
|
import sonia.scm.it.utils.ScmRequests;
|
||||||
@@ -50,12 +45,10 @@ import sonia.scm.repository.client.api.RepositoryClientException;
|
|||||||
import javax.json.Json;
|
import javax.json.Json;
|
||||||
import javax.json.JsonArray;
|
import javax.json.JsonArray;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import static java.util.Collections.emptyMap;
|
import static java.util.Collections.emptyMap;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -65,7 +58,6 @@ import static sonia.scm.it.utils.TestData.USER_ANONYMOUS;
|
|||||||
import static sonia.scm.it.utils.TestData.WRITE;
|
import static sonia.scm.it.utils.TestData.WRITE;
|
||||||
import static sonia.scm.it.utils.TestData.getDefaultRepositoryUrl;
|
import static sonia.scm.it.utils.TestData.getDefaultRepositoryUrl;
|
||||||
|
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
class AnonymousAccessITCase {
|
class AnonymousAccessITCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -118,7 +110,7 @@ class AnonymousAccessITCase {
|
|||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ArgumentsSource(ScmTypes.class)
|
@ArgumentsSource(ScmTypes.class)
|
||||||
void shouldNotCloneRepository(String type, @TempDirectory.TempDir Path temporaryFolder) {
|
void shouldNotCloneRepository(String type, @TempDir Path temporaryFolder) {
|
||||||
assertThrows(RepositoryClientException.class, () -> RepositoryUtil.createAnonymousRepositoryClient(type, Files.createDirectories(temporaryFolder).toFile()));
|
assertThrows(RepositoryClientException.class, () -> RepositoryUtil.createAnonymousRepositoryClient(type, Files.createDirectories(temporaryFolder).toFile()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,7 +134,7 @@ class AnonymousAccessITCase {
|
|||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ArgumentsSource(ScmTypes.class)
|
@ArgumentsSource(ScmTypes.class)
|
||||||
void shouldCloneRepository(String type, @TempDirectory.TempDir Path temporaryFolder) throws IOException {
|
void shouldCloneRepository(String type, @TempDir Path temporaryFolder) throws IOException {
|
||||||
RepositoryClient client = RepositoryUtil.createAnonymousRepositoryClient(type, Files.createDirectories(temporaryFolder).toFile());
|
RepositoryClient client = RepositoryUtil.createAnonymousRepositoryClient(type, Files.createDirectories(temporaryFolder).toFile());
|
||||||
assertEquals(1, Objects.requireNonNull(client.getWorkingCopy().list()).length);
|
assertEquals(1, Objects.requireNonNull(client.getWorkingCopy().list()).length);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class RoleITCase {
|
|||||||
|
|
||||||
given(VndMediaType.REPOSITORY_ROLE)
|
given(VndMediaType.REPOSITORY_ROLE)
|
||||||
.when()
|
.when()
|
||||||
.content("{" +
|
.body("{" +
|
||||||
"\"name\": \"" + ROLE_NAME + "\"," +
|
"\"name\": \"" + ROLE_NAME + "\"," +
|
||||||
"\"verbs\": [\"read\",\"permissionRead\"]" +
|
"\"verbs\": [\"read\",\"permissionRead\"]" +
|
||||||
"}")
|
"}")
|
||||||
@@ -84,7 +84,7 @@ public class RoleITCase {
|
|||||||
|
|
||||||
given(VndMediaType.REPOSITORY_PERMISSION)
|
given(VndMediaType.REPOSITORY_PERMISSION)
|
||||||
.when()
|
.when()
|
||||||
.content("{\n" +
|
.body("{\n" +
|
||||||
"\t\"role\": \"" + ROLE_NAME + "\",\n" +
|
"\t\"role\": \"" + ROLE_NAME + "\",\n" +
|
||||||
"\t\"name\": \"" + USER + "\",\n" +
|
"\t\"name\": \"" + USER + "\",\n" +
|
||||||
"\t\"groupPermission\": false\n" +
|
"\t\"groupPermission\": false\n" +
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ public class TestData {
|
|||||||
String admin = isAdmin ? "true" : "false";
|
String admin = isAdmin ? "true" : "false";
|
||||||
given(VndMediaType.USER)
|
given(VndMediaType.USER)
|
||||||
.when()
|
.when()
|
||||||
.content(new StringBuilder()
|
.body(new StringBuilder()
|
||||||
.append(" {\n")
|
.append(" {\n")
|
||||||
.append(" \"active\": true,\n")
|
.append(" \"active\": true,\n")
|
||||||
.append(" \"admin\": ").append(admin).append(",\n")
|
.append(" \"admin\": ").append(admin).append(",\n")
|
||||||
@@ -124,7 +124,7 @@ public class TestData {
|
|||||||
LOG.info("create group with group name: {} and description {}", groupName, desc);
|
LOG.info("create group with group name: {} and description {}", groupName, desc);
|
||||||
given(VndMediaType.GROUP)
|
given(VndMediaType.GROUP)
|
||||||
.when()
|
.when()
|
||||||
.content(getGroupJson(groupName,desc))
|
.body(getGroupJson(groupName,desc))
|
||||||
.post(getGroupsUrl())
|
.post(getGroupsUrl())
|
||||||
.then()
|
.then()
|
||||||
.statusCode(HttpStatus.SC_CREATED)
|
.statusCode(HttpStatus.SC_CREATED)
|
||||||
@@ -136,7 +136,7 @@ public class TestData {
|
|||||||
LOG.info("create permission with name {} and verbs {} using the endpoint: {}", username, verbs, defaultPermissionUrl);
|
LOG.info("create permission with name {} and verbs {} using the endpoint: {}", username, verbs, defaultPermissionUrl);
|
||||||
given(VndMediaType.REPOSITORY_PERMISSION)
|
given(VndMediaType.REPOSITORY_PERMISSION)
|
||||||
.when()
|
.when()
|
||||||
.content("{\n" +
|
.body("{\n" +
|
||||||
"\t\"verbs\": " + verbs.stream().collect(Collectors.joining("\",\"", "[\"", "\"]")) + ",\n" +
|
"\t\"verbs\": " + verbs.stream().collect(Collectors.joining("\",\"", "[\"", "\"]")) + ",\n" +
|
||||||
"\t\"name\": \"" + username + "\",\n" +
|
"\t\"name\": \"" + username + "\",\n" +
|
||||||
"\t\"groupPermission\": false\n" +
|
"\t\"groupPermission\": false\n" +
|
||||||
|
|||||||
@@ -26,12 +26,14 @@ package sonia.scm.repository.spi;
|
|||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import org.eclipse.jgit.diff.DiffEntry;
|
import org.eclipse.jgit.diff.DiffEntry;
|
||||||
|
import org.eclipse.jgit.diff.DiffFormatter;
|
||||||
import org.eclipse.jgit.errors.MissingObjectException;
|
import org.eclipse.jgit.errors.MissingObjectException;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevTree;
|
import org.eclipse.jgit.revwalk.RevTree;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
|
||||||
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
|
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
import org.eclipse.jgit.treewalk.filter.PathFilter;
|
import org.eclipse.jgit.treewalk.filter.PathFilter;
|
||||||
@@ -48,16 +50,18 @@ final class Differ implements AutoCloseable {
|
|||||||
private final RevWalk walk;
|
private final RevWalk walk;
|
||||||
private final TreeWalk treeWalk;
|
private final TreeWalk treeWalk;
|
||||||
private final RevCommit commit;
|
private final RevCommit commit;
|
||||||
|
private final PathFilter pathFilter;
|
||||||
|
|
||||||
private Differ(RevCommit commit, RevWalk walk, TreeWalk treeWalk) {
|
private Differ(RevCommit commit, RevWalk walk, TreeWalk treeWalk, PathFilter pathFilter) {
|
||||||
this.commit = commit;
|
this.commit = commit;
|
||||||
this.walk = walk;
|
this.walk = walk;
|
||||||
this.treeWalk = treeWalk;
|
this.treeWalk = treeWalk;
|
||||||
|
this.pathFilter = pathFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Diff diff(Repository repository, DiffCommandRequest request) throws IOException {
|
static Diff diff(Repository repository, DiffCommandRequest request) throws IOException {
|
||||||
try (Differ differ = create(repository, request)) {
|
try (Differ differ = create(repository, request)) {
|
||||||
return differ.diff();
|
return differ.diff(repository);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,11 +85,11 @@ final class Differ implements AutoCloseable {
|
|||||||
treeWalk.reset();
|
treeWalk.reset();
|
||||||
treeWalk.setRecursive(true);
|
treeWalk.setRecursive(true);
|
||||||
|
|
||||||
|
PathFilter pathFilter = null;
|
||||||
if (Util.isNotEmpty(request.getPath())) {
|
if (Util.isNotEmpty(request.getPath())) {
|
||||||
treeWalk.setFilter(PathFilter.create(request.getPath()));
|
pathFilter = PathFilter.create(request.getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!Strings.isNullOrEmpty(request.getAncestorChangeset())) {
|
if (!Strings.isNullOrEmpty(request.getAncestorChangeset())) {
|
||||||
ObjectId otherRevision = repository.resolve(request.getAncestorChangeset());
|
ObjectId otherRevision = repository.resolve(request.getAncestorChangeset());
|
||||||
ObjectId ancestorId = GitUtil.computeCommonAncestor(repository, revision, otherRevision);
|
ObjectId ancestorId = GitUtil.computeCommonAncestor(repository, revision, otherRevision);
|
||||||
@@ -105,14 +109,29 @@ final class Differ implements AutoCloseable {
|
|||||||
|
|
||||||
treeWalk.addTree(commit.getTree());
|
treeWalk.addTree(commit.getTree());
|
||||||
|
|
||||||
return new Differ(commit, walk, treeWalk);
|
return new Differ(commit, walk, treeWalk, pathFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Diff diff() throws IOException {
|
private Diff diff(Repository repository) throws IOException {
|
||||||
List<DiffEntry> entries = DiffEntry.scan(treeWalk);
|
List<DiffEntry> entries = scanWithRename(repository, pathFilter, treeWalk);
|
||||||
return new Diff(commit, entries);
|
return new Diff(commit, entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<DiffEntry> scanWithRename(Repository repository, PathFilter pathFilter, TreeWalk treeWalk) throws IOException {
|
||||||
|
List<DiffEntry> entries;
|
||||||
|
try (DiffFormatter diffFormatter = new DiffFormatter(null)) {
|
||||||
|
diffFormatter.setRepository(repository);
|
||||||
|
diffFormatter.setDetectRenames(true);
|
||||||
|
if (pathFilter != null) {
|
||||||
|
diffFormatter.setPathFilter(pathFilter);
|
||||||
|
}
|
||||||
|
entries = diffFormatter.scan(
|
||||||
|
treeWalk.getTree(0, AbstractTreeIterator.class),
|
||||||
|
treeWalk.getTree(1, AbstractTreeIterator.class));
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
GitUtil.release(walk);
|
GitUtil.release(walk);
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand {
|
|||||||
formatter.setRepository(repository);
|
formatter.setRepository(repository);
|
||||||
|
|
||||||
for (DiffEntry e : diff.getEntries()) {
|
for (DiffEntry e : diff.getEntries()) {
|
||||||
if (!e.getOldId().equals(e.getNewId())) {
|
if (idOrPathChanged(e)) {
|
||||||
formatter.format(e);
|
formatter.format(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,6 +67,10 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean idOrPathChanged(DiffEntry e) {
|
||||||
|
return !e.getOldId().equals(e.getNewId()) || !e.getNewPath().equals(e.getOldPath());
|
||||||
|
}
|
||||||
|
|
||||||
static class DequoteOutputStream extends OutputStream {
|
static class DequoteOutputStream extends OutputStream {
|
||||||
|
|
||||||
private static final String[] DEQUOTE_STARTS = {
|
private static final String[] DEQUOTE_STARTS = {
|
||||||
|
|||||||
@@ -108,6 +108,24 @@ public class GitDiffResultCommand extends AbstractGitCommand implements DiffResu
|
|||||||
return diffEntry.getNewPath();
|
return diffEntry.getNewPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChangeType getChangeType() {
|
||||||
|
switch (diffEntry.getChangeType()) {
|
||||||
|
case ADD:
|
||||||
|
return ChangeType.ADD;
|
||||||
|
case MODIFY:
|
||||||
|
return ChangeType.MODIFY;
|
||||||
|
case RENAME:
|
||||||
|
return ChangeType.RENAME;
|
||||||
|
case DELETE:
|
||||||
|
return ChangeType.DELETE;
|
||||||
|
case COPY:
|
||||||
|
return ChangeType.COPY;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown change type: " + diffEntry.getChangeType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<Hunk> iterator() {
|
public Iterator<Hunk> iterator() {
|
||||||
String content = format(repository, diffEntry);
|
String content = format(repository, diffEntry);
|
||||||
|
|||||||
@@ -32,12 +32,19 @@ import org.eclipse.jgit.revwalk.RevTree;
|
|||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
|
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
|
import sonia.scm.repository.Added;
|
||||||
import sonia.scm.repository.GitUtil;
|
import sonia.scm.repository.GitUtil;
|
||||||
import sonia.scm.repository.InternalRepositoryException;
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
|
import sonia.scm.repository.Modification;
|
||||||
import sonia.scm.repository.Modifications;
|
import sonia.scm.repository.Modifications;
|
||||||
|
import sonia.scm.repository.Modified;
|
||||||
|
import sonia.scm.repository.Removed;
|
||||||
|
import sonia.scm.repository.Renamed;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||||
@@ -72,15 +79,14 @@ public class GitModificationsCommand extends AbstractGitCommand implements Modif
|
|||||||
treeWalk.addTree(new EmptyTreeIterator());
|
treeWalk.addTree(new EmptyTreeIterator());
|
||||||
}
|
}
|
||||||
treeWalk.addTree(commit.getTree());
|
treeWalk.addTree(commit.getTree());
|
||||||
List<DiffEntry> entries = DiffEntry.scan(treeWalk);
|
List<DiffEntry> entries = Differ.scanWithRename(context.open(), null, treeWalk);
|
||||||
Modifications modifications = new Modifications();
|
Collection<Modification> modifications = new ArrayList<>();
|
||||||
for (DiffEntry e : entries) {
|
for (DiffEntry e : entries) {
|
||||||
if (!e.getOldId().equals(e.getNewId())) {
|
if (!e.getOldId().equals(e.getNewId()) || !e.getOldPath().equals(e.getNewPath())) {
|
||||||
appendModification(modifications, e);
|
modifications.add(asModification(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
modifications.setRevision(revision);
|
return new Modifications(revision, modifications);
|
||||||
return modifications;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -111,16 +117,19 @@ public class GitModificationsCommand extends AbstractGitCommand implements Modif
|
|||||||
return getModifications(request.getRevision());
|
return getModifications(request.getRevision());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void appendModification(Modifications modifications, DiffEntry entry) throws UnsupportedModificationTypeException {
|
private Modification asModification(DiffEntry entry) throws UnsupportedModificationTypeException {
|
||||||
DiffEntry.ChangeType type = entry.getChangeType();
|
DiffEntry.ChangeType type = entry.getChangeType();
|
||||||
if (type == DiffEntry.ChangeType.ADD) {
|
switch (type) {
|
||||||
modifications.getAdded().add(entry.getNewPath());
|
case ADD:
|
||||||
} else if (type == DiffEntry.ChangeType.MODIFY) {
|
return new Added(entry.getNewPath());
|
||||||
modifications.getModified().add(entry.getNewPath());
|
case MODIFY:
|
||||||
} else if (type == DiffEntry.ChangeType.DELETE) {
|
return new Modified(entry.getNewPath());
|
||||||
modifications.getRemoved().add(entry.getOldPath());
|
case DELETE:
|
||||||
} else {
|
return new Removed(entry.getOldPath());
|
||||||
throw new UnsupportedModificationTypeException(entity(repository), MessageFormat.format("The modification type: {0} is not supported.", type));
|
case RENAME:
|
||||||
|
return new Renamed(entry.getOldPath(), entry.getNewPath());
|
||||||
|
default:
|
||||||
|
throw new UnsupportedModificationTypeException(entity(repository), MessageFormat.format("The modification type: {0} is not supported.", type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,13 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
public class GitDiffCommandTest extends AbstractGitCommandTestBase {
|
public class GitDiffCommandTest extends AbstractGitCommandTestBase {
|
||||||
@@ -140,4 +142,19 @@ public class GitDiffCommandTest extends AbstractGitCommandTestBase {
|
|||||||
gitDiffCommand.getDiffResult(diffCommandRequest).accept(output);
|
gitDiffCommand.getDiffResult(diffCommandRequest).accept(output);
|
||||||
assertEquals(DIFF_FILE_PARTIAL_MERGE, output.toString());
|
assertEquals(DIFF_FILE_PARTIAL_MERGE, output.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void diffBetweenTwoBranchesWithMovedFiles() throws IOException {
|
||||||
|
GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext());
|
||||||
|
DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
|
||||||
|
diffCommandRequest.setRevision("rename");
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
gitDiffCommand.getDiffResult(diffCommandRequest).accept(output);
|
||||||
|
assertThat(output.toString())
|
||||||
|
.contains("similarity index 100%")
|
||||||
|
.contains("rename from a.txt")
|
||||||
|
.contains("rename to a-copy.txt")
|
||||||
|
.contains("rename from b.txt")
|
||||||
|
.contains("rename to b-copy.txt");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,22 @@ public class GitDiffResultCommandTest extends AbstractGitCommandTestBase {
|
|||||||
assertThat(hunk.getNewLineCount()).isEqualTo(2);
|
assertThat(hunk.getNewLineCount()).isEqualTo(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReturnRenames() throws IOException {
|
||||||
|
DiffResult diffResult = createDiffResult("rename");
|
||||||
|
|
||||||
|
Iterator<DiffFile> fileIterator = diffResult.iterator();
|
||||||
|
DiffFile renameA = fileIterator.next();
|
||||||
|
assertThat(renameA.getOldPath()).isEqualTo("a.txt");
|
||||||
|
assertThat(renameA.getNewPath()).isEqualTo("a-copy.txt");
|
||||||
|
assertThat(renameA.iterator().hasNext()).isFalse();
|
||||||
|
|
||||||
|
DiffFile renameB = fileIterator.next();
|
||||||
|
assertThat(renameB.getOldPath()).isEqualTo("b.txt");
|
||||||
|
assertThat(renameB.getNewPath()).isEqualTo("b-copy.txt");
|
||||||
|
assertThat(renameB.iterator().hasNext()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
private DiffResult createDiffResult(String s) throws IOException {
|
private DiffResult createDiffResult(String s) throws IOException {
|
||||||
GitDiffResultCommand gitDiffResultCommand = new GitDiffResultCommand(createContext());
|
GitDiffResultCommand gitDiffResultCommand = new GitDiffResultCommand(createContext());
|
||||||
DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
|
DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static java.nio.charset.Charset.defaultCharset;
|
import static java.nio.charset.Charset.defaultCharset;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.hamcrest.Matchers.contains;
|
import static org.hamcrest.Matchers.contains;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@@ -188,7 +188,9 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase
|
|||||||
assertTrue("removed list should be empty", modifications.getRemoved().isEmpty());
|
assertTrue("removed list should be empty", modifications.getRemoved().isEmpty());
|
||||||
assertFalse("added list should not be empty", modifications.getAdded().isEmpty());
|
assertFalse("added list should not be empty", modifications.getAdded().isEmpty());
|
||||||
assertEquals(2, modifications.getAdded().size());
|
assertEquals(2, modifications.getAdded().size());
|
||||||
assertThat(modifications.getAdded(), contains("a.txt", "b.txt"));
|
assertThat(modifications.getAdded())
|
||||||
|
.extracting("path")
|
||||||
|
.containsExactly("a.txt", "b.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -198,14 +200,14 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase
|
|||||||
GitLogCommand command = createCommand();
|
GitLogCommand command = createCommand();
|
||||||
Changeset c = command.getChangeset("435df2f061add3589cb3", request);
|
Changeset c = command.getChangeset("435df2f061add3589cb3", request);
|
||||||
|
|
||||||
Assertions.assertThat(c.getBranches()).containsOnly("master");
|
assertThat(c.getBranches()).containsOnly("master");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldNotReturnCommitFromDifferentBranch() {
|
public void shouldNotReturnCommitFromDifferentBranch() {
|
||||||
when(request.getBranch()).thenReturn("master");
|
when(request.getBranch()).thenReturn("master");
|
||||||
Changeset changeset = createCommand().getChangeset("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4", request);
|
Changeset changeset = createCommand().getChangeset("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4", request);
|
||||||
Assertions.assertThat(changeset).isNull();
|
assertThat(changeset).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import org.junit.Test;
|
|||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import sonia.scm.NoChangesMadeException;
|
import sonia.scm.NoChangesMadeException;
|
||||||
import sonia.scm.NotFoundException;
|
import sonia.scm.NotFoundException;
|
||||||
|
import sonia.scm.repository.Added;
|
||||||
import sonia.scm.repository.GitWorkdirFactory;
|
import sonia.scm.repository.GitWorkdirFactory;
|
||||||
import sonia.scm.repository.Person;
|
import sonia.scm.repository.Person;
|
||||||
import sonia.scm.repository.api.MergeCommandResult;
|
import sonia.scm.repository.api.MergeCommandResult;
|
||||||
@@ -318,7 +319,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase {
|
|||||||
assertThat(message).isEqualTo("squash three commits");
|
assertThat(message).isEqualTo("squash three commits");
|
||||||
|
|
||||||
GitModificationsCommand modificationsCommand = new GitModificationsCommand(createContext());
|
GitModificationsCommand modificationsCommand = new GitModificationsCommand(createContext());
|
||||||
List<String> changes = modificationsCommand.getModifications("master").getAdded();
|
List<Added> changes = modificationsCommand.getModifications("master").getAdded();
|
||||||
assertThat(changes.size()).isEqualTo(3);
|
assertThat(changes.size()).isEqualTo(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,25 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
|
|||||||
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReadRenamedFiles() throws Exception {
|
||||||
|
String originalFile = "a.txt";
|
||||||
|
write(outgoing, outgoingDirectory, originalFile, "bal bla");
|
||||||
|
commit(outgoing, "add file");
|
||||||
|
write(outgoing, outgoingDirectory, "b.txt", "bal bla");
|
||||||
|
File file = new File(outgoingDirectory, originalFile);
|
||||||
|
file.delete();
|
||||||
|
outgoing.rm().addFilepattern(originalFile).call();
|
||||||
|
|
||||||
|
RevCommit modifiedFileCommit = commit(outgoing, "rename file");
|
||||||
|
String revision = modifiedFileCommit.getName();
|
||||||
|
|
||||||
|
Consumer<Modifications> assertModifications = assertRenamedFiles("b.txt");
|
||||||
|
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
||||||
|
pushOutgoingAndPullIncoming();
|
||||||
|
assertModifications.accept(incomingModificationsCommand.getModifications(revision));
|
||||||
|
}
|
||||||
|
|
||||||
void pushOutgoingAndPullIncoming() throws IOException {
|
void pushOutgoingAndPullIncoming() throws IOException {
|
||||||
GitPushCommand cmd = new GitPushCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, null));
|
GitPushCommand cmd = new GitPushCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, null));
|
||||||
PushCommandRequest request = new PushCommandRequest();
|
PushCommandRequest request = new PushCommandRequest();
|
||||||
@@ -102,31 +121,62 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
|
|||||||
assertThat(modifications).isNotNull();
|
assertThat(modifications).isNotNull();
|
||||||
assertThat(modifications.getAdded())
|
assertThat(modifications.getAdded())
|
||||||
.as("added files modifications")
|
.as("added files modifications")
|
||||||
.hasSize(0);
|
.asList()
|
||||||
|
.isEmpty();
|
||||||
assertThat(modifications.getModified())
|
assertThat(modifications.getModified())
|
||||||
.as("modified files modifications")
|
.as("modified files modifications")
|
||||||
.hasSize(0);
|
.asList()
|
||||||
|
.isEmpty();
|
||||||
assertThat(modifications.getRemoved())
|
assertThat(modifications.getRemoved())
|
||||||
.as("removed files modifications")
|
.as("removed files modifications")
|
||||||
|
.asList()
|
||||||
.hasSize(1)
|
.hasSize(1)
|
||||||
|
.extracting("path")
|
||||||
.containsOnly(fileName);
|
.containsOnly(fileName);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Consumer<Modifications> assertRenamedFiles(String fileName) {
|
||||||
|
return (modifications) -> {
|
||||||
|
assertThat(modifications).isNotNull();
|
||||||
|
assertThat(modifications.getAdded())
|
||||||
|
.as("added files modifications")
|
||||||
|
.asList()
|
||||||
|
.isEmpty();
|
||||||
|
assertThat(modifications.getModified())
|
||||||
|
.as("modified files modifications")
|
||||||
|
.asList()
|
||||||
|
.isEmpty();
|
||||||
|
assertThat(modifications.getRemoved())
|
||||||
|
.as("removed files modifications")
|
||||||
|
.asList()
|
||||||
|
.isEmpty();
|
||||||
|
assertThat(modifications.getRenamed())
|
||||||
|
.as("renamed files modifications")
|
||||||
|
.asList()
|
||||||
|
.hasSize(1)
|
||||||
|
.extracting("newPath")
|
||||||
|
.containsOnly(fileName);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Consumer<Modifications> assertModifiedFiles(String file) {
|
Consumer<Modifications> assertModifiedFiles(String file) {
|
||||||
return (modifications) -> {
|
return (modifications) -> {
|
||||||
assertThat(modifications).isNotNull();
|
assertThat(modifications).isNotNull();
|
||||||
assertThat(modifications.getAdded())
|
assertThat(modifications.getAdded())
|
||||||
.as("added files modifications")
|
.as("added files modifications")
|
||||||
.hasSize(0);
|
.asList()
|
||||||
|
.isEmpty();
|
||||||
assertThat(modifications.getModified())
|
assertThat(modifications.getModified())
|
||||||
.as("modified files modifications")
|
.as("modified files modifications")
|
||||||
|
.asList()
|
||||||
|
.extracting("path")
|
||||||
.hasSize(1)
|
.hasSize(1)
|
||||||
.containsOnly(file);
|
.containsOnly(file);
|
||||||
assertThat(modifications.getRemoved())
|
assertThat(modifications.getRemoved())
|
||||||
.as("removed files modifications")
|
.as("removed files modifications")
|
||||||
.hasSize(0);
|
.asList()
|
||||||
|
.isEmpty();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,14 +185,18 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
|
|||||||
assertThat(modifications).isNotNull();
|
assertThat(modifications).isNotNull();
|
||||||
assertThat(modifications.getAdded())
|
assertThat(modifications.getAdded())
|
||||||
.as("added files modifications")
|
.as("added files modifications")
|
||||||
|
.asList()
|
||||||
.hasSize(1)
|
.hasSize(1)
|
||||||
|
.extracting("path")
|
||||||
.containsOnly(file);
|
.containsOnly(file);
|
||||||
assertThat(modifications.getModified())
|
assertThat(modifications.getModified())
|
||||||
.as("modified files modifications")
|
.as("modified files modifications")
|
||||||
.hasSize(0);
|
.asList()
|
||||||
|
.isEmpty();
|
||||||
assertThat(modifications.getRemoved())
|
assertThat(modifications.getRemoved())
|
||||||
.as("removed files modifications")
|
.as("removed files modifications")
|
||||||
.hasSize(0);
|
.asList()
|
||||||
|
.isEmpty();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
|||||||
import sonia.scm.util.HttpUtil;
|
import sonia.scm.util.HttpUtil;
|
||||||
|
|
||||||
import javax.servlet.ServletOutputStream;
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.WriteListener;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -150,6 +151,16 @@ public class GitPermissionFilterTest {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return baos.toString();
|
return baos.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReady() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWriteListener(WriteListener writeListener) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -24,9 +24,12 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import sonia.scm.repository.Modification;
|
||||||
import sonia.scm.repository.Modifications;
|
import sonia.scm.repository.Modifications;
|
||||||
import sonia.scm.repository.spi.javahg.HgLogChangesetCommand;
|
import sonia.scm.repository.spi.javahg.HgLogChangesetCommand;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
public class HgModificationsCommand extends AbstractCommand implements ModificationsCommand {
|
public class HgModificationsCommand extends AbstractCommand implements ModificationsCommand {
|
||||||
|
|
||||||
HgModificationsCommand(HgCommandContext context) {
|
HgModificationsCommand(HgCommandContext context) {
|
||||||
@@ -38,9 +41,8 @@ public class HgModificationsCommand extends AbstractCommand implements Modificat
|
|||||||
public Modifications getModifications(String revision) {
|
public Modifications getModifications(String revision) {
|
||||||
com.aragost.javahg.Repository repository = open();
|
com.aragost.javahg.Repository repository = open();
|
||||||
HgLogChangesetCommand hgLogChangesetCommand = HgLogChangesetCommand.on(repository, getContext().getConfig());
|
HgLogChangesetCommand hgLogChangesetCommand = HgLogChangesetCommand.on(repository, getContext().getConfig());
|
||||||
Modifications modifications = hgLogChangesetCommand.rev(revision).extractModifications();
|
Collection<Modification> modifications = hgLogChangesetCommand.rev(revision).extractModifications();
|
||||||
modifications.setRevision(revision);
|
return new Modifications(revision, modifications);
|
||||||
return modifications;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -36,10 +36,11 @@ import com.google.common.collect.ImmutableMap;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import sonia.scm.repository.Changeset;
|
import sonia.scm.repository.Changeset;
|
||||||
import sonia.scm.repository.HgConfig;
|
import sonia.scm.repository.HgConfig;
|
||||||
import sonia.scm.repository.Modifications;
|
import sonia.scm.repository.Modification;
|
||||||
import sonia.scm.repository.Person;
|
import sonia.scm.repository.Person;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
@@ -251,7 +252,7 @@ public abstract class AbstractChangesetCommand extends AbstractCommand
|
|||||||
return changeset;
|
return changeset;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Modifications readModificationsFromStream(HgInputStream in) {
|
protected Collection<Modification> readModificationsFromStream(HgInputStream in) {
|
||||||
try {
|
try {
|
||||||
boolean found = in.find(CHANGESET_PATTERN);
|
boolean found = in.find(CHANGESET_PATTERN);
|
||||||
if (found) {
|
if (found) {
|
||||||
@@ -265,20 +266,14 @@ public abstract class AbstractChangesetCommand extends AbstractCommand
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Modifications extractModifications(HgInputStream in) throws IOException {
|
private Collection<Modification> extractModifications(HgInputStream in) throws IOException {
|
||||||
Modifications modifications = new Modifications();
|
HgModificationParser hgModificationParser = new HgModificationParser();
|
||||||
String line = in.textUpTo('\n');
|
String line = in.textUpTo('\n');
|
||||||
while (line.length() > 0) {
|
while (line.length() > 0) {
|
||||||
if (line.startsWith("a ")) {
|
hgModificationParser.addLine(line);
|
||||||
modifications.getAdded().add(line.substring(2));
|
|
||||||
} else if (line.startsWith("m ")) {
|
|
||||||
modifications.getModified().add(line.substring(2));
|
|
||||||
} else if (line.startsWith("d ")) {
|
|
||||||
modifications.getRemoved().add(line.substring(2));
|
|
||||||
}
|
|
||||||
line = in.textUpTo('\n');
|
line = in.textUpTo('\n');
|
||||||
}
|
}
|
||||||
return modifications;
|
return hgModificationParser.getModifications();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -31,9 +31,10 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.repository.Changeset;
|
import sonia.scm.repository.Changeset;
|
||||||
import sonia.scm.repository.HgConfig;
|
import sonia.scm.repository.HgConfig;
|
||||||
import sonia.scm.repository.Modifications;
|
import sonia.scm.repository.Modification;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,7 +65,7 @@ public class HgLogChangesetCommand extends AbstractChangesetCommand {
|
|||||||
return readListFromStream(getHgInputStream(files, CHANGESET_EAGER_STYLE_PATH));
|
return readListFromStream(getHgInputStream(files, CHANGESET_EAGER_STYLE_PATH));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Modifications extractModifications(String... files) {
|
public Collection<Modification> extractModifications(String... files) {
|
||||||
HgInputStream hgInputStream = getHgInputStream(files, CHANGESET_EAGER_STYLE_PATH);
|
HgInputStream hgInputStream = getHgInputStream(files, CHANGESET_EAGER_STYLE_PATH);
|
||||||
try {
|
try {
|
||||||
return readModificationsFromStream(hgInputStream);
|
return readModificationsFromStream(hgInputStream);
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.repository.spi.javahg;
|
||||||
|
|
||||||
|
import sonia.scm.repository.Added;
|
||||||
|
import sonia.scm.repository.Copied;
|
||||||
|
import sonia.scm.repository.Modification;
|
||||||
|
import sonia.scm.repository.Modified;
|
||||||
|
import sonia.scm.repository.Removed;
|
||||||
|
import sonia.scm.repository.Renamed;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
|
||||||
|
class HgModificationParser {
|
||||||
|
private final Collection<Modification> modifications = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
void addLine(String line) {
|
||||||
|
if (line.startsWith("a ")) {
|
||||||
|
modifications.add(new Added(line.substring(2)));
|
||||||
|
} else if (line.startsWith("m ")) {
|
||||||
|
modifications.add(new Modified(line.substring(2)));
|
||||||
|
} else if (line.startsWith("d ")) {
|
||||||
|
modifications.add(new Removed(line.substring(2)));
|
||||||
|
} else if (line.startsWith("c ")) {
|
||||||
|
String sourceTarget = line.substring(2);
|
||||||
|
int divider = sourceTarget.indexOf('\0');
|
||||||
|
String source = sourceTarget.substring(0, divider);
|
||||||
|
String target = sourceTarget.substring(divider + 1);
|
||||||
|
modifications.remove(new Added(target));
|
||||||
|
if (modifications.remove(new Removed(source))) {
|
||||||
|
modifications.add(new Renamed(source, target));
|
||||||
|
} else {
|
||||||
|
modifications.add(new Copied(source, target));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<Modification> getModifications() {
|
||||||
|
return modifications;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ package sonia.scm.web;
|
|||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
import javax.servlet.ReadListener;
|
||||||
import javax.servlet.ServletInputStream;
|
import javax.servlet.ServletInputStream;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -76,4 +77,19 @@ public class HgServletInputStream extends ServletInputStream {
|
|||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
original.close();
|
original.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished() {
|
||||||
|
return original.isFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReady() {
|
||||||
|
return original.isReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReadListener(ReadListener readListener) {
|
||||||
|
original.setReadListener(readListener);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
header = "%{pattern}"
|
header = "%{pattern}"
|
||||||
changeset = "{rev}:{node}{author}\n{date|hgdate}\n{branch}\n{parents}{extras}\n{tags}{file_adds}{file_mods}{file_dels}\n{desc}\0"
|
changeset = "{rev}:{node}{author}\n{date|hgdate}\n{branch}\n{parents}{extras}\n{tags}{file_adds}{file_mods}{file_dels}{file_copies}\n{desc}\0"
|
||||||
tag = "t {tag}\n"
|
tag = "t {tag}\n"
|
||||||
file_add = "a {file_add}\n"
|
file_add = "a {file_add}\n"
|
||||||
file_mod = "m {file_mod}\n"
|
file_mod = "m {file_mod}\n"
|
||||||
file_del = "d {file_del}\n"
|
file_del = "d {file_del}\n"
|
||||||
|
file_copy = "c {source}\0{name}\n"
|
||||||
extra = "{key}={value|stringescape},"
|
extra = "{key}={value|stringescape},"
|
||||||
footer = "%{pattern}"
|
footer = "%{pattern}"
|
||||||
@@ -33,11 +33,10 @@ import sonia.scm.repository.Modifications;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.contains;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
@@ -162,7 +161,9 @@ public class HgLogCommandTest extends AbstractHgCommandTestBase
|
|||||||
assertTrue("removed list should be empty", modifications.getRemoved().isEmpty());
|
assertTrue("removed list should be empty", modifications.getRemoved().isEmpty());
|
||||||
assertFalse("added list should not be empty", modifications.getAdded().isEmpty());
|
assertFalse("added list should not be empty", modifications.getAdded().isEmpty());
|
||||||
assertEquals(2, modifications.getAdded().size());
|
assertEquals(2, modifications.getAdded().size());
|
||||||
assertThat(modifications.getAdded(), contains("a.txt", "b.txt"));
|
assertThat(modifications.getAdded())
|
||||||
|
.extracting("path")
|
||||||
|
.containsExactly("a.txt", "b.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -25,7 +25,9 @@
|
|||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
import com.aragost.javahg.Changeset;
|
import com.aragost.javahg.Changeset;
|
||||||
|
import com.aragost.javahg.commands.CopyCommand;
|
||||||
import com.aragost.javahg.commands.RemoveCommand;
|
import com.aragost.javahg.commands.RemoveCommand;
|
||||||
|
import com.aragost.javahg.commands.RenameCommand;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import sonia.scm.repository.HgTestUtil;
|
import sonia.scm.repository.HgTestUtil;
|
||||||
@@ -34,7 +36,7 @@ import sonia.scm.repository.Modifications;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static org.assertj.core.api.Java6Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
||||||
|
|
||||||
@@ -83,6 +85,31 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
|||||||
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReadRenamedFiles() throws Exception {
|
||||||
|
String oldFileName = "a.txt";
|
||||||
|
String newFileName = "b.txt";
|
||||||
|
writeNewFile(outgoing, outgoingDirectory, oldFileName, "bal bla");
|
||||||
|
commit(outgoing, "added a.txt");
|
||||||
|
RenameCommand.on(outgoing).execute(oldFileName, newFileName);
|
||||||
|
Changeset changeset = commit(outgoing, "rename a.txt to b.txt");
|
||||||
|
String revision = String.valueOf(changeset.getRevision());
|
||||||
|
Consumer<Modifications> assertModifications = assertRenamedFiles(oldFileName, newFileName);
|
||||||
|
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReadCopiedFiles() throws Exception {
|
||||||
|
String srcFileName = "a.txt";
|
||||||
|
String newFileName = "b.txt";
|
||||||
|
writeNewFile(outgoing, outgoingDirectory, srcFileName, "bal bla");
|
||||||
|
commit(outgoing, "added a.txt");
|
||||||
|
CopyCommand.on(outgoing).execute(srcFileName, newFileName);
|
||||||
|
Changeset changeset = commit(outgoing, "copy a.txt to b.txt");
|
||||||
|
String revision = String.valueOf(changeset.getRevision());
|
||||||
|
Consumer<Modifications> assertModifications = assertCopiedFiles(srcFileName, newFileName);
|
||||||
|
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
||||||
|
}
|
||||||
|
|
||||||
Consumer<Modifications> assertRemovedFiles(String fileName) {
|
Consumer<Modifications> assertRemovedFiles(String fileName) {
|
||||||
return (modifications) -> {
|
return (modifications) -> {
|
||||||
@@ -96,10 +123,66 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
|||||||
assertThat(modifications.getRemoved())
|
assertThat(modifications.getRemoved())
|
||||||
.as("removed files modifications")
|
.as("removed files modifications")
|
||||||
.hasSize(1)
|
.hasSize(1)
|
||||||
|
.extracting("path")
|
||||||
.containsOnly(fileName);
|
.containsOnly(fileName);
|
||||||
|
assertThat(modifications.getRenamed())
|
||||||
|
.as("renamed files modifications")
|
||||||
|
.isEmpty();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Consumer<Modifications> assertRenamedFiles(String oldFileName, String newFileName) {
|
||||||
|
return (modifications) -> {
|
||||||
|
assertThat(modifications).isNotNull();
|
||||||
|
assertThat(modifications.getAdded())
|
||||||
|
.as("added files modifications")
|
||||||
|
.hasSize(0);
|
||||||
|
assertThat(modifications.getModified())
|
||||||
|
.as("modified files modifications")
|
||||||
|
.hasSize(0);
|
||||||
|
assertThat(modifications.getRemoved())
|
||||||
|
.as("removed files modifications")
|
||||||
|
.isEmpty();
|
||||||
|
assertThat(modifications.getRenamed())
|
||||||
|
.as("renamed files modifications")
|
||||||
|
.hasSize(1)
|
||||||
|
.extracting("oldPath")
|
||||||
|
.containsOnly(oldFileName);
|
||||||
|
assertThat(modifications.getRenamed())
|
||||||
|
.as("renamed files modifications")
|
||||||
|
.hasSize(1)
|
||||||
|
.extracting("newPath")
|
||||||
|
.containsOnly(newFileName);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Consumer<Modifications> assertCopiedFiles(String srcFileName, String newFileName) {
|
||||||
|
return (modifications) -> {
|
||||||
|
assertThat(modifications).isNotNull();
|
||||||
|
assertThat(modifications.getAdded())
|
||||||
|
.as("added files modifications")
|
||||||
|
.hasSize(0);
|
||||||
|
assertThat(modifications.getModified())
|
||||||
|
.as("modified files modifications")
|
||||||
|
.hasSize(0);
|
||||||
|
assertThat(modifications.getRemoved())
|
||||||
|
.as("removed files modifications")
|
||||||
|
.isEmpty();
|
||||||
|
assertThat(modifications.getRenamed())
|
||||||
|
.as("renamed files modifications")
|
||||||
|
.isEmpty();
|
||||||
|
assertThat(modifications.getCopied())
|
||||||
|
.as("copied files modifications")
|
||||||
|
.hasSize(1)
|
||||||
|
.extracting("sourcePath")
|
||||||
|
.containsOnly(srcFileName);
|
||||||
|
assertThat(modifications.getCopied())
|
||||||
|
.as("copied files modifications")
|
||||||
|
.hasSize(1)
|
||||||
|
.extracting("targetPath")
|
||||||
|
.containsOnly(newFileName);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Consumer<Modifications> assertModifiedFiles(String file) {
|
Consumer<Modifications> assertModifiedFiles(String file) {
|
||||||
return (modifications) -> {
|
return (modifications) -> {
|
||||||
@@ -110,10 +193,14 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
|||||||
assertThat(modifications.getModified())
|
assertThat(modifications.getModified())
|
||||||
.as("modified files modifications")
|
.as("modified files modifications")
|
||||||
.hasSize(1)
|
.hasSize(1)
|
||||||
|
.extracting("path")
|
||||||
.containsOnly(file);
|
.containsOnly(file);
|
||||||
assertThat(modifications.getRemoved())
|
assertThat(modifications.getRemoved())
|
||||||
.as("removed files modifications")
|
.as("removed files modifications")
|
||||||
.hasSize(0);
|
.hasSize(0);
|
||||||
|
assertThat(modifications.getRenamed())
|
||||||
|
.as("renamed files modifications")
|
||||||
|
.hasSize(0);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +210,7 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
|||||||
assertThat(modifications.getAdded())
|
assertThat(modifications.getAdded())
|
||||||
.as("added files modifications")
|
.as("added files modifications")
|
||||||
.hasSize(1)
|
.hasSize(1)
|
||||||
|
.extracting("path")
|
||||||
.containsOnly(addedFile);
|
.containsOnly(addedFile);
|
||||||
assertThat(modifications.getModified())
|
assertThat(modifications.getModified())
|
||||||
.as("modified files modifications")
|
.as("modified files modifications")
|
||||||
@@ -130,6 +218,9 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
|||||||
assertThat(modifications.getRemoved())
|
assertThat(modifications.getRemoved())
|
||||||
.as("removed files modifications")
|
.as("removed files modifications")
|
||||||
.hasSize(0);
|
.hasSize(0);
|
||||||
|
assertThat(modifications.getRenamed())
|
||||||
|
.as("renamed files modifications")
|
||||||
|
.hasSize(0);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.repository.spi.javahg;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import sonia.scm.repository.Added;
|
||||||
|
import sonia.scm.repository.Copied;
|
||||||
|
import sonia.scm.repository.Modified;
|
||||||
|
import sonia.scm.repository.Removed;
|
||||||
|
import sonia.scm.repository.Renamed;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class HgModificationParserTest {
|
||||||
|
|
||||||
|
HgModificationParser parser = new HgModificationParser();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldDetectAddedPath() {
|
||||||
|
parser.addLine("a added/file");
|
||||||
|
|
||||||
|
assertThat(parser.getModifications())
|
||||||
|
.containsExactly(new Added("added/file"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldDetectModifiedPath() {
|
||||||
|
parser.addLine("m modified/file");
|
||||||
|
|
||||||
|
assertThat(parser.getModifications())
|
||||||
|
.containsExactly(new Modified("modified/file"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldDetectRemovedPath() {
|
||||||
|
parser.addLine("d removed/file");
|
||||||
|
|
||||||
|
assertThat(parser.getModifications())
|
||||||
|
.containsExactly(new Removed("removed/file"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldDetectRenamedPath() {
|
||||||
|
parser.addLine("a new/path");
|
||||||
|
parser.addLine("d old/path");
|
||||||
|
parser.addLine("c old/path\0new/path");
|
||||||
|
|
||||||
|
assertThat(parser.getModifications())
|
||||||
|
.containsExactly(new Renamed("old/path", "new/path"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCopiedRenamedPath() {
|
||||||
|
parser.addLine("a new/path");
|
||||||
|
parser.addLine("c old/path\0new/path");
|
||||||
|
|
||||||
|
assertThat(parser.getModifications())
|
||||||
|
.containsExactly(new Copied("old/path", "new/path"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import com.google.common.base.Charsets;
|
|||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.servlet.ReadListener;
|
||||||
import javax.servlet.ServletInputStream;
|
import javax.servlet.ServletInputStream;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -69,6 +70,20 @@ public class HgServletInputStreamTest {
|
|||||||
public int read() {
|
public int read() {
|
||||||
return input.read();
|
return input.read();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReady() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReadListener(ReadListener readListener) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import org.junit.runner.RunWith;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import javax.servlet.ReadListener;
|
||||||
import javax.servlet.ServletInputStream;
|
import javax.servlet.ServletInputStream;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@@ -181,6 +182,19 @@ public class WireProtocolTest {
|
|||||||
return input.read();
|
return input.read();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReady() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReadListener(ReadListener readListener) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,13 +48,6 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>javax.ws.rs</groupId>
|
|
||||||
<artifactId>jsr311-api</artifactId>
|
|
||||||
<version>1.1.1</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -49,7 +49,11 @@ import java.io.IOException;
|
|||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Optional.empty;
|
||||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||||
import static sonia.scm.NotFoundException.notFound;
|
import static sonia.scm.NotFoundException.notFound;
|
||||||
|
|
||||||
@@ -116,30 +120,23 @@ public final class SvnUtil
|
|||||||
|
|
||||||
|
|
||||||
public static Modifications createModifications(SVNLogEntry entry, String revision) {
|
public static Modifications createModifications(SVNLogEntry entry, String revision) {
|
||||||
Modifications modifications = new Modifications();
|
|
||||||
modifications.setRevision(revision);
|
|
||||||
Map<String, SVNLogEntryPath> changeMap = entry.getChangedPaths();
|
Map<String, SVNLogEntryPath> changeMap = entry.getChangedPaths();
|
||||||
|
|
||||||
|
List<Modification> modificationList;
|
||||||
if (Util.isNotEmpty(changeMap)) {
|
if (Util.isNotEmpty(changeMap)) {
|
||||||
|
modificationList = changeMap.values().stream()
|
||||||
for (SVNLogEntryPath e : changeMap.values()) {
|
.map(e -> asModification(e.getType(), e.getPath()))
|
||||||
appendModification(modifications, e.getType(), e.getPath());
|
.filter(Optional::isPresent)
|
||||||
}
|
.map(Optional::get)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
} else {
|
||||||
|
modificationList = emptyList();
|
||||||
}
|
}
|
||||||
return modifications;
|
|
||||||
|
return new Modifications(revision, modificationList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static Optional<Modification> asModification(char type, String path) {
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param modifications
|
|
||||||
* @param type
|
|
||||||
* @param path
|
|
||||||
*/
|
|
||||||
public static void appendModification(Modifications modifications, char type,
|
|
||||||
String path)
|
|
||||||
{
|
|
||||||
if (path.startsWith("/"))
|
if (path.startsWith("/"))
|
||||||
{
|
{
|
||||||
path = path.substring(1);
|
path = path.substring(1);
|
||||||
@@ -148,23 +145,18 @@ public final class SvnUtil
|
|||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case SVNLogEntryPath.TYPE_ADDED :
|
case SVNLogEntryPath.TYPE_ADDED :
|
||||||
modifications.getAdded().add(path);
|
return Optional.of(new Added(path));
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SVNLogEntryPath.TYPE_DELETED :
|
case SVNLogEntryPath.TYPE_DELETED :
|
||||||
modifications.getRemoved().add(path);
|
return Optional.of(new Removed(path));
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TYPE_UPDATED :
|
case TYPE_UPDATED :
|
||||||
case SVNLogEntryPath.TYPE_MODIFIED :
|
case SVNLogEntryPath.TYPE_MODIFIED :
|
||||||
modifications.getModified().add(path);
|
return Optional.of(new Modified(path));
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default :
|
default :
|
||||||
logger.debug("unknown modification type {}", type);
|
logger.debug("unknown modification type {}", type);
|
||||||
|
return empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,10 +31,12 @@ import org.tmatesoft.svn.core.io.SVNRepository;
|
|||||||
import org.tmatesoft.svn.core.wc.SVNClientManager;
|
import org.tmatesoft.svn.core.wc.SVNClientManager;
|
||||||
import org.tmatesoft.svn.core.wc.admin.SVNLookClient;
|
import org.tmatesoft.svn.core.wc.admin.SVNLookClient;
|
||||||
import sonia.scm.repository.InternalRepositoryException;
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
|
import sonia.scm.repository.Modification;
|
||||||
import sonia.scm.repository.Modifications;
|
import sonia.scm.repository.Modifications;
|
||||||
import sonia.scm.repository.SvnUtil;
|
import sonia.scm.repository.SvnUtil;
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -78,12 +80,12 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif
|
|||||||
|
|
||||||
private Modifications getModificationsFromTransaction(String transaction) throws SVNException {
|
private Modifications getModificationsFromTransaction(String transaction) throws SVNException {
|
||||||
log.debug("get svn modifications from transaction: {}", transaction);
|
log.debug("get svn modifications from transaction: {}", transaction);
|
||||||
final Modifications modifications = new Modifications();
|
|
||||||
SVNLookClient client = SVNClientManager.newInstance().getLookClient();
|
SVNLookClient client = SVNClientManager.newInstance().getLookClient();
|
||||||
|
Collection<Modification> modificationList = new ArrayList<>();
|
||||||
client.doGetChanged(context.getDirectory(), transaction,
|
client.doGetChanged(context.getDirectory(), transaction,
|
||||||
e -> SvnUtil.appendModification(modifications, e.getType(), e.getPath()), true);
|
e -> SvnUtil.asModification(e.getType(), e.getPath()).ifPresent(modificationList::add), true);
|
||||||
|
|
||||||
return modifications;
|
return new Modifications(null, modificationList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -143,8 +143,8 @@ public class SvnLogCommandTest extends AbstractSvnCommandTestBase
|
|||||||
assertEquals(1, modifications.getModified().size());
|
assertEquals(1, modifications.getModified().size());
|
||||||
assertEquals(1, modifications.getRemoved().size());
|
assertEquals(1, modifications.getRemoved().size());
|
||||||
assertTrue("added list should be empty", modifications.getAdded().isEmpty());
|
assertTrue("added list should be empty", modifications.getAdded().isEmpty());
|
||||||
assertEquals("a.txt", modifications.getModified().get(0));
|
assertEquals("a.txt", modifications.getModified().get(0).getPath());
|
||||||
assertEquals("b.txt", modifications.getRemoved().get(0));
|
assertEquals("b.txt", modifications.getRemoved().get(0).getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
123
scm-ui/ui-components/src/MarkdownLinkRenderer.test.tsx
Normal file
123
scm-ui/ui-components/src/MarkdownLinkRenderer.test.tsx
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { isAnchorLink, isExternalLink, isLinkWithProtocol, createLocalLink } from "./MarkdownLinkRenderer";
|
||||||
|
|
||||||
|
describe("test isAnchorLink", () => {
|
||||||
|
it("should return true", () => {
|
||||||
|
expect(isAnchorLink("#some-thing")).toBe(true);
|
||||||
|
expect(isAnchorLink("#/some/more/complicated-link")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false", () => {
|
||||||
|
expect(isAnchorLink("https://cloudogu.com")).toBe(false);
|
||||||
|
expect(isAnchorLink("/some/path/link")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("test isExternalLink", () => {
|
||||||
|
it("should return true", () => {
|
||||||
|
expect(isExternalLink("https://cloudogu.com")).toBe(true);
|
||||||
|
expect(isExternalLink("http://cloudogu.com")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false", () => {
|
||||||
|
expect(isExternalLink("some/path/link")).toBe(false);
|
||||||
|
expect(isExternalLink("/some/path/link")).toBe(false);
|
||||||
|
expect(isExternalLink("#some-anchor")).toBe(false);
|
||||||
|
expect(isExternalLink("mailto:trillian@hitchhiker.com")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("test isLinkWithProtocol", () => {
|
||||||
|
it("should return true", () => {
|
||||||
|
expect(isLinkWithProtocol("ldap://[2001:db8::7]/c=GB?objectClass?one")).toBe(true);
|
||||||
|
expect(isLinkWithProtocol("mailto:trillian@hitchhiker.com")).toBe(true);
|
||||||
|
expect(isLinkWithProtocol("tel:+1-816-555-1212")).toBe(true);
|
||||||
|
expect(isLinkWithProtocol("urn:oasis:names:specification:docbook:dtd:xml:4.1.2")).toBe(true);
|
||||||
|
expect(isLinkWithProtocol("about:config")).toBe(true);
|
||||||
|
expect(isLinkWithProtocol("http://cloudogu.com")).toBe(true);
|
||||||
|
expect(isLinkWithProtocol("file:///srv/git/project.git")).toBe(true);
|
||||||
|
expect(isLinkWithProtocol("ssh://trillian@server/project.git")).toBe(true);
|
||||||
|
});
|
||||||
|
it("should return false", () => {
|
||||||
|
expect(isLinkWithProtocol("some/path/link")).toBe(false);
|
||||||
|
expect(isLinkWithProtocol("/some/path/link")).toBe(false);
|
||||||
|
expect(isLinkWithProtocol("#some-anchor")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("test createLocalLink", () => {
|
||||||
|
it("should handle relative links", () => {
|
||||||
|
expectLocalLink("/src", "/src/README.md", "docs/Home.md", "/src/docs/Home.md");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle absolute links", () => {
|
||||||
|
expectLocalLink("/src", "/src/README.md", "/docs/CHANGELOG.md", "/src/docs/CHANGELOG.md");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle relative links from locations with trailing slash", () => {
|
||||||
|
expectLocalLink("/src", "/src/README.md/", "/docs/LICENSE.md", "/src/docs/LICENSE.md");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle relative links from location outside of base", () => {
|
||||||
|
expectLocalLink("/src", "/info/readme", "docs/index.md", "/src/docs/index.md");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle absolute links from location outside of base", () => {
|
||||||
|
expectLocalLink("/src", "/info/readme", "/info/index.md", "/src/info/index.md");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle relative links from sub directories", () => {
|
||||||
|
expectLocalLink("/src", "/src/docs/index.md", "installation/linux.md", "/src/docs/installation/linux.md");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle absolute links from sub directories", () => {
|
||||||
|
expectLocalLink("/src", "/src/docs/index.md", "/docs/CONTRIBUTIONS.md", "/src/docs/CONTRIBUTIONS.md");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resolve .. with in path", () => {
|
||||||
|
expectLocalLink("/src", "/src/docs/installation/index.md", "../../README.md", "/src/README.md");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resolve .. to / if we reached the end", () => {
|
||||||
|
expectLocalLink("/", "/index.md", "../../README.md", "/README.md");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resolve . with in path", () => {
|
||||||
|
expectLocalLink("/src", "/src/README.md", "./SHAPESHIPS.md", "/src/SHAPESHIPS.md");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resolve . with the current directory", () => {
|
||||||
|
expectLocalLink("/", "/README.md", "././HITCHHIKER.md", "/HITCHHIKER.md");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle complex path", () => {
|
||||||
|
expectLocalLink("/src", "/src/docs/installation/index.md", "./.././../docs/index.md", "/src/docs/index.md");
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectLocalLink = (basePath: string, currentPath: string, link: string, expected: string) => {
|
||||||
|
const localLink = createLocalLink(basePath, currentPath, link);
|
||||||
|
expect(localLink).toBe(expected);
|
||||||
|
};
|
||||||
|
});
|
||||||
125
scm-ui/ui-components/src/MarkdownLinkRenderer.tsx
Normal file
125
scm-ui/ui-components/src/MarkdownLinkRenderer.tsx
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
import React, {FC} from "react";
|
||||||
|
import {Link, useLocation} from "react-router-dom";
|
||||||
|
import ExternalLink from "./navigation/ExternalLink";
|
||||||
|
import {withContextPath} from "./urls";
|
||||||
|
|
||||||
|
const externalLinkRegex = new RegExp("^http(s)?://");
|
||||||
|
export const isExternalLink = (link: string) => {
|
||||||
|
return externalLinkRegex.test(link);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isAnchorLink = (link: string) => {
|
||||||
|
return link.startsWith("#");
|
||||||
|
};
|
||||||
|
|
||||||
|
const linkWithProtcolRegex = new RegExp("^[a-z]+:");
|
||||||
|
export const isLinkWithProtocol = (link: string) => {
|
||||||
|
return linkWithProtcolRegex.test(link);
|
||||||
|
};
|
||||||
|
|
||||||
|
const join = (left: string, right: string) => {
|
||||||
|
if (left.endsWith("/") && right.startsWith("/")) {
|
||||||
|
return left + right.substring(1);
|
||||||
|
} else if (!left.endsWith("/") && !right.startsWith("/")) {
|
||||||
|
return left + "/" + right;
|
||||||
|
}
|
||||||
|
return left + right;
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizePath = (path: string) => {
|
||||||
|
const stack = [];
|
||||||
|
const parts = path.split("/");
|
||||||
|
for (const part of parts) {
|
||||||
|
if (part === "..") {
|
||||||
|
stack.pop();
|
||||||
|
} else if (part !== ".") {
|
||||||
|
stack.push(part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const normalizedPath = stack.join("/")
|
||||||
|
if (normalizedPath.startsWith("/")) {
|
||||||
|
return normalizedPath;
|
||||||
|
}
|
||||||
|
return "/" + normalizedPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isAbsolute = (link: string) => {
|
||||||
|
return link.startsWith("/");
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSubDirectoryOf = (basePath: string, currentPath: string) => {
|
||||||
|
return currentPath.startsWith(basePath);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createLocalLink = (basePath: string, currentPath: string, link: string) => {
|
||||||
|
if (isAbsolute(link)) {
|
||||||
|
return join(basePath, link);
|
||||||
|
}
|
||||||
|
if (!isSubDirectoryOf(basePath, currentPath)) {
|
||||||
|
return join(basePath, link);
|
||||||
|
}
|
||||||
|
let path = currentPath;
|
||||||
|
if (currentPath.endsWith("/")) {
|
||||||
|
path = currentPath.substring(0, currentPath.length - 2);
|
||||||
|
}
|
||||||
|
const lastSlash = path.lastIndexOf("/");
|
||||||
|
if (lastSlash < 0) {
|
||||||
|
path = "";
|
||||||
|
} else {
|
||||||
|
path = path.substring(0, lastSlash);
|
||||||
|
}
|
||||||
|
return normalizePath(join(path, link));
|
||||||
|
};
|
||||||
|
|
||||||
|
type LinkProps = {
|
||||||
|
href: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = LinkProps & {
|
||||||
|
base: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MarkdownLinkRenderer: FC<Props> = ({href, base, children}) => {
|
||||||
|
const location = useLocation();
|
||||||
|
if (isExternalLink(href)) {
|
||||||
|
return <ExternalLink to={href}>{children}</ExternalLink>;
|
||||||
|
} else if (isLinkWithProtocol(href)) {
|
||||||
|
return <a href={href}>{children}</a>;
|
||||||
|
} else if (isAnchorLink(href)) {
|
||||||
|
return <a href={withContextPath(location.pathname) + href}>{children}</a>;
|
||||||
|
} else {
|
||||||
|
const localLink = createLocalLink(base, location.pathname, href);
|
||||||
|
return <Link to={localLink}>{children}</Link>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// we use a factory method, because react-markdown does not pass
|
||||||
|
// base as prop down to our link component.
|
||||||
|
export const create = (base: string): FC<LinkProps> => {
|
||||||
|
return props => <MarkdownLinkRenderer base={base} {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MarkdownLinkRenderer;
|
||||||
@@ -30,6 +30,7 @@ import TestPage from "./__resources__/test-page.md";
|
|||||||
import MarkdownWithoutLang from "./__resources__/markdown-without-lang.md";
|
import MarkdownWithoutLang from "./__resources__/markdown-without-lang.md";
|
||||||
import MarkdownXmlCodeBlock from "./__resources__/markdown-xml-codeblock.md";
|
import MarkdownXmlCodeBlock from "./__resources__/markdown-xml-codeblock.md";
|
||||||
import MarkdownInlineXml from "./__resources__/markdown-inline-xml.md";
|
import MarkdownInlineXml from "./__resources__/markdown-inline-xml.md";
|
||||||
|
import MarkdownLinks from "./__resources__/markdown-links.md";
|
||||||
import Title from "./layout/Title";
|
import Title from "./layout/Title";
|
||||||
import { Subtitle } from "./layout";
|
import { Subtitle } from "./layout";
|
||||||
import { MemoryRouter } from "react-router-dom";
|
import { MemoryRouter } from "react-router-dom";
|
||||||
@@ -50,4 +51,5 @@ storiesOf("MarkdownView", module)
|
|||||||
<Subtitle subtitle="Inline xml outside of a code block is not supported" />
|
<Subtitle subtitle="Inline xml outside of a code block is not supported" />
|
||||||
<MarkdownView content={MarkdownInlineXml} />
|
<MarkdownView content={MarkdownInlineXml} />
|
||||||
</>
|
</>
|
||||||
));
|
))
|
||||||
|
.add("Links", () => <MarkdownView content={MarkdownLinks} basePath="/" />);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { binder } from "@scm-manager/ui-extensions";
|
|||||||
import ErrorBoundary from "./ErrorBoundary";
|
import ErrorBoundary from "./ErrorBoundary";
|
||||||
import SyntaxHighlighter from "./SyntaxHighlighter";
|
import SyntaxHighlighter from "./SyntaxHighlighter";
|
||||||
import MarkdownHeadingRenderer from "./MarkdownHeadingRenderer";
|
import MarkdownHeadingRenderer from "./MarkdownHeadingRenderer";
|
||||||
|
import { create } from "./MarkdownLinkRenderer";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Notification from "./Notification";
|
import Notification from "./Notification";
|
||||||
|
|
||||||
@@ -38,6 +39,8 @@ type Props = RouteComponentProps & {
|
|||||||
renderers?: any;
|
renderers?: any;
|
||||||
skipHtml?: boolean;
|
skipHtml?: boolean;
|
||||||
enableAnchorHeadings?: boolean;
|
enableAnchorHeadings?: boolean;
|
||||||
|
// basePath for markdown links
|
||||||
|
basePath?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const xmlMarkupSample = `\`\`\`xml
|
const xmlMarkupSample = `\`\`\`xml
|
||||||
@@ -97,7 +100,7 @@ class MarkdownView extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { content, renderers, renderContext, enableAnchorHeadings, skipHtml } = this.props;
|
const { content, renderers, renderContext, enableAnchorHeadings, skipHtml, basePath } = this.props;
|
||||||
|
|
||||||
const rendererFactory = binder.getExtension("markdown-renderer-factory");
|
const rendererFactory = binder.getExtension("markdown-renderer-factory");
|
||||||
let rendererList = renderers;
|
let rendererList = renderers;
|
||||||
@@ -114,6 +117,10 @@ class MarkdownView extends React.Component<Props> {
|
|||||||
rendererList.heading = MarkdownHeadingRenderer;
|
rendererList.heading = MarkdownHeadingRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (basePath && !rendererList.link) {
|
||||||
|
rendererList.link = create(basePath);
|
||||||
|
}
|
||||||
|
|
||||||
if (!rendererList.code) {
|
if (!rendererList.code) {
|
||||||
rendererList.code = SyntaxHighlighter;
|
rendererList.code = SyntaxHighlighter;
|
||||||
}
|
}
|
||||||
|
|||||||
46
scm-ui/ui-components/src/__resources__/markdown-links.md.ts
Normal file
46
scm-ui/ui-components/src/__resources__/markdown-links.md.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default `# Links
|
||||||
|
|
||||||
|
Show case for different style of markdown links.
|
||||||
|
Please note that some of the links may not work in storybook,
|
||||||
|
the story is mostly for checking if the links are rendered correct.
|
||||||
|
|
||||||
|
## External
|
||||||
|
|
||||||
|
External Links should be opened in a new tab: [external link](https://scm-manager.org)
|
||||||
|
|
||||||
|
## Anchor
|
||||||
|
|
||||||
|
Anchor Links should be rendered a simple a tag with an href: [anchor link](#sample)
|
||||||
|
|
||||||
|
## Protocol
|
||||||
|
|
||||||
|
Links with a protocol other than http should be rendered a simple a tag with an href e.g.: [mail link](mailto:marvin@hitchhiker.com)
|
||||||
|
|
||||||
|
## Internal
|
||||||
|
|
||||||
|
Internal links should be rendered by react-router: [internal link](/buttons)
|
||||||
|
`;
|
||||||
@@ -33060,6 +33060,7 @@ exports[`Storyshots Layout|Footer Default 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://www.scm-manager.org/"
|
href="https://www.scm-manager.org/"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
SCM-Manager 2.0.0
|
SCM-Manager 2.0.0
|
||||||
@@ -33085,6 +33086,7 @@ exports[`Storyshots Layout|Footer Default 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://www.scm-manager.org/support/"
|
href="https://www.scm-manager.org/support/"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
footer.support.community
|
footer.support.community
|
||||||
@@ -33093,6 +33095,7 @@ exports[`Storyshots Layout|Footer Default 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://cloudogu.com/en/scm-manager-enterprise/"
|
href="https://cloudogu.com/en/scm-manager-enterprise/"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
footer.support.enterprise
|
footer.support.enterprise
|
||||||
@@ -33182,6 +33185,7 @@ exports[`Storyshots Layout|Footer Full 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://www.scm-manager.org/"
|
href="https://www.scm-manager.org/"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
SCM-Manager 2.0.0
|
SCM-Manager 2.0.0
|
||||||
@@ -33190,6 +33194,7 @@ exports[`Storyshots Layout|Footer Full 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
REST API
|
REST API
|
||||||
@@ -33198,6 +33203,7 @@ exports[`Storyshots Layout|Footer Full 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
CLI
|
CLI
|
||||||
@@ -33223,6 +33229,7 @@ exports[`Storyshots Layout|Footer Full 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://www.scm-manager.org/support/"
|
href="https://www.scm-manager.org/support/"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
footer.support.community
|
footer.support.community
|
||||||
@@ -33231,6 +33238,7 @@ exports[`Storyshots Layout|Footer Full 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://cloudogu.com/en/scm-manager-enterprise/"
|
href="https://cloudogu.com/en/scm-manager-enterprise/"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
footer.support.enterprise
|
footer.support.enterprise
|
||||||
@@ -33239,6 +33247,7 @@ exports[`Storyshots Layout|Footer Full 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
FAQ
|
FAQ
|
||||||
@@ -33319,6 +33328,7 @@ exports[`Storyshots Layout|Footer With Avatar 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://www.scm-manager.org/"
|
href="https://www.scm-manager.org/"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
SCM-Manager 2.0.0
|
SCM-Manager 2.0.0
|
||||||
@@ -33344,6 +33354,7 @@ exports[`Storyshots Layout|Footer With Avatar 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://www.scm-manager.org/support/"
|
href="https://www.scm-manager.org/support/"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
footer.support.community
|
footer.support.community
|
||||||
@@ -33352,6 +33363,7 @@ exports[`Storyshots Layout|Footer With Avatar 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://cloudogu.com/en/scm-manager-enterprise/"
|
href="https://cloudogu.com/en/scm-manager-enterprise/"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
footer.support.enterprise
|
footer.support.enterprise
|
||||||
@@ -33436,6 +33448,7 @@ exports[`Storyshots Layout|Footer With Plugin Links 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://www.scm-manager.org/"
|
href="https://www.scm-manager.org/"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
SCM-Manager 2.0.0
|
SCM-Manager 2.0.0
|
||||||
@@ -33444,6 +33457,7 @@ exports[`Storyshots Layout|Footer With Plugin Links 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
REST API
|
REST API
|
||||||
@@ -33452,6 +33466,7 @@ exports[`Storyshots Layout|Footer With Plugin Links 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
CLI
|
CLI
|
||||||
@@ -33477,6 +33492,7 @@ exports[`Storyshots Layout|Footer With Plugin Links 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://www.scm-manager.org/support/"
|
href="https://www.scm-manager.org/support/"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
footer.support.community
|
footer.support.community
|
||||||
@@ -33485,6 +33501,7 @@ exports[`Storyshots Layout|Footer With Plugin Links 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://cloudogu.com/en/scm-manager-enterprise/"
|
href="https://cloudogu.com/en/scm-manager-enterprise/"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
footer.support.enterprise
|
footer.support.enterprise
|
||||||
@@ -33493,6 +33510,7 @@ exports[`Storyshots Layout|Footer With Plugin Links 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
FAQ
|
FAQ
|
||||||
@@ -34372,6 +34390,74 @@ exports[`Storyshots MarkdownView Inline Xml 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Storyshots MarkdownView Links 1`] = `
|
||||||
|
<div
|
||||||
|
className="MarkdownViewstories__Spacing-sc-1hk90gd-0 eqTNCI"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className="content is-word-break"
|
||||||
|
>
|
||||||
|
<h1>
|
||||||
|
Links
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
Show case for different style of markdown links.
|
||||||
|
Please note that some of the links may not work in storybook,
|
||||||
|
the story is mostly for checking if the links are rendered correct.
|
||||||
|
</p>
|
||||||
|
<h2>
|
||||||
|
External
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
External Links should be opened in a new tab:
|
||||||
|
<a
|
||||||
|
href="https://scm-manager.org"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
external link
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<h2>
|
||||||
|
Anchor
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
Anchor Links should be rendered a simple a tag with an href:
|
||||||
|
<a
|
||||||
|
href="/#sample"
|
||||||
|
>
|
||||||
|
anchor link
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<h2>
|
||||||
|
Protocol
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
Links with a protocol other than http should be rendered a simple a tag with an href e.g.:
|
||||||
|
<a
|
||||||
|
href="mailto:marvin@hitchhiker.com"
|
||||||
|
>
|
||||||
|
mail link
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<h2>
|
||||||
|
Internal
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
Internal links should be rendered by react-router:
|
||||||
|
<a
|
||||||
|
href="/buttons"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
internal link
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Storyshots MarkdownView Xml Code Block 1`] = `
|
exports[`Storyshots MarkdownView Xml Code Block 1`] = `
|
||||||
<div
|
<div
|
||||||
className="MarkdownViewstories__Spacing-sc-1hk90gd-0 eqTNCI"
|
className="MarkdownViewstories__Spacing-sc-1hk90gd-0 eqTNCI"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import hitchhiker from "../__resources__/hitchhiker.png";
|
|||||||
// @ts-ignore ignore unknown jpg
|
// @ts-ignore ignore unknown jpg
|
||||||
import marvin from "../__resources__/marvin.jpg";
|
import marvin from "../__resources__/marvin.jpg";
|
||||||
import NavLink from "../navigation/NavLink";
|
import NavLink from "../navigation/NavLink";
|
||||||
import ExternalLink from "../navigation/ExternalLink";
|
import ExternalNavLink from "../navigation/ExternalNavLink";
|
||||||
import { MemoryRouter } from "react-router-dom";
|
import { MemoryRouter } from "react-router-dom";
|
||||||
|
|
||||||
const trillian: Me = {
|
const trillian: Me = {
|
||||||
@@ -50,9 +50,9 @@ const bindAvatar = (binder: Binder, avatar: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const bindLinks = (binder: Binder) => {
|
const bindLinks = (binder: Binder) => {
|
||||||
binder.bind("footer.information", () => <ExternalLink to="#" label="REST API" />);
|
binder.bind("footer.information", () => <ExternalNavLink to="#" label="REST API" />);
|
||||||
binder.bind("footer.information", () => <ExternalLink to="#" label="CLI" />);
|
binder.bind("footer.information", () => <ExternalNavLink to="#" label="CLI" />);
|
||||||
binder.bind("footer.support", () => <ExternalLink to="#" label="FAQ" />);
|
binder.bind("footer.support", () => <ExternalNavLink to="#" label="FAQ" />);
|
||||||
binder.bind("profile.setting", () => <NavLink label="Authorized Keys" to="#" />);
|
binder.bind("profile.setting", () => <NavLink label="Authorized Keys" to="#" />);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import NavLink from "../navigation/NavLink";
|
|||||||
import FooterSection from "./FooterSection";
|
import FooterSection from "./FooterSection";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { EXTENSION_POINT } from "../avatar/Avatar";
|
import { EXTENSION_POINT } from "../avatar/Avatar";
|
||||||
import ExternalLink from "../navigation/ExternalLink";
|
import ExternalNavLink from "../navigation/ExternalNavLink";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -99,12 +99,15 @@ const Footer: FC<Props> = ({ me, version, links }) => {
|
|||||||
<ExtensionPoint name="profile.setting" props={extensionProps} renderAll={true} />
|
<ExtensionPoint name="profile.setting" props={extensionProps} renderAll={true} />
|
||||||
</FooterSection>
|
</FooterSection>
|
||||||
<FooterSection title={<TitleWithIcon title={t("footer.information.title")} icon="info-circle" />}>
|
<FooterSection title={<TitleWithIcon title={t("footer.information.title")} icon="info-circle" />}>
|
||||||
<ExternalLink to="https://www.scm-manager.org/" label={`SCM-Manager ${version}`} />
|
<ExternalNavLink to="https://www.scm-manager.org/" label={`SCM-Manager ${version}`} />
|
||||||
<ExtensionPoint name="footer.information" props={extensionProps} renderAll={true} />
|
<ExtensionPoint name="footer.information" props={extensionProps} renderAll={true} />
|
||||||
</FooterSection>
|
</FooterSection>
|
||||||
<FooterSection title={<TitleWithIcon title={t("footer.support.title")} icon="life-ring" />}>
|
<FooterSection title={<TitleWithIcon title={t("footer.support.title")} icon="life-ring" />}>
|
||||||
<ExternalLink to="https://www.scm-manager.org/support/" label={t("footer.support.community")} />
|
<ExternalNavLink to="https://www.scm-manager.org/support/" label={t("footer.support.community")} />
|
||||||
<ExternalLink to="https://cloudogu.com/en/scm-manager-enterprise/" label={t("footer.support.enterprise")} />
|
<ExternalNavLink
|
||||||
|
to="https://cloudogu.com/en/scm-manager-enterprise/"
|
||||||
|
label={t("footer.support.enterprise")}
|
||||||
|
/>
|
||||||
<ExtensionPoint name="footer.support" props={extensionProps} renderAll={true} />
|
<ExtensionPoint name="footer.support" props={extensionProps} renderAll={true} />
|
||||||
</FooterSection>
|
</FooterSection>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,34 +22,15 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import classNames from "classnames";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
to: string;
|
to: string;
|
||||||
icon?: string;
|
|
||||||
label: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO is it used in the menu? should it use MenuContext for collapse state?
|
const ExternalLink: FC<Props> = ({ to, children }) => (
|
||||||
|
<a href={to} target="_blank" rel="noopener noreferrer">
|
||||||
const ExternalLink: FC<Props> = ({ to, icon, label }) => {
|
{children}
|
||||||
let showIcon;
|
</a>
|
||||||
if (icon) {
|
);
|
||||||
showIcon = (
|
|
||||||
<>
|
|
||||||
<i className={classNames(icon, "fa-fw")} />{" "}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li>
|
|
||||||
<a target="_blank" href={to}>
|
|
||||||
{showIcon}
|
|
||||||
{label}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ExternalLink;
|
export default ExternalLink;
|
||||||
|
|||||||
56
scm-ui/ui-components/src/navigation/ExternalNavLink.tsx
Normal file
56
scm-ui/ui-components/src/navigation/ExternalNavLink.tsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import ExternalLink from "./ExternalLink";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
to: string;
|
||||||
|
icon?: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO is it used in the menu? should it use MenuContext for collapse state?
|
||||||
|
|
||||||
|
const ExternalNavLink: FC<Props> = ({ to, icon, label }) => {
|
||||||
|
let showIcon;
|
||||||
|
if (icon) {
|
||||||
|
showIcon = (
|
||||||
|
<>
|
||||||
|
<i className={classNames(icon, "fa-fw")} />{" "}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
<ExternalLink to={to}>
|
||||||
|
{showIcon}
|
||||||
|
{label}
|
||||||
|
</ExternalLink>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExternalNavLink;
|
||||||
@@ -33,3 +33,5 @@ export { default as PrimaryNavigationLink } from "./PrimaryNavigationLink";
|
|||||||
export { default as SecondaryNavigation } from "./SecondaryNavigation";
|
export { default as SecondaryNavigation } from "./SecondaryNavigation";
|
||||||
export { MenuContext, StateMenuContextProvider } from "./MenuContext";
|
export { MenuContext, StateMenuContextProvider } from "./MenuContext";
|
||||||
export { default as SecondaryNavigationItem } from "./SecondaryNavigationItem";
|
export { default as SecondaryNavigationItem } from "./SecondaryNavigationItem";
|
||||||
|
export { default as ExternalLink } from "./ExternalLink";
|
||||||
|
export { default as ExternalNavLink } from "./ExternalNavLink";
|
||||||
|
|||||||
@@ -29,13 +29,14 @@ import styled from "styled-components";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
file: File;
|
file: File;
|
||||||
|
basePath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MarkdownContent = styled.div`
|
const MarkdownContent = styled.div`
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const MarkdownViewer: FC<Props> = ({ file }) => {
|
const MarkdownViewer: FC<Props> = ({ file, basePath }) => {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<Error | undefined>(undefined);
|
const [error, setError] = useState<Error | undefined>(undefined);
|
||||||
const [content, setContent] = useState("");
|
const [content, setContent] = useState("");
|
||||||
@@ -62,7 +63,7 @@ const MarkdownViewer: FC<Props> = ({ file }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MarkdownContent>
|
<MarkdownContent>
|
||||||
<MarkdownView content={content} />
|
<MarkdownView content={content} basePath={basePath} />
|
||||||
</MarkdownContent>
|
</MarkdownContent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,13 +21,13 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import React, { FC, useState } from "react";
|
import React, {FC, useState} from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import MarkdownViewer from "./MarkdownViewer";
|
import MarkdownViewer from "./MarkdownViewer";
|
||||||
import SourcecodeViewer from "./SourcecodeViewer";
|
import SourcecodeViewer from "./SourcecodeViewer";
|
||||||
import { File } from "@scm-manager/ui-types";
|
import {File} from "@scm-manager/ui-types";
|
||||||
import { Button } from "@scm-manager/ui-components";
|
import {Button} from "@scm-manager/ui-components";
|
||||||
import { useTranslation } from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
|
|
||||||
const ToggleButton = styled(Button)`
|
const ToggleButton = styled(Button)`
|
||||||
max-width: 1rem;
|
max-width: 1rem;
|
||||||
@@ -43,10 +43,11 @@ const Container = styled.div`
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
file: File;
|
file: File;
|
||||||
|
basePath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SwitchableMarkdownViewer: FC<Props> = ({ file }) => {
|
const SwitchableMarkdownViewer: FC<Props> = ({file, basePath}) => {
|
||||||
const { t } = useTranslation("repos");
|
const {t} = useTranslation("repos");
|
||||||
const [renderMarkdown, setRenderMarkdown] = useState(true);
|
const [renderMarkdown, setRenderMarkdown] = useState(true);
|
||||||
|
|
||||||
const toggleMarkdown = () => {
|
const toggleMarkdown = () => {
|
||||||
@@ -64,9 +65,10 @@ const SwitchableMarkdownViewer: FC<Props> = ({ file }) => {
|
|||||||
: t("sources.content.toggleButton.showMarkdown")
|
: t("sources.content.toggleButton.showMarkdown")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<i className="fab fa-markdown" />
|
<i className="fab fa-markdown"/>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
{renderMarkdown ? <MarkdownViewer file={file} /> : <SourcecodeViewer file={file} language={"MARKDOWN"} />}
|
{renderMarkdown ? <MarkdownViewer file={file} basePath={basePath}/> :
|
||||||
|
<SourcecodeViewer file={file} language={"MARKDOWN"}/>}
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -76,13 +76,19 @@ class SourcesView extends React.Component<Props, State> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createBasePath() {
|
||||||
|
const { repository, revision } = this.props;
|
||||||
|
return `/repo/${repository.namespace}/${repository.name}/code/sources/${revision}/`;
|
||||||
|
}
|
||||||
|
|
||||||
showSources() {
|
showSources() {
|
||||||
const { file, revision } = this.props;
|
const { file, revision } = this.props;
|
||||||
const { contentType, language } = this.state;
|
const { contentType, language } = this.state;
|
||||||
|
const basePath = this.createBasePath();
|
||||||
if (contentType.startsWith("image/")) {
|
if (contentType.startsWith("image/")) {
|
||||||
return <ImageViewer file={file} />;
|
return <ImageViewer file={file} />;
|
||||||
} else if (contentType.includes("markdown")) {
|
} else if (contentType.includes("markdown")) {
|
||||||
return <SwitchableMarkdownViewer file={file} />;
|
return <SwitchableMarkdownViewer file={file} basePath={basePath} />;
|
||||||
} else if (language) {
|
} else if (language) {
|
||||||
return <SourcecodeViewer file={file} language={language} />;
|
return <SourcecodeViewer file={file} language={language} />;
|
||||||
} else if (contentType.startsWith("text/")) {
|
} else if (contentType.startsWith("text/")) {
|
||||||
@@ -94,7 +100,8 @@ class SourcesView extends React.Component<Props, State> {
|
|||||||
props={{
|
props={{
|
||||||
file,
|
file,
|
||||||
contentType,
|
contentType,
|
||||||
revision
|
revision,
|
||||||
|
basePath
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DownloadViewer file={file} />
|
<DownloadViewer file={file} />
|
||||||
|
|||||||
@@ -146,6 +146,12 @@
|
|||||||
<version>${jackson.version}</version>
|
<version>${jackson.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.ws.rs</groupId>
|
||||||
|
<artifactId>javax.ws.rs-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<!-- rest api -->
|
<!-- rest api -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -191,9 +197,9 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.el</groupId>
|
<groupId>jakarta.xml.bind</groupId>
|
||||||
<artifactId>javax.el-api</artifactId>
|
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>${jaxb.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -202,16 +208,10 @@
|
|||||||
<version>3.0.1-b11</version>
|
<version>3.0.1-b11</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>javax.xml.bind</groupId>
|
|
||||||
<artifactId>jaxb-api</artifactId>
|
|
||||||
<version>2.3.1</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.glassfish.jaxb</groupId>
|
<groupId>org.glassfish.jaxb</groupId>
|
||||||
<artifactId>jaxb-runtime</artifactId>
|
<artifactId>jaxb-runtime</artifactId>
|
||||||
<version>2.3.0</version>
|
<version>${jaxb.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- injection -->
|
<!-- injection -->
|
||||||
@@ -438,13 +438,6 @@
|
|||||||
|
|
||||||
<!-- global excludes -->
|
<!-- global excludes -->
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>commons-logging</groupId>
|
|
||||||
<artifactId>commons-logging</artifactId>
|
|
||||||
<version>1.1.3</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
@@ -654,6 +647,20 @@
|
|||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.basepom.maven</groupId>
|
||||||
|
<artifactId>duplicate-finder-maven-plugin</artifactId>
|
||||||
|
<version>1.3.0</version>
|
||||||
|
<configuration>
|
||||||
|
<printEqualFiles>false</printEqualFiles>
|
||||||
|
<failBuildInCaseOfDifferentContentConflict>false</failBuildInCaseOfDifferentContentConflict>
|
||||||
|
<failBuildInCaseOfEqualContentConflict>false</failBuildInCaseOfEqualContentConflict>
|
||||||
|
<failBuildInCaseOfConflict>false</failBuildInCaseOfConflict>
|
||||||
|
<checkCompileClasspath>true</checkCompileClasspath>
|
||||||
|
<checkRuntimeClasspath>true</checkRuntimeClasspath>
|
||||||
|
<checkTestClasspath>false</checkTestClasspath>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
|
||||||
<finalName>scm-webapp</finalName>
|
<finalName>scm-webapp</finalName>
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.api;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
import sonia.scm.api.v2.resources.ErrorDto;
|
||||||
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.ext.ExceptionMapper;
|
||||||
|
import javax.ws.rs.ext.Provider;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
@Provider
|
||||||
|
public class InvalidFormatExceptionMapper implements ExceptionMapper<InvalidFormatException> {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(InvalidFormatExceptionMapper.class);
|
||||||
|
|
||||||
|
private static final String ERROR_CODE = "2qRyyaVcJ1";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response toResponse(InvalidFormatException exception) {
|
||||||
|
LOG.trace("got invalid format in json: {}", exception.getMessage());
|
||||||
|
ErrorDto errorDto = new ErrorDto();
|
||||||
|
errorDto.setMessage("invalid format in json content: " + exception.getMessage());
|
||||||
|
errorDto.setContext(Collections.emptyList());
|
||||||
|
errorDto.setErrorCode(ERROR_CODE);
|
||||||
|
errorDto.setTransactionId(MDC.get("transaction_id"));
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(errorDto)
|
||||||
|
.type(VndMediaType.ERROR_TYPE)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,6 @@ package sonia.scm.api.v2.resources;
|
|||||||
|
|
||||||
import com.github.sdorra.spotter.ContentTypes;
|
import com.github.sdorra.spotter.ContentTypes;
|
||||||
import com.github.sdorra.spotter.Language;
|
import com.github.sdorra.spotter.Language;
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.api.DiffFile;
|
import sonia.scm.repository.api.DiffFile;
|
||||||
@@ -42,7 +41,7 @@ import java.util.OptionalInt;
|
|||||||
import static de.otto.edison.hal.Links.linkingTo;
|
import static de.otto.edison.hal.Links.linkingTo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO conflicts, copy and rename
|
* TODO conflicts
|
||||||
*/
|
*/
|
||||||
class DiffResultToDiffResultDtoMapper {
|
class DiffResultToDiffResultDtoMapper {
|
||||||
|
|
||||||
@@ -83,18 +82,29 @@ class DiffResultToDiffResultDtoMapper {
|
|||||||
String oldPath = file.getOldPath();
|
String oldPath = file.getOldPath();
|
||||||
|
|
||||||
String path;
|
String path;
|
||||||
if (isFilePath(newPath) && isFileNull(oldPath)) {
|
switch (file.getChangeType()) {
|
||||||
path = newPath;
|
case ADD:
|
||||||
dto.setType("add");
|
path = newPath;
|
||||||
} else if (isFileNull(newPath) && isFilePath(oldPath)) {
|
dto.setType("add");
|
||||||
path = oldPath;
|
break;
|
||||||
dto.setType("delete");
|
case DELETE:
|
||||||
} else if (isFilePath(newPath) && isFilePath(oldPath)) {
|
path = oldPath;
|
||||||
path = newPath;
|
dto.setType("delete");
|
||||||
dto.setType("modify");
|
break;
|
||||||
} else {
|
case RENAME:
|
||||||
// TODO copy and rename?
|
path = newPath;
|
||||||
throw new IllegalStateException("no file without path");
|
dto.setType("rename");
|
||||||
|
break;
|
||||||
|
case MODIFY:
|
||||||
|
path = newPath;
|
||||||
|
dto.setType("modify");
|
||||||
|
break;
|
||||||
|
case COPY:
|
||||||
|
path = newPath;
|
||||||
|
dto.setType("copy");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("unknown change type: " + file.getChangeType());
|
||||||
}
|
}
|
||||||
|
|
||||||
dto.setNewPath(newPath);
|
dto.setNewPath(newPath);
|
||||||
@@ -116,14 +126,6 @@ class DiffResultToDiffResultDtoMapper {
|
|||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isFilePath(String path) {
|
|
||||||
return !isFileNull(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isFileNull(String path) {
|
|
||||||
return Strings.isNullOrEmpty(path) || "/dev/null".equals(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DiffResultDto.HunkDto mapHunk(Hunk hunk) {
|
private DiffResultDto.HunkDto mapHunk(Hunk hunk) {
|
||||||
DiffResultDto.HunkDto dto = new DiffResultDto.HunkDto();
|
DiffResultDto.HunkDto dto = new DiffResultDto.HunkDto();
|
||||||
dto.setContent(hunk.getRawHeader());
|
dto.setContent(hunk.getRawHeader());
|
||||||
|
|||||||
@@ -54,10 +54,21 @@ public class ModificationsDto extends HalRepresentation {
|
|||||||
*/
|
*/
|
||||||
private List<String> removed;
|
private List<String> removed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping of renamed files
|
||||||
|
*/
|
||||||
|
private List<RenamedDto> renamed;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||||
protected HalRepresentation add(Links links) {
|
protected HalRepresentation add(Links links) {
|
||||||
return super.add(links);
|
return super.add(links);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public static class RenamedDto {
|
||||||
|
private String oldPath;
|
||||||
|
private String newPath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,11 @@ import org.mapstruct.Context;
|
|||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
import org.mapstruct.Mapping;
|
import org.mapstruct.Mapping;
|
||||||
import org.mapstruct.MappingTarget;
|
import org.mapstruct.MappingTarget;
|
||||||
|
import sonia.scm.repository.Added;
|
||||||
import sonia.scm.repository.Modifications;
|
import sonia.scm.repository.Modifications;
|
||||||
|
import sonia.scm.repository.Modified;
|
||||||
|
import sonia.scm.repository.Removed;
|
||||||
|
import sonia.scm.repository.Renamed;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -52,4 +56,18 @@ public abstract class ModificationsToDtoMapper {
|
|||||||
.self(resourceLinks.modifications().self(repository.getNamespace(), repository.getName(), target.getRevision()));
|
.self(resourceLinks.modifications().self(repository.getNamespace(), repository.getName(), target.getRevision()));
|
||||||
target.add(linksBuilder.build());
|
target.add(linksBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String map(Added added) {
|
||||||
|
return added.getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
String map(Removed removed) {
|
||||||
|
return removed.getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
String map(Modified modified) {
|
||||||
|
return modified.getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract ModificationsDto.RenamedDto map(Renamed renamed);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,6 +215,10 @@
|
|||||||
"8nRuFzjss1": {
|
"8nRuFzjss1": {
|
||||||
"displayName": "Fehler beim Löschen falscher Downloads",
|
"displayName": "Fehler beim Löschen falscher Downloads",
|
||||||
"description": "Ein fehlerhaft heruntergeladenes Plugin konnte nicht gelöscht werden. Bitte prüfen Sie die Server Logs und löschen die Datei manuell."
|
"description": "Ein fehlerhaft heruntergeladenes Plugin konnte nicht gelöscht werden. Bitte prüfen Sie die Server Logs und löschen die Datei manuell."
|
||||||
|
},
|
||||||
|
"2qRyyaVcJ1": {
|
||||||
|
"displayName": "Ungültig formatiertes Element",
|
||||||
|
"description": "Die Eingabe beinhaltete unfültige Formate. Bitte prüfen Sie die Server Logs für genauere Informationen."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"namespaceStrategies": {
|
"namespaceStrategies": {
|
||||||
|
|||||||
@@ -215,6 +215,10 @@
|
|||||||
"8nRuFzjss1": {
|
"8nRuFzjss1": {
|
||||||
"displayName": "Error while cleaning up failed plugin",
|
"displayName": "Error while cleaning up failed plugin",
|
||||||
"description": "A failed plugin download could not be removed correctly. Please check the server log and remove the plugin manually."
|
"description": "A failed plugin download could not be removed correctly. Please check the server log and remove the plugin manually."
|
||||||
|
},
|
||||||
|
"2qRyyaVcJ1": {
|
||||||
|
"displayName": "Invalid format in element",
|
||||||
|
"description": "The input had some invalid formats. Please check the server log for further information."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"namespaceStrategies": {
|
"namespaceStrategies": {
|
||||||
|
|||||||
@@ -33,16 +33,23 @@ import org.junit.runner.RunWith;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import javax.servlet.ReadListener;
|
||||||
import javax.servlet.ServletInputStream;
|
import javax.servlet.ServletInputStream;
|
||||||
import javax.servlet.ServletOutputStream;
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.WriteListener;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.util.*;
|
import java.util.Collection;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
@@ -173,7 +180,7 @@ public class ProxyPushStateDispatcherTest {
|
|||||||
|
|
||||||
private class DevServletInputStream extends ServletInputStream {
|
private class DevServletInputStream extends ServletInputStream {
|
||||||
|
|
||||||
private InputStream inputStream;
|
private ByteArrayInputStream inputStream;
|
||||||
|
|
||||||
private DevServletInputStream(String content) {
|
private DevServletInputStream(String content) {
|
||||||
inputStream = new ByteArrayInputStream(content.getBytes(Charsets.UTF_8));
|
inputStream = new ByteArrayInputStream(content.getBytes(Charsets.UTF_8));
|
||||||
@@ -183,6 +190,20 @@ public class ProxyPushStateDispatcherTest {
|
|||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
return inputStream.read();
|
return inputStream.read();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReady() {
|
||||||
|
return inputStream.available() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished() {
|
||||||
|
return inputStream.available() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReadListener(ReadListener readListener) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DevServletOutputStream extends ServletOutputStream {
|
private class DevServletOutputStream extends ServletOutputStream {
|
||||||
@@ -193,6 +214,15 @@ public class ProxyPushStateDispatcherTest {
|
|||||||
public void write(int b) {
|
public void write(int b) {
|
||||||
stream.write(b);
|
stream.write(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReady() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWriteListener(WriteListener writeListener) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ package sonia.scm;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
@@ -36,14 +36,14 @@ import java.util.Properties;
|
|||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith({MockitoExtension.class, TempDirectory.class})
|
@ExtendWith({MockitoExtension.class})
|
||||||
class ScmLogFilePropertyDefinerTest {
|
class ScmLogFilePropertyDefinerTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private SCMContextProvider context;
|
private SCMContextProvider context;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnPath(@TempDirectory.TempDir Path tempDir) {
|
void shouldReturnPath(@TempDir Path tempDir) {
|
||||||
when(context.getBaseDirectory()).thenReturn(tempDir.toFile());
|
when(context.getBaseDirectory()).thenReturn(tempDir.toFile());
|
||||||
ScmLogFilePropertyDefiner definer = builder().create();
|
ScmLogFilePropertyDefiner definer = builder().create();
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ class ScmLogFilePropertyDefinerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnOsxPath(@TempDirectory.TempDir Path tempDir) {
|
void shouldReturnOsxPath(@TempDir Path tempDir) {
|
||||||
ScmLogFilePropertyDefiner definer = builder()
|
ScmLogFilePropertyDefiner definer = builder()
|
||||||
.withOs("Mac OS X")
|
.withOs("Mac OS X")
|
||||||
.withUserHome(tempDir.toAbsolutePath().toString())
|
.withUserHome(tempDir.toAbsolutePath().toString())
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import sonia.scm.plugin.PluginLoader;
|
|||||||
import sonia.scm.plugin.UberWebResourceLoader;
|
import sonia.scm.plugin.UberWebResourceLoader;
|
||||||
|
|
||||||
import javax.servlet.ServletOutputStream;
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.WriteListener;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -152,6 +153,15 @@ public class WebResourceServletTest {
|
|||||||
public void write(int b) {
|
public void write(int b) {
|
||||||
buffer.write(b);
|
buffer.write(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReady() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWriteListener(WriteListener writeListener) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.api;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.mock.MockDispatcherFactory;
|
||||||
|
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||||
|
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||||
|
import org.jboss.resteasy.spi.Dispatcher;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class InvalidFormatExceptionMapperTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMapInvalidFormatExceptionDueToInvalidEnum() throws URISyntaxException, UnsupportedEncodingException {
|
||||||
|
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
|
||||||
|
dispatcher.getRegistry().addSingletonResource(new SimpleResource());
|
||||||
|
dispatcher.getProviderFactory().registerProvider(InvalidFormatExceptionMapper.class);
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.post("/")
|
||||||
|
.contentType("application/json")
|
||||||
|
.content("{\"e\": \"NONE\"}".getBytes());
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertThat(response.getStatus()).isEqualTo(400);
|
||||||
|
assertThat(response.getContentAsString()).contains("2qRyyaVcJ1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Path("/")
|
||||||
|
static class SimpleResource {
|
||||||
|
@POST
|
||||||
|
public void post(ObjectWithEnum o) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ObjectWithEnum {
|
||||||
|
public Enum e;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Enum {
|
||||||
|
ONE, TWO
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,11 +35,13 @@ import sonia.scm.repository.api.DiffResult;
|
|||||||
import sonia.scm.repository.api.Hunk;
|
import sonia.scm.repository.api.Hunk;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
|
|
||||||
import static java.net.URI.create;
|
import static java.net.URI.create;
|
||||||
|
import static java.util.Collections.emptyIterator;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -60,6 +62,8 @@ class DiffResultToDiffResultDtoMapperTest {
|
|||||||
assertAddedFile(files.get(0), "A.java", "abc", "java");
|
assertAddedFile(files.get(0), "A.java", "abc", "java");
|
||||||
assertModifiedFile(files.get(1), "B.ts", "abc", "def", "typescript");
|
assertModifiedFile(files.get(1), "B.ts", "abc", "def", "typescript");
|
||||||
assertDeletedFile(files.get(2), "C.go", "ghi", "golang");
|
assertDeletedFile(files.get(2), "C.go", "ghi", "golang");
|
||||||
|
assertRenamedFile(files.get(3), "typo.ts", "okay.ts", "def", "fixed", "typescript");
|
||||||
|
assertCopiedFile(files.get(4), "good.ts", "better.ts", "def", "fixed", "typescript");
|
||||||
|
|
||||||
DiffResultDto.HunkDto hunk = files.get(1).getHunks().get(0);
|
DiffResultDto.HunkDto hunk = files.get(1).getHunks().get(0);
|
||||||
assertHunk(hunk, "@@ -3,4 1,2 @@", 1, 2, 3, 4);
|
assertHunk(hunk, "@@ -3,4 1,2 @@", 1, 2, 3, 4);
|
||||||
@@ -104,7 +108,9 @@ class DiffResultToDiffResultDtoMapperTest {
|
|||||||
deletedLine("c", 3)
|
deletedLine("c", 3)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
deletedFile("C.go", "ghi")
|
deletedFile("C.go", "ghi"),
|
||||||
|
renamedFile("okay.ts", "typo.ts", "fixed", "def"),
|
||||||
|
copiedFile("better.ts", "good.ts", "fixed", "def")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,6 +167,24 @@ class DiffResultToDiffResultDtoMapperTest {
|
|||||||
assertThat(file.getLanguage()).isEqualTo(language);
|
assertThat(file.getLanguage()).isEqualTo(language);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertRenamedFile(DiffResultDto.FileDto file, String oldPath, String newPath, String oldRevision, String newRevision, String language) {
|
||||||
|
assertThat(file.getOldPath()).isEqualTo(oldPath);
|
||||||
|
assertThat(file.getNewPath()).isEqualTo(newPath);
|
||||||
|
assertThat(file.getOldRevision()).isEqualTo(oldRevision);
|
||||||
|
assertThat(file.getNewRevision()).isEqualTo(newRevision);
|
||||||
|
assertThat(file.getType()).isEqualTo("rename");
|
||||||
|
assertThat(file.getLanguage()).isEqualTo(language);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertCopiedFile(DiffResultDto.FileDto file, String oldPath, String newPath, String oldRevision, String newRevision, String language) {
|
||||||
|
assertThat(file.getOldPath()).isEqualTo(oldPath);
|
||||||
|
assertThat(file.getNewPath()).isEqualTo(newPath);
|
||||||
|
assertThat(file.getOldRevision()).isEqualTo(oldRevision);
|
||||||
|
assertThat(file.getNewRevision()).isEqualTo(newRevision);
|
||||||
|
assertThat(file.getType()).isEqualTo("copy");
|
||||||
|
assertThat(file.getLanguage()).isEqualTo(language);
|
||||||
|
}
|
||||||
|
|
||||||
private DiffResult result(DiffFile... files) {
|
private DiffResult result(DiffFile... files) {
|
||||||
DiffResult result = mock(DiffResult.class);
|
DiffResult result = mock(DiffResult.class);
|
||||||
when(result.iterator()).thenReturn(Arrays.asList(files).iterator());
|
when(result.iterator()).thenReturn(Arrays.asList(files).iterator());
|
||||||
@@ -171,6 +195,7 @@ class DiffResultToDiffResultDtoMapperTest {
|
|||||||
DiffFile file = mock(DiffFile.class);
|
DiffFile file = mock(DiffFile.class);
|
||||||
when(file.getNewPath()).thenReturn(path);
|
when(file.getNewPath()).thenReturn(path);
|
||||||
when(file.getNewRevision()).thenReturn(revision);
|
when(file.getNewRevision()).thenReturn(revision);
|
||||||
|
when(file.getChangeType()).thenReturn(DiffFile.ChangeType.ADD);
|
||||||
when(file.iterator()).thenReturn(Arrays.asList(hunks).iterator());
|
when(file.iterator()).thenReturn(Arrays.asList(hunks).iterator());
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
@@ -179,6 +204,7 @@ class DiffResultToDiffResultDtoMapperTest {
|
|||||||
DiffFile file = mock(DiffFile.class);
|
DiffFile file = mock(DiffFile.class);
|
||||||
when(file.getOldPath()).thenReturn(path);
|
when(file.getOldPath()).thenReturn(path);
|
||||||
when(file.getOldRevision()).thenReturn(revision);
|
when(file.getOldRevision()).thenReturn(revision);
|
||||||
|
when(file.getChangeType()).thenReturn(DiffFile.ChangeType.DELETE);
|
||||||
when(file.iterator()).thenReturn(Arrays.asList(hunks).iterator());
|
when(file.iterator()).thenReturn(Arrays.asList(hunks).iterator());
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
@@ -189,10 +215,33 @@ class DiffResultToDiffResultDtoMapperTest {
|
|||||||
when(file.getNewRevision()).thenReturn(newRevision);
|
when(file.getNewRevision()).thenReturn(newRevision);
|
||||||
when(file.getOldPath()).thenReturn(path);
|
when(file.getOldPath()).thenReturn(path);
|
||||||
when(file.getOldRevision()).thenReturn(oldRevision);
|
when(file.getOldRevision()).thenReturn(oldRevision);
|
||||||
|
when(file.getChangeType()).thenReturn(DiffFile.ChangeType.MODIFY);
|
||||||
when(file.iterator()).thenReturn(Arrays.asList(hunks).iterator());
|
when(file.iterator()).thenReturn(Arrays.asList(hunks).iterator());
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DiffFile renamedFile(String newPath, String oldPath, String newRevision, String oldRevision) {
|
||||||
|
DiffFile file = mock(DiffFile.class);
|
||||||
|
when(file.getNewPath()).thenReturn(newPath);
|
||||||
|
when(file.getNewRevision()).thenReturn(newRevision);
|
||||||
|
when(file.getOldPath()).thenReturn(oldPath);
|
||||||
|
when(file.getOldRevision()).thenReturn(oldRevision);
|
||||||
|
when(file.getChangeType()).thenReturn(DiffFile.ChangeType.RENAME);
|
||||||
|
when(file.iterator()).thenReturn(emptyIterator());
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DiffFile copiedFile(String newPath, String oldPath, String newRevision, String oldRevision) {
|
||||||
|
DiffFile file = mock(DiffFile.class);
|
||||||
|
when(file.getNewPath()).thenReturn(newPath);
|
||||||
|
when(file.getNewRevision()).thenReturn(newRevision);
|
||||||
|
when(file.getOldPath()).thenReturn(oldPath);
|
||||||
|
when(file.getOldRevision()).thenReturn(oldRevision);
|
||||||
|
when(file.getChangeType()).thenReturn(DiffFile.ChangeType.COPY);
|
||||||
|
when(file.iterator()).thenReturn(emptyIterator());
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
private Hunk hunk(String rawHeader, int newStart, int newLineCount, int oldStart, int oldLineCount, DiffLine... lines) {
|
private Hunk hunk(String rawHeader, int newStart, int newLineCount, int oldStart, int oldLineCount, DiffLine... lines) {
|
||||||
Hunk hunk = mock(Hunk.class);
|
Hunk hunk = mock(Hunk.class);
|
||||||
when(hunk.getRawHeader()).thenReturn(rawHeader);
|
when(hunk.getRawHeader()).thenReturn(rawHeader);
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import org.apache.shiro.subject.Subject;
|
|||||||
import org.apache.shiro.subject.support.SubjectThreadState;
|
import org.apache.shiro.subject.support.SubjectThreadState;
|
||||||
import org.apache.shiro.util.ThreadContext;
|
import org.apache.shiro.util.ThreadContext;
|
||||||
import org.apache.shiro.util.ThreadState;
|
import org.apache.shiro.util.ThreadState;
|
||||||
import org.assertj.core.util.Lists;
|
|
||||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||||
import org.jboss.resteasy.mock.MockHttpResponse;
|
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
@@ -40,9 +39,12 @@ import org.junit.runner.RunWith;
|
|||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
import sonia.scm.repository.Added;
|
||||||
import sonia.scm.repository.InternalRepositoryException;
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
import sonia.scm.repository.Modifications;
|
import sonia.scm.repository.Modifications;
|
||||||
import sonia.scm.repository.NamespaceAndName;
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
|
import sonia.scm.repository.Removed;
|
||||||
|
import sonia.scm.repository.Modified;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.api.ModificationsCommandBuilder;
|
import sonia.scm.repository.api.ModificationsCommandBuilder;
|
||||||
import sonia.scm.repository.api.RepositoryService;
|
import sonia.scm.repository.api.RepositoryService;
|
||||||
@@ -141,7 +143,6 @@ public class ModificationsResourceTest extends RepositoryTestBase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldGetModifications() throws Exception {
|
public void shouldGetModifications() throws Exception {
|
||||||
Modifications modifications = new Modifications();
|
|
||||||
String revision = "revision";
|
String revision = "revision";
|
||||||
String addedFile_1 = "a.txt";
|
String addedFile_1 = "a.txt";
|
||||||
String addedFile_2 = "b.txt";
|
String addedFile_2 = "b.txt";
|
||||||
@@ -149,10 +150,13 @@ public class ModificationsResourceTest extends RepositoryTestBase {
|
|||||||
String modifiedFile_2 = "c.txt";
|
String modifiedFile_2 = "c.txt";
|
||||||
String removedFile_1 = "e.txt";
|
String removedFile_1 = "e.txt";
|
||||||
String removedFile_2 = "f.txt";
|
String removedFile_2 = "f.txt";
|
||||||
modifications.setRevision(revision);
|
Modifications modifications = new Modifications(revision,
|
||||||
modifications.setAdded(Lists.newArrayList(addedFile_1, addedFile_2));
|
new Added(addedFile_1),
|
||||||
modifications.setModified(Lists.newArrayList(modifiedFile_1, modifiedFile_2));
|
new Added(addedFile_2),
|
||||||
modifications.setRemoved(Lists.newArrayList(removedFile_1, removedFile_2));
|
new Modified(modifiedFile_1),
|
||||||
|
new Modified(modifiedFile_2),
|
||||||
|
new Removed(removedFile_1),
|
||||||
|
new Removed(removedFile_2));
|
||||||
when(modificationsCommandBuilder.getModifications()).thenReturn(modifications);
|
when(modificationsCommandBuilder.getModifications()).thenReturn(modifications);
|
||||||
when(modificationsCommandBuilder.revision(eq(revision))).thenReturn(modificationsCommandBuilder);
|
when(modificationsCommandBuilder.revision(eq(revision))).thenReturn(modificationsCommandBuilder);
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import sonia.scm.Stage;
|
|||||||
import sonia.scm.event.ScmEventBus;
|
import sonia.scm.event.ScmEventBus;
|
||||||
import sonia.scm.event.ScmTestEventBus;
|
import sonia.scm.event.ScmTestEventBus;
|
||||||
|
|
||||||
|
import javax.servlet.ReadListener;
|
||||||
import javax.servlet.ServletInputStream;
|
import javax.servlet.ServletInputStream;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
@@ -128,6 +129,21 @@ public class RestartServletTest {
|
|||||||
|
|
||||||
private ServletInputStream createServletInputStream(final InputStream inputStream) {
|
private ServletInputStream createServletInputStream(final InputStream inputStream) {
|
||||||
return new ServletInputStream() {
|
return new ServletInputStream() {
|
||||||
|
@Override
|
||||||
|
public boolean isFinished() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReady() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReadListener(ReadListener readListener) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
return inputStream.read();
|
return inputStream.read();
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ package sonia.scm.lifecycle;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
@@ -41,7 +41,7 @@ import java.nio.file.Paths;
|
|||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
|
||||||
@ExtendWith({MockitoExtension.class, TempDirectory.class})
|
@ExtendWith({MockitoExtension.class})
|
||||||
class VersionsTest {
|
class VersionsTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
@@ -51,7 +51,7 @@ class VersionsTest {
|
|||||||
private Versions versions;
|
private Versions versions;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnTrueForVersionsPreviousTo160(@TempDirectory.TempDir Path directory) throws IOException {
|
void shouldReturnTrueForVersionsPreviousTo160(@TempDir Path directory) throws IOException {
|
||||||
setVersion(directory, "1.59");
|
setVersion(directory, "1.59");
|
||||||
assertThat(versions.isPreviousVersionTooOld()).isTrue();
|
assertThat(versions.isPreviousVersionTooOld()).isTrue();
|
||||||
|
|
||||||
@@ -60,19 +60,19 @@ class VersionsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnFalseForVersion160(@TempDirectory.TempDir Path directory) throws IOException {
|
void shouldReturnFalseForVersion160(@TempDir Path directory) throws IOException {
|
||||||
setVersion(directory, "1.60");
|
setVersion(directory, "1.60");
|
||||||
assertThat(versions.isPreviousVersionTooOld()).isFalse();
|
assertThat(versions.isPreviousVersionTooOld()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldNotFailIfVersionContainsLineBreak(@TempDirectory.TempDir Path directory) throws IOException {
|
void shouldNotFailIfVersionContainsLineBreak(@TempDir Path directory) throws IOException {
|
||||||
setVersion(directory, "1.59\n");
|
setVersion(directory, "1.59\n");
|
||||||
assertThat(versions.isPreviousVersionTooOld()).isTrue();
|
assertThat(versions.isPreviousVersionTooOld()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnFalseForVersionsNewerAs160(@TempDirectory.TempDir Path directory) throws IOException {
|
void shouldReturnFalseForVersionsNewerAs160(@TempDir Path directory) throws IOException {
|
||||||
setVersion(directory, "1.61");
|
setVersion(directory, "1.61");
|
||||||
assertThat(versions.isPreviousVersionTooOld()).isFalse();
|
assertThat(versions.isPreviousVersionTooOld()).isFalse();
|
||||||
|
|
||||||
@@ -81,13 +81,13 @@ class VersionsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnFalseForNonExistingVersionFile(@TempDirectory.TempDir Path directory) {
|
void shouldReturnFalseForNonExistingVersionFile(@TempDir Path directory) {
|
||||||
setVersionFile(directory.resolve("version.txt"));
|
setVersionFile(directory.resolve("version.txt"));
|
||||||
assertThat(versions.isPreviousVersionTooOld()).isFalse();
|
assertThat(versions.isPreviousVersionTooOld()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldWriteNewVersion(@TempDirectory.TempDir Path directory) {
|
void shouldWriteNewVersion(@TempDir Path directory) {
|
||||||
Path config = directory.resolve("config");
|
Path config = directory.resolve("config");
|
||||||
doReturn(config).when(contextProvider).resolve(Paths.get("config"));
|
doReturn(config).when(contextProvider).resolve(Paths.get("config"));
|
||||||
doReturn("2.0.0").when(contextProvider).getVersion();
|
doReturn("2.0.0").when(contextProvider).getVersion();
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Captor;
|
import org.mockito.Captor;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
@@ -48,7 +48,6 @@ import sonia.scm.lifecycle.Restarter;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -56,7 +55,6 @@ import static java.util.Arrays.asList;
|
|||||||
import static java.util.Collections.singleton;
|
import static java.util.Collections.singleton;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.in;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.Mockito.any;
|
import static org.mockito.Mockito.any;
|
||||||
import static org.mockito.Mockito.doNothing;
|
import static org.mockito.Mockito.doNothing;
|
||||||
@@ -72,7 +70,6 @@ import static sonia.scm.plugin.PluginTestHelper.createAvailable;
|
|||||||
import static sonia.scm.plugin.PluginTestHelper.createInstalled;
|
import static sonia.scm.plugin.PluginTestHelper.createInstalled;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
class DefaultPluginManagerTest {
|
class DefaultPluginManagerTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
@@ -372,7 +369,7 @@ class DefaultPluginManagerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCreateUninstallFile(@TempDirectory.TempDir Path temp) {
|
void shouldCreateUninstallFile(@TempDir Path temp) {
|
||||||
InstalledPlugin mailPlugin = createInstalled("scm-mail-plugin");
|
InstalledPlugin mailPlugin = createInstalled("scm-mail-plugin");
|
||||||
when(mailPlugin.getDirectory()).thenReturn(temp);
|
when(mailPlugin.getDirectory()).thenReturn(temp);
|
||||||
|
|
||||||
@@ -384,7 +381,7 @@ class DefaultPluginManagerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldMarkPluginForUninstall(@TempDirectory.TempDir Path temp) {
|
void shouldMarkPluginForUninstall(@TempDir Path temp) {
|
||||||
InstalledPlugin mailPlugin = createInstalled("scm-mail-plugin");
|
InstalledPlugin mailPlugin = createInstalled("scm-mail-plugin");
|
||||||
when(mailPlugin.getDirectory()).thenReturn(temp);
|
when(mailPlugin.getDirectory()).thenReturn(temp);
|
||||||
|
|
||||||
@@ -414,7 +411,7 @@ class DefaultPluginManagerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldThrowExceptionWhenUninstallingCorePlugin(@TempDirectory.TempDir Path temp) {
|
void shouldThrowExceptionWhenUninstallingCorePlugin(@TempDir Path temp) {
|
||||||
InstalledPlugin mailPlugin = createInstalled("scm-mail-plugin");
|
InstalledPlugin mailPlugin = createInstalled("scm-mail-plugin");
|
||||||
when(mailPlugin.getDirectory()).thenReturn(temp);
|
when(mailPlugin.getDirectory()).thenReturn(temp);
|
||||||
when(mailPlugin.isCore()).thenReturn(true);
|
when(mailPlugin.isCore()).thenReturn(true);
|
||||||
@@ -484,7 +481,7 @@ class DefaultPluginManagerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldUndoPendingInstallations(@TempDirectory.TempDir Path temp) throws IOException {
|
void shouldUndoPendingInstallations(@TempDir Path temp) throws IOException {
|
||||||
InstalledPlugin mailPlugin = createInstalled("scm-ssh-plugin");
|
InstalledPlugin mailPlugin = createInstalled("scm-ssh-plugin");
|
||||||
Path mailPluginPath = temp.resolve("scm-mail-plugin");
|
Path mailPluginPath = temp.resolve("scm-mail-plugin");
|
||||||
Files.createDirectories(mailPluginPath);
|
Files.createDirectories(mailPluginPath);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ package sonia.scm.plugin;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.Answers;
|
import org.mockito.Answers;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
@@ -39,14 +39,14 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith({MockitoExtension.class, TempDirectory.class})
|
@ExtendWith({MockitoExtension.class})
|
||||||
class PendingPluginInstallationTest {
|
class PendingPluginInstallationTest {
|
||||||
|
|
||||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||||
private AvailablePlugin plugin;
|
private AvailablePlugin plugin;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldDeleteFileOnCancel(@TempDirectory.TempDir Path directory) throws IOException {
|
void shouldDeleteFileOnCancel(@TempDir Path directory) throws IOException {
|
||||||
Path file = directory.resolve("file");
|
Path file = directory.resolve("file");
|
||||||
Files.write(file, "42".getBytes());
|
Files.write(file, "42".getBytes());
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ class PendingPluginInstallationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldThrowExceptionIfCancelFailed(@TempDirectory.TempDir Path directory) {
|
void shouldThrowExceptionIfCancelFailed(@TempDir Path directory) {
|
||||||
Path file = directory.resolve("file");
|
Path file = directory.resolve("file");
|
||||||
when(plugin.getDescriptor().getInformation().getName()).thenReturn("scm-awesome-plugin");
|
when(plugin.getDescriptor().getInformation().getName()).thenReturn("scm-awesome-plugin");
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ package sonia.scm.plugin;
|
|||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.Answers;
|
import org.mockito.Answers;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
@@ -45,9 +45,13 @@ import java.util.Collections;
|
|||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
||||||
|
import static org.mockito.Mockito.anyInt;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith({MockitoExtension.class, TempDirectory.class})
|
@ExtendWith({MockitoExtension.class})
|
||||||
class PluginInstallerTest {
|
class PluginInstallerTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
@@ -65,7 +69,7 @@ class PluginInstallerTest {
|
|||||||
private Path directory;
|
private Path directory;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUpContext(@TempDirectory.TempDir Path directory) throws IOException {
|
void setUpContext(@TempDir Path directory) throws IOException {
|
||||||
this.directory = directory;
|
this.directory = directory;
|
||||||
lenient().when(context.resolve(any())).then(ic -> {
|
lenient().when(context.resolve(any())).then(ic -> {
|
||||||
Path arg = ic.getArgument(0);
|
Path arg = ic.getArgument(0);
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ package sonia.scm.plugin;
|
|||||||
|
|
||||||
import org.assertj.core.api.Assertions;
|
import org.assertj.core.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -38,7 +37,6 @@ import java.util.zip.ZipOutputStream;
|
|||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
class SmpDescriptorExtractorTest {
|
class SmpDescriptorExtractorTest {
|
||||||
|
|
||||||
private static final String PLUGIN_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
|
private static final String PLUGIN_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
|
||||||
@@ -66,7 +64,7 @@ class SmpDescriptorExtractorTest {
|
|||||||
"</plugin>\n";
|
"</plugin>\n";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldExtractPluginXml(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldExtractPluginXml(@TempDir Path tempDir) throws IOException {
|
||||||
Path pluginFile = createZipFile(tempDir, "META-INF/scm/plugin.xml", PLUGIN_XML);
|
Path pluginFile = createZipFile(tempDir, "META-INF/scm/plugin.xml", PLUGIN_XML);
|
||||||
|
|
||||||
InstalledPluginDescriptor installedPluginDescriptor = new SmpDescriptorExtractor().extractPluginDescriptor(pluginFile);
|
InstalledPluginDescriptor installedPluginDescriptor = new SmpDescriptorExtractor().extractPluginDescriptor(pluginFile);
|
||||||
@@ -75,14 +73,14 @@ class SmpDescriptorExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldFailWithoutPluginXml(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldFailWithoutPluginXml(@TempDir Path tempDir) throws IOException {
|
||||||
Path pluginFile = createZipFile(tempDir, "META-INF/wrong/plugin.xml", PLUGIN_XML);
|
Path pluginFile = createZipFile(tempDir, "META-INF/wrong/plugin.xml", PLUGIN_XML);
|
||||||
|
|
||||||
assertThrows(IOException.class, () -> new SmpDescriptorExtractor().extractPluginDescriptor(pluginFile));
|
assertThrows(IOException.class, () -> new SmpDescriptorExtractor().extractPluginDescriptor(pluginFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldFailWithIllegalPluginXml(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldFailWithIllegalPluginXml(@TempDir Path tempDir) throws IOException {
|
||||||
Path pluginFile = createZipFile(tempDir, "META-INF/scm/plugin.xml", "<not><parsable>content</parsable></not>");
|
Path pluginFile = createZipFile(tempDir, "META-INF/scm/plugin.xml", "<not><parsable>content</parsable></not>");
|
||||||
|
|
||||||
assertThrows(IOException.class, () -> new SmpDescriptorExtractor().extractPluginDescriptor(pluginFile));
|
assertThrows(IOException.class, () -> new SmpDescriptorExtractor().extractPluginDescriptor(pluginFile));
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ package sonia.scm.plugin;
|
|||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.io.Resources;
|
import com.google.common.io.Resources;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@@ -43,13 +42,12 @@ import java.util.UUID;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
class UberClassLoaderTest {
|
class UberClassLoaderTest {
|
||||||
|
|
||||||
private final URLClassLoader parentClassLoader = new URLClassLoader(new URL[0]);
|
private final URLClassLoader parentClassLoader = new URLClassLoader(new URL[0]);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldOnlyUseClassloaderOnce(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldOnlyUseClassloaderOnce(@TempDir Path tempDir) throws IOException {
|
||||||
ClassLoader mailClassLoader = createClassLoader(tempDir, "plugin.txt", "mail");
|
ClassLoader mailClassLoader = createClassLoader(tempDir, "plugin.txt", "mail");
|
||||||
ClassLoader reviewClassLoader = createClassLoader(mailClassLoader, tempDir, "plugin.txt", "review");
|
ClassLoader reviewClassLoader = createClassLoader(mailClassLoader, tempDir, "plugin.txt", "review");
|
||||||
|
|
||||||
@@ -61,7 +59,7 @@ class UberClassLoaderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnResourceFromEachPluginClassLoader(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldReturnResourceFromEachPluginClassLoader(@TempDir Path tempDir) throws IOException {
|
||||||
ClassLoader mailClassLoader = createClassLoader(tempDir, "scm.txt", "mail");
|
ClassLoader mailClassLoader = createClassLoader(tempDir, "scm.txt", "mail");
|
||||||
ClassLoader reviewClassLoader = createClassLoader(tempDir, "scm.txt", "review");
|
ClassLoader reviewClassLoader = createClassLoader(tempDir, "scm.txt", "review");
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Captor;
|
import org.mockito.Captor;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
@@ -39,7 +39,6 @@ import sonia.scm.store.ConfigurationEntryStore;
|
|||||||
import sonia.scm.store.InMemoryConfigurationEntryStoreFactory;
|
import sonia.scm.store.InMemoryConfigurationEntryStoreFactory;
|
||||||
import sonia.scm.update.UpdateStepTestUtil;
|
import sonia.scm.update.UpdateStepTestUtil;
|
||||||
import sonia.scm.update.V1Properties;
|
import sonia.scm.update.V1Properties;
|
||||||
import sonia.scm.update.V1Property;
|
|
||||||
|
|
||||||
import javax.xml.bind.JAXBException;
|
import javax.xml.bind.JAXBException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -55,7 +54,6 @@ import static org.mockito.Mockito.verify;
|
|||||||
import static sonia.scm.store.InMemoryConfigurationEntryStoreFactory.create;
|
import static sonia.scm.store.InMemoryConfigurationEntryStoreFactory.create;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
class XmlGroupV1UpdateStepTest {
|
class XmlGroupV1UpdateStepTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
@@ -72,7 +70,7 @@ class XmlGroupV1UpdateStepTest {
|
|||||||
|
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void mockScmHome(@TempDirectory.TempDir Path tempDir) {
|
void mockScmHome(@TempDir Path tempDir) {
|
||||||
testUtil = new UpdateStepTestUtil(tempDir);
|
testUtil = new UpdateStepTestUtil(tempDir);
|
||||||
updateStep = new XmlGroupV1UpdateStep(testUtil.getContextProvider(), groupDAO, storeFactory);
|
updateStep = new XmlGroupV1UpdateStep(testUtil.getContextProvider(), groupDAO, storeFactory);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ package sonia.scm.update.repository;
|
|||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import sonia.scm.SCMContextProvider;
|
import sonia.scm.SCMContextProvider;
|
||||||
@@ -44,7 +44,6 @@ import static org.mockito.ArgumentMatchers.anyString;
|
|||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class CopyMigrationStrategyTest {
|
class CopyMigrationStrategyTest {
|
||||||
|
|
||||||
@@ -54,30 +53,30 @@ class CopyMigrationStrategyTest {
|
|||||||
RepositoryLocationResolver locationResolver;
|
RepositoryLocationResolver locationResolver;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void mockContextProvider(@TempDirectory.TempDir Path tempDir) {
|
void mockContextProvider(@TempDir Path tempDir) {
|
||||||
when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
|
when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void createV1Home(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void createV1Home(@TempDir Path tempDir) throws IOException {
|
||||||
V1RepositoryFileSystem.createV1Home(tempDir);
|
V1RepositoryFileSystem.createV1Home(tempDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void mockLocationResolver(@TempDirectory.TempDir Path tempDir) {
|
void mockLocationResolver(@TempDir Path tempDir) {
|
||||||
RepositoryLocationResolver.RepositoryLocationResolverInstance instanceMock = mock(RepositoryLocationResolver.RepositoryLocationResolverInstance.class);
|
RepositoryLocationResolver.RepositoryLocationResolverInstance instanceMock = mock(RepositoryLocationResolver.RepositoryLocationResolverInstance.class);
|
||||||
when(locationResolver.forClass(Path.class)).thenReturn(instanceMock);
|
when(locationResolver.forClass(Path.class)).thenReturn(instanceMock);
|
||||||
when(instanceMock.createLocation(anyString())).thenAnswer(invocation -> tempDir.resolve((String) invocation.getArgument(0)));
|
when(instanceMock.createLocation(anyString())).thenAnswer(invocation -> tempDir.resolve((String) invocation.getArgument(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldUseStandardDirectory(@TempDirectory.TempDir Path tempDir) {
|
void shouldUseStandardDirectory(@TempDir Path tempDir) {
|
||||||
Path target = new CopyMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git").get();
|
Path target = new CopyMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git").get();
|
||||||
assertThat(target).isEqualTo(tempDir.resolve("b4f-a9f0-49f7-ad1f-37d3aae1c55f"));
|
assertThat(target).isEqualTo(tempDir.resolve("b4f-a9f0-49f7-ad1f-37d3aae1c55f"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCopyDataDirectory(@TempDirectory.TempDir Path tempDir) {
|
void shouldCopyDataDirectory(@TempDir Path tempDir) {
|
||||||
Path target = new CopyMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git").get();
|
Path target = new CopyMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git").get();
|
||||||
assertThat(target.resolve("data")).exists();
|
assertThat(target.resolve("data")).exists();
|
||||||
Path originalDataDir = tempDir
|
Path originalDataDir = tempDir
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import sonia.scm.SCMContextProvider;
|
import sonia.scm.SCMContextProvider;
|
||||||
@@ -43,7 +43,6 @@ import static org.mockito.Mockito.when;
|
|||||||
import static sonia.scm.update.repository.MigrationStrategy.INLINE;
|
import static sonia.scm.update.repository.MigrationStrategy.INLINE;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
class DefaultMigrationStrategyDAOTest {
|
class DefaultMigrationStrategyDAOTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
@@ -52,7 +51,7 @@ class DefaultMigrationStrategyDAOTest {
|
|||||||
private ConfigurationStoreFactory storeFactory;
|
private ConfigurationStoreFactory storeFactory;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void initStore(@TempDirectory.TempDir Path tempDir) {
|
void initStore(@TempDir Path tempDir) {
|
||||||
when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
|
when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
|
||||||
storeFactory = new JAXBConfigurationStoreFactory(contextProvider, null);
|
storeFactory = new JAXBConfigurationStoreFactory(contextProvider, null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ package sonia.scm.update.repository;
|
|||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import sonia.scm.SCMContextProvider;
|
import sonia.scm.SCMContextProvider;
|
||||||
@@ -41,7 +41,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class InlineMigrationStrategyTest {
|
class InlineMigrationStrategyTest {
|
||||||
|
|
||||||
@@ -53,25 +52,25 @@ class InlineMigrationStrategyTest {
|
|||||||
RepositoryLocationResolver.RepositoryLocationResolverInstance locationResolverInstance;
|
RepositoryLocationResolver.RepositoryLocationResolverInstance locationResolverInstance;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void mockContextProvider(@TempDirectory.TempDir Path tempDir) {
|
void mockContextProvider(@TempDir Path tempDir) {
|
||||||
when(locationResolver.forClass(Path.class)).thenReturn(locationResolverInstance);
|
when(locationResolver.forClass(Path.class)).thenReturn(locationResolverInstance);
|
||||||
when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
|
when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void createV1Home(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void createV1Home(@TempDir Path tempDir) throws IOException {
|
||||||
V1RepositoryFileSystem.createV1Home(tempDir);
|
V1RepositoryFileSystem.createV1Home(tempDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldUseExistingDirectory(@TempDirectory.TempDir Path tempDir) {
|
void shouldUseExistingDirectory(@TempDir Path tempDir) {
|
||||||
Path target = new InlineMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git").get();
|
Path target = new InlineMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git").get();
|
||||||
assertThat(target).isEqualTo(resolveOldDirectory(tempDir));
|
assertThat(target).isEqualTo(resolveOldDirectory(tempDir));
|
||||||
verify(locationResolverInstance).setLocation("b4f-a9f0-49f7-ad1f-37d3aae1c55f", target);
|
verify(locationResolverInstance).setLocation("b4f-a9f0-49f7-ad1f-37d3aae1c55f", target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldMoveDataDirectory(@TempDirectory.TempDir Path tempDir) {
|
void shouldMoveDataDirectory(@TempDir Path tempDir) {
|
||||||
new InlineMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git");
|
new InlineMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git");
|
||||||
assertThat(resolveOldDirectory(tempDir).resolve("data")).exists();
|
assertThat(resolveOldDirectory(tempDir).resolve("data")).exists();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import org.assertj.core.api.Assertions;
|
|||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
@@ -51,7 +51,6 @@ import static org.mockito.Mockito.doAnswer;
|
|||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
class MigrateVerbsToPermissionRolesTest {
|
class MigrateVerbsToPermissionRolesTest {
|
||||||
|
|
||||||
private static final String EXISTING_REPOSITORY_ID = "id";
|
private static final String EXISTING_REPOSITORY_ID = "id";
|
||||||
@@ -65,7 +64,7 @@ class MigrateVerbsToPermissionRolesTest {
|
|||||||
private MigrateVerbsToPermissionRoles migration;
|
private MigrateVerbsToPermissionRoles migration;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void init(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void init(@TempDir Path tempDir) throws IOException {
|
||||||
URL metadataUrl = Resources.getResource("sonia/scm/update/repository/metadataWithoutRoles.xml");
|
URL metadataUrl = Resources.getResource("sonia/scm/update/repository/metadataWithoutRoles.xml");
|
||||||
Files.copy(metadataUrl.openStream(), tempDir.resolve("metadata.xml"));
|
Files.copy(metadataUrl.openStream(), tempDir.resolve("metadata.xml"));
|
||||||
doAnswer(invocation -> {
|
doAnswer(invocation -> {
|
||||||
@@ -76,7 +75,7 @@ class MigrateVerbsToPermissionRolesTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldUpdateToRolesIfPossible(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldUpdateToRolesIfPossible(@TempDir Path tempDir) throws IOException {
|
||||||
migration.doUpdate();
|
migration.doUpdate();
|
||||||
|
|
||||||
List<String> newMetadata = Files.readAllLines(tempDir.resolve("metadata.xml"));
|
List<String> newMetadata = Files.readAllLines(tempDir.resolve("metadata.xml"));
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ package sonia.scm.update.repository;
|
|||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import sonia.scm.SCMContextProvider;
|
import sonia.scm.SCMContextProvider;
|
||||||
@@ -41,7 +41,6 @@ import static org.mockito.ArgumentMatchers.anyString;
|
|||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class MoveMigrationStrategyTest {
|
class MoveMigrationStrategyTest {
|
||||||
|
|
||||||
@@ -51,30 +50,30 @@ class MoveMigrationStrategyTest {
|
|||||||
RepositoryLocationResolver locationResolver;
|
RepositoryLocationResolver locationResolver;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void mockContextProvider(@TempDirectory.TempDir Path tempDir) {
|
void mockContextProvider(@TempDir Path tempDir) {
|
||||||
when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
|
when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void createV1Home(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void createV1Home(@TempDir Path tempDir) throws IOException {
|
||||||
V1RepositoryFileSystem.createV1Home(tempDir);
|
V1RepositoryFileSystem.createV1Home(tempDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void mockLocationResolver(@TempDirectory.TempDir Path tempDir) {
|
void mockLocationResolver(@TempDir Path tempDir) {
|
||||||
RepositoryLocationResolver.RepositoryLocationResolverInstance instanceMock = mock(RepositoryLocationResolver.RepositoryLocationResolverInstance.class);
|
RepositoryLocationResolver.RepositoryLocationResolverInstance instanceMock = mock(RepositoryLocationResolver.RepositoryLocationResolverInstance.class);
|
||||||
when(locationResolver.forClass(Path.class)).thenReturn(instanceMock);
|
when(locationResolver.forClass(Path.class)).thenReturn(instanceMock);
|
||||||
when(instanceMock.createLocation(anyString())).thenAnswer(invocation -> tempDir.resolve((String) invocation.getArgument(0)));
|
when(instanceMock.createLocation(anyString())).thenAnswer(invocation -> tempDir.resolve((String) invocation.getArgument(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldUseStandardDirectory(@TempDirectory.TempDir Path tempDir) {
|
void shouldUseStandardDirectory(@TempDir Path tempDir) {
|
||||||
Path target = new MoveMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git").get();
|
Path target = new MoveMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git").get();
|
||||||
assertThat(target).isEqualTo(tempDir.resolve("b4f-a9f0-49f7-ad1f-37d3aae1c55f"));
|
assertThat(target).isEqualTo(tempDir.resolve("b4f-a9f0-49f7-ad1f-37d3aae1c55f"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldMoveDataDirectory(@TempDirectory.TempDir Path tempDir) {
|
void shouldMoveDataDirectory(@TempDir Path tempDir) {
|
||||||
Path target = new MoveMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git").get();
|
Path target = new MoveMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git").get();
|
||||||
assertThat(target.resolve("data")).exists();
|
assertThat(target.resolve("data")).exists();
|
||||||
Path originalDataDir = tempDir
|
Path originalDataDir = tempDir
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ package sonia.scm.update.repository;
|
|||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Captor;
|
import org.mockito.Captor;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
@@ -35,7 +35,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||||||
import sonia.scm.SCMContext;
|
import sonia.scm.SCMContext;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryPermission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.repository.RepositoryRolePermissions;
|
|
||||||
import sonia.scm.repository.RepositoryTestData;
|
import sonia.scm.repository.RepositoryTestData;
|
||||||
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
||||||
import sonia.scm.update.UpdateStepTestUtil;
|
import sonia.scm.update.UpdateStepTestUtil;
|
||||||
@@ -49,7 +48,6 @@ import java.nio.file.Path;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junitpioneer.jupiter.TempDirectory.TempDir;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
@@ -58,7 +56,6 @@ import static org.mockito.Mockito.verify;
|
|||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
class PublicFlagUpdateStepTest {
|
class PublicFlagUpdateStepTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ package sonia.scm.update.repository;
|
|||||||
import com.google.common.io.Resources;
|
import com.google.common.io.Resources;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
|
||||||
import sonia.scm.SCMContextProvider;
|
import sonia.scm.SCMContextProvider;
|
||||||
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
|
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
|
||||||
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
||||||
@@ -43,19 +42,18 @@ import static org.mockito.Mockito.mock;
|
|||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
class XmlRepositoryFileNameUpdateStepTest {
|
class XmlRepositoryFileNameUpdateStepTest {
|
||||||
|
|
||||||
SCMContextProvider contextProvider = mock(SCMContextProvider.class);
|
SCMContextProvider contextProvider = mock(SCMContextProvider.class);
|
||||||
XmlRepositoryDAO repositoryDAO = mock(XmlRepositoryDAO.class);
|
XmlRepositoryDAO repositoryDAO = mock(XmlRepositoryDAO.class);
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void mockScmHome(@TempDirectory.TempDir Path tempDir) {
|
void mockScmHome(@TempDir Path tempDir) {
|
||||||
when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
|
when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCopyRepositoriesFileToRepositoryPathsFile(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldCopyRepositoriesFileToRepositoryPathsFile(@TempDir Path tempDir) throws IOException {
|
||||||
XmlRepositoryFileNameUpdateStep updateStep = new XmlRepositoryFileNameUpdateStep(contextProvider, repositoryDAO);
|
XmlRepositoryFileNameUpdateStep updateStep = new XmlRepositoryFileNameUpdateStep(contextProvider, repositoryDAO);
|
||||||
URL url = Resources.getResource("sonia/scm/update/repository/formerV2RepositoryFile.xml");
|
URL url = Resources.getResource("sonia/scm/update/repository/formerV2RepositoryFile.xml");
|
||||||
Path configDir = tempDir.resolve("config");
|
Path configDir = tempDir.resolve("config");
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Captor;
|
import org.mockito.Captor;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
@@ -63,7 +63,6 @@ import static org.mockito.Mockito.when;
|
|||||||
import static sonia.scm.update.repository.MigrationStrategy.MOVE;
|
import static sonia.scm.update.repository.MigrationStrategy.MOVE;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@ExtendWith(TempDirectory.class)
|
|
||||||
class XmlRepositoryV1UpdateStepTest {
|
class XmlRepositoryV1UpdateStepTest {
|
||||||
|
|
||||||
Injector injectorMock = MigrationStrategyMock.init();
|
Injector injectorMock = MigrationStrategyMock.init();
|
||||||
@@ -85,7 +84,7 @@ class XmlRepositoryV1UpdateStepTest {
|
|||||||
XmlRepositoryV1UpdateStep updateStep;
|
XmlRepositoryV1UpdateStep updateStep;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void createUpdateStepFromMocks(@TempDirectory.TempDir Path tempDir) {
|
void createUpdateStepFromMocks(@TempDir Path tempDir) {
|
||||||
testUtil = new UpdateStepTestUtil(tempDir);
|
testUtil = new UpdateStepTestUtil(tempDir);
|
||||||
updateStep = new XmlRepositoryV1UpdateStep(
|
updateStep = new XmlRepositoryV1UpdateStep(
|
||||||
testUtil.getContextProvider(),
|
testUtil.getContextProvider(),
|
||||||
@@ -100,7 +99,7 @@ class XmlRepositoryV1UpdateStepTest {
|
|||||||
class WithExistingDatabase {
|
class WithExistingDatabase {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void createV1Home(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void createV1Home(@TempDir Path tempDir) throws IOException {
|
||||||
V1RepositoryFileSystem.createV1Home(tempDir);
|
V1RepositoryFileSystem.createV1Home(tempDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +164,7 @@ class XmlRepositoryV1UpdateStepTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldUseDirectoryFromStrategy(@TempDirectory.TempDir Path tempDir) throws JAXBException {
|
void shouldUseDirectoryFromStrategy(@TempDir Path tempDir) throws JAXBException {
|
||||||
Path targetDir = tempDir.resolve("someDir");
|
Path targetDir = tempDir.resolve("someDir");
|
||||||
MigrationStrategy.Instance strategyMock = injectorMock.getInstance(MoveMigrationStrategy.class);
|
MigrationStrategy.Instance strategyMock = injectorMock.getInstance(MoveMigrationStrategy.class);
|
||||||
when(strategyMock.migrate("454972da-faf9-4437-b682-dc4a4e0aa8eb", "simple", "git")).thenReturn(of(targetDir));
|
when(strategyMock.migrate("454972da-faf9-4437-b682-dc4a4e0aa8eb", "simple", "git")).thenReturn(of(targetDir));
|
||||||
@@ -195,7 +194,7 @@ class XmlRepositoryV1UpdateStepTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldBackupOldRepositoryDatabaseFile(@TempDirectory.TempDir Path tempDir) throws JAXBException {
|
void shouldBackupOldRepositoryDatabaseFile(@TempDir Path tempDir) throws JAXBException {
|
||||||
updateStep.doUpdate();
|
updateStep.doUpdate();
|
||||||
|
|
||||||
assertThat(tempDir.resolve("config").resolve("repositories.xml")).doesNotExist();
|
assertThat(tempDir.resolve("config").resolve("repositories.xml")).doesNotExist();
|
||||||
@@ -209,14 +208,14 @@ class XmlRepositoryV1UpdateStepTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldNotFailIfFormerV2DatabaseExists(@TempDirectory.TempDir Path tempDir) throws JAXBException, IOException {
|
void shouldNotFailIfFormerV2DatabaseExists(@TempDir Path tempDir) throws JAXBException, IOException {
|
||||||
createFormerV2RepositoriesFile(tempDir);
|
createFormerV2RepositoriesFile(tempDir);
|
||||||
|
|
||||||
updateStep.doUpdate();
|
updateStep.doUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldNotBackupFormerV2DatabaseFile(@TempDirectory.TempDir Path tempDir) throws JAXBException, IOException {
|
void shouldNotBackupFormerV2DatabaseFile(@TempDir Path tempDir) throws JAXBException, IOException {
|
||||||
createFormerV2RepositoriesFile(tempDir);
|
createFormerV2RepositoriesFile(tempDir);
|
||||||
|
|
||||||
updateStep.doUpdate();
|
updateStep.doUpdate();
|
||||||
@@ -226,14 +225,14 @@ class XmlRepositoryV1UpdateStepTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldGetNoMissingStrategiesWithFormerV2DatabaseFile(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldGetNoMissingStrategiesWithFormerV2DatabaseFile(@TempDir Path tempDir) throws IOException {
|
||||||
createFormerV2RepositoriesFile(tempDir);
|
createFormerV2RepositoriesFile(tempDir);
|
||||||
|
|
||||||
assertThat(updateStep.getRepositoriesWithoutMigrationStrategies()).isEmpty();
|
assertThat(updateStep.getRepositoriesWithoutMigrationStrategies()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldFindMissingStrategies(@TempDirectory.TempDir Path tempDir) throws IOException {
|
void shouldFindMissingStrategies(@TempDir Path tempDir) throws IOException {
|
||||||
V1RepositoryFileSystem.createV1Home(tempDir);
|
V1RepositoryFileSystem.createV1Home(tempDir);
|
||||||
|
|
||||||
assertThat(updateStep.getRepositoriesWithoutMigrationStrategies())
|
assertThat(updateStep.getRepositoriesWithoutMigrationStrategies())
|
||||||
@@ -244,7 +243,7 @@ class XmlRepositoryV1UpdateStepTest {
|
|||||||
"454972da-faf9-4437-b682-dc4a4e0aa8eb");
|
"454972da-faf9-4437-b682-dc4a4e0aa8eb");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createFormerV2RepositoriesFile(@TempDirectory.TempDir Path tempDir) throws IOException {
|
private void createFormerV2RepositoriesFile(@TempDir Path tempDir) throws IOException {
|
||||||
URL url = Resources.getResource("sonia/scm/update/repository/formerV2RepositoryFile.xml");
|
URL url = Resources.getResource("sonia/scm/update/repository/formerV2RepositoryFile.xml");
|
||||||
Path configDir = tempDir.resolve("config");
|
Path configDir = tempDir.resolve("config");
|
||||||
Files.createDirectories(configDir);
|
Files.createDirectories(configDir);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user