diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7d52e869d6..410d0f533e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
+
+### Added
+- Introduced merge detection for receive hooks ([#1278](https://github.com/scm-manager/scm-manager/pull/1278))
+
### Fixed
- Repository names may not end with ".git" ([#1277](https://github.com/scm-manager/scm-manager/pull/1277))
diff --git a/docs/en/development/integration-tests.md b/docs/en/development/integration-tests.md
new file mode 100644
index 0000000000..4b9589acfe
--- /dev/null
+++ b/docs/en/development/integration-tests.md
@@ -0,0 +1,24 @@
+---
+title: Integration Tests
+subtitle: How to run integration tests
+displayToc: false
+---
+
+You can find the integration tests in the module **scm-it** (and a few still in **scm-webapp**). To run them,
+simply start maven with the profile `it`:
+
+```
+mvn install -Pit -DskipUnitTests -pl :scm-webapp,:scm-it
+```
+
+This will start a jetty server and execute all integration tests in the maven phase `integration-test` using the
+normal failsafe plugin. Integration tests are all classes ending with `ITCase` and are written using JUnit.
+
+To develop integration tests, you should start a local server with the **scm-integration-test-plugin**. This plugin is
+used as a way to introspect server internals. For example you can register event listeners here and access their
+triggers with a REST endpoint. Of course, this plugin is not and should not be installed in productive systems.
+You can start the server with this plugin using the following maven call:
+
+```
+mvn run -pl :scm-integration-test-plugin
+```
diff --git a/docs/en/navigation.yml b/docs/en/navigation.yml
index 9c30b03156..3d5b6518a8 100644
--- a/docs/en/navigation.yml
+++ b/docs/en/navigation.yml
@@ -31,6 +31,7 @@
- /development/definition-of-done/
- /development/ui-dod/
- /development/decision-table/
+ - /development/integration-tests/
- section: Plugin Development
entries:
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 ff49542222..588fc342d2 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
@@ -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;
//~--- non-JDK imports --------------------------------------------------------
@@ -33,18 +33,18 @@ import sonia.scm.repository.Changeset;
import sonia.scm.repository.PreProcessorUtil;
import sonia.scm.repository.Repository;
import sonia.scm.repository.spi.HookContextProvider;
+import sonia.scm.repository.spi.HookMergeDetectionProvider;
/**
* The context for all repository hooks. With the {@link HookContext} class it
* is able to send messages back to the client, retrieve {@link Changeset}s
- * which are added during this push/commit and gives informations about changed
+ * which are added during this push/commit and gives informations about changed
* branches and tags.
*
* @author Sebastian Sdorra
* @since 1.33
*/
-public final class HookContext
-{
+public final class HookContext {
/**
* the logger for HookContext
@@ -52,8 +52,6 @@ public final class HookContext
private static final Logger logger =
LoggerFactory.getLogger(HookContext.class);
- //~--- constructors ---------------------------------------------------------
-
/**
* Constructs ...
*
@@ -62,9 +60,7 @@ public final class HookContext
* @param repository
* @param preProcessorUtil
*/
- HookContext(HookContextProvider provider, Repository repository,
- PreProcessorUtil preProcessorUtil)
- {
+ HookContext(HookContextProvider provider, Repository repository, PreProcessorUtil preProcessorUtil) {
this.provider = provider;
this.repository = repository;
this.preProcessorUtil = preProcessorUtil;
@@ -77,41 +73,33 @@ public final class HookContext
* about changed branches during the current hook.
*
* @return {@link HookBranchProvider}
- *
- * @throws HookFeatureIsNotSupportedException if the feature is not supported
+ *
+ * @throws HookFeatureIsNotSupportedException if the feature is not supported
* by the underlying provider
- *
+ *
* @since 1.45
*/
- public HookBranchProvider getBranchProvider()
- {
- if (logger.isDebugEnabled())
- {
- logger.debug("create branch provider for repository {}",
- repository.getName());
- }
+ public HookBranchProvider getBranchProvider() {
+ logger.debug("create branch provider for repository {}",
+ repository.getName());
return provider.getBranchProvider();
}
-
+
/**
* Returns a {@link HookTagProvider} which is able to return informations
* about changed tags during the current hook.
*
* @return {@link HookTagProvider}
- *
- * @throws HookFeatureIsNotSupportedException if the feature is not supported
+ *
+ * @throws HookFeatureIsNotSupportedException if the feature is not supported
* by the underlying provider
- *
+ *
* @since 1.50
*/
- public HookTagProvider getTagProvider()
- {
- if (logger.isDebugEnabled())
- {
- logger.debug("create tag provider for repository {}",
- repository.getName());
- }
+ public HookTagProvider getTagProvider() {
+ logger.debug("create tag provider for repository {}",
+ repository.getName());
return provider.getTagProvider();
}
@@ -122,25 +110,19 @@ public final class HookContext
*
*
* @return {@link HookChangesetBuilder}
- *
- * @throws HookFeatureIsNotSupportedException if the feature is not supported
+ *
+ * @throws HookFeatureIsNotSupportedException if the feature is not supported
* by the underlying provider
*/
- public HookChangesetBuilder getChangesetProvider()
- {
- if (logger.isDebugEnabled())
- {
- logger.debug("create changeset provider for repository {}",
- repository.getName());
- }
+ public HookChangesetBuilder getChangesetProvider() {
+ logger.debug("create changeset provider for repository {}",
+ repository.getName());
- //J-
return new HookChangesetBuilder(
- repository,
+ repository,
preProcessorUtil,
provider.getChangesetProvider()
);
- //J+
}
/**
@@ -152,21 +134,33 @@ public final class HookContext
*
* @return {@link HookMessageProvider} which is able to send message back to
* the scm client
- *
- * @throws HookFeatureIsNotSupportedException if the feature is not supported
+ *
+ * @throws HookFeatureIsNotSupportedException if the feature is not supported
* by the underlying provider
*/
- public HookMessageProvider getMessageProvider()
- {
- if (logger.isDebugEnabled())
- {
- logger.debug("create message provider for repository {}",
- repository.getName());
- }
+ public HookMessageProvider getMessageProvider() {
+ logger.debug("create message provider for repository {}",
+ repository.getName());
return provider.getMessageProvider();
}
+ /**
+ * Returns a {@link HookMergeDetectionProvider} which is able to check whether two
+ * branches have been merged with the incoming changesets.
+ *
+ * @return {@link HookMergeDetectionProvider} which is able to detect merges.
+ *
+ * @throws HookFeatureIsNotSupportedException if the feature is not supported
+ * by the underlying provider
+ */
+ public HookMergeDetectionProvider getMergeDetectionProvider() {
+ logger.debug("create merge detection provider for repository {}",
+ repository.getName());
+
+ return provider.getMergeDetectionProvider();
+ }
+
/**
* Returns true if the underlying provider support the requested feature.
*
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 0f0aaf0832..da664b5772 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
@@ -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;
/**
@@ -49,11 +49,18 @@ public enum HookFeature
* @since 1.45
*/
BRANCH_PROVIDER,
-
+
/**
* Hook tag provider
- *
+ *
* @since 1.50
*/
- TAG_PROVIDER;
+ TAG_PROVIDER,
+
+ /**
+ * Provider to detect merges
+ *
+ * @since 2.4.0
+ */
+ MERGE_DETECTION_PROVIDER
}
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 ddb8cb9a9b..f9af110ea5 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
@@ -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.spi;
//~--- non-JDK imports --------------------------------------------------------
@@ -50,7 +50,7 @@ public abstract class HookContextProvider
/**
* Return the provider specific {@link HookMessageProvider} or throws a {@link HookFeatureIsNotSupportedException}.
* The method will throw a {@link HookException} if the client is already disconnected.
- *
+ *
* @return provider specific {@link HookMessageProvider}
*/
public final HookMessageProvider getMessageProvider()
@@ -86,31 +86,31 @@ public abstract class HookContextProvider
/**
* Return the provider specific {@link HookBranchProvider} or throws a {@link HookFeatureIsNotSupportedException}.
- *
+ *
* @return provider specific {@link HookBranchProvider}
- *
+ *
* @since 1.45
*/
public HookBranchProvider getBranchProvider()
{
throw new HookFeatureIsNotSupportedException(HookFeature.BRANCH_PROVIDER);
}
-
+
/**
* Return the provider specific {@link HookTagProvider} or throws a {@link HookFeatureIsNotSupportedException}.
- *
+ *
* @return provider specific {@link HookTagProvider}
- *
+ *
* @since 1.50
*/
- public HookTagProvider getTagProvider()
+ public HookTagProvider getTagProvider()
{
throw new HookFeatureIsNotSupportedException(HookFeature.TAG_PROVIDER);
}
/**
* Return the provider specific {@link HookChangesetProvider} or throws a {@link HookFeatureIsNotSupportedException}.
- *
+ *
* @return provider specific {@link HookChangesetProvider}
*/
public HookChangesetProvider getChangesetProvider()
@@ -118,11 +118,21 @@ public abstract class HookContextProvider
throw new HookFeatureIsNotSupportedException(HookFeature.CHANGESET_PROVIDER);
}
+ /**
+ * Return the provider specific {@link HookMergeDetectionProvider} or throws a {@link HookFeatureIsNotSupportedException}.
+ *
+ * @return provider specific {@link HookMergeDetectionProvider}
+ */
+ public HookMergeDetectionProvider getMergeDetectionProvider()
+ {
+ throw new HookFeatureIsNotSupportedException(HookFeature.MERGE_DETECTION_PROVIDER);
+ }
+
//~--- methods --------------------------------------------------------------
/**
* Creates a new provider specific {@link HookMessageProvider} or throws a {@link HookFeatureIsNotSupportedException}.
- *
+ *
* @return provider specific {@link HookChangesetProvider}
*/
protected HookMessageProvider createMessageProvider()
diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/HookMergeDetectionProvider.java b/scm-core/src/main/java/sonia/scm/repository/spi/HookMergeDetectionProvider.java
new file mode 100644
index 0000000000..3deacd22c3
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/repository/spi/HookMergeDetectionProvider.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+/**
+ * @since 2.4.0
+ */
+public interface HookMergeDetectionProvider {
+
+ /**
+ * Checks whether branch has been merged into target. So this will also return
+ * true, when branch has been deleted with this change.
+ *
+ * @param target The name of the branch to check, whether the other branch has been merged into.
+ * @param branch The name of the branch to check, whether it has been merged into the other branch.
+ * @return true when branch has been merged into target, false
+ * otherwise.
+ */
+ boolean branchesMerged(String target, String branch);
+}
diff --git a/scm-it/pom.xml b/scm-it/pom.xml
index 90c22cda75..41ab2ab3d2 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
+
@@ -273,7 +283,7 @@
DEVELOPMENT
- target/scm-it
+ ${project.parent.build.directory}/scm-it
${project.basedir}/../scm-webapp/src/main/resources/logback.default.xml
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..f0115e8c77
--- /dev/null
+++ b/scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java
@@ -0,0 +1,185 @@
+/*
+ * 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.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+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;
+
+class MergeDetectionITCase {
+
+ private static final Person ARTHUR = new Person("arthur", "arthur@hitchhiker.com");
+
+ RepositoryClient client;
+ String masterFile;
+ String developFile;
+
+ @BeforeEach
+ void createRepository(@TempDir Path tempDir) throws IOException {
+ TestData.createDefault();
+
+ client = RepositoryUtil.createRepositoryClient("git", tempDir.toFile());
+
+ masterFile = createFile(tempDir, "hg2g.md");
+ developFile = createFile(tempDir, "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();
+ }
+
+ @AfterEach
+ 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
+ void shouldDetectSimpleMergeAsMerged() throws IOException {
+ client.getCheckoutCommand().checkout("master");
+ client.getMergeCommand().noFf().merge("develop");
+
+ initializeMergeDetection("master", "develop");
+
+ client.getPushCommand().push();
+
+ Assertions.assertThat(getMergeDetectionResult("preMergeDetection", 0)).isTrue();
+ Assertions.assertThat(getMergeDetectionResult("postMergeDetection", 0)).isTrue();
+ }
+
+ @Test
+ void shouldDetectFastForwardAsMerged() 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
+ 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
+ void shouldDetectNormalPushAsNotMerged(@TempDir Path tempDir) throws IOException {
+ client.getCheckoutCommand().checkout("develop");
+ writeFile(tempDir, 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(Path tempDir, String name) throws IOException {
+ Files.createFile(tempDir.resolve(name));
+ writeFile(tempDir, name, "Some content");
+ return name;
+ }
+
+ private void writeFile(Path tempDir, String name, String content) throws IOException {
+ Path file = tempDir.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/main/java/sonia/scm/repository/GitUtil.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java
index dee72ce345..b55aec06bd 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.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;
//~--- non-JDK imports --------------------------------------------------------
@@ -337,6 +337,16 @@ public final class GitUtil
return Strings.nullToEmpty(refName).startsWith(PREFIX_HEADS);
}
+ public static Ref getBranchIdOrCurrentHead(org.eclipse.jgit.lib.Repository gitRepository, String requestedBranch) throws IOException {
+ if ( Strings.isNullOrEmpty(requestedBranch) ) {
+ logger.trace("no default branch configured, use repository head as default");
+ Optional[ repositoryHeadRef = GitUtil.getRepositoryHeadRef(gitRepository);
+ return repositoryHeadRef.orElse(null);
+ } else {
+ return GitUtil.getBranchId(gitRepository, requestedBranch);
+ }
+ }
+
/**
* Method description
*
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitReceiveHookMergeDetectionProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitReceiveHookMergeDetectionProvider.java
new file mode 100644
index 0000000000..03a5b59837
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitReceiveHookMergeDetectionProvider.java
@@ -0,0 +1,81 @@
+/*
+ * 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.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import sonia.scm.repository.GitUtil;
+import sonia.scm.repository.spi.GitLogComputer;
+import sonia.scm.repository.spi.HookMergeDetectionProvider;
+import sonia.scm.repository.spi.LogCommandRequest;
+
+import java.util.List;
+
+public class GitReceiveHookMergeDetectionProvider implements HookMergeDetectionProvider {
+ private final Repository repository;
+ private final String repositoryId;
+ private final List receiveCommands;
+
+ public GitReceiveHookMergeDetectionProvider(Repository repository, String repositoryId, List receiveCommands) {
+ this.repository = repository;
+ this.repositoryId = repositoryId;
+ this.receiveCommands = receiveCommands;
+ }
+
+ @Override
+ public boolean branchesMerged(String target, String branch) {
+ LogCommandRequest request = new LogCommandRequest();
+ request.setBranch(findRelevantRevisionForBranchIfToBeUpdated(branch));
+ request.setAncestorChangeset(findRelevantRevisionForBranchIfToBeUpdated(target));
+ request.setPagingLimit(1);
+
+ return new GitLogComputer(repositoryId, repository).compute(request).getTotal() == 0;
+ }
+
+ private String findRelevantRevisionForBranchIfToBeUpdated(String branch) {
+ return receiveCommands
+ .stream()
+ .filter(receiveCommand -> isReceiveCommandForBranch(branch, receiveCommand))
+ .map(this::getRelevantRevision)
+ .map(AnyObjectId::getName)
+ .findFirst()
+ .orElse(branch);
+ }
+
+ private boolean isReceiveCommandForBranch(String branch, ReceiveCommand receiveCommand) {
+ return receiveCommand.getType() != ReceiveCommand.Type.CREATE
+ && GitUtil.getBranch(receiveCommand.getRef()).equals(branch);
+ }
+
+ private ObjectId getRelevantRevision(ReceiveCommand receiveCommand) {
+ if (receiveCommand.getType() == ReceiveCommand.Type.DELETE) {
+ return receiveCommand.getOldId();
+ } else {
+ return receiveCommand.getNewId();
+ }
+ }
+}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java
index d52d0a1574..9e13e82971 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java
@@ -41,7 +41,6 @@ import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import sonia.scm.repository.GitUtil;
import sonia.scm.repository.GitWorkingCopyFactory;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.Person;
@@ -59,6 +58,7 @@ import static java.util.Optional.empty;
import static java.util.Optional.of;
import static sonia.scm.ContextEntry.ContextBuilder.entity;
import static sonia.scm.NotFoundException.notFound;
+import static sonia.scm.repository.GitUtil.getBranchIdOrCurrentHead;
//~--- JDK imports ------------------------------------------------------------
@@ -123,15 +123,9 @@ class AbstractGitCommand
Ref getBranchOrDefault(Repository gitRepository, String requestedBranch) throws IOException {
if ( Strings.isNullOrEmpty(requestedBranch) ) {
String defaultBranchName = context.getConfig().getDefaultBranch();
- if (!Strings.isNullOrEmpty(defaultBranchName)) {
- return GitUtil.getBranchId(gitRepository, defaultBranchName);
- } else {
- logger.trace("no default branch configured, use repository head as default");
- Optional][ repositoryHeadRef = GitUtil.getRepositoryHeadRef(gitRepository);
- return repositoryHeadRef.orElse(null);
- }
+ return getBranchIdOrCurrentHead(gitRepository, defaultBranchName);
} else {
- return GitUtil.getBranchId(gitRepository, requestedBranch);
+ return getBranchIdOrCurrentHead(gitRepository, requestedBranch);
}
}
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 8e642a18ce..bc8d633add 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
@@ -21,27 +21,26 @@
* 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 org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.transport.ReceivePack;
-
import sonia.scm.repository.api.GitHookBranchProvider;
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.HookFeature;
import sonia.scm.repository.api.HookMessageProvider;
-
-//~--- JDK imports ------------------------------------------------------------
+import sonia.scm.repository.api.HookTagProvider;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
-import sonia.scm.repository.api.GitHookTagProvider;
-import sonia.scm.repository.api.HookTagProvider;
/**
*
@@ -50,24 +49,34 @@ import sonia.scm.repository.api.HookTagProvider;
public class GitHookContextProvider extends HookContextProvider
{
- /** Field description */
- private static final Set SUPPORTED_FEATURES =
- EnumSet.of(HookFeature.MESSAGE_PROVIDER, HookFeature.CHANGESET_PROVIDER,
- HookFeature.BRANCH_PROVIDER, HookFeature.TAG_PROVIDER);
+ /**
+ * Field description
+ */
+ private static final Set SUPPORTED_FEATURES = EnumSet.of(
+ HookFeature.MESSAGE_PROVIDER,
+ HookFeature.CHANGESET_PROVIDER,
+ HookFeature.BRANCH_PROVIDER,
+ HookFeature.TAG_PROVIDER,
+ HookFeature.MERGE_DETECTION_PROVIDER
+ );
//~--- constructors ---------------------------------------------------------
/**
* Constructs a new instance
- *
* @param receivePack git receive pack
* @param receiveCommands received commands
*/
- public GitHookContextProvider(ReceivePack receivePack,
- List receiveCommands)
- {
+ public GitHookContextProvider(
+ ReceivePack receivePack,
+ List receiveCommands,
+ Repository repository,
+ String repositoryId
+ ) {
this.receivePack = receivePack;
this.receiveCommands = receiveCommands;
+ this.repository = repository;
+ this.repositoryId = repositoryId;
this.changesetProvider = new GitHookChangesetProvider(receivePack,
receiveCommands);
}
@@ -99,20 +108,20 @@ public class GitHookContextProvider extends HookContextProvider
return changesetProvider;
}
+ @Override
+ public HookMergeDetectionProvider getMergeDetectionProvider() {
+ return new GitReceiveHookMergeDetectionProvider(repository, repositoryId, receiveCommands);
+ }
+
@Override
public Set getSupportedFeatures()
{
return SUPPORTED_FEATURES;
}
- //~--- fields ---------------------------------------------------------------
-
- /** Field description */
private final GitHookChangesetProvider changesetProvider;
-
- /** Field description */
private final List receiveCommands;
-
- /** Field description */
private final ReceivePack receivePack;
+ private final Repository repository;
+ private final String repositoryId;
}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java
index da00ba451b..340df65c16 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java
@@ -27,19 +27,12 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
-import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
-import org.eclipse.jgit.treewalk.filter.PathFilter;
-import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import sonia.scm.NotFoundException;
import sonia.scm.repository.Changeset;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.GitChangesetConverter;
@@ -48,9 +41,6 @@ import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.util.IOUtil;
import java.io.IOException;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
import static sonia.scm.ContextEntry.ContextBuilder.entity;
import static sonia.scm.NotFoundException.notFound;
@@ -183,123 +173,14 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand
@Override
@SuppressWarnings("unchecked")
public ChangesetPagingResult getChangesets(LogCommandRequest request) {
- if (logger.isDebugEnabled()) {
- logger.debug("fetch changesets for request: {}", request);
- }
-
- ChangesetPagingResult changesets = null;
- GitChangesetConverter converter = null;
- RevWalk revWalk = null;
-
- try (org.eclipse.jgit.lib.Repository repository = open()) {
- if (!repository.getAllRefs().isEmpty()) {
- int counter = 0;
- int start = request.getPagingStart();
-
- if (start < 0) {
- if (logger.isErrorEnabled()) {
- logger.error("start parameter is negative, reset to 0");
- }
-
- start = 0;
- }
-
- List changesetList = Lists.newArrayList();
- int limit = request.getPagingLimit();
- ObjectId startId = null;
-
- if (!Strings.isNullOrEmpty(request.getStartChangeset())) {
- startId = repository.resolve(request.getStartChangeset());
- }
-
- ObjectId endId = null;
-
- if (!Strings.isNullOrEmpty(request.getEndChangeset())) {
- endId = repository.resolve(request.getEndChangeset());
- }
-
- Ref branch = getBranchOrDefault(repository,request.getBranch());
-
- ObjectId ancestorId = null;
-
- if (!Strings.isNullOrEmpty(request.getAncestorChangeset())) {
- ancestorId = repository.resolve(request.getAncestorChangeset());
- if (ancestorId == null) {
- throw notFound(entity(REVISION, request.getAncestorChangeset()).in(this.repository));
- }
- }
-
- revWalk = new RevWalk(repository);
-
- converter = new GitChangesetConverter(repository, revWalk);
-
- if (!Strings.isNullOrEmpty(request.getPath())) {
- revWalk.setTreeFilter(
- AndTreeFilter.create(
- PathFilter.create(request.getPath()), TreeFilter.ANY_DIFF));
- }
-
- if (branch != null) {
- if (startId != null) {
- revWalk.markStart(revWalk.lookupCommit(startId));
- } else {
- revWalk.markStart(revWalk.lookupCommit(branch.getObjectId()));
- }
-
- if (ancestorId != null) {
- revWalk.markUninteresting(revWalk.lookupCommit(ancestorId));
- }
-
- Iterator iterator = revWalk.iterator();
-
- while (iterator.hasNext()) {
- RevCommit commit = iterator.next();
-
- if ((counter >= start)
- && ((limit < 0) || (counter < start + limit))) {
- changesetList.add(converter.createChangeset(commit));
- }
-
- counter++;
-
- if (commit.getId().equals(endId)) {
- break;
- }
- }
- } else if (ancestorId != null) {
- throw notFound(entity(REVISION, request.getBranch()).in(this.repository));
- }
-
- if (branch != null) {
- changesets = new ChangesetPagingResult(counter, changesetList, GitUtil.getBranch(branch.getName()));
- } else {
- changesets = new ChangesetPagingResult(counter, changesetList);
- }
- } else if (logger.isWarnEnabled()) {
- logger.warn("the repository {} seems to be empty",
- this.repository.getName());
-
- changesets = new ChangesetPagingResult(0, Collections.EMPTY_LIST);
+ try (org.eclipse.jgit.lib.Repository gitRepository = open()) {
+ if (Strings.isNullOrEmpty(request.getBranch())) {
+ request.setBranch(context.getConfig().getDefaultBranch());
}
+ return new GitLogComputer(this.repository.getId(), gitRepository).compute(request);
+ } catch (IOException e) {
+ throw new InternalRepositoryException(repository, "could not create change log", e);
}
- catch (MissingObjectException e)
- {
- throw notFound(entity(REVISION, e.getObjectId().getName()).in(repository));
- }
- catch (NotFoundException e)
- {
- throw e;
- }
- catch (Exception ex)
- {
- throw new InternalRepositoryException(repository, "could not create change log", ex);
- }
- finally
- {
- IOUtil.close(converter);
- GitUtil.release(revWalk);
- }
-
- return changesets;
}
+
}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogComputer.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogComputer.java
new file mode 100644
index 0000000000..eae8ea5851
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogComputer.java
@@ -0,0 +1,186 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020-present Cloudogu GmbH and Contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package sonia.scm.repository.spi;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import org.eclipse.jgit.errors.InvalidObjectIdException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sonia.scm.NotFoundException;
+import sonia.scm.repository.Changeset;
+import sonia.scm.repository.ChangesetPagingResult;
+import sonia.scm.repository.GitChangesetConverter;
+import sonia.scm.repository.GitUtil;
+import sonia.scm.repository.InternalRepositoryException;
+import sonia.scm.util.IOUtil;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import static sonia.scm.ContextEntry.ContextBuilder.entity;
+import static sonia.scm.NotFoundException.notFound;
+
+public class GitLogComputer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(GitLogComputer.class);
+
+ private final String repositoryId;
+ private final Repository gitRepository;
+
+ public GitLogComputer(String repositoryId, Repository repository) {
+ this.repositoryId = repositoryId;
+ this.gitRepository = repository;
+ }
+
+ public ChangesetPagingResult compute(LogCommandRequest request) {
+ LOG.debug("fetch changesets for request: {}", request);
+
+ GitChangesetConverter converter = null;
+ RevWalk revWalk = null;
+
+ try {
+ if (!gitRepository.getAllRefs().isEmpty()) {
+ int counter = 0;
+ int start = request.getPagingStart();
+
+ if (start < 0) {
+ LOG.error("start parameter is negative, reset to 0");
+
+ start = 0;
+ }
+
+ List changesetList = Lists.newArrayList();
+ int limit = request.getPagingLimit();
+ ObjectId startId = null;
+
+ if (!Strings.isNullOrEmpty(request.getStartChangeset())) {
+ startId = gitRepository.resolve(request.getStartChangeset());
+ }
+
+ ObjectId endId = null;
+
+ if (!Strings.isNullOrEmpty(request.getEndChangeset())) {
+ endId = gitRepository.resolve(request.getEndChangeset());
+ }
+
+ Ref branch = GitUtil.getBranchIdOrCurrentHead(gitRepository, request.getBranch());
+ ObjectId branchId;
+ if (branch == null) {
+ if (request.getBranch() != null) {
+ try {
+ branchId = ObjectId.fromString(request.getBranch());
+ } catch (InvalidObjectIdException e) {
+ throw notFound(entity(GitLogCommand.REVISION, request.getBranch()).in(sonia.scm.repository.Repository.class, repositoryId));
+ }
+ } else {
+ branchId = null;
+ }
+ } else {
+ branchId = branch.getObjectId();
+ }
+
+ ObjectId ancestorId = null;
+
+ if (!Strings.isNullOrEmpty(request.getAncestorChangeset())) {
+ ancestorId = gitRepository.resolve(request.getAncestorChangeset());
+ if (ancestorId == null) {
+ throw notFound(entity(GitLogCommand.REVISION, request.getAncestorChangeset()).in(sonia.scm.repository.Repository.class, repositoryId));
+ }
+ }
+
+ revWalk = new RevWalk(gitRepository);
+
+ converter = new GitChangesetConverter(gitRepository, revWalk);
+
+ if (!Strings.isNullOrEmpty(request.getPath())) {
+ revWalk.setTreeFilter(
+ AndTreeFilter.create(
+ PathFilter.create(request.getPath()), TreeFilter.ANY_DIFF));
+ }
+
+ if (branchId != null) {
+ if (startId != null) {
+ revWalk.markStart(revWalk.lookupCommit(startId));
+ } else {
+ revWalk.markStart(revWalk.lookupCommit(branchId));
+ }
+
+ if (ancestorId != null) {
+ revWalk.markUninteresting(revWalk.lookupCommit(ancestorId));
+ }
+
+ Iterator iterator = revWalk.iterator();
+
+ while (iterator.hasNext()) {
+ RevCommit commit = iterator.next();
+
+ if ((counter >= start)
+ && ((limit < 0) || (counter < start + limit))) {
+ changesetList.add(converter.createChangeset(commit));
+ }
+
+ counter++;
+
+ if (commit.getId().equals(endId)) {
+ break;
+ }
+ }
+ } else if (ancestorId != null) {
+ throw notFound(entity(GitLogCommand.REVISION, request.getBranch()).in(sonia.scm.repository.Repository.class, repositoryId));
+ }
+
+ if (branch != null) {
+ return new ChangesetPagingResult(counter, changesetList, GitUtil.getBranch(branch.getName()));
+ } else {
+ return new ChangesetPagingResult(counter, changesetList);
+ }
+ } else {
+ LOG.debug("the repository with id {} seems to be empty", this.repositoryId);
+
+ return new ChangesetPagingResult(0, Collections.emptyList());
+ }
+ } catch (MissingObjectException e) {
+ throw notFound(entity(GitLogCommand.REVISION, e.getObjectId().getName()).in(sonia.scm.repository.Repository.class, repositoryId));
+ } catch (NotFoundException e) {
+ throw e;
+ } catch (Exception ex) {
+ throw new InternalRepositoryException(entity(sonia.scm.repository.Repository.class, repositoryId).build(), "could not create change log", ex);
+ } finally {
+ IOUtil.close(converter);
+ GitUtil.release(revWalk);
+ }
+ }
+}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java
index c7b323224c..a9d74e0357 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.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.web;
//~--- non-JDK imports --------------------------------------------------------
@@ -122,8 +122,7 @@ public class GitReceiveHook implements PreReceiveHook, PostReceiveHook
logger.trace("resolved repository to {}", repositoryId);
- GitHookContextProvider context = new GitHookContextProvider(rpack,
- receiveCommands);
+ GitHookContextProvider context = new GitHookContextProvider(rpack, receiveCommands, repository, repositoryId);
hookEventFacade.handle(repositoryId).fireHookEvent(type, context);
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..69dc53a958
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitDeleteRemoteBranchCommand.java
@@ -0,0 +1,62 @@
+/*
+ * 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();
+ } 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..464d32eba6
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitMergeCommand.java
@@ -0,0 +1,80 @@
+/*
+ * 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());
+ org.eclipse.jgit.api.MergeCommand mergeCommand = git.merge()
+ .include(request.getBranch(), resolved)
+ .setMessage(request.getMessage());
+
+ switch (request.getFfMode()) {
+ case FF:
+ mergeCommand.setFastForward(org.eclipse.jgit.api.MergeCommand.FastForwardMode.FF);
+ break;
+ case NO_FF:
+ mergeCommand.setFastForward(org.eclipse.jgit.api.MergeCommand.FastForwardMode.NO_FF);
+ break;
+ case FF_ONLY:
+ mergeCommand.setFastForward(org.eclipse.jgit.api.MergeCommand.FastForwardMode.FF_ONLY);
+ break;
+ default:
+ throw new IllegalStateException("Unknown FF mode: " + request.getFfMode());
+ }
+
+ MergeResult mergeResult = mergeCommand
+ .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-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommand_DequoteOutputStreamTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommand_DequoteOutputStreamTest.java
index fb4cda8ca0..3e3f87131e 100644
--- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommand_DequoteOutputStreamTest.java
+++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommand_DequoteOutputStreamTest.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.spi;
import org.assertj.core.api.Assertions;
@@ -48,7 +48,7 @@ public class GitDiffCommand_DequoteOutputStreamTest {
stream.write(bytes, 0, bytes.length);
stream.flush();
- Assertions.assertThat(buffer.toString()).isEqualTo("diff --git a/file úüþëéåëåé a b/file úüþëéåëåé b\n" +
+ Assertions.assertThat(buffer.toString("UTF-8")).isEqualTo("diff --git a/file úüþëéåëåé a b/file úüþëéåëåé b\n" +
"new file mode 100644\n" +
"index 0000000..8cb0607\n" +
"--- /dev/null\n" +
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..2c69c5fbb2
--- /dev/null
+++ b/scm-plugins/scm-integration-test-plugin/pom.xml
@@ -0,0 +1,50 @@
+
+
+
+ 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..f6b606e001
--- /dev/null
+++ b/scm-plugins/scm-integration-test-plugin/src/main/java/sonia/scm/it/resource/IntegrationTestResource.java
@@ -0,0 +1,106 @@
+/*
+ * 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 lombok.Getter;
+import lombok.Setter;
+import sonia.scm.api.v2.resources.LinkBuilder;
+import sonia.scm.api.v2.resources.ScmPathInfoStore;
+
+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.
+ */
+@Path(IntegrationTestResource.INTEGRATION_TEST_PATH_V2)
+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..ade806ade0
--- /dev/null
+++ b/scm-test/src/main/java/sonia/scm/repository/client/api/MergeCommandBuilder.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.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;
+ private final MergeRequest request = new MergeRequest();
+
+ MergeCommandBuilder(MergeCommand command) {
+ this.command = command;
+ }
+
+ public MergeCommandBuilder ffOnly() {
+ request.setFfMode(MergeRequest.FastForwardMode.FF_ONLY);
+ return this;
+ }
+
+ public MergeCommandBuilder noFf() {
+ request.setFfMode(MergeRequest.FastForwardMode.NO_FF);
+ return this;
+ }
+
+ public MergeCommandBuilder ffIfPossible() {
+ request.setFfMode(MergeRequest.FastForwardMode.FF);
+ return this;
+ }
+
+ public void merge(String branch) throws IOException {
+ 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..826e5d9f03
--- /dev/null
+++ b/scm-test/src/main/java/sonia/scm/repository/client/spi/MergeRequest.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+ private FastForwardMode ffMode = FastForwardMode.FF;
+
+ @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;
+ }
+
+ public void setFfMode(FastForwardMode ffMode) {
+ this.ffMode = ffMode;
+ }
+
+ String getBranch() {
+ return branch;
+ }
+
+ String getMessage() {
+ return message;
+ }
+
+ public FastForwardMode getFfMode() {
+ return ffMode;
+ }
+
+ public enum FastForwardMode {
+ FF_ONLY, FF, NO_FF
+ }
+}
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.
*
diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml
index 2174f91c2e..31d18327f0 100644
--- a/scm-webapp/pom.xml
+++ b/scm-webapp/pom.xml
@@ -675,7 +675,7 @@
DEVELOPMENT
- target/scm-it
+ ${project.parent.build.directory}/scm-it
default
0.11.2
2.53.1
@@ -814,7 +814,7 @@
scm.home
- target/scm-it
+ ${scm.home}
scm.stage
@@ -903,7 +903,7 @@
scm.home
- target/scm-it
+ ${scm.home}
${project.basedir}/src/main/conf/jetty.xml
]