mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-12-22 16:29:51 +01:00
Implement commit search features for git (#2111)
Implements the required features for the commit search plugin for git.
This commit is contained in:
2
gradle/changelog/commit_search_git.yaml
Normal file
2
gradle/changelog/commit_search_git.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- type: added
|
||||
description: Implement commit search features for git ([#2111](https://github.com/scm-manager/scm-manager/pull/2111))
|
||||
@@ -26,13 +26,10 @@ package sonia.scm.repository.api;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.io.DeepCopy;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.PreProcessorUtil;
|
||||
@@ -41,13 +38,8 @@ import sonia.scm.repository.spi.HookChangesetProvider;
|
||||
import sonia.scm.repository.spi.HookChangesetRequest;
|
||||
import sonia.scm.repository.spi.HookChangesetResponse;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* The {@link HookChangesetBuilder} is able to return all {@link Changeset}s
|
||||
@@ -113,49 +105,21 @@ public final class HookChangesetBuilder
|
||||
{
|
||||
HookChangesetResponse hookChangesetResponse = provider.handleRequest(request);
|
||||
Iterable<Changeset> changesets = hookChangesetResponse.getChangesets();
|
||||
|
||||
if (!disablePreProcessors)
|
||||
{
|
||||
changesets = Iterables.transform(changesets,
|
||||
new Function<Changeset, Changeset>()
|
||||
{
|
||||
|
||||
@Override
|
||||
public Changeset apply(Changeset c)
|
||||
{
|
||||
Changeset copy = null;
|
||||
|
||||
try
|
||||
{
|
||||
copy = DeepCopy.copy(c);
|
||||
preProcessorUtil.prepareForReturn(repository, copy);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
logger.error("could not create a copy of changeset", ex);
|
||||
}
|
||||
|
||||
if (copy == null)
|
||||
{
|
||||
copy = c;
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
return changesets;
|
||||
return applyPreprocessorsIfNotDisabled(changesets);
|
||||
}
|
||||
|
||||
public Iterable<Changeset> getRemovedChangesets() {
|
||||
HookChangesetResponse hookChangesetResponse = provider.handleRequest(request);
|
||||
Iterable<Changeset> changesets = hookChangesetResponse.getRemovedChangesets();
|
||||
return applyPreprocessorsIfNotDisabled(changesets);
|
||||
}
|
||||
|
||||
if (!disablePreProcessors)
|
||||
{
|
||||
changesets = StreamSupport.stream(changesets.spliterator(), false).map(c -> {
|
||||
private Iterable<Changeset> applyPreprocessorsIfNotDisabled(Iterable<Changeset> changesets) {
|
||||
if (disablePreProcessors) {
|
||||
return changesets;
|
||||
}
|
||||
|
||||
return Iterables.transform(changesets, c -> {
|
||||
Changeset copy = null;
|
||||
|
||||
try {
|
||||
@@ -166,14 +130,11 @@ public final class HookChangesetBuilder
|
||||
}
|
||||
|
||||
if (copy == null) {
|
||||
return c;
|
||||
copy = c;
|
||||
}
|
||||
|
||||
return copy;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return changesets;
|
||||
});
|
||||
}
|
||||
|
||||
//~--- set methods ----------------------------------------------------------
|
||||
|
||||
@@ -65,12 +65,8 @@ public class GitChangesetConverter implements Closeable {
|
||||
this.treeWalk = new TreeWalk(repository);
|
||||
}
|
||||
|
||||
public Changeset createChangeset(RevCommit commit) {
|
||||
return createChangeset(commit, Collections.emptyList());
|
||||
}
|
||||
|
||||
public Changeset createChangeset(RevCommit commit, String branch) {
|
||||
return createChangeset(commit, Lists.newArrayList(branch));
|
||||
public Changeset createChangeset(RevCommit commit, String... branches) {
|
||||
return createChangeset(commit, Arrays.asList(branches));
|
||||
}
|
||||
|
||||
public Changeset createChangeset(RevCommit commit, List<String> branches) {
|
||||
@@ -112,7 +108,7 @@ public class GitChangesetConverter implements Closeable {
|
||||
changeset.getTags().addAll(Lists.newArrayList(tagCollection));
|
||||
}
|
||||
|
||||
changeset.setBranches(branches);
|
||||
changeset.setBranches(new ArrayList<>(branches));
|
||||
|
||||
Signature signature = createSignature(commit);
|
||||
if (signature != null) {
|
||||
@@ -142,7 +138,7 @@ public class GitChangesetConverter implements Closeable {
|
||||
}
|
||||
|
||||
Optional<PublicKey> publicKeyById = gpg.findPublicKey(publicKeyId);
|
||||
if (!publicKeyById.isPresent()) {
|
||||
if (publicKeyById.isEmpty()) {
|
||||
// key not found
|
||||
return new Signature(publicKeyId, "gpg", SignatureStatus.NOT_FOUND, null, Collections.emptySet());
|
||||
}
|
||||
|
||||
@@ -1,217 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevSort;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
import org.eclipse.jgit.transport.ReceivePack;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.web.CollectingPackParserListener;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class GitHookChangesetCollector
|
||||
{
|
||||
|
||||
/**
|
||||
* the logger for GitHookChangesetCollector
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(GitHookChangesetCollector.class);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs a new instance
|
||||
*
|
||||
*
|
||||
* @param rpack
|
||||
* @param receiveCommands
|
||||
*/
|
||||
public GitHookChangesetCollector(GitChangesetConverterFactory converterFactory, ReceivePack rpack,
|
||||
List<ReceiveCommand> receiveCommands)
|
||||
{
|
||||
this.converterFactory = converterFactory;
|
||||
this.rpack = rpack;
|
||||
this.receiveCommands = receiveCommands;
|
||||
this.listener = CollectingPackParserListener.get(rpack);
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Collect all new changesets from the received hook.
|
||||
*
|
||||
* @return new changesets
|
||||
*/
|
||||
public List<Changeset> collectChangesets()
|
||||
{
|
||||
Map<String, Changeset> changesets = Maps.newLinkedHashMap();
|
||||
|
||||
try (
|
||||
org.eclipse.jgit.lib.Repository repository = rpack.getRepository();
|
||||
RevWalk walk = rpack.getRevWalk();
|
||||
GitChangesetConverter converter = converterFactory.create(repository, walk)
|
||||
) {
|
||||
repository.incrementOpen();
|
||||
|
||||
for (ReceiveCommand rc : receiveCommands)
|
||||
{
|
||||
String ref = rc.getRefName();
|
||||
|
||||
logger.trace("handle receive command, type={}, ref={}, result={}", rc.getType(), ref, rc.getResult());
|
||||
|
||||
if (rc.getType() == ReceiveCommand.Type.DELETE)
|
||||
{
|
||||
logger.debug("skip delete of ref {}", ref);
|
||||
}
|
||||
else if (! GitUtil.isBranch(ref))
|
||||
{
|
||||
logger.debug("skip ref {}, because it is not a branch", ref);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
collectChangesets(changesets, converter, walk, rc);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append("could not handle receive command, type=");
|
||||
builder.append(rc.getType()).append(", ref=");
|
||||
builder.append(rc.getRefName()).append(", result=");
|
||||
builder.append(rc.getResult());
|
||||
|
||||
logger.error(builder.toString(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.error("could not collect changesets", ex);
|
||||
}
|
||||
|
||||
return Lists.newArrayList(changesets.values());
|
||||
}
|
||||
|
||||
private void collectChangesets(Map<String, Changeset> changesets,
|
||||
GitChangesetConverter converter, RevWalk walk, ReceiveCommand rc)
|
||||
throws IOException
|
||||
{
|
||||
ObjectId newId = rc.getNewId();
|
||||
|
||||
String branch = GitUtil.getBranch(rc.getRefName());
|
||||
|
||||
walk.reset();
|
||||
walk.sort(RevSort.TOPO);
|
||||
walk.sort(RevSort.REVERSE, true);
|
||||
|
||||
logger.trace("mark {} as start for rev walk", newId.getName());
|
||||
|
||||
walk.markStart(walk.parseCommit(newId));
|
||||
|
||||
ObjectId oldId = rc.getOldId();
|
||||
|
||||
if ((oldId != null) && !oldId.equals(ObjectId.zeroId()))
|
||||
{
|
||||
logger.trace("mark {} as uninteresting for rev walk", oldId.getName());
|
||||
|
||||
walk.markUninteresting(walk.parseCommit(oldId));
|
||||
}
|
||||
|
||||
RevCommit commit = walk.next();
|
||||
|
||||
while (commit != null)
|
||||
{
|
||||
String id = commit.getId().name();
|
||||
Changeset changeset = changesets.get(id);
|
||||
|
||||
if (changeset != null)
|
||||
{
|
||||
logger.trace(
|
||||
"commit {} already received durring this push, add branch {} to the commit",
|
||||
commit, branch);
|
||||
changeset.getBranches().add(branch);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// only append new commits
|
||||
if (listener.isNew(commit))
|
||||
{
|
||||
|
||||
// parse commit body to avoid npe
|
||||
walk.parseBody(commit);
|
||||
|
||||
changeset = converter.createChangeset(commit, branch);
|
||||
|
||||
logger.trace("retrieve commit {} for hook", changeset.getId());
|
||||
|
||||
changesets.put(id, changeset);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.trace("commit {} was already received", commit.getId());
|
||||
}
|
||||
}
|
||||
|
||||
commit = walk.next();
|
||||
}
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** listener to track new objects */
|
||||
private final CollectingPackParserListener listener;
|
||||
|
||||
private final List<ReceiveCommand> receiveCommands;
|
||||
|
||||
private final GitChangesetConverterFactory converterFactory;
|
||||
private final ReceivePack rpack;
|
||||
}
|
||||
@@ -68,8 +68,10 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
@@ -233,6 +235,17 @@ public final class GitUtil {
|
||||
return Strings.nullToEmpty(refName).startsWith(PREFIX_HEADS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the provided reference name is a tag name.
|
||||
*
|
||||
* @param refName reference name
|
||||
* @return {@code true} if the name is a tag name
|
||||
* @since 2.39.0
|
||||
*/
|
||||
public static boolean isTag(String refName) {
|
||||
return Strings.nullToEmpty(refName).startsWith(PREFIX_TAG);
|
||||
}
|
||||
|
||||
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");
|
||||
@@ -700,4 +713,21 @@ public final class GitUtil {
|
||||
.setRefSpecs(new RefSpec(REF_SPEC))
|
||||
.setTagOpt(TagOpt.FETCH_TAGS);
|
||||
}
|
||||
|
||||
public static Stream<RevCommit> getAllCommits(org.eclipse.jgit.lib.Repository repository, RevWalk revWalk) throws IOException {
|
||||
return repository.getRefDatabase()
|
||||
.getRefs()
|
||||
.stream()
|
||||
.map(ref -> getCommitFromRef(ref, revWalk))
|
||||
.filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
public static RevCommit getCommitFromRef(Ref ref, RevWalk revWalk) {
|
||||
try {
|
||||
return getCommit(null, revWalk, ref);
|
||||
} catch (IOException e) {
|
||||
logger.info("could not get commit for {}", ref, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,14 @@ package sonia.scm.repository.spi;
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.errors.CannotDeleteCurrentBranchException;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.repository.Branch;
|
||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||
@@ -44,28 +49,41 @@ import sonia.scm.repository.api.HookFeature;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Collections.*;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singleton;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.eclipse.jgit.lib.ObjectId.zeroId;
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
|
||||
public class GitBranchCommand extends AbstractGitCommand implements BranchCommand {
|
||||
|
||||
private final HookContextFactory hookContextFactory;
|
||||
private final ScmEventBus eventBus;
|
||||
private final GitChangesetConverterFactory converterFactory;
|
||||
|
||||
@Inject
|
||||
GitBranchCommand(GitContext context, HookContextFactory hookContextFactory, ScmEventBus eventBus) {
|
||||
GitBranchCommand(GitContext context, HookContextFactory hookContextFactory, ScmEventBus eventBus, GitChangesetConverterFactory converterFactory) {
|
||||
super(context);
|
||||
this.hookContextFactory = hookContextFactory;
|
||||
this.eventBus = eventBus;
|
||||
this.converterFactory = converterFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Branch branch(BranchRequest request) {
|
||||
try (Git git = new Git(context.open())) {
|
||||
RepositoryHookEvent hookEvent = createBranchHookEvent(BranchHookContextProvider.createHookEvent(request.getNewBranch()));
|
||||
ObjectId newRef;
|
||||
if (request.getParentBranch() == null) {
|
||||
newRef = git.log().call().iterator().next();
|
||||
} else {
|
||||
newRef = getRef(git.getRepository(), request.getParentBranch());
|
||||
}
|
||||
RepositoryHookEvent hookEvent = createBranchHookEvent(createHookEvent(request.getNewBranch(), newRef));
|
||||
eventBus.post(new PreReceiveRepositoryHookEvent(hookEvent));
|
||||
Ref ref = git.branchCreate().setStartPoint(request.getParentBranch()).setName(request.getNewBranch()).call();
|
||||
eventBus.post(new PostReceiveRepositoryHookEvent(hookEvent));
|
||||
@@ -78,7 +96,8 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman
|
||||
@Override
|
||||
public void deleteOrClose(String branchName) {
|
||||
try (Git gitRepo = new Git(context.open())) {
|
||||
RepositoryHookEvent hookEvent = createBranchHookEvent(BranchHookContextProvider.deleteHookEvent(branchName));
|
||||
ObjectId oldRef = getRef(gitRepo.getRepository(), branchName);
|
||||
RepositoryHookEvent hookEvent = createBranchHookEvent(deleteHookEvent(branchName, oldRef));
|
||||
eventBus.post(new PreReceiveRepositoryHookEvent(hookEvent));
|
||||
gitRepo
|
||||
.branchDelete()
|
||||
@@ -98,21 +117,31 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman
|
||||
return new RepositoryHookEvent(context, this.context.getRepository(), RepositoryHookType.PRE_RECEIVE);
|
||||
}
|
||||
|
||||
private static class BranchHookContextProvider extends HookContextProvider {
|
||||
private ObjectId getRef(Repository gitRepo, String branch) {
|
||||
try {
|
||||
return gitRepo.getRefDatabase().findRef("refs/heads/" + branch).getObjectId();
|
||||
} catch (IOException e) {
|
||||
throw new InternalRepositoryException(repository, "error reading ref for branch", e);
|
||||
}
|
||||
}
|
||||
|
||||
private BranchHookContextProvider createHookEvent(String newBranch, ObjectId objectId) {
|
||||
return new BranchHookContextProvider(singletonList(newBranch), emptyList(), objectId);
|
||||
}
|
||||
|
||||
private BranchHookContextProvider deleteHookEvent(String deletedBranch, ObjectId oldObjectId) {
|
||||
return new BranchHookContextProvider(emptyList(), singletonList(deletedBranch), oldObjectId);
|
||||
}
|
||||
|
||||
private class BranchHookContextProvider extends HookContextProvider {
|
||||
private final List<String> newBranches;
|
||||
private final List<String> deletedBranches;
|
||||
private final ObjectId objectId;
|
||||
|
||||
private BranchHookContextProvider(List<String> newBranches, List<String> deletedBranches) {
|
||||
private BranchHookContextProvider(List<String> newBranches, List<String> deletedBranches, ObjectId objectId) {
|
||||
this.newBranches = newBranches;
|
||||
this.deletedBranches = deletedBranches;
|
||||
}
|
||||
|
||||
static BranchHookContextProvider createHookEvent(String newBranch) {
|
||||
return new BranchHookContextProvider(singletonList(newBranch), emptyList());
|
||||
}
|
||||
|
||||
static BranchHookContextProvider deleteHookEvent(String deletedBranch) {
|
||||
return new BranchHookContextProvider(emptyList(), singletonList(deletedBranch));
|
||||
this.objectId = objectId;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -137,7 +166,31 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman
|
||||
|
||||
@Override
|
||||
public HookChangesetProvider getChangesetProvider() {
|
||||
return r -> new HookChangesetResponse(emptyList());
|
||||
Repository gitRepo;
|
||||
try {
|
||||
gitRepo = context.open();
|
||||
} catch (IOException e) {
|
||||
throw new InternalRepositoryException(repository, "failed to open repository for post receive hook after internal change", e);
|
||||
}
|
||||
|
||||
Collection<ReceiveCommand> receiveCommands = new ArrayList<>();
|
||||
newBranches.stream()
|
||||
.map(branch -> new ReceiveCommand(zeroId(), objectId, "refs/heads/" + branch))
|
||||
.forEach(receiveCommands::add);
|
||||
deletedBranches.stream()
|
||||
.map(branch -> new ReceiveCommand(objectId, zeroId(), "refs/heads/" + branch))
|
||||
.forEach(receiveCommands::add);
|
||||
return x -> {
|
||||
GitHookChangesetCollector collector =
|
||||
GitHookChangesetCollector.collectChangesets(
|
||||
converterFactory,
|
||||
receiveCommands,
|
||||
gitRepo,
|
||||
new RevWalk(gitRepo),
|
||||
commit -> false // we cannot create new commits with this tag command
|
||||
);
|
||||
return new HookChangesetResponse(collector.getAddedChangesets(), collector.getRemovedChangesets());
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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 lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.GitChangesetConverter;
|
||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public class GitChangesetsCommand extends AbstractGitCommand implements ChangesetsCommand {
|
||||
|
||||
private final GitChangesetConverterFactory converterFactory;
|
||||
|
||||
@Inject
|
||||
GitChangesetsCommand(GitContext context, GitChangesetConverterFactory converterFactory) {
|
||||
super(context);
|
||||
this.converterFactory = converterFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Changeset> getChangesets(ChangesetsCommandRequest request) {
|
||||
try {
|
||||
log.debug("computing changesets for repository {}", repository);
|
||||
Repository gitRepository = open();
|
||||
|
||||
try (RevWalk revWalk = new RevWalk(gitRepository)) {
|
||||
revWalk.markStart(GitUtil.getAllCommits(gitRepository, revWalk).collect(Collectors.toList()));
|
||||
log.trace("got git iterator for all changesets for repository {}", repository);
|
||||
Iterator<RevCommit> iterator = revWalk.iterator();
|
||||
return () -> new ChangesetIterator(iterator, revWalk, gitRepository);
|
||||
} finally {
|
||||
log.trace("returned iterator for all changesets for repository {}", gitRepository);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new InternalRepositoryException(context.getRepository(), "failed to get latest commit", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Changeset> getLatestChangeset() {
|
||||
try {
|
||||
Repository repository = open();
|
||||
|
||||
try (RevWalk revWalk = new RevWalk(repository)) {
|
||||
return GitUtil.getAllCommits(repository, revWalk)
|
||||
.max(new ByCommitDateComparator())
|
||||
.map(commit -> converterFactory.create(repository, revWalk).createChangeset(commit));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new InternalRepositoryException(context.getRepository(), "failed to get latest commit", e);
|
||||
}
|
||||
}
|
||||
|
||||
private class ChangesetIterator implements Iterator<Changeset> {
|
||||
|
||||
private final Iterator<RevCommit> iterator;
|
||||
private final GitChangesetConverter changesetConverter;
|
||||
private final RevWalk revWalk;
|
||||
|
||||
ChangesetIterator(Iterator<RevCommit> iterator, RevWalk revWalk, Repository gitRepository) {
|
||||
this.iterator = iterator;
|
||||
this.changesetConverter = converterFactory.create(gitRepository, revWalk);
|
||||
this.revWalk = revWalk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Changeset next() {
|
||||
try {
|
||||
log.trace("mapping changeset for repository {}", repository);
|
||||
return changesetConverter.createChangeset(revWalk.parseCommit(iterator.next()));
|
||||
} catch (IOException e) {
|
||||
throw new InternalRepositoryException(context.getRepository(), "failed to create changeset for single git revision", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ByCommitDateComparator implements Comparator<RevCommit> {
|
||||
@Override
|
||||
public int compare(RevCommit rev1, RevCommit rev2) {
|
||||
long commitTime1 = rev1.getCommitTime();
|
||||
long commitTime2 = rev2.getCommitTime();
|
||||
return Long.compare(commitTime1, commitTime2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* 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.collect.Maps;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevSort;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
import org.eclipse.jgit.transport.ReceivePack;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.GitChangesetConverter;
|
||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.web.CollectingPackParserListener;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.unmodifiableCollection;
|
||||
|
||||
/**
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
class GitHookChangesetCollector {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GitHookChangesetCollector.class);
|
||||
|
||||
|
||||
private final Collection<ReceiveCommand> receiveCommands;
|
||||
|
||||
private final GitChangesetConverterFactory converterFactory;
|
||||
|
||||
/**
|
||||
* listener to track new objects
|
||||
*/
|
||||
private final NewCommitDetector newCommitDetector;
|
||||
|
||||
private final Repository repository;
|
||||
private final RevWalk walk;
|
||||
|
||||
private final Map<String, Changeset> addedChangesets = Maps.newLinkedHashMap();
|
||||
private final Map<String, Changeset> removedChangesets = Maps.newLinkedHashMap();
|
||||
|
||||
private GitHookChangesetCollector(GitChangesetConverterFactory converterFactory, Collection<ReceiveCommand> receiveCommands, NewCommitDetector newCommitDetector, Repository repository, RevWalk walk) {
|
||||
this.converterFactory = converterFactory;
|
||||
this.receiveCommands = receiveCommands;
|
||||
this.newCommitDetector = newCommitDetector;
|
||||
this.repository = repository;
|
||||
this.walk = walk;
|
||||
}
|
||||
|
||||
static GitHookChangesetCollector collectChangesets(GitChangesetConverterFactory converterFactory, Collection<ReceiveCommand> receiveCommands, ReceivePack rpack) {
|
||||
try (Repository repository = rpack.getRepository();
|
||||
RevWalk walk = rpack.getRevWalk()) {
|
||||
CollectingPackParserListener listener = CollectingPackParserListener.get(rpack);
|
||||
return collectChangesets(converterFactory, receiveCommands, repository, walk, listener::isNew);
|
||||
}
|
||||
}
|
||||
|
||||
static GitHookChangesetCollector collectChangesets(GitChangesetConverterFactory converterFactory, Collection<ReceiveCommand> receiveCommands, Repository repository, RevWalk walk, NewCommitDetector newCommitDetector) {
|
||||
GitHookChangesetCollector gitHookChangesetCollector = new GitHookChangesetCollector(converterFactory, receiveCommands, newCommitDetector, repository, walk);
|
||||
gitHookChangesetCollector.collectChangesets();
|
||||
return gitHookChangesetCollector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all new changesets from the received hook. Afterwards, the results can be
|
||||
* retrieved with {@link #getAddedChangesets()} and {@link #getRemovedChangesets()}
|
||||
*/
|
||||
private void collectChangesets() {
|
||||
try (GitChangesetConverter converter = converterFactory.create(repository, walk)) {
|
||||
repository.incrementOpen();
|
||||
|
||||
for (ReceiveCommand rc : receiveCommands) {
|
||||
String ref = rc.getRefName();
|
||||
|
||||
LOG.trace("handle receive command, type={}, ref={}, result={}", rc.getType(), ref, rc.getResult());
|
||||
|
||||
handle(repository, walk, converter, rc, ref);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOG.error("could not collect changesets", ex);
|
||||
}
|
||||
}
|
||||
|
||||
Iterable<Changeset> getAddedChangesets() {
|
||||
return unmodifiableCollection(addedChangesets.values());
|
||||
}
|
||||
|
||||
Iterable<Changeset> getRemovedChangesets() {
|
||||
return unmodifiableCollection(removedChangesets.values());
|
||||
}
|
||||
|
||||
void handle(Repository repository, RevWalk walk, GitChangesetConverter converter, ReceiveCommand rc, String ref) {
|
||||
try {
|
||||
if (!(GitUtil.isBranch(ref) || GitUtil.isTag(ref))) {
|
||||
LOG.debug("skip ref {}, because it is neither branch nor tag", ref);
|
||||
} else if (rc.getType() == ReceiveCommand.Type.UPDATE_NONFASTFORWARD) {
|
||||
LOG.debug("handle deleted/added ref {}", ref);
|
||||
collectRemovedChangeset(repository, walk, converter, rc);
|
||||
collectAddedChangesets(converter, walk, rc, ref);
|
||||
} else if (rc.getType() == ReceiveCommand.Type.DELETE) {
|
||||
LOG.debug("handle deleted ref {}", ref);
|
||||
collectRemovedChangeset(repository, walk, converter, rc);
|
||||
} else {
|
||||
LOG.debug("handle added ref {}", ref);
|
||||
collectAddedChangesets(converter, walk, rc, ref);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
String message = "could not handle receive command, type=" +
|
||||
rc.getType() + ", ref=" +
|
||||
rc.getRefName() + ", result=" +
|
||||
rc.getResult();
|
||||
|
||||
LOG.error(message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void collectAddedChangesets(GitChangesetConverter converter,
|
||||
RevWalk walk,
|
||||
ReceiveCommand rc,
|
||||
String ref)
|
||||
throws IOException {
|
||||
walk.reset();
|
||||
ObjectId newId = rc.getNewId();
|
||||
|
||||
String branch = GitUtil.getBranch(rc.getRefName());
|
||||
|
||||
walk.sort(RevSort.TOPO);
|
||||
walk.sort(RevSort.REVERSE, true);
|
||||
|
||||
LOG.trace("mark {} as start for rev walk", newId.getName());
|
||||
|
||||
walk.markStart(walk.parseCommit(newId));
|
||||
|
||||
ObjectId oldId = rc.getOldId();
|
||||
|
||||
if ((oldId != null) && !oldId.equals(ObjectId.zeroId())) {
|
||||
LOG.trace("mark {} as uninteresting for rev walk", oldId.getName());
|
||||
walk.markUninteresting(walk.parseCommit(oldId));
|
||||
}
|
||||
|
||||
RevCommit commit = walk.next();
|
||||
|
||||
while (commit != null) {
|
||||
String id = commit.getId().name();
|
||||
Changeset changeset = addedChangesets.get(id);
|
||||
|
||||
if (changeset != null) {
|
||||
if (GitUtil.isBranch(ref)) {
|
||||
LOG.trace(
|
||||
"commit {} already received during this push, add branch {} to the commit",
|
||||
commit, branch);
|
||||
changeset.getBranches().add(branch);
|
||||
}
|
||||
} else if (newCommitDetector.isNew(commit)) {
|
||||
// only append new commits
|
||||
addToCollection(addedChangesets, converter, walk, commit, id, branch);
|
||||
} else {
|
||||
LOG.trace("commit {} was already received", commit.getId());
|
||||
}
|
||||
|
||||
commit = walk.next();
|
||||
}
|
||||
}
|
||||
|
||||
private void collectRemovedChangeset(Repository repository, RevWalk walk, GitChangesetConverter converter, ReceiveCommand rc) throws IOException {
|
||||
walk.reset();
|
||||
ObjectId oldId = rc.getOldId();
|
||||
|
||||
walk.markStart(walk.parseCommit(oldId));
|
||||
GitUtil.getAllCommits(repository, walk).forEach(c -> {
|
||||
try {
|
||||
walk.markUninteresting(c);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("failed to mark commit as to be ignored", e);
|
||||
}
|
||||
});
|
||||
|
||||
RevCommit commit = walk.next();
|
||||
|
||||
while (commit != null) {
|
||||
String id = commit.getId().name();
|
||||
Changeset changeset = removedChangesets.get(id);
|
||||
|
||||
if (changeset == null) {
|
||||
addToCollection(removedChangesets, converter, walk, commit, id);
|
||||
}
|
||||
|
||||
commit = walk.next();
|
||||
}
|
||||
}
|
||||
|
||||
private void addToCollection(Map<String, Changeset> changesets, GitChangesetConverter converter, RevWalk walk, RevCommit commit, String id, String... branches) throws IOException {
|
||||
// parse commit body to avoid npe
|
||||
walk.parseBody(commit);
|
||||
|
||||
Changeset newChangeset = converter.createChangeset(commit, branches);
|
||||
|
||||
LOG.trace("retrieve commit {} for hook", newChangeset.getId());
|
||||
|
||||
changesets.put(id, newChangeset);
|
||||
}
|
||||
|
||||
interface NewCommitDetector {
|
||||
boolean isNew(RevCommit commit);
|
||||
}
|
||||
}
|
||||
@@ -24,15 +24,9 @@
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
import org.eclipse.jgit.transport.ReceivePack;
|
||||
|
||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||
import sonia.scm.repository.GitHookChangesetCollector;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -58,8 +52,8 @@ public class GitHookChangesetProvider implements HookChangesetProvider {
|
||||
@Override
|
||||
public synchronized HookChangesetResponse handleRequest(HookChangesetRequest request) {
|
||||
if (response == null) {
|
||||
GitHookChangesetCollector collector = new GitHookChangesetCollector(converterFactory, receivePack, receiveCommands);
|
||||
response = new HookChangesetResponse(collector.collectChangesets());
|
||||
GitHookChangesetCollector collector = GitHookChangesetCollector.collectChangesets(converterFactory, receiveCommands, receivePack);
|
||||
response = new HookChangesetResponse(collector.getAddedChangesets(), collector.getRemovedChangesets());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,8 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider {
|
||||
Command.UNBUNDLE,
|
||||
Command.MIRROR,
|
||||
Command.FILE_LOCK,
|
||||
Command.BRANCH_DETAILS
|
||||
Command.BRANCH_DETAILS,
|
||||
Command.CHANGESETS
|
||||
);
|
||||
|
||||
protected static final Set<Feature> FEATURES = EnumSet.of(
|
||||
@@ -192,6 +193,11 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider {
|
||||
return commandInjector.getInstance(GitBranchDetailsCommand.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangesetsCommand getChangesetsCommand() {
|
||||
return commandInjector.getInstance(GitChangesetsCommand.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Command> getSupportedCommands() {
|
||||
return COMMANDS;
|
||||
|
||||
@@ -35,7 +35,9 @@ import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevObject;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||
@@ -53,6 +55,8 @@ import sonia.scm.user.User;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@@ -60,18 +64,23 @@ import java.util.Set;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singleton;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.eclipse.jgit.lib.ObjectId.fromString;
|
||||
import static org.eclipse.jgit.lib.ObjectId.zeroId;
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
|
||||
public class GitTagCommand extends AbstractGitCommand implements TagCommand {
|
||||
public static final String REFS_TAGS_PREFIX = "refs/tags/";
|
||||
private final HookContextFactory hookContextFactory;
|
||||
private final ScmEventBus eventBus;
|
||||
private final GitChangesetConverterFactory converterFactory;
|
||||
|
||||
@Inject
|
||||
GitTagCommand(GitContext context, HookContextFactory hookContextFactory, ScmEventBus eventBus) {
|
||||
GitTagCommand(GitContext context, HookContextFactory hookContextFactory, ScmEventBus eventBus, GitChangesetConverterFactory converterFactory) {
|
||||
super(context);
|
||||
this.hookContextFactory = hookContextFactory;
|
||||
this.eventBus = eventBus;
|
||||
this.converterFactory = converterFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,7 +114,7 @@ public class GitTagCommand extends AbstractGitCommand implements TagCommand {
|
||||
|
||||
Tag tag = new Tag(name, revision, tagTime);
|
||||
|
||||
RepositoryHookEvent hookEvent = createTagHookEvent(TagHookContextProvider.createHookEvent(tag));
|
||||
RepositoryHookEvent hookEvent = createTagHookEvent(createHookEvent(tag), RepositoryHookType.PRE_RECEIVE);
|
||||
eventBus.post(new PreReceiveRepositoryHookEvent(hookEvent));
|
||||
|
||||
User user = SecurityUtils.getSubject().getPrincipals().oneByType(User.class);
|
||||
@@ -140,7 +149,7 @@ public class GitTagCommand extends AbstractGitCommand implements TagCommand {
|
||||
|
||||
// Deleting a non-existent tag is a valid action and simply succeeds without
|
||||
// anything happening.
|
||||
if (!tagRef.isPresent()) {
|
||||
if (tagRef.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -150,26 +159,37 @@ public class GitTagCommand extends AbstractGitCommand implements TagCommand {
|
||||
tag = new Tag(name, commit.name(), tagTime);
|
||||
}
|
||||
|
||||
RepositoryHookEvent hookEvent = createTagHookEvent(TagHookContextProvider.deleteHookEvent(tag));
|
||||
eventBus.post(new PreReceiveRepositoryHookEvent(hookEvent));
|
||||
eventBus.post(new PreReceiveRepositoryHookEvent(
|
||||
createTagHookEvent(deleteHookEvent(tag), RepositoryHookType.PRE_RECEIVE)
|
||||
));
|
||||
git.tagDelete().setTags(name).call();
|
||||
eventBus.post(new PostReceiveRepositoryHookEvent(hookEvent));
|
||||
eventBus.post(new PostReceiveRepositoryHookEvent(
|
||||
createTagHookEvent(deleteHookEvent(tag), RepositoryHookType.POST_RECEIVE)
|
||||
));
|
||||
} catch (GitAPIException | IOException e) {
|
||||
throw new InternalRepositoryException(repository, "could not delete tag " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Ref> findTagRef(Git git, String name) throws GitAPIException {
|
||||
final String tagRef = "refs/tags/" + name;
|
||||
final String tagRef = REFS_TAGS_PREFIX + name;
|
||||
return git.tagList().call().stream().filter(it -> it.getName().equals(tagRef)).findAny();
|
||||
}
|
||||
|
||||
private RepositoryHookEvent createTagHookEvent(TagHookContextProvider hookEvent) {
|
||||
private RepositoryHookEvent createTagHookEvent(TagHookContextProvider hookEvent, RepositoryHookType type) {
|
||||
HookContext context = hookContextFactory.createContext(hookEvent, this.context.getRepository());
|
||||
return new RepositoryHookEvent(context, this.context.getRepository(), RepositoryHookType.PRE_RECEIVE);
|
||||
return new RepositoryHookEvent(context, this.context.getRepository(), type);
|
||||
}
|
||||
|
||||
private static class TagHookContextProvider extends HookContextProvider {
|
||||
private TagHookContextProvider createHookEvent(Tag newTag) {
|
||||
return new TagHookContextProvider(singletonList(newTag), emptyList());
|
||||
}
|
||||
|
||||
private TagHookContextProvider deleteHookEvent(Tag deletedTag) {
|
||||
return new TagHookContextProvider(emptyList(), singletonList(deletedTag));
|
||||
}
|
||||
|
||||
private class TagHookContextProvider extends HookContextProvider {
|
||||
private final List<Tag> newTags;
|
||||
private final List<Tag> deletedTags;
|
||||
|
||||
@@ -178,14 +198,6 @@ public class GitTagCommand extends AbstractGitCommand implements TagCommand {
|
||||
this.deletedTags = deletedTags;
|
||||
}
|
||||
|
||||
static TagHookContextProvider createHookEvent(Tag newTag) {
|
||||
return new TagHookContextProvider(singletonList(newTag), emptyList());
|
||||
}
|
||||
|
||||
static TagHookContextProvider deleteHookEvent(Tag deletedTag) {
|
||||
return new TagHookContextProvider(emptyList(), singletonList(deletedTag));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<HookFeature> getSupportedFeatures() {
|
||||
return singleton(HookFeature.TAG_PROVIDER);
|
||||
@@ -208,7 +220,30 @@ public class GitTagCommand extends AbstractGitCommand implements TagCommand {
|
||||
|
||||
@Override
|
||||
public HookChangesetProvider getChangesetProvider() {
|
||||
return r -> new HookChangesetResponse(emptyList());
|
||||
Collection<ReceiveCommand> receiveCommands = new ArrayList<>();
|
||||
newTags.stream()
|
||||
.map(tag -> new ReceiveCommand(zeroId(), fromString(tag.getRevision()), REFS_TAGS_PREFIX + tag.getName()))
|
||||
.forEach(receiveCommands::add);
|
||||
deletedTags.stream()
|
||||
.map(tag -> new ReceiveCommand(fromString(tag.getRevision()), zeroId(), REFS_TAGS_PREFIX + tag.getName()))
|
||||
.forEach(receiveCommands::add);
|
||||
return x -> {
|
||||
Repository gitRepo;
|
||||
try {
|
||||
gitRepo = context.open();
|
||||
} catch (IOException e) {
|
||||
throw new InternalRepositoryException(repository, "failed to open repository for post receive hook after internal change", e);
|
||||
}
|
||||
GitHookChangesetCollector collector =
|
||||
GitHookChangesetCollector.collectChangesets(
|
||||
converterFactory,
|
||||
receiveCommands,
|
||||
gitRepo,
|
||||
new RevWalk(gitRepo),
|
||||
commit -> false // we cannot create new commits with this tag command
|
||||
);
|
||||
return new HookChangesetResponse(collector.getAddedChangesets(), collector.getRemovedChangesets());
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -32,9 +34,14 @@ import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.repository.Branch;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.GitChangesetConverter;
|
||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||
import sonia.scm.repository.PreProcessorUtil;
|
||||
import sonia.scm.repository.PreReceiveRepositoryHookEvent;
|
||||
import sonia.scm.repository.api.BranchRequest;
|
||||
import sonia.scm.repository.api.HookChangesetBuilder;
|
||||
import sonia.scm.repository.api.HookContext;
|
||||
import sonia.scm.repository.api.HookContextFactory;
|
||||
|
||||
@@ -48,14 +55,30 @@ import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class GitBranchCommandTest extends AbstractGitCommandTestBase {
|
||||
|
||||
@Mock
|
||||
private PreProcessorUtil preProcessorUtil;
|
||||
private HookContextFactory hookContextFactory;
|
||||
@Mock
|
||||
private ScmEventBus eventBus;
|
||||
@Mock
|
||||
private GitChangesetConverterFactory converterFactory;
|
||||
|
||||
@Before
|
||||
public void mockConverterFactory() {
|
||||
GitChangesetConverter gitChangesetConverter = mock(GitChangesetConverter.class);
|
||||
when(converterFactory.create(any(), any()))
|
||||
.thenReturn(gitChangesetConverter);
|
||||
when(gitChangesetConverter.createChangeset(any(), (String[]) any()))
|
||||
.thenAnswer(invocation -> {
|
||||
RevCommit revCommit = invocation.getArgument(0, RevCommit.class);
|
||||
Changeset changeset = new Changeset(revCommit.name(), null, null);
|
||||
return changeset;
|
||||
});
|
||||
hookContextFactory = new HookContextFactory(preProcessorUtil);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateBranchWithDefinedSourceBranch() throws IOException {
|
||||
@@ -108,7 +131,7 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase {
|
||||
}
|
||||
|
||||
private GitBranchCommand createCommand() {
|
||||
return new GitBranchCommand(createContext(), hookContextFactory, eventBus);
|
||||
return new GitBranchCommand(createContext(), hookContextFactory, eventBus, converterFactory);
|
||||
}
|
||||
|
||||
private List<Branch> readBranches(GitContext context) throws IOException {
|
||||
@@ -119,7 +142,6 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase {
|
||||
public void shouldPostCreateEvents() {
|
||||
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
|
||||
doNothing().when(eventBus).post(captor.capture());
|
||||
when(hookContextFactory.createContext(any(), any())).thenAnswer(this::createMockedContext);
|
||||
|
||||
BranchRequest branchRequest = new BranchRequest();
|
||||
branchRequest.setParentBranch("mergeable");
|
||||
@@ -140,7 +162,6 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase {
|
||||
public void shouldPostDeleteEvents() {
|
||||
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
|
||||
doNothing().when(eventBus).post(captor.capture());
|
||||
when(hookContextFactory.createContext(any(), any())).thenAnswer(this::createMockedContext);
|
||||
|
||||
createCommand().deleteOrClose("squash");
|
||||
|
||||
@@ -151,11 +172,15 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase {
|
||||
PreReceiveRepositoryHookEvent event = (PreReceiveRepositoryHookEvent) events.get(0);
|
||||
assertThat(event.getContext().getBranchProvider().getDeletedOrClosed()).containsExactly("squash");
|
||||
assertThat(event.getContext().getBranchProvider().getCreatedOrModified()).isEmpty();
|
||||
}
|
||||
|
||||
private HookContext createMockedContext(InvocationOnMock invocation) {
|
||||
HookContext mock = mock(HookContext.class);
|
||||
when(mock.getBranchProvider()).thenReturn(((HookContextProvider) invocation.getArgument(0)).getBranchProvider());
|
||||
return mock;
|
||||
HookChangesetBuilder changesetProvider = event.getContext().getChangesetProvider();
|
||||
assertThat(changesetProvider.getChangesets()).isEmpty();
|
||||
assertThat(changesetProvider.getRemovedChangesets())
|
||||
.extracting("id")
|
||||
.containsExactly(
|
||||
"35597e9e98fe53167266583848bfef985c2adb27",
|
||||
"f360a8738e4a29333786c5817f97a2c912814536",
|
||||
"d1dfecbfd5b4a2f77fe40e1bde29e640f7f944be"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 org.junit.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.GitTestHelper;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class GitChangesetsCommandTest extends AbstractGitCommandTestBase {
|
||||
|
||||
@Test
|
||||
public void shouldFindLatestCommit() {
|
||||
GitChangesetsCommand command = new GitChangesetsCommand(createContext(), GitTestHelper.createConverterFactory());
|
||||
Optional<Changeset> changeset = command.getLatestChangeset();
|
||||
assertThat(changeset).get().extracting("id").isEqualTo("a8495c0335a13e6e432df90b3727fa91943189a7");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldListAllRevisions() {
|
||||
GitChangesetsCommand command = new GitChangesetsCommand(createContext(), GitTestHelper.createConverterFactory());
|
||||
Iterable<Changeset> changesets = command.getChangesets(new ChangesetsCommandRequest());
|
||||
assertThat(changesets)
|
||||
.extracting("id")
|
||||
.hasSize(19)
|
||||
.contains(
|
||||
"a8495c0335a13e6e432df90b3727fa91943189a7",
|
||||
"03ca33468c2094249973d0ca11b80243a20de368",
|
||||
"9e93d8631675a89615fac56b09209686146ff3c0",
|
||||
"383b954b27e052db6880d57f1c860dc208795247",
|
||||
"1fcebf45a215a43f0713a57b807d55e8387a6d70",
|
||||
"9f28cf5eb3a4df05d284c6f2d276c20c0f0e5b6c",
|
||||
"674ca9a2208df60224b0f33beeea5259b374d2d0",
|
||||
"35597e9e98fe53167266583848bfef985c2adb27",
|
||||
"f360a8738e4a29333786c5817f97a2c912814536",
|
||||
"d1dfecbfd5b4a2f77fe40e1bde29e640f7f944be",
|
||||
"d81ad6c63d7e2162308d69637b339dedd1d9201c",
|
||||
"6a2abf9dca5cff5d76720d1276e1112d9c75ea60",
|
||||
"2f95f02d9c568594d31e78464bd11a96c62e3f91",
|
||||
"fcd0ef1831e4002ac43ea539f4094334c79ea9ec",
|
||||
"86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1",
|
||||
"91b99de908fcd04772798a31c308a64aea1a5523",
|
||||
"3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4",
|
||||
"592d797cd36432e591416e8b2b98154f4f163411",
|
||||
"435df2f061add3589cb326cc64be9b9c3897ceca"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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 org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
import org.eclipse.jgit.transport.ReceivePack;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.GitChangesetConverter;
|
||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||
import sonia.scm.web.CollectingPackParserListener;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.util.Arrays.asList;
|
||||
import static org.eclipse.jgit.lib.ObjectId.fromString;
|
||||
import static org.eclipse.jgit.lib.ObjectId.zeroId;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class GitHookChangesetCollectorTest extends AbstractGitCommandTestBase {
|
||||
|
||||
private final ReceivePack rpack = mock(ReceivePack.class);
|
||||
private final Collection<ReceiveCommand> receiveCommands = new ArrayList<>();
|
||||
private final CollectingPackParserListener listener = mock(CollectingPackParserListener.class);
|
||||
|
||||
private final GitChangesetConverterFactory converterFactory = mock(GitChangesetConverterFactory.class);
|
||||
private final GitChangesetConverter converter = mock(GitChangesetConverter.class);
|
||||
|
||||
private GitHookChangesetCollector collector;
|
||||
|
||||
@Before
|
||||
public void init() throws IOException {
|
||||
|
||||
GitContext context = createContext();
|
||||
Repository repository = context.open();
|
||||
RevWalk revWalk = new RevWalk(repository);
|
||||
when(rpack.getRepository()).thenReturn(repository);
|
||||
when(rpack.getRevWalk()).thenReturn(revWalk);
|
||||
when(rpack.getPackParserListener()).thenReturn(listener);
|
||||
when(converterFactory.create(repository, revWalk)).thenReturn(converter);
|
||||
when(converter.createChangeset(any(), (String[]) any()))
|
||||
.thenAnswer(invocation -> new Changeset(invocation.getArgument(0, RevCommit.class).name(), null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateEmptyCollectionsWithoutChanges() {
|
||||
collector = GitHookChangesetCollector.collectChangesets(converterFactory, receiveCommands, rpack);
|
||||
|
||||
assertThat(collector.getAddedChangesets()).isEmpty();
|
||||
assertThat(collector.getRemovedChangesets()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindAddedChangesetsFromNewBranch() {
|
||||
receiveCommands.add(
|
||||
new ReceiveCommand(
|
||||
zeroId(),
|
||||
fromString("91b99de908fcd04772798a31c308a64aea1a5523"),
|
||||
"refs/heads/mergeable")
|
||||
);
|
||||
mockNewCommits(
|
||||
"91b99de908fcd04772798a31c308a64aea1a5523",
|
||||
"3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4",
|
||||
"592d797cd36432e591416e8b2b98154f4f163411");
|
||||
|
||||
collector = GitHookChangesetCollector.collectChangesets(converterFactory, receiveCommands, rpack);
|
||||
|
||||
assertThat(collector.getAddedChangesets())
|
||||
.extracting("id")
|
||||
.contains(
|
||||
"91b99de908fcd04772798a31c308a64aea1a5523",
|
||||
"592d797cd36432e591416e8b2b98154f4f163411");
|
||||
assertThat(collector.getRemovedChangesets()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindAddedChangesetsFromNewBranchesOnce() throws IOException, GitAPIException {
|
||||
new Git(createContext().open()).branchCreate().setStartPoint("mergeable").setName("second").call();
|
||||
receiveCommands.add(
|
||||
new ReceiveCommand(
|
||||
zeroId(),
|
||||
fromString("91b99de908fcd04772798a31c308a64aea1a5523"),
|
||||
"refs/heads/mergeable")
|
||||
);
|
||||
receiveCommands.add(
|
||||
new ReceiveCommand(
|
||||
zeroId(),
|
||||
fromString("91b99de908fcd04772798a31c308a64aea1a5523"),
|
||||
"refs/heads/second")
|
||||
);
|
||||
mockNewCommits(
|
||||
"91b99de908fcd04772798a31c308a64aea1a5523",
|
||||
"3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4",
|
||||
"592d797cd36432e591416e8b2b98154f4f163411");
|
||||
|
||||
collector = GitHookChangesetCollector.collectChangesets(converterFactory, receiveCommands, rpack);
|
||||
|
||||
assertThat(collector.getAddedChangesets())
|
||||
.extracting("id")
|
||||
.hasSize(2)
|
||||
.contains(
|
||||
"91b99de908fcd04772798a31c308a64aea1a5523",
|
||||
"592d797cd36432e591416e8b2b98154f4f163411");
|
||||
assertThat(collector.getRemovedChangesets()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindAddedChangesetsFromChangedBranchWithoutIteratingOldCommits() {
|
||||
receiveCommands.add(
|
||||
new ReceiveCommand(
|
||||
fromString("592d797cd36432e591416e8b2b98154f4f163411"),
|
||||
fromString("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4"),
|
||||
"refs/heads/test-branch")
|
||||
);
|
||||
mockNewCommits("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
|
||||
|
||||
collector = GitHookChangesetCollector.collectChangesets(converterFactory, receiveCommands, rpack);
|
||||
|
||||
assertThat(collector.getAddedChangesets())
|
||||
.extracting("id")
|
||||
.contains("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
|
||||
assertThat(collector.getRemovedChangesets()).isEmpty();
|
||||
|
||||
verify(listener, never()).isNew(argThat(argument -> argument.name().equals("592d797cd36432e591416e8b2b98154f4f163411")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindRemovedChangesetsFromDeletedBranch() throws IOException, GitAPIException {
|
||||
new Git(createContext().open()).branchDelete().setBranchNames("test-branch").setForce(true).call();
|
||||
receiveCommands.add(
|
||||
new ReceiveCommand(
|
||||
fromString("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4"),
|
||||
zeroId(),
|
||||
"refs/heads/test-branch",
|
||||
ReceiveCommand.Type.DELETE)
|
||||
);
|
||||
mockNewCommits("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
|
||||
|
||||
collector = GitHookChangesetCollector.collectChangesets(converterFactory, receiveCommands, rpack);
|
||||
|
||||
assertThat(collector.getAddedChangesets()).isEmpty();
|
||||
assertThat(collector.getRemovedChangesets())
|
||||
.extracting("id")
|
||||
.contains("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
|
||||
|
||||
verify(listener, never()).isNew(argThat(argument -> argument.name().equals("592d797cd36432e591416e8b2b98154f4f163411")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindRemovedAndAddedChangesetsFromNonFastForwardChanged() throws IOException, GitAPIException {
|
||||
new Git(createContext().open()).branchDelete().setBranchNames("test-branch").setForce(true).call();
|
||||
receiveCommands.add(
|
||||
new ReceiveCommand(
|
||||
fromString("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4"),
|
||||
fromString("91b99de908fcd04772798a31c308a64aea1a5523"),
|
||||
"refs/heads/test-branch",
|
||||
ReceiveCommand.Type.UPDATE_NONFASTFORWARD)
|
||||
);
|
||||
mockNewCommits("91b99de908fcd04772798a31c308a64aea1a5523");
|
||||
|
||||
collector = GitHookChangesetCollector.collectChangesets(converterFactory, receiveCommands, rpack);
|
||||
|
||||
assertThat(collector.getAddedChangesets())
|
||||
.extracting("id")
|
||||
.contains("91b99de908fcd04772798a31c308a64aea1a5523");
|
||||
assertThat(collector.getRemovedChangesets())
|
||||
.extracting("id")
|
||||
.contains("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
|
||||
}
|
||||
|
||||
private void mockNewCommits(String... objectIds) {
|
||||
when(listener.isNew(any()))
|
||||
.thenAnswer(invocation -> asList(objectIds).contains(invocation.getArgument(0, RevCommit.class).name()));
|
||||
}
|
||||
}
|
||||
@@ -28,24 +28,33 @@ import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.mgt.DefaultSecurityManager;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.util.ThreadContext;
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.lib.GpgSigner;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.GitChangesetConverter;
|
||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||
import sonia.scm.repository.GitTestHelper;
|
||||
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||
import sonia.scm.repository.PreProcessorUtil;
|
||||
import sonia.scm.repository.PreReceiveRepositoryHookEvent;
|
||||
import sonia.scm.repository.Tag;
|
||||
import sonia.scm.repository.api.HookContext;
|
||||
import sonia.scm.repository.api.HookChangesetBuilder;
|
||||
import sonia.scm.repository.api.HookContextFactory;
|
||||
import sonia.scm.repository.api.TagDeleteRequest;
|
||||
import sonia.scm.repository.api.HookTagProvider;
|
||||
import sonia.scm.repository.api.TagCreateRequest;
|
||||
import sonia.scm.repository.api.TagDeleteRequest;
|
||||
import sonia.scm.security.GPG;
|
||||
import sonia.scm.util.MockUtil;
|
||||
|
||||
@@ -66,12 +75,15 @@ public class GitTagCommandTest extends AbstractGitCommandTestBase {
|
||||
private GPG gpg;
|
||||
|
||||
@Mock
|
||||
private PreProcessorUtil preProcessorUtil;
|
||||
private HookContextFactory hookContextFactory;
|
||||
|
||||
@Mock
|
||||
private ScmEventBus eventBus;
|
||||
|
||||
private Subject subject;
|
||||
@Mock
|
||||
private GitChangesetConverterFactory converterFactory;
|
||||
|
||||
|
||||
@Before
|
||||
public void setSigner() {
|
||||
@@ -81,10 +93,24 @@ public class GitTagCommandTest extends AbstractGitCommandTestBase {
|
||||
@Before
|
||||
public void bindThreadContext() {
|
||||
SecurityUtils.setSecurityManager(new DefaultSecurityManager());
|
||||
subject = MockUtil.createUserSubject(SecurityUtils.getSecurityManager());
|
||||
Subject subject = MockUtil.createUserSubject(SecurityUtils.getSecurityManager());
|
||||
ThreadContext.bind(subject);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void mockConverterFactory() {
|
||||
GitChangesetConverter gitChangesetConverter = mock(GitChangesetConverter.class);
|
||||
when(converterFactory.create(any(), any()))
|
||||
.thenReturn(gitChangesetConverter);
|
||||
when(gitChangesetConverter.createChangeset(any(), (String[]) any()))
|
||||
.thenAnswer(invocation -> {
|
||||
RevCommit revCommit = invocation.getArgument(0, RevCommit.class);
|
||||
Changeset changeset = new Changeset(revCommit.name(), null, null);
|
||||
return changeset;
|
||||
});
|
||||
hookContextFactory = new HookContextFactory(preProcessorUtil);
|
||||
}
|
||||
|
||||
@After
|
||||
public void unbindThreadContext() {
|
||||
ThreadContext.unbindSubject();
|
||||
@@ -105,7 +131,6 @@ public class GitTagCommandTest extends AbstractGitCommandTestBase {
|
||||
public void shouldPostCreateEvent() {
|
||||
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
|
||||
doNothing().when(eventBus).post(captor.capture());
|
||||
when(hookContextFactory.createContext(any(), any())).thenAnswer(this::createMockedContext);
|
||||
|
||||
createCommand().create(new TagCreateRequest("592d797cd36432e591416e8b2b98154f4f163411", "newtag"));
|
||||
|
||||
@@ -131,26 +156,36 @@ public class GitTagCommandTest extends AbstractGitCommandTestBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldPostDeleteEvent() {
|
||||
public void shouldPostDeleteEvent() throws IOException, GitAPIException {
|
||||
Git git = new Git(createContext().open());
|
||||
git.tag().setName("to-be-deleted").setObjectId(getCommit("383b954b27e052db6880d57f1c860dc208795247")).call();
|
||||
git.branchDelete().setBranchNames("rename").setForce(true).call();
|
||||
|
||||
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
|
||||
doNothing().when(eventBus).post(captor.capture());
|
||||
when(hookContextFactory.createContext(any(), any())).thenAnswer(this::createMockedContext);
|
||||
|
||||
createCommand().delete(new TagDeleteRequest("test-tag"));
|
||||
createCommand().delete(new TagDeleteRequest("to-be-deleted"));
|
||||
|
||||
List<Object> events = captor.getAllValues();
|
||||
assertThat(events.get(0)).isInstanceOf(PreReceiveRepositoryHookEvent.class);
|
||||
assertThat(events.get(1)).isInstanceOf(PostReceiveRepositoryHookEvent.class);
|
||||
|
||||
PreReceiveRepositoryHookEvent event = (PreReceiveRepositoryHookEvent) events.get(0);
|
||||
assertThat(event.getContext().getTagProvider().getCreatedTags()).isEmpty();
|
||||
final Tag deletedTag = event.getContext().getTagProvider().getDeletedTags().get(0);
|
||||
assertThat(deletedTag.getName()).isEqualTo("test-tag");
|
||||
assertThat(deletedTag.getRevision()).isEqualTo("86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1");
|
||||
HookTagProvider tagProvider = event.getContext().getTagProvider();
|
||||
assertThat(tagProvider.getCreatedTags()).isEmpty();
|
||||
Tag deletedTag = tagProvider.getDeletedTags().get(0);
|
||||
assertThat(deletedTag.getName()).isEqualTo("to-be-deleted");
|
||||
assertThat(deletedTag.getRevision()).isEqualTo("383b954b27e052db6880d57f1c860dc208795247");
|
||||
|
||||
HookChangesetBuilder changesetProvider = event.getContext().getChangesetProvider();
|
||||
assertThat(changesetProvider.getChangesets()).isEmpty();
|
||||
assertThat(changesetProvider.getRemovedChangesets())
|
||||
.extracting("id")
|
||||
.containsExactly("383b954b27e052db6880d57f1c860dc208795247");
|
||||
}
|
||||
|
||||
private GitTagCommand createCommand() {
|
||||
return new GitTagCommand(createContext(), hookContextFactory, eventBus);
|
||||
return new GitTagCommand(createContext(), hookContextFactory, eventBus, converterFactory);
|
||||
}
|
||||
|
||||
private List<Tag> readTags(GitContext context) throws IOException {
|
||||
@@ -162,9 +197,10 @@ public class GitTagCommandTest extends AbstractGitCommandTestBase {
|
||||
return tags.stream().filter(t -> name.equals(t.getName())).findFirst();
|
||||
}
|
||||
|
||||
private HookContext createMockedContext(InvocationOnMock invocation) {
|
||||
HookContext mock = mock(HookContext.class);
|
||||
when(mock.getTagProvider()).thenReturn(((HookContextProvider) invocation.getArgument(0)).getTagProvider());
|
||||
return mock;
|
||||
private RevCommit getCommit(String revision) throws IOException {
|
||||
ObjectId commitId = ObjectId.fromString(revision);
|
||||
try (RevWalk revWalk = new RevWalk(createContext().open())) {
|
||||
return revWalk.parseCommit(commitId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user