merge with develop

This commit is contained in:
Eduard Heimbuch
2020-08-10 11:39:18 +02:00
40 changed files with 1593 additions and 243 deletions

View File

@@ -5,9 +5,14 @@ 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))
- Anonymous mode for the web ui ([#1284](https://github.com/scm-manager/scm-manager/pull/1284))
### Fixed
- Repository names may not end with ".git" ([#1277](https://github.com/scm-manager/scm-manager/pull/1277))
## [2.3.1] - 2020-08-04
### Added
- New api to resolve SCM-Manager root url ([#1276](https://github.com/scm-manager/scm-manager/pull/1276))

View File

@@ -25,7 +25,7 @@ The location of the file depends also on the type of installation.
| Type of Installation | Path |
|----------------------|---------|
| Docker | /opt/scm-server/conf/logging.xml |
| Docker | /etc/scm/logging.xml |
| RPM | /etc/scm/logging.xml |
| DEB | /etc/scm/logging.xml |
| Unix | $EXTRACT_PATH/scm-server/conf/logging.xml |

View File

@@ -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
```

View File

@@ -31,6 +31,7 @@
- /development/definition-of-done/
- /development/ui-dod/
- /development/decision-table/
- /development/integration-tests/
- section: Plugin Development
entries:

View File

@@ -33,6 +33,7 @@ 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
@@ -43,8 +44,7 @@ import sonia.scm.repository.spi.HookContextProvider;
* @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;
@@ -83,13 +79,9 @@ public final class HookContext
*
* @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();
}
@@ -105,13 +97,9 @@ public final class HookContext
*
* @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();
}
@@ -126,21 +114,15 @@ public final class HookContext
* @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,
preProcessorUtil,
provider.getChangesetProvider()
);
//J+
}
/**
@@ -156,17 +138,29 @@ public final class HookContext
* @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.
*

View File

@@ -55,5 +55,12 @@ public enum HookFeature
*
* @since 1.50
*/
TAG_PROVIDER;
TAG_PROVIDER,
/**
* Provider to detect merges
*
* @since 2.4.0
*/
MERGE_DETECTION_PROVIDER
}

View File

@@ -118,6 +118,16 @@ 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 --------------------------------------------------------------
/**

View File

@@ -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 <code>branch</code> has been merged into <code>target</code>. So this will also return
* <code>true</code>, when <code>branch</code> 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 <code>true</code> when <code>branch</code> has been merged into <code>target</code>, <code>false</code>
* otherwise.
*/
boolean branchesMerged(String target, String branch);
}

View File

@@ -192,6 +192,7 @@
<version>2.10</version>
<configuration>
<artifactItems>
<!-- Copy war file -->
<artifactItem>
<groupId>sonia.scm</groupId>
<artifactId>scm-webapp</artifactId>
@@ -200,6 +201,15 @@
<outputDirectory>${project.build.outputDirectory}</outputDirectory>
<destFileName>scm-webapp.war</destFileName>
</artifactItem>
<!-- Copy integration test plugin -->
<artifactItem>
<groupId>sonia.scm.plugins</groupId>
<artifactId>scm-integration-test-plugin</artifactId>
<version>${project.version}</version>
<type>smp</type>
<outputDirectory>${scm.home}/plugins</outputDirectory>
<destFileName>scm-integration-test-plugin.smp</destFileName>
</artifactItem>
</artifactItems>
</configuration>
<executions>
@@ -273,7 +283,7 @@
<properties>
<scm.stage>DEVELOPMENT</scm.stage>
<scm.home>target/scm-it</scm.home>
<scm.home>${project.parent.build.directory}/scm-it</scm.home>
<scm-it.logbackConfiguration>${project.basedir}/../scm-webapp/src/main/resources/logback.default.xml</scm-it.logbackConfiguration>
</properties>

View File

@@ -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("'", "\"");
}
}

View File

@@ -45,6 +45,7 @@
<module>scm-git-plugin</module>
<module>scm-svn-plugin</module>
<module>scm-legacy-plugin</module>
<module>scm-integration-test-plugin</module>
</modules>
<dependencies>

View File

@@ -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<Ref> repositoryHeadRef = GitUtil.getRepositoryHeadRef(gitRepository);
return repositoryHeadRef.orElse(null);
} else {
return GitUtil.getBranchId(gitRepository, requestedBranch);
}
}
/**
* Method description
*

View File

@@ -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<ReceiveCommand> receiveCommands;
public GitReceiveHookMergeDetectionProvider(Repository repository, String repositoryId, List<ReceiveCommand> 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();
}
}
}

View File

@@ -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<Ref> repositoryHeadRef = GitUtil.getRepositoryHeadRef(gitRepository);
return repositoryHeadRef.orElse(null);
}
return getBranchIdOrCurrentHead(gitRepository, defaultBranchName);
} else {
return GitUtil.getBranchId(gitRepository, requestedBranch);
return getBranchIdOrCurrentHead(gitRepository, requestedBranch);
}
}

View File

@@ -26,22 +26,21 @@ 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<HookFeature> SUPPORTED_FEATURES =
EnumSet.of(HookFeature.MESSAGE_PROVIDER, HookFeature.CHANGESET_PROVIDER,
HookFeature.BRANCH_PROVIDER, HookFeature.TAG_PROVIDER);
/**
* Field description
*/
private static final Set<HookFeature> 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<ReceiveCommand> receiveCommands)
{
public GitHookContextProvider(
ReceivePack receivePack,
List<ReceiveCommand> 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<HookFeature> getSupportedFeatures()
{
return SUPPORTED_FEATURES;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final GitHookChangesetProvider changesetProvider;
/** Field description */
private final List<ReceiveCommand> receiveCommands;
/** Field description */
private final ReceivePack receivePack;
private final Repository repository;
private final String repositoryId;
}

View File

@@ -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<Changeset> 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<RevCommit> 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;
}
}

View File

@@ -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<Changeset> 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<RevCommit> 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);
}
}
}

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -49,7 +49,7 @@ public class GitRepositoryClientProvider extends RepositoryClientProvider
private static final Set<ClientCommand> 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();

View File

@@ -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" +

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>sonia.scm.plugins</groupId>
<artifactId>scm-plugins</artifactId>
<version>2.4.0-SNAPSHOT</version>
</parent>
<artifactId>scm-integration-test-plugin</artifactId>
<description>Add functions for integration tests. This is not intended for production systems.</description>
<version>2.4.0-SNAPSHOT</version>
<packaging>smp</packaging>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -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;
}
}

View File

@@ -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<ResultDto> preMergeDetections = new ArrayList<>();
private final List<ResultDto> 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<ResultDto> getPreMergeDetections() {
return preMergeDetections;
}
public List<ResultDto> getPostMergeDetections() {
return postMergeDetections;
}
@Getter
@AllArgsConstructor
static class ResultDto extends HalRepresentation {
private String type;
private String namespace;
private String name;
private boolean merged;
}
}

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
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.
-->
<!DOCTYPE plugin SYSTEM "https://download.scm-manager.org/dtd/plugin/2.0.0-01.dtd">
<plugin>
<scm-version>2</scm-version>
<information>
<displayName>Integration Test Support</displayName>
<author>Cloudogu GmbH</author>
<category>Test</category>
</information>
<conditions>
<min-version>${project.parent.version}</min-version>
</conditions>
</plugin>

View File

@@ -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;
}
}

View File

@@ -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
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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,6 +96,12 @@ 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();
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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
}
}

View File

@@ -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.
*

View File

@@ -37,7 +37,7 @@ describe("repository name validation", () => {
});
it("should allow same names as the backend", () => {
const validPaths = ["scm", "s", "sc", ".hiddenrepo", "b.", "...", "..c", "d..", "a..c"];
const validPaths = ["scm", "scm.gitz", "s", "sc", ".hiddenrepo", "b.", "...", "..c", "d..", "a..c"];
validPaths.forEach(path => expect(validator.isNameValid(path)).toBe(true));
});
@@ -91,7 +91,8 @@ describe("repository name validation", () => {
"a/..b",
"scm/main",
"scm/plugins/git-plugin",
"scm/plugins/git-plugin"
"scm/plugins/git-plugin",
"scm.git"
];
invalidPaths.forEach(path => expect(validator.isNameValid(path)).toBe(false));

View File

@@ -24,7 +24,7 @@
import { validation } from "@scm-manager/ui-components";
const nameRegex = /(?!^\.\.$)(?!^\.$)(?!.*[\\\[\]])^[A-Za-z0-9\.][A-Za-z0-9\.\-_]*$/;
const nameRegex = /(?!^\.\.$)(?!^\.$)(?!.*[.]git$)(?!.*[\\\[\]])^[A-Za-z0-9\.][A-Za-z0-9\.\-_]*$/;
export const isNameValid = (name: string) => {
return nameRegex.test(name);

View File

@@ -324,7 +324,7 @@
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.5.0</version>
<version>5.6.0</version>
</dependency>
<!-- test scope -->
@@ -675,7 +675,7 @@
<properties>
<scm.stage>DEVELOPMENT</scm.stage>
<scm.home>target/scm-it</scm.home>
<scm.home>${project.parent.build.directory}/scm-it</scm.home>
<environment.profile>default</environment.profile>
<jjwt.version>0.11.2</jjwt.version>
<selenium.version>2.53.1</selenium.version>
@@ -814,7 +814,7 @@
<systemProperties>
<systemProperty>
<name>scm.home</name>
<value>target/scm-it</value>
<value>${scm.home}</value>
</systemProperty>
<systemProperty>
<name>scm.stage</name>
@@ -903,7 +903,7 @@
<systemProperties>
<systemProperty>
<name>scm.home</name>
<value>target/scm-it</value>
<value>${scm.home}</value>
</systemProperty>
</systemProperties>
<jettyXml>${project.basedir}/src/main/conf/jetty.xml</jettyXml>