feature_branch into integration_branch do this:
+ *
+ * repositoryService.gerMergeCommand()
+ * .setBranchToMerge("feature_branch")
+ * .setTargetBranch("integration_branch")
+ * .executeMerge();
+ *
+ *
+ * If the merge is successful, the result will look like this:
+ *
+ * O <- Merge result (new head of integration_branch)
+ * |\
+ * | \
+ * old integration_branch -> O O <- feature_branch
+ * | |
+ * O O
+ *
+ *
+ * To check whether they can be merged without conflicts beforehand do this:
+ *
+ * repositoryService.gerMergeCommand()
+ * .setBranchToMerge("feature_branch")
+ * .setTargetBranch("integration_branch")
+ * .dryRun()
+ * .isMergeable();
+ *
+ *
+ * Keep in mind that you should always check the result of a merge even though you may have done a dry run
+ * beforehand, because the branches can change between the dry run and the actual merge.
+ *
+ * @since 2.0.0
+ */
+public class MergeCommandBuilder {
+
+ private final MergeCommand mergeCommand;
+ private final MergeCommandRequest request = new MergeCommandRequest();
+
+ MergeCommandBuilder(MergeCommand mergeCommand) {
+ this.mergeCommand = mergeCommand;
+ }
+
+ /**
+ * Use this to set the branch that should be merged into the target branch.
+ *
+ * This is mandatory.
+ *
+ * @return This builder instance.
+ */
+ public MergeCommandBuilder setBranchToMerge(String branchToMerge) {
+ request.setBranchToMerge(branchToMerge);
+ return this;
+ }
+
+ /**
+ * Use this to set the target branch the other branch should be merged into.
+ *
+ * This is mandatory.
+ *
+ * @return This builder instance.
+ */
+ public MergeCommandBuilder setTargetBranch(String targetBranch) {
+ request.setTargetBranch(targetBranch);
+ return this;
+ }
+
+ /**
+ * Use this to set the author of the merge commit manually. If this is omitted, the currently logged in user will be
+ * used instead.
+ *
+ * This is optional and for {@link #executeMerge()} only.
+ *
+ * @return This builder instance.
+ */
+ public MergeCommandBuilder setAuthor(Person author) {
+ request.setAuthor(author);
+ return this;
+ }
+
+ /**
+ * Use this to set a template for the commit message. If no message is set, a default message will be used.
+ *
+ * You can use the placeholder {0} for the branch to be merged and {1} for the target
+ * branch, eg.:
+ *
+ *
+ * ...setMessageTemplate("Merge of {0} into {1}")...
+ *
+ *
+ * This is optional and for {@link #executeMerge()} only.
+ *
+ * @return This builder instance.
+ */
+ public MergeCommandBuilder setMessageTemplate(String messageTemplate) {
+ request.setMessageTemplate(messageTemplate);
+ return this;
+ }
+
+ /**
+ * Use this to reset the command.
+ * @return This builder instance.
+ */
+ public MergeCommandBuilder reset() {
+ request.reset();
+ return this;
+ }
+
+ /**
+ * Use this to actually do the merge. If an automatic merge is not possible, {@link MergeCommandResult#isSuccess()}
+ * will return false.
+ *
+ * @return The result of the merge.
+ */
+ public MergeCommandResult executeMerge() {
+ Preconditions.checkArgument(request.isValid(), "revision to merge and target revision is required");
+ return mergeCommand.merge(request);
+ }
+
+ /**
+ * Use this to check whether the given branches can be merged autmatically. If this is possible,
+ * {@link MergeDryRunCommandResult#isMergeable()} will return true.
+ *
+ * @return The result whether the given branches can be merged automatically.
+ */
+ public MergeDryRunCommandResult dryRun() {
+ Preconditions.checkArgument(request.isValid(), "revision to merge and target revision is required");
+ return mergeCommand.dryRun(request);
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandResult.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandResult.java
new file mode 100644
index 0000000000..53f712cddc
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandResult.java
@@ -0,0 +1,44 @@
+package sonia.scm.repository.api;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.unmodifiableCollection;
+
+/**
+ * This class keeps the result of a merge of branches. Use {@link #isSuccess()} to check whether the merge was
+ * sucessfully executed. If the result is false the merge could not be done without conflicts. In this
+ * case you can use {@link #getFilesWithConflict()} to get a list of files with merge conflicts.
+ */
+public class MergeCommandResult {
+ private final Collectiontrue, the merge was successfull. If this returns false there were
+ * merge conflicts. In this case you can use {@link #getFilesWithConflict()} to check what files could not be merged.
+ */
+ public boolean isSuccess() {
+ return filesWithConflict.isEmpty();
+ }
+
+ /**
+ * If the merge was not successful ({@link #isSuccess()} returns false) this will give you a list of
+ * file paths that could not be merged automatically.
+ */
+ public Collectiontrue, when an automatic merge is possible at the moment; false
+ * otherwise.
+ */
+ public boolean isMergeable() {
+ return mergeable;
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/ModificationsCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/ModificationsCommandBuilder.java
index 6459b47cd5..498746cc60 100644
--- a/scm-core/src/main/java/sonia/scm/repository/api/ModificationsCommandBuilder.java
+++ b/scm-core/src/main/java/sonia/scm/repository/api/ModificationsCommandBuilder.java
@@ -13,7 +13,6 @@ import sonia.scm.repository.Modifications;
import sonia.scm.repository.PreProcessorUtil;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryCacheKey;
-import sonia.scm.repository.RevisionNotFoundException;
import sonia.scm.repository.spi.ModificationsCommand;
import sonia.scm.repository.spi.ModificationsCommandRequest;
@@ -67,7 +66,7 @@ public final class ModificationsCommandBuilder {
return this;
}
- public Modifications getModifications() throws IOException, RevisionNotFoundException {
+ public Modifications getModifications() throws IOException {
Modifications modifications;
if (disableCache) {
log.info("Get modifications for {} with disabled cache", 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 bdd6e4b320..fe0529e6b5 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
@@ -79,6 +79,7 @@ import java.util.stream.Stream;
* @apiviz.uses sonia.scm.repository.api.PushCommandBuilder
* @apiviz.uses sonia.scm.repository.api.BundleCommandBuilder
* @apiviz.uses sonia.scm.repository.api.UnbundleCommandBuilder
+ * @apiviz.uses sonia.scm.repository.api.MergeCommandBuilder
* @since 1.17
*/
@Slf4j
@@ -353,6 +354,22 @@ public final class RepositoryService implements Closeable {
repository);
}
+ /**
+ * The merge command executes a merge of two branches. It is possible to do a dry run to check, whether the given
+ * branches can be merged without conflicts.
+ *
+ * @return instance of {@link MergeCommandBuilder}
+ * @throws CommandNotSupportedException if the command is not supported
+ * by the implementation of the repository service provider.
+ * @since 2.0.0
+ */
+ public MergeCommandBuilder gerMergeCommand() {
+ logger.debug("create unbundle command for repository {}",
+ repository.getNamespaceAndName());
+
+ return new MergeCommandBuilder(provider.getMergeCommand());
+ }
+
/**
* Returns true if the command is supported by the repository service.
*
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java
index fbb1ee6b58..8db3ab7546 100644
--- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java
+++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java
@@ -45,6 +45,7 @@ import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.HandlerEventType;
+import sonia.scm.NotFoundException;
import sonia.scm.cache.Cache;
import sonia.scm.cache.CacheManager;
import sonia.scm.config.ScmConfiguration;
@@ -57,7 +58,6 @@ import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryCacheKeyPredicate;
import sonia.scm.repository.RepositoryEvent;
import sonia.scm.repository.RepositoryManager;
-import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.repository.spi.RepositoryServiceProvider;
import sonia.scm.repository.spi.RepositoryServiceResolver;
@@ -65,6 +65,9 @@ import sonia.scm.security.ScmSecurityException;
import java.util.Set;
+import static sonia.scm.ContextEntry.ContextBuilder.entity;
+import static sonia.scm.NotFoundException.notFound;
+
//~--- JDK imports ------------------------------------------------------------
/**
@@ -161,7 +164,7 @@ public final class RepositoryServiceFactory
* @return a implementation of RepositoryService
* for the given type of repository
*
- * @throws RepositoryNotFoundException if no repository
+ * @throws NotFoundException if no repository
* with the given id is available
* @throws RepositoryServiceNotFoundException if no repository service
* implementation for this kind of repository is available
@@ -169,7 +172,7 @@ public final class RepositoryServiceFactory
* @throws ScmSecurityException if current user has not read permissions
* for that repository
*/
- public RepositoryService create(String repositoryId) throws RepositoryNotFoundException {
+ public RepositoryService create(String repositoryId) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(repositoryId),
"a non empty repositoryId is required");
@@ -177,7 +180,7 @@ public final class RepositoryServiceFactory
if (repository == null)
{
- throw new RepositoryNotFoundException(repositoryId);
+ throw new NotFoundException(Repository.class, repositoryId);
}
return create(repository);
@@ -192,7 +195,7 @@ public final class RepositoryServiceFactory
* @return a implementation of RepositoryService
* for the given type of repository
*
- * @throws RepositoryNotFoundException if no repository
+ * @throws NotFoundException if no repository
* with the given id is available
* @throws RepositoryServiceNotFoundException if no repository service
* implementation for this kind of repository is available
@@ -201,7 +204,6 @@ public final class RepositoryServiceFactory
* for that repository
*/
public RepositoryService create(NamespaceAndName namespaceAndName)
- throws RepositoryNotFoundException
{
Preconditions.checkArgument(namespaceAndName != null,
"a non empty namespace and name is required");
@@ -210,7 +212,7 @@ public final class RepositoryServiceFactory
if (repository == null)
{
- throw new RepositoryNotFoundException(namespaceAndName);
+ throw notFound(entity(namespaceAndName));
}
return create(repository);
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommand.java
index be679f9df1..ee37d6243e 100644
--- a/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommand.java
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommand.java
@@ -35,7 +35,6 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
-import sonia.scm.NotFoundException;
import sonia.scm.repository.BrowserResult;
import java.io.IOException;
@@ -60,5 +59,5 @@ public interface BrowseCommand
*
* @throws IOException
*/
- BrowserResult getBrowserResult(BrowseCommandRequest request) throws IOException, NotFoundException;
+ BrowserResult getBrowserResult(BrowseCommandRequest request) throws IOException;
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/CatCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/CatCommand.java
index 06f242783b..81600230db 100644
--- a/scm-core/src/main/java/sonia/scm/repository/spi/CatCommand.java
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/CatCommand.java
@@ -33,9 +33,6 @@
package sonia.scm.repository.spi;
-import sonia.scm.repository.PathNotFoundException;
-import sonia.scm.repository.RevisionNotFoundException;
-
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -47,7 +44,7 @@ import java.io.OutputStream;
*/
public interface CatCommand {
- void getCatResult(CatCommandRequest request, OutputStream output) throws IOException, RevisionNotFoundException, PathNotFoundException;
+ void getCatResult(CatCommandRequest request, OutputStream output) throws IOException;
- InputStream getCatResultStream(CatCommandRequest request) throws IOException, RevisionNotFoundException, PathNotFoundException;
+ InputStream getCatResultStream(CatCommandRequest request) throws IOException;
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/DiffCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/DiffCommand.java
index 105f4e48e1..bba42cd86d 100644
--- a/scm-core/src/main/java/sonia/scm/repository/spi/DiffCommand.java
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/DiffCommand.java
@@ -33,8 +33,6 @@
package sonia.scm.repository.spi;
-import sonia.scm.repository.RevisionNotFoundException;
-
import java.io.IOException;
import java.io.OutputStream;
@@ -56,5 +54,5 @@ public interface DiffCommand
* @throws IOException
* @throws RuntimeException
*/
- public void getDiffResult(DiffCommandRequest request, OutputStream output) throws IOException, RevisionNotFoundException;
+ public void getDiffResult(DiffCommandRequest request, OutputStream output) throws IOException;
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/HookEventFacade.java b/scm-core/src/main/java/sonia/scm/repository/spi/HookEventFacade.java
index 52ef68f272..4bbe61ea41 100644
--- a/scm-core/src/main/java/sonia/scm/repository/spi/HookEventFacade.java
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/HookEventFacade.java
@@ -40,10 +40,12 @@ import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryHookEvent;
import sonia.scm.repository.RepositoryHookType;
import sonia.scm.repository.RepositoryManager;
-import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.api.HookContext;
import sonia.scm.repository.api.HookContextFactory;
+import static sonia.scm.ContextEntry.ContextBuilder.entity;
+import static sonia.scm.NotFoundException.notFound;
+
/**
*
* @author Sebastian Sdorra
@@ -71,18 +73,18 @@ public final class HookEventFacade
//~--- methods --------------------------------------------------------------
- public HookEventHandler handle(String id) throws RepositoryNotFoundException {
+ public HookEventHandler handle(String id) {
return handle(repositoryManagerProvider.get().get(id));
}
- public HookEventHandler handle(NamespaceAndName namespaceAndName) throws RepositoryNotFoundException {
+ public HookEventHandler handle(NamespaceAndName namespaceAndName) {
return handle(repositoryManagerProvider.get().get(namespaceAndName));
}
- public HookEventHandler handle(Repository repository) throws RepositoryNotFoundException {
+ public HookEventHandler handle(Repository repository) {
if (repository == null)
{
- throw new RepositoryNotFoundException(repository);
+ throw notFound(entity(repository));
}
return new HookEventHandler(repositoryManagerProvider.get(),
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/LogCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/LogCommand.java
index e4a7f3437b..f4babcee72 100644
--- a/scm-core/src/main/java/sonia/scm/repository/spi/LogCommand.java
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/LogCommand.java
@@ -37,7 +37,6 @@ package sonia.scm.repository.spi;
import sonia.scm.repository.Changeset;
import sonia.scm.repository.ChangesetPagingResult;
-import sonia.scm.repository.RevisionNotFoundException;
import java.io.IOException;
@@ -50,7 +49,7 @@ import java.io.IOException;
*/
public interface LogCommand {
- Changeset getChangeset(String id) throws IOException, RevisionNotFoundException;
+ Changeset getChangeset(String id) throws IOException;
- ChangesetPagingResult getChangesets(LogCommandRequest request) throws IOException, RevisionNotFoundException;
+ ChangesetPagingResult getChangesets(LogCommandRequest request) throws IOException;
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommand.java
new file mode 100644
index 0000000000..0a3680f6b3
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommand.java
@@ -0,0 +1,10 @@
+package sonia.scm.repository.spi;
+
+import sonia.scm.repository.api.MergeCommandResult;
+import sonia.scm.repository.api.MergeDryRunCommandResult;
+
+public interface MergeCommand {
+ MergeCommandResult merge(MergeCommandRequest request);
+
+ MergeDryRunCommandResult dryRun(MergeCommandRequest request);
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java
new file mode 100644
index 0000000000..baf03a0aef
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java
@@ -0,0 +1,93 @@
+package sonia.scm.repository.spi;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import sonia.scm.Validateable;
+import sonia.scm.repository.Person;
+import sonia.scm.util.Util;
+
+import java.io.Serializable;
+
+public class MergeCommandRequest implements Validateable, Resetable, Serializable, Cloneable {
+
+ private static final long serialVersionUID = -2650236557922431528L;
+
+ private String branchToMerge;
+ private String targetBranch;
+ private Person author;
+ private String messageTemplate;
+
+ public String getBranchToMerge() {
+ return branchToMerge;
+ }
+
+ public void setBranchToMerge(String branchToMerge) {
+ this.branchToMerge = branchToMerge;
+ }
+
+ public String getTargetBranch() {
+ return targetBranch;
+ }
+
+ public void setTargetBranch(String targetBranch) {
+ this.targetBranch = targetBranch;
+ }
+
+ public Person getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(Person author) {
+ this.author = author;
+ }
+
+ public String getMessageTemplate() {
+ return messageTemplate;
+ }
+
+ public void setMessageTemplate(String messageTemplate) {
+ this.messageTemplate = messageTemplate;
+ }
+
+ public boolean isValid() {
+ return !Strings.isNullOrEmpty(getBranchToMerge())
+ && !Strings.isNullOrEmpty(getTargetBranch());
+ }
+
+ public void reset() {
+ this.setBranchToMerge(null);
+ this.setTargetBranch(null);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+
+ final MergeCommandRequest other = (MergeCommandRequest) obj;
+
+ return Objects.equal(branchToMerge, other.branchToMerge)
+ && Objects.equal(targetBranch, other.targetBranch)
+ && Objects.equal(author, other.author);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(branchToMerge, targetBranch, author);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("branchToMerge", branchToMerge)
+ .add("targetBranch", targetBranch)
+ .add("author", author)
+ .toString();
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/ModificationsCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/ModificationsCommand.java
index e9b40e8a17..322468f827 100644
--- a/scm-core/src/main/java/sonia/scm/repository/spi/ModificationsCommand.java
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/ModificationsCommand.java
@@ -32,7 +32,6 @@
package sonia.scm.repository.spi;
import sonia.scm.repository.Modifications;
-import sonia.scm.repository.RevisionNotFoundException;
import java.io.IOException;
@@ -46,8 +45,8 @@ import java.io.IOException;
*/
public interface ModificationsCommand {
- Modifications getModifications(String revision) throws IOException, RevisionNotFoundException;
+ Modifications getModifications(String revision) throws IOException;
- Modifications getModifications(ModificationsCommandRequest request) throws IOException, RevisionNotFoundException;
+ Modifications getModifications(ModificationsCommandRequest request) throws IOException;
}
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 c66c56c0f1..77201d1a72 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
@@ -251,4 +251,12 @@ public abstract class RepositoryServiceProvider implements Closeable
{
throw new CommandNotSupportedException(Command.UNBUNDLE);
}
+
+ /**
+ * @since 2.0
+ */
+ public MergeCommand getMergeCommand()
+ {
+ throw new CommandNotSupportedException(Command.MERGE);
+ }
}
diff --git a/scm-core/src/main/java/sonia/scm/user/ChangePasswordNotAllowedException.java b/scm-core/src/main/java/sonia/scm/user/ChangePasswordNotAllowedException.java
index fda5e69323..caa35e0b88 100644
--- a/scm-core/src/main/java/sonia/scm/user/ChangePasswordNotAllowedException.java
+++ b/scm-core/src/main/java/sonia/scm/user/ChangePasswordNotAllowedException.java
@@ -1,11 +1,19 @@
package sonia.scm.user;
-public class ChangePasswordNotAllowedException extends RuntimeException {
+import sonia.scm.ContextEntry;
+import sonia.scm.ExceptionWithContext;
+public class ChangePasswordNotAllowedException extends ExceptionWithContext {
+
+ private static final String CODE = "9BR7qpDAe1";
public static final String WRONG_USER_TYPE = "User of type %s are not allowed to change password";
- public ChangePasswordNotAllowedException(String type) {
- super(String.format(WRONG_USER_TYPE, type));
+ public ChangePasswordNotAllowedException(ContextEntry.ContextBuilder context, String type) {
+ super(context.build(), String.format(WRONG_USER_TYPE, type));
}
+ @Override
+ public String getCode() {
+ return CODE;
+ }
}
diff --git a/scm-core/src/main/java/sonia/scm/user/InvalidPasswordException.java b/scm-core/src/main/java/sonia/scm/user/InvalidPasswordException.java
index 870430a1bb..93a6a7c1d1 100644
--- a/scm-core/src/main/java/sonia/scm/user/InvalidPasswordException.java
+++ b/scm-core/src/main/java/sonia/scm/user/InvalidPasswordException.java
@@ -1,8 +1,18 @@
package sonia.scm.user;
-public class InvalidPasswordException extends RuntimeException {
+import sonia.scm.ContextEntry;
+import sonia.scm.ExceptionWithContext;
- public InvalidPasswordException() {
- super("The given Password does not match with the stored one.");
+public class InvalidPasswordException extends ExceptionWithContext {
+
+ private static final String CODE = "8YR7aawFW1";
+
+ public InvalidPasswordException(ContextEntry.ContextBuilder context) {
+ super(context.build(), "The given old password does not match with the stored one.");
+ }
+
+ @Override
+ public String getCode() {
+ return CODE;
}
}
diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java
index 7b6d5cb039..e2a2218d34 100644
--- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java
+++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java
@@ -44,6 +44,7 @@ public class VndMediaType {
public static final String ME = PREFIX + "me" + SUFFIX;
public static final String SOURCE = PREFIX + "source" + SUFFIX;
+ public static final String ERROR_TYPE = PREFIX + "error" + SUFFIX;
private VndMediaType() {
}
diff --git a/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java
index b88dda8a97..ffe6ecc787 100644
--- a/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java
+++ b/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java
@@ -301,7 +301,7 @@ public class AuthenticationFilter extends HttpFilter
}
}
- chain.doFilter(new SecurityHttpServletRequestWrapper(request, username),
+ chain.doFilter(new PropagatePrincipleServletRequestWrapper(request, username),
response);
}
diff --git a/scm-core/src/main/java/sonia/scm/web/filter/SecurityHttpServletRequestWrapper.java b/scm-core/src/main/java/sonia/scm/web/filter/PropagatePrincipleServletRequestWrapper.java
similarity index 78%
rename from scm-core/src/main/java/sonia/scm/web/filter/SecurityHttpServletRequestWrapper.java
rename to scm-core/src/main/java/sonia/scm/web/filter/PropagatePrincipleServletRequestWrapper.java
index 2cd78ce807..2b40b0e73f 100644
--- a/scm-core/src/main/java/sonia/scm/web/filter/SecurityHttpServletRequestWrapper.java
+++ b/scm-core/src/main/java/sonia/scm/web/filter/PropagatePrincipleServletRequestWrapper.java
@@ -38,37 +38,17 @@ package sonia.scm.web.filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
-/**
- *
- * @author Sebastian Sdorra
- */
-public class SecurityHttpServletRequestWrapper extends HttpServletRequestWrapper
-{
+public class PropagatePrincipleServletRequestWrapper extends HttpServletRequestWrapper {
- /**
- * Constructs ...
- *
- *
- * @param request
- * @param principal
- */
- public SecurityHttpServletRequestWrapper(HttpServletRequest request,
- String principal)
- {
+ private final String principal;
+
+ public PropagatePrincipleServletRequestWrapper(HttpServletRequest request, String principal) {
super(request);
this.principal = principal;
}
- //~--- get methods ----------------------------------------------------------
-
@Override
- public String getRemoteUser()
- {
+ public String getRemoteUser() {
return principal;
}
-
- //~--- fields ---------------------------------------------------------------
-
- /** Field description */
- private final String principal;
}
diff --git a/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java b/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java
index 93b0752766..50bde53ae1 100644
--- a/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java
+++ b/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java
@@ -43,7 +43,6 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.AlreadyExistsException;
-import sonia.scm.NotFoundException;
import sonia.scm.group.Group;
import sonia.scm.group.GroupManager;
import sonia.scm.group.GroupNames;
@@ -132,7 +131,7 @@ public class SyncingRealmHelperTest {
* @throws IOException
*/
@Test
- public void testStoreGroupCreate() throws AlreadyExistsException {
+ public void testStoreGroupCreate() {
Group group = new Group("unit-test", "heartOfGold");
helper.store(group);
@@ -143,7 +142,7 @@ public class SyncingRealmHelperTest {
* Tests {@link SyncingRealmHelper#store(Group)}.
*/
@Test(expected = IllegalStateException.class)
- public void testStoreGroupFailure() throws AlreadyExistsException {
+ public void testStoreGroupFailure() {
Group group = new Group("unit-test", "heartOfGold");
doThrow(AlreadyExistsException.class).when(groupManager).create(group);
@@ -169,7 +168,7 @@ public class SyncingRealmHelperTest {
* @throws IOException
*/
@Test
- public void testStoreUserCreate() throws AlreadyExistsException {
+ public void testStoreUserCreate() {
User user = new User("tricia");
helper.store(user);
@@ -180,7 +179,7 @@ public class SyncingRealmHelperTest {
* Tests {@link SyncingRealmHelper#store(User)} with a thrown {@link AlreadyExistsException}.
*/
@Test(expected = IllegalStateException.class)
- public void testStoreUserFailure() throws AlreadyExistsException {
+ public void testStoreUserFailure() {
User user = new User("tricia");
doThrow(AlreadyExistsException.class).when(userManager).create(user);
diff --git a/scm-it/src/test/java/sonia/scm/it/AnonymousAccessITCase.java b/scm-it/src/test/java/sonia/scm/it/AnonymousAccessITCase.java
new file mode 100644
index 0000000000..0a329b8e1e
--- /dev/null
+++ b/scm-it/src/test/java/sonia/scm/it/AnonymousAccessITCase.java
@@ -0,0 +1,26 @@
+package sonia.scm.it;
+
+import io.restassured.RestAssured;
+import org.junit.Test;
+import sonia.scm.it.utils.RestUtil;
+import sonia.scm.it.utils.ScmRequests;
+
+import static org.junit.Assert.assertEquals;
+
+public class AnonymousAccessITCase {
+
+ @Test
+ public void shouldAccessIndexResourceWithoutAuthentication() {
+ ScmRequests.start()
+ .requestIndexResource()
+ .assertStatusCode(200);
+ }
+
+ @Test
+ public void shouldRejectUserResourceWithoutAuthentication() {
+ assertEquals(401, RestAssured.given()
+ .when()
+ .get(RestUtil.REST_BASE_URL.resolve("users/"))
+ .statusCode());
+ }
+}
diff --git a/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java b/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java
index 8785f1d8ce..aa91e67022 100644
--- a/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java
+++ b/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java
@@ -32,6 +32,7 @@
package sonia.scm.it;
import org.apache.http.HttpStatus;
+import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -49,8 +50,10 @@ import sonia.scm.web.VndMediaType;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static sonia.scm.it.utils.RepositoryUtil.addAndCommitRandomFile;
@@ -72,7 +75,7 @@ public class PermissionsITCase {
public TemporaryFolder temporaryFolder = new TemporaryFolder();
private final String repositoryType;
- private int createdPermissions;
+ private Collection