mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-10-26 08:06:09 +01:00
merge with develop
This commit is contained in:
10
CHANGELOG.md
10
CHANGELOG.md
@@ -8,11 +8,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
- Anonymous mode for the web ui ([#1284](https://github.com/scm-manager/scm-manager/pull/1284))
|
||||
|
||||
## [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))
|
||||
- Help tooltips are now multiline by default ([#1271](https://github.com/scm-manager/scm-manager/pull/1271))
|
||||
|
||||
### Fixed
|
||||
- Fixed unecessary horizontal scrollbar in modal dialogs ([#1271](https://github.com/scm-manager/scm-manager/pull/1271))
|
||||
- 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
|
||||
@@ -254,3 +259,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
|
||||
|
||||
@@ -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/
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,10 +21,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import 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,30 +86,45 @@ 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() {
|
||||
return getPathFromScmPathInfoIfAvailable().orElse(getPathFromConfiguration());
|
||||
}
|
||||
private static class LegacySupplier implements Supplier<String> {
|
||||
|
||||
private Optional<String> getPathFromScmPathInfoIfAvailable() {
|
||||
try {
|
||||
ScmPathInfoStore scmPathInfoStore = pathInfoStore.get();
|
||||
if (scmPathInfoStore != null && scmPathInfoStore.get() != null) {
|
||||
return of(scmPathInfoStore.get().getRootUri().toASCIIString());
|
||||
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());
|
||||
}
|
||||
|
||||
private Optional<String> getPathFromScmPathInfoIfAvailable() {
|
||||
try {
|
||||
ScmPathInfoStore scmPathInfoStore = pathInfoStore.get();
|
||||
if (scmPathInfoStore != null && scmPathInfoStore.get() != null) {
|
||||
return of(scmPathInfoStore.get().getRootUri().toASCIIString());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("could not get ScmPathInfoStore from context", e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("could not get ScmPathInfoStore from context", e);
|
||||
return empty();
|
||||
}
|
||||
|
||||
private String getPathFromConfiguration() {
|
||||
log.debug("using base path from configuration: {}", scmConfiguration.getBaseUrl());
|
||||
return scmConfiguration.getBaseUrl();
|
||||
}
|
||||
return empty();
|
||||
}
|
||||
|
||||
private String getPathFromConfiguration() {
|
||||
log.debug("using base path from configuration: {}", scmConfiguration.getBaseUrl());
|
||||
return scmConfiguration.getBaseUrl();
|
||||
}
|
||||
|
||||
private class ProtocolWrapper extends HttpScmProtocol {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.util;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -21,15 +21,19 @@
|
||||
* 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.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,101 +48,135 @@ 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;
|
||||
@Mock
|
||||
private ScmPathInfoStore pathInfoStore;
|
||||
@Mock
|
||||
private ScmConfiguration scmConfiguration;
|
||||
private Provider<ScmPathInfoStore> pathInfoStoreProvider;
|
||||
|
||||
@Mock
|
||||
private HttpServletRequest request;
|
||||
@Mock
|
||||
private HttpServletResponse response;
|
||||
@Mock
|
||||
private ServletConfig servletConfig;
|
||||
|
||||
private InitializingHttpScmProtocolWrapper wrapper;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
initMocks(this);
|
||||
pathInfoStoreProvider = mock(Provider.class);
|
||||
when(pathInfoStoreProvider.get()).thenReturn(pathInfoStore);
|
||||
|
||||
wrapper = new InitializingHttpScmProtocolWrapper(Providers.of(this.delegateServlet), pathInfoStoreProvider, scmConfiguration) {
|
||||
@Override
|
||||
public String getType() {
|
||||
return "git";
|
||||
}
|
||||
};
|
||||
when(scmConfiguration.getBaseUrl()).thenReturn("http://example.com/scm");
|
||||
@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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUsePathFromPathInfo() {
|
||||
mockSetPathInfo();
|
||||
@Nested
|
||||
class WithPathInfoStore {
|
||||
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
@Mock
|
||||
private ScmPathInfoStore pathInfoStore;
|
||||
@Mock
|
||||
private ScmConfiguration scmConfiguration;
|
||||
|
||||
assertEquals("http://example.com/scm/repo/space/name", httpScmProtocol.getUrl());
|
||||
}
|
||||
private Provider<ScmPathInfoStore> pathInfoStoreProvider;
|
||||
|
||||
@Test
|
||||
public void shouldUseConfigurationWhenPathInfoNotSet() {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
@Mock
|
||||
private HttpServletRequest request;
|
||||
@Mock
|
||||
private HttpServletResponse response;
|
||||
@Mock
|
||||
private ServletConfig servletConfig;
|
||||
|
||||
assertEquals("http://example.com/scm/repo/space/name", httpScmProtocol.getUrl());
|
||||
}
|
||||
@BeforeEach
|
||||
void init() {
|
||||
pathInfoStoreProvider = mock(Provider.class);
|
||||
lenient().when(pathInfoStoreProvider.get()).thenReturn(pathInfoStore);
|
||||
|
||||
@Test
|
||||
public void shouldUseConfigurationWhenNotInRequestScope() {
|
||||
when(pathInfoStoreProvider.get()).thenThrow(new ProvisionException("test"));
|
||||
wrapper = new InitializingHttpScmProtocolWrapper(Providers.of(delegateServlet), pathInfoStoreProvider, scmConfiguration) {
|
||||
@Override
|
||||
public String getType() {
|
||||
return "git";
|
||||
}
|
||||
};
|
||||
lenient().when(scmConfiguration.getBaseUrl()).thenReturn("http://example.com/scm");
|
||||
}
|
||||
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
@Test
|
||||
void shouldUsePathFromPathInfo() {
|
||||
mockSetPathInfo();
|
||||
|
||||
assertEquals("http://example.com/scm/repo/space/name", httpScmProtocol.getUrl());
|
||||
}
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
@Test
|
||||
public void shouldInitializeAndDelegateRequestThroughFilter() throws ServletException, IOException {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
assertEquals("http://example.com/scm/repo/space/name", httpScmProtocol.getUrl());
|
||||
}
|
||||
|
||||
httpScmProtocol.serve(request, response, servletConfig);
|
||||
@Test
|
||||
void shouldUseConfigurationWhenPathInfoNotSet() {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
verify(delegateServlet).init(servletConfig);
|
||||
verify(delegateServlet).service(request, response, REPOSITORY);
|
||||
}
|
||||
assertEquals("http://example.com/scm/repo/space/name", httpScmProtocol.getUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldInitializeOnlyOnce() throws ServletException, IOException {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
@Test
|
||||
void shouldUseConfigurationWhenNotInRequestScope() {
|
||||
when(pathInfoStoreProvider.get()).thenThrow(new ProvisionException("test"));
|
||||
|
||||
httpScmProtocol.serve(request, response, servletConfig);
|
||||
httpScmProtocol.serve(request, response, servletConfig);
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
verify(delegateServlet, times(1)).init(servletConfig);
|
||||
verify(delegateServlet, times(2)).service(request, response, REPOSITORY);
|
||||
}
|
||||
assertEquals("http://example.com/scm/repo/space/name", httpScmProtocol.getUrl());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldFailForIllegalScmType() {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(new Repository("", "other", "space", "name"));
|
||||
}
|
||||
@Test
|
||||
void shouldInitializeAndDelegateRequestThroughFilter() throws ServletException, IOException {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
httpScmProtocol.serve(request, response, servletConfig);
|
||||
|
||||
verify(delegateServlet).init(servletConfig);
|
||||
verify(delegateServlet).service(request, response, REPOSITORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInitializeOnlyOnce() throws ServletException, IOException {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
httpScmProtocol.serve(request, response, servletConfig);
|
||||
httpScmProtocol.serve(request, response, servletConfig);
|
||||
|
||||
verify(delegateServlet, times(1)).init(servletConfig);
|
||||
verify(delegateServlet, times(2)).service(request, response, REPOSITORY);
|
||||
}
|
||||
|
||||
@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/"));
|
||||
}
|
||||
|
||||
private OngoingStubbing<ScmPathInfo> mockSetPathInfo() {
|
||||
return when(pathInfoStore.get()).thenReturn(() -> URI.create("http://example.com/scm/api/"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.util;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -21,25 +21,23 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -21,25 +21,24 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -21,18 +21,16 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user