Merge branch 'develop' into feature/create_gpg_signatures

# Conflicts:
#	CHANGELOG.md
#	scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java
#	scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java
#	scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java
This commit is contained in:
Konstantin Schaper
2020-08-11 13:33:21 +02:00
40 changed files with 1590 additions and 243 deletions

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,81 @@
/*
* 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.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.ReceiveCommand;
import sonia.scm.repository.GitUtil;
import sonia.scm.repository.spi.GitLogComputer;
import sonia.scm.repository.spi.HookMergeDetectionProvider;
import sonia.scm.repository.spi.LogCommandRequest;
import java.util.List;
public class GitReceiveHookMergeDetectionProvider implements HookMergeDetectionProvider {
private final Repository repository;
private final String repositoryId;
private final List<ReceiveCommand> receiveCommands;
public GitReceiveHookMergeDetectionProvider(Repository repository, String repositoryId, List<ReceiveCommand> receiveCommands) {
this.repository = repository;
this.repositoryId = repositoryId;
this.receiveCommands = receiveCommands;
}
@Override
public boolean branchesMerged(String target, String branch) {
LogCommandRequest request = new LogCommandRequest();
request.setBranch(findRelevantRevisionForBranchIfToBeUpdated(branch));
request.setAncestorChangeset(findRelevantRevisionForBranchIfToBeUpdated(target));
request.setPagingLimit(1);
return new GitLogComputer(repositoryId, repository).compute(request).getTotal() == 0;
}
private String findRelevantRevisionForBranchIfToBeUpdated(String branch) {
return receiveCommands
.stream()
.filter(receiveCommand -> isReceiveCommandForBranch(branch, receiveCommand))
.map(this::getRelevantRevision)
.map(AnyObjectId::getName)
.findFirst()
.orElse(branch);
}
private boolean isReceiveCommandForBranch(String branch, ReceiveCommand receiveCommand) {
return receiveCommand.getType() != ReceiveCommand.Type.CREATE
&& GitUtil.getBranch(receiveCommand.getRef()).equals(branch);
}
private ObjectId getRelevantRevision(ReceiveCommand receiveCommand) {
if (receiveCommand.getType() == ReceiveCommand.Type.DELETE) {
return receiveCommand.getOldId();
} else {
return receiveCommand.getNewId();
}
}
}

View File

@@ -41,7 +41,6 @@ import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.GitUtil;
import sonia.scm.repository.GitWorkingCopyFactory;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.Person;
@@ -59,6 +58,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 ------------------------------------------------------------
@@ -116,15 +116,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

@@ -26,23 +26,22 @@ 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.GitChangesetConverterFactory;
import sonia.scm.repository.api.GitHookBranchProvider;
import sonia.scm.repository.GitChangesetConverterFactory;
import sonia.scm.repository.api.GitHookMessageProvider;
import sonia.scm.repository.api.GitHookTagProvider;
import sonia.scm.repository.api.GitReceiveHookMergeDetectionProvider;
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;
/**
*
@@ -51,24 +50,34 @@ 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 receiveCommands received commands
*/
public GitHookContextProvider(GitChangesetConverterFactory converterFactory, ReceivePack receivePack,
List<ReceiveCommand> receiveCommands)
{
public GitHookContextProvider(
GitChangesetConverterFactory converterFactory, ReceivePack receivePack,
List<ReceiveCommand> receiveCommands,
Repository repository,
String repositoryId
) {
this.receivePack = receivePack;
this.receiveCommands = receiveCommands;
this.repository = repository;
this.repositoryId = repositoryId;
this.changesetProvider = new GitHookChangesetProvider(converterFactory, receivePack,
receiveCommands);
}
@@ -100,20 +109,20 @@ public class GitHookContextProvider extends HookContextProvider
return changesetProvider;
}
@Override
public HookMergeDetectionProvider getMergeDetectionProvider() {
return new GitReceiveHookMergeDetectionProvider(repository, repositoryId, receiveCommands);
}
@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 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;
@@ -50,9 +43,6 @@ import sonia.scm.util.IOUtil;
import javax.inject.Inject;
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;
@@ -188,123 +178,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 = converterFactory.create(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,186 @@
/*
* 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.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.emptyList());
}
} 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

@@ -124,8 +124,7 @@ public class GitReceiveHook implements PreReceiveHook, PostReceiveHook
logger.trace("resolved repository to {}", repositoryId);
GitHookContextProvider context = new GitHookContextProvider(converterFactory, rpack,
receiveCommands);
GitHookContextProvider context = new GitHookContextProvider(converterFactory, rpack, receiveCommands, repository, repositoryId);
hookEventFacade.handle(repositoryId).fireHookEvent(type, context);