mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-10 15:35:49 +01:00
merge
This commit is contained in:
5
Jenkinsfile
vendored
5
Jenkinsfile
vendored
@@ -50,6 +50,11 @@ node('docker') {
|
||||
def dockerImageTag = "2.0.0-dev-${commitHash.substring(0,7)}-${BUILD_NUMBER}"
|
||||
|
||||
if (isMainBranch()) {
|
||||
|
||||
stage('Lifecycle') {
|
||||
nexusPolicyEvaluation iqApplication: selectedApplication('scm'), iqScanPatterns: [[scanPattern: 'scm-server/target/scm-server-app.zip']], iqStage: 'build'
|
||||
}
|
||||
|
||||
stage('Archive') {
|
||||
archiveArtifacts 'scm-webapp/target/scm-webapp.war'
|
||||
archiveArtifacts 'scm-server/target/scm-server-app.*'
|
||||
|
||||
21
pom.xml
21
pom.xml
@@ -351,21 +351,6 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- utils -->
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-beanutils</groupId>
|
||||
<artifactId>commons-beanutils</artifactId>
|
||||
<version>1.9.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-collections</groupId>
|
||||
<artifactId>commons-collections</artifactId>
|
||||
<version>3.2.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- http -->
|
||||
|
||||
<dependency>
|
||||
@@ -825,11 +810,11 @@
|
||||
<logback.version>1.2.3</logback.version>
|
||||
<servlet.version>3.0.1</servlet.version>
|
||||
|
||||
<jaxrs.version>2.0.1</jaxrs.version>
|
||||
<resteasy.version>3.1.3.Final</resteasy.version>
|
||||
<jaxrs.version>2.1.1</jaxrs.version>
|
||||
<resteasy.version>3.6.2.Final</resteasy.version>
|
||||
<jersey-client.version>1.19.4</jersey-client.version>
|
||||
<enunciate.version>2.11.1</enunciate.version>
|
||||
<jackson.version>2.8.6</jackson.version>
|
||||
<jackson.version>2.9.8</jackson.version>
|
||||
<guice.version>4.0</guice.version>
|
||||
<jaxb.version>2.3.0</jaxb.version>
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
<dependency>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -235,7 +236,6 @@
|
||||
<links>
|
||||
<link>http://download.oracle.com/javase/6/docs/api/</link>
|
||||
<link>http://download.oracle.com/docs/cd/E17802_01/products/products/servlet/2.5/docs/servlet-2_5-mr2/</link>
|
||||
<link>http://jersey.java.net/nonav/apidocs/${jersey.version}/jersey/</link>
|
||||
<link>https://google.github.io/guice/api-docs/${guice.version}/javadoc</link>
|
||||
<link>http://www.slf4j.org/api/</link>
|
||||
<link>http://shiro.apache.org/static/${shiro.version}/apidocs/</link>
|
||||
|
||||
@@ -36,7 +36,6 @@ package sonia.scm;
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* The base class of all handlers.
|
||||
|
||||
@@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import org.mapstruct.Mapping;
|
||||
|
||||
public abstract class BaseMapper<T, D extends HalRepresentation> extends LinkAppenderMapper implements InstantAttributeMapper {
|
||||
public abstract class BaseMapper<T, D extends HalRepresentation> extends HalAppenderMapper implements InstantAttributeMapper {
|
||||
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
public abstract D map(T modelObject);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
@@ -7,7 +8,6 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -34,16 +34,7 @@ public class ChangesetDto extends HalRepresentation {
|
||||
*/
|
||||
private String description;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
public ChangesetDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation withEmbedded(String rel, List<? extends HalRepresentation> halRepresentations) {
|
||||
return super.withEmbedded(rel, halRepresentations);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import org.mapstruct.Context;
|
||||
import org.mapstruct.Mapping;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
public interface ChangesetToChangesetDtoMapper {
|
||||
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
ChangesetDto map(Changeset changeset, @Context Repository repository);
|
||||
|
||||
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
|
||||
/**
|
||||
* The {@link LinkAppender} can be used within an {@link LinkEnricher} to append hateoas links to a json response.
|
||||
* The {@link HalAppender} can be used within an {@link HalEnricher} to append hateoas links to a json response.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface LinkAppender {
|
||||
public interface HalAppender {
|
||||
|
||||
/**
|
||||
* Appends one link to the json response.
|
||||
@@ -14,7 +16,7 @@ public interface LinkAppender {
|
||||
* @param rel name of relation
|
||||
* @param href link uri
|
||||
*/
|
||||
void appendOne(String rel, String href);
|
||||
void appendLink(String rel, String href);
|
||||
|
||||
/**
|
||||
* Returns a builder which is able to append an array of links to the resource.
|
||||
@@ -22,8 +24,15 @@ public interface LinkAppender {
|
||||
* @param rel name of link relation
|
||||
* @return multi link builder
|
||||
*/
|
||||
LinkArrayBuilder arrayBuilder(String rel);
|
||||
LinkArrayBuilder linkArrayBuilder(String rel);
|
||||
|
||||
/**
|
||||
* Appends one embedded to the json response.
|
||||
*
|
||||
* @param rel name of relation
|
||||
* @param embeddedItem embedded object
|
||||
*/
|
||||
void appendEmbedded(String rel, HalRepresentation embeddedItem);
|
||||
|
||||
/**
|
||||
* Builder for link arrays.
|
||||
@@ -4,17 +4,17 @@ import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class LinkAppenderMapper {
|
||||
public class HalAppenderMapper {
|
||||
|
||||
@Inject
|
||||
private LinkEnricherRegistry registry;
|
||||
private HalEnricherRegistry registry;
|
||||
|
||||
@VisibleForTesting
|
||||
void setRegistry(LinkEnricherRegistry registry) {
|
||||
void setRegistry(HalEnricherRegistry registry) {
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
protected void appendLinks(LinkAppender appender, Object source, Object... contextEntries) {
|
||||
protected void applyEnrichers(HalAppender appender, Object source, Object... contextEntries) {
|
||||
// null check is only their to not break existing tests
|
||||
if (registry != null) {
|
||||
|
||||
@@ -24,10 +24,10 @@ public class LinkAppenderMapper {
|
||||
ctx[i + 1] = contextEntries[i];
|
||||
}
|
||||
|
||||
LinkEnricherContext context = LinkEnricherContext.of(ctx);
|
||||
HalEnricherContext context = HalEnricherContext.of(ctx);
|
||||
|
||||
Iterable<LinkEnricher> enrichers = registry.allByType(source.getClass());
|
||||
for (LinkEnricher enricher : enrichers) {
|
||||
Iterable<HalEnricher> enrichers = registry.allByType(source.getClass());
|
||||
for (HalEnricher enricher : enrichers) {
|
||||
enricher.enrich(context, appender);
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@ package sonia.scm.api.v2.resources;
|
||||
import sonia.scm.plugin.ExtensionPoint;
|
||||
|
||||
/**
|
||||
* A {@link LinkEnricher} can be used to append hateoas links to a specific json response.
|
||||
* To register an enricher use the {@link Enrich} annotation or the {@link LinkEnricherRegistry} which is available
|
||||
* A {@link HalEnricher} can be used to append hal specific attributes, such as links, to the json response.
|
||||
* To register an enricher use the {@link Enrich} annotation or the {@link HalEnricherRegistry} which is available
|
||||
* via injection.
|
||||
*
|
||||
* <b>Warning:</b> enrichers are always registered as singletons.
|
||||
@@ -14,13 +14,13 @@ import sonia.scm.plugin.ExtensionPoint;
|
||||
*/
|
||||
@ExtensionPoint
|
||||
@FunctionalInterface
|
||||
public interface LinkEnricher {
|
||||
public interface HalEnricher {
|
||||
|
||||
/**
|
||||
* Enriches the response with hateoas links.
|
||||
* Enriches the response with hal specific attributes.
|
||||
*
|
||||
* @param context contains the source for the json mapping and related objects
|
||||
* @param appender can be used to append links to the json response
|
||||
* @param appender can be used to append links or embedded objects to the json response
|
||||
*/
|
||||
void enrich(LinkEnricherContext context, LinkAppender appender);
|
||||
void enrich(HalEnricherContext context, HalAppender appender);
|
||||
}
|
||||
@@ -7,17 +7,17 @@ import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Context object for the {@link LinkEnricher}. The context holds the source object for the json and all related
|
||||
* objects, which can be useful for the link creation.
|
||||
* Context object for the {@link HalEnricher}. The context holds the source object for the json and all related
|
||||
* objects, which can be useful for the enrichment.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public final class LinkEnricherContext {
|
||||
public final class HalEnricherContext {
|
||||
|
||||
private final Map<Class, Object> instanceMap;
|
||||
|
||||
private LinkEnricherContext(Map<Class,Object> instanceMap) {
|
||||
private HalEnricherContext(Map<Class,Object> instanceMap) {
|
||||
this.instanceMap = instanceMap;
|
||||
}
|
||||
|
||||
@@ -28,12 +28,12 @@ public final class LinkEnricherContext {
|
||||
*
|
||||
* @return context of given entries
|
||||
*/
|
||||
public static LinkEnricherContext of(Object... instances) {
|
||||
public static HalEnricherContext of(Object... instances) {
|
||||
ImmutableMap.Builder<Class, Object> builder = ImmutableMap.builder();
|
||||
for (Object instance : instances) {
|
||||
builder.put(instance.getClass(), instance);
|
||||
}
|
||||
return new LinkEnricherContext(builder.build());
|
||||
return new HalEnricherContext(builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -7,34 +7,34 @@ import sonia.scm.plugin.Extension;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* The {@link LinkEnricherRegistry} is responsible for binding {@link LinkEnricher} instances to their source types.
|
||||
* The {@link HalEnricherRegistry} is responsible for binding {@link HalEnricher} instances to their source types.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Extension
|
||||
@Singleton
|
||||
public final class LinkEnricherRegistry {
|
||||
public final class HalEnricherRegistry {
|
||||
|
||||
private final Multimap<Class, LinkEnricher> enrichers = HashMultimap.create();
|
||||
private final Multimap<Class, HalEnricher> enrichers = HashMultimap.create();
|
||||
|
||||
/**
|
||||
* Registers a new {@link LinkEnricher} for the given source type.
|
||||
* Registers a new {@link HalEnricher} for the given source type.
|
||||
*
|
||||
* @param sourceType type of json mapping source
|
||||
* @param enricher link enricher instance
|
||||
*/
|
||||
public void register(Class sourceType, LinkEnricher enricher) {
|
||||
public void register(Class sourceType, HalEnricher enricher) {
|
||||
enrichers.put(sourceType, enricher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all registered {@link LinkEnricher} for the given type.
|
||||
* Returns all registered {@link HalEnricher} for the given type.
|
||||
*
|
||||
* @param sourceType type of json mapping source
|
||||
* @return all registered enrichers
|
||||
*/
|
||||
public Iterable<LinkEnricher> allByType(Class sourceType) {
|
||||
public Iterable<HalEnricher> allByType(Class sourceType) {
|
||||
return enrichers.get(sourceType);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
/**
|
||||
* The {@link Index} object can be used to register a {@link LinkEnricher} for the index resource.
|
||||
* The {@link Index} object can be used to register a {@link HalEnricher} for the index resource.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
/**
|
||||
* The {@link Me} object can be used to register a {@link LinkEnricher} for the me resource.
|
||||
* The {@link Me} object can be used to register a {@link HalEnricher} for the me resource.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
|
||||
@@ -1,24 +1,59 @@
|
||||
package sonia.scm.filter;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sonia.scm.util.WebUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.container.ContainerResponseContext;
|
||||
import javax.ws.rs.container.ContainerResponseFilter;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.ext.WriterInterceptor;
|
||||
import javax.ws.rs.ext.WriterInterceptorContext;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Locale;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
@Provider
|
||||
@Slf4j
|
||||
public class GZipResponseFilter implements ContainerResponseFilter {
|
||||
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
|
||||
if (WebUtil.isGzipSupported(requestContext::getHeaderString)) {
|
||||
log.trace("compress output with gzip");
|
||||
GZIPOutputStream wrappedResponse = new GZIPOutputStream(responseContext.getEntityStream());
|
||||
responseContext.getHeaders().add("Content-Encoding", "gzip");
|
||||
responseContext.setEntityStream(wrappedResponse);
|
||||
@javax.ws.rs.ext.Provider
|
||||
public class GZipResponseFilter implements WriterInterceptor {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GZipResponseFilter.class);
|
||||
|
||||
private final Provider<HttpServletRequest> requestProvider;
|
||||
|
||||
@Inject
|
||||
public GZipResponseFilter(Provider<HttpServletRequest> requestProvider) {
|
||||
this.requestProvider = requestProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
|
||||
if (isGZipSupported()) {
|
||||
LOG.trace("compress output with gzip");
|
||||
encodeWithGZip(context);
|
||||
} else {
|
||||
context.proceed();
|
||||
}
|
||||
}
|
||||
|
||||
private void encodeWithGZip(WriterInterceptorContext context) throws IOException {
|
||||
context.getHeaders().remove(HttpHeaders.CONTENT_LENGTH);
|
||||
context.getHeaders().add(HttpHeaders.CONTENT_ENCODING, "gzip");
|
||||
|
||||
OutputStream outputStream = context.getOutputStream();
|
||||
GZIPOutputStream compressedOutputStream = new GZIPOutputStream(outputStream);
|
||||
context.setOutputStream(compressedOutputStream);
|
||||
try {
|
||||
context.proceed();
|
||||
} finally {
|
||||
compressedOutputStream.finish();
|
||||
context.setOutputStream(outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isGZipSupported() {
|
||||
Object encoding = requestProvider.get().getHeader(HttpHeaders.ACCEPT_ENCODING);
|
||||
return encoding != null && encoding.toString().toLowerCase(Locale.ENGLISH).contains("gzip");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ package sonia.scm.net.ahc;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.LinkedHashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
|
||||
@@ -35,8 +35,6 @@ package sonia.scm.plugin;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.Module;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.ConfigurationException;
|
||||
import sonia.scm.io.CommandResult;
|
||||
import sonia.scm.io.ExtendedCommand;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
|
||||
import java.io.File;
|
||||
@@ -67,11 +68,14 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
|
||||
LoggerFactory.getLogger(AbstractSimpleRepositoryHandler.class);
|
||||
|
||||
private final RepositoryLocationResolver repositoryLocationResolver;
|
||||
private final PluginLoader pluginLoader;
|
||||
|
||||
public AbstractSimpleRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
||||
RepositoryLocationResolver repositoryLocationResolver) {
|
||||
RepositoryLocationResolver repositoryLocationResolver,
|
||||
PluginLoader pluginLoader) {
|
||||
super(storeFactory);
|
||||
this.repositoryLocationResolver = repositoryLocationResolver;
|
||||
this.pluginLoader = pluginLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -155,7 +159,7 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
|
||||
String content = defaultContent;
|
||||
|
||||
try {
|
||||
URL url = Resources.getResource(resource);
|
||||
URL url = pluginLoader.getUberClassLoader().getResource(resource);
|
||||
|
||||
if (url != null) {
|
||||
content = Resources.toString(url, Charsets.UTF_8);
|
||||
|
||||
@@ -37,7 +37,6 @@ package sonia.scm.repository;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Objects;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import sonia.scm.security.PermissionObject;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
@@ -47,9 +46,10 @@ import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.unmodifiableCollection;
|
||||
import static java.util.Collections.unmodifiableSet;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
@@ -68,7 +68,7 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
||||
private boolean groupPermission = false;
|
||||
private String name;
|
||||
@XmlElement(name = "verb")
|
||||
private Collection<String> verbs;
|
||||
private Set<String> verbs;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link RepositoryPermission}.
|
||||
@@ -79,7 +79,7 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
||||
public RepositoryPermission(String name, Collection<String> verbs, boolean groupPermission)
|
||||
{
|
||||
this.name = name;
|
||||
this.verbs = unmodifiableCollection(new LinkedHashSet<>(verbs));
|
||||
this.verbs = unmodifiableSet(new LinkedHashSet<>(verbs));
|
||||
this.groupPermission = groupPermission;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,8 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
||||
final RepositoryPermission other = (RepositoryPermission) obj;
|
||||
|
||||
return Objects.equal(name, other.name)
|
||||
&& CollectionUtils.isEqualCollection(verbs, other.verbs)
|
||||
&& verbs.containsAll(other.verbs)
|
||||
&& verbs.size() == other.verbs.size()
|
||||
&& Objects.equal(groupPermission, other.groupPermission);
|
||||
}
|
||||
|
||||
@@ -209,6 +210,6 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
||||
*/
|
||||
public void setVerbs(Collection<String> verbs)
|
||||
{
|
||||
this.verbs = verbs;
|
||||
this.verbs = unmodifiableSet(new LinkedHashSet<>(verbs));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.google.common.base.Objects;
|
||||
import com.google.common.base.Strings;
|
||||
import sonia.scm.Validateable;
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ import com.google.common.base.Objects;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
@@ -34,8 +34,6 @@ package sonia.scm.security;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
88
scm-core/src/main/java/sonia/scm/util/Comparables.java
Normal file
88
scm-core/src/main/java/sonia/scm/util/Comparables.java
Normal file
@@ -0,0 +1,88 @@
|
||||
package sonia.scm.util;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
public final class Comparables {
|
||||
|
||||
private static final CacheLoader<Class, BeanInfo> beanInfoCacheLoader = new CacheLoader<Class, BeanInfo>() {
|
||||
@Override
|
||||
public BeanInfo load(Class type) throws IntrospectionException {
|
||||
return Introspector.getBeanInfo(type);
|
||||
}
|
||||
};
|
||||
|
||||
private static final LoadingCache<Class, BeanInfo> beanInfoCache = CacheBuilder.newBuilder()
|
||||
.maximumSize(50) // limit the cache to avoid consuming to much memory on miss usage
|
||||
.build(beanInfoCacheLoader);
|
||||
|
||||
private Comparables() {
|
||||
}
|
||||
|
||||
public static <T> Comparator<T> comparator(Class<T> type, String sortBy) {
|
||||
BeanInfo info = createBeanInfo(type);
|
||||
PropertyDescriptor propertyDescriptor = findPropertyDescriptor(sortBy, info);
|
||||
|
||||
Method readMethod = propertyDescriptor.getReadMethod();
|
||||
checkIfPropertyIsComparable(readMethod, sortBy);
|
||||
|
||||
return new MethodComparator<>(readMethod);
|
||||
}
|
||||
|
||||
private static void checkIfPropertyIsComparable(Method readMethod, String sortBy) {
|
||||
checkArgument(isReturnTypeComparable(readMethod), "property %s is not comparable", sortBy);
|
||||
}
|
||||
|
||||
private static boolean isReturnTypeComparable(Method readMethod) {
|
||||
return Comparable.class.isAssignableFrom(readMethod.getReturnType());
|
||||
}
|
||||
|
||||
private static PropertyDescriptor findPropertyDescriptor(String sortBy, BeanInfo info) {
|
||||
PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors();
|
||||
|
||||
Optional<PropertyDescriptor> sortByPropertyDescriptor = Arrays.stream(propertyDescriptors)
|
||||
.filter(p -> p.getName().equals(sortBy))
|
||||
.findFirst();
|
||||
|
||||
return sortByPropertyDescriptor.orElseThrow(() -> new IllegalArgumentException("could not find property " + sortBy));
|
||||
}
|
||||
|
||||
private static <T> BeanInfo createBeanInfo(Class<T> type) {
|
||||
return beanInfoCache.getUnchecked(type);
|
||||
}
|
||||
|
||||
private static class MethodComparator<T> implements Comparator<T> {
|
||||
|
||||
private final Method readMethod;
|
||||
|
||||
private MethodComparator(Method readMethod) {
|
||||
this.readMethod = readMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public int compare(T left, T right) {
|
||||
try {
|
||||
Comparable leftResult = (Comparable) readMethod.invoke(left);
|
||||
Comparable rightResult = (Comparable) readMethod.invoke(right);
|
||||
return leftResult.compareTo(rightResult);
|
||||
} catch (IllegalAccessException | InvocationTargetException ex) {
|
||||
throw new IllegalArgumentException("failed to invoke read method", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,51 +11,51 @@ import java.util.Optional;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LinkAppenderMapperTest {
|
||||
class HalAppenderMapperTest {
|
||||
|
||||
@Mock
|
||||
private LinkAppender appender;
|
||||
private HalAppender appender;
|
||||
|
||||
private LinkEnricherRegistry registry;
|
||||
private LinkAppenderMapper mapper;
|
||||
private HalEnricherRegistry registry;
|
||||
private HalAppenderMapper mapper;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
registry = new LinkEnricherRegistry();
|
||||
mapper = new LinkAppenderMapper();
|
||||
registry = new HalEnricherRegistry();
|
||||
mapper = new HalAppenderMapper();
|
||||
mapper.setRegistry(registry);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendSimpleLink() {
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendOne("42", "https://hitchhiker.com"));
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendLink("42", "https://hitchhiker.com"));
|
||||
|
||||
mapper.appendLinks(appender, "hello");
|
||||
mapper.applyEnrichers(appender, "hello");
|
||||
|
||||
verify(appender).appendOne("42", "https://hitchhiker.com");
|
||||
verify(appender).appendLink("42", "https://hitchhiker.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCallMultipleEnrichers() {
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendOne("42", "https://hitchhiker.com"));
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendOne("21", "https://scm.hitchhiker.com"));
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendLink("42", "https://hitchhiker.com"));
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendLink("21", "https://scm.hitchhiker.com"));
|
||||
|
||||
mapper.appendLinks(appender, "hello");
|
||||
mapper.applyEnrichers(appender, "hello");
|
||||
|
||||
verify(appender).appendOne("42", "https://hitchhiker.com");
|
||||
verify(appender).appendOne("21", "https://scm.hitchhiker.com");
|
||||
verify(appender).appendLink("42", "https://hitchhiker.com");
|
||||
verify(appender).appendLink("21", "https://scm.hitchhiker.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendLinkByUsingSourceFromContext() {
|
||||
registry.register(String.class, (ctx, appender) -> {
|
||||
Optional<String> rel = ctx.oneByType(String.class);
|
||||
appender.appendOne(rel.get(), "https://hitchhiker.com");
|
||||
appender.appendLink(rel.get(), "https://hitchhiker.com");
|
||||
});
|
||||
|
||||
mapper.appendLinks(appender, "42");
|
||||
mapper.applyEnrichers(appender, "42");
|
||||
|
||||
verify(appender).appendOne("42", "https://hitchhiker.com");
|
||||
verify(appender).appendLink("42", "https://hitchhiker.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -63,12 +63,12 @@ class LinkAppenderMapperTest {
|
||||
registry.register(Integer.class, (ctx, appender) -> {
|
||||
Optional<Integer> rel = ctx.oneByType(Integer.class);
|
||||
Optional<String> href = ctx.oneByType(String.class);
|
||||
appender.appendOne(String.valueOf(rel.get()), href.get());
|
||||
appender.appendLink(String.valueOf(rel.get()), href.get());
|
||||
});
|
||||
|
||||
mapper.appendLinks(appender, Integer.valueOf(42), "https://hitchhiker.com");
|
||||
mapper.applyEnrichers(appender, Integer.valueOf(42), "https://hitchhiker.com");
|
||||
|
||||
verify(appender).appendOne("42", "https://hitchhiker.com");
|
||||
verify(appender).appendLink("42", "https://hitchhiker.com");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,17 +7,17 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
class LinkEnricherContextTest {
|
||||
class HalEnricherContextTest {
|
||||
|
||||
@Test
|
||||
void shouldCreateContextFromSingleObject() {
|
||||
LinkEnricherContext context = LinkEnricherContext.of("hello");
|
||||
HalEnricherContext context = HalEnricherContext.of("hello");
|
||||
assertThat(context.oneByType(String.class)).contains("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateContextFromMultipleObjects() {
|
||||
LinkEnricherContext context = LinkEnricherContext.of("hello", Integer.valueOf(42), Long.valueOf(21L));
|
||||
HalEnricherContext context = HalEnricherContext.of("hello", Integer.valueOf(42), Long.valueOf(21L));
|
||||
assertThat(context.oneByType(String.class)).contains("hello");
|
||||
assertThat(context.oneByType(Integer.class)).contains(42);
|
||||
assertThat(context.oneByType(Long.class)).contains(21L);
|
||||
@@ -25,19 +25,19 @@ class LinkEnricherContextTest {
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyOptionalForUnknownTypes() {
|
||||
LinkEnricherContext context = LinkEnricherContext.of();
|
||||
HalEnricherContext context = HalEnricherContext.of();
|
||||
assertThat(context.oneByType(String.class)).isNotPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnRequiredObject() {
|
||||
LinkEnricherContext context = LinkEnricherContext.of("hello");
|
||||
HalEnricherContext context = HalEnricherContext.of("hello");
|
||||
assertThat(context.oneRequireByType(String.class)).isEqualTo("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowAnNoSuchElementExceptionForUnknownTypes() {
|
||||
LinkEnricherContext context = LinkEnricherContext.of();
|
||||
HalEnricherContext context = HalEnricherContext.of();
|
||||
assertThrows(NoSuchElementException.class, () -> context.oneRequireByType(String.class));
|
||||
}
|
||||
|
||||
@@ -5,54 +5,54 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class LinkEnricherRegistryTest {
|
||||
class HalEnricherRegistryTest {
|
||||
|
||||
private LinkEnricherRegistry registry;
|
||||
private HalEnricherRegistry registry;
|
||||
|
||||
@BeforeEach
|
||||
void setUpObjectUnderTest() {
|
||||
registry = new LinkEnricherRegistry();
|
||||
registry = new HalEnricherRegistry();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterTheEnricher() {
|
||||
SampleLinkEnricher enricher = new SampleLinkEnricher();
|
||||
SampleHalEnricher enricher = new SampleHalEnricher();
|
||||
registry.register(String.class, enricher);
|
||||
|
||||
Iterable<LinkEnricher> enrichers = registry.allByType(String.class);
|
||||
Iterable<HalEnricher> enrichers = registry.allByType(String.class);
|
||||
assertThat(enrichers).containsOnly(enricher);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterMultipleEnrichers() {
|
||||
SampleLinkEnricher one = new SampleLinkEnricher();
|
||||
SampleHalEnricher one = new SampleHalEnricher();
|
||||
registry.register(String.class, one);
|
||||
|
||||
SampleLinkEnricher two = new SampleLinkEnricher();
|
||||
SampleHalEnricher two = new SampleHalEnricher();
|
||||
registry.register(String.class, two);
|
||||
|
||||
Iterable<LinkEnricher> enrichers = registry.allByType(String.class);
|
||||
Iterable<HalEnricher> enrichers = registry.allByType(String.class);
|
||||
assertThat(enrichers).containsOnly(one, two);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterEnrichersForDifferentTypes() {
|
||||
SampleLinkEnricher one = new SampleLinkEnricher();
|
||||
SampleHalEnricher one = new SampleHalEnricher();
|
||||
registry.register(String.class, one);
|
||||
|
||||
SampleLinkEnricher two = new SampleLinkEnricher();
|
||||
SampleHalEnricher two = new SampleHalEnricher();
|
||||
registry.register(Integer.class, two);
|
||||
|
||||
Iterable<LinkEnricher> enrichers = registry.allByType(String.class);
|
||||
Iterable<HalEnricher> enrichers = registry.allByType(String.class);
|
||||
assertThat(enrichers).containsOnly(one);
|
||||
|
||||
enrichers = registry.allByType(Integer.class);
|
||||
assertThat(enrichers).containsOnly(two);
|
||||
}
|
||||
|
||||
private static class SampleLinkEnricher implements LinkEnricher {
|
||||
private static class SampleHalEnricher implements HalEnricher {
|
||||
@Override
|
||||
public void enrich(LinkEnricherContext context, LinkAppender appender) {
|
||||
public void enrich(HalEnricherContext context, HalAppender appender) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package sonia.scm.filter;
|
||||
|
||||
import com.google.inject.util.Providers;
|
||||
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 javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.ext.WriterInterceptorContext;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GZipResponseFilterTest {
|
||||
|
||||
@Mock
|
||||
private HttpServletRequest request;
|
||||
|
||||
@Mock
|
||||
private WriterInterceptorContext context;
|
||||
|
||||
@Mock
|
||||
private MultivaluedMap<String,Object> headers;
|
||||
|
||||
private GZipResponseFilter filter;
|
||||
|
||||
@BeforeEach
|
||||
void setupObjectUnderTest() {
|
||||
filter = new GZipResponseFilter(Providers.of(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipGZipCompression() throws IOException {
|
||||
when(request.getHeader(HttpHeaders.ACCEPT_ENCODING)).thenReturn("deflate, br");
|
||||
|
||||
filter.aroundWriteTo(context);
|
||||
|
||||
verifySkipped();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipGZipCompressionWithoutAcceptEncodingHeader() throws IOException {
|
||||
filter.aroundWriteTo(context);
|
||||
|
||||
verifySkipped();
|
||||
}
|
||||
|
||||
private void verifySkipped() throws IOException {
|
||||
verify(context, never()).getOutputStream();
|
||||
verify(context).proceed();
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class AcceptGZipEncoding {
|
||||
|
||||
@BeforeEach
|
||||
void setUpContext() {
|
||||
when(request.getHeader(HttpHeaders.ACCEPT_ENCODING)).thenReturn("gzip, deflate, br");
|
||||
when(context.getHeaders()).thenReturn(headers);
|
||||
when(context.getOutputStream()).thenReturn(new ByteArrayOutputStream());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEncode() throws IOException {
|
||||
filter.aroundWriteTo(context);
|
||||
|
||||
verify(headers).remove(HttpHeaders.CONTENT_LENGTH);
|
||||
verify(headers).add(HttpHeaders.CONTENT_ENCODING, "gzip");
|
||||
|
||||
verify(context).setOutputStream(any(GZIPOutputStream.class));
|
||||
verify(context, times(2)).setOutputStream(any(OutputStream.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -36,7 +36,7 @@ import com.google.common.io.ByteSource;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
@@ -46,4 +46,13 @@ class RepositoryPermissionTest {
|
||||
|
||||
assertThat(permission1).isNotEqualTo(permission2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBeEqualWithRedundantVerbs() {
|
||||
RepositoryPermission permission1 = new RepositoryPermission("name1", asList("one", "two"), false);
|
||||
RepositoryPermission permission2 = new RepositoryPermission("name1", asList("one", "two"), false);
|
||||
permission2.setVerbs(asList("one", "two", "two"));
|
||||
|
||||
assertThat(permission1).isEqualTo(permission2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package sonia.scm.security;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.apache.shiro.authc.AuthenticationInfo;
|
||||
import org.apache.shiro.authc.DisabledAccountException;
|
||||
import org.apache.shiro.authc.UnknownAccountException;
|
||||
|
||||
57
scm-core/src/test/java/sonia/scm/util/ComparablesTest.java
Normal file
57
scm-core/src/test/java/sonia/scm/util/ComparablesTest.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package sonia.scm.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import static org.assertj.core.api.Java6Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class ComparablesTest {
|
||||
|
||||
@Test
|
||||
void shouldCompare() {
|
||||
One a = new One("a");
|
||||
One b = new One("b");
|
||||
|
||||
Comparator<One> comparable = Comparables.comparator(One.class, "value");
|
||||
assertThat(comparable.compare(a, b)).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowAnExceptionForNonExistingField() {
|
||||
assertThrows(IllegalArgumentException.class, () -> Comparables.comparator(One.class, "awesome"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowAnExceptionForNonComparableField() {
|
||||
assertThrows(IllegalArgumentException.class, () -> Comparables.comparator(One.class, "nonComparable"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowAnExceptionIfTheFieldHasNoGetter() {
|
||||
assertThrows(IllegalArgumentException.class, () -> Comparables.comparator(One.class, "incredible"));
|
||||
}
|
||||
|
||||
private static class One {
|
||||
|
||||
private String value;
|
||||
private String incredible;
|
||||
private NonComparable nonComparable;
|
||||
|
||||
One(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public NonComparable getNonComparable() {
|
||||
return nonComparable;
|
||||
}
|
||||
}
|
||||
|
||||
private static class NonComparable {}
|
||||
|
||||
}
|
||||
@@ -37,9 +37,6 @@ package sonia.scm.store;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.security.KeyGenerator;
|
||||
|
||||
@@ -38,7 +38,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.GenericDAO;
|
||||
import sonia.scm.ModelObject;
|
||||
import sonia.scm.group.xml.XmlGroupDAO;
|
||||
import sonia.scm.store.ConfigurationStore;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package sonia.scm.xml;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ package sonia.scm.it;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
@@ -5,10 +5,8 @@ import io.restassured.response.Response;
|
||||
import org.junit.Assert;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -18,8 +18,6 @@ import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
|
||||
/**
|
||||
* RESTful Web Service Resource to manage the configuration of the git plugin.
|
||||
*/
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.repository.spi.GitRepositoryServiceProvider;
|
||||
import sonia.scm.schedule.Scheduler;
|
||||
import sonia.scm.schedule.Task;
|
||||
@@ -103,9 +104,10 @@ public class GitRepositoryHandler
|
||||
public GitRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
||||
Scheduler scheduler,
|
||||
RepositoryLocationResolver repositoryLocationResolver,
|
||||
GitWorkdirFactory workdirFactory)
|
||||
GitWorkdirFactory workdirFactory,
|
||||
PluginLoader pluginLoader)
|
||||
{
|
||||
super(storeFactory, repositoryLocationResolver);
|
||||
super(storeFactory, repositoryLocationResolver, pluginLoader);
|
||||
this.scheduler = scheduler;
|
||||
this.workdirFactory = workdirFactory;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ package sonia.scm.web;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.inject.Inject;
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import React from "react";
|
||||
|
||||
import {apiClient, BranchSelector, ErrorPage, Loading, SubmitButton} from "@scm-manager/ui-components";
|
||||
import {apiClient, BranchSelector, ErrorPage, Loading, Subtitle, SubmitButton} from "@scm-manager/ui-components";
|
||||
import type {Branch, Repository} from "@scm-manager/ui-types";
|
||||
import {translate} from "react-i18next";
|
||||
|
||||
@@ -113,6 +113,7 @@ class RepositoryConfig extends React.Component<Props, State> {
|
||||
if (!(loadingBranches || loadingDefaultBranch)) {
|
||||
return (
|
||||
<>
|
||||
<Subtitle subtitle={t("scm-git-plugin.repo-config.title")}/>
|
||||
{this.renderBranchChangedNotification()}
|
||||
<form onSubmit={this.submit}>
|
||||
<BranchSelector
|
||||
@@ -127,6 +128,7 @@ class RepositoryConfig extends React.Component<Props, State> {
|
||||
disabled={!this.state.selectedBranchName}
|
||||
/>
|
||||
</form>
|
||||
<hr />
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -27,14 +27,9 @@ binder.bind(
|
||||
);
|
||||
binder.bind("repos.repository-avatar", GitAvatar, gitPredicate);
|
||||
|
||||
cfgBinder.bindRepository(
|
||||
"/configuration",
|
||||
"scm-git-plugin.repo-config.link",
|
||||
"configuration",
|
||||
RepositoryConfig
|
||||
);
|
||||
// global config
|
||||
binder.bind("repo-config.route", RepositoryConfig, gitPredicate);
|
||||
|
||||
// global config
|
||||
cfgBinder.bindGlobal(
|
||||
"/git",
|
||||
"scm-git-plugin.config.link",
|
||||
|
||||
@@ -1,9 +1,56 @@
|
||||
{
|
||||
"scm-git-plugin": {
|
||||
"information": {
|
||||
"clone" : "Repository Klonen",
|
||||
"create" : "Neue Repository erstellen",
|
||||
"replace" : "Eine existierende Repository aktualisieren"
|
||||
"clone" : "Repository klonen",
|
||||
"create" : "Neues Repository erstellen",
|
||||
"replace" : "Ein bestehendes Repository aktualisieren",
|
||||
"merge": {
|
||||
"heading": "Merge des Source Branch in den Target Branch",
|
||||
"checkout": "1. Sicherstellen, dass der Workspace aufgeräumt ist und der Target Branch ausgecheckt wurde.",
|
||||
"update": "2. Update Workspace",
|
||||
"merge": "3. Merge Source Branch",
|
||||
"resolve": "4. Merge Konflikte auflösen und korrigierte Dateien dem Index hinzufügen.",
|
||||
"commit": "5. Commit",
|
||||
"push": "6. Push des Merge"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"link": "Git",
|
||||
"title": "Git Konfiguration",
|
||||
"gcExpression": "GC Cron Ausdruck",
|
||||
"gcExpressionHelpText": "Benutze Quartz Cron Ausdrücke (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK), um git GC regelmäßig auszuführen.",
|
||||
"nonFastForwardDisallowed": "Deaktiviere \"Non Fast-Forward\"",
|
||||
"nonFastForwardDisallowedHelpText": "Git Pushes ablehnen, die nicht \"fast-forward\" sind, wie \"--force\".",
|
||||
"disabled": "Deaktiviert",
|
||||
"disabledHelpText": "Aktiviere oder deaktiviere das Git Plugin",
|
||||
"submit": "Speichern"
|
||||
},
|
||||
"repo-config": {
|
||||
"link": "Konfiguration",
|
||||
"title": "Git Einstellungen",
|
||||
"default-branch": "Standard Branch",
|
||||
"submit": "Speichern",
|
||||
"error": {
|
||||
"title": "Fehler",
|
||||
"subtitle": "Ein Fehler ist aufgetreten."
|
||||
},
|
||||
"success": "Der standard Branch wurde geändert!"
|
||||
}
|
||||
},
|
||||
"permissions" : {
|
||||
"configuration": {
|
||||
"read": {
|
||||
"git": {
|
||||
"displayName": "Git Konfiguration lesen",
|
||||
"description": "Darf die git Konfiguration lesen."
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"git": {
|
||||
"displayName": "Git Konfiguration schreiben",
|
||||
"description": "Darf die git Konfiguration verändern."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
},
|
||||
"repo-config": {
|
||||
"link": "Configuration",
|
||||
"title": "Git Settings",
|
||||
"default-branch": "Default branch",
|
||||
"submit": "Submit",
|
||||
"error": {
|
||||
|
||||
@@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.GitConfig;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.mockito.Captor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.GitConfig;
|
||||
import sonia.scm.repository.GitRepositoryConfig;
|
||||
import sonia.scm.repository.GitRepositoryHandler;
|
||||
@@ -29,6 +29,7 @@ import sonia.scm.store.ConfigurationStoreFactory;
|
||||
import sonia.scm.web.GitVndMediaType;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
@@ -100,7 +101,7 @@ public class GitConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readWrite")
|
||||
public void shouldGetGitConfig() throws URISyntaxException {
|
||||
public void shouldGetGitConfig() throws URISyntaxException, UnsupportedEncodingException {
|
||||
MockHttpResponse response = get();
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
@@ -115,7 +116,7 @@ public class GitConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readWrite")
|
||||
public void shouldGetGitConfigEvenWhenItsEmpty() throws URISyntaxException {
|
||||
public void shouldGetGitConfigEvenWhenItsEmpty() throws URISyntaxException, UnsupportedEncodingException {
|
||||
when(repositoryHandler.getConfig()).thenReturn(null);
|
||||
|
||||
MockHttpResponse response = get();
|
||||
@@ -126,7 +127,7 @@ public class GitConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldGetGitConfigWithoutUpdateLink() throws URISyntaxException {
|
||||
public void shouldGetGitConfigWithoutUpdateLink() throws URISyntaxException, UnsupportedEncodingException {
|
||||
MockHttpResponse response = get();
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
@@ -159,7 +160,7 @@ public class GitConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldReadDefaultRepositoryConfig() throws URISyntaxException {
|
||||
public void shouldReadDefaultRepositoryConfig() throws URISyntaxException, UnsupportedEncodingException {
|
||||
when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(new Repository("id", "git", "space", "X"));
|
||||
|
||||
MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2 + "/space/X");
|
||||
@@ -176,7 +177,7 @@ public class GitConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldNotHaveUpdateLinkForReadOnlyUser() throws URISyntaxException {
|
||||
public void shouldNotHaveUpdateLinkForReadOnlyUser() throws URISyntaxException, UnsupportedEncodingException {
|
||||
when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(new Repository("id", "git", "space", "X"));
|
||||
|
||||
MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2 + "/space/X");
|
||||
@@ -193,7 +194,7 @@ public class GitConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldReadStoredRepositoryConfig() throws URISyntaxException {
|
||||
public void shouldReadStoredRepositoryConfig() throws URISyntaxException, UnsupportedEncodingException {
|
||||
when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(new Repository("id", "git", "space", "X"));
|
||||
GitRepositoryConfig gitRepositoryConfig = new GitRepositoryConfig();
|
||||
gitRepositoryConfig.setDefaultBranch("test");
|
||||
|
||||
@@ -11,10 +11,9 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.GitConfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -14,9 +14,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryManager;
|
||||
import sonia.scm.repository.api.Command;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
import sonia.scm.web.JsonEnricherContext;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -94,7 +94,7 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
||||
RepositoryLocationResolver locationResolver,
|
||||
File directory) {
|
||||
GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory,
|
||||
scheduler, locationResolver, gitWorkdirFactory);
|
||||
scheduler, locationResolver, gitWorkdirFactory, null);
|
||||
repositoryHandler.init(contextProvider);
|
||||
|
||||
GitConfig config = new GitConfig();
|
||||
@@ -108,7 +108,7 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
||||
@Test
|
||||
public void getDirectory() {
|
||||
GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory,
|
||||
scheduler, locationResolver, gitWorkdirFactory);
|
||||
scheduler, locationResolver, gitWorkdirFactory, null);
|
||||
GitConfig config = new GitConfig();
|
||||
config.setDisabled(false);
|
||||
config.setGcExpression("gc exp");
|
||||
|
||||
@@ -35,10 +35,8 @@ package sonia.scm.repository.spi;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||
import sonia.scm.repository.GitRepositoryConfig;
|
||||
import sonia.scm.store.InMemoryConfigurationStore;
|
||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,11 +35,9 @@ package sonia.scm.repository.spi;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import org.junit.Test;
|
||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||
import sonia.scm.repository.BlameLine;
|
||||
import sonia.scm.repository.BlameResult;
|
||||
import sonia.scm.repository.GitRepositoryConfig;
|
||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
@@ -32,11 +32,9 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import org.junit.Test;
|
||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||
import sonia.scm.repository.BrowserResult;
|
||||
import sonia.scm.repository.FileObject;
|
||||
import sonia.scm.repository.GitRepositoryConfig;
|
||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -36,10 +36,8 @@ package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import org.junit.Test;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.ChangesetPagingResult;
|
||||
import sonia.scm.repository.ClearRepositoryCacheEvent;
|
||||
import sonia.scm.repository.GitRepositoryConfig;
|
||||
import sonia.scm.repository.Modifications;
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.GitConfig;
|
||||
import sonia.scm.repository.GitRepositoryHandler;
|
||||
|
||||
|
||||
@@ -35,14 +35,12 @@ package sonia.scm.installer;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.util.IOUtil;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,7 +39,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.io.INIConfiguration;
|
||||
import sonia.scm.io.INIConfigurationReader;
|
||||
import sonia.scm.io.INIConfigurationWriter;
|
||||
import sonia.scm.io.INISection;
|
||||
import sonia.scm.util.ValidationUtil;
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ import sonia.scm.io.INIConfigurationReader;
|
||||
import sonia.scm.io.INIConfigurationWriter;
|
||||
import sonia.scm.io.INISection;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.repository.spi.HgRepositoryServiceProvider;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
import sonia.scm.util.IOUtil;
|
||||
@@ -111,9 +112,10 @@ public class HgRepositoryHandler
|
||||
@Inject
|
||||
public HgRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
||||
Provider<HgContext> hgContextProvider,
|
||||
RepositoryLocationResolver repositoryLocationResolver)
|
||||
RepositoryLocationResolver repositoryLocationResolver,
|
||||
PluginLoader pluginLoader)
|
||||
{
|
||||
super(storeFactory, repositoryLocationResolver);
|
||||
super(storeFactory, repositoryLocationResolver, pluginLoader);
|
||||
this.hgContextProvider = hgContextProvider;
|
||||
|
||||
try
|
||||
|
||||
@@ -41,7 +41,6 @@ import com.aragost.javahg.internals.AbstractCommand;
|
||||
import com.aragost.javahg.internals.HgInputStream;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import sonia.scm.repository.FileObject;
|
||||
import sonia.scm.repository.SubRepository;
|
||||
@@ -52,7 +51,6 @@ import java.io.IOException;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Mercurial command to list files of a repository.
|
||||
|
||||
@@ -44,7 +44,6 @@ import sonia.scm.web.filter.PermissionFilter;
|
||||
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -1,9 +1,48 @@
|
||||
{
|
||||
"scm-hg-plugin": {
|
||||
"information": {
|
||||
"clone" : "Repository Klonen",
|
||||
"create" : "Neue Repository erstellen",
|
||||
"replace" : "Eine existierende Repository aktualisieren"
|
||||
"clone" : "Repository klonen",
|
||||
"create" : "Neues Repository erstellen",
|
||||
"replace" : "Ein bestehendes Repository aktualisieren"
|
||||
},
|
||||
"config": {
|
||||
"link": "Mercurial",
|
||||
"title": "Mercurial Konfiguration",
|
||||
"hgBinary": "HG Binary",
|
||||
"hgBinaryHelpText": "Pfad des Mercurial Binary.",
|
||||
"pythonBinary": "Python Binary",
|
||||
"pythonBinaryHelpText": "Pfad des Python binary.",
|
||||
"pythonPath": "Python Module Such Pfad",
|
||||
"pythonPathHelpText": "Python Module Such Pfad (PYTHONPATH).",
|
||||
"encoding": "Encoding",
|
||||
"encodingHelpText": "Repository Encoding.",
|
||||
"useOptimizedBytecode": "Optimized Bytecode (.pyo)",
|
||||
"useOptimizedBytecodeHelpText": "Verwende den Python '-O' Switch.",
|
||||
"showRevisionInId": "Revision anzeigen",
|
||||
"showRevisionInIdHelpText": "Die Revision als Teil der Node ID anzeigen.",
|
||||
"enableHttpPostArgs": "HttpPostArgs Protocol aktivieren",
|
||||
"enableHttpPostArgsHelpText": "Aktiviert das experimentelle HttpPostArgs Protokoll von Mercurial. Das HttpPostArgs Protokoll verwendet den Post Request Body anstatt des HTTP Headers um Meta Informationen zu versenden. Dieses Vorgehen reduziert die Header Größe der Mercurial Requests. HttpPostArgs wird seit Mercurial 3.8 unterstützt.",
|
||||
"disableHookSSLValidation": "SSL Validierung für Hooks deaktivieren",
|
||||
"disableHookSSLValidationHelpText": "Deaktiviert die Validierung von SSL Zertifikaten für den Mercurial Hook, der die Repositoryänderungen wieder zurück an den SCM-Manager leitet. Diese Option sollte nur benutzt werden, wenn der SCM-Manager ein selbstsigniertes Zertifikat verwendet.",
|
||||
"disabled": "Deaktiviert",
|
||||
"disabledHelpText": "Aktiviert oder deaktiviert das Mercurial Plugin.",
|
||||
"required": "Dieser Konfigurationswert wird benötigt"
|
||||
}
|
||||
},
|
||||
"permissions" : {
|
||||
"configuration": {
|
||||
"read": {
|
||||
"hg": {
|
||||
"displayName": "Mercurial Konfiguration lesen",
|
||||
"description": "Darf die Mercurial Konfiguration lesen"
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"hg": {
|
||||
"displayName": "Mercurial Konfiguration schreiben",
|
||||
"description": "Darf die Mercurial Konfiguration verändern"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
"showRevisionInId": "Show Revision",
|
||||
"showRevisionInIdHelpText": "Show revision as part of the node id.",
|
||||
"enableHttpPostArgs": "Enable HttpPostArgs Protocol",
|
||||
"enableHttpPostArgsHelpText": "Disables the validation of ssl certificates for the mercurial hook, which forwards the repository changes back to scm-manager. This option should only be used, if SCM-Manager uses a self signed certificate.",
|
||||
"enableHttpPostArgsHelpText": "Enables the experimental HttpPostArgs Protocol of mercurial. The HttpPostArgs Protocol uses the body of post requests to send the meta information instead of http headers. This helps to reduce the header size of mercurial requests. HttpPostArgs is supported since mercurial 3.8.",
|
||||
"disableHookSSLValidation": "Disable SSL Validation on Hooks",
|
||||
"disableHookSSLValidationHelpText": "Enables the experimental HttpPostArgs Protocol of mercurial. The HttpPostArgs Protocol uses the body of post requests to send the meta information instead of http headers. This helps to reduce the header size of mercurial requests. HttpPostArgs is supported since mercurial 3.8.",
|
||||
"disableHookSSLValidationHelpText": "Disables the validation of ssl certificates for the mercurial hook, which forwards the repository changes back to scm-manager. This option should only be used, if SCM-Manager uses a self signed certificate.",
|
||||
"disabled": "Disabled",
|
||||
"disabledHelpText": "Enable or disable the Mercurial plugin.",
|
||||
"required": "This configuration value is required"
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.web.HgVndMediaType;
|
||||
|
||||
@@ -3,11 +3,9 @@ package sonia.scm.api.v2.resources;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.installer.HgPackage;
|
||||
import sonia.scm.installer.HgPackageReader;
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.installer.HgPackage;
|
||||
import sonia.scm.installer.HgPackages;
|
||||
|
||||
|
||||
@@ -16,15 +16,15 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.web.HgVndMediaType;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
@@ -99,7 +99,7 @@ public class HgConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readWrite")
|
||||
public void shouldGetHgConfigEvenWhenItsEmpty() throws URISyntaxException {
|
||||
public void shouldGetHgConfigEvenWhenItsEmpty() throws URISyntaxException, UnsupportedEncodingException {
|
||||
when(repositoryHandler.getConfig()).thenReturn(null);
|
||||
|
||||
MockHttpResponse response = get();
|
||||
@@ -110,7 +110,7 @@ public class HgConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldGetHgConfigWithoutUpdateLink() throws URISyntaxException {
|
||||
public void shouldGetHgConfigWithoutUpdateLink() throws URISyntaxException, UnsupportedEncodingException {
|
||||
MockHttpResponse response = get();
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
|
||||
@@ -3,8 +3,6 @@ package sonia.scm.api.v2.resources;
|
||||
import sonia.scm.installer.HgPackage;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
@@ -77,7 +77,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
||||
|
||||
@Override
|
||||
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) {
|
||||
HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver);
|
||||
HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver, null);
|
||||
|
||||
handler.init(contextProvider);
|
||||
HgTestUtil.checkForSkip(handler);
|
||||
@@ -87,7 +87,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
||||
|
||||
@Test
|
||||
public void getDirectory() {
|
||||
HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver);
|
||||
HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver, null);
|
||||
|
||||
HgConfig hgConfig = new HgConfig();
|
||||
hgConfig.setHgBinary("hg");
|
||||
|
||||
@@ -105,7 +105,7 @@ public final class HgTestUtil
|
||||
|
||||
RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(context, repoDao, new InitialRepositoryLocationResolver());
|
||||
HgRepositoryHandler handler =
|
||||
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver);
|
||||
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null);
|
||||
Path repoDir = directory.toPath();
|
||||
when(repoDao.getPath(any())).thenReturn(repoDir);
|
||||
handler.init(context);
|
||||
|
||||
@@ -8,7 +8,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@@ -48,8 +48,6 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static sonia.scm.web.WireProtocolRequestMockFactory.CMDS_HEADS_KNOWN_NODES;
|
||||
import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.BOOKMARKS;
|
||||
import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.PHASES;
|
||||
|
||||
@@ -37,7 +37,7 @@ import com.google.common.collect.Lists;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@@ -53,9 +53,9 @@ import sonia.scm.io.INIConfigurationWriter;
|
||||
import sonia.scm.io.INISection;
|
||||
import sonia.scm.logging.SVNKitLogger;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.repository.spi.HookEventFacade;
|
||||
import sonia.scm.repository.spi.SvnRepositoryServiceProvider;
|
||||
import sonia.scm.store.ConfigurationStore;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
@@ -97,9 +97,10 @@ public class SvnRepositoryHandler
|
||||
@Inject
|
||||
public SvnRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
||||
HookEventFacade eventFacade,
|
||||
RepositoryLocationResolver repositoryLocationResolver)
|
||||
RepositoryLocationResolver repositoryLocationResolver,
|
||||
PluginLoader pluginLoader)
|
||||
{
|
||||
super(storeFactory, repositoryLocationResolver);
|
||||
super(storeFactory, repositoryLocationResolver, pluginLoader);
|
||||
|
||||
// register logger
|
||||
SVNDebugLog.setDefaultLog(new SVNKitLogger());
|
||||
|
||||
@@ -36,7 +36,6 @@ package sonia.scm.repository.spi;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.tmatesoft.svn.core.SVNDirEntry;
|
||||
@@ -53,7 +52,6 @@ import sonia.scm.repository.SvnUtil;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.tmatesoft.svn.core.SVNException;
|
||||
import org.tmatesoft.svn.core.SVNLogEntry;
|
||||
import org.tmatesoft.svn.core.io.SVNRepository;
|
||||
import org.tmatesoft.svn.core.wc.SVNClientManager;
|
||||
import org.tmatesoft.svn.core.wc.admin.SVNLookClient;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.Repository;
|
||||
@@ -19,23 +21,45 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif
|
||||
super(context, repository);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Modifications getModifications(String revision) {
|
||||
Modifications modifications = null;
|
||||
log.debug("get modifications {}", revision);
|
||||
public Modifications getModifications(String revisionOrTransactionId) {
|
||||
Modifications modifications;
|
||||
try {
|
||||
long revisionNumber = SvnUtil.parseRevision(revision, repository);
|
||||
if (SvnUtil.isTransactionEntryId(revisionOrTransactionId)) {
|
||||
modifications = getModificationsFromTransaction(SvnUtil.getTransactionId(revisionOrTransactionId));
|
||||
} else {
|
||||
modifications = getModificationFromRevision(revisionOrTransactionId);
|
||||
}
|
||||
return modifications;
|
||||
} catch (SVNException ex) {
|
||||
throw new InternalRepositoryException(
|
||||
repository,
|
||||
"failed to get svn modifications for " + revisionOrTransactionId,
|
||||
ex
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Modifications getModificationFromRevision(String revision) throws SVNException {
|
||||
log.debug("get svn modifications from revision: {}", revision);
|
||||
long revisionNumber = SvnUtil.getRevisionNumber(revision, repository);
|
||||
SVNRepository repo = open();
|
||||
Collection<SVNLogEntry> entries = repo.log(null, null, revisionNumber,
|
||||
revisionNumber, true, true);
|
||||
if (Util.isNotEmpty(entries)) {
|
||||
modifications = SvnUtil.createModifications(entries.iterator().next(), revision);
|
||||
return SvnUtil.createModifications(entries.iterator().next(), revision);
|
||||
}
|
||||
} catch (SVNException ex) {
|
||||
throw new InternalRepositoryException(repository, "could not open repository", ex);
|
||||
return null;
|
||||
}
|
||||
|
||||
private Modifications getModificationsFromTransaction(String transaction) throws SVNException {
|
||||
log.debug("get svn modifications from transaction: {}", transaction);
|
||||
final Modifications modifications = new Modifications();
|
||||
SVNLookClient client = SVNClientManager.newInstance().getLookClient();
|
||||
client.doGetChanged(context.getDirectory(), transaction,
|
||||
e -> SvnUtil.appendModification(modifications, e.getType(), e.getPath()), true);
|
||||
|
||||
return modifications;
|
||||
}
|
||||
|
||||
@@ -44,5 +68,4 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif
|
||||
return getModifications(request.getRevision());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,41 @@
|
||||
"scm-svn-plugin": {
|
||||
"information": {
|
||||
"checkout": "Repository auschecken"
|
||||
},
|
||||
"config": {
|
||||
"link": "Subversion",
|
||||
"title": "Subversion Konfiguration",
|
||||
"compatibility": "Version Kompatibilität",
|
||||
"compatibilityHelpText": "Gibt an, mit welcher Subversion Version die Repositories kompatibel sind.",
|
||||
"compatibility-values": {
|
||||
"none": "Keine Kompatibilität",
|
||||
"pre14": "Vor 1.4 kompatibel",
|
||||
"pre15": "Vor 1.5 kompatibel",
|
||||
"pre16": "Vor 1.6 kompatibel",
|
||||
"pre17": "Vor 1.7 kompatibel",
|
||||
"with17": "Mit 1.7 kompatibel"
|
||||
},
|
||||
"enabledGZip": "GZip Compression aktivieren",
|
||||
"enabledGZipHelpText": "Aktiviert GZip Kompression für SVN Responses",
|
||||
"disabled": "Deaktiviert",
|
||||
"disabledHelpText": "Aktiviert oder deaktiviert das SVN Plugin",
|
||||
"required": "Dieser Konfigurationswert wird benötigt"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"configuration": {
|
||||
"read": {
|
||||
"svn": {
|
||||
"displayName": "Subversion Konfiguration lesen",
|
||||
"description": "Darf die Subversion Konfiguration lesen"
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"svn": {
|
||||
"displayName": "Subversion Konfiguration schreiben",
|
||||
"description": "Darf die Subversion Konfiguration verändern"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"link": "Subversion",
|
||||
"title": "Subversion Configuration",
|
||||
"compatibility": "Version Compatibility",
|
||||
"compatibilityHelpText": "Specifies with which subversion version repositories are compatible.",
|
||||
"compatibilityHelpText": "Specifies with which Subversion version repositories are compatible.",
|
||||
"compatibility-values": {
|
||||
"none": "No compatibility",
|
||||
"pre14": "Pre 1.4 Compatible",
|
||||
@@ -17,9 +17,9 @@
|
||||
"with17": "With 1.7 Compatible"
|
||||
},
|
||||
"enabledGZip": "Enable GZip Compression",
|
||||
"enabledGZipHelpText": "Enable GZip compression for svn responses.",
|
||||
"enabledGZipHelpText": "Enable GZip compression for SVN responses.",
|
||||
"disabled": "Disabled",
|
||||
"disabledHelpText": "Enable or disable the Git plugin",
|
||||
"disabledHelpText": "Enable or disable the SVN plugin",
|
||||
"required": "This configuration value is required"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.Compatibility;
|
||||
import sonia.scm.repository.SvnConfig;
|
||||
|
||||
|
||||
@@ -16,14 +16,14 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.SvnConfig;
|
||||
import sonia.scm.repository.SvnRepositoryHandler;
|
||||
import sonia.scm.web.SvnVndMediaType;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
@@ -98,7 +98,7 @@ public class SvnConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldGetSvnConfigWithoutUpdateLink() throws URISyntaxException {
|
||||
public void shouldGetSvnConfigWithoutUpdateLink() throws URISyntaxException, UnsupportedEncodingException {
|
||||
MockHttpResponse response = get();
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
|
||||
@@ -11,11 +11,10 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.Compatibility;
|
||||
import sonia.scm.repository.SvnConfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -32,14 +32,10 @@
|
||||
package sonia.scm.repository;
|
||||
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.api.HookContextFactory;
|
||||
import sonia.scm.repository.spi.HookEventFacade;
|
||||
import sonia.scm.store.ConfigurationStore;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
|
||||
import java.io.File;
|
||||
@@ -47,7 +43,7 @@ import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
@@ -93,7 +89,7 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
||||
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory,
|
||||
RepositoryLocationResolver locationResolver,
|
||||
File directory) {
|
||||
SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, locationResolver);
|
||||
SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, locationResolver, null);
|
||||
|
||||
handler.init(contextProvider);
|
||||
|
||||
@@ -109,7 +105,7 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
||||
public void getDirectory() {
|
||||
when(factory.withType(any())).thenCallRealMethod();
|
||||
SvnRepositoryHandler repositoryHandler = new SvnRepositoryHandler(factory,
|
||||
facade, locationResolver);
|
||||
facade, locationResolver, null);
|
||||
|
||||
SvnConfig svnConfig = new SvnConfig();
|
||||
repositoryHandler.setConfig(svnConfig);
|
||||
|
||||
@@ -39,8 +39,6 @@ import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.ChangesetPagingResult;
|
||||
import sonia.scm.repository.Modifications;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@@ -59,7 +59,7 @@ public class DummyRepositoryHandler
|
||||
private final Set<String> existingRepoNames = new HashSet<>();
|
||||
|
||||
public DummyRepositoryHandler(ConfigurationStoreFactory storeFactory, RepositoryLocationResolver repositoryLocationResolver) {
|
||||
super(storeFactory, repositoryLocationResolver);
|
||||
super(storeFactory, repositoryLocationResolver, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -13,10 +13,11 @@ const styles = {
|
||||
minWidthOfLabel: {
|
||||
minWidth: "4.5rem"
|
||||
},
|
||||
wrapper: {
|
||||
padding: "1rem 1.5rem 0.25rem 1.5rem",
|
||||
border: "1px solid #eee",
|
||||
borderRadius: "5px 5px 0 0"
|
||||
labelSizing: {
|
||||
fontSize: "1rem !important"
|
||||
},
|
||||
noBottomMargin: {
|
||||
marginBottom: "0 !important"
|
||||
}
|
||||
};
|
||||
|
||||
@@ -52,9 +53,9 @@ class BranchSelector extends React.Component<Props, State> {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"has-background-light field",
|
||||
"field",
|
||||
"is-horizontal",
|
||||
classes.wrapper
|
||||
classes.noBottomMargin
|
||||
)}
|
||||
>
|
||||
<div
|
||||
@@ -65,10 +66,14 @@ class BranchSelector extends React.Component<Props, State> {
|
||||
classes.minWidthOfLabel
|
||||
)}
|
||||
>
|
||||
<label className="label">{label}</label>
|
||||
<label className={classNames("label", classes.labelSizing)}>
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field is-narrow">
|
||||
<div
|
||||
className={classNames("field is-narrow", classes.noBottomMargin)}
|
||||
>
|
||||
<div className="control">
|
||||
<DropDown
|
||||
className="is-fullwidth"
|
||||
|
||||
@@ -3,14 +3,17 @@ import React from "react";
|
||||
|
||||
type Props = {
|
||||
displayName: string,
|
||||
url: string
|
||||
url: string,
|
||||
disabled: boolean,
|
||||
onClick?: () => void
|
||||
};
|
||||
|
||||
class DownloadButton extends React.Component<Props> {
|
||||
render() {
|
||||
const { displayName, url } = this.props;
|
||||
const { displayName, url, disabled, onClick } = this.props;
|
||||
const onClickOrDefault = !!onClick ? onClick : () => {};
|
||||
return (
|
||||
<a className="button is-large is-link" href={url}>
|
||||
<a className="button is-large is-link" href={url} disabled={disabled} onClick={onClickOrDefault}>
|
||||
<span className="icon is-medium">
|
||||
<i className="fas fa-arrow-circle-down" />
|
||||
</span>
|
||||
|
||||
@@ -36,9 +36,9 @@ class ConfigurationBinder {
|
||||
binder.bind("config.navigation", ConfigNavLink, configPredicate);
|
||||
|
||||
// route for global configuration, passes the link from the index resource to component
|
||||
const ConfigRoute = ({ url, links }) => {
|
||||
const ConfigRoute = ({ url, links, ...additionalProps }) => {
|
||||
const link = links[linkName].href;
|
||||
return this.route(url + to, <ConfigurationComponent link={link}/>);
|
||||
return this.route(url + to, <ConfigurationComponent link={link} {...additionalProps} />);
|
||||
};
|
||||
|
||||
// bind config route to extension point
|
||||
@@ -63,9 +63,36 @@ class ConfigurationBinder {
|
||||
|
||||
|
||||
// route for global configuration, passes the current repository to component
|
||||
const RepoRoute = ({url, repository}) => {
|
||||
const link = repository._links[linkName].href
|
||||
return this.route(url + to, <RepositoryComponent repository={repository} link={link}/>);
|
||||
const RepoRoute = ({url, repository, ...additionalProps}) => {
|
||||
const link = repository._links[linkName].href;
|
||||
return this.route(url + to, <RepositoryComponent repository={repository} link={link} {...additionalProps}/>);
|
||||
};
|
||||
|
||||
// bind config route to extension point
|
||||
binder.bind("repository.route", RepoRoute, repoPredicate);
|
||||
}
|
||||
|
||||
bindRepositorySetting(to: string, labelI18nKey: string, linkName: string, RepositoryComponent: any) {
|
||||
|
||||
// create predicate based on the link name of the current repository route
|
||||
// if the linkname is not available, the navigation link and the route are not bound to the extension points
|
||||
const repoPredicate = (props: Object) => {
|
||||
return props.repository && props.repository._links && props.repository._links[linkName];
|
||||
};
|
||||
|
||||
// create NavigationLink with translated label
|
||||
const RepoNavLink = translate(this.i18nNamespace)(({t, url}) => {
|
||||
return this.navLink(url + "/settings" + to, labelI18nKey, t);
|
||||
});
|
||||
|
||||
// bind navigation link to extension point
|
||||
binder.bind("repository.subnavigation", RepoNavLink, repoPredicate);
|
||||
|
||||
|
||||
// route for global configuration, passes the current repository to component
|
||||
const RepoRoute = ({url, repository, ...additionalProps}) => {
|
||||
const link = repository._links[linkName].href;
|
||||
return this.route(url + "/settings" + to, <RepositoryComponent repository={repository} link={link} {...additionalProps}/>);
|
||||
};
|
||||
|
||||
// bind config route to extension point
|
||||
|
||||
@@ -10,7 +10,8 @@ type Props = {
|
||||
buttonLabel: string,
|
||||
fieldLabel: string,
|
||||
errorMessage: string,
|
||||
helpText?: string
|
||||
helpText?: string,
|
||||
validateEntry?: string => boolean
|
||||
};
|
||||
|
||||
type State = {
|
||||
@@ -25,6 +26,15 @@ class AddEntryToTableField extends React.Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
isValid = () => {
|
||||
const {validateEntry} = this.props;
|
||||
if (!this.state.entryToAdd || this.state.entryToAdd === "" || !validateEntry) {
|
||||
return true;
|
||||
} else {
|
||||
return validateEntry(this.state.entryToAdd);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
disabled,
|
||||
@@ -39,7 +49,7 @@ class AddEntryToTableField extends React.Component<Props, State> {
|
||||
label={fieldLabel}
|
||||
errorMessage={errorMessage}
|
||||
onChange={this.handleAddEntryChange}
|
||||
validationError={false}
|
||||
validationError={!this.isValid()}
|
||||
value={this.state.entryToAdd}
|
||||
onReturnPressed={this.appendEntry}
|
||||
disabled={disabled}
|
||||
@@ -48,7 +58,7 @@ class AddEntryToTableField extends React.Component<Props, State> {
|
||||
<AddButton
|
||||
label={buttonLabel}
|
||||
action={this.addButtonClicked}
|
||||
disabled={disabled}
|
||||
disabled={disabled || this.state.entryToAdd ==="" || !this.isValid()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -28,7 +28,7 @@ class NavLink extends React.Component<Props> {
|
||||
|
||||
let showIcon = null;
|
||||
if (icon) {
|
||||
showIcon = (<><i className={icon}></i>{" "}</>);
|
||||
showIcon = (<><i className={icon} />{" "}</>);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -50,8 +50,19 @@ class PrimaryNavigation extends React.Component<Props> {
|
||||
|
||||
createNavigationItems = () => {
|
||||
const navigationItems = [];
|
||||
const { t, links } = this.props;
|
||||
|
||||
const props = {
|
||||
links,
|
||||
label: t("primary-navigation.first-menu")
|
||||
};
|
||||
|
||||
const append = this.createNavigationAppender(navigationItems);
|
||||
if (binder.hasExtension("primary-navigation.first-menu", props)) {
|
||||
navigationItems.push(
|
||||
<ExtensionPoint name="primary-navigation.first-menu" props={props} />
|
||||
);
|
||||
}
|
||||
append("/repos", "/(repo|repos)", "primary-navigation.repositories", "repositories");
|
||||
append("/users", "/(user|users)", "primary-navigation.users", "users");
|
||||
append("/groups", "/(group|groups)", "primary-navigation.groups", "groups");
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
//@flow
|
||||
import * as React from "react";
|
||||
import { Link, Route } from "react-router-dom";
|
||||
|
||||
type Props = {
|
||||
to: string,
|
||||
icon?: string,
|
||||
label: string,
|
||||
activeOnlyWhenExact?: boolean,
|
||||
activeWhenMatch?: (route: any) => boolean,
|
||||
children?: React.Node
|
||||
};
|
||||
|
||||
class SubNavigation extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
activeOnlyWhenExact: false
|
||||
};
|
||||
|
||||
isActive(route: any) {
|
||||
const { activeWhenMatch } = this.props;
|
||||
return route.match || (activeWhenMatch && activeWhenMatch(route));
|
||||
}
|
||||
|
||||
renderLink = (route: any) => {
|
||||
const { to, icon, label } = this.props;
|
||||
|
||||
let defaultIcon = "fas fa-cog";
|
||||
if (icon) {
|
||||
defaultIcon = icon;
|
||||
}
|
||||
|
||||
let children = null;
|
||||
if (this.isActive(route)) {
|
||||
children = <ul className="sub-menu">{this.props.children}</ul>;
|
||||
}
|
||||
|
||||
return (
|
||||
<li>
|
||||
<Link className={this.isActive(route) ? "is-active" : ""} to={to}>
|
||||
<i className={defaultIcon} /> {label}
|
||||
</Link>
|
||||
{children}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { to, activeOnlyWhenExact } = this.props;
|
||||
|
||||
// removes last part of url
|
||||
let parents = to.split("/");
|
||||
parents.splice(-1, 1);
|
||||
let parent = parents.join("/");
|
||||
|
||||
return (
|
||||
<Route
|
||||
path={parent}
|
||||
exact={activeOnlyWhenExact}
|
||||
children={this.renderLink}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SubNavigation;
|
||||
@@ -3,6 +3,7 @@
|
||||
export { default as NavAction } from "./NavAction.js";
|
||||
export { default as NavLink } from "./NavLink.js";
|
||||
export { default as Navigation } from "./Navigation.js";
|
||||
export { default as SubNavigation } from "./SubNavigation.js";
|
||||
export { default as PrimaryNavigation } from "./PrimaryNavigation.js";
|
||||
export { default as PrimaryNavigationLink } from "./PrimaryNavigationLink.js";
|
||||
export { default as Section } from "./Section.js";
|
||||
|
||||
@@ -25,7 +25,7 @@ class ChangesetDiff extends React.Component<Props> {
|
||||
render() {
|
||||
const { changeset, t } = this.props;
|
||||
if (!this.isDiffSupported(changeset)) {
|
||||
return <Notification type="danger">{t("changesets.diff.not-supported")}</Notification>;
|
||||
return <Notification type="danger">{t("changesets.changeset.diffNotSupported")}</Notification>;
|
||||
} else {
|
||||
const url = this.createUrl(changeset);
|
||||
return <LoadingDiff url={url} />;
|
||||
|
||||
@@ -21,7 +21,7 @@ class ChangesetList extends React.Component<Props> {
|
||||
/>
|
||||
);
|
||||
});
|
||||
return <div className="box">{content}</div>;
|
||||
return <>{content}</>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"login": {
|
||||
"title": "Anmeldung",
|
||||
"subtitle": "Bitte anmelden um fortzufahren.",
|
||||
"subtitle": "Bitte anmelden, um fortzufahren.",
|
||||
"logo-alt": "SCM-Manager",
|
||||
"username-placeholder": "Benutzername",
|
||||
"password-placeholder": "Passwort",
|
||||
@@ -10,13 +10,13 @@
|
||||
"logout": {
|
||||
"error": {
|
||||
"title": "Abmeldung fehlgeschlagen",
|
||||
"subtitle": "Während der Abmeldung ist ein Fehler aufgetreten"
|
||||
"subtitle": "Während der Abmeldung ist ein Fehler aufgetreten."
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"error": {
|
||||
"title": "Fehler",
|
||||
"subtitle": "Ein unbekannter Fehler ist aufgetreten"
|
||||
"subtitle": "Ein unbekannter Fehler ist aufgetreten."
|
||||
}
|
||||
},
|
||||
"error-notification": {
|
||||
@@ -43,8 +43,10 @@
|
||||
"previous": "Zurück"
|
||||
},
|
||||
"profile": {
|
||||
"navigation-label": "Navigation",
|
||||
"actions-label": "Aktionen",
|
||||
"navigationLabel": "Profil Navigation",
|
||||
"informationNavLink": "Information",
|
||||
"changePasswordNavLink": "Passwort ändern",
|
||||
"settingsNavLink": "Einstellungen",
|
||||
"username": "Benutzername",
|
||||
"displayName": "Anzeigename",
|
||||
"mail": "E-Mail",
|
||||
@@ -52,21 +54,21 @@
|
||||
"information": "Informationen",
|
||||
"change-password": "Passwort ändern",
|
||||
"error-title": "Fehler",
|
||||
"error-subtitle": "Das Profil kann nicht angezeigt werden",
|
||||
"error-subtitle": "Das Profil kann nicht angezeigt werden.",
|
||||
"error": "Fehler",
|
||||
"error-message": "'me' ist nicht definiert"
|
||||
},
|
||||
"password": {
|
||||
"label": "Passwort",
|
||||
"newPassword": "Neues Passwort",
|
||||
"passwordHelpText": "Plaintext Passwort des Benutzers.",
|
||||
"passwordHelpText": "Klartext Passwort des Benutzers.",
|
||||
"passwordConfirmHelpText": "Passwort zur Bestätigen wiederholen.",
|
||||
"currentPassword": "Aktuelles Passwort",
|
||||
"currentPasswordHelpText": "Dieses Passwort wird momentan bereits verwendet.",
|
||||
"confirmPassword": "Passwort wiederholen",
|
||||
"passwordInvalid": "Das Passwort muss zwischen 6 und 32 Zeichen lang sein",
|
||||
"passwordConfirmFailed": "Passwörter müssen identisch sein",
|
||||
"passwordInvalid": "Das Passwort muss zwischen 6 und 32 Zeichen lang sein!",
|
||||
"passwordConfirmFailed": "Passwörter müssen identisch sein!",
|
||||
"submit": "Speichern",
|
||||
"changedSuccessfully": "Passwort erfolgreich geändert"
|
||||
"changedSuccessfully": "Passwort erfolgreich geändert!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
{
|
||||
"config": {
|
||||
"navigation-title": "Navigation"
|
||||
},
|
||||
"global-config": {
|
||||
"navigationLabel": "Einstellungs Navigation",
|
||||
"globalConfigurationNavLink": "Globale Einstellungen",
|
||||
"title": "Einstellungen",
|
||||
"navigation-label": "Globale Einstellungen",
|
||||
"error-title": "Fehler",
|
||||
"error-subtitle": "Unbekannter Einstellungen Fehler"
|
||||
"errorTitle": "Fehler",
|
||||
"errorSubtitle": "Unbekannter Einstellungen Fehler"
|
||||
},
|
||||
"config-form": {
|
||||
"submit": "Speichern",
|
||||
@@ -47,12 +45,12 @@
|
||||
"login-attempt": {
|
||||
"name": "Anmeldeversuche",
|
||||
"login-attempt-limit": "Limit für Anmeldeversuche",
|
||||
"login-attempt-limit-timeout": "Timeout bei fehlgeschlagenen Anmeldeversuche"
|
||||
"login-attempt-limit-timeout": "Timeout bei fehlgeschlagenen Anmeldeversuchen"
|
||||
},
|
||||
"general-settings": {
|
||||
"realm-description": "Realm Beschreibung",
|
||||
"enable-repository-archive": "Repository Archiv aktivieren",
|
||||
"disable-grouping-grid": "Gruppen deaktiviern",
|
||||
"disable-grouping-grid": "Gruppen deaktivieren",
|
||||
"date-format": "Datumsformat",
|
||||
"anonymous-access-enabled": "Anonyme Zugriffe erlauben",
|
||||
"skip-failed-authenticators": "Fehlgeschlagene Authentifizierer überspringen",
|
||||
@@ -67,27 +65,27 @@
|
||||
"plugin-url-invalid": "Dies ist keine gültige URL"
|
||||
},
|
||||
"help": {
|
||||
"realmDescriptionHelpText": "Beschreibung des authentication realm",
|
||||
"dateFormatHelpText": "Moments Datumsformat. Zulässige Formate sind in der momentjs Dokumentation beschrieben.",
|
||||
"realmDescriptionHelpText": "Beschreibung des Authentication Realm.",
|
||||
"dateFormatHelpText": "Moments Datumsformat. Zulässige Formate sind in der MomentJS Dokumentation beschrieben.",
|
||||
"pluginRepositoryHelpText": "Die URL des Plugin Repositories. Beschreibung der Platzhalter: version = SCM-Manager Version; os = Betriebssystem; arch = Architektur",
|
||||
"enableForwardingHelpText": "mod_proxy Port Weiterleitung aktivieren.",
|
||||
"enableRepositoryArchiveHelpText": "Repository Archive aktivieren. Nach einer Änderung an dieser Einstellung muss die Seite komplett neu geladen werden.",
|
||||
"disableGroupingGridHelpText": "Repository Gruppen deaktivieren. Nach einer Änderung an dieser Einstellung muss die Seite komplett neu geladen werden.",
|
||||
"allowAnonymousAccessHelpText": "Anonyme Benutzer haben Zugriff auf öffentliche Repositories.",
|
||||
"skipFailedAuthenticatorsHelpText": "Die Kette der Authentifikatoren wird nicht beendet wenn ein Authentifikator einen Benutzer findet, ihn aber nicht erfolgreich authentifizieren kann.",
|
||||
"skipFailedAuthenticatorsHelpText": "Die Kette der Authentifikatoren wird nicht beendet, wenn ein Authentifikator einen Benutzer findet, ihn aber nicht erfolgreich authentifizieren kann.",
|
||||
"adminGroupsHelpText": "Namen von Gruppen mit Admin-Berechtigungen.",
|
||||
"adminUsersHelpText": "Namen von Benutzern mit Admin-Berechtigungen.",
|
||||
"forceBaseUrlHelpText": "Zugriffe, die von einer anderen URL kommen, werden auf die Base URL weiter geleitet.",
|
||||
"baseUrlHelpText": "Die URL der Applikation mit Kontextpfad, z.B. http://localhost:8080/scm",
|
||||
"loginAttemptLimitHelpText": "Maximale Anzahl von Anmeldeversuchen. Durch Verwendung von -1 wird die Begrenzung der Anmeldeversuche deaktiviert.",
|
||||
"loginAttemptLimitTimeoutHelpText": "Timeout in Sekunden für Benutzer, die vorübergehend wegen zu vieler fehlgeschlagener Anmeldeversuche deaktiviert wurden.",
|
||||
"enableProxyHelpText": "Proxy aktiviern",
|
||||
"loginAttemptLimitTimeoutHelpText": "Timeout in Sekunden für Benutzer, die vorübergehend wegen zu vieler fehlgeschlagener Anmeldeversuche, deaktiviert wurden.",
|
||||
"enableProxyHelpText": "Proxy aktivieren",
|
||||
"proxyPortHelpText": "Der Proxy Port",
|
||||
"proxyPasswordHelpText": "Das Passwort für die Proxy Server Anmeldung.",
|
||||
"proxyServerHelpText": "Der Proxy Server",
|
||||
"proxyUserHelpText": "Der Benutzername für die Proxy Server Anmeldung.",
|
||||
"proxyExcludesHelpText": "Glob patterns für Hostnamen, die von den Proxy-Einstellungen ausgeschlossen werden sollen.",
|
||||
"enableXsrfProtectionHelpText": "Xsrf Cookie Protection aktivieren. Hinweis: Dieses Feature befindet sich noch im Experimentalstatus.",
|
||||
"defaultNameSpaceStrategyHelpText": "Die Standardstrategie für Namespaces"
|
||||
"defaultNameSpaceStrategyHelpText": "Die Standardstrategie für Namespaces."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,20 +11,23 @@
|
||||
"title": "Gruppen",
|
||||
"subtitle": "Verwaltung der Gruppen"
|
||||
},
|
||||
"single-group": {
|
||||
"error-title": "Fehler",
|
||||
"error-subtitle": "Unbekannter Gruppen Fehler",
|
||||
"navigation-label": "Navigation",
|
||||
"actions-label": "Aktionen",
|
||||
"information-label": "Informationen",
|
||||
"back-label": "Zurück"
|
||||
"singleGroup": {
|
||||
"errorTitle": "Fehler",
|
||||
"errorSubtitle": "Unbekannter Gruppen Fehler",
|
||||
"menu": {
|
||||
"navigationLabel": "Gruppen Navigation",
|
||||
"informationNavLink": "Informationen",
|
||||
"settingsNavLink": "Einstellungen",
|
||||
"generalNavLink": "Generell",
|
||||
"setPermissionsNavLink": "Berechtigungen"
|
||||
}
|
||||
},
|
||||
"add-group": {
|
||||
"title": "Gruppe erstellen",
|
||||
"subtitle": "Erstllen einer neuen Gruppe"
|
||||
"subtitle": "Erstellen einer neuen Gruppe"
|
||||
},
|
||||
"create-group-button": {
|
||||
"label": "Erstellen"
|
||||
"label": "Gruppe erstellen"
|
||||
},
|
||||
"edit-group-button": {
|
||||
"label": "Bearbeiten"
|
||||
@@ -41,30 +44,28 @@
|
||||
},
|
||||
"add-member-autocomplete": {
|
||||
"placeholder": "Benutzername eingeben",
|
||||
"loading": "suche...",
|
||||
"loading": "Suche...",
|
||||
"no-options": "Kein Vorschlag für Benutzername verfügbar"
|
||||
},
|
||||
|
||||
"group-form": {
|
||||
"groupForm": {
|
||||
"subtitle": "Gruppe bearbeiten",
|
||||
"submit": "Speichern",
|
||||
"name-error": "Name ist ungültig",
|
||||
"description-error": "Beschreibung ist ungültig",
|
||||
"nameError": "Name ist ungültig",
|
||||
"descriptionError": "Beschreibung ist ungültig",
|
||||
"help": {
|
||||
"nameHelpText": "Einzigartiger Name der Gruppe",
|
||||
"nameHelpText": "Eindeutiger Name der Gruppe",
|
||||
"descriptionHelpText": "Eine kurze Beschreibung der Gruppe",
|
||||
"memberHelpText": "Benutzername des Mitglieds der Gruppe"
|
||||
}
|
||||
},
|
||||
"delete-group-button": {
|
||||
"label": "Löschen",
|
||||
"confirm-alert": {
|
||||
"deleteGroup": {
|
||||
"subtitle": "Gruppe löschen",
|
||||
"button": "Löschen",
|
||||
"confirmAlert": {
|
||||
"title": "Gruppe löschen",
|
||||
"message": "Soll die Gruppe wirklich gelöscht werden?",
|
||||
"submit": "Ja",
|
||||
"cancel": "Nein"
|
||||
}
|
||||
},
|
||||
"set-permissions-button": {
|
||||
"label": "Berechtigungen ändern"
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user