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 --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import sonia.scm.io.DeepCopy;
|
import sonia.scm.io.DeepCopy;
|
||||||
import sonia.scm.repository.Changeset;
|
import sonia.scm.repository.Changeset;
|
||||||
import sonia.scm.repository.PreProcessorUtil;
|
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.HookChangesetRequest;
|
||||||
import sonia.scm.repository.spi.HookChangesetResponse;
|
import sonia.scm.repository.spi.HookChangesetResponse;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import java.util.List;
|
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
|
* The {@link HookChangesetBuilder} is able to return all {@link Changeset}s
|
||||||
@@ -113,49 +105,21 @@ public final class HookChangesetBuilder
|
|||||||
{
|
{
|
||||||
HookChangesetResponse hookChangesetResponse = provider.handleRequest(request);
|
HookChangesetResponse hookChangesetResponse = provider.handleRequest(request);
|
||||||
Iterable<Changeset> changesets = hookChangesetResponse.getChangesets();
|
Iterable<Changeset> changesets = hookChangesetResponse.getChangesets();
|
||||||
|
return applyPreprocessorsIfNotDisabled(changesets);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Iterable<Changeset> getRemovedChangesets() {
|
public Iterable<Changeset> getRemovedChangesets() {
|
||||||
HookChangesetResponse hookChangesetResponse = provider.handleRequest(request);
|
HookChangesetResponse hookChangesetResponse = provider.handleRequest(request);
|
||||||
Iterable<Changeset> changesets = hookChangesetResponse.getRemovedChangesets();
|
Iterable<Changeset> changesets = hookChangesetResponse.getRemovedChangesets();
|
||||||
|
return applyPreprocessorsIfNotDisabled(changesets);
|
||||||
|
}
|
||||||
|
|
||||||
if (!disablePreProcessors)
|
private Iterable<Changeset> applyPreprocessorsIfNotDisabled(Iterable<Changeset> changesets) {
|
||||||
{
|
if (disablePreProcessors) {
|
||||||
changesets = StreamSupport.stream(changesets.spliterator(), false).map(c -> {
|
return changesets;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Iterables.transform(changesets, c -> {
|
||||||
Changeset copy = null;
|
Changeset copy = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -166,14 +130,11 @@ public final class HookChangesetBuilder
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (copy == null) {
|
if (copy == null) {
|
||||||
return c;
|
copy = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
return copy;
|
return copy;
|
||||||
}).collect(Collectors.toList());
|
});
|
||||||
}
|
|
||||||
|
|
||||||
return changesets;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
//~--- set methods ----------------------------------------------------------
|
||||||
|
|||||||
@@ -65,12 +65,8 @@ public class GitChangesetConverter implements Closeable {
|
|||||||
this.treeWalk = new TreeWalk(repository);
|
this.treeWalk = new TreeWalk(repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Changeset createChangeset(RevCommit commit) {
|
public Changeset createChangeset(RevCommit commit, String... branches) {
|
||||||
return createChangeset(commit, Collections.emptyList());
|
return createChangeset(commit, Arrays.asList(branches));
|
||||||
}
|
|
||||||
|
|
||||||
public Changeset createChangeset(RevCommit commit, String branch) {
|
|
||||||
return createChangeset(commit, Lists.newArrayList(branch));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Changeset createChangeset(RevCommit commit, List<String> 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.getTags().addAll(Lists.newArrayList(tagCollection));
|
||||||
}
|
}
|
||||||
|
|
||||||
changeset.setBranches(branches);
|
changeset.setBranches(new ArrayList<>(branches));
|
||||||
|
|
||||||
Signature signature = createSignature(commit);
|
Signature signature = createSignature(commit);
|
||||||
if (signature != null) {
|
if (signature != null) {
|
||||||
@@ -142,7 +138,7 @@ public class GitChangesetConverter implements Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Optional<PublicKey> publicKeyById = gpg.findPublicKey(publicKeyId);
|
Optional<PublicKey> publicKeyById = gpg.findPublicKey(publicKeyId);
|
||||||
if (!publicKeyById.isPresent()) {
|
if (publicKeyById.isEmpty()) {
|
||||||
// key not found
|
// key not found
|
||||||
return new Signature(publicKeyId, "gpg", SignatureStatus.NOT_FOUND, null, Collections.emptySet());
|
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.io.InputStream;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static java.util.Optional.empty;
|
import static java.util.Optional.empty;
|
||||||
import static java.util.Optional.of;
|
import static java.util.Optional.of;
|
||||||
@@ -233,6 +235,17 @@ public final class GitUtil {
|
|||||||
return Strings.nullToEmpty(refName).startsWith(PREFIX_HEADS);
|
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 {
|
public static Ref getBranchIdOrCurrentHead(org.eclipse.jgit.lib.Repository gitRepository, String requestedBranch) throws IOException {
|
||||||
if (Strings.isNullOrEmpty(requestedBranch)) {
|
if (Strings.isNullOrEmpty(requestedBranch)) {
|
||||||
logger.trace("no default branch configured, use repository head as default");
|
logger.trace("no default branch configured, use repository head as default");
|
||||||
@@ -700,4 +713,21 @@ public final class GitUtil {
|
|||||||
.setRefSpecs(new RefSpec(REF_SPEC))
|
.setRefSpecs(new RefSpec(REF_SPEC))
|
||||||
.setTagOpt(TagOpt.FETCH_TAGS);
|
.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.Git;
|
||||||
import org.eclipse.jgit.api.errors.CannotDeleteCurrentBranchException;
|
import org.eclipse.jgit.api.errors.CannotDeleteCurrentBranchException;
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.Ref;
|
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.event.ScmEventBus;
|
||||||
import sonia.scm.repository.Branch;
|
import sonia.scm.repository.Branch;
|
||||||
|
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||||
import sonia.scm.repository.GitUtil;
|
import sonia.scm.repository.GitUtil;
|
||||||
import sonia.scm.repository.InternalRepositoryException;
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||||
@@ -44,28 +49,41 @@ import sonia.scm.repository.api.HookFeature;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
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;
|
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||||
|
|
||||||
public class GitBranchCommand extends AbstractGitCommand implements BranchCommand {
|
public class GitBranchCommand extends AbstractGitCommand implements BranchCommand {
|
||||||
|
|
||||||
private final HookContextFactory hookContextFactory;
|
private final HookContextFactory hookContextFactory;
|
||||||
private final ScmEventBus eventBus;
|
private final ScmEventBus eventBus;
|
||||||
|
private final GitChangesetConverterFactory converterFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
GitBranchCommand(GitContext context, HookContextFactory hookContextFactory, ScmEventBus eventBus) {
|
GitBranchCommand(GitContext context, HookContextFactory hookContextFactory, ScmEventBus eventBus, GitChangesetConverterFactory converterFactory) {
|
||||||
super(context);
|
super(context);
|
||||||
this.hookContextFactory = hookContextFactory;
|
this.hookContextFactory = hookContextFactory;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
|
this.converterFactory = converterFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Branch branch(BranchRequest request) {
|
public Branch branch(BranchRequest request) {
|
||||||
try (Git git = new Git(context.open())) {
|
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));
|
eventBus.post(new PreReceiveRepositoryHookEvent(hookEvent));
|
||||||
Ref ref = git.branchCreate().setStartPoint(request.getParentBranch()).setName(request.getNewBranch()).call();
|
Ref ref = git.branchCreate().setStartPoint(request.getParentBranch()).setName(request.getNewBranch()).call();
|
||||||
eventBus.post(new PostReceiveRepositoryHookEvent(hookEvent));
|
eventBus.post(new PostReceiveRepositoryHookEvent(hookEvent));
|
||||||
@@ -78,7 +96,8 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman
|
|||||||
@Override
|
@Override
|
||||||
public void deleteOrClose(String branchName) {
|
public void deleteOrClose(String branchName) {
|
||||||
try (Git gitRepo = new Git(context.open())) {
|
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));
|
eventBus.post(new PreReceiveRepositoryHookEvent(hookEvent));
|
||||||
gitRepo
|
gitRepo
|
||||||
.branchDelete()
|
.branchDelete()
|
||||||
@@ -98,21 +117,31 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman
|
|||||||
return new RepositoryHookEvent(context, this.context.getRepository(), RepositoryHookType.PRE_RECEIVE);
|
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> newBranches;
|
||||||
private final List<String> deletedBranches;
|
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.newBranches = newBranches;
|
||||||
this.deletedBranches = deletedBranches;
|
this.deletedBranches = deletedBranches;
|
||||||
}
|
this.objectId = objectId;
|
||||||
|
|
||||||
static BranchHookContextProvider createHookEvent(String newBranch) {
|
|
||||||
return new BranchHookContextProvider(singletonList(newBranch), emptyList());
|
|
||||||
}
|
|
||||||
|
|
||||||
static BranchHookContextProvider deleteHookEvent(String deletedBranch) {
|
|
||||||
return new BranchHookContextProvider(emptyList(), singletonList(deletedBranch));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -137,7 +166,31 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HookChangesetProvider getChangesetProvider() {
|
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;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||||
import org.eclipse.jgit.transport.ReceivePack;
|
import org.eclipse.jgit.transport.ReceivePack;
|
||||||
|
|
||||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||||
import sonia.scm.repository.GitHookChangesetCollector;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -58,8 +52,8 @@ public class GitHookChangesetProvider implements HookChangesetProvider {
|
|||||||
@Override
|
@Override
|
||||||
public synchronized HookChangesetResponse handleRequest(HookChangesetRequest request) {
|
public synchronized HookChangesetResponse handleRequest(HookChangesetRequest request) {
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
GitHookChangesetCollector collector = new GitHookChangesetCollector(converterFactory, receivePack, receiveCommands);
|
GitHookChangesetCollector collector = GitHookChangesetCollector.collectChangesets(converterFactory, receiveCommands, receivePack);
|
||||||
response = new HookChangesetResponse(collector.collectChangesets());
|
response = new HookChangesetResponse(collector.getAddedChangesets(), collector.getRemovedChangesets());
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider {
|
|||||||
Command.UNBUNDLE,
|
Command.UNBUNDLE,
|
||||||
Command.MIRROR,
|
Command.MIRROR,
|
||||||
Command.FILE_LOCK,
|
Command.FILE_LOCK,
|
||||||
Command.BRANCH_DETAILS
|
Command.BRANCH_DETAILS,
|
||||||
|
Command.CHANGESETS
|
||||||
);
|
);
|
||||||
|
|
||||||
protected static final Set<Feature> FEATURES = EnumSet.of(
|
protected static final Set<Feature> FEATURES = EnumSet.of(
|
||||||
@@ -192,6 +193,11 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider {
|
|||||||
return commandInjector.getInstance(GitBranchDetailsCommand.class);
|
return commandInjector.getInstance(GitBranchDetailsCommand.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChangesetsCommand getChangesetsCommand() {
|
||||||
|
return commandInjector.getInstance(GitChangesetsCommand.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<Command> getSupportedCommands() {
|
public Set<Command> getSupportedCommands() {
|
||||||
return COMMANDS;
|
return COMMANDS;
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ import org.eclipse.jgit.lib.Repository;
|
|||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevObject;
|
import org.eclipse.jgit.revwalk.RevObject;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||||
import sonia.scm.event.ScmEventBus;
|
import sonia.scm.event.ScmEventBus;
|
||||||
|
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||||
import sonia.scm.repository.GitUtil;
|
import sonia.scm.repository.GitUtil;
|
||||||
import sonia.scm.repository.InternalRepositoryException;
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||||
@@ -53,6 +55,8 @@ import sonia.scm.user.User;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -60,18 +64,23 @@ import java.util.Set;
|
|||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Collections.singleton;
|
import static java.util.Collections.singleton;
|
||||||
import static java.util.Collections.singletonList;
|
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.ContextEntry.ContextBuilder.entity;
|
||||||
import static sonia.scm.NotFoundException.notFound;
|
import static sonia.scm.NotFoundException.notFound;
|
||||||
|
|
||||||
public class GitTagCommand extends AbstractGitCommand implements TagCommand {
|
public class GitTagCommand extends AbstractGitCommand implements TagCommand {
|
||||||
|
public static final String REFS_TAGS_PREFIX = "refs/tags/";
|
||||||
private final HookContextFactory hookContextFactory;
|
private final HookContextFactory hookContextFactory;
|
||||||
private final ScmEventBus eventBus;
|
private final ScmEventBus eventBus;
|
||||||
|
private final GitChangesetConverterFactory converterFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
GitTagCommand(GitContext context, HookContextFactory hookContextFactory, ScmEventBus eventBus) {
|
GitTagCommand(GitContext context, HookContextFactory hookContextFactory, ScmEventBus eventBus, GitChangesetConverterFactory converterFactory) {
|
||||||
super(context);
|
super(context);
|
||||||
this.hookContextFactory = hookContextFactory;
|
this.hookContextFactory = hookContextFactory;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
|
this.converterFactory = converterFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -105,7 +114,7 @@ public class GitTagCommand extends AbstractGitCommand implements TagCommand {
|
|||||||
|
|
||||||
Tag tag = new Tag(name, revision, tagTime);
|
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));
|
eventBus.post(new PreReceiveRepositoryHookEvent(hookEvent));
|
||||||
|
|
||||||
User user = SecurityUtils.getSubject().getPrincipals().oneByType(User.class);
|
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
|
// Deleting a non-existent tag is a valid action and simply succeeds without
|
||||||
// anything happening.
|
// anything happening.
|
||||||
if (!tagRef.isPresent()) {
|
if (tagRef.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,26 +159,37 @@ public class GitTagCommand extends AbstractGitCommand implements TagCommand {
|
|||||||
tag = new Tag(name, commit.name(), tagTime);
|
tag = new Tag(name, commit.name(), tagTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
RepositoryHookEvent hookEvent = createTagHookEvent(TagHookContextProvider.deleteHookEvent(tag));
|
eventBus.post(new PreReceiveRepositoryHookEvent(
|
||||||
eventBus.post(new PreReceiveRepositoryHookEvent(hookEvent));
|
createTagHookEvent(deleteHookEvent(tag), RepositoryHookType.PRE_RECEIVE)
|
||||||
|
));
|
||||||
git.tagDelete().setTags(name).call();
|
git.tagDelete().setTags(name).call();
|
||||||
eventBus.post(new PostReceiveRepositoryHookEvent(hookEvent));
|
eventBus.post(new PostReceiveRepositoryHookEvent(
|
||||||
|
createTagHookEvent(deleteHookEvent(tag), RepositoryHookType.POST_RECEIVE)
|
||||||
|
));
|
||||||
} catch (GitAPIException | IOException e) {
|
} catch (GitAPIException | IOException e) {
|
||||||
throw new InternalRepositoryException(repository, "could not delete tag " + name, e);
|
throw new InternalRepositoryException(repository, "could not delete tag " + name, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<Ref> findTagRef(Git git, String name) throws GitAPIException {
|
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();
|
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());
|
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> newTags;
|
||||||
private final List<Tag> deletedTags;
|
private final List<Tag> deletedTags;
|
||||||
|
|
||||||
@@ -178,14 +198,6 @@ public class GitTagCommand extends AbstractGitCommand implements TagCommand {
|
|||||||
this.deletedTags = deletedTags;
|
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
|
@Override
|
||||||
public Set<HookFeature> getSupportedFeatures() {
|
public Set<HookFeature> getSupportedFeatures() {
|
||||||
return singleton(HookFeature.TAG_PROVIDER);
|
return singleton(HookFeature.TAG_PROVIDER);
|
||||||
@@ -208,7 +220,30 @@ public class GitTagCommand extends AbstractGitCommand implements TagCommand {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HookChangesetProvider getChangesetProvider() {
|
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;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
@@ -32,9 +34,14 @@ import org.mockito.invocation.InvocationOnMock;
|
|||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
import sonia.scm.event.ScmEventBus;
|
import sonia.scm.event.ScmEventBus;
|
||||||
import sonia.scm.repository.Branch;
|
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.PostReceiveRepositoryHookEvent;
|
||||||
|
import sonia.scm.repository.PreProcessorUtil;
|
||||||
import sonia.scm.repository.PreReceiveRepositoryHookEvent;
|
import sonia.scm.repository.PreReceiveRepositoryHookEvent;
|
||||||
import sonia.scm.repository.api.BranchRequest;
|
import sonia.scm.repository.api.BranchRequest;
|
||||||
|
import sonia.scm.repository.api.HookChangesetBuilder;
|
||||||
import sonia.scm.repository.api.HookContext;
|
import sonia.scm.repository.api.HookContext;
|
||||||
import sonia.scm.repository.api.HookContextFactory;
|
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.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class GitBranchCommandTest extends AbstractGitCommandTestBase {
|
public class GitBranchCommandTest extends AbstractGitCommandTestBase {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
|
private PreProcessorUtil preProcessorUtil;
|
||||||
private HookContextFactory hookContextFactory;
|
private HookContextFactory hookContextFactory;
|
||||||
@Mock
|
@Mock
|
||||||
private ScmEventBus eventBus;
|
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
|
@Test
|
||||||
public void shouldCreateBranchWithDefinedSourceBranch() throws IOException {
|
public void shouldCreateBranchWithDefinedSourceBranch() throws IOException {
|
||||||
@@ -108,7 +131,7 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private GitBranchCommand createCommand() {
|
private GitBranchCommand createCommand() {
|
||||||
return new GitBranchCommand(createContext(), hookContextFactory, eventBus);
|
return new GitBranchCommand(createContext(), hookContextFactory, eventBus, converterFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Branch> readBranches(GitContext context) throws IOException {
|
private List<Branch> readBranches(GitContext context) throws IOException {
|
||||||
@@ -119,7 +142,6 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase {
|
|||||||
public void shouldPostCreateEvents() {
|
public void shouldPostCreateEvents() {
|
||||||
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
|
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
|
||||||
doNothing().when(eventBus).post(captor.capture());
|
doNothing().when(eventBus).post(captor.capture());
|
||||||
when(hookContextFactory.createContext(any(), any())).thenAnswer(this::createMockedContext);
|
|
||||||
|
|
||||||
BranchRequest branchRequest = new BranchRequest();
|
BranchRequest branchRequest = new BranchRequest();
|
||||||
branchRequest.setParentBranch("mergeable");
|
branchRequest.setParentBranch("mergeable");
|
||||||
@@ -140,7 +162,6 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase {
|
|||||||
public void shouldPostDeleteEvents() {
|
public void shouldPostDeleteEvents() {
|
||||||
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
|
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
|
||||||
doNothing().when(eventBus).post(captor.capture());
|
doNothing().when(eventBus).post(captor.capture());
|
||||||
when(hookContextFactory.createContext(any(), any())).thenAnswer(this::createMockedContext);
|
|
||||||
|
|
||||||
createCommand().deleteOrClose("squash");
|
createCommand().deleteOrClose("squash");
|
||||||
|
|
||||||
@@ -151,11 +172,15 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase {
|
|||||||
PreReceiveRepositoryHookEvent event = (PreReceiveRepositoryHookEvent) events.get(0);
|
PreReceiveRepositoryHookEvent event = (PreReceiveRepositoryHookEvent) events.get(0);
|
||||||
assertThat(event.getContext().getBranchProvider().getDeletedOrClosed()).containsExactly("squash");
|
assertThat(event.getContext().getBranchProvider().getDeletedOrClosed()).containsExactly("squash");
|
||||||
assertThat(event.getContext().getBranchProvider().getCreatedOrModified()).isEmpty();
|
assertThat(event.getContext().getBranchProvider().getCreatedOrModified()).isEmpty();
|
||||||
}
|
|
||||||
|
|
||||||
private HookContext createMockedContext(InvocationOnMock invocation) {
|
HookChangesetBuilder changesetProvider = event.getContext().getChangesetProvider();
|
||||||
HookContext mock = mock(HookContext.class);
|
assertThat(changesetProvider.getChangesets()).isEmpty();
|
||||||
when(mock.getBranchProvider()).thenReturn(((HookContextProvider) invocation.getArgument(0)).getBranchProvider());
|
assertThat(changesetProvider.getRemovedChangesets())
|
||||||
return mock;
|
.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.mgt.DefaultSecurityManager;
|
||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import org.apache.shiro.util.ThreadContext;
|
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.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.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
import sonia.scm.event.ScmEventBus;
|
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.GitTestHelper;
|
||||||
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||||
|
import sonia.scm.repository.PreProcessorUtil;
|
||||||
import sonia.scm.repository.PreReceiveRepositoryHookEvent;
|
import sonia.scm.repository.PreReceiveRepositoryHookEvent;
|
||||||
import sonia.scm.repository.Tag;
|
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.HookContextFactory;
|
||||||
import sonia.scm.repository.api.TagDeleteRequest;
|
import sonia.scm.repository.api.HookTagProvider;
|
||||||
import sonia.scm.repository.api.TagCreateRequest;
|
import sonia.scm.repository.api.TagCreateRequest;
|
||||||
|
import sonia.scm.repository.api.TagDeleteRequest;
|
||||||
import sonia.scm.security.GPG;
|
import sonia.scm.security.GPG;
|
||||||
import sonia.scm.util.MockUtil;
|
import sonia.scm.util.MockUtil;
|
||||||
|
|
||||||
@@ -66,12 +75,15 @@ public class GitTagCommandTest extends AbstractGitCommandTestBase {
|
|||||||
private GPG gpg;
|
private GPG gpg;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
|
private PreProcessorUtil preProcessorUtil;
|
||||||
private HookContextFactory hookContextFactory;
|
private HookContextFactory hookContextFactory;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private ScmEventBus eventBus;
|
private ScmEventBus eventBus;
|
||||||
|
|
||||||
private Subject subject;
|
@Mock
|
||||||
|
private GitChangesetConverterFactory converterFactory;
|
||||||
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setSigner() {
|
public void setSigner() {
|
||||||
@@ -81,10 +93,24 @@ public class GitTagCommandTest extends AbstractGitCommandTestBase {
|
|||||||
@Before
|
@Before
|
||||||
public void bindThreadContext() {
|
public void bindThreadContext() {
|
||||||
SecurityUtils.setSecurityManager(new DefaultSecurityManager());
|
SecurityUtils.setSecurityManager(new DefaultSecurityManager());
|
||||||
subject = MockUtil.createUserSubject(SecurityUtils.getSecurityManager());
|
Subject subject = MockUtil.createUserSubject(SecurityUtils.getSecurityManager());
|
||||||
ThreadContext.bind(subject);
|
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
|
@After
|
||||||
public void unbindThreadContext() {
|
public void unbindThreadContext() {
|
||||||
ThreadContext.unbindSubject();
|
ThreadContext.unbindSubject();
|
||||||
@@ -105,7 +131,6 @@ public class GitTagCommandTest extends AbstractGitCommandTestBase {
|
|||||||
public void shouldPostCreateEvent() {
|
public void shouldPostCreateEvent() {
|
||||||
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
|
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
|
||||||
doNothing().when(eventBus).post(captor.capture());
|
doNothing().when(eventBus).post(captor.capture());
|
||||||
when(hookContextFactory.createContext(any(), any())).thenAnswer(this::createMockedContext);
|
|
||||||
|
|
||||||
createCommand().create(new TagCreateRequest("592d797cd36432e591416e8b2b98154f4f163411", "newtag"));
|
createCommand().create(new TagCreateRequest("592d797cd36432e591416e8b2b98154f4f163411", "newtag"));
|
||||||
|
|
||||||
@@ -131,26 +156,36 @@ public class GitTagCommandTest extends AbstractGitCommandTestBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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);
|
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
|
||||||
doNothing().when(eventBus).post(captor.capture());
|
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();
|
List<Object> events = captor.getAllValues();
|
||||||
assertThat(events.get(0)).isInstanceOf(PreReceiveRepositoryHookEvent.class);
|
assertThat(events.get(0)).isInstanceOf(PreReceiveRepositoryHookEvent.class);
|
||||||
assertThat(events.get(1)).isInstanceOf(PostReceiveRepositoryHookEvent.class);
|
assertThat(events.get(1)).isInstanceOf(PostReceiveRepositoryHookEvent.class);
|
||||||
|
|
||||||
PreReceiveRepositoryHookEvent event = (PreReceiveRepositoryHookEvent) events.get(0);
|
PreReceiveRepositoryHookEvent event = (PreReceiveRepositoryHookEvent) events.get(0);
|
||||||
assertThat(event.getContext().getTagProvider().getCreatedTags()).isEmpty();
|
HookTagProvider tagProvider = event.getContext().getTagProvider();
|
||||||
final Tag deletedTag = event.getContext().getTagProvider().getDeletedTags().get(0);
|
assertThat(tagProvider.getCreatedTags()).isEmpty();
|
||||||
assertThat(deletedTag.getName()).isEqualTo("test-tag");
|
Tag deletedTag = tagProvider.getDeletedTags().get(0);
|
||||||
assertThat(deletedTag.getRevision()).isEqualTo("86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1");
|
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() {
|
private GitTagCommand createCommand() {
|
||||||
return new GitTagCommand(createContext(), hookContextFactory, eventBus);
|
return new GitTagCommand(createContext(), hookContextFactory, eventBus, converterFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Tag> readTags(GitContext context) throws IOException {
|
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();
|
return tags.stream().filter(t -> name.equals(t.getName())).findFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
private HookContext createMockedContext(InvocationOnMock invocation) {
|
private RevCommit getCommit(String revision) throws IOException {
|
||||||
HookContext mock = mock(HookContext.class);
|
ObjectId commitId = ObjectId.fromString(revision);
|
||||||
when(mock.getTagProvider()).thenReturn(((HookContextProvider) invocation.getArgument(0)).getTagProvider());
|
try (RevWalk revWalk = new RevWalk(createContext().open())) {
|
||||||
return mock;
|
return revWalk.parseCommit(commitId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user