From 571e6ad92fe0b7b26a61d5ee01df2385bf06b7b2 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 16 Nov 2023 13:18:40 +0100 Subject: [PATCH] Add a modifications provider for hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Pushed-by: Alexander Dammeier Co-authored-by: René Pfeuffer Committed-by: René Pfeuffer --- gradle/changelog/hook_provider.yaml | 2 + .../repository/api/HookChangesetBuilder.java | 1 - .../{spi => api}/HookChangesetProvider.java | 5 +- .../sonia/scm/repository/api/HookContext.java | 31 +++- .../sonia/scm/repository/api/HookFeature.java | 9 +- .../api/HookModificationsProvider.java | 25 ++- .../repository/spi/HookContextProvider.java | 12 +- .../scm/repository/api/HookContextTest.java | 31 ++-- .../api/GitHookModificationsProvider.java | 131 +++++++++++++ .../spi/BranchBasedModificationsComputer.java | 63 +++++++ .../java/sonia/scm/repository/spi/Differ.java | 4 +- .../scm/repository/spi/GitBranchCommand.java | 128 +++++++++---- .../scm/repository/spi/GitDiffResult.java | 175 ++++++++++++++++++ .../repository/spi/GitDiffResultCommand.java | 147 +-------------- .../spi/GitHookChangesetProvider.java | 1 + .../spi/GitHookContextProvider.java | 9 + .../spi/GitImportHookContextProvider.java | 1 + .../spi/GitModificationsCommand.java | 102 +--------- .../scm/repository/spi/GitTagCommand.java | 1 + .../repository/spi/ModificationsComputer.java | 143 ++++++++++++++ .../api/GitHookBranchProviderTest.java | 26 +-- .../api/GitHookModificationsProviderTest.java | 116 ++++++++++++ .../repository/spi/GitBranchCommandTest.java | 11 ++ .../spi/GitModificationsCommandTest.java | 2 +- .../repository/api/HgHookBranchProvider.java | 19 +- .../scm/repository/api/HgHookTagProvider.java | 11 +- .../spi/HgHookChangesetProvider.java | 1 + .../repository/spi/HgHookContextProvider.java | 3 +- .../spi/HgImportHookContextProvider.java | 1 + .../repository/api/HgHookTagProviderTest.java | 1 - .../spi/AbstractSvnHookChangesetProvider.java | 8 +- .../repository/spi/SvnUnbundleCommand.java | 1 + 32 files changed, 857 insertions(+), 364 deletions(-) create mode 100644 gradle/changelog/hook_provider.yaml rename scm-core/src/main/java/sonia/scm/repository/{spi => api}/HookChangesetProvider.java (90%) rename scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/UnsupportedModificationTypeException.java => scm-core/src/main/java/sonia/scm/repository/api/HookModificationsProvider.java (61%) create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookModificationsProvider.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/BranchBasedModificationsComputer.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResult.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/ModificationsComputer.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookModificationsProviderTest.java diff --git a/gradle/changelog/hook_provider.yaml b/gradle/changelog/hook_provider.yaml new file mode 100644 index 0000000000..4c2fb0849c --- /dev/null +++ b/gradle/changelog/hook_provider.yaml @@ -0,0 +1,2 @@ +- type: added + description: Modifications in hook provider diff --git a/scm-core/src/main/java/sonia/scm/repository/api/HookChangesetBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/HookChangesetBuilder.java index f3b2a4a19b..e8aec2f958 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/HookChangesetBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/HookChangesetBuilder.java @@ -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; diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/HookChangesetProvider.java b/scm-core/src/main/java/sonia/scm/repository/api/HookChangesetProvider.java similarity index 90% rename from scm-core/src/main/java/sonia/scm/repository/spi/HookChangesetProvider.java rename to scm-core/src/main/java/sonia/scm/repository/api/HookChangesetProvider.java index 8dc70797a5..d271d43609 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/HookChangesetProvider.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/HookChangesetProvider.java @@ -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 diff --git a/scm-core/src/main/java/sonia/scm/repository/api/HookContext.java b/scm-core/src/main/java/sonia/scm/repository/api/HookContext.java index 588fc342d2..43915f3e6d 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/HookContext.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/HookContext.java @@ -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(); } diff --git a/scm-core/src/main/java/sonia/scm/repository/api/HookFeature.java b/scm-core/src/main/java/sonia/scm/repository/api/HookFeature.java index da664b5772..fe3abbfbe5 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/HookFeature.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/HookFeature.java @@ -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 } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/UnsupportedModificationTypeException.java b/scm-core/src/main/java/sonia/scm/repository/api/HookModificationsProvider.java similarity index 61% rename from scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/UnsupportedModificationTypeException.java rename to scm-core/src/main/java/sonia/scm/repository/api/HookModificationsProvider.java index 7f0df84c40..dd1b966a11 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/UnsupportedModificationTypeException.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/HookModificationsProvider.java @@ -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); } diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/HookContextProvider.java b/scm-core/src/main/java/sonia/scm/repository/spi/HookContextProvider.java index f9af110ea5..b4e6db826e 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/HookContextProvider.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/HookContextProvider.java @@ -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 -------------------------------------------------------------- /** diff --git a/scm-core/src/test/java/sonia/scm/repository/api/HookContextTest.java b/scm-core/src/test/java/sonia/scm/repository/api/HookContextTest.java index ff9901c856..b3ddd95279 100644 --- a/scm-core/src/test/java/sonia/scm/repository/api/HookContextTest.java +++ b/scm-core/src/test/java/sonia/scm/repository/api/HookContextTest.java @@ -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 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)); } - + } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookModificationsProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookModificationsProvider.java new file mode 100644 index 0000000000..c1aa79d745 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookModificationsProvider.java @@ -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 modificationsCommandRequests; + + public GitHookModificationsProvider(List commands, org.eclipse.jgit.lib.Repository repository) { + this.repository = repository; + ImmutableMap.Builder 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 modificationFactory) throws IOException { + return new BranchBasedModificationsComputer(repository).createModifications(revision, modificationFactory); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/BranchBasedModificationsComputer.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/BranchBasedModificationsComputer.java new file mode 100644 index 0000000000..f752e8f300 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/BranchBasedModificationsComputer.java @@ -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 modificationFactory) throws IOException { + Collection 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); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/Differ.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/Differ.java index 7a5a301b79..ca0274f86b 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/Differ.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/Differ.java @@ -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); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java index 67934cf37d..77754e4a3f 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java @@ -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 newBranches; - private final List deletedBranches; - private final ObjectId objectId; + private abstract class BranchHookContextProvider extends HookContextProvider { + final String branchName; + final ObjectId objectId; - private BranchHookContextProvider(List newBranches, List deletedBranches, ObjectId objectId) { - this.newBranches = newBranches; - this.deletedBranches = deletedBranches; + private BranchHookContextProvider(String branchName, ObjectId objectId) { + this.branchName = branchName; this.objectId = objectId; } @Override public Set 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 getCreatedOrModified() { - return newBranches; - } - - @Override - public List 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 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 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 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 getCreatedOrModified() { + return asList(branchName); + } + + @Override + public List getDeletedOrClosed() { + return emptyList(); + } + }; + } + + @Override + ReceiveCommand createReceiveCommand() { + return new ReceiveCommand(zeroId(), objectId, "refs/heads/" + branchName); + } + + @Override + Function 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 getCreatedOrModified() { + return emptyList(); + } + + @Override + public List getDeletedOrClosed() { + return asList(branchName); + } + }; + } + + @Override + ReceiveCommand createReceiveCommand() { + return new ReceiveCommand(objectId, zeroId(), "refs/heads/" + branchName); + } + + @Override + Function getModificationFactory() { + return Removed::new; + } } public interface Factory { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResult.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResult.java new file mode 100644 index 0000000000..a7cd48f680 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResult.java @@ -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 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 getLimit() { + return ofNullable(limit); + } + + @Override + public Iterator iterator() { + Stream 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 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); + } + } + + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java index 29ae06a205..e0ca5411d6 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java @@ -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 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 getLimit() { - return ofNullable(limit); - } - - @Override - public Iterator iterator() { - Stream 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 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); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookChangesetProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookChangesetProvider.java index fc56b66c3c..fba8cd2cab 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookChangesetProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookChangesetProvider.java @@ -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; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java index 1b03a87406..00f640d29f 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java @@ -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 getSupportedFeatures() { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitImportHookContextProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitImportHookContextProvider.java index c5cf350e5f..49742f3c8c 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitImportHookContextProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitImportHookContextProvider.java @@ -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; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModificationsCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModificationsCommand.java index f8b9f8b87d..cb9a41a8c6 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModificationsCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModificationsCommand.java @@ -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 createModifications(TreeWalk treeWalk, RevCommit commit) - throws IOException { - treeWalk.addTree(commit.getTree()); - List entries = Differ.scanWithRename(context.open(), null, treeWalk); - Collection 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); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagCommand.java index 1c4c1a191f..9af85d07fb 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagCommand.java @@ -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; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/ModificationsComputer.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/ModificationsComputer.java new file mode 100644 index 0000000000..b8c19541b1 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/ModificationsComputer.java @@ -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 createModifications(TreeWalk treeWalk, RevCommit commit) + throws IOException { + treeWalk.addTree(commit.getTree()); + List entries = Differ.scanWithRename(gitRepository, null, treeWalk); + Collection 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)); + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookBranchProviderTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookBranchProviderTest.java index db77f05e61..ad7a4bf7cc 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookBranchProviderTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookBranchProviderTest.java @@ -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 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 types = Arrays.asList( - ReceiveCommand.Type.CREATE, ReceiveCommand.Type.UPDATE, ReceiveCommand.Type.UPDATE_NONFASTFORWARD + List 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); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookModificationsProviderTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookModificationsProviderTest.java new file mode 100644 index 0000000000..d91bcc7627 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookModificationsProviderTest.java @@ -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()); + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java index 9a61a461e3..0fbc1a4113 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java @@ -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); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModificationsCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModificationsCommandTest.java index 06d777e1f9..059d053e0c 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModificationsCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModificationsCommandTest.java @@ -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(); } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookBranchProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookBranchProvider.java index b2fc618754..a0705922b8 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookBranchProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookBranchProvider.java @@ -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 deletedOrClosedBuilder = ImmutableList.builder(); logger.trace("collecting branches from hook changesets"); - + for (Changeset c : changesets()) { if (c.getProperty(AbstractChangesetCommand.PROPERTY_CLOSE) != null) diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookTagProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookTagProvider.java index 7b8ecef67d..2fc48140be 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookTagProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookTagProvider.java @@ -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 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 tags, Changeset c){ List tagNames = c.getTags(); if (tagNames != null){ diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java index b403b79e23..6570b34863 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java @@ -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; diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java index eb38d9c792..5119b0dd2e 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java @@ -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; diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgImportHookContextProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgImportHookContextProvider.java index 432aeecb8c..ea8df15d13 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgImportHookContextProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgImportHookContextProvider.java @@ -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; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/api/HgHookTagProviderTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/api/HgHookTagProviderTest.java index b756b4097d..2429aff18f 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/api/HgHookTagProviderTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/api/HgHookTagProviderTest.java @@ -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; diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/AbstractSvnHookChangesetProvider.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/AbstractSvnHookChangesetProvider.java index c03a99e3ac..e82d9d33e4 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/AbstractSvnHookChangesetProvider.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/AbstractSvnHookChangesetProvider.java @@ -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; diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnUnbundleCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnUnbundleCommand.java index 6b4d5d7265..196b922c01 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnUnbundleCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnUnbundleCommand.java @@ -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;