diff --git a/CHANGELOG.md b/CHANGELOG.md
index 83c05e5423..3733f78776 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,11 +6,26 @@ 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).
## Unreleased
+
+### Added
+- Delete branches directly in the UI ([#1422](https://github.com/scm-manager/scm-manager/pull/1422))
+- Lookup command which provides further repository information ([#1415](https://github.com/scm-manager/scm-manager/pull/1415))
+- Include messages from scm protocol in modification or merge errors ([#1420](https://github.com/scm-manager/scm-manager/pull/1420))
+- Enhance trace api to accepted status codes ([#1430](https://github.com/scm-manager/scm-manager/pull/1430))
+
### Changed
- Send mercurial hook callbacks over separate tcp socket instead of http ([#1416](https://github.com/scm-manager/scm-manager/pull/1416))
### Fixed
+- Missing close of hg diff command ([#1417](https://github.com/scm-manager/scm-manager/pull/1417))
- Error on repository initialization with least-privilege user ([#1414](https://github.com/scm-manager/scm-manager/pull/1414))
+- Adhere to git quiet flag ([#1421](https://github.com/scm-manager/scm-manager/pull/1421))
+- Resolve svn binary diffs properly [#1427](https://github.com/scm-manager/scm-manager/pull/1427)
+
+## [2.9.1] - 2020-11-11
+### Fixed
+- German translation for repositories view
+
## [2.9.0] - 2020-11-06
### Added
diff --git a/docs/de/user/repo/assets/repository-branch-detailView.png b/docs/de/user/repo/assets/repository-branch-detailView.png
index a73b369e26..d671846fef 100644
Binary files a/docs/de/user/repo/assets/repository-branch-detailView.png and b/docs/de/user/repo/assets/repository-branch-detailView.png differ
diff --git a/docs/de/user/repo/assets/repository-branches-overview.png b/docs/de/user/repo/assets/repository-branches-overview.png
index 9258f4be97..39dcf5e424 100644
Binary files a/docs/de/user/repo/assets/repository-branches-overview.png and b/docs/de/user/repo/assets/repository-branches-overview.png differ
diff --git a/docs/de/user/repo/branches.md b/docs/de/user/repo/branches.md
index b52b6492b2..17afe92924 100644
--- a/docs/de/user/repo/branches.md
+++ b/docs/de/user/repo/branches.md
@@ -6,6 +6,7 @@ subtitle: Branches
Auf der Branches-Übersicht sind die bereits existierenden Branches aufgeführt. Bei einem Klick auf einen Branch wird man zur Detailseite des Branches weitergeleitet.
Der Tag "Default" gibt an welcher Branch aktuell, als Standard-Branch dieses Repository im SCM-Manager markiert ist. Der Standard-Branch wird immer zuerst angezeigt, wenn man das Repository im SCM-Manager öffnet.
+Alle Branches mit Ausnahme des Default Branches können über den Mülleimer-Icon unwiderruflich gelöscht werden.
Über den "Branch erstellen"-Button gelangt man zum Formular, um neue Branches anzulegen.
@@ -19,4 +20,6 @@ Mit dem "Branch erstellen"-Formular können neue Branches für das Repository er
### Branch Detailseite
Hier werden einige Befehle zum Arbeiten mit dem Branch auf einer Kommandozeile aufgeführt.
+Handelt es sich nicht um den Default Branch des Repositories, kann im unteren Bereich der Branch unwiderruflich gelöscht werden.
+

diff --git a/docs/en/user/repo/assets/repository-branch-detailView.png b/docs/en/user/repo/assets/repository-branch-detailView.png
index 2660d58a82..615ba696c1 100644
Binary files a/docs/en/user/repo/assets/repository-branch-detailView.png and b/docs/en/user/repo/assets/repository-branch-detailView.png differ
diff --git a/docs/en/user/repo/assets/repository-branches-overview.png b/docs/en/user/repo/assets/repository-branches-overview.png
index 32560b4f31..e63ebab775 100644
Binary files a/docs/en/user/repo/assets/repository-branches-overview.png and b/docs/en/user/repo/assets/repository-branches-overview.png differ
diff --git a/docs/en/user/repo/branches.md b/docs/en/user/repo/branches.md
index 8c39eae6f0..370165710b 100644
--- a/docs/en/user/repo/branches.md
+++ b/docs/en/user/repo/branches.md
@@ -6,6 +6,7 @@ subtitle: Branches
The branches overview shows the branches that are already existing. By clicking on a branch, the details page of the branch is shown.
The tag "Default" shows which branch is currently set as the default branch of the repository in SCM-Manager. The default branch is always shown first when opening the repository in SCM-Manager.
+All branches except the default branch of the repository can be deleted by clicking on the trash bin icon.
The button "Create Branch" opens the form to create a new branch.
@@ -18,5 +19,6 @@ New branches can be created with the "Create Branch" form. There, you have to ch
### Branch Details Page
This page shows some commands to work with the branch on the command line.
+If the branch is not the default branch of the repository it can be deleted using the action inside the bottom section.

diff --git a/pom.xml b/pom.xml
index fa8eaab8d0..b8192de3a8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -903,7 +903,7 @@
- 3.5.13
+ 3.5.152.15.7.0
@@ -925,8 +925,8 @@
1.6.2
- 9.4.33.v20201020
- 9.4.30.v20200611
+ 9.4.34.v20201102
+ 9.4.34.v202011021.2.0
diff --git a/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java b/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java
index 694120dc2b..93c84e01cc 100644
--- a/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java
+++ b/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm;
import java.util.Collections;
@@ -33,7 +33,7 @@ public class ConcurrentModificationException extends ExceptionWithContext {
private static final String CODE = "2wR7UzpPG1";
- public ConcurrentModificationException(Class type, String id) {
+ public ConcurrentModificationException(Class> type, String id) {
this(Collections.singletonList(new ContextEntry(type, id)));
}
@@ -56,4 +56,4 @@ public class ConcurrentModificationException extends ExceptionWithContext {
.collect(joining(" in ", "", " has been modified concurrently"));
}
}
-
+
diff --git a/scm-core/src/main/java/sonia/scm/ContextEntry.java b/scm-core/src/main/java/sonia/scm/ContextEntry.java
index d398e54609..15217e3173 100644
--- a/scm-core/src/main/java/sonia/scm/ContextEntry.java
+++ b/scm-core/src/main/java/sonia/scm/ContextEntry.java
@@ -21,22 +21,23 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.util.AssertUtil;
+import java.io.Serializable;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
-public class ContextEntry {
+public class ContextEntry implements Serializable {
private final String type;
private final String id;
- ContextEntry(Class type, String id) {
+ ContextEntry(Class> type, String id) {
this(type.getSimpleName(), id);
}
@@ -91,7 +92,7 @@ public class ContextEntry {
return this.in(Repository.class, namespaceAndName.logString());
}
- public ContextBuilder in(Class type, String id) {
+ public ContextBuilder in(Class> type, String id) {
context.add(new ContextEntry(type, id));
return this;
}
diff --git a/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java b/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java
index 1631aea178..16c0f0218c 100644
--- a/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java
+++ b/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java
@@ -24,6 +24,7 @@
package sonia.scm;
+import java.io.Serializable;
import java.util.List;
import java.util.Optional;
@@ -34,15 +35,26 @@ public abstract class ExceptionWithContext extends RuntimeException {
private static final long serialVersionUID = 4327413456580409224L;
private final List context;
+ private final List additionalMessages;
- public ExceptionWithContext(List context, String message) {
- super(message);
- this.context = context;
+ protected ExceptionWithContext(List context, String message) {
+ this(context, null, message);
}
- public ExceptionWithContext(List context, String message, Exception cause) {
+ protected ExceptionWithContext(List context, List additionalMessages, String message) {
+ super(message);
+ this.context = context;
+ this.additionalMessages = additionalMessages;
+ }
+
+ protected ExceptionWithContext(List context, String message, Exception cause) {
+ this(context, null, message, cause);
+ }
+
+ protected ExceptionWithContext(List context, List additionalMessages, String message, Exception cause) {
super(message, cause);
this.context = context;
+ this.additionalMessages = additionalMessages;
}
public List getContext() {
@@ -61,4 +73,26 @@ public abstract class ExceptionWithContext extends RuntimeException {
public Optional getUrl() {
return Optional.empty();
}
+
+ public List getAdditionalMessages() {
+ return additionalMessages;
+ }
+
+ public static class AdditionalMessage implements Serializable {
+ private final String key;
+ private final String message;
+
+ public AdditionalMessage(String key, String message) {
+ this.key = key;
+ this.message = message;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+ }
}
diff --git a/scm-core/src/main/java/sonia/scm/NotFoundException.java b/scm-core/src/main/java/sonia/scm/NotFoundException.java
index 2ca74fc60c..0c0a06926c 100644
--- a/scm-core/src/main/java/sonia/scm/NotFoundException.java
+++ b/scm-core/src/main/java/sonia/scm/NotFoundException.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm;
import java.util.Collections;
@@ -35,7 +35,7 @@ public class NotFoundException extends ExceptionWithContext {
private static final String CODE = "AGR7UzkhA1";
- public NotFoundException(Class type, String id) {
+ public NotFoundException(Class> type, String id) {
this(Collections.singletonList(new ContextEntry(type, id)));
}
diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java
index 5199446a4f..737d4b1fc2 100644
--- a/scm-core/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java
+++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -40,7 +40,10 @@ public class ErrorDto {
private List context;
private String message;
- @JsonInclude(JsonInclude.Include.NON_NULL)
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ private List additionalMessages;
+
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
@XmlElementWrapper(name = "violations")
private List violations;
@@ -53,4 +56,10 @@ public class ErrorDto {
private String path;
private String message;
}
+
+ @Getter @Setter
+ public static class AdditionalMessageDto {
+ private String key;
+ private String message;
+ }
}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java b/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java
index 302f2ac6fe..5b0ebc453c 100644
--- a/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java
@@ -257,6 +257,22 @@ public abstract class BaseHttpRequest
return self();
}
+ /**
+ * Sets the response codes which should be traced as successful.
+ *
+ * Example: If 400 is set as {@link #acceptedStatusCodes} then all requests
+ * which get a response with status code 400 will be traced as successful (not failed) request
+ *
+ * @param codes status codes which should be traced as successful
+ * @return request instance
+ *
+ * @since 2.10.0
+ */
+ public T acceptStatusCodes(int... codes) {
+ this.acceptedStatusCodes = codes;
+ return self();
+ }
+
/**
* Disables tracing for the request.
* This should only be done for internal requests.
@@ -314,6 +330,18 @@ public abstract class BaseHttpRequest
return spanKind;
}
+
+ /**
+ * Returns the response codes which are accepted as successful by tracer.
+ *
+ * @return codes
+ *
+ * @since 2.10.0
+ */
+ public int[] getAcceptedStatus() {
+ return acceptedStatusCodes;
+ }
+
/**
* Returns true if the request decodes gzip compression.
*
@@ -434,4 +462,7 @@ public abstract class BaseHttpRequest
/** kind of span for trace api */
private String spanKind = "HTTP Request";
+
+ /** codes which will be marked as successful by tracer */
+ private int[] acceptedStatusCodes = new int[]{};
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/Command.java b/scm-core/src/main/java/sonia/scm/repository/api/Command.java
index d00c898233..4a2d3e77ae 100644
--- a/scm-core/src/main/java/sonia/scm/repository/api/Command.java
+++ b/scm-core/src/main/java/sonia/scm/repository/api/Command.java
@@ -57,5 +57,10 @@ public enum Command
/**
* @since 2.0
*/
- MODIFICATIONS, MERGE, DIFF_RESULT, BRANCH, MODIFY;
+ MODIFICATIONS, MERGE, DIFF_RESULT, BRANCH, MODIFY,
+
+ /**
+ * @since 2.10.0
+ */
+ LOOKUP;
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/LookupCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/LookupCommandBuilder.java
new file mode 100644
index 0000000000..2c047b97eb
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/repository/api/LookupCommandBuilder.java
@@ -0,0 +1,51 @@
+/*
+ * 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.api;
+
+import sonia.scm.repository.spi.LookupCommand;
+import sonia.scm.repository.spi.LookupCommandRequest;
+
+import java.util.Optional;
+
+/**
+ * The lookup command executes a lookup for additional repository information.
+ *
+ * @since 2.10.0
+ */
+public class LookupCommandBuilder {
+
+ private final LookupCommand lookupCommand;
+
+ public LookupCommandBuilder(LookupCommand lookupCommand) {
+ this.lookupCommand = lookupCommand;
+ }
+
+ public Optional lookup(Class type, String... args) {
+ LookupCommandRequest request = new LookupCommandRequest<>();
+ request.setType(type);
+ request.setArgs(args);
+ return lookupCommand.lookup(request);
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java
index a6b4265239..15c2f3f523 100644
--- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java
+++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java
@@ -430,6 +430,20 @@ public final class RepositoryService implements Closeable {
return new ModifyCommandBuilder(provider.getModifyCommand(), workdirProvider, eMail);
}
+ /**
+ * The lookup command executes a lookup which returns additional information for the repository.
+ *
+ * @return instance of {@link LookupCommandBuilder}
+ * @throws CommandNotSupportedException if the command is not supported
+ * by the implementation of the repository service provider.
+ * @since 2.10.0
+ */
+ public LookupCommandBuilder getLookupCommand() {
+ LOG.debug("create lookup command for repository {}",
+ repository.getNamespaceAndName());
+ return new LookupCommandBuilder(provider.getLookupCommand());
+ }
+
/**
* Returns true if the command is supported by the repository service.
*
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/IntegrateChangesFromWorkdirException.java b/scm-core/src/main/java/sonia/scm/repository/spi/IntegrateChangesFromWorkdirException.java
index 3259cc2094..f8dc0ba2d0 100644
--- a/scm-core/src/main/java/sonia/scm/repository/spi/IntegrateChangesFromWorkdirException.java
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/IntegrateChangesFromWorkdirException.java
@@ -21,27 +21,58 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository.spi;
-import sonia.scm.ContextEntry;
import sonia.scm.ExceptionWithContext;
import sonia.scm.repository.Repository;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static java.util.Arrays.stream;
+import static sonia.scm.ContextEntry.ContextBuilder.entity;
+
public class IntegrateChangesFromWorkdirException extends ExceptionWithContext {
- private static final String CODE = "CHRM7IQzo1";
+ static final String CODE_WITH_ADDITIONAL_MESSAGES = "CHRM7IQzo1";
+ static final String CODE_WITHOUT_ADDITIONAL_MESSAGES = "ASSG1ehZ01";
- public IntegrateChangesFromWorkdirException(Repository repository, String message) {
- super(ContextEntry.ContextBuilder.entity(repository).build(), message);
+ private static final Pattern SCM_MESSAGE_PATTERN = Pattern.compile(".*\\[SCM\\] (.*)");
+
+ public static MessageExtractor withPattern(Pattern pattern) {
+ return new MessageExtractor(pattern);
}
- public IntegrateChangesFromWorkdirException(Repository repository, String message, Exception cause) {
- super(ContextEntry.ContextBuilder.entity(repository).build(), message, cause);
+ public static IntegrateChangesFromWorkdirException forMessage(Repository repository, String message) {
+ return new MessageExtractor(SCM_MESSAGE_PATTERN).forMessage(repository, message);
+ }
+
+ private IntegrateChangesFromWorkdirException(Repository repository, List additionalMessages) {
+ super(entity(repository).build(), additionalMessages, "errors from hook");
}
@Override
public String getCode() {
- return CODE;
+ return getAdditionalMessages().isEmpty()? CODE_WITHOUT_ADDITIONAL_MESSAGES : CODE_WITH_ADDITIONAL_MESSAGES;
+ }
+
+ public static class MessageExtractor {
+
+ private final Pattern extractorPattern;
+
+ public MessageExtractor(Pattern extractorPattern) {
+ this.extractorPattern = extractorPattern;
+ }
+
+ public IntegrateChangesFromWorkdirException forMessage(Repository repository, String message) {
+ return new IntegrateChangesFromWorkdirException(repository, stream(message.split("\\n"))
+ .map(extractorPattern::matcher)
+ .filter(Matcher::matches)
+ .map(matcher -> new AdditionalMessage(null, matcher.group(1)))
+ .collect(Collectors.toList()));
+ }
}
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/LookupCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/LookupCommand.java
new file mode 100644
index 0000000000..363d50ec4e
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/LookupCommand.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import java.util.Optional;
+
+public interface LookupCommand {
+
+ /**
+ * Executes lookup for given parameters.
+ *
+ * @param request Arguments provided for the lookup.
+ * @return Result of provided type.
+ */
+ Optional lookup(LookupCommandRequest request);
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/LookupCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/LookupCommandRequest.java
new file mode 100644
index 0000000000..0094351038
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/LookupCommandRequest.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class LookupCommandRequest {
+ private Class type;
+ private String[] args;
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java b/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java
index 6bde899617..091c9b46b3 100644
--- a/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java
@@ -274,4 +274,12 @@ public abstract class RepositoryServiceProvider implements Closeable
{
throw new CommandNotSupportedException(Command.MODIFY);
}
+
+ /**
+ * @since 2.10.0
+ */
+ public LookupCommand getLookupCommand()
+ {
+ throw new CommandNotSupportedException(Command.LOOKUP);
+ }
}
diff --git a/scm-core/src/test/java/sonia/scm/repository/spi/IntegrateChangesFromWorkdirExceptionTest.java b/scm-core/src/test/java/sonia/scm/repository/spi/IntegrateChangesFromWorkdirExceptionTest.java
new file mode 100644
index 0000000000..d990a64ef7
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/repository/spi/IntegrateChangesFromWorkdirExceptionTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import org.junit.jupiter.api.Test;
+import sonia.scm.repository.Repository;
+
+import java.util.regex.Pattern;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static sonia.scm.repository.spi.IntegrateChangesFromWorkdirException.CODE_WITHOUT_ADDITIONAL_MESSAGES;
+import static sonia.scm.repository.spi.IntegrateChangesFromWorkdirException.CODE_WITH_ADDITIONAL_MESSAGES;
+import static sonia.scm.repository.spi.IntegrateChangesFromWorkdirException.forMessage;
+import static sonia.scm.repository.spi.IntegrateChangesFromWorkdirException.withPattern;
+
+class IntegrateChangesFromWorkdirExceptionTest {
+
+ private static final Repository REPOSITORY = new Repository("1", "git", "hitchhiker", "hog");
+
+ @Test
+ void shouldExtractMessagesWithDefaultPrefix() {
+ IntegrateChangesFromWorkdirException exception =
+ forMessage(REPOSITORY, "prefix [SCM] line 1\nprefix [SCM] line 2\nirrelevant line\n");
+
+ assertThat(exception.getAdditionalMessages())
+ .extracting("message")
+ .containsExactly("line 1", "line 2");
+ assertThat(exception.getCode()).isEqualTo(CODE_WITH_ADDITIONAL_MESSAGES);
+ }
+
+ @Test
+ void shouldExtractMessagesWithCustomPattern() {
+ IntegrateChangesFromWorkdirException exception =
+ withPattern(Pattern.compile("-custom- (.*)"))
+ .forMessage(REPOSITORY, "to be ignored\n-custom- line\n");
+
+ assertThat(exception.getAdditionalMessages())
+ .extracting("message")
+ .containsExactly("line");
+ assertThat(exception.getCode()).isEqualTo(CODE_WITH_ADDITIONAL_MESSAGES);
+ }
+
+ @Test
+ void shouldCreateSpecialMessageForMissingAdditionalMessages() {
+ IntegrateChangesFromWorkdirException exception =
+ forMessage(REPOSITORY, "There is no message");
+
+ assertThat(exception.getAdditionalMessages()).isEmpty();
+ assertThat(exception.getCode()).isEqualTo(CODE_WITHOUT_ADDITIONAL_MESSAGES);
+ }
+}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookMessageProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookMessageProvider.java
index f5e451df84..b7ca70fac9 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookMessageProvider.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookMessageProvider.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository.api;
//~--- non-JDK imports --------------------------------------------------------
@@ -71,7 +71,9 @@ public final class GitHookMessageProvider implements HookMessageProvider
@Override
public void sendMessage(String message)
{
- GitHooks.sendPrefixedMessage(receivePack, message);
+ if (!receivePack.isQuiet()) {
+ GitHooks.sendPrefixedMessage(receivePack, message);
+ }
}
//~--- fields ---------------------------------------------------------------
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java
index c083123242..8ad6e3ddf4 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java
@@ -59,6 +59,7 @@ import static java.util.Optional.of;
import static sonia.scm.ContextEntry.ContextBuilder.entity;
import static sonia.scm.NotFoundException.notFound;
import static sonia.scm.repository.GitUtil.getBranchIdOrCurrentHead;
+import static sonia.scm.repository.spi.IntegrateChangesFromWorkdirException.forMessage;
//~--- JDK imports ------------------------------------------------------------
@@ -255,7 +256,7 @@ class AbstractGitCommand {
.findAny()
.ifPresent(remoteRefUpdate -> {
logger.info("message for failed push: {}", pushResult.getMessages());
- throw new IntegrateChangesFromWorkdirException(repository, "could not push changes into central repository: " + remoteRefUpdate.getStatus());
+ throw forMessage(repository, pushResult.getMessages());
});
} catch (GitAPIException e) {
throw new InternalRepositoryException(repository, "could not push changes into central repository", e);
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java
index 3a291b4501..7319a312ff 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java
@@ -37,6 +37,8 @@ import sonia.scm.repository.api.BranchRequest;
import sonia.scm.repository.work.WorkingCopy;
import sonia.scm.user.User;
+import java.io.IOException;
+
/**
* Mercurial implementation of the {@link BranchCommand}.
* Note that this creates an empty commit to "persist" the new branch.
@@ -106,9 +108,8 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand {
PullCommand pullCommand = PullCommand.on(workingCopy.getCentralRepository());
workingCopyFactory.configure(pullCommand);
pullCommand.execute(workingCopy.getDirectory().getAbsolutePath());
- } catch (Exception e) {
- // TODO handle failed update
- throw new IntegrateChangesFromWorkdirException(getRepository(),
+ } catch (IOException e) {
+ throw new InternalRepositoryException(getRepository(),
String.format("Could not pull changes '%s' into central repository", branch),
e);
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgDiffCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgDiffCommand.java
index 605ce5555a..08c42a023e 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgDiffCommand.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgDiffCommand.java
@@ -24,71 +24,65 @@
package sonia.scm.repository.spi;
-//~--- non-JDK imports --------------------------------------------------------
-
+import com.aragost.javahg.Repository;
import com.google.common.base.Strings;
import com.google.common.io.ByteStreams;
-import com.google.common.io.Closeables;
import sonia.scm.repository.api.DiffCommandBuilder;
import sonia.scm.repository.api.DiffFormat;
import sonia.scm.repository.spi.javahg.HgDiffInternalCommand;
import sonia.scm.web.HgUtil;
+import javax.annotation.Nonnull;
+import java.io.IOException;
import java.io.InputStream;
-
-//~--- JDK imports ------------------------------------------------------------
+import java.io.OutputStream;
/**
*
* @author Sebastian Sdorra
*/
-public class HgDiffCommand extends AbstractCommand implements DiffCommand
-{
+public class HgDiffCommand extends AbstractCommand implements DiffCommand {
- /**
- * Constructs ...
- *
- * @param context
- *
- */
- HgDiffCommand(HgCommandContext context)
- {
+ HgDiffCommand(HgCommandContext context) {
super(context);
}
- //~--- get methods ----------------------------------------------------------
-
@Override
- public DiffCommandBuilder.OutputStreamConsumer getDiffResult(DiffCommandRequest request)
- {
+ public DiffCommandBuilder.OutputStreamConsumer getDiffResult(DiffCommandRequest request) {
return output -> {
- com.aragost.javahg.Repository hgRepo = open();
-
- HgDiffInternalCommand cmd = HgDiffInternalCommand.on(hgRepo);
- DiffFormat format = request.getFormat();
-
- if (format == DiffFormat.GIT)
- {
- cmd.git();
- }
-
- cmd.change(HgUtil.getRevision(request.getRevision()));
-
- InputStream inputStream = null;
-
+ Repository hgRepo = open();
try {
-
- if (!Strings.isNullOrEmpty(request.getPath())) {
- inputStream = cmd.stream(hgRepo.file(request.getPath()));
- } else {
- inputStream = cmd.stream();
- }
-
- ByteStreams.copy(inputStream, output);
-
+ diff(hgRepo, request, output);
} finally {
- Closeables.close(inputStream, true);
+ getContext().close();
}
};
}
+
+ @SuppressWarnings("UnstableApiUsage")
+ private void diff(Repository hgRepo, DiffCommandRequest request, OutputStream output) throws IOException {
+ HgDiffInternalCommand cmd = createDiffCommand(hgRepo, request);
+ try (InputStream inputStream = streamDiff(hgRepo, cmd, request.getPath())) {
+ ByteStreams.copy(inputStream, output);
+ }
+ }
+
+ @Nonnull
+ private HgDiffInternalCommand createDiffCommand(Repository hgRepo, DiffCommandRequest request) {
+ HgDiffInternalCommand cmd = HgDiffInternalCommand.on(hgRepo);
+ DiffFormat format = request.getFormat();
+ if (format == DiffFormat.GIT) {
+ cmd.git();
+ }
+ cmd.change(HgUtil.getRevision(request.getRevision()));
+ return cmd;
+ }
+
+ private InputStream streamDiff(Repository hgRepo, HgDiffInternalCommand cmd, String path) {
+ if (!Strings.isNullOrEmpty(path)) {
+ return cmd.stream(hgRepo.file(path));
+ } else {
+ return cmd.stream();
+ }
+ }
}
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java
index 10e444b03a..aaea12eeac 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java
@@ -41,11 +41,13 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
+import java.util.regex.Pattern;
@SuppressWarnings("java:S3252") // it is ok for javahg classes to access static method of subtype
public class HgModifyCommand implements ModifyCommand {
private static final Logger LOG = LoggerFactory.getLogger(HgModifyCommand.class);
+ static final Pattern HG_MESSAGE_PATTERN = Pattern.compile(".*\\[SCM\\](?: Error:)? (.*)");
private final HgCommandContext context;
private final HgWorkingCopyFactory workingCopyFactory;
@@ -128,8 +130,12 @@ public class HgModifyCommand implements ModifyCommand {
com.aragost.javahg.commands.PullCommand pullCommand = PullCommand.on(workingCopy.getCentralRepository());
workingCopyFactory.configure(pullCommand);
return pullCommand.execute(workingCopy.getDirectory().getAbsolutePath());
- } catch (Exception e) {
- throw new IntegrateChangesFromWorkdirException(context.getScmRepository(),
+ } catch (ExecutionException e) {
+ throw IntegrateChangesFromWorkdirException
+ .withPattern(HG_MESSAGE_PATTERN)
+ .forMessage(context.getScmRepository(), e.getMessage());
+ } catch (IOException e) {
+ throw new InternalRepositoryException(context.getScmRepository(),
String.format("Could not pull modify changes from working copy to central repository for branch %s", request.getBranch()),
e);
}
diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgDiffCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgDiffCommandTest.java
new file mode 100644
index 0000000000..9775a5cd42
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgDiffCommandTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import org.junit.Test;
+import sonia.scm.repository.api.DiffCommandBuilder;
+import sonia.scm.repository.api.DiffFormat;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+public class HgDiffCommandTest extends AbstractHgCommandTestBase {
+
+ @Test
+ public void shouldCreateDiff() throws IOException {
+ String content = diff(cmdContext, "3049df33fdbbded08b707bac3eccd0f7b453c58b");
+ assertThat(content).contains("+e");
+ }
+
+ @Test
+ public void shouldCreateGitDiff() throws IOException {
+ DiffCommandRequest request = new DiffCommandRequest();
+ request.setRevision("3049df33fdbbded08b707bac3eccd0f7b453c58b");
+ request.setFormat(DiffFormat.GIT);
+
+ String content = diff(cmdContext, request);
+ assertThat(content).contains("git");
+ }
+
+ @Test
+ public void shouldCloseContent() throws IOException {
+ HgCommandContext context = spy(cmdContext);
+ String content = diff(context, "a9bacaf1b7fa0cebfca71fed4e59ed69a6319427");
+ assertThat(content).contains("+b");
+ verify(context).close();
+ }
+
+ private String diff(HgCommandContext context, String revision) throws IOException {
+ DiffCommandRequest request = new DiffCommandRequest();
+ request.setRevision(revision);
+ return diff(context, request);
+ }
+
+ private String diff(HgCommandContext context, DiffCommandRequest request) throws IOException {
+ HgDiffCommand diff = new HgDiffCommand(context);
+ DiffCommandBuilder.OutputStreamConsumer consumer = diff.getDiffResult(request);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ consumer.accept(baos);
+ return baos.toString("UTF-8");
+ }
+
+}
diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModifyCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModifyCommandTest.java
index 0ccc7d94c6..168d741bcb 100644
--- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModifyCommandTest.java
+++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModifyCommandTest.java
@@ -38,6 +38,7 @@ import sonia.scm.repository.work.WorkdirProvider;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.regex.Matcher;
import static org.assertj.core.api.Assertions.assertThat;
@@ -180,4 +181,18 @@ public class HgModifyCommandTest extends AbstractHgCommandTestBase {
public void shouldThrowNoChangesMadeExceptionIfEmptyLocalChangesetAfterRequest() {
hgModifyCommand.execute(new ModifyCommandRequest());
}
+
+ @Test
+ public void shouldExtractSimpleMessage() {
+ Matcher matcher = HgModifyCommand.HG_MESSAGE_PATTERN.matcher("[SCM] This is a simple message");
+ matcher.matches();
+ assertThat(matcher.group(1)).isEqualTo("This is a simple message");
+ }
+
+ @Test
+ public void shouldExtractErrorMessage() {
+ Matcher matcher = HgModifyCommand.HG_MESSAGE_PATTERN.matcher("[SCM] Error: This is an error message");
+ matcher.matches();
+ assertThat(matcher.group(1)).isEqualTo("This is an error message");
+ }
}
diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SCMSvnDiffGenerator.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SCMSvnDiffGenerator.java
index 81e40023f6..c7668c0520 100644
--- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SCMSvnDiffGenerator.java
+++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SCMSvnDiffGenerator.java
@@ -565,6 +565,8 @@ public class SCMSvnDiffGenerator implements ISvnDiffGenerator {
}
if (!useGitFormat){
displayBinary(mimeType1, mimeType2, outputStream, leftIsBinary, rightIsBinary);
+ } else {
+ displayBinaryGit(target, operation, outputStream);
}
return;
@@ -590,6 +592,26 @@ public class SCMSvnDiffGenerator implements ISvnDiffGenerator {
}
}
+ private void displayBinaryGit(SvnTarget target, SvnDiffCallback.OperationKind operation, OutputStream outputStream) throws SVNException {
+ String path1 = operation == SvnDiffCallback.OperationKind.Added ? "/dev/null" : getRelativeToRootPath(target, originalTarget1);
+ String path2 = operation == SvnDiffCallback.OperationKind.Deleted ? "/dev/null" : getRelativeToRootPath(target, originalTarget2);
+ try {
+ displayString(outputStream, formatPath(path1, "---", "a"));
+ displayString(outputStream, formatPath(path2, "+++", "b"));
+ displayString(outputStream, "Binary files differ");
+ } catch (IOException e) {
+ wrapException(e);
+ }
+ }
+
+ private String formatPath(String path, String start, String aOrB) {
+ if (path.equals("/dev/null")) {
+ return String.format("%s %s\n", start, path);
+ } else {
+ return String.format("%s %s/%s\n", start, aOrB, path);
+ }
+ }
+
private void displayBinary(String mimeType1, String mimeType2, OutputStream outputStream, boolean leftIsBinary, boolean rightIsBinary) throws SVNException {
displayCannotDisplayFileMarkedBinary(outputStream);
diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLookupCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLookupCommand.java
new file mode 100644
index 0000000000..ed79e40e25
--- /dev/null
+++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLookupCommand.java
@@ -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;
+
+import lombok.extern.slf4j.Slf4j;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.io.SVNRepository;
+
+import java.util.Optional;
+
+@Slf4j
+public class SvnLookupCommand extends AbstractSvnCommand implements LookupCommand {
+
+ protected SvnLookupCommand(SvnContext context) {
+ super(context);
+ }
+
+ @Override
+ public Optional lookup(LookupCommandRequest request) {
+ try {
+ if (request.getArgs().length > 1 && "propget".equalsIgnoreCase(request.getArgs()[0])) {
+ return lookupProps(request);
+ }
+ } catch (SVNException e) {
+ log.error("Lookup failed: ", e);
+ }
+
+ return Optional.empty();
+ }
+
+ private Optional lookupProps(LookupCommandRequest request) throws SVNException {
+ if (request.getArgs()[1].equalsIgnoreCase("uuid")) {
+ if (!request.getType().equals(String.class)) {
+ throw new IllegalArgumentException("uuid can only be returned as String");
+ }
+ SVNRepository repository = context.open();
+ return Optional.of((T) repository.getRepositoryUUID(true));
+ }
+ log.debug("No result found on lookup");
+ return Optional.empty();
+ }
+}
diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModifyCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModifyCommand.java
index 849d5619a5..326b710874 100644
--- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModifyCommand.java
+++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModifyCommand.java
@@ -27,6 +27,7 @@ package sonia.scm.repository.spi;
import org.apache.shiro.SecurityUtils;
import org.tmatesoft.svn.core.SVNCommitInfo;
import org.tmatesoft.svn.core.SVNDepth;
+import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNWCClient;
@@ -40,9 +41,14 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.regex.Pattern;
+
+import static sonia.scm.repository.spi.IntegrateChangesFromWorkdirException.withPattern;
public class SvnModifyCommand implements ModifyCommand {
+ public static final Pattern SVN_ERROR_PATTERN = Pattern.compile(".*E" + SVNErrorCode.CANCELLED.getCode() + ": (.*)");
+
private final SvnContext context;
private final SvnWorkingCopyFactory workingCopyFactory;
private final Repository repository;
@@ -81,7 +87,7 @@ public class SvnModifyCommand implements ModifyCommand {
);
return String.valueOf(svnCommitInfo.getNewRevision());
} catch (SVNException e) {
- throw new InternalRepositoryException(repository, "could not commit changes on repository");
+ throw withPattern(SVN_ERROR_PATTERN).forMessage(repository, e.getErrorMessage().getRootErrorMessage().getFullMessage());
}
}
diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java
index 1548fba869..f948a7168f 100644
--- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java
+++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java
@@ -46,7 +46,7 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider
//J-
public static final Set COMMANDS = ImmutableSet.of(
Command.BLAME, Command.BROWSE, Command.CAT, Command.DIFF,
- Command.LOG, Command.BUNDLE, Command.UNBUNDLE, Command.MODIFY
+ Command.LOG, Command.BUNDLE, Command.UNBUNDLE, Command.MODIFY, Command.LOOKUP
);
//J+
@@ -148,14 +148,21 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider
return new SvnLogCommand(context);
}
+ @Override
public ModificationsCommand getModificationsCommand() {
return new SvnModificationsCommand(context);
}
+ @Override
public ModifyCommand getModifyCommand() {
return new SvnModifyCommand(context, workingCopyFactory);
}
+ @Override
+ public LookupCommand getLookupCommand() {
+ return new SvnLookupCommand(context);
+ }
+
/**
* Method description
*
diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLookupCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLookupCommandTest.java
new file mode 100644
index 0000000000..df4fedae92
--- /dev/null
+++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLookupCommandTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.io.SVNRepository;
+
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class SvnLookupCommandTest {
+
+ @Mock
+ SvnContext context;
+
+ @Mock
+ SVNRepository svnRepository;
+
+ @InjectMocks
+ SvnLookupCommand command;
+
+ @Test
+ void shouldReturnEmptyOptional() {
+ LookupCommandRequest request = new LookupCommandRequest();
+ request.setType(String.class);
+ request.setArgs(new String[]{"propget"});
+
+ Optional