mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-02 03:25:56 +01:00
Add a modifications provider for hooks
This new modifications provider consistently computes the modifications caused by a push to a branch. In contrast to the changeset provider that often has been used before to check what has changed, this also works for forced updates, rebased branches and fast-forwards. Because these types of changes are normally only used with git, this provider (for now) has only been implemented for git. Pushed-by: Rene Pfeuffer<rene.pfeuffer@cloudogu.com> Pushed-by: Alexander Dammeier<alexander.dammeier@cloudogu.com> Co-authored-by: René Pfeuffer<rene.pfeuffer@cloudogu.com> Committed-by: René Pfeuffer<rene.pfeuffer@cloudogu.com>
This commit is contained in:
2
gradle/changelog/hook_provider.yaml
Normal file
2
gradle/changelog/hook_provider.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- type: added
|
||||
description: Modifications in hook provider
|
||||
@@ -34,7 +34,6 @@ import sonia.scm.io.DeepCopy;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.PreProcessorUtil;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.spi.HookChangesetProvider;
|
||||
import sonia.scm.repository.spi.HookChangesetRequest;
|
||||
import sonia.scm.repository.spi.HookChangesetResponse;
|
||||
|
||||
|
||||
@@ -22,7 +22,10 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import sonia.scm.repository.spi.HookChangesetRequest;
|
||||
import sonia.scm.repository.spi.HookChangesetResponse;
|
||||
|
||||
/**
|
||||
* @author Sebastian Sdorra
|
||||
@@ -80,8 +80,7 @@ public final class HookContext {
|
||||
* @since 1.45
|
||||
*/
|
||||
public HookBranchProvider getBranchProvider() {
|
||||
logger.debug("create branch provider for repository {}",
|
||||
repository.getName());
|
||||
logger.debug("create branch provider for repository {}", repository);
|
||||
|
||||
return provider.getBranchProvider();
|
||||
}
|
||||
@@ -98,8 +97,7 @@ public final class HookContext {
|
||||
* @since 1.50
|
||||
*/
|
||||
public HookTagProvider getTagProvider() {
|
||||
logger.debug("create tag provider for repository {}",
|
||||
repository.getName());
|
||||
logger.debug("create tag provider for repository {}", repository);
|
||||
|
||||
return provider.getTagProvider();
|
||||
}
|
||||
@@ -115,8 +113,7 @@ public final class HookContext {
|
||||
* by the underlying provider
|
||||
*/
|
||||
public HookChangesetBuilder getChangesetProvider() {
|
||||
logger.debug("create changeset provider for repository {}",
|
||||
repository.getName());
|
||||
logger.debug("create changeset provider for repository {}", repository);
|
||||
|
||||
return new HookChangesetBuilder(
|
||||
repository,
|
||||
@@ -125,6 +122,22 @@ public final class HookContext {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link HookChangesetBuilder} which is able to return all
|
||||
* {@link Changeset}'s during this push/commit.
|
||||
*
|
||||
*
|
||||
* @return {@link HookChangesetBuilder}
|
||||
*
|
||||
* @throws HookFeatureIsNotSupportedException if the feature is not supported
|
||||
* by the underlying provider
|
||||
*/
|
||||
public HookModificationsProvider getModificationsProvider() {
|
||||
logger.debug("create diff provider for repository {}", repository);
|
||||
|
||||
return provider.getModificationsProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link HookMessageProvider} which is able to send message back to
|
||||
* the scm client.
|
||||
@@ -139,8 +152,7 @@ public final class HookContext {
|
||||
* by the underlying provider
|
||||
*/
|
||||
public HookMessageProvider getMessageProvider() {
|
||||
logger.debug("create message provider for repository {}",
|
||||
repository.getName());
|
||||
logger.debug("create message provider for repository {}", repository);
|
||||
|
||||
return provider.getMessageProvider();
|
||||
}
|
||||
@@ -155,8 +167,7 @@ public final class HookContext {
|
||||
* by the underlying provider
|
||||
*/
|
||||
public HookMergeDetectionProvider getMergeDetectionProvider() {
|
||||
logger.debug("create merge detection provider for repository {}",
|
||||
repository.getName());
|
||||
logger.debug("create merge detection provider for repository {}", repository);
|
||||
|
||||
return provider.getMergeDetectionProvider();
|
||||
}
|
||||
|
||||
@@ -62,5 +62,12 @@ public enum HookFeature
|
||||
*
|
||||
* @since 2.4.0
|
||||
*/
|
||||
MERGE_DETECTION_PROVIDER
|
||||
MERGE_DETECTION_PROVIDER,
|
||||
|
||||
/**
|
||||
* Provider to compute modifications
|
||||
*
|
||||
* @since 2.48.0
|
||||
*/
|
||||
MODIFICATIONS_PROVIDER
|
||||
}
|
||||
|
||||
@@ -21,14 +21,23 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import sonia.scm.ContextEntry;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
public class UnsupportedModificationTypeException extends InternalRepositoryException {
|
||||
public UnsupportedModificationTypeException(ContextEntry.ContextBuilder entity, String message) {
|
||||
super(entity, message);
|
||||
}
|
||||
import sonia.scm.repository.Modifications;
|
||||
|
||||
/**
|
||||
* The HookDiffProvider returns modifications of branches that have been changed during the current hook.
|
||||
*
|
||||
* @since 2.48.0
|
||||
*/
|
||||
public interface HookModificationsProvider {
|
||||
/**
|
||||
* If the given branch has been updated during the current hook, this method returns an {@link Modifications} instance
|
||||
* with the modifications of the branch.
|
||||
* If the branch has been deleted, this will return {@link sonia.scm.repository.Removed} modifications for all paths.
|
||||
* Accordingly, if the branch has been created, this will return {@link sonia.scm.repository.Added} modifications
|
||||
* for all paths.
|
||||
*/
|
||||
Modifications getModifications(String branchName);
|
||||
}
|
||||
@@ -24,18 +24,16 @@
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import sonia.scm.repository.api.HookBranchProvider;
|
||||
import sonia.scm.repository.api.HookChangesetProvider;
|
||||
import sonia.scm.repository.api.HookContext;
|
||||
import sonia.scm.repository.api.HookException;
|
||||
import sonia.scm.repository.api.HookFeature;
|
||||
import sonia.scm.repository.api.HookFeatureIsNotSupportedException;
|
||||
import sonia.scm.repository.api.HookMessageProvider;
|
||||
import sonia.scm.repository.api.HookContext;
|
||||
import sonia.scm.repository.api.HookModificationsProvider;
|
||||
import sonia.scm.repository.api.HookTagProvider;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -128,6 +126,10 @@ public abstract class HookContextProvider
|
||||
throw new HookFeatureIsNotSupportedException(HookFeature.MERGE_DETECTION_PROVIDER);
|
||||
}
|
||||
|
||||
public HookModificationsProvider getModificationsProvider() {
|
||||
throw new HookFeatureIsNotSupportedException(HookFeature.MODIFICATIONS_PROVIDER);
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
@@ -36,7 +36,6 @@ import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.repository.PreProcessorUtil;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.spi.HookChangesetProvider;
|
||||
import sonia.scm.repository.spi.HookChangesetRequest;
|
||||
import sonia.scm.repository.spi.HookChangesetResponse;
|
||||
import sonia.scm.repository.spi.HookContextProvider;
|
||||
@@ -53,7 +52,7 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link HookContext}.
|
||||
*
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@@ -61,19 +60,19 @@ public class HookContextTest {
|
||||
|
||||
@Mock
|
||||
private HookContextProvider provider;
|
||||
|
||||
|
||||
@Mock
|
||||
private Repository repository;
|
||||
|
||||
@Mock
|
||||
private PreProcessorUtil preProcessorUtil;
|
||||
|
||||
|
||||
@Mock
|
||||
private HookChangesetProvider changesetProvider;
|
||||
|
||||
|
||||
@InjectMocks
|
||||
private HookContext context;
|
||||
|
||||
|
||||
/**
|
||||
* Set up mocks for upcoming test.
|
||||
*/
|
||||
@@ -81,19 +80,19 @@ public class HookContextTest {
|
||||
public void setUpMocks(){
|
||||
when(provider.getChangesetProvider()).thenReturn(changesetProvider);
|
||||
when(provider.getSupportedFeatures()).thenReturn(Sets.newHashSet(HookFeature.CHANGESET_PROVIDER));
|
||||
|
||||
|
||||
List<Changeset> changesets = Lists.newArrayList(new Changeset("1", Long.MIN_VALUE, new Person("Trillian")));
|
||||
HookChangesetResponse response = new HookChangesetResponse(changesets);
|
||||
when(changesetProvider.handleRequest(any(HookChangesetRequest.class))).thenReturn(response);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests {@link HookContext#getBranchProvider()}.
|
||||
*/
|
||||
@Test
|
||||
public void testGetBranchProvider() {
|
||||
context.getBranchProvider();
|
||||
|
||||
|
||||
verify(provider).getBranchProvider();
|
||||
}
|
||||
|
||||
@@ -103,20 +102,20 @@ public class HookContextTest {
|
||||
@Test
|
||||
public void testGetTagProvider() {
|
||||
context.getTagProvider();
|
||||
|
||||
|
||||
verify(provider).getTagProvider();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests {@link HookContext#getMessageProvider()}.
|
||||
*/
|
||||
@Test
|
||||
public void testGetMessageProvider() {
|
||||
context.getMessageProvider();
|
||||
|
||||
|
||||
verify(provider).getMessageProvider();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests {@link HookContext#getChangesetProvider()}.
|
||||
*/
|
||||
@@ -127,7 +126,7 @@ public class HookContextTest {
|
||||
assertNotNull(changesets);
|
||||
assertEquals("1", changesets.get(0).getId());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests {@link HookContext#isFeatureSupported(sonia.scm.repository.api.HookFeature)}.
|
||||
*/
|
||||
@@ -136,5 +135,5 @@ public class HookContextTest {
|
||||
assertTrue(context.isFeatureSupported(HookFeature.CHANGESET_PROVIDER));
|
||||
assertFalse(context.isFeatureSupported(HookFeature.BRANCH_PROVIDER));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.ContextEntry;
|
||||
import sonia.scm.repository.Added;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.Modification;
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.Removed;
|
||||
import sonia.scm.repository.spi.BranchBasedModificationsComputer;
|
||||
import sonia.scm.repository.spi.ModificationsComputer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Computes modifications for created, modified and deleted git branches during a hook.
|
||||
*/
|
||||
public class GitHookModificationsProvider implements HookModificationsProvider {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GitHookModificationsProvider.class);
|
||||
private final org.eclipse.jgit.lib.Repository repository;
|
||||
private final Map<String, BranchEntry> modificationsCommandRequests;
|
||||
|
||||
public GitHookModificationsProvider(List<ReceiveCommand> commands, org.eclipse.jgit.lib.Repository repository) {
|
||||
this.repository = repository;
|
||||
ImmutableMap.Builder<String, BranchEntry> modificationsCommandRequestBuilder = ImmutableMap.builder();
|
||||
|
||||
for (ReceiveCommand command : commands) {
|
||||
String ref = command.getRefName();
|
||||
String branch = GitUtil.getBranch(ref);
|
||||
if (Strings.isNullOrEmpty(branch)) {
|
||||
logger.debug("ref {} is not a branch", ref);
|
||||
} else if (command.getType() == ReceiveCommand.Type.UPDATE || command.getType() == ReceiveCommand.Type.UPDATE_NONFASTFORWARD) {
|
||||
modificationsCommandRequestBuilder.put(branch, new UpdateBranchEntry(command.getNewId().name(), command.getOldId().name()));
|
||||
} else if (command.getType() == ReceiveCommand.Type.CREATE) {
|
||||
modificationsCommandRequestBuilder.put(branch, new CreateBranchEntry(command.getNewId()));
|
||||
} else if (command.getType() == ReceiveCommand.Type.DELETE) {
|
||||
modificationsCommandRequestBuilder.put(branch, new DeleteBranchEntry(command.getOldId()));
|
||||
}
|
||||
}
|
||||
|
||||
modificationsCommandRequests = modificationsCommandRequestBuilder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifications getModifications(String branchName) {
|
||||
BranchEntry branchEntry = modificationsCommandRequests.get(branchName);
|
||||
try {
|
||||
return branchEntry.getModifications();
|
||||
} catch (IOException ex) {
|
||||
throw new InternalRepositoryException(ContextEntry.ContextBuilder.entity("Git Repository", repository.toString()), "could not compute diff for branch " + branchName, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private interface BranchEntry {
|
||||
Modifications getModifications() throws IOException;
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
private class UpdateBranchEntry implements BranchEntry {
|
||||
private final String newRevision;
|
||||
private final String oldRevision;
|
||||
|
||||
@Override
|
||||
public Modifications getModifications() throws IOException {
|
||||
return new Modifications(
|
||||
oldRevision,
|
||||
newRevision,
|
||||
new ModificationsComputer(repository).compute(oldRevision, newRevision).getModifications()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
private class DeleteBranchEntry implements BranchEntry {
|
||||
private final ObjectId oldRevision;
|
||||
|
||||
@Override
|
||||
public Modifications getModifications() throws IOException {
|
||||
return createModifications(oldRevision, Removed::new);
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
private class CreateBranchEntry implements BranchEntry {
|
||||
private final ObjectId newRevision;
|
||||
|
||||
@Override
|
||||
public Modifications getModifications() throws IOException {
|
||||
return createModifications(newRevision, Added::new);
|
||||
}
|
||||
}
|
||||
|
||||
private Modifications createModifications(ObjectId revision, Function<String, Modification> modificationFactory) throws IOException {
|
||||
return new BranchBasedModificationsComputer(repository).createModifications(revision, modificationFactory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevTree;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||
import sonia.scm.repository.Modification;
|
||||
import sonia.scm.repository.Modifications;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class BranchBasedModificationsComputer {
|
||||
|
||||
private final Repository repository;
|
||||
|
||||
public BranchBasedModificationsComputer(Repository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public Modifications createModifications(ObjectId revision, Function<String, Modification> modificationFactory) throws IOException {
|
||||
Collection<Modification> modifications = new ArrayList<>();
|
||||
try (RevWalk revWalk = new RevWalk(repository); TreeWalk treeWalk = new TreeWalk(repository)) {
|
||||
RevTree tree = revWalk.parseTree(revision);
|
||||
treeWalk.addTree(tree);
|
||||
while (treeWalk.next()) {
|
||||
if (treeWalk.isSubtree()) {
|
||||
treeWalk.enterSubtree();
|
||||
} else {
|
||||
modifications.add(modificationFactory.apply(treeWalk.getPathString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Modifications(revision.name(), modifications);
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ import java.util.List;
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
|
||||
final class Differ implements AutoCloseable {
|
||||
public final class Differ implements AutoCloseable {
|
||||
|
||||
private final RevWalk walk;
|
||||
private final TreeWalk treeWalk;
|
||||
@@ -62,7 +62,7 @@ final class Differ implements AutoCloseable {
|
||||
this.commonAncestor = commonAncestor;
|
||||
}
|
||||
|
||||
static Diff diff(Repository repository, DiffCommandRequest request) throws IOException {
|
||||
public static Diff diff(Repository repository, DiffCommandRequest request) throws IOException {
|
||||
try (Differ differ = create(repository, request)) {
|
||||
return differ.diff(repository, differ.commonAncestor);
|
||||
}
|
||||
|
||||
@@ -35,31 +35,36 @@ 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.ContextEntry;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.repository.Added;
|
||||
import sonia.scm.repository.Branch;
|
||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.Modification;
|
||||
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||
import sonia.scm.repository.PreReceiveRepositoryHookEvent;
|
||||
import sonia.scm.repository.Removed;
|
||||
import sonia.scm.repository.RepositoryHookEvent;
|
||||
import sonia.scm.repository.RepositoryHookType;
|
||||
import sonia.scm.repository.api.BranchRequest;
|
||||
import sonia.scm.repository.api.HookBranchProvider;
|
||||
import sonia.scm.repository.api.HookChangesetProvider;
|
||||
import sonia.scm.repository.api.HookContext;
|
||||
import sonia.scm.repository.api.HookContextFactory;
|
||||
import sonia.scm.repository.api.HookFeature;
|
||||
import sonia.scm.repository.api.HookModificationsProvider;
|
||||
|
||||
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 java.util.function.Function;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
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.ScmConstraintViolationException.Builder.doThrow;
|
||||
@@ -138,43 +143,29 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman
|
||||
}
|
||||
|
||||
private BranchHookContextProvider createHookEvent(String newBranch, ObjectId objectId) {
|
||||
return new BranchHookContextProvider(singletonList(newBranch), emptyList(), objectId);
|
||||
return new CreatedBranchHookContextProvider(newBranch, objectId);
|
||||
}
|
||||
|
||||
private BranchHookContextProvider deleteHookEvent(String deletedBranch, ObjectId oldObjectId) {
|
||||
return new BranchHookContextProvider(emptyList(), singletonList(deletedBranch), oldObjectId);
|
||||
return new DeletedBranchHookContextProvider(deletedBranch, oldObjectId);
|
||||
}
|
||||
|
||||
private class BranchHookContextProvider extends HookContextProvider {
|
||||
private final List<String> newBranches;
|
||||
private final List<String> deletedBranches;
|
||||
private final ObjectId objectId;
|
||||
private abstract class BranchHookContextProvider extends HookContextProvider {
|
||||
final String branchName;
|
||||
final ObjectId objectId;
|
||||
|
||||
private BranchHookContextProvider(List<String> newBranches, List<String> deletedBranches, ObjectId objectId) {
|
||||
this.newBranches = newBranches;
|
||||
this.deletedBranches = deletedBranches;
|
||||
private BranchHookContextProvider(String branchName, ObjectId objectId) {
|
||||
this.branchName = branchName;
|
||||
this.objectId = objectId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<HookFeature> getSupportedFeatures() {
|
||||
return singleton(HookFeature.BRANCH_PROVIDER);
|
||||
return Set.of(HookFeature.BRANCH_PROVIDER, HookFeature.MODIFICATIONS_PROVIDER, HookFeature.CHANGESET_PROVIDER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HookBranchProvider getBranchProvider() {
|
||||
return new HookBranchProvider() {
|
||||
@Override
|
||||
public List<String> getCreatedOrModified() {
|
||||
return newBranches;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDeletedOrClosed() {
|
||||
return deletedBranches;
|
||||
}
|
||||
};
|
||||
}
|
||||
public abstract HookBranchProvider getBranchProvider();
|
||||
|
||||
@Override
|
||||
public HookChangesetProvider getChangesetProvider() {
|
||||
@@ -185,13 +176,7 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman
|
||||
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);
|
||||
Collection<ReceiveCommand> receiveCommands = asList(createReceiveCommand());
|
||||
return x -> {
|
||||
GitHookChangesetCollector collector =
|
||||
GitHookChangesetCollector.collectChangesets(
|
||||
@@ -204,6 +189,83 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman
|
||||
return new HookChangesetResponse(collector.getAddedChangesets(), collector.getRemovedChangesets());
|
||||
};
|
||||
}
|
||||
|
||||
abstract ReceiveCommand createReceiveCommand();
|
||||
|
||||
@Override
|
||||
public HookModificationsProvider getModificationsProvider() {
|
||||
return branchName -> {
|
||||
try {
|
||||
return new BranchBasedModificationsComputer(context.open()).createModifications(objectId, getModificationFactory());
|
||||
} catch (IOException ex) {
|
||||
throw new InternalRepositoryException(ContextEntry.ContextBuilder.entity("Git Repository", repository.toString()), "could not compute diff for branch " + branchName, ex);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
abstract Function<String, Modification> getModificationFactory();
|
||||
}
|
||||
|
||||
private class CreatedBranchHookContextProvider extends BranchHookContextProvider {
|
||||
public CreatedBranchHookContextProvider(String branchName, ObjectId objectId) {
|
||||
super(branchName, objectId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HookBranchProvider getBranchProvider() {
|
||||
return new HookBranchProvider() {
|
||||
@Override
|
||||
public List<String> getCreatedOrModified() {
|
||||
return asList(branchName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDeletedOrClosed() {
|
||||
return emptyList();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
ReceiveCommand createReceiveCommand() {
|
||||
return new ReceiveCommand(zeroId(), objectId, "refs/heads/" + branchName);
|
||||
}
|
||||
|
||||
@Override
|
||||
Function<String, Modification> getModificationFactory() {
|
||||
return Added::new;
|
||||
}
|
||||
}
|
||||
|
||||
private class DeletedBranchHookContextProvider extends BranchHookContextProvider {
|
||||
public DeletedBranchHookContextProvider(String branchName, ObjectId objectId) {
|
||||
super(branchName, objectId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HookBranchProvider getBranchProvider() {
|
||||
return new HookBranchProvider() {
|
||||
@Override
|
||||
public List<String> getCreatedOrModified() {
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDeletedOrClosed() {
|
||||
return asList(branchName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
ReceiveCommand createReceiveCommand() {
|
||||
return new ReceiveCommand(objectId, zeroId(), "refs/heads/" + branchName);
|
||||
}
|
||||
|
||||
@Override
|
||||
Function<String, Modification> getModificationFactory() {
|
||||
return Removed::new;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Factory {
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* 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.diff.DiffEntry;
|
||||
import org.eclipse.jgit.diff.DiffFormatter;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.api.DiffFile;
|
||||
import sonia.scm.repository.api.DiffResult;
|
||||
import sonia.scm.repository.api.Hunk;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
public class GitDiffResult implements DiffResult {
|
||||
|
||||
private final Repository scmRepository;
|
||||
private final org.eclipse.jgit.lib.Repository repository;
|
||||
private final Differ.Diff diff;
|
||||
private final List<DiffEntry> diffEntries;
|
||||
|
||||
private final int offset;
|
||||
private final Integer limit;
|
||||
|
||||
public GitDiffResult(Repository scmRepository, org.eclipse.jgit.lib.Repository repository, Differ.Diff diff, int offset, Integer limit) {
|
||||
this.scmRepository = scmRepository;
|
||||
this.repository = repository;
|
||||
this.diff = diff;
|
||||
this.offset = offset;
|
||||
this.limit = limit;
|
||||
this.diffEntries = diff.getEntries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOldRevision() {
|
||||
ObjectId commonAncestor = diff.getCommonAncestor();
|
||||
if (commonAncestor != null) {
|
||||
return commonAncestor.name();
|
||||
}
|
||||
return diff.getCommit().getParentCount() > 0 ? GitUtil.getId(diff.getCommit().getParent(0).getId()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNewRevision() {
|
||||
return GitUtil.getId(diff.getCommit().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPartial() {
|
||||
return limit != null && limit + offset < diffEntries.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Integer> getLimit() {
|
||||
return ofNullable(limit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<DiffFile> iterator() {
|
||||
Stream<DiffEntry> diffEntryStream = diffEntries
|
||||
.stream()
|
||||
.skip(offset);
|
||||
if (limit != null) {
|
||||
diffEntryStream = diffEntryStream.limit(limit);
|
||||
}
|
||||
return diffEntryStream
|
||||
.map(diffEntry -> new GitDiffFile(repository, diffEntry))
|
||||
.map(DiffFile.class::cast)
|
||||
.iterator();
|
||||
}
|
||||
|
||||
private class GitDiffFile implements DiffFile {
|
||||
|
||||
private final org.eclipse.jgit.lib.Repository repository;
|
||||
private final DiffEntry diffEntry;
|
||||
|
||||
private GitDiffFile(org.eclipse.jgit.lib.Repository repository, DiffEntry diffEntry) {
|
||||
this.repository = repository;
|
||||
this.diffEntry = diffEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOldRevision() {
|
||||
return GitDiffResult.this.getOldRevision();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNewRevision() {
|
||||
return GitDiffResult.this.getNewRevision();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOldPath() {
|
||||
return diffEntry.getOldPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNewPath() {
|
||||
return diffEntry.getNewPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangeType getChangeType() {
|
||||
switch (diffEntry.getChangeType()) {
|
||||
case ADD:
|
||||
return ChangeType.ADD;
|
||||
case MODIFY:
|
||||
return ChangeType.MODIFY;
|
||||
case RENAME:
|
||||
return ChangeType.RENAME;
|
||||
case DELETE:
|
||||
return ChangeType.DELETE;
|
||||
case COPY:
|
||||
return ChangeType.COPY;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown change type: " + diffEntry.getChangeType());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Hunk> iterator() {
|
||||
String content = format(repository, diffEntry);
|
||||
GitHunkParser parser = new GitHunkParser();
|
||||
return parser.parse(content).iterator();
|
||||
}
|
||||
|
||||
private String format(org.eclipse.jgit.lib.Repository repository, DiffEntry entry) {
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); DiffFormatter formatter = new DiffFormatter(baos)) {
|
||||
formatter.setRepository(repository);
|
||||
formatter.format(entry);
|
||||
return baos.toString(StandardCharsets.UTF_8);
|
||||
} catch (IOException ex) {
|
||||
throw new InternalRepositoryException(scmRepository, "failed to format diff entry", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -25,28 +25,13 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import org.eclipse.jgit.diff.DiffEntry;
|
||||
import org.eclipse.jgit.diff.DiffFormatter;
|
||||
import org.eclipse.jgit.errors.AmbiguousObjectException;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import sonia.scm.NotUniqueRevisionException;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.api.DiffFile;
|
||||
import sonia.scm.repository.api.DiffResult;
|
||||
import sonia.scm.repository.api.Hunk;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
public class GitDiffResultCommand extends AbstractGitCommand implements DiffResultCommand {
|
||||
|
||||
@@ -57,7 +42,7 @@ public class GitDiffResultCommand extends AbstractGitCommand implements DiffResu
|
||||
|
||||
public DiffResult getDiffResult(DiffCommandRequest diffCommandRequest) throws IOException {
|
||||
org.eclipse.jgit.lib.Repository repository = open();
|
||||
return new GitDiffResult(repository, Differ.diff(repository, diffCommandRequest), 0, null);
|
||||
return new GitDiffResult(this.repository, repository, Differ.diff(repository, diffCommandRequest), 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,140 +50,12 @@ public class GitDiffResultCommand extends AbstractGitCommand implements DiffResu
|
||||
org.eclipse.jgit.lib.Repository repository = open();
|
||||
int offset = request.getOffset() == null ? 0 : request.getOffset();
|
||||
try {
|
||||
return new GitDiffResult(repository, Differ.diff(repository, request), offset, request.getLimit());
|
||||
return new GitDiffResult(this.repository, repository, Differ.diff(repository, request), offset, request.getLimit());
|
||||
} catch (AmbiguousObjectException ex) {
|
||||
throw new NotUniqueRevisionException(Repository.class, context.getRepository().getId());
|
||||
}
|
||||
}
|
||||
|
||||
private class GitDiffResult implements DiffResult {
|
||||
|
||||
private final org.eclipse.jgit.lib.Repository repository;
|
||||
private final Differ.Diff diff;
|
||||
private final List<DiffEntry> diffEntries;
|
||||
|
||||
private final int offset;
|
||||
private final Integer limit;
|
||||
|
||||
private GitDiffResult(org.eclipse.jgit.lib.Repository repository, Differ.Diff diff, int offset, Integer limit) {
|
||||
this.repository = repository;
|
||||
this.diff = diff;
|
||||
this.offset = offset;
|
||||
this.limit = limit;
|
||||
this.diffEntries = diff.getEntries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOldRevision() {
|
||||
ObjectId commonAncestor = diff.getCommonAncestor();
|
||||
if (commonAncestor != null) {
|
||||
return commonAncestor.name();
|
||||
}
|
||||
return diff.getCommit().getParentCount() > 0 ? GitUtil.getId(diff.getCommit().getParent(0).getId()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNewRevision() {
|
||||
return GitUtil.getId(diff.getCommit().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPartial() {
|
||||
return limit != null && limit + offset < diffEntries.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Integer> getLimit() {
|
||||
return ofNullable(limit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<DiffFile> iterator() {
|
||||
Stream<DiffEntry> diffEntryStream = diffEntries
|
||||
.stream()
|
||||
.skip(offset);
|
||||
if (limit != null) {
|
||||
diffEntryStream = diffEntryStream.limit(limit);
|
||||
}
|
||||
return diffEntryStream
|
||||
.map(diffEntry -> new GitDiffFile(repository, diffEntry))
|
||||
.map(DiffFile.class::cast)
|
||||
.iterator();
|
||||
}
|
||||
|
||||
private class GitDiffFile implements DiffFile {
|
||||
|
||||
private final org.eclipse.jgit.lib.Repository repository;
|
||||
private final DiffEntry diffEntry;
|
||||
|
||||
private GitDiffFile(org.eclipse.jgit.lib.Repository repository, DiffEntry diffEntry) {
|
||||
this.repository = repository;
|
||||
this.diffEntry = diffEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOldRevision() {
|
||||
return GitDiffResult.this.getOldRevision();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNewRevision() {
|
||||
return GitDiffResult.this.getNewRevision();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOldPath() {
|
||||
return diffEntry.getOldPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNewPath() {
|
||||
return diffEntry.getNewPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangeType getChangeType() {
|
||||
switch (diffEntry.getChangeType()) {
|
||||
case ADD:
|
||||
return ChangeType.ADD;
|
||||
case MODIFY:
|
||||
return ChangeType.MODIFY;
|
||||
case RENAME:
|
||||
return ChangeType.RENAME;
|
||||
case DELETE:
|
||||
return ChangeType.DELETE;
|
||||
case COPY:
|
||||
return ChangeType.COPY;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown change type: " + diffEntry.getChangeType());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Hunk> iterator() {
|
||||
String content = format(repository, diffEntry);
|
||||
GitHunkParser parser = new GitHunkParser();
|
||||
return parser.parse(content).iterator();
|
||||
}
|
||||
|
||||
private String format(org.eclipse.jgit.lib.Repository repository, DiffEntry entry) {
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); DiffFormatter formatter = new DiffFormatter(baos)) {
|
||||
formatter.setRepository(repository);
|
||||
formatter.format(entry);
|
||||
return baos.toString(StandardCharsets.UTF_8);
|
||||
} catch (IOException ex) {
|
||||
throw new InternalRepositoryException(GitDiffResultCommand.this.repository, "failed to format diff entry", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public interface Factory {
|
||||
DiffResultCommand create(GitContext context);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ package sonia.scm.repository.spi;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
import org.eclipse.jgit.transport.ReceivePack;
|
||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||
import sonia.scm.repository.api.HookChangesetProvider;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -31,10 +31,13 @@ import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
import org.eclipse.jgit.transport.ReceivePack;
|
||||
import sonia.scm.repository.api.GitHookBranchProvider;
|
||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
||||
import sonia.scm.repository.api.GitHookModificationsProvider;
|
||||
import sonia.scm.repository.api.GitHookMessageProvider;
|
||||
import sonia.scm.repository.api.GitHookTagProvider;
|
||||
import sonia.scm.repository.api.GitReceiveHookMergeDetectionProvider;
|
||||
import sonia.scm.repository.api.HookBranchProvider;
|
||||
import sonia.scm.repository.api.HookChangesetProvider;
|
||||
import sonia.scm.repository.api.HookModificationsProvider;
|
||||
import sonia.scm.repository.api.HookFeature;
|
||||
import sonia.scm.repository.api.HookMessageProvider;
|
||||
import sonia.scm.repository.api.HookTagProvider;
|
||||
@@ -58,6 +61,7 @@ public class GitHookContextProvider extends HookContextProvider
|
||||
HookFeature.CHANGESET_PROVIDER,
|
||||
HookFeature.BRANCH_PROVIDER,
|
||||
HookFeature.TAG_PROVIDER,
|
||||
HookFeature.MODIFICATIONS_PROVIDER,
|
||||
HookFeature.MERGE_DETECTION_PROVIDER
|
||||
);
|
||||
|
||||
@@ -117,6 +121,11 @@ public class GitHookContextProvider extends HookContextProvider
|
||||
return new GitReceiveHookMergeDetectionProvider(repository, repositoryId, receiveCommands, converterFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HookModificationsProvider getModificationsProvider() {
|
||||
return new GitHookModificationsProvider(receiveCommands, repository);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<HookFeature> getSupportedFeatures()
|
||||
{
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
import sonia.scm.repository.GitChangesetConverter;
|
||||
import sonia.scm.repository.Tag;
|
||||
import sonia.scm.repository.api.HookBranchProvider;
|
||||
import sonia.scm.repository.api.HookChangesetProvider;
|
||||
import sonia.scm.repository.api.HookFeature;
|
||||
import sonia.scm.repository.api.HookTagProvider;
|
||||
|
||||
|
||||
@@ -26,30 +26,11 @@ package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.jgit.diff.DiffEntry;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevTree;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
|
||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||
import sonia.scm.repository.Added;
|
||||
import sonia.scm.repository.Copied;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.Modification;
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.Modified;
|
||||
import sonia.scm.repository.Removed;
|
||||
import sonia.scm.repository.Renamed;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
|
||||
@@ -58,106 +39,27 @@ import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
public class GitModificationsCommand extends AbstractGitCommand implements ModificationsCommand {
|
||||
|
||||
@Inject
|
||||
GitModificationsCommand(@Assisted GitContext context) {
|
||||
public GitModificationsCommand(@Assisted GitContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("java:S2093")
|
||||
public Modifications getModifications(String baseRevision, String revision) {
|
||||
org.eclipse.jgit.lib.Repository gitRepository = null;
|
||||
RevWalk revWalk = null;
|
||||
try {
|
||||
gitRepository = open();
|
||||
if (!gitRepository.getAllRefs().isEmpty()) {
|
||||
revWalk = new RevWalk(gitRepository);
|
||||
RevCommit commit = getCommit(revision, gitRepository, revWalk);
|
||||
TreeWalk treeWalk = createTreeWalk(gitRepository);
|
||||
if (baseRevision == null) {
|
||||
determineParentAsBase(treeWalk, commit, revWalk);
|
||||
} else {
|
||||
RevCommit baseCommit = getCommit(baseRevision, gitRepository, revWalk);
|
||||
treeWalk.addTree(baseCommit.getTree());
|
||||
}
|
||||
return new Modifications(baseRevision, revision, createModifications(treeWalk, commit));
|
||||
}
|
||||
return new ModificationsComputer(open()).compute(baseRevision, revision);
|
||||
} catch (IOException ex) {
|
||||
log.error("could not open repository: " + repository.getNamespaceAndName(), ex);
|
||||
throw new InternalRepositoryException(entity(repository), "could not open repository: " + repository.getNamespaceAndName(), ex);
|
||||
} finally {
|
||||
GitUtil.release(revWalk);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private RevCommit getCommit(String revision, Repository gitRepository, RevWalk revWalk) throws IOException {
|
||||
ObjectId id = GitUtil.getRevisionId(gitRepository, revision);
|
||||
return revWalk.parseCommit(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifications getModifications(String revision) {
|
||||
return getModifications(null, revision);
|
||||
}
|
||||
|
||||
private TreeWalk createTreeWalk(Repository gitRepository) {
|
||||
TreeWalk treeWalk = new TreeWalk(gitRepository);
|
||||
treeWalk.reset();
|
||||
treeWalk.setRecursive(true);
|
||||
return treeWalk;
|
||||
}
|
||||
|
||||
private Collection<Modification> createModifications(TreeWalk treeWalk, RevCommit commit)
|
||||
throws IOException {
|
||||
treeWalk.addTree(commit.getTree());
|
||||
List<DiffEntry> entries = Differ.scanWithRename(context.open(), null, treeWalk);
|
||||
Collection<Modification> modifications = new ArrayList<>();
|
||||
for (DiffEntry e : entries) {
|
||||
if (!e.getOldId().equals(e.getNewId()) || !e.getOldPath().equals(e.getNewPath())) {
|
||||
modifications.add(asModification(e));
|
||||
}
|
||||
}
|
||||
return modifications;
|
||||
}
|
||||
|
||||
private void determineParentAsBase(TreeWalk treeWalk, RevCommit commit, RevWalk revWalk) throws IOException {
|
||||
if (commit.getParentCount() > 0) {
|
||||
RevCommit parent = commit.getParent(0);
|
||||
RevTree tree = parent.getTree();
|
||||
if ((tree == null) && (revWalk != null)) {
|
||||
revWalk.parseHeaders(parent);
|
||||
tree = parent.getTree();
|
||||
}
|
||||
if (tree != null) {
|
||||
treeWalk.addTree(tree);
|
||||
} else {
|
||||
log.trace("no parent tree at position 0 for commit {}", commit.getName());
|
||||
treeWalk.addTree(new EmptyTreeIterator());
|
||||
}
|
||||
} else {
|
||||
log.trace("no parent available for commit {}", commit.getName());
|
||||
treeWalk.addTree(new EmptyTreeIterator());
|
||||
}
|
||||
}
|
||||
|
||||
private Modification asModification(DiffEntry entry) throws UnsupportedModificationTypeException {
|
||||
DiffEntry.ChangeType type = entry.getChangeType();
|
||||
switch (type) {
|
||||
case ADD:
|
||||
return new Added(entry.getNewPath());
|
||||
case MODIFY:
|
||||
return new Modified(entry.getNewPath());
|
||||
case DELETE:
|
||||
return new Removed(entry.getOldPath());
|
||||
case RENAME:
|
||||
return new Renamed(entry.getOldPath(), entry.getNewPath());
|
||||
case COPY:
|
||||
return new Copied(entry.getOldPath(), entry.getNewPath());
|
||||
default:
|
||||
throw new UnsupportedModificationTypeException(entity(repository), MessageFormat.format("The modification type: {0} is not supported.", type));
|
||||
}
|
||||
}
|
||||
|
||||
public interface Factory {
|
||||
ModificationsCommand create(GitContext context);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import sonia.scm.repository.PreReceiveRepositoryHookEvent;
|
||||
import sonia.scm.repository.RepositoryHookEvent;
|
||||
import sonia.scm.repository.RepositoryHookType;
|
||||
import sonia.scm.repository.Tag;
|
||||
import sonia.scm.repository.api.HookChangesetProvider;
|
||||
import sonia.scm.repository.api.HookContext;
|
||||
import sonia.scm.repository.api.HookContextFactory;
|
||||
import sonia.scm.repository.api.HookFeature;
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.diff.DiffEntry;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevTree;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
|
||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||
import sonia.scm.repository.Added;
|
||||
import sonia.scm.repository.Copied;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.Modification;
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.Modified;
|
||||
import sonia.scm.repository.Removed;
|
||||
import sonia.scm.repository.Renamed;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class ModificationsComputer {
|
||||
|
||||
private final Repository gitRepository;
|
||||
|
||||
public ModificationsComputer(Repository gitRepository) {
|
||||
this.gitRepository = gitRepository;
|
||||
}
|
||||
|
||||
public Modifications compute(String baseRevision, String revision) throws IOException {
|
||||
RevWalk revWalk = null;
|
||||
if (!gitRepository.getAllRefs().isEmpty()) {
|
||||
try {
|
||||
revWalk = new RevWalk(gitRepository);
|
||||
RevCommit commit = getCommit(revision, gitRepository, revWalk);
|
||||
TreeWalk treeWalk = createTreeWalk(gitRepository);
|
||||
if (baseRevision == null) {
|
||||
determineParentAsBase(treeWalk, commit, revWalk);
|
||||
} else {
|
||||
RevCommit baseCommit = getCommit(baseRevision, gitRepository, revWalk);
|
||||
treeWalk.addTree(baseCommit.getTree());
|
||||
}
|
||||
return new Modifications(baseRevision, revision, createModifications(treeWalk, commit));
|
||||
} finally {
|
||||
GitUtil.release(revWalk);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private RevCommit getCommit(String revision, Repository gitRepository, RevWalk revWalk) throws IOException {
|
||||
ObjectId id = GitUtil.getRevisionId(gitRepository, revision);
|
||||
return revWalk.parseCommit(id);
|
||||
}
|
||||
|
||||
private TreeWalk createTreeWalk(Repository gitRepository) {
|
||||
TreeWalk treeWalk = new TreeWalk(gitRepository);
|
||||
treeWalk.reset();
|
||||
treeWalk.setRecursive(true);
|
||||
return treeWalk;
|
||||
}
|
||||
|
||||
private Collection<Modification> createModifications(TreeWalk treeWalk, RevCommit commit)
|
||||
throws IOException {
|
||||
treeWalk.addTree(commit.getTree());
|
||||
List<DiffEntry> entries = Differ.scanWithRename(gitRepository, null, treeWalk);
|
||||
Collection<Modification> modifications = new ArrayList<>();
|
||||
for (DiffEntry e : entries) {
|
||||
if (!e.getOldId().equals(e.getNewId()) || !e.getOldPath().equals(e.getNewPath())) {
|
||||
modifications.add(asModification(e));
|
||||
}
|
||||
}
|
||||
return modifications;
|
||||
}
|
||||
|
||||
private void determineParentAsBase(TreeWalk treeWalk, RevCommit commit, RevWalk revWalk) throws IOException {
|
||||
if (commit.getParentCount() > 0) {
|
||||
RevCommit parent = commit.getParent(0);
|
||||
RevTree tree = parent.getTree();
|
||||
if ((tree == null) && (revWalk != null)) {
|
||||
revWalk.parseHeaders(parent);
|
||||
tree = parent.getTree();
|
||||
}
|
||||
if (tree != null) {
|
||||
treeWalk.addTree(tree);
|
||||
} else {
|
||||
log.trace("no parent tree at position 0 for commit {}", commit.getName());
|
||||
treeWalk.addTree(new EmptyTreeIterator());
|
||||
}
|
||||
} else {
|
||||
log.trace("no parent available for commit {}", commit.getName());
|
||||
treeWalk.addTree(new EmptyTreeIterator());
|
||||
}
|
||||
}
|
||||
|
||||
private Modification asModification(DiffEntry entry) {
|
||||
DiffEntry.ChangeType type = entry.getChangeType();
|
||||
switch (type) {
|
||||
case ADD:
|
||||
return new Added(entry.getNewPath());
|
||||
case MODIFY:
|
||||
return new Modified(entry.getNewPath());
|
||||
case DELETE:
|
||||
return new Removed(entry.getOldPath());
|
||||
case RENAME:
|
||||
return new Renamed(entry.getOldPath(), entry.getNewPath());
|
||||
case COPY:
|
||||
return new Copied(entry.getOldPath(), entry.getNewPath());
|
||||
default:
|
||||
throw new IllegalArgumentException(MessageFormat.format("The modification type: {0} is not supported.", type));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
@@ -40,7 +40,7 @@ import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link GitHookBranchProvider}.
|
||||
*
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@@ -48,9 +48,9 @@ public class GitHookBranchProviderTest {
|
||||
|
||||
@Mock
|
||||
private ReceiveCommand command;
|
||||
|
||||
|
||||
private List<ReceiveCommand> commands;
|
||||
|
||||
|
||||
/**
|
||||
* Prepare mocks for upcoming test.
|
||||
*/
|
||||
@@ -58,37 +58,37 @@ public class GitHookBranchProviderTest {
|
||||
public void setUpMocks(){
|
||||
commands = Lists.newArrayList(command);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests {@link GitHookBranchProvider#getCreatedOrModified()}.
|
||||
*/
|
||||
@Test
|
||||
public void testGetCreatedOrModified(){
|
||||
List<ReceiveCommand.Type> types = Arrays.asList(
|
||||
ReceiveCommand.Type.CREATE, ReceiveCommand.Type.UPDATE, ReceiveCommand.Type.UPDATE_NONFASTFORWARD
|
||||
List<ReceiveCommand.Type> types = Arrays.asList(
|
||||
ReceiveCommand.Type.CREATE, ReceiveCommand.Type.UPDATE, ReceiveCommand.Type.UPDATE_NONFASTFORWARD
|
||||
);
|
||||
for ( ReceiveCommand.Type type : types ){
|
||||
checkCreatedOrModified(type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void checkCreatedOrModified(ReceiveCommand.Type type){
|
||||
GitHookBranchProvider provider = createGitHookBranchProvider(type, "refs/heads/hello");
|
||||
assertThat(provider.getCreatedOrModified(), Matchers.contains("hello"));
|
||||
assertThat(provider.getDeletedOrClosed(), empty());
|
||||
assertThat(provider.getDeletedOrClosed(), empty());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Tests {@link GitHookBranchProvider#getDeletedOrClosed()}.
|
||||
*/
|
||||
*/
|
||||
@Test
|
||||
public void testGetDeletedOrClosed(){
|
||||
GitHookBranchProvider provider = createGitHookBranchProvider(ReceiveCommand.Type.DELETE, "refs/heads/hello");
|
||||
assertThat(provider.getDeletedOrClosed(), Matchers.contains("hello"));
|
||||
assertThat(provider.getCreatedOrModified(), empty());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests {@link GitHookBranchProvider} with a tag instead of a branch.
|
||||
*/
|
||||
@@ -98,7 +98,7 @@ public class GitHookBranchProviderTest {
|
||||
assertThat(provider.getCreatedOrModified(), empty());
|
||||
assertThat(provider.getDeletedOrClosed(), empty());
|
||||
}
|
||||
|
||||
|
||||
private GitHookBranchProvider createGitHookBranchProvider(ReceiveCommand.Type type, String refName){
|
||||
when(command.getType()).thenReturn(type);
|
||||
when(command.getRefName()).thenReturn(refName);
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
import org.junit.Test;
|
||||
import sonia.scm.repository.spi.AbstractGitCommandTestBase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class GitHookModificationsProviderTest extends AbstractGitCommandTestBase {
|
||||
|
||||
@Test
|
||||
public void shouldReturnModificationsForNormalUpdate() throws IOException {
|
||||
GitHookModificationsProvider provider = mockProviderWithChange(ReceiveCommand.Type.UPDATE);
|
||||
|
||||
assertThat(provider.getModifications("rename"))
|
||||
.extracting("modifications")
|
||||
.asList()
|
||||
.hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnModificationsForFastForward() throws IOException {
|
||||
GitHookModificationsProvider provider = mockProviderWithChange(ReceiveCommand.Type.UPDATE_NONFASTFORWARD);
|
||||
|
||||
assertThat(provider.getModifications("rename"))
|
||||
.extracting("modifications")
|
||||
.asList()
|
||||
.hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnEmptyModificationsForBranchWithRevertedCommit() throws IOException {
|
||||
GitHookModificationsProvider provider = mockProviderWithChange(ReceiveCommand.Type.UPDATE, "03ca33468c2094249973d0ca11b80243a20de368", "592d797cd36432e591416e8b2b98154f4f163411");
|
||||
|
||||
assertThat(provider.getModifications("rename"))
|
||||
.extracting("modifications")
|
||||
.asList()
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnEmptyModificationsForDeletedBranch() throws IOException {
|
||||
GitHookModificationsProvider provider = mockProviderWithChange(
|
||||
ReceiveCommand.Type.DELETE,
|
||||
"0000000000000000000000000000000000000000",
|
||||
"fcd0ef1831e4002ac43ea539f4094334c79ea9ec");
|
||||
|
||||
assertThat(provider.getModifications("rename"))
|
||||
.extracting("modifications")
|
||||
.asList()
|
||||
.hasSize(5)
|
||||
.extracting("path")
|
||||
.contains("a.txt", "b.txt", "c/d.txt", "c/e.txt", "f.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnEmptyModificationsForCreatedBranch() throws IOException {
|
||||
GitHookModificationsProvider provider = mockProviderWithChange(
|
||||
ReceiveCommand.Type.CREATE,
|
||||
"fcd0ef1831e4002ac43ea539f4094334c79ea9ec",
|
||||
"0000000000000000000000000000000000000000");
|
||||
|
||||
assertThat(provider.getModifications("rename"))
|
||||
.extracting("modifications")
|
||||
.asList()
|
||||
.hasSize(5)
|
||||
.extracting("path")
|
||||
.contains("a.txt", "b.txt", "c/d.txt", "c/e.txt", "f.txt");
|
||||
}
|
||||
|
||||
private GitHookModificationsProvider mockProviderWithChange(ReceiveCommand.Type update) throws IOException {
|
||||
return mockProviderWithChange(
|
||||
update,
|
||||
"383b954b27e052db6880d57f1c860dc208795247",
|
||||
"fcd0ef1831e4002ac43ea539f4094334c79ea9ec");
|
||||
}
|
||||
|
||||
private GitHookModificationsProvider mockProviderWithChange(ReceiveCommand.Type update, String newObjectId, String oldObjectId) throws IOException {
|
||||
ReceiveCommand receiveCommand = mock(ReceiveCommand.class);
|
||||
when(receiveCommand.getRefName()).thenReturn("refs/heads/rename");
|
||||
when(receiveCommand.getType()).thenReturn(update);
|
||||
when(receiveCommand.getNewId()).thenReturn(ObjectId.fromString(newObjectId));
|
||||
when(receiveCommand.getOldId()).thenReturn(ObjectId.fromString(oldObjectId));
|
||||
return new GitHookModificationsProvider(List.of(receiveCommand), createContext().open());
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ import sonia.scm.repository.PreReceiveRepositoryHookEvent;
|
||||
import sonia.scm.repository.api.BranchRequest;
|
||||
import sonia.scm.repository.api.HookChangesetBuilder;
|
||||
import sonia.scm.repository.api.HookContextFactory;
|
||||
import sonia.scm.repository.api.HookModificationsProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -164,6 +165,11 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase {
|
||||
PreReceiveRepositoryHookEvent event = (PreReceiveRepositoryHookEvent) events.get(0);
|
||||
assertThat(event.getContext().getBranchProvider().getCreatedOrModified()).containsExactly("new_branch");
|
||||
assertThat(event.getContext().getBranchProvider().getDeletedOrClosed()).isEmpty();
|
||||
HookModificationsProvider modificationsProvider = event.getContext().getModificationsProvider();
|
||||
assertThat(modificationsProvider.getModifications("new_branch"))
|
||||
.extracting("modifications")
|
||||
.asList()
|
||||
.hasSize(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -190,5 +196,10 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase {
|
||||
"f360a8738e4a29333786c5817f97a2c912814536",
|
||||
"d1dfecbfd5b4a2f77fe40e1bde29e640f7f944be"
|
||||
);
|
||||
HookModificationsProvider modificationsProvider = event.getContext().getModificationsProvider();
|
||||
assertThat(modificationsProvider.getModifications("squash"))
|
||||
.extracting("modifications")
|
||||
.asList()
|
||||
.hasSize(8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
|
||||
|
||||
command.getModifications(revision);
|
||||
|
||||
Mockito.verify(command.context, times(3)).open();
|
||||
Mockito.verify(command.context, times(2)).open();
|
||||
Mockito.verify(repository, never()).close();
|
||||
|
||||
}
|
||||
|
||||
@@ -21,35 +21,28 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.spi.HookChangesetProvider;
|
||||
import sonia.scm.repository.spi.HookChangesetRequest;
|
||||
import sonia.scm.repository.spi.javahg.AbstractChangesetCommand;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Mercurial hook branch provider implementation.
|
||||
*
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class HgHookBranchProvider implements HookBranchProvider
|
||||
{
|
||||
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(HgHookBranchProvider.class);
|
||||
|
||||
private static final HookChangesetRequest REQUEST =
|
||||
@@ -121,7 +114,7 @@ public class HgHookBranchProvider implements HookBranchProvider
|
||||
Builder<String> deletedOrClosedBuilder = ImmutableList.builder();
|
||||
|
||||
logger.trace("collecting branches from hook changesets");
|
||||
|
||||
|
||||
for (Changeset c : changesets())
|
||||
{
|
||||
if (c.getProperty(AbstractChangesetCommand.PROPERTY_CLOSE) != null)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -31,7 +31,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.Tag;
|
||||
import sonia.scm.repository.spi.HookChangesetProvider;
|
||||
import sonia.scm.repository.spi.HookChangesetRequest;
|
||||
import sonia.scm.repository.spi.HookChangesetResponse;
|
||||
|
||||
@@ -44,7 +43,7 @@ import sonia.scm.repository.spi.HookChangesetResponse;
|
||||
public class HgHookTagProvider implements HookTagProvider {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(HgHookTagProvider.class);
|
||||
|
||||
|
||||
private static final HookChangesetRequest REQUEST = new HookChangesetRequest();
|
||||
|
||||
private final HookChangesetProvider changesetProvider;
|
||||
@@ -77,16 +76,16 @@ public class HgHookTagProvider implements HookTagProvider {
|
||||
|
||||
private void collect() {
|
||||
ImmutableList.Builder<Tag> createdTagsBuilder = ImmutableList.builder();
|
||||
|
||||
|
||||
logger.trace("collecting tags from hook changesets");
|
||||
HookChangesetResponse response = changesetProvider.handleRequest(REQUEST);
|
||||
for ( Changeset c : response.getChangesets() ){
|
||||
appendTags(createdTagsBuilder, c);
|
||||
}
|
||||
|
||||
|
||||
createdTags = createdTagsBuilder.build();
|
||||
}
|
||||
|
||||
|
||||
private void appendTags(ImmutableList.Builder<Tag> tags, Changeset c){
|
||||
List<String> tagNames = c.getTags();
|
||||
if (tagNames != null){
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgConfigResolver;
|
||||
import sonia.scm.repository.HgRepositoryFactory;
|
||||
import sonia.scm.repository.api.HookChangesetProvider;
|
||||
import sonia.scm.repository.spi.javahg.HgLogChangesetCommand;
|
||||
import sonia.scm.web.HgUtil;
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import sonia.scm.repository.HgConfigResolver;
|
||||
import sonia.scm.repository.HgRepositoryFactory;
|
||||
import sonia.scm.repository.Repository;
|
||||
@@ -33,6 +31,7 @@ import sonia.scm.repository.api.HgHookBranchProvider;
|
||||
import sonia.scm.repository.api.HgHookMessageProvider;
|
||||
import sonia.scm.repository.api.HgHookTagProvider;
|
||||
import sonia.scm.repository.api.HookBranchProvider;
|
||||
import sonia.scm.repository.api.HookChangesetProvider;
|
||||
import sonia.scm.repository.api.HookFeature;
|
||||
import sonia.scm.repository.api.HookMessageProvider;
|
||||
import sonia.scm.repository.api.HookTagProvider;
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.Tag;
|
||||
import sonia.scm.repository.api.HookBranchProvider;
|
||||
import sonia.scm.repository.api.HookChangesetProvider;
|
||||
import sonia.scm.repository.api.HookFeature;
|
||||
import sonia.scm.repository.api.HookTagProvider;
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.mockito.Mockito;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.Tag;
|
||||
import sonia.scm.repository.spi.HookChangesetProvider;
|
||||
import sonia.scm.repository.spi.HookChangesetRequest;
|
||||
import sonia.scm.repository.spi.HookChangesetResponse;
|
||||
|
||||
|
||||
@@ -21,17 +21,13 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.RepositoryHookType;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
import sonia.scm.repository.api.HookChangesetProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryHookEvent;
|
||||
import sonia.scm.repository.SvnUtil;
|
||||
import sonia.scm.repository.api.HookChangesetProvider;
|
||||
import sonia.scm.repository.api.HookContext;
|
||||
import sonia.scm.repository.api.HookContextFactory;
|
||||
import sonia.scm.repository.api.HookFeature;
|
||||
|
||||
Reference in New Issue
Block a user