From 6bfefb3348dee8b0a5c92ae94cbb9a5d63768e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 5 Aug 2020 12:12:30 +0200 Subject: [PATCH 01/13] Introduce merge detection for receive hooks Here we add a merge detection provider for pre and post receive hooks and implement this new provider for git. --- CHANGELOG.md | 4 + .../sonia/scm/repository/api/HookContext.java | 96 +++++---- .../sonia/scm/repository/api/HookFeature.java | 15 +- .../repository/spi/HookContextProvider.java | 30 ++- .../spi/HookMergeDetectionProvider.java | 42 ++++ .../java/sonia/scm/repository/GitUtil.java | 12 +- ...PostReceiveHookMergeDetectionProvider.java | 52 +++++ ...tPreReceiveHookMergeDetectionProvider.java | 63 ++++++ .../repository/spi/AbstractGitCommand.java | 11 +- .../spi/GitHookContextProvider.java | 63 +++--- .../scm/repository/spi/GitLogCommand.java | 133 +------------ .../scm/repository/spi/GitLogComputer.java | 187 ++++++++++++++++++ .../java/sonia/scm/web/GitReceiveHook.java | 5 +- 13 files changed, 488 insertions(+), 225 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/repository/spi/HookMergeDetectionProvider.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitPostReceiveHookMergeDetectionProvider.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitPreReceiveHookMergeDetectionProvider.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogComputer.java diff --git a/CHANGELOG.md b/CHANGELOG.md index dd1413127c..e763de6daf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased +### Added +- Introduced merge detection for receive hooks ([#1278](https://github.com/scm-manager/scm-manager/pull/1278)) + ## [2.3.1] - 2020-08-04 ### Added - New api to resolve SCM-Manager root url ([#1276](https://github.com/scm-manager/scm-manager/pull/1276)) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/HookContext.java b/scm-core/src/main/java/sonia/scm/repository/api/HookContext.java index ff49542222..588fc342d2 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/HookContext.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/HookContext.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 -------------------------------------------------------- @@ -33,18 +33,18 @@ import sonia.scm.repository.Changeset; import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.Repository; import sonia.scm.repository.spi.HookContextProvider; +import sonia.scm.repository.spi.HookMergeDetectionProvider; /** * The context for all repository hooks. With the {@link HookContext} class it * is able to send messages back to the client, retrieve {@link Changeset}s - * which are added during this push/commit and gives informations about changed + * which are added during this push/commit and gives informations about changed * branches and tags. * * @author Sebastian Sdorra * @since 1.33 */ -public final class HookContext -{ +public final class HookContext { /** * the logger for HookContext @@ -52,8 +52,6 @@ public final class HookContext private static final Logger logger = LoggerFactory.getLogger(HookContext.class); - //~--- constructors --------------------------------------------------------- - /** * Constructs ... * @@ -62,9 +60,7 @@ public final class HookContext * @param repository * @param preProcessorUtil */ - HookContext(HookContextProvider provider, Repository repository, - PreProcessorUtil preProcessorUtil) - { + HookContext(HookContextProvider provider, Repository repository, PreProcessorUtil preProcessorUtil) { this.provider = provider; this.repository = repository; this.preProcessorUtil = preProcessorUtil; @@ -77,41 +73,33 @@ public final class HookContext * about changed branches during the current hook. * * @return {@link HookBranchProvider} - * - * @throws HookFeatureIsNotSupportedException if the feature is not supported + * + * @throws HookFeatureIsNotSupportedException if the feature is not supported * by the underlying provider - * + * * @since 1.45 */ - public HookBranchProvider getBranchProvider() - { - if (logger.isDebugEnabled()) - { - logger.debug("create branch provider for repository {}", - repository.getName()); - } + public HookBranchProvider getBranchProvider() { + logger.debug("create branch provider for repository {}", + repository.getName()); return provider.getBranchProvider(); } - + /** * Returns a {@link HookTagProvider} which is able to return informations * about changed tags during the current hook. * * @return {@link HookTagProvider} - * - * @throws HookFeatureIsNotSupportedException if the feature is not supported + * + * @throws HookFeatureIsNotSupportedException if the feature is not supported * by the underlying provider - * + * * @since 1.50 */ - public HookTagProvider getTagProvider() - { - if (logger.isDebugEnabled()) - { - logger.debug("create tag provider for repository {}", - repository.getName()); - } + public HookTagProvider getTagProvider() { + logger.debug("create tag provider for repository {}", + repository.getName()); return provider.getTagProvider(); } @@ -122,25 +110,19 @@ public final class HookContext * * * @return {@link HookChangesetBuilder} - * - * @throws HookFeatureIsNotSupportedException if the feature is not supported + * + * @throws HookFeatureIsNotSupportedException if the feature is not supported * by the underlying provider */ - public HookChangesetBuilder getChangesetProvider() - { - if (logger.isDebugEnabled()) - { - logger.debug("create changeset provider for repository {}", - repository.getName()); - } + public HookChangesetBuilder getChangesetProvider() { + logger.debug("create changeset provider for repository {}", + repository.getName()); - //J- return new HookChangesetBuilder( - repository, + repository, preProcessorUtil, provider.getChangesetProvider() ); - //J+ } /** @@ -152,21 +134,33 @@ public final class HookContext * * @return {@link HookMessageProvider} which is able to send message back to * the scm client - * - * @throws HookFeatureIsNotSupportedException if the feature is not supported + * + * @throws HookFeatureIsNotSupportedException if the feature is not supported * by the underlying provider */ - public HookMessageProvider getMessageProvider() - { - if (logger.isDebugEnabled()) - { - logger.debug("create message provider for repository {}", - repository.getName()); - } + public HookMessageProvider getMessageProvider() { + logger.debug("create message provider for repository {}", + repository.getName()); return provider.getMessageProvider(); } + /** + * Returns a {@link HookMergeDetectionProvider} which is able to check whether two + * branches have been merged with the incoming changesets. + * + * @return {@link HookMergeDetectionProvider} which is able to detect merges. + * + * @throws HookFeatureIsNotSupportedException if the feature is not supported + * by the underlying provider + */ + public HookMergeDetectionProvider getMergeDetectionProvider() { + logger.debug("create merge detection provider for repository {}", + repository.getName()); + + return provider.getMergeDetectionProvider(); + } + /** * Returns true if the underlying provider support the requested feature. * diff --git a/scm-core/src/main/java/sonia/scm/repository/api/HookFeature.java b/scm-core/src/main/java/sonia/scm/repository/api/HookFeature.java index 0f0aaf0832..da664b5772 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/HookFeature.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/HookFeature.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; /** @@ -49,11 +49,18 @@ public enum HookFeature * @since 1.45 */ BRANCH_PROVIDER, - + /** * Hook tag provider - * + * * @since 1.50 */ - TAG_PROVIDER; + TAG_PROVIDER, + + /** + * Provider to detect merges + * + * @since 2.4.0 + */ + MERGE_DETECTION_PROVIDER } diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/HookContextProvider.java b/scm-core/src/main/java/sonia/scm/repository/spi/HookContextProvider.java index ddb8cb9a9b..f9af110ea5 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/HookContextProvider.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/HookContextProvider.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; //~--- non-JDK imports -------------------------------------------------------- @@ -50,7 +50,7 @@ public abstract class HookContextProvider /** * Return the provider specific {@link HookMessageProvider} or throws a {@link HookFeatureIsNotSupportedException}. * The method will throw a {@link HookException} if the client is already disconnected. - * + * * @return provider specific {@link HookMessageProvider} */ public final HookMessageProvider getMessageProvider() @@ -86,31 +86,31 @@ public abstract class HookContextProvider /** * Return the provider specific {@link HookBranchProvider} or throws a {@link HookFeatureIsNotSupportedException}. - * + * * @return provider specific {@link HookBranchProvider} - * + * * @since 1.45 */ public HookBranchProvider getBranchProvider() { throw new HookFeatureIsNotSupportedException(HookFeature.BRANCH_PROVIDER); } - + /** * Return the provider specific {@link HookTagProvider} or throws a {@link HookFeatureIsNotSupportedException}. - * + * * @return provider specific {@link HookTagProvider} - * + * * @since 1.50 */ - public HookTagProvider getTagProvider() + public HookTagProvider getTagProvider() { throw new HookFeatureIsNotSupportedException(HookFeature.TAG_PROVIDER); } /** * Return the provider specific {@link HookChangesetProvider} or throws a {@link HookFeatureIsNotSupportedException}. - * + * * @return provider specific {@link HookChangesetProvider} */ public HookChangesetProvider getChangesetProvider() @@ -118,11 +118,21 @@ public abstract class HookContextProvider throw new HookFeatureIsNotSupportedException(HookFeature.CHANGESET_PROVIDER); } + /** + * Return the provider specific {@link HookMergeDetectionProvider} or throws a {@link HookFeatureIsNotSupportedException}. + * + * @return provider specific {@link HookMergeDetectionProvider} + */ + public HookMergeDetectionProvider getMergeDetectionProvider() + { + throw new HookFeatureIsNotSupportedException(HookFeature.MERGE_DETECTION_PROVIDER); + } + //~--- methods -------------------------------------------------------------- /** * Creates a new provider specific {@link HookMessageProvider} or throws a {@link HookFeatureIsNotSupportedException}. - * + * * @return provider specific {@link HookChangesetProvider} */ protected HookMessageProvider createMessageProvider() diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/HookMergeDetectionProvider.java b/scm-core/src/main/java/sonia/scm/repository/spi/HookMergeDetectionProvider.java new file mode 100644 index 0000000000..3deacd22c3 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/spi/HookMergeDetectionProvider.java @@ -0,0 +1,42 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.spi; + +/** + * @since 2.4.0 + */ +public interface HookMergeDetectionProvider { + + /** + * Checks whether branch has been merged into target. So this will also return + * true, when branch has been deleted with this change. + * + * @param target The name of the branch to check, whether the other branch has been merged into. + * @param branch The name of the branch to check, whether it has been merged into the other branch. + * @return true when branch has been merged into target, false + * otherwise. + */ + boolean branchesMerged(String target, String branch); +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java index dee72ce345..b55aec06bd 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.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; //~--- non-JDK imports -------------------------------------------------------- @@ -337,6 +337,16 @@ public final class GitUtil return Strings.nullToEmpty(refName).startsWith(PREFIX_HEADS); } + public static Ref getBranchIdOrCurrentHead(org.eclipse.jgit.lib.Repository gitRepository, String requestedBranch) throws IOException { + if ( Strings.isNullOrEmpty(requestedBranch) ) { + logger.trace("no default branch configured, use repository head as default"); + Optional repositoryHeadRef = GitUtil.getRepositoryHeadRef(gitRepository); + return repositoryHeadRef.orElse(null); + } else { + return GitUtil.getBranchId(gitRepository, requestedBranch); + } + } + /** * Method description * diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitPostReceiveHookMergeDetectionProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitPostReceiveHookMergeDetectionProvider.java new file mode 100644 index 0000000000..e8e41ac3a2 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitPostReceiveHookMergeDetectionProvider.java @@ -0,0 +1,52 @@ +/* + * 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 org.eclipse.jgit.lib.Repository; +import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.spi.GitLogComputer; +import sonia.scm.repository.spi.HookMergeDetectionProvider; +import sonia.scm.repository.spi.LogCommandRequest; + +public class GitPostReceiveHookMergeDetectionProvider implements HookMergeDetectionProvider { + private final Repository repository; + private final String repositoryId; + + public GitPostReceiveHookMergeDetectionProvider(Repository repository, String repositoryId) { + this.repository = repository; + this.repositoryId = repositoryId; + } + + @Override + public boolean branchesMerged(String target, String branch) { + LogCommandRequest request = new LogCommandRequest(); + request.setBranch(branch); + request.setAncestorChangeset(target); + request.setPagingLimit(1); + + ChangesetPagingResult changesets = new GitLogComputer(repositoryId, repository).compute(request); + return changesets.getTotal() == 0; + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitPreReceiveHookMergeDetectionProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitPreReceiveHookMergeDetectionProvider.java new file mode 100644 index 0000000000..3f760bdaaa --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitPreReceiveHookMergeDetectionProvider.java @@ -0,0 +1,63 @@ +/* + * 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 org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.ReceiveCommand; +import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.GitUtil; +import sonia.scm.repository.spi.GitLogComputer; +import sonia.scm.repository.spi.HookMergeDetectionProvider; +import sonia.scm.repository.spi.LogCommandRequest; + +import java.util.List; + +public class GitPreReceiveHookMergeDetectionProvider implements HookMergeDetectionProvider { + private final List receiveCommands; + private final Repository repository; + private final String repositoryId; + + public GitPreReceiveHookMergeDetectionProvider(List receiveCommands, Repository repository, String repositoryId) { + this.receiveCommands = receiveCommands; + this.repository = repository; + this.repositoryId = repositoryId; + } + + @Override + public boolean branchesMerged(String target, String branch) { + + String sourceToUse = receiveCommands.stream().filter(receiveCommand -> GitUtil.getBranch(receiveCommand.getRef()).equals(branch)).findFirst().map(ReceiveCommand::getNewId).map(AnyObjectId::getName).orElse(branch); + String targetToUse = receiveCommands.stream().filter(receiveCommand -> GitUtil.getBranch(receiveCommand.getRef()).equals(target)).findFirst().map(ReceiveCommand::getNewId).map(AnyObjectId::getName).orElse(target); + + LogCommandRequest request = new LogCommandRequest(); + request.setBranch(sourceToUse); + request.setAncestorChangeset(targetToUse); + request.setPagingLimit(1); + + ChangesetPagingResult changesets = new GitLogComputer(repositoryId, repository).compute(request); + return changesets.getTotal() == 0; + } +} 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 d52d0a1574..28f0f72b8b 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.empty; 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; //~--- JDK imports ------------------------------------------------------------ @@ -123,15 +124,9 @@ class AbstractGitCommand Ref getBranchOrDefault(Repository gitRepository, String requestedBranch) throws IOException { if ( Strings.isNullOrEmpty(requestedBranch) ) { String defaultBranchName = context.getConfig().getDefaultBranch(); - if (!Strings.isNullOrEmpty(defaultBranchName)) { - return GitUtil.getBranchId(gitRepository, defaultBranchName); - } else { - logger.trace("no default branch configured, use repository head as default"); - Optional repositoryHeadRef = GitUtil.getRepositoryHeadRef(gitRepository); - return repositoryHeadRef.orElse(null); - } + return getBranchIdOrCurrentHead(gitRepository, defaultBranchName); } else { - return GitUtil.getBranchId(gitRepository, requestedBranch); + return getBranchIdOrCurrentHead(gitRepository, requestedBranch); } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java index 8e642a18ce..aaa5f09bda 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java @@ -21,27 +21,28 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.transport.ReceivePack; - +import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.api.GitHookBranchProvider; import sonia.scm.repository.api.GitHookMessageProvider; +import sonia.scm.repository.api.GitHookTagProvider; +import sonia.scm.repository.api.GitPostReceiveHookMergeDetectionProvider; +import sonia.scm.repository.api.GitPreReceiveHookMergeDetectionProvider; import sonia.scm.repository.api.HookBranchProvider; import sonia.scm.repository.api.HookFeature; import sonia.scm.repository.api.HookMessageProvider; - -//~--- JDK imports ------------------------------------------------------------ +import sonia.scm.repository.api.HookTagProvider; import java.util.EnumSet; import java.util.List; import java.util.Set; -import sonia.scm.repository.api.GitHookTagProvider; -import sonia.scm.repository.api.HookTagProvider; /** * @@ -50,24 +51,37 @@ import sonia.scm.repository.api.HookTagProvider; public class GitHookContextProvider extends HookContextProvider { - /** Field description */ - private static final Set SUPPORTED_FEATURES = - EnumSet.of(HookFeature.MESSAGE_PROVIDER, HookFeature.CHANGESET_PROVIDER, - HookFeature.BRANCH_PROVIDER, HookFeature.TAG_PROVIDER); + /** + * Field description + */ + private static final Set SUPPORTED_FEATURES = EnumSet.of( + HookFeature.MESSAGE_PROVIDER, + HookFeature.CHANGESET_PROVIDER, + HookFeature.BRANCH_PROVIDER, + HookFeature.TAG_PROVIDER, + HookFeature.MERGE_DETECTION_PROVIDER + ); //~--- constructors --------------------------------------------------------- /** * Constructs a new instance - * - * @param receivePack git receive pack + * @param receivePack git receive pack * @param receiveCommands received commands + * @param type */ - public GitHookContextProvider(ReceivePack receivePack, - List receiveCommands) - { + public GitHookContextProvider( + ReceivePack receivePack, + List receiveCommands, + RepositoryHookType type, + Repository repository, + String repositoryId + ) { this.receivePack = receivePack; this.receiveCommands = receiveCommands; + this.type = type; + this.repository = repository; + this.repositoryId = repositoryId; this.changesetProvider = new GitHookChangesetProvider(receivePack, receiveCommands); } @@ -99,20 +113,25 @@ public class GitHookContextProvider extends HookContextProvider return changesetProvider; } + @Override + public HookMergeDetectionProvider getMergeDetectionProvider() { + if (type == RepositoryHookType.POST_RECEIVE) { + return new GitPostReceiveHookMergeDetectionProvider(repository, repositoryId); + } else { + return new GitPreReceiveHookMergeDetectionProvider(receiveCommands, repository, repositoryId); + } + } + @Override public Set getSupportedFeatures() { return SUPPORTED_FEATURES; } - //~--- fields --------------------------------------------------------------- - - /** Field description */ private final GitHookChangesetProvider changesetProvider; - - /** Field description */ private final List receiveCommands; - - /** Field description */ private final ReceivePack receivePack; + private final RepositoryHookType type; + private final Repository repository; + private final String repositoryId; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java index da00ba451b..340df65c16 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java @@ -27,19 +27,12 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.base.Strings; -import com.google.common.collect.Lists; -import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.treewalk.filter.AndTreeFilter; -import org.eclipse.jgit.treewalk.filter.PathFilter; -import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.NotFoundException; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.GitChangesetConverter; @@ -48,9 +41,6 @@ import sonia.scm.repository.InternalRepositoryException; import sonia.scm.util.IOUtil; import java.io.IOException; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; import static sonia.scm.ContextEntry.ContextBuilder.entity; import static sonia.scm.NotFoundException.notFound; @@ -183,123 +173,14 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand @Override @SuppressWarnings("unchecked") public ChangesetPagingResult getChangesets(LogCommandRequest request) { - if (logger.isDebugEnabled()) { - logger.debug("fetch changesets for request: {}", request); - } - - ChangesetPagingResult changesets = null; - GitChangesetConverter converter = null; - RevWalk revWalk = null; - - try (org.eclipse.jgit.lib.Repository repository = open()) { - if (!repository.getAllRefs().isEmpty()) { - int counter = 0; - int start = request.getPagingStart(); - - if (start < 0) { - if (logger.isErrorEnabled()) { - logger.error("start parameter is negative, reset to 0"); - } - - start = 0; - } - - List changesetList = Lists.newArrayList(); - int limit = request.getPagingLimit(); - ObjectId startId = null; - - if (!Strings.isNullOrEmpty(request.getStartChangeset())) { - startId = repository.resolve(request.getStartChangeset()); - } - - ObjectId endId = null; - - if (!Strings.isNullOrEmpty(request.getEndChangeset())) { - endId = repository.resolve(request.getEndChangeset()); - } - - Ref branch = getBranchOrDefault(repository,request.getBranch()); - - ObjectId ancestorId = null; - - if (!Strings.isNullOrEmpty(request.getAncestorChangeset())) { - ancestorId = repository.resolve(request.getAncestorChangeset()); - if (ancestorId == null) { - throw notFound(entity(REVISION, request.getAncestorChangeset()).in(this.repository)); - } - } - - revWalk = new RevWalk(repository); - - converter = new GitChangesetConverter(repository, revWalk); - - if (!Strings.isNullOrEmpty(request.getPath())) { - revWalk.setTreeFilter( - AndTreeFilter.create( - PathFilter.create(request.getPath()), TreeFilter.ANY_DIFF)); - } - - if (branch != null) { - if (startId != null) { - revWalk.markStart(revWalk.lookupCommit(startId)); - } else { - revWalk.markStart(revWalk.lookupCommit(branch.getObjectId())); - } - - if (ancestorId != null) { - revWalk.markUninteresting(revWalk.lookupCommit(ancestorId)); - } - - Iterator iterator = revWalk.iterator(); - - while (iterator.hasNext()) { - RevCommit commit = iterator.next(); - - if ((counter >= start) - && ((limit < 0) || (counter < start + limit))) { - changesetList.add(converter.createChangeset(commit)); - } - - counter++; - - if (commit.getId().equals(endId)) { - break; - } - } - } else if (ancestorId != null) { - throw notFound(entity(REVISION, request.getBranch()).in(this.repository)); - } - - if (branch != null) { - changesets = new ChangesetPagingResult(counter, changesetList, GitUtil.getBranch(branch.getName())); - } else { - changesets = new ChangesetPagingResult(counter, changesetList); - } - } else if (logger.isWarnEnabled()) { - logger.warn("the repository {} seems to be empty", - this.repository.getName()); - - changesets = new ChangesetPagingResult(0, Collections.EMPTY_LIST); + try (org.eclipse.jgit.lib.Repository gitRepository = open()) { + if (Strings.isNullOrEmpty(request.getBranch())) { + request.setBranch(context.getConfig().getDefaultBranch()); } + return new GitLogComputer(this.repository.getId(), gitRepository).compute(request); + } catch (IOException e) { + throw new InternalRepositoryException(repository, "could not create change log", e); } - catch (MissingObjectException e) - { - throw notFound(entity(REVISION, e.getObjectId().getName()).in(repository)); - } - catch (NotFoundException e) - { - throw e; - } - catch (Exception ex) - { - throw new InternalRepositoryException(repository, "could not create change log", ex); - } - finally - { - IOUtil.close(converter); - GitUtil.release(revWalk); - } - - return changesets; } + } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogComputer.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogComputer.java new file mode 100644 index 0000000000..b8c31045d8 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogComputer.java @@ -0,0 +1,187 @@ +/* + * 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 com.google.common.base.Strings; +import com.google.common.collect.Lists; +import org.eclipse.jgit.errors.InvalidObjectIdException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.treewalk.filter.AndTreeFilter; +import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.eclipse.jgit.treewalk.filter.TreeFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.ContextEntry; +import sonia.scm.NotFoundException; +import sonia.scm.repository.Changeset; +import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.GitChangesetConverter; +import sonia.scm.repository.GitUtil; +import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.util.IOUtil; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; + +public class GitLogComputer { + + private static final Logger LOG = LoggerFactory.getLogger(GitLogComputer.class); + + private final String repositoryId; + private final Repository gitRepository; + + public GitLogComputer(String repositoryId, Repository repository) { + this.repositoryId = repositoryId; + this.gitRepository = repository; + } + + public ChangesetPagingResult compute(LogCommandRequest request) { + LOG.debug("fetch changesets for request: {}", request); + + GitChangesetConverter converter = null; + RevWalk revWalk = null; + + try { + if (!gitRepository.getAllRefs().isEmpty()) { + int counter = 0; + int start = request.getPagingStart(); + + if (start < 0) { + LOG.error("start parameter is negative, reset to 0"); + + start = 0; + } + + List changesetList = Lists.newArrayList(); + int limit = request.getPagingLimit(); + ObjectId startId = null; + + if (!Strings.isNullOrEmpty(request.getStartChangeset())) { + startId = gitRepository.resolve(request.getStartChangeset()); + } + + ObjectId endId = null; + + if (!Strings.isNullOrEmpty(request.getEndChangeset())) { + endId = gitRepository.resolve(request.getEndChangeset()); + } + + Ref branch = GitUtil.getBranchIdOrCurrentHead(gitRepository, request.getBranch()); + ObjectId branchId; + if (branch == null) { + if (request.getBranch() != null) { + try { + branchId = ObjectId.fromString(request.getBranch()); + } catch (InvalidObjectIdException e) { + throw notFound(entity(GitLogCommand.REVISION, request.getBranch()).in(sonia.scm.repository.Repository.class, repositoryId)); + } + } else { + branchId = null; + } + } else { + branchId = branch.getObjectId(); + } + + ObjectId ancestorId = null; + + if (!Strings.isNullOrEmpty(request.getAncestorChangeset())) { + ancestorId = gitRepository.resolve(request.getAncestorChangeset()); + if (ancestorId == null) { + throw notFound(entity(GitLogCommand.REVISION, request.getAncestorChangeset()).in(sonia.scm.repository.Repository.class, repositoryId)); + } + } + + revWalk = new RevWalk(gitRepository); + + converter = new GitChangesetConverter(gitRepository, revWalk); + + if (!Strings.isNullOrEmpty(request.getPath())) { + revWalk.setTreeFilter( + AndTreeFilter.create( + PathFilter.create(request.getPath()), TreeFilter.ANY_DIFF)); + } + + if (branchId != null) { + if (startId != null) { + revWalk.markStart(revWalk.lookupCommit(startId)); + } else { + revWalk.markStart(revWalk.lookupCommit(branchId)); + } + + if (ancestorId != null) { + revWalk.markUninteresting(revWalk.lookupCommit(ancestorId)); + } + + Iterator iterator = revWalk.iterator(); + + while (iterator.hasNext()) { + RevCommit commit = iterator.next(); + + if ((counter >= start) + && ((limit < 0) || (counter < start + limit))) { + changesetList.add(converter.createChangeset(commit)); + } + + counter++; + + if (commit.getId().equals(endId)) { + break; + } + } + } else if (ancestorId != null) { + throw notFound(entity(GitLogCommand.REVISION, request.getBranch()).in(sonia.scm.repository.Repository.class, repositoryId)); + } + + if (branch != null) { + return new ChangesetPagingResult(counter, changesetList, GitUtil.getBranch(branch.getName())); + } else { + return new ChangesetPagingResult(counter, changesetList); + } + } else { + LOG.debug("the repository with id {} seems to be empty", this.repositoryId); + + return new ChangesetPagingResult(0, Collections.EMPTY_LIST); + } + } catch (MissingObjectException e) { + throw notFound(entity(GitLogCommand.REVISION, e.getObjectId().getName()).in(sonia.scm.repository.Repository.class, repositoryId)); + } catch (NotFoundException e) { + throw e; + } catch (Exception ex) { + throw new InternalRepositoryException(entity(sonia.scm.repository.Repository.class, repositoryId).build(), "could not create change log", ex); + } finally { + IOUtil.close(converter); + GitUtil.release(revWalk); + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java index c7b323224c..a2d218b99c 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.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.web; //~--- non-JDK imports -------------------------------------------------------- @@ -122,8 +122,7 @@ public class GitReceiveHook implements PreReceiveHook, PostReceiveHook logger.trace("resolved repository to {}", repositoryId); - GitHookContextProvider context = new GitHookContextProvider(rpack, - receiveCommands); + GitHookContextProvider context = new GitHookContextProvider(rpack, receiveCommands, type, repository, repositoryId); hookEventFacade.handle(repositoryId).fireHookEvent(type, context); From ac983d6067e25cb545b2023dfc8f0d41475a6ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 5 Aug 2020 18:43:08 +0200 Subject: [PATCH 02/13] Remove redundant code --- ...GitReceiveHookMergeDetectionProvider.java} | 29 +++++++------------ .../spi/GitHookContextProvider.java | 23 ++++++++++++--- 2 files changed, 30 insertions(+), 22 deletions(-) rename scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/{GitPreReceiveHookMergeDetectionProvider.java => GitReceiveHookMergeDetectionProvider.java} (59%) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitPreReceiveHookMergeDetectionProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitReceiveHookMergeDetectionProvider.java similarity index 59% rename from scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitPreReceiveHookMergeDetectionProvider.java rename to scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitReceiveHookMergeDetectionProvider.java index 3f760bdaaa..fef5806412 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitPreReceiveHookMergeDetectionProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitReceiveHookMergeDetectionProvider.java @@ -24,40 +24,33 @@ package sonia.scm.repository.api; -import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.transport.ReceiveCommand; -import sonia.scm.repository.ChangesetPagingResult; -import sonia.scm.repository.GitUtil; import sonia.scm.repository.spi.GitLogComputer; import sonia.scm.repository.spi.HookMergeDetectionProvider; import sonia.scm.repository.spi.LogCommandRequest; -import java.util.List; - -public class GitPreReceiveHookMergeDetectionProvider implements HookMergeDetectionProvider { - private final List receiveCommands; +public class GitReceiveHookMergeDetectionProvider implements HookMergeDetectionProvider { private final Repository repository; private final String repositoryId; + private final BranchMapper branchMapper; - public GitPreReceiveHookMergeDetectionProvider(List receiveCommands, Repository repository, String repositoryId) { - this.receiveCommands = receiveCommands; + public GitReceiveHookMergeDetectionProvider(Repository repository, String repositoryId, BranchMapper branchMapper) { this.repository = repository; this.repositoryId = repositoryId; + this.branchMapper = branchMapper; } @Override public boolean branchesMerged(String target, String branch) { - - String sourceToUse = receiveCommands.stream().filter(receiveCommand -> GitUtil.getBranch(receiveCommand.getRef()).equals(branch)).findFirst().map(ReceiveCommand::getNewId).map(AnyObjectId::getName).orElse(branch); - String targetToUse = receiveCommands.stream().filter(receiveCommand -> GitUtil.getBranch(receiveCommand.getRef()).equals(target)).findFirst().map(ReceiveCommand::getNewId).map(AnyObjectId::getName).orElse(target); - LogCommandRequest request = new LogCommandRequest(); - request.setBranch(sourceToUse); - request.setAncestorChangeset(targetToUse); + request.setBranch(branchMapper.map(branch)); + request.setAncestorChangeset(branchMapper.map(target)); request.setPagingLimit(1); - ChangesetPagingResult changesets = new GitLogComputer(repositoryId, repository).compute(request); - return changesets.getTotal() == 0; + return new GitLogComputer(repositoryId, repository).compute(request).getTotal() == 0; + } + + public interface BranchMapper { + String map(String branch); } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java index aaa5f09bda..755223afc2 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java @@ -26,15 +26,16 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- +import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.transport.ReceivePack; +import sonia.scm.repository.GitUtil; import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.api.GitHookBranchProvider; import sonia.scm.repository.api.GitHookMessageProvider; import sonia.scm.repository.api.GitHookTagProvider; -import sonia.scm.repository.api.GitPostReceiveHookMergeDetectionProvider; -import sonia.scm.repository.api.GitPreReceiveHookMergeDetectionProvider; +import sonia.scm.repository.api.GitReceiveHookMergeDetectionProvider; import sonia.scm.repository.api.HookBranchProvider; import sonia.scm.repository.api.HookFeature; import sonia.scm.repository.api.HookMessageProvider; @@ -116,12 +117,26 @@ public class GitHookContextProvider extends HookContextProvider @Override public HookMergeDetectionProvider getMergeDetectionProvider() { if (type == RepositoryHookType.POST_RECEIVE) { - return new GitPostReceiveHookMergeDetectionProvider(repository, repositoryId); + return new GitReceiveHookMergeDetectionProvider(repository, repositoryId, branch -> branch); } else { - return new GitPreReceiveHookMergeDetectionProvider(receiveCommands, repository, repositoryId); + return new GitReceiveHookMergeDetectionProvider(repository, repositoryId, this::findNewRevisionForBranchIfToBeUpdated); } } + private String findNewRevisionForBranchIfToBeUpdated(String branch) { + return receiveCommands + .stream() + .filter(receiveCommand -> isReceiveCommandForBranch(branch, receiveCommand)) + .map(ReceiveCommand::getNewId) + .map(AnyObjectId::getName) + .findFirst() + .orElse(branch); + } + + private boolean isReceiveCommandForBranch(String branch, ReceiveCommand receiveCommand) { + return GitUtil.getBranch(receiveCommand.getRef()).equals(branch); + } + @Override public Set getSupportedFeatures() { From 072d8f15c9221967582d4f4084487c9064ed1c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 6 Aug 2020 07:58:22 +0200 Subject: [PATCH 03/13] Detect merges where branch has been deleted, too --- ...PostReceiveHookMergeDetectionProvider.java | 52 ------------------- .../GitReceiveHookMergeDetectionProvider.java | 39 +++++++++++--- .../spi/GitHookContextProvider.java | 29 +---------- .../java/sonia/scm/web/GitReceiveHook.java | 2 +- 4 files changed, 35 insertions(+), 87 deletions(-) delete mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitPostReceiveHookMergeDetectionProvider.java diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitPostReceiveHookMergeDetectionProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitPostReceiveHookMergeDetectionProvider.java deleted file mode 100644 index e8e41ac3a2..0000000000 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitPostReceiveHookMergeDetectionProvider.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 org.eclipse.jgit.lib.Repository; -import sonia.scm.repository.ChangesetPagingResult; -import sonia.scm.repository.spi.GitLogComputer; -import sonia.scm.repository.spi.HookMergeDetectionProvider; -import sonia.scm.repository.spi.LogCommandRequest; - -public class GitPostReceiveHookMergeDetectionProvider implements HookMergeDetectionProvider { - private final Repository repository; - private final String repositoryId; - - public GitPostReceiveHookMergeDetectionProvider(Repository repository, String repositoryId) { - this.repository = repository; - this.repositoryId = repositoryId; - } - - @Override - public boolean branchesMerged(String target, String branch) { - LogCommandRequest request = new LogCommandRequest(); - request.setBranch(branch); - request.setAncestorChangeset(target); - request.setPagingLimit(1); - - ChangesetPagingResult changesets = new GitLogComputer(repositoryId, repository).compute(request); - return changesets.getTotal() == 0; - } -} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitReceiveHookMergeDetectionProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitReceiveHookMergeDetectionProvider.java index fef5806412..03a5b59837 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitReceiveHookMergeDetectionProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitReceiveHookMergeDetectionProvider.java @@ -24,33 +24,58 @@ package sonia.scm.repository.api; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.ReceiveCommand; +import sonia.scm.repository.GitUtil; import sonia.scm.repository.spi.GitLogComputer; import sonia.scm.repository.spi.HookMergeDetectionProvider; import sonia.scm.repository.spi.LogCommandRequest; +import java.util.List; + public class GitReceiveHookMergeDetectionProvider implements HookMergeDetectionProvider { private final Repository repository; private final String repositoryId; - private final BranchMapper branchMapper; + private final List receiveCommands; - public GitReceiveHookMergeDetectionProvider(Repository repository, String repositoryId, BranchMapper branchMapper) { + public GitReceiveHookMergeDetectionProvider(Repository repository, String repositoryId, List receiveCommands) { this.repository = repository; this.repositoryId = repositoryId; - this.branchMapper = branchMapper; + this.receiveCommands = receiveCommands; } @Override public boolean branchesMerged(String target, String branch) { LogCommandRequest request = new LogCommandRequest(); - request.setBranch(branchMapper.map(branch)); - request.setAncestorChangeset(branchMapper.map(target)); + request.setBranch(findRelevantRevisionForBranchIfToBeUpdated(branch)); + request.setAncestorChangeset(findRelevantRevisionForBranchIfToBeUpdated(target)); request.setPagingLimit(1); return new GitLogComputer(repositoryId, repository).compute(request).getTotal() == 0; } - public interface BranchMapper { - String map(String branch); + private String findRelevantRevisionForBranchIfToBeUpdated(String branch) { + return receiveCommands + .stream() + .filter(receiveCommand -> isReceiveCommandForBranch(branch, receiveCommand)) + .map(this::getRelevantRevision) + .map(AnyObjectId::getName) + .findFirst() + .orElse(branch); + } + + private boolean isReceiveCommandForBranch(String branch, ReceiveCommand receiveCommand) { + return receiveCommand.getType() != ReceiveCommand.Type.CREATE + && GitUtil.getBranch(receiveCommand.getRef()).equals(branch); + } + + private ObjectId getRelevantRevision(ReceiveCommand receiveCommand) { + if (receiveCommand.getType() == ReceiveCommand.Type.DELETE) { + return receiveCommand.getOldId(); + } else { + return receiveCommand.getNewId(); + } } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java index 755223afc2..bc8d633add 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java @@ -26,12 +26,9 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- -import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.transport.ReceivePack; -import sonia.scm.repository.GitUtil; -import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.api.GitHookBranchProvider; import sonia.scm.repository.api.GitHookMessageProvider; import sonia.scm.repository.api.GitHookTagProvider; @@ -67,20 +64,17 @@ public class GitHookContextProvider extends HookContextProvider /** * Constructs a new instance - * @param receivePack git receive pack + * @param receivePack git receive pack * @param receiveCommands received commands - * @param type */ public GitHookContextProvider( ReceivePack receivePack, List receiveCommands, - RepositoryHookType type, Repository repository, String repositoryId ) { this.receivePack = receivePack; this.receiveCommands = receiveCommands; - this.type = type; this.repository = repository; this.repositoryId = repositoryId; this.changesetProvider = new GitHookChangesetProvider(receivePack, @@ -116,25 +110,7 @@ public class GitHookContextProvider extends HookContextProvider @Override public HookMergeDetectionProvider getMergeDetectionProvider() { - if (type == RepositoryHookType.POST_RECEIVE) { - return new GitReceiveHookMergeDetectionProvider(repository, repositoryId, branch -> branch); - } else { - return new GitReceiveHookMergeDetectionProvider(repository, repositoryId, this::findNewRevisionForBranchIfToBeUpdated); - } - } - - private String findNewRevisionForBranchIfToBeUpdated(String branch) { - return receiveCommands - .stream() - .filter(receiveCommand -> isReceiveCommandForBranch(branch, receiveCommand)) - .map(ReceiveCommand::getNewId) - .map(AnyObjectId::getName) - .findFirst() - .orElse(branch); - } - - private boolean isReceiveCommandForBranch(String branch, ReceiveCommand receiveCommand) { - return GitUtil.getBranch(receiveCommand.getRef()).equals(branch); + return new GitReceiveHookMergeDetectionProvider(repository, repositoryId, receiveCommands); } @Override @@ -146,7 +122,6 @@ public class GitHookContextProvider extends HookContextProvider private final GitHookChangesetProvider changesetProvider; private final List receiveCommands; private final ReceivePack receivePack; - private final RepositoryHookType type; private final Repository repository; private final String repositoryId; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java index a2d218b99c..a9d74e0357 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java @@ -122,7 +122,7 @@ public class GitReceiveHook implements PreReceiveHook, PostReceiveHook logger.trace("resolved repository to {}", repositoryId); - GitHookContextProvider context = new GitHookContextProvider(rpack, receiveCommands, type, repository, repositoryId); + GitHookContextProvider context = new GitHookContextProvider(rpack, receiveCommands, repository, repositoryId); hookEventFacade.handle(repositoryId).fireHookEvent(type, context); From 6f207818124ad9162e2c11b13d7c9f7b44ba7320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 6 Aug 2020 14:40:43 +0200 Subject: [PATCH 04/13] Add first integration tests for merge detection --- scm-it/pom.xml | 10 + .../sonia/scm/it/MergeDetectionITCase.java | 176 ++++++++++++++++++ scm-plugins/pom.xml | 1 + .../client/spi/GitCheckoutCommand.java | 49 +++++ .../spi/GitDeleteRemoteBranchCommand.java | 64 +++++++ .../client/spi/GitMergeCommand.java | 64 +++++++ .../spi/GitRepositoryClientProvider.java | 19 +- .../scm-integration-test-plugin/pom.xml | 52 ++++++ .../it/resource/IntegrationTestResource.java | 113 +++++++++++ .../scm/it/resource/MergeDetectionHelper.java | 101 ++++++++++ .../main/resources/META-INF/scm/plugin.xml | 42 +++++ .../client/api/CheckoutCommandBuilder.java | 53 ++++++ .../repository/client/api/ClientCommand.java | 4 +- .../api/DeleteRemoteBranchCommandBuilder.java | 53 ++++++ .../client/api/MergeCommandBuilder.java | 48 +++++ .../client/api/RepositoryClient.java | 22 ++- .../client/spi/CheckoutCommand.java | 35 ++++ .../client/spi/DeleteRemoteBranchCommand.java | 32 ++++ .../repository/client/spi/MergeCommand.java | 37 ++++ .../repository/client/spi/MergeRequest.java | 89 +++++++++ .../client/spi/RepositoryClientProvider.java | 14 +- 21 files changed, 1071 insertions(+), 7 deletions(-) create mode 100644 scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitCheckoutCommand.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitDeleteRemoteBranchCommand.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitMergeCommand.java create mode 100644 scm-plugins/scm-integration-test-plugin/pom.xml create mode 100644 scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/IntegrationTestResource.java create mode 100644 scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/MergeDetectionHelper.java create mode 100644 scm-plugins/scm-integration-test-plugin/src/main/resources/META-INF/scm/plugin.xml create mode 100644 scm-test/src/main/java/sonia/scm/repository/client/api/CheckoutCommandBuilder.java create mode 100644 scm-test/src/main/java/sonia/scm/repository/client/api/DeleteRemoteBranchCommandBuilder.java create mode 100644 scm-test/src/main/java/sonia/scm/repository/client/api/MergeCommandBuilder.java create mode 100644 scm-test/src/main/java/sonia/scm/repository/client/spi/CheckoutCommand.java create mode 100644 scm-test/src/main/java/sonia/scm/repository/client/spi/DeleteRemoteBranchCommand.java create mode 100644 scm-test/src/main/java/sonia/scm/repository/client/spi/MergeCommand.java create mode 100644 scm-test/src/main/java/sonia/scm/repository/client/spi/MergeRequest.java diff --git a/scm-it/pom.xml b/scm-it/pom.xml index 90c22cda75..cdfd14f3e3 100644 --- a/scm-it/pom.xml +++ b/scm-it/pom.xml @@ -192,6 +192,7 @@ 2.10 + sonia.scm scm-webapp @@ -200,6 +201,15 @@ ${project.build.outputDirectory} scm-webapp.war + + + sonia.scm.plugins + scm-integration-test-plugin + ${project.version} + smp + ${scm.home}/plugins + scm-integration-test-plugin.smp + diff --git a/scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java b/scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java new file mode 100644 index 0000000000..6779b76b68 --- /dev/null +++ b/scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java @@ -0,0 +1,176 @@ +/* + * 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.it; + +import io.restassured.RestAssured; +import org.assertj.core.api.Assertions; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import sonia.scm.it.utils.RepositoryUtil; +import sonia.scm.it.utils.RestUtil; +import sonia.scm.it.utils.TestData; +import sonia.scm.repository.Person; +import sonia.scm.repository.client.api.RepositoryClient; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static java.lang.String.format; +import static java.util.Collections.singletonList; +import static sonia.scm.it.utils.TestData.USER_SCM_ADMIN; + +public class MergeDetectionITCase { + + private static final Person ARTHUR = new Person("arthur", "arthur@hitchhiker.com"); + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + RepositoryClient client; + String masterFile; + String developFile; + + @Before + public void createRepository() throws IOException { + TestData.createDefault(); + + client = RepositoryUtil.createRepositoryClient("git", temporaryFolder.getRoot()); + + masterFile = createFile("hg2g.md"); + developFile = createFile("how_to_make_tea.md"); + + client.getAddCommand().add(masterFile); + client.getCommitCommand().commit(ARTHUR, "Add base file"); + client.getPushCommand().push(); + + client.getBranchCommand().branch("develop"); + client.getCheckoutCommand().checkout("develop"); + client.getAddCommand().add(developFile); + client.getCommitCommand().commit(ARTHUR, "add more important things"); + client.getPushCommand().push(); + } + + @After + public void disableMergeDetection() { + RestAssured.given() + .auth().preemptive().basic(USER_SCM_ADMIN, USER_SCM_ADMIN) + .when() + .contentType("application/json") + .accept("application/json") + .body(toJson("{}")) + .post(RestUtil.createResourceUrl("integration-test/merge-detection/")) + .then() + .statusCode(204); + } + + @Test + public void shouldDetectSimpleMergeAsMerged() throws IOException { + client.getCheckoutCommand().checkout("master"); + client.getMergeCommand().merge("develop"); + + initializeMergeDetection("master", "develop"); + + client.getPushCommand().push(); + + Assertions.assertThat(getMergeDetectionResult("preMergeDetection", 0)).isTrue(); + Assertions.assertThat(getMergeDetectionResult("postMergeDetection", 0)).isTrue(); + } + + @Test + public void shouldDetectMergeWhenBranchHasBeenDeletedAsMerged() throws IOException { + client.getCheckoutCommand().checkout("master"); + client.getMergeCommand().merge("develop"); + client.getPushCommand().push(); + + initializeMergeDetection("master", "develop"); + + client.getDeleteRemoteBranchCommand().delete("develop"); + client.getPushCommand().push(); + + Assertions.assertThat(getMergeDetectionResult("preMergeDetection", 0)).isTrue(); + Assertions.assertThat(getMergeDetectionResult("postMergeDetection", 0)).isTrue(); + } + + @Test + public void shouldDetectNormalPushAsNotMerged() throws IOException { + client.getCheckoutCommand().checkout("develop"); + writeFile(developFile, "other content"); + client.getAddCommand().add(developFile); + client.getCommitCommand().commit(ARTHUR, "simple commit"); + + initializeMergeDetection("master", "develop"); + + client.getPushCommand().push(); + + Assertions.assertThat(getMergeDetectionResult("preMergeDetection", 0)).isFalse(); + Assertions.assertThat(getMergeDetectionResult("postMergeDetection", 0)).isFalse(); + } + + private boolean getMergeDetectionResult(String type, int n) { + return RestAssured.given() + .auth().preemptive().basic(USER_SCM_ADMIN, USER_SCM_ADMIN) + .when() + .accept("application/json") + .get(RestUtil.createResourceUrl("integration-test/")) + .then() + .statusCode(200) + .extract() + .jsonPath() + .getBoolean("_embedded." + + type + + "[" + n + "].merged"); + } + + private void initializeMergeDetection(String target, String branch) { + RestAssured.given() + .auth().preemptive().basic(USER_SCM_ADMIN, USER_SCM_ADMIN) + .when() + .contentType("application/json") + .accept("application/json") + .body(toJson(format("{'target': '%s', 'branch': '%s'}", target, branch))) + .post(RestUtil.createResourceUrl("integration-test/merge-detection/")) + .then() + .statusCode(204); + } + + private String createFile(String name) throws IOException { + temporaryFolder.newFile(name); + writeFile(name, "Some content"); + return name; + } + + private void writeFile(String name, String content) throws IOException { + Path file = temporaryFolder.getRoot().toPath().resolve(name); + Files.write(file, singletonList(content)); + } + + private String toJson(String json) { + return json.replaceAll("'", "\""); + } +} diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index a407c38471..f4f927fe0e 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -45,6 +45,7 @@ scm-git-plugin scm-svn-plugin scm-legacy-plugin + scm-integration-test-plugin diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitCheckoutCommand.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitCheckoutCommand.java new file mode 100644 index 0000000000..1db0140041 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitCheckoutCommand.java @@ -0,0 +1,49 @@ +/* + * 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.client.spi; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import sonia.scm.repository.client.api.RepositoryClientException; + +import java.io.IOException; + +public class GitCheckoutCommand implements CheckoutCommand { + + private Git git; + + GitCheckoutCommand(Git git) { + this.git = git; + } + + @Override + public void checkout(String name) throws IOException { + try { + git.checkout().setName(name).call(); + } catch (GitAPIException ex) { + throw new RepositoryClientException("could not checkout branch or revision", ex); + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitDeleteRemoteBranchCommand.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitDeleteRemoteBranchCommand.java new file mode 100644 index 0000000000..e7a5b03c25 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitDeleteRemoteBranchCommand.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.client.spi; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.PushCommand; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.RefSpec; +import sonia.scm.repository.client.api.RepositoryClientException; + +import java.io.IOException; + +public class GitDeleteRemoteBranchCommand implements DeleteRemoteBranchCommand { + + private final Git git; + private final CredentialsProvider credentialsProvider; + + GitDeleteRemoteBranchCommand(Git git, CredentialsProvider credentialsProvider) { + this.git = git; + this.credentialsProvider = credentialsProvider; + } + + @Override + public void delete(String name) throws IOException { + try { + git.branchDelete().setBranchNames("refs/heads/" + name).call(); + RefSpec refSpec = new RefSpec() + .setSource(null) + .setDestination("refs/heads/" + name); + PushCommand push = git.push(); + if (credentialsProvider != null) { + push.setCredentialsProvider(credentialsProvider); + } + push.setRefSpecs(refSpec).call(); + +// List result = git.branchDelete().setBranchNames(name).call(); + } catch (GitAPIException ex) { + throw new RepositoryClientException("could not delete branch", ex); + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitMergeCommand.java new file mode 100644 index 0000000000..6b27ee4a06 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitMergeCommand.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.client.spi; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.MergeResult; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import sonia.scm.repository.Changeset; +import sonia.scm.repository.GitChangesetConverter; +import sonia.scm.repository.client.api.RepositoryClientException; + +import java.io.IOException; + +public class GitMergeCommand implements MergeCommand { + + private final Git git; + + GitMergeCommand(Git git) { + this.git = git; + } + + @Override + public Changeset merge(MergeRequest request) throws IOException { + try (GitChangesetConverter converter = new GitChangesetConverter(git.getRepository())) { + ObjectId resolved = git.getRepository().resolve(request.getBranch()); + MergeResult mergeResult = git.merge() + .include(request.getBranch(), resolved) + .setMessage(request.getMessage()) + .call(); + + try (RevWalk revWalk = new RevWalk(git.getRepository())) { + RevCommit commit = revWalk.parseCommit(mergeResult.getNewHead()); + return converter.createChangeset(commit); + } + } catch (GitAPIException ex) { + throw new RepositoryClientException("could not commit changes to repository", ex); + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitRepositoryClientProvider.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitRepositoryClientProvider.java index 665e4bc8eb..1cb11dbe2c 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitRepositoryClientProvider.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitRepositoryClientProvider.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.client.spi; //~--- non-JDK imports -------------------------------------------------------- @@ -49,7 +49,7 @@ public class GitRepositoryClientProvider extends RepositoryClientProvider private static final Set SUPPORTED_COMMANDS = ImmutableSet.of(ClientCommand.ADD, ClientCommand.REMOVE, ClientCommand.COMMIT, ClientCommand.TAG, ClientCommand.BRANCH, - ClientCommand.PUSH); + ClientCommand.DELETE_REMOTE_BRANCH, ClientCommand.MERGE, ClientCommand.PUSH); //~--- constructors --------------------------------------------------------- @@ -118,6 +118,16 @@ public class GitRepositoryClientProvider extends RepositoryClientProvider return new GitBranchCommand(git); } + @Override + public DeleteRemoteBranchCommand getDeleteRemoteBranchCommand() { + return new GitDeleteRemoteBranchCommand(git, credentialsProvider); + } + + @Override + public CheckoutCommand getCheckoutCommand() { + return new GitCheckoutCommand(git); + } + /** * Method description * @@ -178,6 +188,11 @@ public class GitRepositoryClientProvider extends RepositoryClientProvider return new GitTagCommand(git); } + @Override + public MergeCommand getMergeCommand() { + return new GitMergeCommand(git); + } + @Override public File getWorkingCopy() { return git.getRepository().getWorkTree(); diff --git a/scm-plugins/scm-integration-test-plugin/pom.xml b/scm-plugins/scm-integration-test-plugin/pom.xml new file mode 100644 index 0000000000..4728510608 --- /dev/null +++ b/scm-plugins/scm-integration-test-plugin/pom.xml @@ -0,0 +1,52 @@ + + + + 4.0.0 + + sonia.scm.plugins + scm-plugins + 2.4.0-SNAPSHOT + + + scm-integration-test-plugin + Add functions for integration tests. This is not intended for production systems. + 2.4.0-SNAPSHOT + smp + + + + + + + javax.servlet + javax.servlet-api + ${servlet.version} + provided + + + + diff --git a/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/IntegrationTestResource.java b/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/IntegrationTestResource.java new file mode 100644 index 0000000000..3d6e131ebd --- /dev/null +++ b/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/IntegrationTestResource.java @@ -0,0 +1,113 @@ +/* + * 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.it.resource; + +import de.otto.edison.hal.Embedded; +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.Getter; +import lombok.Setter; +import sonia.scm.api.v2.resources.LinkBuilder; +import sonia.scm.api.v2.resources.ScmPathInfoStore; +import sonia.scm.plugin.Extension; + +import javax.inject.Inject; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +import static de.otto.edison.hal.Embedded.embeddedBuilder; +import static de.otto.edison.hal.Links.linkingTo; + +/** + * Web Service Resource to support integration tests. + */ +@OpenAPIDefinition(tags = { + @Tag(name = "Integration Test", description = "Support for integration tests") +}) +@Path(IntegrationTestResource.INTEGRATION_TEST_PATH_V2) +@Extension +public class IntegrationTestResource { + + static final String INTEGRATION_TEST_PATH_V2 = "v2/integration-test"; + + private final ScmPathInfoStore scmPathInfoStore; + private final MergeDetectionHelper mergeDetectionHelper; + + @Inject + public IntegrationTestResource(ScmPathInfoStore scmPathInfoStore, MergeDetectionHelper mergeDetectionHelper) { + this.scmPathInfoStore = scmPathInfoStore; + this.mergeDetectionHelper = mergeDetectionHelper; + } + + @GET + @Path("") + @Produces("application/json") + public CollectionDto get() { + Links links = linkingTo() + .self(self()) + .build(); + Embedded embedded = embeddedBuilder() + .with("preMergeDetection", mergeDetectionHelper.getPreMergeDetections()) + .with("postMergeDetection", mergeDetectionHelper.getPostMergeDetections()) + .build(); + return new CollectionDto(links, embedded); + } + + @POST + @Path("merge-detection") + @Consumes("application/json") + public void initMergeDetection(MergeDetectionConfigurationDto mergeDetectionConfiguration) { + mergeDetectionHelper.initialize(mergeDetectionConfiguration.getTarget(), mergeDetectionConfiguration.getBranch()); + } + + private String self() { + LinkBuilder linkBuilder = new LinkBuilder(scmPathInfoStore.get(), IntegrationTestResource.class); + return linkBuilder.method("get").parameters().href(); + } + + static class CollectionDto extends HalRepresentation { + + CollectionDto(Links links, Embedded embedded) { + super(links, embedded); + } + + @Override + protected HalRepresentation withEmbedded(String rel, HalRepresentation embeddedItem) { + return super.withEmbedded(rel, embeddedItem); + } + } + + @Getter + @Setter + static class MergeDetectionConfigurationDto { + private String target; + private String branch; + } +} diff --git a/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/MergeDetectionHelper.java b/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/MergeDetectionHelper.java new file mode 100644 index 0000000000..10d9fbc31a --- /dev/null +++ b/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/MergeDetectionHelper.java @@ -0,0 +1,101 @@ +/* + * 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.it.resource; + +import com.github.legman.Subscribe; +import de.otto.edison.hal.HalRepresentation; +import lombok.AllArgsConstructor; +import lombok.Getter; +import sonia.scm.EagerSingleton; +import sonia.scm.plugin.Extension; +import sonia.scm.repository.PostReceiveRepositoryHookEvent; +import sonia.scm.repository.PreReceiveRepositoryHookEvent; +import sonia.scm.repository.RepositoryHookEvent; +import sonia.scm.repository.spi.HookMergeDetectionProvider; + +import java.util.ArrayList; +import java.util.List; + +@EagerSingleton +@Extension +public class MergeDetectionHelper { + + private final List preMergeDetections = new ArrayList<>(); + private final List postMergeDetections = new ArrayList<>(); + + private String target; + private String branch; + + @Subscribe + public void handlePreReceiveEvent(PreReceiveRepositoryHookEvent event) { + if (target == null || branch == null) { + return; + } + preMergeDetections.add(createDto(event)); + } + + @Subscribe + public void handlePostReceiveEvent(PostReceiveRepositoryHookEvent event) { + if (target == null || branch == null) { + return; + } + postMergeDetections.add(createDto(event)); + } + + public ResultDto createDto(RepositoryHookEvent event) { + HookMergeDetectionProvider mergeDetectionProvider = event.getContext().getMergeDetectionProvider(); + boolean merged = mergeDetectionProvider.branchesMerged(target, branch); + return new ResultDto( + event.getClass().getSimpleName(), + event.getRepository().getNamespace(), + event.getRepository().getName(), + merged + ); + } + + void initialize(String target, String branch) { + this.target = target; + this.branch = branch; + preMergeDetections.clear(); + postMergeDetections.clear(); + } + + public List getPreMergeDetections() { + return preMergeDetections; + } + + public List getPostMergeDetections() { + return postMergeDetections; + } + + @Getter + @AllArgsConstructor + static class ResultDto extends HalRepresentation { + private String type; + private String namespace; + private String name; + private boolean merged; + } +} diff --git a/scm-plugins/scm-integration-test-plugin/src/main/resources/META-INF/scm/plugin.xml b/scm-plugins/scm-integration-test-plugin/src/main/resources/META-INF/scm/plugin.xml new file mode 100644 index 0000000000..f2f70ba9ff --- /dev/null +++ b/scm-plugins/scm-integration-test-plugin/src/main/resources/META-INF/scm/plugin.xml @@ -0,0 +1,42 @@ + + + + + + 2 + + + Integration Test Support + Cloudogu GmbH + Test + + + + ${project.parent.version} + + + diff --git a/scm-test/src/main/java/sonia/scm/repository/client/api/CheckoutCommandBuilder.java b/scm-test/src/main/java/sonia/scm/repository/client/api/CheckoutCommandBuilder.java new file mode 100644 index 0000000000..9fab6f40c9 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/repository/client/api/CheckoutCommandBuilder.java @@ -0,0 +1,53 @@ +/* + * 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.client.api; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.repository.client.spi.CheckoutCommand; + +import java.io.IOException; + +/** + * @since 2.4.0 + */ +public final class CheckoutCommandBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(CheckoutCommandBuilder.class); + + private final CheckoutCommand command; + + public CheckoutCommandBuilder(CheckoutCommand command) { + this.command = command; + } + + public CheckoutCommandBuilder checkout(String name) throws IOException { + LOG.debug("checkout {}", name); + + command.checkout(name); + + return this; + } +} diff --git a/scm-test/src/main/java/sonia/scm/repository/client/api/ClientCommand.java b/scm-test/src/main/java/sonia/scm/repository/client/api/ClientCommand.java index bc022a284e..37e1cce24a 100644 --- a/scm-test/src/main/java/sonia/scm/repository/client/api/ClientCommand.java +++ b/scm-test/src/main/java/sonia/scm/repository/client/api/ClientCommand.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.client.api; /** @@ -31,5 +31,5 @@ package sonia.scm.repository.client.api; */ public enum ClientCommand { - ADD, REMOVE, COMMIT, PUSH, TAG, BRANCH + ADD, REMOVE, COMMIT, PUSH, TAG, BRANCH, DELETE_REMOTE_BRANCH, CHECKOUT, MERGE } diff --git a/scm-test/src/main/java/sonia/scm/repository/client/api/DeleteRemoteBranchCommandBuilder.java b/scm-test/src/main/java/sonia/scm/repository/client/api/DeleteRemoteBranchCommandBuilder.java new file mode 100644 index 0000000000..cc20321b15 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/repository/client/api/DeleteRemoteBranchCommandBuilder.java @@ -0,0 +1,53 @@ +/* + * 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.client.api; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.repository.client.spi.DeleteRemoteBranchCommand; + +import java.io.IOException; + +/** + * @since 2.4.0 + */ +public final class DeleteRemoteBranchCommandBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(DeleteRemoteBranchCommandBuilder.class); + + private DeleteRemoteBranchCommand command; + + public DeleteRemoteBranchCommandBuilder(DeleteRemoteBranchCommand command) { + this.command = command; + } + + public DeleteRemoteBranchCommandBuilder delete(String name) throws IOException { + LOG.debug("delete branch {}", name); + + command.delete(name); + + return this; + } +} diff --git a/scm-test/src/main/java/sonia/scm/repository/client/api/MergeCommandBuilder.java b/scm-test/src/main/java/sonia/scm/repository/client/api/MergeCommandBuilder.java new file mode 100644 index 0000000000..78cbe240d1 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/repository/client/api/MergeCommandBuilder.java @@ -0,0 +1,48 @@ +/* + * 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.client.api; + +import sonia.scm.repository.client.spi.MergeCommand; +import sonia.scm.repository.client.spi.MergeRequest; + +import java.io.IOException; + +/** + * @since 2.4.0 + */ +public final class MergeCommandBuilder { + + private final MergeCommand command; + + MergeCommandBuilder(MergeCommand command) { + this.command = command; + } + + public void merge(String branch) throws IOException { + MergeRequest request = new MergeRequest(); + request.setBranch(branch); + command.merge(request); + } +} diff --git a/scm-test/src/main/java/sonia/scm/repository/client/api/RepositoryClient.java b/scm-test/src/main/java/sonia/scm/repository/client/api/RepositoryClient.java index 2dbb01d667..660cdb160b 100644 --- a/scm-test/src/main/java/sonia/scm/repository/client/api/RepositoryClient.java +++ b/scm-test/src/main/java/sonia/scm/repository/client/api/RepositoryClient.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.client.api; import org.slf4j.Logger; @@ -60,6 +60,18 @@ public final class RepositoryClient implements Closeable { return new BranchCommandBuilder(clientProvider.getBranchCommand()); } + public DeleteRemoteBranchCommandBuilder getDeleteRemoteBranchCommand() { + logger.trace("delete branch command"); + + return new DeleteRemoteBranchCommandBuilder(clientProvider.getDeleteRemoteBranchCommand()); + } + + public CheckoutCommandBuilder getCheckoutCommand() { + logger.trace("create checkout command"); + + return new CheckoutCommandBuilder(clientProvider.getCheckoutCommand()); + } + public CommitCommandBuilder getCommitCommand() { logger.trace("create commit command"); @@ -84,10 +96,16 @@ public final class RepositoryClient implements Closeable { return new TagCommandBuilder(clientProvider.getTagCommand()); } + public MergeCommandBuilder getMergeCommand() { + logger.trace("create merge command"); + + return new MergeCommandBuilder(clientProvider.getMergeCommand()); + } + public File getWorkingCopy() { return clientProvider.getWorkingCopy(); } - + public boolean isCommandSupported(ClientCommand command) { return clientProvider.getSupportedClientCommands().contains(command); } diff --git a/scm-test/src/main/java/sonia/scm/repository/client/spi/CheckoutCommand.java b/scm-test/src/main/java/sonia/scm/repository/client/spi/CheckoutCommand.java new file mode 100644 index 0000000000..262ca3894c --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/repository/client/spi/CheckoutCommand.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.client.spi; + +import java.io.IOException; + +/** + * @since 2.4.0 + */ +public interface CheckoutCommand { + + void checkout(String name) throws IOException; +} diff --git a/scm-test/src/main/java/sonia/scm/repository/client/spi/DeleteRemoteBranchCommand.java b/scm-test/src/main/java/sonia/scm/repository/client/spi/DeleteRemoteBranchCommand.java new file mode 100644 index 0000000000..5dea57f965 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/repository/client/spi/DeleteRemoteBranchCommand.java @@ -0,0 +1,32 @@ +/* + * 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.client.spi; + +import java.io.IOException; + +public interface DeleteRemoteBranchCommand { + + void delete(String name) throws IOException; +} diff --git a/scm-test/src/main/java/sonia/scm/repository/client/spi/MergeCommand.java b/scm-test/src/main/java/sonia/scm/repository/client/spi/MergeCommand.java new file mode 100644 index 0000000000..a80031b655 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/repository/client/spi/MergeCommand.java @@ -0,0 +1,37 @@ +/* + * 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.client.spi; + +import sonia.scm.repository.Changeset; + +import java.io.IOException; + +/** + * @since 2.4.0 + */ +public interface MergeCommand { + + Changeset merge(MergeRequest request) throws IOException; +} diff --git a/scm-test/src/main/java/sonia/scm/repository/client/spi/MergeRequest.java b/scm-test/src/main/java/sonia/scm/repository/client/spi/MergeRequest.java new file mode 100644 index 0000000000..ba7a133cb9 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/repository/client/spi/MergeRequest.java @@ -0,0 +1,89 @@ +/* + * 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.client.spi; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; + +/** + * @since 2.4.0 + */ +public final class MergeRequest { + + private String branch; + private String message; + + @Override + public boolean equals(Object obj) { + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + final MergeRequest other = (MergeRequest) obj; + + return Objects.equal(branch, other.branch) + && Objects.equal(message, other.message); + } + + @Override + public int hashCode() { + return Objects.hashCode(branch, message); + } + + public void reset() { + this.branch = null; + this.message = null; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("branch", branch) + .add("message", message) + .toString(); + } + + public void setBranch(String branch) { + this.branch = branch; + } + + public void setMessage(String message) { + this.message = message; + } + + String getBranch() { + return branch; + } + + String getMessage() { + return message; + } +} diff --git a/scm-test/src/main/java/sonia/scm/repository/client/spi/RepositoryClientProvider.java b/scm-test/src/main/java/sonia/scm/repository/client/spi/RepositoryClientProvider.java index feb1d3dd09..bfe4c5afe8 100644 --- a/scm-test/src/main/java/sonia/scm/repository/client/spi/RepositoryClientProvider.java +++ b/scm-test/src/main/java/sonia/scm/repository/client/spi/RepositoryClientProvider.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.client.spi; //~--- non-JDK imports -------------------------------------------------------- @@ -87,6 +87,14 @@ public abstract class RepositoryClientProvider implements Closeable throw new ClientCommandNotSupportedException(ClientCommand.BRANCH); } + public DeleteRemoteBranchCommand getDeleteRemoteBranchCommand() { + throw new ClientCommandNotSupportedException(ClientCommand.DELETE_REMOTE_BRANCH); + } + + public CheckoutCommand getCheckoutCommand() { + throw new ClientCommandNotSupportedException(ClientCommand.CHECKOUT); + } + /** * Method description * @@ -131,6 +139,10 @@ public abstract class RepositoryClientProvider implements Closeable throw new ClientCommandNotSupportedException(ClientCommand.TAG); } + public MergeCommand getMergeCommand() { + throw new ClientCommandNotSupportedException(ClientCommand.MERGE); + } + /** * Returns the working copy of the repository client. * From e8146943143b47b937a82314ae7ee51be140d0f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 7 Aug 2020 07:34:43 +0200 Subject: [PATCH 05/13] Fix scm home folder for integration tests This variable should be absolute, because otherwise this may resolve to different directories. For instance the web server resolves the relative path to the root of the parent pom, while the dependency plugin resolves it to the path of the scm-it pom. This led to the error, that the scm-integration-test-plugin was not copied to the correct plugin directory and therefore was not installed by the test scm server. --- scm-it/pom.xml | 2 +- scm-webapp/pom.xml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scm-it/pom.xml b/scm-it/pom.xml index cdfd14f3e3..3077b38f78 100644 --- a/scm-it/pom.xml +++ b/scm-it/pom.xml @@ -283,7 +283,7 @@ DEVELOPMENT - target/scm-it + ${project.build.directory}/scm-it ${project.basedir}/../scm-webapp/src/main/resources/logback.default.xml diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index a2f1b97957..aad8a67163 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -675,7 +675,7 @@ DEVELOPMENT - target/scm-it + ${project.build.directory}/scm-it default 0.11.2 2.53.1 @@ -814,7 +814,7 @@ scm.home - target/scm-it + ${scm.home} scm.stage @@ -903,7 +903,7 @@ scm.home - target/scm-it + ${scm.home} ${project.basedir}/src/main/conf/jetty.xml From 2433c50d64a1e2fd7130d7eb1eb1b405b9788e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 7 Aug 2020 08:01:13 +0200 Subject: [PATCH 06/13] Add test for "real" merge commits --- .../sonia/scm/it/MergeDetectionITCase.java | 13 ++++++++++++ .../client/spi/GitMergeCommand.java | 20 +++++++++++++++++-- .../client/api/MergeCommandBuilder.java | 17 +++++++++++++++- .../repository/client/spi/MergeRequest.java | 13 ++++++++++++ 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java b/scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java index 6779b76b68..adc5a9efe0 100644 --- a/scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java @@ -91,6 +91,19 @@ public class MergeDetectionITCase { @Test public void shouldDetectSimpleMergeAsMerged() throws IOException { + client.getCheckoutCommand().checkout("master"); + client.getMergeCommand().noFf().merge("develop"); + + initializeMergeDetection("master", "develop"); + + client.getPushCommand().push(); + + Assertions.assertThat(getMergeDetectionResult("preMergeDetection", 0)).isTrue(); + Assertions.assertThat(getMergeDetectionResult("postMergeDetection", 0)).isTrue(); + } + + @Test + public void shouldDetectFastForwardAsMerged() throws IOException { client.getCheckoutCommand().checkout("master"); client.getMergeCommand().merge("develop"); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitMergeCommand.java index 6b27ee4a06..464d32eba6 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitMergeCommand.java @@ -48,9 +48,25 @@ public class GitMergeCommand implements MergeCommand { public Changeset merge(MergeRequest request) throws IOException { try (GitChangesetConverter converter = new GitChangesetConverter(git.getRepository())) { ObjectId resolved = git.getRepository().resolve(request.getBranch()); - MergeResult mergeResult = git.merge() + org.eclipse.jgit.api.MergeCommand mergeCommand = git.merge() .include(request.getBranch(), resolved) - .setMessage(request.getMessage()) + .setMessage(request.getMessage()); + + switch (request.getFfMode()) { + case FF: + mergeCommand.setFastForward(org.eclipse.jgit.api.MergeCommand.FastForwardMode.FF); + break; + case NO_FF: + mergeCommand.setFastForward(org.eclipse.jgit.api.MergeCommand.FastForwardMode.NO_FF); + break; + case FF_ONLY: + mergeCommand.setFastForward(org.eclipse.jgit.api.MergeCommand.FastForwardMode.FF_ONLY); + break; + default: + throw new IllegalStateException("Unknown FF mode: " + request.getFfMode()); + } + + MergeResult mergeResult = mergeCommand .call(); try (RevWalk revWalk = new RevWalk(git.getRepository())) { diff --git a/scm-test/src/main/java/sonia/scm/repository/client/api/MergeCommandBuilder.java b/scm-test/src/main/java/sonia/scm/repository/client/api/MergeCommandBuilder.java index 78cbe240d1..ade806ade0 100644 --- a/scm-test/src/main/java/sonia/scm/repository/client/api/MergeCommandBuilder.java +++ b/scm-test/src/main/java/sonia/scm/repository/client/api/MergeCommandBuilder.java @@ -35,13 +35,28 @@ import java.io.IOException; public final class MergeCommandBuilder { private final MergeCommand command; + private final MergeRequest request = new MergeRequest(); MergeCommandBuilder(MergeCommand command) { this.command = command; } + public MergeCommandBuilder ffOnly() { + request.setFfMode(MergeRequest.FastForwardMode.FF_ONLY); + return this; + } + + public MergeCommandBuilder noFf() { + request.setFfMode(MergeRequest.FastForwardMode.NO_FF); + return this; + } + + public MergeCommandBuilder ffIfPossible() { + request.setFfMode(MergeRequest.FastForwardMode.FF); + return this; + } + public void merge(String branch) throws IOException { - MergeRequest request = new MergeRequest(); request.setBranch(branch); command.merge(request); } diff --git a/scm-test/src/main/java/sonia/scm/repository/client/spi/MergeRequest.java b/scm-test/src/main/java/sonia/scm/repository/client/spi/MergeRequest.java index ba7a133cb9..826e5d9f03 100644 --- a/scm-test/src/main/java/sonia/scm/repository/client/spi/MergeRequest.java +++ b/scm-test/src/main/java/sonia/scm/repository/client/spi/MergeRequest.java @@ -34,6 +34,7 @@ public final class MergeRequest { private String branch; private String message; + private FastForwardMode ffMode = FastForwardMode.FF; @Override public boolean equals(Object obj) { @@ -79,6 +80,10 @@ public final class MergeRequest { this.message = message; } + public void setFfMode(FastForwardMode ffMode) { + this.ffMode = ffMode; + } + String getBranch() { return branch; } @@ -86,4 +91,12 @@ public final class MergeRequest { String getMessage() { return message; } + + public FastForwardMode getFfMode() { + return ffMode; + } + + public enum FastForwardMode { + FF_ONLY, FF, NO_FF + } } From fa560f9913ba4740fa96ee9c4fff222f5c9a4dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 7 Aug 2020 09:52:29 +0200 Subject: [PATCH 07/13] Use target dir of root pom It looks like the variable scm.home is bound differently for the dependency plugin and the jetty plugin, when scm-webapp was build beforehand. --- scm-it/pom.xml | 2 +- scm-webapp/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-it/pom.xml b/scm-it/pom.xml index 3077b38f78..41ab2ab3d2 100644 --- a/scm-it/pom.xml +++ b/scm-it/pom.xml @@ -283,7 +283,7 @@ DEVELOPMENT - ${project.build.directory}/scm-it + ${project.parent.build.directory}/scm-it ${project.basedir}/../scm-webapp/src/main/resources/logback.default.xml diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index aad8a67163..6032487171 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -675,7 +675,7 @@ DEVELOPMENT - ${project.build.directory}/scm-it + ${project.parent.build.directory}/scm-it default 0.11.2 2.53.1 From a3a26c057e7fee538652445caaace0cc0d2af452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 7 Aug 2020 10:53:08 +0200 Subject: [PATCH 08/13] Cleanup --- .../repository/client/spi/GitDeleteRemoteBranchCommand.java | 2 -- scm-plugins/scm-integration-test-plugin/pom.xml | 2 -- .../java/sonia/scm/it/resource/IntegrationTestResource.java | 4 ---- 3 files changed, 8 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitDeleteRemoteBranchCommand.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitDeleteRemoteBranchCommand.java index e7a5b03c25..69dc53a958 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitDeleteRemoteBranchCommand.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitDeleteRemoteBranchCommand.java @@ -55,8 +55,6 @@ public class GitDeleteRemoteBranchCommand implements DeleteRemoteBranchCommand { push.setCredentialsProvider(credentialsProvider); } push.setRefSpecs(refSpec).call(); - -// List result = git.branchDelete().setBranchNames(name).call(); } catch (GitAPIException ex) { throw new RepositoryClientException("could not delete branch", ex); } diff --git a/scm-plugins/scm-integration-test-plugin/pom.xml b/scm-plugins/scm-integration-test-plugin/pom.xml index 4728510608..2c69c5fbb2 100644 --- a/scm-plugins/scm-integration-test-plugin/pom.xml +++ b/scm-plugins/scm-integration-test-plugin/pom.xml @@ -39,8 +39,6 @@ - - javax.servlet javax.servlet-api diff --git a/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/IntegrationTestResource.java b/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/IntegrationTestResource.java index 3d6e131ebd..eaa32430bb 100644 --- a/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/IntegrationTestResource.java +++ b/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/IntegrationTestResource.java @@ -48,11 +48,7 @@ import static de.otto.edison.hal.Links.linkingTo; /** * Web Service Resource to support integration tests. */ -@OpenAPIDefinition(tags = { - @Tag(name = "Integration Test", description = "Support for integration tests") -}) @Path(IntegrationTestResource.INTEGRATION_TEST_PATH_V2) -@Extension public class IntegrationTestResource { static final String INTEGRATION_TEST_PATH_V2 = "v2/integration-test"; From e2e74e73af55fe9d95908f10ea0c8b218f8a7e59 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 7 Aug 2020 14:18:30 +0200 Subject: [PATCH 09/13] Remove unused imports --- .../java/sonia/scm/it/resource/IntegrationTestResource.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/IntegrationTestResource.java b/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/IntegrationTestResource.java index eaa32430bb..f6b606e001 100644 --- a/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/IntegrationTestResource.java +++ b/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/IntegrationTestResource.java @@ -27,13 +27,10 @@ package sonia.scm.it.resource; import de.otto.edison.hal.Embedded; import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Links; -import io.swagger.v3.oas.annotations.OpenAPIDefinition; -import io.swagger.v3.oas.annotations.tags.Tag; import lombok.Getter; import lombok.Setter; import sonia.scm.api.v2.resources.LinkBuilder; import sonia.scm.api.v2.resources.ScmPathInfoStore; -import sonia.scm.plugin.Extension; import javax.inject.Inject; import javax.ws.rs.Consumes; From 264ad2bef1b8cf9735fe8ac2ab741eef232a3f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 7 Aug 2020 17:25:23 +0200 Subject: [PATCH 10/13] Migrate to JUnit 5 --- .../sonia/scm/it/MergeDetectionITCase.java | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java b/scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java index adc5a9efe0..f0115e8c77 100644 --- a/scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java @@ -26,11 +26,10 @@ package sonia.scm.it; import io.restassured.RestAssured; import org.assertj.core.api.Assertions; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import sonia.scm.it.utils.RepositoryUtil; import sonia.scm.it.utils.RestUtil; import sonia.scm.it.utils.TestData; @@ -45,25 +44,22 @@ import static java.lang.String.format; import static java.util.Collections.singletonList; import static sonia.scm.it.utils.TestData.USER_SCM_ADMIN; -public class MergeDetectionITCase { +class MergeDetectionITCase { private static final Person ARTHUR = new Person("arthur", "arthur@hitchhiker.com"); - @Rule - public TemporaryFolder temporaryFolder = new TemporaryFolder(); - RepositoryClient client; String masterFile; String developFile; - @Before - public void createRepository() throws IOException { + @BeforeEach + void createRepository(@TempDir Path tempDir) throws IOException { TestData.createDefault(); - client = RepositoryUtil.createRepositoryClient("git", temporaryFolder.getRoot()); + client = RepositoryUtil.createRepositoryClient("git", tempDir.toFile()); - masterFile = createFile("hg2g.md"); - developFile = createFile("how_to_make_tea.md"); + masterFile = createFile(tempDir, "hg2g.md"); + developFile = createFile(tempDir, "how_to_make_tea.md"); client.getAddCommand().add(masterFile); client.getCommitCommand().commit(ARTHUR, "Add base file"); @@ -76,8 +72,8 @@ public class MergeDetectionITCase { client.getPushCommand().push(); } - @After - public void disableMergeDetection() { + @AfterEach + void disableMergeDetection() { RestAssured.given() .auth().preemptive().basic(USER_SCM_ADMIN, USER_SCM_ADMIN) .when() @@ -90,7 +86,7 @@ public class MergeDetectionITCase { } @Test - public void shouldDetectSimpleMergeAsMerged() throws IOException { + void shouldDetectSimpleMergeAsMerged() throws IOException { client.getCheckoutCommand().checkout("master"); client.getMergeCommand().noFf().merge("develop"); @@ -103,7 +99,7 @@ public class MergeDetectionITCase { } @Test - public void shouldDetectFastForwardAsMerged() throws IOException { + void shouldDetectFastForwardAsMerged() throws IOException { client.getCheckoutCommand().checkout("master"); client.getMergeCommand().merge("develop"); @@ -116,7 +112,7 @@ public class MergeDetectionITCase { } @Test - public void shouldDetectMergeWhenBranchHasBeenDeletedAsMerged() throws IOException { + void shouldDetectMergeWhenBranchHasBeenDeletedAsMerged() throws IOException { client.getCheckoutCommand().checkout("master"); client.getMergeCommand().merge("develop"); client.getPushCommand().push(); @@ -131,9 +127,9 @@ public class MergeDetectionITCase { } @Test - public void shouldDetectNormalPushAsNotMerged() throws IOException { + void shouldDetectNormalPushAsNotMerged(@TempDir Path tempDir) throws IOException { client.getCheckoutCommand().checkout("develop"); - writeFile(developFile, "other content"); + writeFile(tempDir, developFile, "other content"); client.getAddCommand().add(developFile); client.getCommitCommand().commit(ARTHUR, "simple commit"); @@ -172,14 +168,14 @@ public class MergeDetectionITCase { .statusCode(204); } - private String createFile(String name) throws IOException { - temporaryFolder.newFile(name); - writeFile(name, "Some content"); + private String createFile(Path tempDir, String name) throws IOException { + Files.createFile(tempDir.resolve(name)); + writeFile(tempDir, name, "Some content"); return name; } - private void writeFile(String name, String content) throws IOException { - Path file = temporaryFolder.getRoot().toPath().resolve(name); + private void writeFile(Path tempDir, String name, String content) throws IOException { + Path file = tempDir.resolve(name); Files.write(file, singletonList(content)); } From 1ffe68484f7ef53b0ff1032f51129b815ce00136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 7 Aug 2020 20:54:23 +0200 Subject: [PATCH 11/13] Add documentation for integration tests --- docs/en/development/integration-tests.md | 24 ++++++++++++++++++++++++ docs/en/navigation.yml | 1 + 2 files changed, 25 insertions(+) create mode 100644 docs/en/development/integration-tests.md diff --git a/docs/en/development/integration-tests.md b/docs/en/development/integration-tests.md new file mode 100644 index 0000000000..4b9589acfe --- /dev/null +++ b/docs/en/development/integration-tests.md @@ -0,0 +1,24 @@ +--- +title: Integration Tests +subtitle: How to run integration tests +displayToc: false +--- + +You can find the integration tests in the module **scm-it** (and a few still in **scm-webapp**). To run them, +simply start maven with the profile `it`: + +``` +mvn install -Pit -DskipUnitTests -pl :scm-webapp,:scm-it +``` + +This will start a jetty server and execute all integration tests in the maven phase `integration-test` using the +normal failsafe plugin. Integration tests are all classes ending with `ITCase` and are written using JUnit. + +To develop integration tests, you should start a local server with the **scm-integration-test-plugin**. This plugin is +used as a way to introspect server internals. For example you can register event listeners here and access their +triggers with a REST endpoint. Of course, this plugin is not and should not be installed in productive systems. +You can start the server with this plugin using the following maven call: + +``` +mvn run -pl :scm-integration-test-plugin +``` diff --git a/docs/en/navigation.yml b/docs/en/navigation.yml index 9c30b03156..3d5b6518a8 100644 --- a/docs/en/navigation.yml +++ b/docs/en/navigation.yml @@ -31,6 +31,7 @@ - /development/definition-of-done/ - /development/ui-dod/ - /development/decision-table/ + - /development/integration-tests/ - section: Plugin Development entries: From 3ea55c34226fba38b11c41424c5dc1dd5ab451c1 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 10 Aug 2020 08:53:49 +0200 Subject: [PATCH 12/13] Fix small SonarQube findings --- .../main/java/sonia/scm/repository/spi/AbstractGitCommand.java | 1 - .../src/main/java/sonia/scm/repository/spi/GitLogComputer.java | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) 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 28f0f72b8b..9e13e82971 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 @@ -41,7 +41,6 @@ import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.RemoteRefUpdate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.repository.GitUtil; import sonia.scm.repository.GitWorkingCopyFactory; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Person; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogComputer.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogComputer.java index b8c31045d8..eae8ea5851 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogComputer.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogComputer.java @@ -38,7 +38,6 @@ import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.ContextEntry; import sonia.scm.NotFoundException; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; @@ -171,7 +170,7 @@ public class GitLogComputer { } else { LOG.debug("the repository with id {} seems to be empty", this.repositoryId); - return new ChangesetPagingResult(0, Collections.EMPTY_LIST); + return new ChangesetPagingResult(0, Collections.emptyList()); } } catch (MissingObjectException e) { throw notFound(entity(GitLogCommand.REVISION, e.getObjectId().getName()).in(sonia.scm.repository.Repository.class, repositoryId)); From e7535903f7eb04d98bb74036b1bf174c02c66c50 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 10 Aug 2020 09:22:29 +0200 Subject: [PATCH 13/13] Fix unit test on platforms which are not using UTF-8 as default --- .../spi/GitDiffCommand_DequoteOutputStreamTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommand_DequoteOutputStreamTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommand_DequoteOutputStreamTest.java index fb4cda8ca0..3e3f87131e 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommand_DequoteOutputStreamTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommand_DequoteOutputStreamTest.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; import org.assertj.core.api.Assertions; @@ -48,7 +48,7 @@ public class GitDiffCommand_DequoteOutputStreamTest { stream.write(bytes, 0, bytes.length); stream.flush(); - Assertions.assertThat(buffer.toString()).isEqualTo("diff --git a/file úüþëéåëåé a b/file úüþëéåëåé b\n" + + Assertions.assertThat(buffer.toString("UTF-8")).isEqualTo("diff --git a/file úüþëéåëåé a b/file úüþëéåëåé b\n" + "new file mode 100644\n" + "index 0000000..8cb0607\n" + "--- /dev/null\n" +