diff --git a/scm-it/pom.xml b/scm-it/pom.xml index 90c22cda75..cdfd14f3e3 100644 --- a/scm-it/pom.xml +++ b/scm-it/pom.xml @@ -192,6 +192,7 @@ 2.10 + sonia.scm scm-webapp @@ -200,6 +201,15 @@ ${project.build.outputDirectory} scm-webapp.war + + + sonia.scm.plugins + scm-integration-test-plugin + ${project.version} + smp + ${scm.home}/plugins + scm-integration-test-plugin.smp + diff --git a/scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java b/scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java new file mode 100644 index 0000000000..6779b76b68 --- /dev/null +++ b/scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java @@ -0,0 +1,176 @@ +/* + * 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.it; + +import io.restassured.RestAssured; +import org.assertj.core.api.Assertions; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import sonia.scm.it.utils.RepositoryUtil; +import sonia.scm.it.utils.RestUtil; +import sonia.scm.it.utils.TestData; +import sonia.scm.repository.Person; +import sonia.scm.repository.client.api.RepositoryClient; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static java.lang.String.format; +import static java.util.Collections.singletonList; +import static sonia.scm.it.utils.TestData.USER_SCM_ADMIN; + +public class MergeDetectionITCase { + + private static final Person ARTHUR = new Person("arthur", "arthur@hitchhiker.com"); + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + RepositoryClient client; + String masterFile; + String developFile; + + @Before + public void createRepository() throws IOException { + TestData.createDefault(); + + client = RepositoryUtil.createRepositoryClient("git", temporaryFolder.getRoot()); + + masterFile = createFile("hg2g.md"); + developFile = createFile("how_to_make_tea.md"); + + client.getAddCommand().add(masterFile); + client.getCommitCommand().commit(ARTHUR, "Add base file"); + client.getPushCommand().push(); + + client.getBranchCommand().branch("develop"); + client.getCheckoutCommand().checkout("develop"); + client.getAddCommand().add(developFile); + client.getCommitCommand().commit(ARTHUR, "add more important things"); + client.getPushCommand().push(); + } + + @After + public void disableMergeDetection() { + RestAssured.given() + .auth().preemptive().basic(USER_SCM_ADMIN, USER_SCM_ADMIN) + .when() + .contentType("application/json") + .accept("application/json") + .body(toJson("{}")) + .post(RestUtil.createResourceUrl("integration-test/merge-detection/")) + .then() + .statusCode(204); + } + + @Test + public void shouldDetectSimpleMergeAsMerged() throws IOException { + client.getCheckoutCommand().checkout("master"); + client.getMergeCommand().merge("develop"); + + initializeMergeDetection("master", "develop"); + + client.getPushCommand().push(); + + Assertions.assertThat(getMergeDetectionResult("preMergeDetection", 0)).isTrue(); + Assertions.assertThat(getMergeDetectionResult("postMergeDetection", 0)).isTrue(); + } + + @Test + public void shouldDetectMergeWhenBranchHasBeenDeletedAsMerged() throws IOException { + client.getCheckoutCommand().checkout("master"); + client.getMergeCommand().merge("develop"); + client.getPushCommand().push(); + + initializeMergeDetection("master", "develop"); + + client.getDeleteRemoteBranchCommand().delete("develop"); + client.getPushCommand().push(); + + Assertions.assertThat(getMergeDetectionResult("preMergeDetection", 0)).isTrue(); + Assertions.assertThat(getMergeDetectionResult("postMergeDetection", 0)).isTrue(); + } + + @Test + public void shouldDetectNormalPushAsNotMerged() throws IOException { + client.getCheckoutCommand().checkout("develop"); + writeFile(developFile, "other content"); + client.getAddCommand().add(developFile); + client.getCommitCommand().commit(ARTHUR, "simple commit"); + + initializeMergeDetection("master", "develop"); + + client.getPushCommand().push(); + + Assertions.assertThat(getMergeDetectionResult("preMergeDetection", 0)).isFalse(); + Assertions.assertThat(getMergeDetectionResult("postMergeDetection", 0)).isFalse(); + } + + private boolean getMergeDetectionResult(String type, int n) { + return RestAssured.given() + .auth().preemptive().basic(USER_SCM_ADMIN, USER_SCM_ADMIN) + .when() + .accept("application/json") + .get(RestUtil.createResourceUrl("integration-test/")) + .then() + .statusCode(200) + .extract() + .jsonPath() + .getBoolean("_embedded." + + type + + "[" + n + "].merged"); + } + + private void initializeMergeDetection(String target, String branch) { + RestAssured.given() + .auth().preemptive().basic(USER_SCM_ADMIN, USER_SCM_ADMIN) + .when() + .contentType("application/json") + .accept("application/json") + .body(toJson(format("{'target': '%s', 'branch': '%s'}", target, branch))) + .post(RestUtil.createResourceUrl("integration-test/merge-detection/")) + .then() + .statusCode(204); + } + + private String createFile(String name) throws IOException { + temporaryFolder.newFile(name); + writeFile(name, "Some content"); + return name; + } + + private void writeFile(String name, String content) throws IOException { + Path file = temporaryFolder.getRoot().toPath().resolve(name); + Files.write(file, singletonList(content)); + } + + private String toJson(String json) { + return json.replaceAll("'", "\""); + } +} diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index a407c38471..f4f927fe0e 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -45,6 +45,7 @@ scm-git-plugin scm-svn-plugin scm-legacy-plugin + scm-integration-test-plugin diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitCheckoutCommand.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitCheckoutCommand.java new file mode 100644 index 0000000000..1db0140041 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitCheckoutCommand.java @@ -0,0 +1,49 @@ +/* + * 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.client.spi; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import sonia.scm.repository.client.api.RepositoryClientException; + +import java.io.IOException; + +public class GitCheckoutCommand implements CheckoutCommand { + + private Git git; + + GitCheckoutCommand(Git git) { + this.git = git; + } + + @Override + public void checkout(String name) throws IOException { + try { + git.checkout().setName(name).call(); + } catch (GitAPIException ex) { + throw new RepositoryClientException("could not checkout branch or revision", ex); + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitDeleteRemoteBranchCommand.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitDeleteRemoteBranchCommand.java new file mode 100644 index 0000000000..e7a5b03c25 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitDeleteRemoteBranchCommand.java @@ -0,0 +1,64 @@ +/* + * 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.client.spi; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.PushCommand; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.RefSpec; +import sonia.scm.repository.client.api.RepositoryClientException; + +import java.io.IOException; + +public class GitDeleteRemoteBranchCommand implements DeleteRemoteBranchCommand { + + private final Git git; + private final CredentialsProvider credentialsProvider; + + GitDeleteRemoteBranchCommand(Git git, CredentialsProvider credentialsProvider) { + this.git = git; + this.credentialsProvider = credentialsProvider; + } + + @Override + public void delete(String name) throws IOException { + try { + git.branchDelete().setBranchNames("refs/heads/" + name).call(); + RefSpec refSpec = new RefSpec() + .setSource(null) + .setDestination("refs/heads/" + name); + PushCommand push = git.push(); + if (credentialsProvider != null) { + push.setCredentialsProvider(credentialsProvider); + } + push.setRefSpecs(refSpec).call(); + +// List result = git.branchDelete().setBranchNames(name).call(); + } catch (GitAPIException ex) { + throw new RepositoryClientException("could not delete branch", ex); + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitMergeCommand.java new file mode 100644 index 0000000000..6b27ee4a06 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitMergeCommand.java @@ -0,0 +1,64 @@ +/* + * 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.client.spi; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.MergeResult; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import sonia.scm.repository.Changeset; +import sonia.scm.repository.GitChangesetConverter; +import sonia.scm.repository.client.api.RepositoryClientException; + +import java.io.IOException; + +public class GitMergeCommand implements MergeCommand { + + private final Git git; + + GitMergeCommand(Git git) { + this.git = git; + } + + @Override + public Changeset merge(MergeRequest request) throws IOException { + try (GitChangesetConverter converter = new GitChangesetConverter(git.getRepository())) { + ObjectId resolved = git.getRepository().resolve(request.getBranch()); + MergeResult mergeResult = git.merge() + .include(request.getBranch(), resolved) + .setMessage(request.getMessage()) + .call(); + + try (RevWalk revWalk = new RevWalk(git.getRepository())) { + RevCommit commit = revWalk.parseCommit(mergeResult.getNewHead()); + return converter.createChangeset(commit); + } + } catch (GitAPIException ex) { + throw new RepositoryClientException("could not commit changes to repository", ex); + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitRepositoryClientProvider.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitRepositoryClientProvider.java index 665e4bc8eb..1cb11dbe2c 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitRepositoryClientProvider.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitRepositoryClientProvider.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.client.spi; //~--- non-JDK imports -------------------------------------------------------- @@ -49,7 +49,7 @@ public class GitRepositoryClientProvider extends RepositoryClientProvider private static final Set SUPPORTED_COMMANDS = ImmutableSet.of(ClientCommand.ADD, ClientCommand.REMOVE, ClientCommand.COMMIT, ClientCommand.TAG, ClientCommand.BRANCH, - ClientCommand.PUSH); + ClientCommand.DELETE_REMOTE_BRANCH, ClientCommand.MERGE, ClientCommand.PUSH); //~--- constructors --------------------------------------------------------- @@ -118,6 +118,16 @@ public class GitRepositoryClientProvider extends RepositoryClientProvider return new GitBranchCommand(git); } + @Override + public DeleteRemoteBranchCommand getDeleteRemoteBranchCommand() { + return new GitDeleteRemoteBranchCommand(git, credentialsProvider); + } + + @Override + public CheckoutCommand getCheckoutCommand() { + return new GitCheckoutCommand(git); + } + /** * Method description * @@ -178,6 +188,11 @@ public class GitRepositoryClientProvider extends RepositoryClientProvider return new GitTagCommand(git); } + @Override + public MergeCommand getMergeCommand() { + return new GitMergeCommand(git); + } + @Override public File getWorkingCopy() { return git.getRepository().getWorkTree(); diff --git a/scm-plugins/scm-integration-test-plugin/pom.xml b/scm-plugins/scm-integration-test-plugin/pom.xml new file mode 100644 index 0000000000..4728510608 --- /dev/null +++ b/scm-plugins/scm-integration-test-plugin/pom.xml @@ -0,0 +1,52 @@ + + + + 4.0.0 + + sonia.scm.plugins + scm-plugins + 2.4.0-SNAPSHOT + + + scm-integration-test-plugin + Add functions for integration tests. This is not intended for production systems. + 2.4.0-SNAPSHOT + smp + + + + + + + javax.servlet + javax.servlet-api + ${servlet.version} + provided + + + + diff --git a/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/IntegrationTestResource.java b/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/IntegrationTestResource.java new file mode 100644 index 0000000000..3d6e131ebd --- /dev/null +++ b/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/IntegrationTestResource.java @@ -0,0 +1,113 @@ +/* + * 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.it.resource; + +import de.otto.edison.hal.Embedded; +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.Getter; +import lombok.Setter; +import sonia.scm.api.v2.resources.LinkBuilder; +import sonia.scm.api.v2.resources.ScmPathInfoStore; +import sonia.scm.plugin.Extension; + +import javax.inject.Inject; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +import static de.otto.edison.hal.Embedded.embeddedBuilder; +import static de.otto.edison.hal.Links.linkingTo; + +/** + * Web Service Resource to support integration tests. + */ +@OpenAPIDefinition(tags = { + @Tag(name = "Integration Test", description = "Support for integration tests") +}) +@Path(IntegrationTestResource.INTEGRATION_TEST_PATH_V2) +@Extension +public class IntegrationTestResource { + + static final String INTEGRATION_TEST_PATH_V2 = "v2/integration-test"; + + private final ScmPathInfoStore scmPathInfoStore; + private final MergeDetectionHelper mergeDetectionHelper; + + @Inject + public IntegrationTestResource(ScmPathInfoStore scmPathInfoStore, MergeDetectionHelper mergeDetectionHelper) { + this.scmPathInfoStore = scmPathInfoStore; + this.mergeDetectionHelper = mergeDetectionHelper; + } + + @GET + @Path("") + @Produces("application/json") + public CollectionDto get() { + Links links = linkingTo() + .self(self()) + .build(); + Embedded embedded = embeddedBuilder() + .with("preMergeDetection", mergeDetectionHelper.getPreMergeDetections()) + .with("postMergeDetection", mergeDetectionHelper.getPostMergeDetections()) + .build(); + return new CollectionDto(links, embedded); + } + + @POST + @Path("merge-detection") + @Consumes("application/json") + public void initMergeDetection(MergeDetectionConfigurationDto mergeDetectionConfiguration) { + mergeDetectionHelper.initialize(mergeDetectionConfiguration.getTarget(), mergeDetectionConfiguration.getBranch()); + } + + private String self() { + LinkBuilder linkBuilder = new LinkBuilder(scmPathInfoStore.get(), IntegrationTestResource.class); + return linkBuilder.method("get").parameters().href(); + } + + static class CollectionDto extends HalRepresentation { + + CollectionDto(Links links, Embedded embedded) { + super(links, embedded); + } + + @Override + protected HalRepresentation withEmbedded(String rel, HalRepresentation embeddedItem) { + return super.withEmbedded(rel, embeddedItem); + } + } + + @Getter + @Setter + static class MergeDetectionConfigurationDto { + private String target; + private String branch; + } +} diff --git a/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/MergeDetectionHelper.java b/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/MergeDetectionHelper.java new file mode 100644 index 0000000000..10d9fbc31a --- /dev/null +++ b/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/MergeDetectionHelper.java @@ -0,0 +1,101 @@ +/* + * 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.it.resource; + +import com.github.legman.Subscribe; +import de.otto.edison.hal.HalRepresentation; +import lombok.AllArgsConstructor; +import lombok.Getter; +import sonia.scm.EagerSingleton; +import sonia.scm.plugin.Extension; +import sonia.scm.repository.PostReceiveRepositoryHookEvent; +import sonia.scm.repository.PreReceiveRepositoryHookEvent; +import sonia.scm.repository.RepositoryHookEvent; +import sonia.scm.repository.spi.HookMergeDetectionProvider; + +import java.util.ArrayList; +import java.util.List; + +@EagerSingleton +@Extension +public class MergeDetectionHelper { + + private final List preMergeDetections = new ArrayList<>(); + private final List postMergeDetections = new ArrayList<>(); + + private String target; + private String branch; + + @Subscribe + public void handlePreReceiveEvent(PreReceiveRepositoryHookEvent event) { + if (target == null || branch == null) { + return; + } + preMergeDetections.add(createDto(event)); + } + + @Subscribe + public void handlePostReceiveEvent(PostReceiveRepositoryHookEvent event) { + if (target == null || branch == null) { + return; + } + postMergeDetections.add(createDto(event)); + } + + public ResultDto createDto(RepositoryHookEvent event) { + HookMergeDetectionProvider mergeDetectionProvider = event.getContext().getMergeDetectionProvider(); + boolean merged = mergeDetectionProvider.branchesMerged(target, branch); + return new ResultDto( + event.getClass().getSimpleName(), + event.getRepository().getNamespace(), + event.getRepository().getName(), + merged + ); + } + + void initialize(String target, String branch) { + this.target = target; + this.branch = branch; + preMergeDetections.clear(); + postMergeDetections.clear(); + } + + public List getPreMergeDetections() { + return preMergeDetections; + } + + public List getPostMergeDetections() { + return postMergeDetections; + } + + @Getter + @AllArgsConstructor + static class ResultDto extends HalRepresentation { + private String type; + private String namespace; + private String name; + private boolean merged; + } +} diff --git a/scm-plugins/scm-integration-test-plugin/src/main/resources/META-INF/scm/plugin.xml b/scm-plugins/scm-integration-test-plugin/src/main/resources/META-INF/scm/plugin.xml new file mode 100644 index 0000000000..f2f70ba9ff --- /dev/null +++ b/scm-plugins/scm-integration-test-plugin/src/main/resources/META-INF/scm/plugin.xml @@ -0,0 +1,42 @@ + + + + + + 2 + + + Integration Test Support + Cloudogu GmbH + Test + + + + ${project.parent.version} + + + diff --git a/scm-test/src/main/java/sonia/scm/repository/client/api/CheckoutCommandBuilder.java b/scm-test/src/main/java/sonia/scm/repository/client/api/CheckoutCommandBuilder.java new file mode 100644 index 0000000000..9fab6f40c9 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/repository/client/api/CheckoutCommandBuilder.java @@ -0,0 +1,53 @@ +/* + * 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.client.api; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.repository.client.spi.CheckoutCommand; + +import java.io.IOException; + +/** + * @since 2.4.0 + */ +public final class CheckoutCommandBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(CheckoutCommandBuilder.class); + + private final CheckoutCommand command; + + public CheckoutCommandBuilder(CheckoutCommand command) { + this.command = command; + } + + public CheckoutCommandBuilder checkout(String name) throws IOException { + LOG.debug("checkout {}", name); + + command.checkout(name); + + return this; + } +} diff --git a/scm-test/src/main/java/sonia/scm/repository/client/api/ClientCommand.java b/scm-test/src/main/java/sonia/scm/repository/client/api/ClientCommand.java index bc022a284e..37e1cce24a 100644 --- a/scm-test/src/main/java/sonia/scm/repository/client/api/ClientCommand.java +++ b/scm-test/src/main/java/sonia/scm/repository/client/api/ClientCommand.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.client.api; /** @@ -31,5 +31,5 @@ package sonia.scm.repository.client.api; */ public enum ClientCommand { - ADD, REMOVE, COMMIT, PUSH, TAG, BRANCH + ADD, REMOVE, COMMIT, PUSH, TAG, BRANCH, DELETE_REMOTE_BRANCH, CHECKOUT, MERGE } diff --git a/scm-test/src/main/java/sonia/scm/repository/client/api/DeleteRemoteBranchCommandBuilder.java b/scm-test/src/main/java/sonia/scm/repository/client/api/DeleteRemoteBranchCommandBuilder.java new file mode 100644 index 0000000000..cc20321b15 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/repository/client/api/DeleteRemoteBranchCommandBuilder.java @@ -0,0 +1,53 @@ +/* + * 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.client.api; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.repository.client.spi.DeleteRemoteBranchCommand; + +import java.io.IOException; + +/** + * @since 2.4.0 + */ +public final class DeleteRemoteBranchCommandBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(DeleteRemoteBranchCommandBuilder.class); + + private DeleteRemoteBranchCommand command; + + public DeleteRemoteBranchCommandBuilder(DeleteRemoteBranchCommand command) { + this.command = command; + } + + public DeleteRemoteBranchCommandBuilder delete(String name) throws IOException { + LOG.debug("delete branch {}", name); + + command.delete(name); + + return this; + } +} diff --git a/scm-test/src/main/java/sonia/scm/repository/client/api/MergeCommandBuilder.java b/scm-test/src/main/java/sonia/scm/repository/client/api/MergeCommandBuilder.java new file mode 100644 index 0000000000..78cbe240d1 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/repository/client/api/MergeCommandBuilder.java @@ -0,0 +1,48 @@ +/* + * 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.client.api; + +import sonia.scm.repository.client.spi.MergeCommand; +import sonia.scm.repository.client.spi.MergeRequest; + +import java.io.IOException; + +/** + * @since 2.4.0 + */ +public final class MergeCommandBuilder { + + private final MergeCommand command; + + MergeCommandBuilder(MergeCommand command) { + this.command = command; + } + + public void merge(String branch) throws IOException { + MergeRequest request = new MergeRequest(); + request.setBranch(branch); + command.merge(request); + } +} diff --git a/scm-test/src/main/java/sonia/scm/repository/client/api/RepositoryClient.java b/scm-test/src/main/java/sonia/scm/repository/client/api/RepositoryClient.java index 2dbb01d667..660cdb160b 100644 --- a/scm-test/src/main/java/sonia/scm/repository/client/api/RepositoryClient.java +++ b/scm-test/src/main/java/sonia/scm/repository/client/api/RepositoryClient.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.client.api; import org.slf4j.Logger; @@ -60,6 +60,18 @@ public final class RepositoryClient implements Closeable { return new BranchCommandBuilder(clientProvider.getBranchCommand()); } + public DeleteRemoteBranchCommandBuilder getDeleteRemoteBranchCommand() { + logger.trace("delete branch command"); + + return new DeleteRemoteBranchCommandBuilder(clientProvider.getDeleteRemoteBranchCommand()); + } + + public CheckoutCommandBuilder getCheckoutCommand() { + logger.trace("create checkout command"); + + return new CheckoutCommandBuilder(clientProvider.getCheckoutCommand()); + } + public CommitCommandBuilder getCommitCommand() { logger.trace("create commit command"); @@ -84,10 +96,16 @@ public final class RepositoryClient implements Closeable { return new TagCommandBuilder(clientProvider.getTagCommand()); } + public MergeCommandBuilder getMergeCommand() { + logger.trace("create merge command"); + + return new MergeCommandBuilder(clientProvider.getMergeCommand()); + } + public File getWorkingCopy() { return clientProvider.getWorkingCopy(); } - + public boolean isCommandSupported(ClientCommand command) { return clientProvider.getSupportedClientCommands().contains(command); } diff --git a/scm-test/src/main/java/sonia/scm/repository/client/spi/CheckoutCommand.java b/scm-test/src/main/java/sonia/scm/repository/client/spi/CheckoutCommand.java new file mode 100644 index 0000000000..262ca3894c --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/repository/client/spi/CheckoutCommand.java @@ -0,0 +1,35 @@ +/* + * 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.client.spi; + +import java.io.IOException; + +/** + * @since 2.4.0 + */ +public interface CheckoutCommand { + + void checkout(String name) throws IOException; +} diff --git a/scm-test/src/main/java/sonia/scm/repository/client/spi/DeleteRemoteBranchCommand.java b/scm-test/src/main/java/sonia/scm/repository/client/spi/DeleteRemoteBranchCommand.java new file mode 100644 index 0000000000..5dea57f965 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/repository/client/spi/DeleteRemoteBranchCommand.java @@ -0,0 +1,32 @@ +/* + * 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.client.spi; + +import java.io.IOException; + +public interface DeleteRemoteBranchCommand { + + void delete(String name) throws IOException; +} diff --git a/scm-test/src/main/java/sonia/scm/repository/client/spi/MergeCommand.java b/scm-test/src/main/java/sonia/scm/repository/client/spi/MergeCommand.java new file mode 100644 index 0000000000..a80031b655 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/repository/client/spi/MergeCommand.java @@ -0,0 +1,37 @@ +/* + * 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.client.spi; + +import sonia.scm.repository.Changeset; + +import java.io.IOException; + +/** + * @since 2.4.0 + */ +public interface MergeCommand { + + Changeset merge(MergeRequest request) throws IOException; +} diff --git a/scm-test/src/main/java/sonia/scm/repository/client/spi/MergeRequest.java b/scm-test/src/main/java/sonia/scm/repository/client/spi/MergeRequest.java new file mode 100644 index 0000000000..ba7a133cb9 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/repository/client/spi/MergeRequest.java @@ -0,0 +1,89 @@ +/* + * 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.client.spi; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; + +/** + * @since 2.4.0 + */ +public final class MergeRequest { + + private String branch; + private String message; + + @Override + public boolean equals(Object obj) { + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + final MergeRequest other = (MergeRequest) obj; + + return Objects.equal(branch, other.branch) + && Objects.equal(message, other.message); + } + + @Override + public int hashCode() { + return Objects.hashCode(branch, message); + } + + public void reset() { + this.branch = null; + this.message = null; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("branch", branch) + .add("message", message) + .toString(); + } + + public void setBranch(String branch) { + this.branch = branch; + } + + public void setMessage(String message) { + this.message = message; + } + + String getBranch() { + return branch; + } + + String getMessage() { + return message; + } +} diff --git a/scm-test/src/main/java/sonia/scm/repository/client/spi/RepositoryClientProvider.java b/scm-test/src/main/java/sonia/scm/repository/client/spi/RepositoryClientProvider.java index feb1d3dd09..bfe4c5afe8 100644 --- a/scm-test/src/main/java/sonia/scm/repository/client/spi/RepositoryClientProvider.java +++ b/scm-test/src/main/java/sonia/scm/repository/client/spi/RepositoryClientProvider.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.client.spi; //~--- non-JDK imports -------------------------------------------------------- @@ -87,6 +87,14 @@ public abstract class RepositoryClientProvider implements Closeable throw new ClientCommandNotSupportedException(ClientCommand.BRANCH); } + public DeleteRemoteBranchCommand getDeleteRemoteBranchCommand() { + throw new ClientCommandNotSupportedException(ClientCommand.DELETE_REMOTE_BRANCH); + } + + public CheckoutCommand getCheckoutCommand() { + throw new ClientCommandNotSupportedException(ClientCommand.CHECKOUT); + } + /** * Method description * @@ -131,6 +139,10 @@ public abstract class RepositoryClientProvider implements Closeable throw new ClientCommandNotSupportedException(ClientCommand.TAG); } + public MergeCommand getMergeCommand() { + throw new ClientCommandNotSupportedException(ClientCommand.MERGE); + } + /** * Returns the working copy of the repository client. *