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);