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:
René Pfeuffer
2020-08-05 12:12:30 +02:00
parent 592282eb0f
commit 6bfefb3348
13 changed files with 488 additions and 225 deletions

View File

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

View File

@@ -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.
*

View File

@@ -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
}

View File

@@ -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()

View File

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

View File

@@ -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
*

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

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

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

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

View File

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