mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-11 07:55:47 +01:00
Merge branch 'develop' into feature/source_jump
# Conflicts: # CHANGELOG.md
This commit is contained in:
18
CHANGELOG.md
18
CHANGELOG.md
@@ -5,10 +5,27 @@ 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))
|
||||
- Add link to source file in diff sections ([#1267](https://github.com/scm-manager/scm-manager/pull/1267))
|
||||
|
||||
### 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))
|
||||
|
||||
### Changed
|
||||
- Help tooltips are now mutliline by default ([#1271](https://github.com/scm-manager/scm-manager/pull/1271))
|
||||
|
||||
### Fixed
|
||||
- Fixed unnecessary horizontal scrollbar in modal dialogs ([#1271](https://github.com/scm-manager/scm-manager/pull/1271))
|
||||
- Avoid stacktrace logging when protocol url is accessed outside of request scope ([#1276](https://github.com/scm-manager/scm-manager/pull/1276))
|
||||
|
||||
## [2.3.0] - 2020-07-23
|
||||
|
||||
### Added
|
||||
- Add branch link provider to access branch links in plugins ([#1243](https://github.com/scm-manager/scm-manager/pull/1243))
|
||||
- Add key value input field component ([#1246](https://github.com/scm-manager/scm-manager/pull/1246))
|
||||
@@ -248,3 +265,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
[2.1.1]: https://www.scm-manager.org/download/2.1.1
|
||||
[2.2.0]: https://www.scm-manager.org/download/2.2.0
|
||||
[2.3.0]: https://www.scm-manager.org/download/2.3.0
|
||||
[2.3.1]: https://www.scm-manager.org/download/2.3.1
|
||||
|
||||
@@ -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 |
|
||||
|
||||
24
docs/en/development/integration-tests.md
Normal file
24
docs/en/development/integration-tests.md
Normal 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
|
||||
```
|
||||
@@ -40,3 +40,7 @@ After changing the configuration, SCM-Manager must be restarted.
|
||||
### How do I install plugins?
|
||||
|
||||
Find the plugin you like to install at [plugins](/plugins#categories) and follow the installation instructions on the install page of the plugin.
|
||||
|
||||
### How can I import my existing (git|mercurial|subversion) repository
|
||||
|
||||
Please have a look on [these](../import/) detailed instructions.
|
||||
|
||||
55
docs/en/import.md
Normal file
55
docs/en/import.md
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: Import existing repositories
|
||||
subtitle: How to import existing repositories into SCM-Manager
|
||||
displayToc: true
|
||||
---
|
||||
|
||||
## Git
|
||||
|
||||
First you have to clone the old repository with the `mirror` option.
|
||||
This option ensures that all branches and tags are fetched from the remote repository.
|
||||
Assuming that your remote repository is accessible under the url `https://hgttg.com/r/git/heart-of-gold`, the clone command should look like this:
|
||||
|
||||
```bash
|
||||
git clone --mirror https://hgttg.com/r/git/heart-of-gold
|
||||
```
|
||||
|
||||
Than you have to create your new repository via the SCM-Manager web interface and copy the url.
|
||||
In this example we assume that the new repository is available at `https://hitchhiker.com/scm/repo/hgttg/heart-of-gold`. After the new repository is created, we can configure our local repository for the new location and push all refs.
|
||||
|
||||
```bash
|
||||
cd heart-of-gold
|
||||
git remote set-url origin https://hitchhiker.com/scm/repo/hgttg/heart-of-gold
|
||||
git push --mirror
|
||||
```
|
||||
|
||||
## Mercurial
|
||||
|
||||
To import an existing mercurial repository, we have to create a new repository over the SCM-Manager web interface, clone it, pull from the old repository and push to the new repository.
|
||||
In this example we assume that the old repository is `https://hgttg.com/r/hg/heart-of-gold` and the newly created is located at `https://hitchhiker.com/scm/repo/hgttg/heart-of-gold`:
|
||||
|
||||
```bash
|
||||
hg clone https://hitchhiker.com/scm/repo/hgttg/heart-of-gold
|
||||
cd heart-of-gold
|
||||
hg pull https://hgttg.com/r/hg/heart-of-gold
|
||||
hg push
|
||||
```
|
||||
|
||||
## Subversion
|
||||
|
||||
Subversion is not as easy as mercurial or git.
|
||||
For subversion we have to locate the old repository on the filesystem and create a dump with the `svnadmin` tool.
|
||||
|
||||
```bash
|
||||
svnadmin dump /path/to/repo > oldrepo.dump
|
||||
```
|
||||
|
||||
Now we have to create a new repository via the SCM-Manager web interface.
|
||||
After the repository is created, we have to find its location on the filesystem.
|
||||
This could be done by finding the directory with the newest timestamp in your scm home directory under `repositories`.
|
||||
You can check whether you have found the correct directory by having a look at the file `metadata.xml`. Here you should find the namespace and the name of the repository created.
|
||||
Now its time to import the dump from the old repository:
|
||||
|
||||
```bash
|
||||
svnadmin load /path/to/scm-home/repositories/id/data < oldrepo.dump
|
||||
```
|
||||
@@ -2,6 +2,7 @@
|
||||
entries:
|
||||
- /installation/
|
||||
- /migrate-scm-manager-from-v1/
|
||||
- /import/
|
||||
- /faq/
|
||||
- /known-issues/
|
||||
|
||||
@@ -30,6 +31,7 @@
|
||||
- /development/definition-of-done/
|
||||
- /development/ui-dod/
|
||||
- /development/decision-table/
|
||||
- /development/integration-tests/
|
||||
|
||||
- section: Plugin Development
|
||||
entries:
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "2.3.0"
|
||||
"version": "2.4.0-SNAPSHOT"
|
||||
}
|
||||
|
||||
14
package.json
14
package.json
@@ -14,9 +14,12 @@
|
||||
"deploy": "ui-scripts publish",
|
||||
"set-version": "ui-scripts version"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"babel-plugin-reflow": "^0.2.7",
|
||||
"lerna": "^3.17.0"
|
||||
"husky": "^4.2.5",
|
||||
"lerna": "^3.17.0",
|
||||
"lint-staged": "^10.2.11"
|
||||
},
|
||||
"resolutions": {
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
@@ -32,5 +35,12 @@
|
||||
"preset": "@scm-manager/jest-preset"
|
||||
},
|
||||
"prettier": "@scm-manager/prettier-config",
|
||||
"dependencies": {}
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged --verbose"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": "eslint"
|
||||
}
|
||||
}
|
||||
|
||||
53
scm-core/src/main/java/sonia/scm/RootURL.java
Normal file
53
scm-core/src/main/java/sonia/scm/RootURL.java
Normal 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;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* RootURL is able to return the root url of the SCM-Manager instance,
|
||||
* regardless of the scope (web request, async hook, ssh command, etc).
|
||||
*
|
||||
* @since 2.3.1
|
||||
*/
|
||||
public interface RootURL {
|
||||
|
||||
/**
|
||||
* Returns the root url of the SCM-Manager instance.
|
||||
*
|
||||
* @return root url
|
||||
*/
|
||||
URL get();
|
||||
|
||||
/**
|
||||
* Returns the root url of the SCM-Manager instance as string.
|
||||
*
|
||||
* @return root url as string
|
||||
*/
|
||||
default String getAsString() {
|
||||
return get().toExternalForm();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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())
|
||||
{
|
||||
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())
|
||||
{
|
||||
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())
|
||||
{
|
||||
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())
|
||||
{
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sonia.scm.RootURL;
|
||||
import sonia.scm.api.v2.resources.ScmPathInfoStore;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.Repository;
|
||||
@@ -37,6 +38,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
@@ -45,16 +47,36 @@ import static java.util.Optional.of;
|
||||
public abstract class InitializingHttpScmProtocolWrapper implements ScmProtocolProvider<HttpScmProtocol> {
|
||||
|
||||
private final Provider<? extends ScmProviderHttpServlet> delegateProvider;
|
||||
private final Provider<ScmPathInfoStore> pathInfoStore;
|
||||
private final ScmConfiguration scmConfiguration;
|
||||
private final Supplier<String> basePathSupplier;
|
||||
|
||||
private volatile boolean isInitialized = false;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new {@link InitializingHttpScmProtocolWrapper}.
|
||||
*
|
||||
* @param delegateProvider injection provider for the servlet delegate
|
||||
* @param pathInfoStore url info store
|
||||
* @param scmConfiguration scm-manager main configuration
|
||||
*
|
||||
* @deprecated use {@link InitializingHttpScmProtocolWrapper(Provider, RootURL)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
protected InitializingHttpScmProtocolWrapper(Provider<? extends ScmProviderHttpServlet> delegateProvider, Provider<ScmPathInfoStore> pathInfoStore, ScmConfiguration scmConfiguration) {
|
||||
this.delegateProvider = delegateProvider;
|
||||
this.pathInfoStore = pathInfoStore;
|
||||
this.scmConfiguration = scmConfiguration;
|
||||
this.basePathSupplier = new LegacySupplier(pathInfoStore, scmConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link InitializingHttpScmProtocolWrapper}.
|
||||
*
|
||||
* @param delegateProvider injection provider for the servlet delegate
|
||||
* @param rootURL root url
|
||||
*
|
||||
* @since 2.3.1
|
||||
*/
|
||||
public InitializingHttpScmProtocolWrapper(Provider<? extends ScmProviderHttpServlet> delegateProvider, RootURL rootURL) {
|
||||
this.delegateProvider = delegateProvider;
|
||||
this.basePathSupplier = rootURL::getAsString;
|
||||
}
|
||||
|
||||
protected void initializeServlet(ServletConfig config, ScmProviderHttpServlet httpServlet) throws ServletException {
|
||||
@@ -64,12 +86,25 @@ public abstract class InitializingHttpScmProtocolWrapper implements ScmProtocolP
|
||||
@Override
|
||||
public HttpScmProtocol get(Repository repository) {
|
||||
if (!repository.getType().equals(getType())) {
|
||||
throw new IllegalArgumentException(String.format("cannot handle repository with type %s with protocol for type %s", repository.getType(), getType()));
|
||||
throw new IllegalArgumentException(
|
||||
String.format("cannot handle repository with type %s with protocol for type %s", repository.getType(), getType())
|
||||
);
|
||||
}
|
||||
return new ProtocolWrapper(repository, computeBasePath());
|
||||
return new ProtocolWrapper(repository, basePathSupplier.get());
|
||||
}
|
||||
|
||||
private String computeBasePath() {
|
||||
private static class LegacySupplier implements Supplier<String> {
|
||||
|
||||
private final Provider<ScmPathInfoStore> pathInfoStore;
|
||||
private final ScmConfiguration scmConfiguration;
|
||||
|
||||
private LegacySupplier(Provider<ScmPathInfoStore> pathInfoStore, ScmConfiguration scmConfiguration) {
|
||||
this.pathInfoStore = pathInfoStore;
|
||||
this.scmConfiguration = scmConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get() {
|
||||
return getPathFromScmPathInfoIfAvailable().orElse(getPathFromConfiguration());
|
||||
}
|
||||
|
||||
@@ -90,6 +125,8 @@ public abstract class InitializingHttpScmProtocolWrapper implements ScmProtocolP
|
||||
return scmConfiguration.getBaseUrl();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ProtocolWrapper extends HttpScmProtocol {
|
||||
|
||||
public ProtocolWrapper(Repository repository, String basePath) {
|
||||
|
||||
@@ -925,11 +925,16 @@ public final class HttpUtil
|
||||
@VisibleForTesting
|
||||
static String createForwardedBaseUrl(HttpServletRequest request)
|
||||
{
|
||||
String proto = getHeader(request, HEADER_X_FORWARDED_PROTO,
|
||||
request.getScheme());
|
||||
String fhost = getHeader(request, HEADER_X_FORWARDED_HOST, null);
|
||||
if (fhost == null) {
|
||||
throw new IllegalStateException(
|
||||
String.format("request has no %s header and does not look like it is forwarded", HEADER_X_FORWARDED_HOST)
|
||||
);
|
||||
}
|
||||
|
||||
String proto = getHeader(request, HEADER_X_FORWARDED_PROTO, request.getScheme());
|
||||
String host;
|
||||
String fhost = getHeader(request, HEADER_X_FORWARDED_HOST,
|
||||
request.getScheme());
|
||||
|
||||
String port = request.getHeader(HEADER_X_FORWARDED_PORT);
|
||||
int s = fhost.indexOf(SEPARATOR_PORT);
|
||||
|
||||
|
||||
@@ -26,10 +26,14 @@ package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.inject.ProvisionException;
|
||||
import com.google.inject.util.Providers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.stubbing.OngoingStubbing;
|
||||
import sonia.scm.RootURL;
|
||||
import sonia.scm.api.v2.resources.ScmPathInfo;
|
||||
import sonia.scm.api.v2.resources.ScmPathInfoStore;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
@@ -44,22 +48,53 @@ import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class InitializingHttpScmProtocolWrapperTest {
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class InitializingHttpScmProtocolWrapperTest {
|
||||
|
||||
private static final Repository REPOSITORY = new Repository("", "git", "space", "name");
|
||||
|
||||
@Mock
|
||||
private ScmProviderHttpServlet delegateServlet;
|
||||
private InitializingHttpScmProtocolWrapper wrapper;
|
||||
|
||||
|
||||
@Nested
|
||||
class WithRootURL {
|
||||
|
||||
@Mock
|
||||
private RootURL rootURL;
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
wrapper = new InitializingHttpScmProtocolWrapper(Providers.of(delegateServlet), rootURL) {
|
||||
@Override
|
||||
public String getType() {
|
||||
return "git";
|
||||
}
|
||||
};
|
||||
when(rootURL.getAsString()).thenReturn("https://hitchhiker.com/scm");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnUrlFromRootURL() {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
assertEquals("https://hitchhiker.com/scm/repo/space/name", httpScmProtocol.getUrl());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WithPathInfoStore {
|
||||
|
||||
@Mock
|
||||
private ScmPathInfoStore pathInfoStore;
|
||||
@Mock
|
||||
private ScmConfiguration scmConfiguration;
|
||||
|
||||
private Provider<ScmPathInfoStore> pathInfoStoreProvider;
|
||||
|
||||
@Mock
|
||||
@@ -69,25 +104,22 @@ public class InitializingHttpScmProtocolWrapperTest {
|
||||
@Mock
|
||||
private ServletConfig servletConfig;
|
||||
|
||||
private InitializingHttpScmProtocolWrapper wrapper;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
initMocks(this);
|
||||
@BeforeEach
|
||||
void init() {
|
||||
pathInfoStoreProvider = mock(Provider.class);
|
||||
when(pathInfoStoreProvider.get()).thenReturn(pathInfoStore);
|
||||
lenient().when(pathInfoStoreProvider.get()).thenReturn(pathInfoStore);
|
||||
|
||||
wrapper = new InitializingHttpScmProtocolWrapper(Providers.of(this.delegateServlet), pathInfoStoreProvider, scmConfiguration) {
|
||||
wrapper = new InitializingHttpScmProtocolWrapper(Providers.of(delegateServlet), pathInfoStoreProvider, scmConfiguration) {
|
||||
@Override
|
||||
public String getType() {
|
||||
return "git";
|
||||
}
|
||||
};
|
||||
when(scmConfiguration.getBaseUrl()).thenReturn("http://example.com/scm");
|
||||
lenient().when(scmConfiguration.getBaseUrl()).thenReturn("http://example.com/scm");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUsePathFromPathInfo() {
|
||||
void shouldUsePathFromPathInfo() {
|
||||
mockSetPathInfo();
|
||||
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
@@ -96,14 +128,14 @@ public class InitializingHttpScmProtocolWrapperTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUseConfigurationWhenPathInfoNotSet() {
|
||||
void shouldUseConfigurationWhenPathInfoNotSet() {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
assertEquals("http://example.com/scm/repo/space/name", httpScmProtocol.getUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUseConfigurationWhenNotInRequestScope() {
|
||||
void shouldUseConfigurationWhenNotInRequestScope() {
|
||||
when(pathInfoStoreProvider.get()).thenThrow(new ProvisionException("test"));
|
||||
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
@@ -112,7 +144,7 @@ public class InitializingHttpScmProtocolWrapperTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldInitializeAndDelegateRequestThroughFilter() throws ServletException, IOException {
|
||||
void shouldInitializeAndDelegateRequestThroughFilter() throws ServletException, IOException {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
httpScmProtocol.serve(request, response, servletConfig);
|
||||
@@ -122,7 +154,7 @@ public class InitializingHttpScmProtocolWrapperTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldInitializeOnlyOnce() throws ServletException, IOException {
|
||||
void shouldInitializeOnlyOnce() throws ServletException, IOException {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
httpScmProtocol.serve(request, response, servletConfig);
|
||||
@@ -132,13 +164,19 @@ public class InitializingHttpScmProtocolWrapperTest {
|
||||
verify(delegateServlet, times(2)).service(request, response, REPOSITORY);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldFailForIllegalScmType() {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(new Repository("", "other", "space", "name"));
|
||||
@Test
|
||||
void shouldFailForIllegalScmType() {
|
||||
Repository repository = new Repository("", "other", "space", "name");
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> wrapper.get(repository)
|
||||
);
|
||||
}
|
||||
|
||||
private OngoingStubbing<ScmPathInfo> mockSetPathInfo() {
|
||||
return when(pathInfoStore.get()).thenReturn(() -> URI.create("http://example.com/scm/api/"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -234,6 +234,12 @@ public class HttpUtilTest
|
||||
HttpUtil.createForwardedBaseUrl(request));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void shouldTrowIllegalStateExceptionWithoutForwardedHostHeader() {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
HttpUtil.createForwardedBaseUrl(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
185
scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java
Normal file
185
scm-it/src/test/java/sonia/scm/it/MergeDetectionITCase.java
Normal 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("'", "\"");
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@scm-manager/scm-git-plugin",
|
||||
"private": true,
|
||||
"version": "2.3.0",
|
||||
"version": "2.4.0-SNAPSHOT",
|
||||
"license": "MIT",
|
||||
"main": "./src/main/js/index.ts",
|
||||
"scripts": {
|
||||
@@ -20,6 +20,6 @@
|
||||
},
|
||||
"prettier": "@scm-manager/prettier-config",
|
||||
"dependencies": {
|
||||
"@scm-manager/ui-plugins": "^2.3.0"
|
||||
"@scm-manager/ui-plugins": "^2.4.0-SNAPSHOT"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
return getBranchIdOrCurrentHead(gitRepository, defaultBranchName);
|
||||
} else {
|
||||
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);
|
||||
return getBranchIdOrCurrentHead(gitRepository, requestedBranch);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
try (org.eclipse.jgit.lib.Repository gitRepository = open()) {
|
||||
if (Strings.isNullOrEmpty(request.getBranch())) {
|
||||
request.setBranch(context.getConfig().getDefaultBranch());
|
||||
}
|
||||
|
||||
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));
|
||||
return new GitLogComputer(this.repository.getId(), gitRepository).compute(request);
|
||||
} catch (IOException e) {
|
||||
throw new InternalRepositoryException(repository, "could not create change log", e);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -24,22 +24,20 @@
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
import sonia.scm.api.v2.resources.ScmPathInfoStore;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.RootURL;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.GitRepositoryHandler;
|
||||
import sonia.scm.repository.spi.InitializingHttpScmProtocolWrapper;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
@Extension
|
||||
public class GitScmProtocolProviderWrapper extends InitializingHttpScmProtocolWrapper {
|
||||
@Inject
|
||||
public GitScmProtocolProviderWrapper(ScmGitServletProvider servletProvider, Provider<ScmPathInfoStore> uriInfoStore, ScmConfiguration scmConfiguration) {
|
||||
super(servletProvider, uriInfoStore, scmConfiguration);
|
||||
public GitScmProtocolProviderWrapper(ScmGitServletProvider servletProvider, RootURL rootURL) {
|
||||
super(servletProvider, rootURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@scm-manager/scm-hg-plugin",
|
||||
"private": true,
|
||||
"version": "2.3.0",
|
||||
"version": "2.4.0-SNAPSHOT",
|
||||
"license": "MIT",
|
||||
"main": "./src/main/js/index.ts",
|
||||
"scripts": {
|
||||
@@ -19,6 +19,6 @@
|
||||
},
|
||||
"prettier": "@scm-manager/prettier-config",
|
||||
"dependencies": {
|
||||
"@scm-manager/ui-plugins": "^2.3.0"
|
||||
"@scm-manager/ui-plugins": "^2.4.0-SNAPSHOT"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,22 +24,21 @@
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
import sonia.scm.api.v2.resources.ScmPathInfoStore;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.RootURL;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.spi.InitializingHttpScmProtocolWrapper;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
@Extension
|
||||
public class HgScmProtocolProviderWrapper extends InitializingHttpScmProtocolWrapper {
|
||||
|
||||
@Inject
|
||||
public HgScmProtocolProviderWrapper(HgCGIServletProvider servletProvider, Provider<ScmPathInfoStore> uriInfoStore, ScmConfiguration scmConfiguration) {
|
||||
super(servletProvider, uriInfoStore, scmConfiguration);
|
||||
public HgScmProtocolProviderWrapper(HgCGIServletProvider servletProvider, RootURL rootURL) {
|
||||
super(servletProvider, rootURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
50
scm-plugins/scm-integration-test-plugin/pom.xml
Normal file
50
scm-plugins/scm-integration-test-plugin/pom.xml
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@scm-manager/scm-legacy-plugin",
|
||||
"private": true,
|
||||
"version": "2.3.0",
|
||||
"version": "2.4.0-SNAPSHOT",
|
||||
"license": "MIT",
|
||||
"main": "./src/main/js/index.tsx",
|
||||
"scripts": {
|
||||
@@ -19,6 +19,6 @@
|
||||
},
|
||||
"prettier": "@scm-manager/prettier-config",
|
||||
"dependencies": {
|
||||
"@scm-manager/ui-plugins": "^2.3.0"
|
||||
"@scm-manager/ui-plugins": "^2.4.0-SNAPSHOT"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@scm-manager/scm-svn-plugin",
|
||||
"private": true,
|
||||
"version": "2.3.0",
|
||||
"version": "2.4.0-SNAPSHOT",
|
||||
"license": "MIT",
|
||||
"main": "./src/main/js/index.ts",
|
||||
"scripts": {
|
||||
@@ -19,6 +19,6 @@
|
||||
},
|
||||
"prettier": "@scm-manager/prettier-config",
|
||||
"dependencies": {
|
||||
"@scm-manager/ui-plugins": "^2.3.0"
|
||||
"@scm-manager/ui-plugins": "^2.4.0-SNAPSHOT"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,15 +24,13 @@
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
import sonia.scm.api.v2.resources.ScmPathInfoStore;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.RootURL;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.SvnRepositoryHandler;
|
||||
import sonia.scm.repository.spi.InitializingHttpScmProtocolWrapper;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
@@ -45,19 +43,18 @@ public class SvnScmProtocolProviderWrapper extends InitializingHttpScmProtocolWr
|
||||
|
||||
public static final String PARAMETER_SVN_PARENTPATH = "SVNParentPath";
|
||||
|
||||
@Inject
|
||||
public SvnScmProtocolProviderWrapper(SvnDAVServletProvider servletProvider, RootURL rootURL) {
|
||||
super(servletProvider, rootURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return SvnRepositoryHandler.TYPE_NAME;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public SvnScmProtocolProviderWrapper(SvnDAVServletProvider servletProvider, Provider<ScmPathInfoStore> uriInfoStore, ScmConfiguration scmConfiguration) {
|
||||
super(servletProvider, uriInfoStore, scmConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeServlet(ServletConfig config, ScmProviderHttpServlet httpServlet) throws ServletException {
|
||||
|
||||
super.initializeServlet(new SvnConfigEnhancer(config), httpServlet);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scm-manager/ui-components",
|
||||
"version": "2.3.0",
|
||||
"version": "2.4.0-SNAPSHOT",
|
||||
"description": "UI Components for SCM-Manager and its plugins",
|
||||
"main": "src/index.ts",
|
||||
"files": [
|
||||
|
||||
58
scm-ui/ui-components/src/Help.stories.tsx
Normal file
58
scm-ui/ui-components/src/Help.stories.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import styled from "styled-components";
|
||||
import * as React from "react";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import Help from "./Help";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
margin: 5rem;
|
||||
`;
|
||||
|
||||
const Spacing = styled.div`
|
||||
margin-top: 1rem;
|
||||
`;
|
||||
|
||||
const longContent =
|
||||
"Cleverness nuclear genuine static irresponsibility invited President Zaphod\n" +
|
||||
"Beeblebrox hyperspace ship. Another custard through computer-generated universe\n" +
|
||||
"shapes field strong disaster parties Russell’s ancestors infinite colour\n" +
|
||||
"imaginative generator sweep.";
|
||||
|
||||
storiesOf("Help", module)
|
||||
.addDecorator(storyFn => <Wrapper>{storyFn()}</Wrapper>)
|
||||
.add("Default", () => <Help message="This is a help message" />)
|
||||
.add("Multiline", () => (
|
||||
<>
|
||||
<Spacing>
|
||||
<label>With multiline (default):</label>
|
||||
<Help message={longContent} />
|
||||
</Spacing>
|
||||
<Spacing>
|
||||
<label>Without multiline:</label>
|
||||
<Help message={longContent} multiline={false} />
|
||||
</Spacing>
|
||||
</>
|
||||
));
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import React from "react";
|
||||
import React, { FC } from "react";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import Tooltip from "./Tooltip";
|
||||
@@ -29,6 +29,7 @@ import HelpIcon from "./HelpIcon";
|
||||
|
||||
type Props = {
|
||||
message: string;
|
||||
multiline?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
@@ -37,13 +38,17 @@ const HelpTooltip = styled(Tooltip)`
|
||||
padding-left: 3px;
|
||||
`;
|
||||
|
||||
export default class Help extends React.Component<Props> {
|
||||
render() {
|
||||
const { message, className } = this.props;
|
||||
return (
|
||||
<HelpTooltip className={classNames("is-inline-block", className)} message={message}>
|
||||
const Help: FC<Props> = ({ message, multiline, className }) => (
|
||||
<HelpTooltip
|
||||
className={classNames("is-inline-block", multiline ? "has-tooltip-multiline" : undefined, className)}
|
||||
message={message}
|
||||
>
|
||||
<HelpIcon />
|
||||
</HelpTooltip>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
Help.defaultProps = {
|
||||
multiline: true
|
||||
};
|
||||
|
||||
export default Help;
|
||||
|
||||
@@ -42976,6 +42976,65 @@ exports[`Storyshots Forms|Checkbox Disabled 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Forms|Checkbox With HelpText 1`] = `
|
||||
<div
|
||||
className="Checkboxstories__Spacing-sc-1bg8q8e-0 kJtXav"
|
||||
>
|
||||
<div
|
||||
className="field"
|
||||
>
|
||||
<div
|
||||
className="control"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<label
|
||||
className="checkbox"
|
||||
>
|
||||
<i
|
||||
className="is-outlined fa-square has-text-black far"
|
||||
/>
|
||||
|
||||
Classic helpText
|
||||
<span
|
||||
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
|
||||
data-tooltip="This is a classic help text."
|
||||
>
|
||||
<i
|
||||
className="fas fa-question-circle has-text-blue-light"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="field"
|
||||
>
|
||||
<div
|
||||
className="control"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<label
|
||||
className="checkbox"
|
||||
>
|
||||
<i
|
||||
className="is-outlined fa-check-square has-text-link fa"
|
||||
/>
|
||||
|
||||
Long helpText
|
||||
<span
|
||||
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
|
||||
data-tooltip="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
|
||||
>
|
||||
<i
|
||||
className="fas fa-question-circle has-text-blue-light"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Forms|DropDown Default 1`] = `
|
||||
<div
|
||||
className="select"
|
||||
@@ -43085,6 +43144,51 @@ exports[`Storyshots Forms|Radio Disabled 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Forms|Radio With HelpText 1`] = `
|
||||
<div
|
||||
className="Radiostories__RadioList-sc-18hvwhd-1 fSTSuW"
|
||||
>
|
||||
<label
|
||||
className="Radio__StyledRadio-ays4vp-0 hrHCWE radio"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
onChange={[Function]}
|
||||
type="radio"
|
||||
/>
|
||||
|
||||
Classic helpText
|
||||
<span
|
||||
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
|
||||
data-tooltip="This is a classic help text."
|
||||
>
|
||||
<i
|
||||
className="fas fa-question-circle has-text-blue-light"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
className="Radio__StyledRadio-ays4vp-0 hrHCWE radio"
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
onChange={[Function]}
|
||||
type="radio"
|
||||
/>
|
||||
|
||||
Long helpText
|
||||
<span
|
||||
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
|
||||
data-tooltip="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
|
||||
>
|
||||
<i
|
||||
className="fas fa-question-circle has-text-blue-light"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Forms|Textarea OnCancel 1`] = `
|
||||
<div
|
||||
className="Textareastories__Spacing-lk5v3m-0 hldael"
|
||||
@@ -43162,6 +43266,64 @@ exports[`Storyshots Forms|Textarea OnSubmit 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Help Default 1`] = `
|
||||
<div
|
||||
className="Helpstories__Wrapper-yq79zu-0 fkZuPN"
|
||||
>
|
||||
<span
|
||||
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
|
||||
data-tooltip="This is a help message"
|
||||
>
|
||||
<i
|
||||
className="fas fa-question-circle has-text-blue-light"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Help Multiline 1`] = `
|
||||
<div
|
||||
className="Helpstories__Wrapper-yq79zu-0 fkZuPN"
|
||||
>
|
||||
<div
|
||||
className="Helpstories__Spacing-yq79zu-1 jVjwc"
|
||||
>
|
||||
<label>
|
||||
With multiline (default):
|
||||
</label>
|
||||
<span
|
||||
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
|
||||
data-tooltip="Cleverness nuclear genuine static irresponsibility invited President Zaphod
|
||||
Beeblebrox hyperspace ship. Another custard through computer-generated universe
|
||||
shapes field strong disaster parties Russell’s ancestors infinite colour
|
||||
imaginative generator sweep."
|
||||
>
|
||||
<i
|
||||
className="fas fa-question-circle has-text-blue-light"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="Helpstories__Spacing-yq79zu-1 jVjwc"
|
||||
>
|
||||
<label>
|
||||
Without multiline:
|
||||
</label>
|
||||
<span
|
||||
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block"
|
||||
data-tooltip="Cleverness nuclear genuine static irresponsibility invited President Zaphod
|
||||
Beeblebrox hyperspace ship. Another custard through computer-generated universe
|
||||
shapes field strong disaster parties Russell’s ancestors infinite colour
|
||||
imaginative generator sweep."
|
||||
>
|
||||
<i
|
||||
className="fas fa-question-circle has-text-blue-light"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Icon Colors 1`] = `
|
||||
<div
|
||||
className="Iconstories__Wrapper-sc-1g657fe-0 culAJk"
|
||||
@@ -45853,6 +46015,44 @@ exports[`Storyshots Modal|ConfirmAlert Default 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Modal|Modal Closeable 1`] = `
|
||||
<div
|
||||
className="modal is-active"
|
||||
>
|
||||
<div
|
||||
className="modal-background"
|
||||
/>
|
||||
<div
|
||||
className="modal-card"
|
||||
>
|
||||
<header
|
||||
className="modal-card-head has-background-light"
|
||||
>
|
||||
<p
|
||||
className="modal-card-title is-marginless"
|
||||
>
|
||||
Hitchhiker Modal
|
||||
</p>
|
||||
<button
|
||||
aria-label="close"
|
||||
className="delete"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</header>
|
||||
<section
|
||||
className="modal-card-body"
|
||||
>
|
||||
<p>
|
||||
Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
|
||||
hostess’s really oblong Infinite Improbability thing into the starship against which behavior accordance.with
|
||||
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
|
||||
ordinary mob.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Modal|Modal Default 1`] = `
|
||||
<div
|
||||
className="modal is-active"
|
||||
@@ -45881,7 +46081,434 @@ exports[`Storyshots Modal|Modal Default 1`] = `
|
||||
className="modal-card-body"
|
||||
>
|
||||
<p>
|
||||
Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows hostess’s really oblong Infinite Improbability thing into the starship against which behavior accordance.with Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly ordinary mob.
|
||||
Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
|
||||
hostess’s really oblong Infinite Improbability thing into the starship against which behavior accordance.with
|
||||
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
|
||||
ordinary mob.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Modal|Modal Long content 1`] = `
|
||||
<div
|
||||
className="modal is-active"
|
||||
>
|
||||
<div
|
||||
className="modal-background"
|
||||
/>
|
||||
<div
|
||||
className="modal-card"
|
||||
>
|
||||
<header
|
||||
className="modal-card-head has-background-light"
|
||||
>
|
||||
<p
|
||||
className="modal-card-title is-marginless"
|
||||
>
|
||||
Hitchhiker Modal
|
||||
</p>
|
||||
<button
|
||||
aria-label="close"
|
||||
className="delete"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</header>
|
||||
<section
|
||||
className="modal-card-body"
|
||||
>
|
||||
<h1
|
||||
className="title"
|
||||
>
|
||||
Marvin
|
||||
</h1>
|
||||
<h2
|
||||
className="subtitle"
|
||||
>
|
||||
The Paranoid Android
|
||||
</h2>
|
||||
<hr />
|
||||
<div
|
||||
className="notification is-info"
|
||||
>
|
||||
|
||||
The following content comes from the awesome
|
||||
|
||||
<a
|
||||
href="https://hitchhikers.fandom.com/wiki/Main_Page"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Hitchhikers Wiki
|
||||
</a>
|
||||
</div>
|
||||
<hr />
|
||||
<div
|
||||
className="has-text-centered"
|
||||
>
|
||||
<img
|
||||
alt="Marvin"
|
||||
src="https://vignette.wikia.nocookie.net/hitchhikers/images/a/a4/Marvin.jpg/revision/latest/scale-to-width-down/150?cb=20100530114055"
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
<p
|
||||
className="content"
|
||||
>
|
||||
Marvin, more fully known as Marvin the Paranoid Android, is an incredibly brilliant but overwhelmingly depressed robot manufactured by the Sirius Cybernetics Corporation and unwilling servant to the crew of the Heart of Gold.
|
||||
</p>
|
||||
<hr />
|
||||
<div
|
||||
className="content"
|
||||
>
|
||||
<h4>
|
||||
Physical Appearance
|
||||
</h4>
|
||||
<p>
|
||||
In the novels, Marvin is described thusly: "...though it was beautifully constructed and polished it looked somehow as if the various parts of its more or less humanoid body didn't quite fit properly. In fact, they fit perfectly well, but something in its bearing suggested that they might have fitted better."
|
||||
</p>
|
||||
<p>
|
||||
On the radio show, there's no physical description of Marvin, though his voice is digitally altered to sound more robotic, and any scene that focuses on him is accompanied by sounds of mechanical clanking and hissing.
|
||||
</p>
|
||||
<p>
|
||||
In the TV series, Marvin is built in the style of a 1950's robot similar to Robbie the Robot from Forbidden Planet or Twiki from Buck Rogers. His body is blocky and angular, with a pair of clamp-claw hands, shuffling feet and a squarish head with a dour face.
|
||||
</p>
|
||||
<p>
|
||||
In the movie, Marvin is a short, stout robot built of smooth, white plastic. His arms are much longer than his legs, and his head is a massive sphere with only a pair of triangle eyes for a face. His large head and simian-like proportions give Marvin a perpetual slouch, adding to his melancholy personality. At the start of the film his eyes glow, but at the end he is shot but unharmed, leaving a hole in his head and dimming his eyes. This is probably the most depressing and unacceptable manifestation of Marvin ever conceived, and thus paradoxically the most accurate.
|
||||
</p>
|
||||
</div>
|
||||
<hr />
|
||||
<div
|
||||
className="content"
|
||||
>
|
||||
<h4>
|
||||
Personality
|
||||
</h4>
|
||||
<p>
|
||||
Marvin the robot has a prototype version of the Genuine People Personality (GPP) software from SCC, allowing him sentience and the ability to feel emotions and develop a personality. He's also incredibly smart, having a "brain the size of a planet" capable of computing extremely complex mathematics, as well as solving difficult problems and operating high-tech devices.
|
||||
</p>
|
||||
<p>
|
||||
However, despite being so smart, Marvin is typically made to perform menial tasks and labour such as escorting people, opening doors, picking up pieces of paper, and other tasks well beneath his skills. Even extremely hard tasks, such as computing for the vast Krikkit robot army, are trivial for Marvin. All this leaves him extremely bored, frustrated, and overwhelmingly depressed. Because of this, all modern GPP-capable machines, such as Eddie the computer and the Heart of Gold's automatic doors, are programmed to be extremely cheerful and happy, much to Marvin's disgust.
|
||||
</p>
|
||||
<p>
|
||||
Marvin hates everyone and everything he comes into contact with, having no respect for anybody and will criticise and insult others at any opportunity, or otherwise rant and complain for hours on end about his own problems, such as the terrible pain he suffers in all the diodes down his left side. His contempt for everyone is often justified, as almost every person he comes across, even those who consider him a friend, (such as Arthur and Trillian, who treat him more kindly than Ford and Zaphod) treat Marvin as an expendable servant, even sending him to his death more than once (such as when Zaphod ordered Marvin to fight the gigantic, heavy-duty Frogstar Scout Robot Class D so he could escape). Being a robot, he still does what he's told (he won't enjoy it, nor will he let you forget it, but he'll do it anyway), though he'd much rather sulk in a corner by himself.
|
||||
</p>
|
||||
<p>
|
||||
Several times in the series Marvin ends up alone and isolated for extremely long periods of time, sometimes spanning millions of years, either by sheer bad luck (such as the explosion that propelled everyone but Marvin to Milliways in the far-off future) or because his unpleasantly depressing personality drives them away or, in more than one case, makes them commit suicide. In his spare time (which he has a lot of), Marvin will attempt to occupy himself by composing songs and writing poetry. Of course, none of them are particularly cheerful, or even that good.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Modal|Modal With form elements 1`] = `
|
||||
<div
|
||||
className="modal is-active"
|
||||
>
|
||||
<div
|
||||
className="modal-background"
|
||||
/>
|
||||
<div
|
||||
className="modal-card"
|
||||
>
|
||||
<header
|
||||
className="modal-card-head has-background-light"
|
||||
>
|
||||
<p
|
||||
className="modal-card-title is-marginless"
|
||||
>
|
||||
Hitchhiker Modal
|
||||
</p>
|
||||
<button
|
||||
aria-label="close"
|
||||
className="delete"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</header>
|
||||
<section
|
||||
className="modal-card-body"
|
||||
>
|
||||
<div
|
||||
className="Modalstories__RadioList-sc-2lb0wg-1 hFfBOw"
|
||||
>
|
||||
<label
|
||||
className="Radio__StyledRadio-ays4vp-0 hrHCWE radio"
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
onChange={[Function]}
|
||||
type="radio"
|
||||
/>
|
||||
|
||||
One
|
||||
<span
|
||||
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
|
||||
data-tooltip="The first one"
|
||||
>
|
||||
<i
|
||||
className="fas fa-question-circle has-text-blue-light"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
className="Radio__StyledRadio-ays4vp-0 hrHCWE radio"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
onChange={[Function]}
|
||||
type="radio"
|
||||
/>
|
||||
|
||||
Two
|
||||
<span
|
||||
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
|
||||
data-tooltip="The second one"
|
||||
>
|
||||
<i
|
||||
className="fas fa-question-circle has-text-blue-light"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<hr />
|
||||
<p>
|
||||
Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
|
||||
hostess’s really oblong Infinite Improbability thing into the starship against which behavior accordance.with
|
||||
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
|
||||
ordinary mob.
|
||||
</p>
|
||||
<hr />
|
||||
<div
|
||||
className="field"
|
||||
>
|
||||
<label
|
||||
className="label"
|
||||
>
|
||||
Text
|
||||
|
||||
</label>
|
||||
<div
|
||||
className="control"
|
||||
>
|
||||
<textarea
|
||||
className="textarea"
|
||||
disabled={false}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div
|
||||
className="field is-grouped"
|
||||
>
|
||||
<div
|
||||
className="control"
|
||||
>
|
||||
<button
|
||||
className="button is-default"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
One
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="control"
|
||||
>
|
||||
<button
|
||||
className="button is-default"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Two
|
||||
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Modal|Modal With long tooltips 1`] = `
|
||||
<div
|
||||
className="modal is-active"
|
||||
>
|
||||
<div
|
||||
className="modal-background"
|
||||
/>
|
||||
<div
|
||||
className="modal-card"
|
||||
>
|
||||
<header
|
||||
className="modal-card-head has-background-light"
|
||||
>
|
||||
<p
|
||||
className="modal-card-title is-marginless"
|
||||
>
|
||||
Hitchhiker Modal
|
||||
</p>
|
||||
<button
|
||||
aria-label="close"
|
||||
className="delete"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</header>
|
||||
<section
|
||||
className="modal-card-body"
|
||||
>
|
||||
<div
|
||||
className="notification is-info"
|
||||
>
|
||||
|
||||
This story exists because we had a problem, that long tooltips causes a horizontal scrollbar on the modal.
|
||||
</div>
|
||||
<hr />
|
||||
<p>
|
||||
The following elements will have a verly long help text, which has triggered the scrollbar in the past.
|
||||
</p>
|
||||
<hr />
|
||||
<div
|
||||
className="Modalstories__TopAndBottomMargin-sc-2lb0wg-0 bfocSI"
|
||||
>
|
||||
<div
|
||||
className="field"
|
||||
>
|
||||
<div
|
||||
className="control"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<label
|
||||
className="checkbox"
|
||||
>
|
||||
<i
|
||||
className="is-outlined fa-check-square has-text-link fa"
|
||||
/>
|
||||
|
||||
Checkbox
|
||||
<span
|
||||
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
|
||||
data-tooltip="Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
|
||||
hostess’s really oblong Infinite Improbability thing into the starship against which behavior accordance.with
|
||||
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
|
||||
ordinary mob."
|
||||
>
|
||||
<i
|
||||
className="fas fa-question-circle has-text-blue-light"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div
|
||||
className="Modalstories__TopAndBottomMargin-sc-2lb0wg-0 bfocSI"
|
||||
>
|
||||
<label
|
||||
className="Radio__StyledRadio-ays4vp-0 hrHCWE radio"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
onChange={[Function]}
|
||||
type="radio"
|
||||
/>
|
||||
|
||||
Radio button
|
||||
<span
|
||||
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
|
||||
data-tooltip="Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
|
||||
hostess’s really oblong Infinite Improbability thing into the starship against which behavior accordance.with
|
||||
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
|
||||
ordinary mob."
|
||||
>
|
||||
<i
|
||||
className="fas fa-question-circle has-text-blue-light"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<hr />
|
||||
<div
|
||||
className="Modalstories__TopAndBottomMargin-sc-2lb0wg-0 bfocSI"
|
||||
>
|
||||
<div
|
||||
className="field"
|
||||
>
|
||||
<label
|
||||
className="label"
|
||||
>
|
||||
Input
|
||||
|
||||
<span
|
||||
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
|
||||
data-tooltip="Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
|
||||
hostess’s really oblong Infinite Improbability thing into the starship against which behavior accordance.with
|
||||
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
|
||||
ordinary mob."
|
||||
>
|
||||
<i
|
||||
className="fas fa-question-circle has-text-blue-light"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
className="control"
|
||||
>
|
||||
<input
|
||||
className="input"
|
||||
onChange={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
placeholder=""
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div
|
||||
className="Modalstories__TopAndBottomMargin-sc-2lb0wg-0 bfocSI"
|
||||
>
|
||||
<div
|
||||
className="field"
|
||||
>
|
||||
<label
|
||||
className="label"
|
||||
>
|
||||
Textarea
|
||||
|
||||
<span
|
||||
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
|
||||
data-tooltip="Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
|
||||
hostess’s really oblong Infinite Improbability thing into the starship against which behavior accordance.with
|
||||
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
|
||||
ordinary mob."
|
||||
>
|
||||
<i
|
||||
className="fas fa-question-circle has-text-blue-light"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
className="control"
|
||||
>
|
||||
<textarea
|
||||
className="textarea"
|
||||
disabled={false}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<p>
|
||||
If this modal has no horizontal scrollbar the issue is fixed
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -42,4 +42,14 @@ storiesOf("Forms|Checkbox", module)
|
||||
<Spacing>
|
||||
<Checkbox label="Checked but disabled" checked={true} disabled={true} />
|
||||
</Spacing>
|
||||
))
|
||||
.add("With HelpText", () => (
|
||||
<Spacing>
|
||||
<Checkbox label="Classic helpText" checked={false} helpText="This is a classic help text." />
|
||||
<Checkbox
|
||||
label="Long helpText"
|
||||
checked={true}
|
||||
helpText="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
|
||||
/>
|
||||
</Spacing>
|
||||
));
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import React, { ChangeEvent } from "react";
|
||||
import React from "react";
|
||||
import { Help } from "../index";
|
||||
import LabelWithHelpIcon from "./LabelWithHelpIcon";
|
||||
import TriStateCheckbox from "./TriStateCheckbox";
|
||||
|
||||
@@ -30,6 +30,15 @@ const Spacing = styled.div`
|
||||
padding: 2em;
|
||||
`;
|
||||
|
||||
const RadioList = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> label:not(:last-child) {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
padding: 2em;
|
||||
`;
|
||||
|
||||
storiesOf("Forms|Radio", module)
|
||||
.add("Default", () => (
|
||||
<Spacing>
|
||||
@@ -41,4 +50,14 @@ storiesOf("Forms|Radio", module)
|
||||
<Spacing>
|
||||
<Radio label="Checked but disabled" checked={true} disabled={true} />
|
||||
</Spacing>
|
||||
))
|
||||
.add("With HelpText", () => (
|
||||
<RadioList>
|
||||
<Radio label="Classic helpText" checked={false} helpText="This is a classic help text." />
|
||||
<Radio
|
||||
label="Long helpText"
|
||||
checked={true}
|
||||
helpText="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
|
||||
/>
|
||||
</RadioList>
|
||||
));
|
||||
|
||||
@@ -24,29 +24,190 @@
|
||||
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
import React, { useState, FC } from "react";
|
||||
import Modal from "./Modal";
|
||||
import Checkbox from "../forms/Checkbox";
|
||||
import styled from "styled-components";
|
||||
import ExternalLink from "../navigation/ExternalLink";
|
||||
import { Radio, Textarea, InputField } from "../forms";
|
||||
import { ButtonGroup, Button } from "../buttons";
|
||||
import Notification from "../Notification";
|
||||
|
||||
const body = (
|
||||
<p>
|
||||
Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
|
||||
const TopAndBottomMargin = styled.div`
|
||||
margin: 0.75rem 0; // only for aesthetic reasons
|
||||
`;
|
||||
|
||||
const RadioList = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> label:not(:last-child) {
|
||||
margin-bottom: 0.6em;
|
||||
}
|
||||
`;
|
||||
|
||||
const text = `Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
|
||||
hostess’s really oblong Infinite Improbability thing into the starship against which behavior accordance.with
|
||||
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
|
||||
ordinary mob.
|
||||
</p>
|
||||
);
|
||||
ordinary mob.`;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const doNothing = () => {};
|
||||
|
||||
storiesOf("Modal|Modal", module)
|
||||
.addDecorator(story => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>)
|
||||
.add("Default", () => <CloseableModal />);
|
||||
.add("Default", () => (
|
||||
<NonCloseableModal>
|
||||
<p>{text}</p>
|
||||
</NonCloseableModal>
|
||||
))
|
||||
.add("Closeable", () => (
|
||||
<CloseableModal>
|
||||
<p>{text}</p>
|
||||
</CloseableModal>
|
||||
))
|
||||
.add("With form elements", () => (
|
||||
<NonCloseableModal>
|
||||
<RadioList>
|
||||
<Radio label="One" checked={true} helpText="The first one" />
|
||||
<Radio label="Two" checked={false} helpText="The second one" />
|
||||
</RadioList>
|
||||
<hr />
|
||||
<p>{text}</p>
|
||||
<hr />
|
||||
<Textarea label="Text" onChange={doNothing} />
|
||||
<hr />
|
||||
<ButtonGroup>
|
||||
<Button label="One" />
|
||||
<Button label="Two" />
|
||||
</ButtonGroup>
|
||||
</NonCloseableModal>
|
||||
))
|
||||
.add("With long tooltips", () => {
|
||||
return (
|
||||
<NonCloseableModal>
|
||||
<Notification type="info">
|
||||
This story exists because we had a problem, that long tooltips causes a horizontal scrollbar on the modal.
|
||||
</Notification>
|
||||
<hr />
|
||||
<p>The following elements will have a verly long help text, which has triggered the scrollbar in the past.</p>
|
||||
<hr />
|
||||
<TopAndBottomMargin>
|
||||
<Checkbox label="Checkbox" checked={true} helpText={text} />
|
||||
</TopAndBottomMargin>
|
||||
<hr />
|
||||
<TopAndBottomMargin>
|
||||
<Radio label="Radio button" checked={false} helpText={text} />
|
||||
</TopAndBottomMargin>
|
||||
<hr />
|
||||
<TopAndBottomMargin>
|
||||
<InputField onChange={doNothing} label="Input" helpText={text} />
|
||||
</TopAndBottomMargin>
|
||||
<hr />
|
||||
<TopAndBottomMargin>
|
||||
<Textarea onChange={doNothing} label="Textarea" helpText={text} />
|
||||
</TopAndBottomMargin>
|
||||
<hr />
|
||||
<p>If this modal has no horizontal scrollbar the issue is fixed</p>
|
||||
</NonCloseableModal>
|
||||
);
|
||||
})
|
||||
.add("Long content", () => (
|
||||
<NonCloseableModal>
|
||||
<h1 className="title">Marvin</h1>
|
||||
<h2 className="subtitle">The Paranoid Android</h2>
|
||||
<hr />
|
||||
<Notification type="info">
|
||||
The following content comes from the awesome{" "}
|
||||
<ExternalLink to="https://hitchhikers.fandom.com/wiki/Main_Page">Hitchhikers Wiki</ExternalLink>
|
||||
</Notification>
|
||||
<hr />
|
||||
<div className="has-text-centered">
|
||||
<img
|
||||
alt="Marvin"
|
||||
src="https://vignette.wikia.nocookie.net/hitchhikers/images/a/a4/Marvin.jpg/revision/latest/scale-to-width-down/150?cb=20100530114055"
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
<p className="content">
|
||||
Marvin, more fully known as Marvin the Paranoid Android, is an incredibly brilliant but overwhelmingly depressed
|
||||
robot manufactured by the Sirius Cybernetics Corporation and unwilling servant to the crew of the Heart of Gold.
|
||||
</p>
|
||||
<hr />
|
||||
<div className="content">
|
||||
<h4>Physical Appearance</h4>
|
||||
<p>
|
||||
In the novels, Marvin is described thusly: "...though it was beautifully constructed and polished it looked
|
||||
somehow as if the various parts of its more or less humanoid body didn't quite fit properly. In fact, they fit
|
||||
perfectly well, but something in its bearing suggested that they might have fitted better."
|
||||
</p>
|
||||
<p>
|
||||
On the radio show, there's no physical description of Marvin, though his voice is digitally altered to sound
|
||||
more robotic, and any scene that focuses on him is accompanied by sounds of mechanical clanking and hissing.
|
||||
</p>
|
||||
<p>
|
||||
In the TV series, Marvin is built in the style of a 1950's robot similar to Robbie the Robot from Forbidden
|
||||
Planet or Twiki from Buck Rogers. His body is blocky and angular, with a pair of clamp-claw hands, shuffling
|
||||
feet and a squarish head with a dour face.
|
||||
</p>
|
||||
<p>
|
||||
In the movie, Marvin is a short, stout robot built of smooth, white plastic. His arms are much longer than his
|
||||
legs, and his head is a massive sphere with only a pair of triangle eyes for a face. His large head and
|
||||
simian-like proportions give Marvin a perpetual slouch, adding to his melancholy personality. At the start of
|
||||
the film his eyes glow, but at the end he is shot but unharmed, leaving a hole in his head and dimming his
|
||||
eyes. This is probably the most depressing and unacceptable manifestation of Marvin ever conceived, and thus
|
||||
paradoxically the most accurate.
|
||||
</p>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="content">
|
||||
<h4>Personality</h4>
|
||||
<p>
|
||||
Marvin the robot has a prototype version of the Genuine People Personality (GPP) software from SCC, allowing
|
||||
him sentience and the ability to feel emotions and develop a personality. He's also incredibly smart, having a
|
||||
"brain the size of a planet" capable of computing extremely complex mathematics, as well as solving difficult
|
||||
problems and operating high-tech devices.
|
||||
</p>
|
||||
<p>
|
||||
However, despite being so smart, Marvin is typically made to perform menial tasks and labour such as escorting
|
||||
people, opening doors, picking up pieces of paper, and other tasks well beneath his skills. Even extremely
|
||||
hard tasks, such as computing for the vast Krikkit robot army, are trivial for Marvin. All this leaves him
|
||||
extremely bored, frustrated, and overwhelmingly depressed. Because of this, all modern GPP-capable machines,
|
||||
such as Eddie the computer and the Heart of Gold's automatic doors, are programmed to be extremely cheerful
|
||||
and happy, much to Marvin's disgust.
|
||||
</p>
|
||||
<p>
|
||||
Marvin hates everyone and everything he comes into contact with, having no respect for anybody and will
|
||||
criticise and insult others at any opportunity, or otherwise rant and complain for hours on end about his own
|
||||
problems, such as the terrible pain he suffers in all the diodes down his left side. His contempt for everyone
|
||||
is often justified, as almost every person he comes across, even those who consider him a friend, (such as
|
||||
Arthur and Trillian, who treat him more kindly than Ford and Zaphod) treat Marvin as an expendable servant,
|
||||
even sending him to his death more than once (such as when Zaphod ordered Marvin to fight the gigantic,
|
||||
heavy-duty Frogstar Scout Robot Class D so he could escape). Being a robot, he still does what he's told (he
|
||||
won't enjoy it, nor will he let you forget it, but he'll do it anyway), though he'd much rather sulk in a
|
||||
corner by himself.
|
||||
</p>
|
||||
<p>
|
||||
Several times in the series Marvin ends up alone and isolated for extremely long periods of time, sometimes
|
||||
spanning millions of years, either by sheer bad luck (such as the explosion that propelled everyone but Marvin
|
||||
to Milliways in the far-off future) or because his unpleasantly depressing personality drives them away or, in
|
||||
more than one case, makes them commit suicide. In his spare time (which he has a lot of), Marvin will attempt
|
||||
to occupy himself by composing songs and writing poetry. Of course, none of them are particularly cheerful, or
|
||||
even that good.
|
||||
</p>
|
||||
</div>
|
||||
</NonCloseableModal>
|
||||
));
|
||||
|
||||
const CloseableModal = () => {
|
||||
const NonCloseableModal: FC = ({ children }) => {
|
||||
return <Modal body={children} closeFunction={doNothing} active={true} title={"Hitchhiker Modal"} />;
|
||||
};
|
||||
|
||||
const CloseableModal: FC = ({ children }) => {
|
||||
const [show, setShow] = useState(true);
|
||||
|
||||
const toggleModal = () => {
|
||||
setShow(!show);
|
||||
};
|
||||
|
||||
return <Modal body={body} closeFunction={toggleModal} active={show} title={"Hitchhiker Modal"} />;
|
||||
return <Modal body={children} closeFunction={toggleModal} active={show} title={"Hitchhiker Modal"} />;
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scm-manager/ui-plugins",
|
||||
"version": "2.3.0",
|
||||
"version": "2.4.0-SNAPSHOT",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"ui-plugins": "./bin/ui-plugins.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@scm-manager/ui-components": "^2.3.0",
|
||||
"@scm-manager/ui-components": "^2.4.0-SNAPSHOT",
|
||||
"@scm-manager/ui-extensions": "^2.1.0",
|
||||
"classnames": "^2.2.6",
|
||||
"query-string": "^5.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scm-manager/ui-styles",
|
||||
"version": "2.1.0",
|
||||
"version": "2.4.0-SNAPSHOT",
|
||||
"description": "Styles for SCM-Manager",
|
||||
"main": "src/scm.scss",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "@scm-manager/ui-webapp",
|
||||
"version": "2.3.0",
|
||||
"version": "2.4.0-SNAPSHOT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@scm-manager/ui-components": "^2.3.0",
|
||||
"@scm-manager/ui-components": "^2.4.0-SNAPSHOT",
|
||||
"@scm-manager/ui-extensions": "^2.1.0",
|
||||
"classnames": "^2.2.5",
|
||||
"history": "^4.10.1",
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
84
scm-webapp/src/main/java/sonia/scm/DefaultRootURL.java
Normal file
84
scm-webapp/src/main/java/sonia/scm/DefaultRootURL.java
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.google.inject.OutOfScopeException;
|
||||
import com.google.inject.ProvisionException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link RootURL}.
|
||||
*
|
||||
* @since 2.3.1
|
||||
*/
|
||||
public class DefaultRootURL implements RootURL {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultRootURL.class);
|
||||
|
||||
private final Provider<HttpServletRequest> requestProvider;
|
||||
private final ScmConfiguration configuration;
|
||||
|
||||
@Inject
|
||||
public DefaultRootURL(Provider<HttpServletRequest> requestProvider, ScmConfiguration configuration) {
|
||||
this.requestProvider = requestProvider;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL get() {
|
||||
String url = fromRequest().orElse(configuration.getBaseUrl());
|
||||
if (url == null) {
|
||||
throw new IllegalStateException("The configured base url is empty. This can only happened if SCM-Manager has not received any requests.");
|
||||
}
|
||||
try {
|
||||
return new URL(url);
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalStateException(String.format("base url \"%s\" is malformed", url), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<String> fromRequest() {
|
||||
try {
|
||||
HttpServletRequest request = requestProvider.get();
|
||||
return Optional.of(HttpUtil.getCompleteUrl(request));
|
||||
} catch (ProvisionException ex) {
|
||||
if (ex.getCause() instanceof OutOfScopeException) {
|
||||
LOG.debug("could not find request, fall back to base url from configuration");
|
||||
return Optional.empty();
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,10 @@ import com.google.inject.throwingproviders.ThrowingProviderBinder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.Default;
|
||||
import sonia.scm.DefaultRootURL;
|
||||
import sonia.scm.PushStateDispatcher;
|
||||
import sonia.scm.PushStateDispatcherProvider;
|
||||
import sonia.scm.RootURL;
|
||||
import sonia.scm.Undecorated;
|
||||
import sonia.scm.api.rest.ObjectMapperProvider;
|
||||
import sonia.scm.api.v2.resources.BranchLinkProvider;
|
||||
@@ -239,6 +241,9 @@ class ScmServletModule extends ServletModule {
|
||||
|
||||
// bind api link provider
|
||||
bind(BranchLinkProvider.class).to(DefaultBranchLinkProvider.class);
|
||||
|
||||
// bind url helper
|
||||
bind(RootURL.class).to(DefaultRootURL.class);
|
||||
}
|
||||
|
||||
private <T> void bind(Class<T> clazz, Class<? extends T> defaultImplementation) {
|
||||
|
||||
134
scm-webapp/src/test/java/sonia/scm/DefaultRootURLTest.java
Normal file
134
scm-webapp/src/test/java/sonia/scm/DefaultRootURLTest.java
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.google.inject.OutOfScopeException;
|
||||
import com.google.inject.ProvisionException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DefaultRootURLTest {
|
||||
|
||||
private static final String URL_CONFIG = "https://hitchhiker.com/from-configuration";
|
||||
private static final String URL_REQUEST = "https://hitchhiker.com/from-request";
|
||||
|
||||
@Mock
|
||||
private Provider<HttpServletRequest> requestProvider;
|
||||
|
||||
@Mock
|
||||
private HttpServletRequest request;
|
||||
|
||||
private ScmConfiguration configuration;
|
||||
|
||||
private RootURL rootURL;
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
configuration = new ScmConfiguration();
|
||||
rootURL = new DefaultRootURL(requestProvider, configuration);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseRootURLFromRequest() {
|
||||
bindRequestUrl();
|
||||
assertThat(rootURL.getAsString()).isEqualTo(URL_REQUEST);
|
||||
}
|
||||
|
||||
private void bindRequestUrl() {
|
||||
when(requestProvider.get()).thenReturn(request);
|
||||
when(request.getRequestURL()).thenReturn(new StringBuffer(URL_REQUEST));
|
||||
when(request.getRequestURI()).thenReturn("/from-request");
|
||||
when(request.getContextPath()).thenReturn("/from-request");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseRootURLFromConfiguration() {
|
||||
bindNonHttpScope();
|
||||
configuration.setBaseUrl(URL_CONFIG);
|
||||
assertThat(rootURL.getAsString()).isEqualTo(URL_CONFIG);
|
||||
}
|
||||
|
||||
private void bindNonHttpScope() {
|
||||
when(requestProvider.get()).thenThrow(
|
||||
new ProvisionException("no request available", new OutOfScopeException("out of scope"))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowNonOutOfScopeProvisioningExceptions() {
|
||||
when(requestProvider.get()).thenThrow(
|
||||
new ProvisionException("something ugly happened", new IllegalStateException("some wrong state"))
|
||||
);
|
||||
|
||||
assertThrows(ProvisionException.class, () -> rootURL.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowIllegalStateExceptionForMalformedBaseUrl() {
|
||||
bindNonHttpScope();
|
||||
configuration.setBaseUrl("non_url");
|
||||
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> rootURL.get());
|
||||
assertThat(exception.getMessage()).contains("malformed", "non_url");
|
||||
assertThat(exception.getCause()).isInstanceOf(MalformedURLException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowIllegalStateExceptionIfBaseURLIsNotConfigured() {
|
||||
bindNonHttpScope();
|
||||
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> rootURL.get());
|
||||
assertThat(exception.getMessage()).contains("empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseRootURLFromForwardedRequest() {
|
||||
bindForwardedRequestUrl();
|
||||
assertThat(rootURL.get()).hasHost("hitchhiker.com");
|
||||
}
|
||||
|
||||
private void bindForwardedRequestUrl() {
|
||||
when(requestProvider.get()).thenReturn(request);
|
||||
when(request.getHeader(HttpUtil.HEADER_X_FORWARDED_HOST)).thenReturn("hitchhiker.com");
|
||||
when(request.getScheme()).thenReturn("https");
|
||||
when(request.getServerPort()).thenReturn(443);
|
||||
when(request.getContextPath()).thenReturn("/from-request");
|
||||
}
|
||||
|
||||
}
|
||||
243
yarn.lock
243
yarn.lock
@@ -984,6 +984,13 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.10.1":
|
||||
version "7.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.0.tgz#f10245877042a815e07f7e693faff0ae9d3a2aac"
|
||||
integrity sha512-qArkXsjJq7H+T86WrIFV0Fnu/tNOkZ4cgXmjkzAu3b/58D5mFIO8JH/y77t7C9q0OdDRdh9s7Ue5GasYssxtXw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/template@^7.10.1", "@babel/template@^7.3.3":
|
||||
version "7.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811"
|
||||
@@ -3774,12 +3781,17 @@ ansi-colors@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
|
||||
integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==
|
||||
|
||||
ansi-colors@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
|
||||
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
|
||||
|
||||
ansi-escapes@^3.0.0, ansi-escapes@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
|
||||
integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
|
||||
|
||||
ansi-escapes@^4.2.1:
|
||||
ansi-escapes@^4.2.1, ansi-escapes@^4.3.0:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61"
|
||||
integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==
|
||||
@@ -4077,6 +4089,11 @@ astral-regex@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
|
||||
integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
|
||||
|
||||
astral-regex@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
|
||||
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
|
||||
|
||||
async-each@^1.0.1:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
|
||||
@@ -4917,10 +4934,10 @@ bulma-tooltip@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/bulma-tooltip/-/bulma-tooltip-3.0.2.tgz#2cf0abab1de2eba07f9d84eb7f07a8a88819ea92"
|
||||
integrity sha512-CsT3APjhlZScskFg38n8HYL8oYNUHQtcu4sz6ERarxkUpBRbk9v0h/5KAvXeKapVSn2dp9l7bOGit5SECP8EWQ==
|
||||
|
||||
bulma@^0.8.0:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.8.2.tgz#5d928f16ed4a84549c2873f95c92c38c69c631a7"
|
||||
integrity sha512-vMM/ijYSxX+Sm+nD7Lmc1UgWDy2JcL2nTKqwgEqXuOMU+IGALbXd5MLt/BcjBAPLIx36TtzhzBcSnOP974gcqA==
|
||||
bulma@^0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.9.0.tgz#948c5445a49e9d7546f0826cb3820d17178a814f"
|
||||
integrity sha512-rV75CJkubNUroAt0qCRkjznZLoaXq/ctfMXsMvKSL84UetbSyx5REl96e8GoQ04G4Tkw0XF3STECffTOQrbzOQ==
|
||||
|
||||
byline@^5.0.0:
|
||||
version "5.0.0"
|
||||
@@ -5155,7 +5172,7 @@ chalk@^3.0.0:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.0.0:
|
||||
chalk@^4.0.0, chalk@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
|
||||
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
|
||||
@@ -5320,6 +5337,14 @@ cli-table3@0.5.1:
|
||||
optionalDependencies:
|
||||
colors "^1.1.2"
|
||||
|
||||
cli-truncate@2.1.0, cli-truncate@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7"
|
||||
integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==
|
||||
dependencies:
|
||||
slice-ansi "^3.0.0"
|
||||
string-width "^4.2.0"
|
||||
|
||||
cli-width@^2.0.0:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
|
||||
@@ -5494,6 +5519,11 @@ commander@^4.0.1, commander@^4.1.1:
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
|
||||
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
|
||||
|
||||
commander@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
|
||||
integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
|
||||
|
||||
commondir@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
|
||||
@@ -5507,6 +5537,11 @@ compare-func@^1.3.1:
|
||||
array-ify "^1.0.0"
|
||||
dot-prop "^3.0.0"
|
||||
|
||||
compare-versions@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62"
|
||||
integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==
|
||||
|
||||
component-emitter@^1.2.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
|
||||
@@ -6773,6 +6808,13 @@ enhanced-resolve@^4.1.0:
|
||||
memory-fs "^0.5.0"
|
||||
tapable "^1.0.0"
|
||||
|
||||
enquirer@^2.3.5:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
|
||||
integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
|
||||
dependencies:
|
||||
ansi-colors "^4.1.1"
|
||||
|
||||
entities@^1.1.1, entities@^1.1.2, entities@~1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
|
||||
@@ -7313,6 +7355,21 @@ execa@^4.0.0:
|
||||
signal-exit "^3.0.2"
|
||||
strip-final-newline "^2.0.0"
|
||||
|
||||
execa@^4.0.1:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2"
|
||||
integrity sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==
|
||||
dependencies:
|
||||
cross-spawn "^7.0.0"
|
||||
get-stream "^5.0.0"
|
||||
human-signals "^1.1.1"
|
||||
is-stream "^2.0.0"
|
||||
merge-stream "^2.0.0"
|
||||
npm-run-path "^4.0.0"
|
||||
onetime "^5.1.0"
|
||||
signal-exit "^3.0.2"
|
||||
strip-final-newline "^2.0.0"
|
||||
|
||||
exit@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
|
||||
@@ -7553,7 +7610,7 @@ figures@^2.0.0:
|
||||
dependencies:
|
||||
escape-string-regexp "^1.0.5"
|
||||
|
||||
figures@^3.0.0:
|
||||
figures@^3.0.0, figures@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
|
||||
integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
|
||||
@@ -7677,6 +7734,13 @@ find-up@^4.0.0, find-up@^4.1.0:
|
||||
locate-path "^5.0.0"
|
||||
path-exists "^4.0.0"
|
||||
|
||||
find-versions@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-3.2.0.tgz#10297f98030a786829681690545ef659ed1d254e"
|
||||
integrity sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==
|
||||
dependencies:
|
||||
semver-regex "^2.0.0"
|
||||
|
||||
findup-sync@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1"
|
||||
@@ -7916,6 +7980,11 @@ get-caller-file@^2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
|
||||
get-own-enumerable-property-symbols@^3.0.0:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
|
||||
integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==
|
||||
|
||||
get-package-type@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
|
||||
@@ -8637,6 +8706,22 @@ humanize-ms@^1.2.1:
|
||||
dependencies:
|
||||
ms "^2.0.0"
|
||||
|
||||
husky@^4.2.5:
|
||||
version "4.2.5"
|
||||
resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.5.tgz#2b4f7622673a71579f901d9885ed448394b5fa36"
|
||||
integrity sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ==
|
||||
dependencies:
|
||||
chalk "^4.0.0"
|
||||
ci-info "^2.0.0"
|
||||
compare-versions "^3.6.0"
|
||||
cosmiconfig "^6.0.0"
|
||||
find-versions "^3.2.0"
|
||||
opencollective-postinstall "^2.0.2"
|
||||
pkg-dir "^4.2.0"
|
||||
please-upgrade-node "^3.2.0"
|
||||
slash "^3.0.0"
|
||||
which-pm-runs "^1.0.0"
|
||||
|
||||
i18next-browser-languagedetector@^4.0.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-4.2.0.tgz#82e35d31f88a1d7c2b6d5913bf8c8481cd40aafb"
|
||||
@@ -8656,12 +8741,12 @@ i18next@*:
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
|
||||
i18next@^17.3.0:
|
||||
version "17.3.1"
|
||||
resolved "https://registry.yarnpkg.com/i18next/-/i18next-17.3.1.tgz#5fe75e054aae39a6f38f1a79f7ab49184c6dc7a1"
|
||||
integrity sha512-4nY+yaENaoZKmpbiDXPzucVHCN3hN9Z9Zk7LyQXVOKVIpnYOJ3L/yxHJlBPtJDq3PGgjFwA0QBFm/26Z0iDT5A==
|
||||
i18next@^19.6.0:
|
||||
version "19.6.3"
|
||||
resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.6.3.tgz#ce2346161b35c4c5ab691b0674119c7b349c0817"
|
||||
integrity sha512-eYr98kw/C5z6kY21ti745p4IvbOJwY8F2T9tf/Lvy5lFnYRqE45+bppSgMPmcZZqYNT+xO0N0x6rexVR2wtZZQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
"@babel/runtime" "^7.10.1"
|
||||
|
||||
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13:
|
||||
version "0.4.24"
|
||||
@@ -9202,7 +9287,7 @@ is-number@^7.0.0:
|
||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
||||
|
||||
is-obj@^1.0.0:
|
||||
is-obj@^1.0.0, is-obj@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
|
||||
integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
|
||||
@@ -9267,6 +9352,11 @@ is-regex@^1.0.4, is-regex@^1.0.5:
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
|
||||
is-regexp@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
|
||||
integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk=
|
||||
|
||||
is-resolvable@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
|
||||
@@ -10594,6 +10684,41 @@ lines-and-columns@^1.1.6:
|
||||
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
|
||||
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
|
||||
|
||||
lint-staged@^10.2.11:
|
||||
version "10.2.11"
|
||||
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.2.11.tgz#713c80877f2dc8b609b05bc59020234e766c9720"
|
||||
integrity sha512-LRRrSogzbixYaZItE2APaS4l2eJMjjf5MbclRZpLJtcQJShcvUzKXsNeZgsLIZ0H0+fg2tL4B59fU9wHIHtFIA==
|
||||
dependencies:
|
||||
chalk "^4.0.0"
|
||||
cli-truncate "2.1.0"
|
||||
commander "^5.1.0"
|
||||
cosmiconfig "^6.0.0"
|
||||
debug "^4.1.1"
|
||||
dedent "^0.7.0"
|
||||
enquirer "^2.3.5"
|
||||
execa "^4.0.1"
|
||||
listr2 "^2.1.0"
|
||||
log-symbols "^4.0.0"
|
||||
micromatch "^4.0.2"
|
||||
normalize-path "^3.0.0"
|
||||
please-upgrade-node "^3.2.0"
|
||||
string-argv "0.3.1"
|
||||
stringify-object "^3.3.0"
|
||||
|
||||
listr2@^2.1.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.4.1.tgz#006fc94ae77b3195403cbf3a4a563e2d6366224f"
|
||||
integrity sha512-8pYsCZCztr5+KAjReLyBeGhLV0vaQ2Du/eMe/ux9QAfQl7efiWejM1IWjALh0zHIRYuIbhQ8N2KztZ4ci56pnQ==
|
||||
dependencies:
|
||||
chalk "^4.1.0"
|
||||
cli-truncate "^2.1.0"
|
||||
figures "^3.2.0"
|
||||
indent-string "^4.0.0"
|
||||
log-update "^4.0.0"
|
||||
p-map "^4.0.0"
|
||||
rxjs "^6.6.0"
|
||||
through "^2.3.8"
|
||||
|
||||
load-json-file@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
|
||||
@@ -10796,6 +10921,23 @@ lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
|
||||
log-symbols@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
|
||||
integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
|
||||
dependencies:
|
||||
chalk "^4.0.0"
|
||||
|
||||
log-update@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
|
||||
integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==
|
||||
dependencies:
|
||||
ansi-escapes "^4.3.0"
|
||||
cli-cursor "^3.1.0"
|
||||
slice-ansi "^4.0.0"
|
||||
wrap-ansi "^6.2.0"
|
||||
|
||||
loglevel@^1.6.8:
|
||||
version "1.6.8"
|
||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171"
|
||||
@@ -11926,6 +12068,11 @@ open@^7.0.0:
|
||||
is-docker "^2.0.0"
|
||||
is-wsl "^2.1.1"
|
||||
|
||||
opencollective-postinstall@^2.0.2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
|
||||
integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==
|
||||
|
||||
opn@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"
|
||||
@@ -12093,6 +12240,13 @@ p-map@^3.0.0:
|
||||
dependencies:
|
||||
aggregate-error "^3.0.0"
|
||||
|
||||
p-map@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
|
||||
integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==
|
||||
dependencies:
|
||||
aggregate-error "^3.0.0"
|
||||
|
||||
p-pipe@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-1.2.0.tgz#4b1a11399a11520a67790ee5a0c1d5881d6befe9"
|
||||
@@ -12455,6 +12609,13 @@ pkg-up@2.0.0, pkg-up@^2.0.0:
|
||||
dependencies:
|
||||
find-up "^2.1.0"
|
||||
|
||||
please-upgrade-node@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
|
||||
integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==
|
||||
dependencies:
|
||||
semver-compare "^1.0.0"
|
||||
|
||||
pn@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
|
||||
@@ -14223,6 +14384,13 @@ rxjs@^6.4.0, rxjs@^6.5.3:
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
rxjs@^6.6.0:
|
||||
version "6.6.2"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.2.tgz#8096a7ac03f2cc4fe5860ef6e572810d9e01c0d2"
|
||||
integrity sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
safe-buffer@5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
|
||||
@@ -14344,6 +14512,16 @@ selfsigned@^1.10.7:
|
||||
dependencies:
|
||||
node-forge "0.9.0"
|
||||
|
||||
semver-compare@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
||||
integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
|
||||
|
||||
semver-regex@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338"
|
||||
integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==
|
||||
|
||||
"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
@@ -14604,6 +14782,24 @@ slice-ansi@^2.1.0:
|
||||
astral-regex "^1.0.0"
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
|
||||
slice-ansi@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"
|
||||
integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
astral-regex "^2.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
|
||||
slice-ansi@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
|
||||
integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
astral-regex "^2.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
|
||||
slide@^1.1.6:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
|
||||
@@ -14949,6 +15145,11 @@ strict-uri-encode@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
|
||||
integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=
|
||||
|
||||
string-argv@0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
|
||||
integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
|
||||
|
||||
string-length@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
|
||||
@@ -15085,6 +15286,15 @@ string_decoder@~1.1.1:
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
stringify-object@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629"
|
||||
integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==
|
||||
dependencies:
|
||||
get-own-enumerable-property-symbols "^3.0.0"
|
||||
is-obj "^1.0.1"
|
||||
is-regexp "^1.0.0"
|
||||
|
||||
strip-ansi@5.2.0, strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
|
||||
@@ -15481,7 +15691,7 @@ through2@^3.0.0:
|
||||
dependencies:
|
||||
readable-stream "2 || 3"
|
||||
|
||||
through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6:
|
||||
through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
||||
@@ -16433,6 +16643,11 @@ which-module@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
|
||||
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
|
||||
|
||||
which-pm-runs@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
|
||||
integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=
|
||||
|
||||
which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||
|
||||
Reference in New Issue
Block a user