mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-10-26 08:06:09 +01:00
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.
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 <code>branch</code> has been merged into <code>target</code>. So this will also return
|
||||
* <code>true</code>, when <code>branch</code> 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 <code>true</code> when <code>branch</code> has been merged into <code>target</code>, <code>false</code>
|
||||
* otherwise.
|
||||
*/
|
||||
boolean branchesMerged(String target, String branch);
|
||||
}
|
||||
@@ -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<Ref> repositoryHeadRef = GitUtil.getRepositoryHeadRef(gitRepository);
|
||||
return repositoryHeadRef.orElse(null);
|
||||
} else {
|
||||
return GitUtil.getBranchId(gitRepository, requestedBranch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<ReceiveCommand> receiveCommands;
|
||||
private final Repository repository;
|
||||
private final String repositoryId;
|
||||
|
||||
public GitPreReceiveHookMergeDetectionProvider(List<ReceiveCommand> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Ref> repositoryHeadRef = GitUtil.getRepositoryHeadRef(gitRepository);
|
||||
return repositoryHeadRef.orElse(null);
|
||||
}
|
||||
return getBranchIdOrCurrentHead(gitRepository, defaultBranchName);
|
||||
} else {
|
||||
return GitUtil.getBranchId(gitRepository, requestedBranch);
|
||||
return getBranchIdOrCurrentHead(gitRepository, requestedBranch);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<HookFeature> SUPPORTED_FEATURES =
|
||||
EnumSet.of(HookFeature.MESSAGE_PROVIDER, HookFeature.CHANGESET_PROVIDER,
|
||||
HookFeature.BRANCH_PROVIDER, HookFeature.TAG_PROVIDER);
|
||||
/**
|
||||
* Field description
|
||||
*/
|
||||
private static final Set<HookFeature> 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<ReceiveCommand> receiveCommands)
|
||||
{
|
||||
public GitHookContextProvider(
|
||||
ReceivePack receivePack,
|
||||
List<ReceiveCommand> 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<HookFeature> getSupportedFeatures()
|
||||
{
|
||||
return SUPPORTED_FEATURES;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private final GitHookChangesetProvider changesetProvider;
|
||||
|
||||
/** Field description */
|
||||
private final List<ReceiveCommand> receiveCommands;
|
||||
|
||||
/** Field description */
|
||||
private final ReceivePack receivePack;
|
||||
private final RepositoryHookType type;
|
||||
private final Repository repository;
|
||||
private final String repositoryId;
|
||||
}
|
||||
|
||||
@@ -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<Changeset> 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<RevCommit> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Changeset> 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<RevCommit> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user