diff --git a/CHANGELOG.md b/CHANGELOG.md index 8004d390ec..dc85ccc883 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ + # Changelog All notable changes to this project will be documented in this file. @@ -6,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Added +- Add support for pr merge with prior rebase ([#1332](https://github.com/scm-manager/scm-manager/pull/1332)) - Tags overview for repository [#1331](https://github.com/scm-manager/scm-manager/pull/1331) - Permissions can be specified for namespaces ([#1335](https://github.com/scm-manager/scm-manager/pull/1335)) - Show update info on admin information page ([#1342](https://github.com/scm-manager/scm-manager/pull/1342)) @@ -17,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Overflow for too long branch names ([#1339](https://github.com/scm-manager/scm-manager/pull/1339)) - Set default branch in branch selector if nothing is selected ([#1338](https://github.com/scm-manager/scm-manager/pull/1338)) - Handling of branch with slashes in source view ([#1340](https://github.com/scm-manager/scm-manager/pull/1340)) +- Detect not existing paths correctly in Mercurial ([#1343](https://github.com/scm-manager/scm-manager/pull/1343)) ## [2.5.0] - 2020-09-10 ### Added diff --git a/pom.xml b/pom.xml index df3a55d56b..961aa2ce56 100644 --- a/pom.xml +++ b/pom.xml @@ -464,7 +464,7 @@ org.assertj assertj-core - 3.17.0 + 3.17.1 test @@ -903,7 +903,7 @@ - 3.5.7 + 3.5.9 2.1 5.6.2 diff --git a/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java index 98a9938346..a4083cef1d 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeStrategy.java @@ -21,11 +21,22 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.api; public enum MergeStrategy { - MERGE_COMMIT, - FAST_FORWARD_IF_POSSIBLE, - SQUASH + MERGE_COMMIT(true), + FAST_FORWARD_IF_POSSIBLE(true), + SQUASH(true), + REBASE(false); + + private final boolean commitMessageAllowed; + + MergeStrategy(boolean commitMessageAllowed) { + this.commitMessageAllowed = commitMessageAllowed; + } + + public boolean isCommitMessageAllowed() { + return commitMessageAllowed; + } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java index bd4e6b26b5..38b6b6dee5 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java @@ -60,7 +60,8 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand private static final Set STRATEGIES = ImmutableSet.of( MergeStrategy.MERGE_COMMIT, MergeStrategy.FAST_FORWARD_IF_POSSIBLE, - MergeStrategy.SQUASH + MergeStrategy.SQUASH, + MergeStrategy.REBASE ); @Inject @@ -94,6 +95,9 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand case MERGE_COMMIT: return inClone(clone -> new GitMergeCommit(clone, request, context, repository), workingCopyFactory, request.getTargetBranch()); + case REBASE: + return inClone(clone -> new GitMergeRebase(clone, request, context, repository), workingCopyFactory, request.getTargetBranch()); + default: throw new MergeStrategyNotSupportedException(repository, request.getMergeStrategy()); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeRebase.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeRebase.java new file mode 100644 index 0000000000..65cba4f7ba --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeRebase.java @@ -0,0 +1,96 @@ +/* + * 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.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.MergeCommand; +import org.eclipse.jgit.api.RebaseResult; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.ObjectId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.Repository; +import sonia.scm.repository.api.MergeCommandResult; + +import java.io.IOException; +import java.util.Collections; +import java.util.Optional; + +public class GitMergeRebase extends GitMergeStrategy { + + private static final Logger logger = LoggerFactory.getLogger(GitMergeRebase.class); + + private final MergeCommandRequest request; + + GitMergeRebase(Git clone, MergeCommandRequest request, GitContext context, Repository repository) { + super(clone, request, context, repository); + this.request = request; + } + + @Override + MergeCommandResult run() throws IOException { + RebaseResult result; + String branchToMerge = request.getBranchToMerge(); + String targetBranch = request.getTargetBranch(); + try { + checkOutBranch(branchToMerge); + result = + getClone() + .rebase() + .setUpstream(targetBranch) + .call(); + } catch (GitAPIException e) { + throw new InternalRepositoryException(getContext().getRepository(), "could not rebase branch " + branchToMerge + " onto " + targetBranch, e); + } + + if (result.getStatus().isSuccessful()) { + return fastForwardTargetBranch(branchToMerge, targetBranch, result); + } else { + logger.info("could not rebase branch {} into {} with rebase status '{}' due to ...", branchToMerge, targetBranch, result.getStatus()); + logger.info("... conflicts: {}", result.getConflicts()); + logger.info("... failing paths: {}", result.getFailingPaths()); + logger.info("... message: {}", result); + return MergeCommandResult.failure(branchToMerge, targetBranch, Optional.ofNullable(result.getConflicts()).orElse(Collections.singletonList("UNKNOWN"))); + } + } + + private MergeCommandResult fastForwardTargetBranch(String branchToMerge, String targetBranch, RebaseResult result) throws IOException { + try { + getClone().checkout().setName(targetBranch).call(); + ObjectId sourceRevision = resolveRevision(branchToMerge); + getClone() + .merge() + .setFastForward(MergeCommand.FastForwardMode.FF_ONLY) + .include(branchToMerge, sourceRevision) + .call(); + push(); + return MergeCommandResult.success(getTargetRevision().name(), branchToMerge, sourceRevision.name()); + } catch (GitAPIException e) { + return MergeCommandResult.failure(branchToMerge, targetBranch, result.getConflicts()); + } + + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java index 716b172981..84c17a299a 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java @@ -473,6 +473,47 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { } + @Test + public void shouldAllowMergeWithRebase() throws IOException, GitAPIException { + GitMergeCommand command = createCommand(); + MergeCommandRequest request = new MergeCommandRequest(); + request.setTargetBranch("master"); + request.setBranchToMerge("mergeable"); + request.setMergeStrategy(MergeStrategy.REBASE); + request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det")); + + MergeCommandResult mergeCommandResult = command.merge(request); + + assertThat(mergeCommandResult.isSuccess()).isTrue(); + + Repository repository = createContext().open(); + Iterable commits = new Git(repository).log().add(repository.resolve("master")).setMaxCount(1).call(); + RevCommit mergeCommit = commits.iterator().next(); + assertThat(mergeCommit.getParentCount()).isEqualTo(1); + assertThat(mergeCommit.getParent(0).name()).isEqualTo("fcd0ef1831e4002ac43ea539f4094334c79ea9ec"); + assertThat(mergeCommit.getName()).isEqualTo(mergeCommandResult.getNewHeadRevision()); + assertThat(mergeCommit.getName()).doesNotStartWith("91b99de908fcd04772798a31c308a64aea1a5523"); + } + + @Test + public void shouldRejectRebaseMergeIfBranchCannotBeRebased() throws IOException, GitAPIException { + GitMergeCommand command = createCommand(); + MergeCommandRequest request = new MergeCommandRequest(); + request.setTargetBranch("master"); + request.setBranchToMerge("not-rebasable"); + request.setMergeStrategy(MergeStrategy.REBASE); + request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det")); + + MergeCommandResult mergeCommandResult = command.merge(request); + + assertThat(mergeCommandResult.isSuccess()).isFalse(); + Repository repository = createContext().open(); + Iterable commits = new Git(repository).log().add(repository.resolve("master")).setMaxCount(1).call(); + RevCommit headCommit = commits.iterator().next(); + assertThat(headCommit.getName()).isEqualTo("fcd0ef1831e4002ac43ea539f4094334c79ea9ec"); + + } + private GitMergeCommand createCommand() { return createCommand(git -> { }); diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test.zip b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test.zip index 80483d04d1..4d23fa0284 100644 Binary files a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test.zip and b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test.zip differ diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java index 6c83368cc4..8c217d6d4c 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java @@ -36,6 +36,9 @@ import sonia.scm.repository.spi.javahg.HgFileviewCommand; import java.io.IOException; +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; + //~--- JDK imports ------------------------------------------------------------ /** @@ -93,7 +96,8 @@ public class HgBrowseCommand extends AbstractCommand implements BrowseCommand cmd.setLimit(request.getLimit()); cmd.setOffset(request.getOffset()); - FileObject file = cmd.execute(); + FileObject file = cmd.execute() + .orElseThrow(() -> notFound(entity("File", request.getPath()).in("Revision", revision).in(getRepository()))); return new BrowserResult(c == null? "tip": c.getNode(), revision, file); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgFileviewCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgFileviewCommand.java index 776bfcaa7d..0ecd0a7d27 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgFileviewCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgFileviewCommand.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.spi.javahg; //~--- non-JDK imports -------------------------------------------------------- @@ -29,12 +29,12 @@ package sonia.scm.repository.spi.javahg; import com.aragost.javahg.Repository; import com.aragost.javahg.internals.AbstractCommand; import com.aragost.javahg.internals.HgInputStream; - import sonia.scm.repository.FileObject; //~--- JDK imports ------------------------------------------------------------ import java.io.IOException; +import java.util.Optional; /** * Mercurial command to list files of a repository. @@ -161,7 +161,7 @@ public class HgFileviewCommand extends AbstractCommand * * @throws IOException */ - public FileObject execute() throws IOException + public Optional execute() throws IOException { cmdAppend("-t"); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgFileviewCommandResultReader.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgFileviewCommandResultReader.java index 80d6a69a5a..c9e315ab00 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgFileviewCommandResultReader.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgFileviewCommandResultReader.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.spi.javahg; import com.aragost.javahg.DateTime; @@ -35,6 +35,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Deque; import java.util.LinkedList; +import java.util.Optional; + +import static java.util.Optional.empty; +import static java.util.Optional.of; class HgFileviewCommandResultReader { @@ -48,7 +52,7 @@ class HgFileviewCommandResultReader { this.disableLastCommit = disableLastCommit; } - FileObject parseResult() throws IOException { + Optional parseResult() throws IOException { Deque stack = new LinkedList<>(); FileObject last = null; @@ -82,13 +86,17 @@ class HgFileviewCommandResultReader { if (stack.isEmpty()) { // if the stack is empty, the requested path is probably a file - return last; + return of(last); + } else if (stack.size() == 1 && stack.getFirst().isDirectory() && stack.getFirst().getChildren().isEmpty()) { + // There are no empty directories in hg. When we get this, + // we just get the requested path as a directory, but it does not exist. + return empty(); } else { // if the stack is not empty, the requested path is a directory if (stream.read() == TRUNCATED_MARK) { stack.getLast().setTruncated(true); } - return stack.getLast(); + return of(stack.getLast()); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/javahg/HgFileviewCommandResultReaderTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/javahg/HgFileviewCommandResultReaderTest.java index 2cb6a4af04..c268175108 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/javahg/HgFileviewCommandResultReaderTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/javahg/HgFileviewCommandResultReaderTest.java @@ -21,11 +21,10 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi.javahg; import com.aragost.javahg.internals.HgInputStream; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import sonia.scm.repository.FileObject; @@ -34,6 +33,7 @@ import java.io.IOException; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Iterator; +import java.util.Optional; import java.util.OptionalLong; import static java.nio.charset.StandardCharsets.UTF_8; @@ -55,7 +55,7 @@ class HgFileviewCommandResultReaderTest { .file("b.txt", 100, time2.toEpochMilli(), "file b\nwith some\nmore text") .build(); - FileObject fileObject = reader.parseResult(); + FileObject fileObject = reader.parseResult().get(); assertThat(fileObject.isDirectory()).isTrue(); assertThat(fileObject.getChildren()) @@ -84,7 +84,7 @@ class HgFileviewCommandResultReaderTest { .file("a.txt") .truncated(); - FileObject fileObject = reader.parseResult(); + FileObject fileObject = reader.parseResult().get(); assertThat(fileObject.isTruncated()).isTrue(); } @@ -96,7 +96,7 @@ class HgFileviewCommandResultReaderTest { .file("dir/a.txt") .build(); - FileObject fileObject = reader.parseResult(); + FileObject fileObject = reader.parseResult().get(); assertThat(fileObject.isDirectory()).isTrue(); assertThat(fileObject.getName()).isEqualTo("dir"); @@ -117,7 +117,7 @@ class HgFileviewCommandResultReaderTest { .file("d.txt") .build(); - FileObject fileObject = reader.parseResult(); + FileObject fileObject = reader.parseResult().get(); assertThat(fileObject.getChildren()) .extracting("name") @@ -152,7 +152,7 @@ class HgFileviewCommandResultReaderTest { .file("d.txt") .build(); - FileObject fileObject = reader.parseResult(); + FileObject fileObject = reader.parseResult().get(); assertThat(fileObject.getChildren()) .extracting("name") @@ -179,7 +179,7 @@ class HgFileviewCommandResultReaderTest { .file("d.txt") .build(); - FileObject fileObject = reader.parseResult(); + FileObject fileObject = reader.parseResult().get(); assertThat(fileObject.getChildren()) .extracting("name") @@ -213,7 +213,7 @@ class HgFileviewCommandResultReaderTest { .file("directory/b.txt") .build(); - FileObject fileObject = reader.parseResult(); + FileObject fileObject = reader.parseResult().get(); assertThat(fileObject.getChildren()) .extracting("name") @@ -248,7 +248,7 @@ class HgFileviewCommandResultReaderTest { .file("a.txt") .build(); - FileObject fileObject = reader.parseResult(); + FileObject fileObject = reader.parseResult().get(); assertThat(fileObject.getChildren()) .extracting("description") @@ -258,6 +258,17 @@ class HgFileviewCommandResultReaderTest { .containsOnly(OptionalLong.empty()); } + @Test + void shouldIgnoreSingleEmptyDir() throws IOException { + HgFileviewCommandResultReader reader = new MockInput() + .dir("empty") + .build(); + + Optional fileObject = reader.parseResult(); + + assertThat(fileObject).isEmpty(); + } + private HgInputStream createInputStream(String input) { return new HgInputStream(new ByteArrayInputStream(input.getBytes(UTF_8)), UTF_8.newDecoder()); } diff --git a/scm-server/pom.xml b/scm-server/pom.xml index 780d7aed4b..e012b5e7f0 100644 --- a/scm-server/pom.xml +++ b/scm-server/pom.xml @@ -69,7 +69,7 @@ - 1.2.2 + 1.2.3 diff --git a/scm-ui/ui-webapp/public/locales/de/config.json b/scm-ui/ui-webapp/public/locales/de/config.json index 8a872a5987..ad749c12c8 100644 --- a/scm-ui/ui-webapp/public/locales/de/config.json +++ b/scm-ui/ui-webapp/public/locales/de/config.json @@ -61,7 +61,7 @@ "realmDescriptionHelpText": "Beschreibung des Authentication Realm.", "dateFormatHelpText": "Moments Datumsformat. Zulässige Formate sind in der MomentJS Dokumentation beschrieben.", "pluginUrlHelpText": "Die URL der Plugin Center API. Beschreibung der Platzhalter: version = SCM-Manager Version; os = Betriebssystem; arch = Architektur", - "releaseFeedUrlHelpText": "Die URL des RSS Release Feed des SCM-Manager. Darüber wird über die neue SCM-Manager Versionen informiert. Um diese Funktion zu deaktivieren lassen Sie dieses Feld leer.", + "releaseFeedUrlHelpText": "Die URL des RSS Release Feed des SCM-Manager. Darüber wird über die neue SCM-Manager Version informiert. Um diese Funktion zu deaktivieren lassen Sie dieses Feld leer.", "enableForwardingHelpText": "mod_proxy Port Weiterleitung aktivieren.", "disableGroupingGridHelpText": "Repository Gruppen deaktivieren. Nach einer Änderung an dieser Einstellung muss die Seite komplett neu geladen werden.", "allowAnonymousAccessHelpText": "Anonyme Benutzer haben Zugriff auf freigegebene Repositories.", diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceToNamespaceDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceToNamespaceDtoMapper.java index 70b85374ce..5067f95c0f 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceToNamespaceDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceToNamespaceDtoMapper.java @@ -47,7 +47,7 @@ class NamespaceToNamespaceDtoMapper { .self(links.namespace().self(namespace)) .single(link("repositories", links.repositoryCollection().forNamespace(namespace))); - if (NamespacePermissions.permissionRead().isPermitted() || NamespacePermissions.permissionWrite().isPermitted()) { + if (NamespacePermissions.permissionRead().isPermitted()) { linkingTo .single(link("permissions", links.namespacePermission().all(namespace))); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionCollectionToDtoMapper.java index 6928dec872..db285c369a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionCollectionToDtoMapper.java @@ -78,9 +78,7 @@ public class RepositoryPermissionCollectionToDtoMapper { } private Links createLinks(Namespace namespace) { - if (!NamespacePermissions.permissionWrite().isPermitted()) { - NamespacePermissions.permissionRead().check(); - } + NamespacePermissions.permissionRead().check(); Links.Builder linksBuilder = linkingTo() .with(Links.linkingTo().self(resourceLinks.namespacePermission().all(namespace.getNamespace())).build()); if (NamespacePermissions.permissionWrite().isPermitted()) { diff --git a/scm-webapp/src/main/java/sonia/scm/repository/DefaultNamespaceManager.java b/scm-webapp/src/main/java/sonia/scm/repository/DefaultNamespaceManager.java index 6d6ef8963f..c291cfa97e 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultNamespaceManager.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultNamespaceManager.java @@ -104,7 +104,7 @@ public class DefaultNamespaceManager implements NamespaceManager { } private Namespace createNamespaceForName(String namespace) { - if (NamespacePermissions.permissionRead().isPermitted() || NamespacePermissions.permissionWrite().isPermitted()) { + if (NamespacePermissions.permissionRead().isPermitted()) { return dao.get(namespace) .map(Namespace::clone) .orElse(new Namespace(namespace)); diff --git a/scm-webapp/src/main/resources/META-INF/scm/permissions.xml b/scm-webapp/src/main/resources/META-INF/scm/permissions.xml index 0889341d25..76d1e7fa46 100644 --- a/scm-webapp/src/main/resources/META-INF/scm/permissions.xml +++ b/scm-webapp/src/main/resources/META-INF/scm/permissions.xml @@ -48,7 +48,7 @@ namespace:permissionRead - namespace:permissionWrite + namespace:permissionRead,permissionWrite user:* diff --git a/scm-webapp/src/main/resources/locales/de/plugins.json b/scm-webapp/src/main/resources/locales/de/plugins.json index f3372e2d85..cf2c29f21f 100644 --- a/scm-webapp/src/main/resources/locales/de/plugins.json +++ b/scm-webapp/src/main/resources/locales/de/plugins.json @@ -98,7 +98,7 @@ "displayName": "Berechtigungen auf Namespaces lesen", "description": "Darf die Berechtigungen auf Namespace-Ebene sehen" }, - "permissionWrite": { + "permissionRead,permissionWrite": { "displayName": "Berechtigungen auf Namespaces modifizieren", "description": "Darf die Berechtigungen auf Namespace-Ebene lesen und bearbeiten" } diff --git a/scm-webapp/src/main/resources/locales/en/plugins.json b/scm-webapp/src/main/resources/locales/en/plugins.json index 3dfeef2300..4de2202976 100644 --- a/scm-webapp/src/main/resources/locales/en/plugins.json +++ b/scm-webapp/src/main/resources/locales/en/plugins.json @@ -98,7 +98,7 @@ "displayName": "Read permissions on namespaces", "description": "May see the permissions set for namespaces" }, - "permissionWrite": { + "permissionRead,permissionWrite": { "displayName": "Modify permissions on namespaces", "description": "May read and modify the permissions set for namespaces" } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexDtoGeneratorTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexDtoGeneratorTest.java index 97c9714513..94c78f798b 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexDtoGeneratorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexDtoGeneratorTest.java @@ -132,6 +132,7 @@ class IndexDtoGeneratorTest { when(resourceLinks.repositoryTypeCollection()).thenReturn(new ResourceLinks.RepositoryTypeCollectionLinks(scmPathInfo)); when(resourceLinks.repositoryRoleCollection()).thenReturn(new ResourceLinks.RepositoryRoleCollectionLinks(scmPathInfo)); when(resourceLinks.namespaceStrategies()).thenReturn(new ResourceLinks.NamespaceStrategiesLinks(scmPathInfo)); + when(resourceLinks.namespaceCollection()).thenReturn(new ResourceLinks.NamespaceCollectionLinks(scmPathInfo)); when(resourceLinks.me()).thenReturn(new ResourceLinks.MeLinks(scmPathInfo, new ResourceLinks.UserLinks(scmPathInfo))); } }