This commit is contained in:
Florian Scholdei
2019-02-07 13:59:57 +01:00
247 changed files with 2885 additions and 2195 deletions

5
Jenkinsfile vendored
View File

@@ -50,6 +50,11 @@ node('docker') {
def dockerImageTag = "2.0.0-dev-${commitHash.substring(0,7)}-${BUILD_NUMBER}" def dockerImageTag = "2.0.0-dev-${commitHash.substring(0,7)}-${BUILD_NUMBER}"
if (isMainBranch()) { if (isMainBranch()) {
stage('Lifecycle') {
nexusPolicyEvaluation iqApplication: selectedApplication('scm'), iqScanPatterns: [[scanPattern: 'scm-server/target/scm-server-app.zip']], iqStage: 'build'
}
stage('Archive') { stage('Archive') {
archiveArtifacts 'scm-webapp/target/scm-webapp.war' archiveArtifacts 'scm-webapp/target/scm-webapp.war'
archiveArtifacts 'scm-server/target/scm-server-app.*' archiveArtifacts 'scm-server/target/scm-server-app.*'

21
pom.xml
View File

@@ -351,21 +351,6 @@
<scope>test</scope> <scope>test</scope>
</dependency> </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 --> <!-- http -->
<dependency> <dependency>
@@ -825,11 +810,11 @@
<logback.version>1.2.3</logback.version> <logback.version>1.2.3</logback.version>
<servlet.version>3.0.1</servlet.version> <servlet.version>3.0.1</servlet.version>
<jaxrs.version>2.0.1</jaxrs.version> <jaxrs.version>2.1.1</jaxrs.version>
<resteasy.version>3.1.3.Final</resteasy.version> <resteasy.version>3.6.2.Final</resteasy.version>
<jersey-client.version>1.19.4</jersey-client.version> <jersey-client.version>1.19.4</jersey-client.version>
<enunciate.version>2.11.1</enunciate.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> <guice.version>4.0</guice.version>
<jaxb.version>2.3.0</jaxb.version> <jaxb.version>2.3.0</jaxb.version>

View File

@@ -93,6 +93,7 @@
<dependency> <dependency>
<groupId>javax.ws.rs</groupId> <groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId> <artifactId>javax.ws.rs-api</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -235,7 +236,6 @@
<links> <links>
<link>http://download.oracle.com/javase/6/docs/api/</link> <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://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>https://google.github.io/guice/api-docs/${guice.version}/javadoc</link>
<link>http://www.slf4j.org/api/</link> <link>http://www.slf4j.org/api/</link>
<link>http://shiro.apache.org/static/${shiro.version}/apidocs/</link> <link>http://shiro.apache.org/static/${shiro.version}/apidocs/</link>

View File

@@ -36,7 +36,6 @@ package sonia.scm;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException;
/** /**
* The base class of all handlers. * The base class of all handlers.

View File

@@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.HalRepresentation;
import org.mapstruct.Mapping; 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 @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
public abstract D map(T modelObject); public abstract D map(T modelObject);

View File

@@ -1,5 +1,6 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import lombok.Getter; import lombok.Getter;
@@ -7,7 +8,6 @@ import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import java.time.Instant; import java.time.Instant;
import java.util.List;
@Getter @Getter
@Setter @Setter
@@ -34,16 +34,7 @@ public class ChangesetDto extends HalRepresentation {
*/ */
private String description; private String description;
@Override public ChangesetDto(Links links, Embedded embedded) {
@SuppressWarnings("squid:S1185") // We want to have this method available in this package super(links, embedded);
protected HalRepresentation add(Links links) {
return super.add(links);
} }
@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);
}
} }

View File

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

View File

@@ -1,12 +1,14 @@
package sonia.scm.api.v2.resources; 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 * @author Sebastian Sdorra
* @since 2.0.0 * @since 2.0.0
*/ */
public interface LinkAppender { public interface HalAppender {
/** /**
* Appends one link to the json response. * Appends one link to the json response.
@@ -14,7 +16,7 @@ public interface LinkAppender {
* @param rel name of relation * @param rel name of relation
* @param href link uri * @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. * 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 * @param rel name of link relation
* @return multi link builder * @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. * Builder for link arrays.

View File

@@ -4,17 +4,17 @@ import com.google.common.annotations.VisibleForTesting;
import javax.inject.Inject; import javax.inject.Inject;
public class LinkAppenderMapper { public class HalAppenderMapper {
@Inject @Inject
private LinkEnricherRegistry registry; private HalEnricherRegistry registry;
@VisibleForTesting @VisibleForTesting
void setRegistry(LinkEnricherRegistry registry) { void setRegistry(HalEnricherRegistry registry) {
this.registry = 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 // null check is only their to not break existing tests
if (registry != null) { if (registry != null) {
@@ -24,10 +24,10 @@ public class LinkAppenderMapper {
ctx[i + 1] = contextEntries[i]; ctx[i + 1] = contextEntries[i];
} }
LinkEnricherContext context = LinkEnricherContext.of(ctx); HalEnricherContext context = HalEnricherContext.of(ctx);
Iterable<LinkEnricher> enrichers = registry.allByType(source.getClass()); Iterable<HalEnricher> enrichers = registry.allByType(source.getClass());
for (LinkEnricher enricher : enrichers) { for (HalEnricher enricher : enrichers) {
enricher.enrich(context, appender); enricher.enrich(context, appender);
} }
} }

View File

@@ -3,8 +3,8 @@ package sonia.scm.api.v2.resources;
import sonia.scm.plugin.ExtensionPoint; import sonia.scm.plugin.ExtensionPoint;
/** /**
* A {@link LinkEnricher} can be used to append hateoas links to a specific json response. * 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 LinkEnricherRegistry} which is available * To register an enricher use the {@link Enrich} annotation or the {@link HalEnricherRegistry} which is available
* via injection. * via injection.
* *
* <b>Warning:</b> enrichers are always registered as singletons. * <b>Warning:</b> enrichers are always registered as singletons.
@@ -14,13 +14,13 @@ import sonia.scm.plugin.ExtensionPoint;
*/ */
@ExtensionPoint @ExtensionPoint
@FunctionalInterface @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 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);
} }

View File

@@ -7,17 +7,17 @@ import java.util.NoSuchElementException;
import java.util.Optional; import java.util.Optional;
/** /**
* Context object for the {@link LinkEnricher}. The context holds the source object for the json and all related * 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 link creation. * objects, which can be useful for the enrichment.
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
* @since 2.0.0 * @since 2.0.0
*/ */
public final class LinkEnricherContext { public final class HalEnricherContext {
private final Map<Class, Object> instanceMap; private final Map<Class, Object> instanceMap;
private LinkEnricherContext(Map<Class,Object> instanceMap) { private HalEnricherContext(Map<Class,Object> instanceMap) {
this.instanceMap = instanceMap; this.instanceMap = instanceMap;
} }
@@ -28,12 +28,12 @@ public final class LinkEnricherContext {
* *
* @return context of given entries * @return context of given entries
*/ */
public static LinkEnricherContext of(Object... instances) { public static HalEnricherContext of(Object... instances) {
ImmutableMap.Builder<Class, Object> builder = ImmutableMap.builder(); ImmutableMap.Builder<Class, Object> builder = ImmutableMap.builder();
for (Object instance : instances) { for (Object instance : instances) {
builder.put(instance.getClass(), instance); builder.put(instance.getClass(), instance);
} }
return new LinkEnricherContext(builder.build()); return new HalEnricherContext(builder.build());
} }
/** /**

View File

@@ -7,34 +7,34 @@ import sonia.scm.plugin.Extension;
import javax.inject.Singleton; 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 * @author Sebastian Sdorra
* @since 2.0.0 * @since 2.0.0
*/ */
@Extension @Extension
@Singleton @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 sourceType type of json mapping source
* @param enricher link enricher instance * @param enricher link enricher instance
*/ */
public void register(Class sourceType, LinkEnricher enricher) { public void register(Class sourceType, HalEnricher enricher) {
enrichers.put(sourceType, 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 * @param sourceType type of json mapping source
* @return all registered enrichers * @return all registered enrichers
*/ */
public Iterable<LinkEnricher> allByType(Class sourceType) { public Iterable<HalEnricher> allByType(Class sourceType) {
return enrichers.get(sourceType); return enrichers.get(sourceType);
} }
} }

View File

@@ -1,7 +1,7 @@
package sonia.scm.api.v2.resources; 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 * @author Sebastian Sdorra
* @since 2.0.0 * @since 2.0.0

View File

@@ -1,7 +1,7 @@
package sonia.scm.api.v2.resources; 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 * @author Sebastian Sdorra
* @since 2.0.0 * @since 2.0.0

View File

@@ -1,24 +1,59 @@
package sonia.scm.filter; package sonia.scm.filter;
import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger;
import sonia.scm.util.WebUtil; import org.slf4j.LoggerFactory;
import javax.ws.rs.container.ContainerRequestContext; import javax.inject.Inject;
import javax.ws.rs.container.ContainerResponseContext; import javax.inject.Provider;
import javax.ws.rs.container.ContainerResponseFilter; import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.ext.Provider; 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.IOException;
import java.io.OutputStream;
import java.util.Locale;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
@Provider @javax.ws.rs.ext.Provider
@Slf4j public class GZipResponseFilter implements WriterInterceptor {
public class GZipResponseFilter implements ContainerResponseFilter {
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { private static final Logger LOG = LoggerFactory.getLogger(GZipResponseFilter.class);
if (WebUtil.isGzipSupported(requestContext::getHeaderString)) {
log.trace("compress output with gzip"); private final Provider<HttpServletRequest> requestProvider;
GZIPOutputStream wrappedResponse = new GZIPOutputStream(responseContext.getEntityStream());
responseContext.getHeaders().add("Content-Encoding", "gzip"); @Inject
responseContext.setEntityStream(wrappedResponse); 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");
} }
} }

View File

@@ -35,7 +35,6 @@ package sonia.scm.net.ahc;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;

View File

@@ -35,8 +35,6 @@ package sonia.scm.plugin;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Module;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
import java.util.Collection; import java.util.Collection;

View File

@@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory;
import sonia.scm.ConfigurationException; import sonia.scm.ConfigurationException;
import sonia.scm.io.CommandResult; import sonia.scm.io.CommandResult;
import sonia.scm.io.ExtendedCommand; import sonia.scm.io.ExtendedCommand;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.ConfigurationStoreFactory;
import java.io.File; import java.io.File;
@@ -67,11 +68,14 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
LoggerFactory.getLogger(AbstractSimpleRepositoryHandler.class); LoggerFactory.getLogger(AbstractSimpleRepositoryHandler.class);
private final RepositoryLocationResolver repositoryLocationResolver; private final RepositoryLocationResolver repositoryLocationResolver;
private final PluginLoader pluginLoader;
public AbstractSimpleRepositoryHandler(ConfigurationStoreFactory storeFactory, public AbstractSimpleRepositoryHandler(ConfigurationStoreFactory storeFactory,
RepositoryLocationResolver repositoryLocationResolver) { RepositoryLocationResolver repositoryLocationResolver,
PluginLoader pluginLoader) {
super(storeFactory); super(storeFactory);
this.repositoryLocationResolver = repositoryLocationResolver; this.repositoryLocationResolver = repositoryLocationResolver;
this.pluginLoader = pluginLoader;
} }
@Override @Override
@@ -155,7 +159,7 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
String content = defaultContent; String content = defaultContent;
try { try {
URL url = Resources.getResource(resource); URL url = pluginLoader.getUberClassLoader().getResource(resource);
if (url != null) { if (url != null) {
content = Resources.toString(url, Charsets.UTF_8); content = Resources.toString(url, Charsets.UTF_8);

View File

@@ -37,7 +37,6 @@ package sonia.scm.repository;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import org.apache.commons.collections.CollectionUtils;
import sonia.scm.security.PermissionObject; import sonia.scm.security.PermissionObject;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
@@ -47,9 +46,10 @@ import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Set;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableCollection; import static java.util.Collections.unmodifiableSet;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
@@ -68,7 +68,7 @@ public class RepositoryPermission implements PermissionObject, Serializable
private boolean groupPermission = false; private boolean groupPermission = false;
private String name; private String name;
@XmlElement(name = "verb") @XmlElement(name = "verb")
private Collection<String> verbs; private Set<String> verbs;
/** /**
* Constructs a new {@link RepositoryPermission}. * Constructs a new {@link RepositoryPermission}.
@@ -79,7 +79,7 @@ public class RepositoryPermission implements PermissionObject, Serializable
public RepositoryPermission(String name, Collection<String> verbs, boolean groupPermission) public RepositoryPermission(String name, Collection<String> verbs, boolean groupPermission)
{ {
this.name = name; this.name = name;
this.verbs = unmodifiableCollection(new LinkedHashSet<>(verbs)); this.verbs = unmodifiableSet(new LinkedHashSet<>(verbs));
this.groupPermission = groupPermission; this.groupPermission = groupPermission;
} }
@@ -109,7 +109,8 @@ public class RepositoryPermission implements PermissionObject, Serializable
final RepositoryPermission other = (RepositoryPermission) obj; final RepositoryPermission other = (RepositoryPermission) obj;
return Objects.equal(name, other.name) 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); && Objects.equal(groupPermission, other.groupPermission);
} }
@@ -209,6 +210,6 @@ public class RepositoryPermission implements PermissionObject, Serializable
*/ */
public void setVerbs(Collection<String> verbs) public void setVerbs(Collection<String> verbs)
{ {
this.verbs = verbs; this.verbs = unmodifiableSet(new LinkedHashSet<>(verbs));
} }
} }

View File

@@ -5,7 +5,6 @@ import com.google.common.base.Objects;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import sonia.scm.Validateable; import sonia.scm.Validateable;
import sonia.scm.repository.Person; import sonia.scm.repository.Person;
import sonia.scm.util.Util;
import java.io.Serializable; import java.io.Serializable;

View File

@@ -39,7 +39,6 @@ import com.google.common.base.Objects;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable; import java.io.Serializable;

View File

@@ -34,8 +34,6 @@ package sonia.scm.security;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
import com.google.common.base.Objects;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;

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

View File

@@ -11,51 +11,51 @@ import java.util.Optional;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class LinkAppenderMapperTest { class HalAppenderMapperTest {
@Mock @Mock
private LinkAppender appender; private HalAppender appender;
private LinkEnricherRegistry registry; private HalEnricherRegistry registry;
private LinkAppenderMapper mapper; private HalAppenderMapper mapper;
@BeforeEach @BeforeEach
void beforeEach() { void beforeEach() {
registry = new LinkEnricherRegistry(); registry = new HalEnricherRegistry();
mapper = new LinkAppenderMapper(); mapper = new HalAppenderMapper();
mapper.setRegistry(registry); mapper.setRegistry(registry);
} }
@Test @Test
void shouldAppendSimpleLink() { 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 @Test
void shouldCallMultipleEnrichers() { void shouldCallMultipleEnrichers() {
registry.register(String.class, (ctx, appender) -> appender.appendOne("42", "https://hitchhiker.com")); registry.register(String.class, (ctx, appender) -> appender.appendLink("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("21", "https://scm.hitchhiker.com"));
mapper.appendLinks(appender, "hello"); mapper.applyEnrichers(appender, "hello");
verify(appender).appendOne("42", "https://hitchhiker.com"); verify(appender).appendLink("42", "https://hitchhiker.com");
verify(appender).appendOne("21", "https://scm.hitchhiker.com"); verify(appender).appendLink("21", "https://scm.hitchhiker.com");
} }
@Test @Test
void shouldAppendLinkByUsingSourceFromContext() { void shouldAppendLinkByUsingSourceFromContext() {
registry.register(String.class, (ctx, appender) -> { registry.register(String.class, (ctx, appender) -> {
Optional<String> rel = ctx.oneByType(String.class); 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 @Test
@@ -63,12 +63,12 @@ class LinkAppenderMapperTest {
registry.register(Integer.class, (ctx, appender) -> { registry.register(Integer.class, (ctx, appender) -> {
Optional<Integer> rel = ctx.oneByType(Integer.class); Optional<Integer> rel = ctx.oneByType(Integer.class);
Optional<String> href = ctx.oneByType(String.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");
} }
} }

View File

@@ -7,17 +7,17 @@ import org.junit.jupiter.api.Test;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
class LinkEnricherContextTest { class HalEnricherContextTest {
@Test @Test
void shouldCreateContextFromSingleObject() { void shouldCreateContextFromSingleObject() {
LinkEnricherContext context = LinkEnricherContext.of("hello"); HalEnricherContext context = HalEnricherContext.of("hello");
assertThat(context.oneByType(String.class)).contains("hello"); assertThat(context.oneByType(String.class)).contains("hello");
} }
@Test @Test
void shouldCreateContextFromMultipleObjects() { 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(String.class)).contains("hello");
assertThat(context.oneByType(Integer.class)).contains(42); assertThat(context.oneByType(Integer.class)).contains(42);
assertThat(context.oneByType(Long.class)).contains(21L); assertThat(context.oneByType(Long.class)).contains(21L);
@@ -25,19 +25,19 @@ class LinkEnricherContextTest {
@Test @Test
void shouldReturnEmptyOptionalForUnknownTypes() { void shouldReturnEmptyOptionalForUnknownTypes() {
LinkEnricherContext context = LinkEnricherContext.of(); HalEnricherContext context = HalEnricherContext.of();
assertThat(context.oneByType(String.class)).isNotPresent(); assertThat(context.oneByType(String.class)).isNotPresent();
} }
@Test @Test
void shouldReturnRequiredObject() { void shouldReturnRequiredObject() {
LinkEnricherContext context = LinkEnricherContext.of("hello"); HalEnricherContext context = HalEnricherContext.of("hello");
assertThat(context.oneRequireByType(String.class)).isEqualTo("hello"); assertThat(context.oneRequireByType(String.class)).isEqualTo("hello");
} }
@Test @Test
void shouldThrowAnNoSuchElementExceptionForUnknownTypes() { void shouldThrowAnNoSuchElementExceptionForUnknownTypes() {
LinkEnricherContext context = LinkEnricherContext.of(); HalEnricherContext context = HalEnricherContext.of();
assertThrows(NoSuchElementException.class, () -> context.oneRequireByType(String.class)); assertThrows(NoSuchElementException.class, () -> context.oneRequireByType(String.class));
} }

View File

@@ -5,54 +5,54 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
class LinkEnricherRegistryTest { class HalEnricherRegistryTest {
private LinkEnricherRegistry registry; private HalEnricherRegistry registry;
@BeforeEach @BeforeEach
void setUpObjectUnderTest() { void setUpObjectUnderTest() {
registry = new LinkEnricherRegistry(); registry = new HalEnricherRegistry();
} }
@Test @Test
void shouldRegisterTheEnricher() { void shouldRegisterTheEnricher() {
SampleLinkEnricher enricher = new SampleLinkEnricher(); SampleHalEnricher enricher = new SampleHalEnricher();
registry.register(String.class, enricher); registry.register(String.class, enricher);
Iterable<LinkEnricher> enrichers = registry.allByType(String.class); Iterable<HalEnricher> enrichers = registry.allByType(String.class);
assertThat(enrichers).containsOnly(enricher); assertThat(enrichers).containsOnly(enricher);
} }
@Test @Test
void shouldRegisterMultipleEnrichers() { void shouldRegisterMultipleEnrichers() {
SampleLinkEnricher one = new SampleLinkEnricher(); SampleHalEnricher one = new SampleHalEnricher();
registry.register(String.class, one); registry.register(String.class, one);
SampleLinkEnricher two = new SampleLinkEnricher(); SampleHalEnricher two = new SampleHalEnricher();
registry.register(String.class, two); registry.register(String.class, two);
Iterable<LinkEnricher> enrichers = registry.allByType(String.class); Iterable<HalEnricher> enrichers = registry.allByType(String.class);
assertThat(enrichers).containsOnly(one, two); assertThat(enrichers).containsOnly(one, two);
} }
@Test @Test
void shouldRegisterEnrichersForDifferentTypes() { void shouldRegisterEnrichersForDifferentTypes() {
SampleLinkEnricher one = new SampleLinkEnricher(); SampleHalEnricher one = new SampleHalEnricher();
registry.register(String.class, one); registry.register(String.class, one);
SampleLinkEnricher two = new SampleLinkEnricher(); SampleHalEnricher two = new SampleHalEnricher();
registry.register(Integer.class, two); registry.register(Integer.class, two);
Iterable<LinkEnricher> enrichers = registry.allByType(String.class); Iterable<HalEnricher> enrichers = registry.allByType(String.class);
assertThat(enrichers).containsOnly(one); assertThat(enrichers).containsOnly(one);
enrichers = registry.allByType(Integer.class); enrichers = registry.allByType(Integer.class);
assertThat(enrichers).containsOnly(two); assertThat(enrichers).containsOnly(two);
} }
private static class SampleLinkEnricher implements LinkEnricher { private static class SampleHalEnricher implements HalEnricher {
@Override @Override
public void enrich(LinkEnricherContext context, LinkAppender appender) { public void enrich(HalEnricherContext context, HalAppender appender) {
} }
} }

View File

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

View File

@@ -36,7 +36,7 @@ import com.google.common.io.ByteSource;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;

View File

@@ -46,4 +46,13 @@ class RepositoryPermissionTest {
assertThat(permission1).isNotEqualTo(permission2); 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);
}
} }

View File

@@ -1,7 +1,6 @@
package sonia.scm.security; package sonia.scm.security;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.DisabledAccountException; import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UnknownAccountException;

View 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 {}
}

View File

@@ -37,9 +37,6 @@ package sonia.scm.store;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider; import sonia.scm.SCMContextProvider;
import sonia.scm.repository.RepositoryLocationResolver; import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.security.KeyGenerator; import sonia.scm.security.KeyGenerator;

View File

@@ -38,7 +38,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.GenericDAO; import sonia.scm.GenericDAO;
import sonia.scm.ModelObject; import sonia.scm.ModelObject;
import sonia.scm.group.xml.XmlGroupDAO;
import sonia.scm.store.ConfigurationStore; import sonia.scm.store.ConfigurationStore;
import java.util.Collection; import java.util.Collection;

View File

@@ -1,6 +1,5 @@
package sonia.scm.xml; package sonia.scm.xml;
import com.google.common.base.Charsets;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;

View File

@@ -36,7 +36,6 @@ package sonia.scm.it;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;

View File

@@ -5,10 +5,8 @@ import io.restassured.response.Response;
import org.junit.Assert; import org.junit.Assert;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.user.User;
import sonia.scm.web.VndMediaType; import sonia.scm.web.VndMediaType;
import java.net.ConnectException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;

View File

@@ -18,8 +18,6 @@ import javax.ws.rs.PathParam;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.core.Response; 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. * RESTful Web Service Resource to manage the configuration of the git plugin.
*/ */

View File

@@ -44,6 +44,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider; import sonia.scm.SCMContextProvider;
import sonia.scm.plugin.Extension; import sonia.scm.plugin.Extension;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.repository.spi.GitRepositoryServiceProvider; import sonia.scm.repository.spi.GitRepositoryServiceProvider;
import sonia.scm.schedule.Scheduler; import sonia.scm.schedule.Scheduler;
import sonia.scm.schedule.Task; import sonia.scm.schedule.Task;
@@ -103,9 +104,10 @@ public class GitRepositoryHandler
public GitRepositoryHandler(ConfigurationStoreFactory storeFactory, public GitRepositoryHandler(ConfigurationStoreFactory storeFactory,
Scheduler scheduler, Scheduler scheduler,
RepositoryLocationResolver repositoryLocationResolver, RepositoryLocationResolver repositoryLocationResolver,
GitWorkdirFactory workdirFactory) GitWorkdirFactory workdirFactory,
PluginLoader pluginLoader)
{ {
super(storeFactory, repositoryLocationResolver); super(storeFactory, repositoryLocationResolver, pluginLoader);
this.scheduler = scheduler; this.scheduler = scheduler;
this.workdirFactory = workdirFactory; this.workdirFactory = workdirFactory;
} }

View File

@@ -35,7 +35,6 @@ package sonia.scm.web;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.inject.Inject; import com.google.inject.Inject;
import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.errors.RepositoryNotFoundException;

View File

@@ -2,7 +2,7 @@
import React from "react"; 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 type {Branch, Repository} from "@scm-manager/ui-types";
import {translate} from "react-i18next"; import {translate} from "react-i18next";
@@ -113,6 +113,7 @@ class RepositoryConfig extends React.Component<Props, State> {
if (!(loadingBranches || loadingDefaultBranch)) { if (!(loadingBranches || loadingDefaultBranch)) {
return ( return (
<> <>
<Subtitle subtitle={t("scm-git-plugin.repo-config.title")}/>
{this.renderBranchChangedNotification()} {this.renderBranchChangedNotification()}
<form onSubmit={this.submit}> <form onSubmit={this.submit}>
<BranchSelector <BranchSelector
@@ -127,6 +128,7 @@ class RepositoryConfig extends React.Component<Props, State> {
disabled={!this.state.selectedBranchName} disabled={!this.state.selectedBranchName}
/> />
</form> </form>
<hr />
</> </>
); );
} else { } else {

View File

@@ -27,14 +27,9 @@ binder.bind(
); );
binder.bind("repos.repository-avatar", GitAvatar, gitPredicate); binder.bind("repos.repository-avatar", GitAvatar, gitPredicate);
cfgBinder.bindRepository( binder.bind("repo-config.route", RepositoryConfig, gitPredicate);
"/configuration",
"scm-git-plugin.repo-config.link",
"configuration",
RepositoryConfig
);
// global config
// global config
cfgBinder.bindGlobal( cfgBinder.bindGlobal(
"/git", "/git",
"scm-git-plugin.config.link", "scm-git-plugin.config.link",

View File

@@ -1,9 +1,56 @@
{ {
"scm-git-plugin": { "scm-git-plugin": {
"information": { "information": {
"clone" : "Repository Klonen", "clone" : "Repository klonen",
"create" : "Neue Repository erstellen", "create" : "Neues Repository erstellen",
"replace" : "Eine existierende Repository aktualisieren" "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."
}
}
} }
} }
} }

View File

@@ -27,6 +27,7 @@
}, },
"repo-config": { "repo-config": {
"link": "Configuration", "link": "Configuration",
"title": "Git Settings",
"default-branch": "Default branch", "default-branch": "Default branch",
"submit": "Submit", "submit": "Submit",
"error": { "error": {

View File

@@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.GitConfig; import sonia.scm.repository.GitConfig;
import static org.junit.Assert.*; import static org.junit.Assert.*;

View File

@@ -17,7 +17,7 @@ import org.mockito.Captor;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Spy; import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.GitConfig; import sonia.scm.repository.GitConfig;
import sonia.scm.repository.GitRepositoryConfig; import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.GitRepositoryHandler;
@@ -29,6 +29,7 @@ import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.web.GitVndMediaType; import sonia.scm.web.GitVndMediaType;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@@ -100,7 +101,7 @@ public class GitConfigResourceTest {
@Test @Test
@SubjectAware(username = "readWrite") @SubjectAware(username = "readWrite")
public void shouldGetGitConfig() throws URISyntaxException { public void shouldGetGitConfig() throws URISyntaxException, UnsupportedEncodingException {
MockHttpResponse response = get(); MockHttpResponse response = get();
assertEquals(HttpServletResponse.SC_OK, response.getStatus()); assertEquals(HttpServletResponse.SC_OK, response.getStatus());
@@ -115,7 +116,7 @@ public class GitConfigResourceTest {
@Test @Test
@SubjectAware(username = "readWrite") @SubjectAware(username = "readWrite")
public void shouldGetGitConfigEvenWhenItsEmpty() throws URISyntaxException { public void shouldGetGitConfigEvenWhenItsEmpty() throws URISyntaxException, UnsupportedEncodingException {
when(repositoryHandler.getConfig()).thenReturn(null); when(repositoryHandler.getConfig()).thenReturn(null);
MockHttpResponse response = get(); MockHttpResponse response = get();
@@ -126,7 +127,7 @@ public class GitConfigResourceTest {
@Test @Test
@SubjectAware(username = "readOnly") @SubjectAware(username = "readOnly")
public void shouldGetGitConfigWithoutUpdateLink() throws URISyntaxException { public void shouldGetGitConfigWithoutUpdateLink() throws URISyntaxException, UnsupportedEncodingException {
MockHttpResponse response = get(); MockHttpResponse response = get();
assertEquals(HttpServletResponse.SC_OK, response.getStatus()); assertEquals(HttpServletResponse.SC_OK, response.getStatus());
@@ -159,7 +160,7 @@ public class GitConfigResourceTest {
@Test @Test
@SubjectAware(username = "writeOnly") @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")); 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"); MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2 + "/space/X");
@@ -176,7 +177,7 @@ public class GitConfigResourceTest {
@Test @Test
@SubjectAware(username = "readOnly") @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")); 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"); MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2 + "/space/X");
@@ -193,7 +194,7 @@ public class GitConfigResourceTest {
@Test @Test
@SubjectAware(username = "writeOnly") @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")); when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(new Repository("id", "git", "space", "X"));
GitRepositoryConfig gitRepositoryConfig = new GitRepositoryConfig(); GitRepositoryConfig gitRepositoryConfig = new GitRepositoryConfig();
gitRepositoryConfig.setDefaultBranch("test"); gitRepositoryConfig.setDefaultBranch("test");

View File

@@ -11,10 +11,9 @@ import org.junit.runner.RunWith;
import org.mockito.Answers; import org.mockito.Answers;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.GitConfig; import sonia.scm.repository.GitConfig;
import java.io.File;
import java.net.URI; import java.net.URI;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;

View File

@@ -14,9 +14,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager; 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.JsonEnricherContext;
import sonia.scm.web.VndMediaType; import sonia.scm.web.VndMediaType;

View File

@@ -40,7 +40,7 @@ import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;

View File

@@ -94,7 +94,7 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
RepositoryLocationResolver locationResolver, RepositoryLocationResolver locationResolver,
File directory) { File directory) {
GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory, GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory,
scheduler, locationResolver, gitWorkdirFactory); scheduler, locationResolver, gitWorkdirFactory, null);
repositoryHandler.init(contextProvider); repositoryHandler.init(contextProvider);
GitConfig config = new GitConfig(); GitConfig config = new GitConfig();
@@ -108,7 +108,7 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Test @Test
public void getDirectory() { public void getDirectory() {
GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory, GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory,
scheduler, locationResolver, gitWorkdirFactory); scheduler, locationResolver, gitWorkdirFactory, null);
GitConfig config = new GitConfig(); GitConfig config = new GitConfig();
config.setDisabled(false); config.setDisabled(false);
config.setGcExpression("gc exp"); config.setGcExpression("gc exp");

View File

@@ -35,10 +35,8 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import org.junit.After; import org.junit.After;
import org.junit.Before;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.GitRepositoryConfig; import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.store.InMemoryConfigurationStore;
import sonia.scm.store.InMemoryConfigurationStoreFactory; import sonia.scm.store.InMemoryConfigurationStoreFactory;
/** /**

View File

@@ -35,11 +35,9 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import org.junit.Test; import org.junit.Test;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.BlameLine; import sonia.scm.repository.BlameLine;
import sonia.scm.repository.BlameResult; import sonia.scm.repository.BlameResult;
import sonia.scm.repository.GitRepositoryConfig; import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.store.InMemoryConfigurationStoreFactory;
import java.io.IOException; import java.io.IOException;

View File

@@ -32,11 +32,9 @@
package sonia.scm.repository.spi; package sonia.scm.repository.spi;
import org.junit.Test; import org.junit.Test;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.BrowserResult; import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.FileObject; import sonia.scm.repository.FileObject;
import sonia.scm.repository.GitRepositoryConfig; import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.store.InMemoryConfigurationStoreFactory;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;

View File

@@ -36,10 +36,8 @@ package sonia.scm.repository.spi;
import com.google.common.io.Files; import com.google.common.io.Files;
import org.junit.Test; import org.junit.Test;
import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.Changeset; import sonia.scm.repository.Changeset;
import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.ClearRepositoryCacheEvent;
import sonia.scm.repository.GitRepositoryConfig; import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.repository.Modifications; import sonia.scm.repository.Modifications;

View File

@@ -42,7 +42,7 @@ import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.GitConfig; import sonia.scm.repository.GitConfig;
import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.GitRepositoryHandler;

View File

@@ -35,14 +35,12 @@ package sonia.scm.installer;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.util.IOUtil;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
import java.io.File; import java.io.File;
import java.io.IOException;
import sonia.scm.net.ahc.AdvancedHttpClient; import sonia.scm.net.ahc.AdvancedHttpClient;
/** /**

View File

@@ -39,7 +39,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.io.INIConfiguration; import sonia.scm.io.INIConfiguration;
import sonia.scm.io.INIConfigurationReader; import sonia.scm.io.INIConfigurationReader;
import sonia.scm.io.INIConfigurationWriter;
import sonia.scm.io.INISection; import sonia.scm.io.INISection;
import sonia.scm.util.ValidationUtil; import sonia.scm.util.ValidationUtil;

View File

@@ -51,6 +51,7 @@ import sonia.scm.io.INIConfigurationReader;
import sonia.scm.io.INIConfigurationWriter; import sonia.scm.io.INIConfigurationWriter;
import sonia.scm.io.INISection; import sonia.scm.io.INISection;
import sonia.scm.plugin.Extension; import sonia.scm.plugin.Extension;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.repository.spi.HgRepositoryServiceProvider; import sonia.scm.repository.spi.HgRepositoryServiceProvider;
import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.util.IOUtil; import sonia.scm.util.IOUtil;
@@ -111,9 +112,10 @@ public class HgRepositoryHandler
@Inject @Inject
public HgRepositoryHandler(ConfigurationStoreFactory storeFactory, public HgRepositoryHandler(ConfigurationStoreFactory storeFactory,
Provider<HgContext> hgContextProvider, Provider<HgContext> hgContextProvider,
RepositoryLocationResolver repositoryLocationResolver) RepositoryLocationResolver repositoryLocationResolver,
PluginLoader pluginLoader)
{ {
super(storeFactory, repositoryLocationResolver); super(storeFactory, repositoryLocationResolver, pluginLoader);
this.hgContextProvider = hgContextProvider; this.hgContextProvider = hgContextProvider;
try try

View File

@@ -41,7 +41,6 @@ import com.aragost.javahg.internals.AbstractCommand;
import com.aragost.javahg.internals.HgInputStream; import com.aragost.javahg.internals.HgInputStream;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import sonia.scm.repository.FileObject; import sonia.scm.repository.FileObject;
import sonia.scm.repository.SubRepository; import sonia.scm.repository.SubRepository;
@@ -52,7 +51,6 @@ import java.io.IOException;
import java.util.Deque; import java.util.Deque;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List;
/** /**
* Mercurial command to list files of a repository. * Mercurial command to list files of a repository.

View File

@@ -44,7 +44,6 @@ import sonia.scm.web.filter.PermissionFilter;
import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.HgRepositoryHandler;
import javax.servlet.FilterChain;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.Set; import java.util.Set;

View File

@@ -1,9 +1,48 @@
{ {
"scm-hg-plugin": { "scm-hg-plugin": {
"information": { "information": {
"clone" : "Repository Klonen", "clone" : "Repository klonen",
"create" : "Neue Repository erstellen", "create" : "Neues Repository erstellen",
"replace" : "Eine existierende Repository aktualisieren" "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"
}
}
} }
} }
} }

View File

@@ -21,9 +21,9 @@
"showRevisionInId": "Show Revision", "showRevisionInId": "Show Revision",
"showRevisionInIdHelpText": "Show revision as part of the node id.", "showRevisionInIdHelpText": "Show revision as part of the node id.",
"enableHttpPostArgs": "Enable HttpPostArgs Protocol", "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", "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", "disabled": "Disabled",
"disabledHelpText": "Enable or disable the Mercurial plugin.", "disabledHelpText": "Enable or disable the Mercurial plugin.",
"required": "This configuration value is required" "required": "This configuration value is required"

View File

@@ -14,7 +14,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.web.HgVndMediaType; import sonia.scm.web.HgVndMediaType;

View File

@@ -3,11 +3,9 @@ package sonia.scm.api.v2.resources;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgConfig;
import java.io.File;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;

View File

@@ -14,7 +14,7 @@ import org.junit.runner.RunWith;
import org.mockito.Answers; import org.mockito.Answers;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import javax.inject.Provider; import javax.inject.Provider;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;

View File

@@ -6,7 +6,7 @@ import org.junit.runner.RunWith;
import org.mockito.Answers; import org.mockito.Answers;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import java.net.URI; import java.net.URI;
import java.util.Arrays; import java.util.Arrays;

View File

@@ -17,7 +17,7 @@ import org.junit.runner.RunWith;
import org.mockito.Answers; import org.mockito.Answers;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.installer.HgPackage; import sonia.scm.installer.HgPackage;
import sonia.scm.installer.HgPackageReader; import sonia.scm.installer.HgPackageReader;
import sonia.scm.net.ahc.AdvancedHttpClient; import sonia.scm.net.ahc.AdvancedHttpClient;

View File

@@ -6,7 +6,7 @@ import org.junit.runner.RunWith;
import org.mockito.Answers; import org.mockito.Answers;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.installer.HgPackage; import sonia.scm.installer.HgPackage;
import sonia.scm.installer.HgPackages; import sonia.scm.installer.HgPackages;

View File

@@ -16,15 +16,15 @@ import org.junit.runner.RunWith;
import org.mockito.Answers; import org.mockito.Answers;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.web.HgVndMediaType; import sonia.scm.web.HgVndMediaType;
import javax.inject.Provider; import javax.inject.Provider;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@@ -99,7 +99,7 @@ public class HgConfigResourceTest {
@Test @Test
@SubjectAware(username = "readWrite") @SubjectAware(username = "readWrite")
public void shouldGetHgConfigEvenWhenItsEmpty() throws URISyntaxException { public void shouldGetHgConfigEvenWhenItsEmpty() throws URISyntaxException, UnsupportedEncodingException {
when(repositoryHandler.getConfig()).thenReturn(null); when(repositoryHandler.getConfig()).thenReturn(null);
MockHttpResponse response = get(); MockHttpResponse response = get();
@@ -110,7 +110,7 @@ public class HgConfigResourceTest {
@Test @Test
@SubjectAware(username = "readOnly") @SubjectAware(username = "readOnly")
public void shouldGetHgConfigWithoutUpdateLink() throws URISyntaxException { public void shouldGetHgConfigWithoutUpdateLink() throws URISyntaxException, UnsupportedEncodingException {
MockHttpResponse response = get(); MockHttpResponse response = get();
assertEquals(HttpServletResponse.SC_OK, response.getStatus()); assertEquals(HttpServletResponse.SC_OK, response.getStatus());

View File

@@ -3,8 +3,6 @@ package sonia.scm.api.v2.resources;
import sonia.scm.installer.HgPackage; import sonia.scm.installer.HgPackage;
import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgConfig;
import java.io.File;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;

View File

@@ -11,7 +11,7 @@ import org.junit.runner.RunWith;
import org.mockito.Answers; import org.mockito.Answers;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgConfig;
import java.net.URI; import java.net.URI;

View File

@@ -77,7 +77,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Override @Override
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) { 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); handler.init(contextProvider);
HgTestUtil.checkForSkip(handler); HgTestUtil.checkForSkip(handler);
@@ -87,7 +87,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
@Test @Test
public void getDirectory() { public void getDirectory() {
HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver); HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver, null);
HgConfig hgConfig = new HgConfig(); HgConfig hgConfig = new HgConfig();
hgConfig.setHgBinary("hg"); hgConfig.setHgBinary("hg");

View File

@@ -105,7 +105,7 @@ public final class HgTestUtil
RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(context, repoDao, new InitialRepositoryLocationResolver()); RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(context, repoDao, new InitialRepositoryLocationResolver());
HgRepositoryHandler handler = HgRepositoryHandler handler =
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver); new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null);
Path repoDir = directory.toPath(); Path repoDir = directory.toPath();
when(repoDao.getPath(any())).thenReturn(repoDir); when(repoDao.getPath(any())).thenReturn(repoDir);
handler.init(context); handler.init(context);

View File

@@ -8,7 +8,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; 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.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;

View File

@@ -48,8 +48,6 @@ import javax.servlet.http.HttpServletRequest;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*; 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.CMDS_HEADS_KNOWN_NODES;
import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.BOOKMARKS; import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.BOOKMARKS;
import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.PHASES; import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.PHASES;

View File

@@ -37,7 +37,7 @@ import com.google.common.collect.Lists;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import javax.servlet.ServletInputStream; import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;

View File

@@ -53,9 +53,9 @@ import sonia.scm.io.INIConfigurationWriter;
import sonia.scm.io.INISection; import sonia.scm.io.INISection;
import sonia.scm.logging.SVNKitLogger; import sonia.scm.logging.SVNKitLogger;
import sonia.scm.plugin.Extension; import sonia.scm.plugin.Extension;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.repository.spi.HookEventFacade; import sonia.scm.repository.spi.HookEventFacade;
import sonia.scm.repository.spi.SvnRepositoryServiceProvider; import sonia.scm.repository.spi.SvnRepositoryServiceProvider;
import sonia.scm.store.ConfigurationStore;
import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.util.Util; import sonia.scm.util.Util;
@@ -97,9 +97,10 @@ public class SvnRepositoryHandler
@Inject @Inject
public SvnRepositoryHandler(ConfigurationStoreFactory storeFactory, public SvnRepositoryHandler(ConfigurationStoreFactory storeFactory,
HookEventFacade eventFacade, HookEventFacade eventFacade,
RepositoryLocationResolver repositoryLocationResolver) RepositoryLocationResolver repositoryLocationResolver,
PluginLoader pluginLoader)
{ {
super(storeFactory, repositoryLocationResolver); super(storeFactory, repositoryLocationResolver, pluginLoader);
// register logger // register logger
SVNDebugLog.setDefaultLog(new SVNKitLogger()); SVNDebugLog.setDefaultLog(new SVNKitLogger());

View File

@@ -36,7 +36,6 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.tmatesoft.svn.core.SVNDirEntry; import org.tmatesoft.svn.core.SVNDirEntry;
@@ -53,7 +52,6 @@ import sonia.scm.repository.SvnUtil;
import sonia.scm.util.Util; import sonia.scm.util.Util;
import java.util.Collection; import java.util.Collection;
import java.util.List;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------

View File

@@ -4,6 +4,8 @@ import lombok.extern.slf4j.Slf4j;
import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLogEntry; import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.io.SVNRepository; 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.InternalRepositoryException;
import sonia.scm.repository.Modifications; import sonia.scm.repository.Modifications;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
@@ -19,23 +21,45 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif
super(context, repository); super(context, repository);
} }
@Override @Override
@SuppressWarnings("unchecked") public Modifications getModifications(String revisionOrTransactionId) {
public Modifications getModifications(String revision) { Modifications modifications;
Modifications modifications = null;
log.debug("get modifications {}", revision);
try { 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(); SVNRepository repo = open();
Collection<SVNLogEntry> entries = repo.log(null, null, revisionNumber, Collection<SVNLogEntry> entries = repo.log(null, null, revisionNumber,
revisionNumber, true, true); revisionNumber, true, true);
if (Util.isNotEmpty(entries)) { if (Util.isNotEmpty(entries)) {
modifications = SvnUtil.createModifications(entries.iterator().next(), revision); return SvnUtil.createModifications(entries.iterator().next(), revision);
} }
} catch (SVNException ex) { return null;
throw new InternalRepositoryException(repository, "could not open repository", ex);
} }
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; return modifications;
} }
@@ -44,5 +68,4 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif
return getModifications(request.getRevision()); return getModifications(request.getRevision());
} }
} }

View File

@@ -1,7 +1,42 @@
{ {
"scm-svn-plugin": { "scm-svn-plugin": {
"information": { "information": {
"checkout" : "Repository auschecken" "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"
}
}
} }
} }
} }

View File

@@ -7,7 +7,7 @@
"link": "Subversion", "link": "Subversion",
"title": "Subversion Configuration", "title": "Subversion Configuration",
"compatibility": "Version Compatibility", "compatibility": "Version Compatibility",
"compatibilityHelpText": "Specifies with which subversion version repositories are compatible.", "compatibilityHelpText": "Specifies with which Subversion version repositories are compatible.",
"compatibility-values": { "compatibility-values": {
"none": "No compatibility", "none": "No compatibility",
"pre14": "Pre 1.4 Compatible", "pre14": "Pre 1.4 Compatible",
@@ -17,9 +17,9 @@
"with17": "With 1.7 Compatible" "with17": "With 1.7 Compatible"
}, },
"enabledGZip": "Enable GZip Compression", "enabledGZip": "Enable GZip Compression",
"enabledGZipHelpText": "Enable GZip compression for svn responses.", "enabledGZipHelpText": "Enable GZip compression for SVN responses.",
"disabled": "Disabled", "disabled": "Disabled",
"disabledHelpText": "Enable or disable the Git plugin", "disabledHelpText": "Enable or disable the SVN plugin",
"required": "This configuration value is required" "required": "This configuration value is required"
} }
}, },

View File

@@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.Compatibility; import sonia.scm.repository.Compatibility;
import sonia.scm.repository.SvnConfig; import sonia.scm.repository.SvnConfig;

View File

@@ -16,14 +16,14 @@ import org.junit.runner.RunWith;
import org.mockito.Answers; import org.mockito.Answers;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.SvnConfig; import sonia.scm.repository.SvnConfig;
import sonia.scm.repository.SvnRepositoryHandler; import sonia.scm.repository.SvnRepositoryHandler;
import sonia.scm.web.SvnVndMediaType; import sonia.scm.web.SvnVndMediaType;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@@ -98,7 +98,7 @@ public class SvnConfigResourceTest {
@Test @Test
@SubjectAware(username = "readOnly") @SubjectAware(username = "readOnly")
public void shouldGetSvnConfigWithoutUpdateLink() throws URISyntaxException { public void shouldGetSvnConfigWithoutUpdateLink() throws URISyntaxException, UnsupportedEncodingException {
MockHttpResponse response = get(); MockHttpResponse response = get();
assertEquals(HttpServletResponse.SC_OK, response.getStatus()); assertEquals(HttpServletResponse.SC_OK, response.getStatus());

View File

@@ -11,11 +11,10 @@ import org.junit.runner.RunWith;
import org.mockito.Answers; import org.mockito.Answers;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.Compatibility; import sonia.scm.repository.Compatibility;
import sonia.scm.repository.SvnConfig; import sonia.scm.repository.SvnConfig;
import java.io.File;
import java.net.URI; import java.net.URI;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;

View File

@@ -32,14 +32,10 @@
package sonia.scm.repository; package sonia.scm.repository;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.api.HookContextFactory; import sonia.scm.repository.api.HookContextFactory;
import sonia.scm.repository.spi.HookEventFacade; import sonia.scm.repository.spi.HookEventFacade;
import sonia.scm.store.ConfigurationStore;
import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.ConfigurationStoreFactory;
import java.io.File; import java.io.File;
@@ -47,7 +43,7 @@ import java.io.IOException;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; 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.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks; import static org.mockito.MockitoAnnotations.initMocks;
@@ -93,7 +89,7 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory,
RepositoryLocationResolver locationResolver, RepositoryLocationResolver locationResolver,
File directory) { File directory) {
SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, locationResolver); SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, locationResolver, null);
handler.init(contextProvider); handler.init(contextProvider);
@@ -109,7 +105,7 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
public void getDirectory() { public void getDirectory() {
when(factory.withType(any())).thenCallRealMethod(); when(factory.withType(any())).thenCallRealMethod();
SvnRepositoryHandler repositoryHandler = new SvnRepositoryHandler(factory, SvnRepositoryHandler repositoryHandler = new SvnRepositoryHandler(factory,
facade, locationResolver); facade, locationResolver, null);
SvnConfig svnConfig = new SvnConfig(); SvnConfig svnConfig = new SvnConfig();
repositoryHandler.setConfig(svnConfig); repositoryHandler.setConfig(svnConfig);

View File

@@ -39,8 +39,6 @@ import sonia.scm.repository.Changeset;
import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.Modifications; import sonia.scm.repository.Modifications;
import java.io.IOException;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;

View File

@@ -59,7 +59,7 @@ public class DummyRepositoryHandler
private final Set<String> existingRepoNames = new HashSet<>(); private final Set<String> existingRepoNames = new HashSet<>();
public DummyRepositoryHandler(ConfigurationStoreFactory storeFactory, RepositoryLocationResolver repositoryLocationResolver) { public DummyRepositoryHandler(ConfigurationStoreFactory storeFactory, RepositoryLocationResolver repositoryLocationResolver) {
super(storeFactory, repositoryLocationResolver); super(storeFactory, repositoryLocationResolver, null);
} }
@Override @Override

View File

@@ -13,10 +13,11 @@ const styles = {
minWidthOfLabel: { minWidthOfLabel: {
minWidth: "4.5rem" minWidth: "4.5rem"
}, },
wrapper: { labelSizing: {
padding: "1rem 1.5rem 0.25rem 1.5rem", fontSize: "1rem !important"
border: "1px solid #eee", },
borderRadius: "5px 5px 0 0" noBottomMargin: {
marginBottom: "0 !important"
} }
}; };
@@ -52,9 +53,9 @@ class BranchSelector extends React.Component<Props, State> {
return ( return (
<div <div
className={classNames( className={classNames(
"has-background-light field", "field",
"is-horizontal", "is-horizontal",
classes.wrapper classes.noBottomMargin
)} )}
> >
<div <div
@@ -65,10 +66,14 @@ class BranchSelector extends React.Component<Props, State> {
classes.minWidthOfLabel classes.minWidthOfLabel
)} )}
> >
<label className="label">{label}</label> <label className={classNames("label", classes.labelSizing)}>
{label}
</label>
</div> </div>
<div className="field-body"> <div className="field-body">
<div className="field is-narrow"> <div
className={classNames("field is-narrow", classes.noBottomMargin)}
>
<div className="control"> <div className="control">
<DropDown <DropDown
className="is-fullwidth" className="is-fullwidth"

View File

@@ -3,14 +3,17 @@ import React from "react";
type Props = { type Props = {
displayName: string, displayName: string,
url: string url: string,
disabled: boolean,
onClick?: () => void
}; };
class DownloadButton extends React.Component<Props> { class DownloadButton extends React.Component<Props> {
render() { render() {
const { displayName, url } = this.props; const { displayName, url, disabled, onClick } = this.props;
const onClickOrDefault = !!onClick ? onClick : () => {};
return ( 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"> <span className="icon is-medium">
<i className="fas fa-arrow-circle-down" /> <i className="fas fa-arrow-circle-down" />
</span> </span>

View File

@@ -36,9 +36,9 @@ class ConfigurationBinder {
binder.bind("config.navigation", ConfigNavLink, configPredicate); binder.bind("config.navigation", ConfigNavLink, configPredicate);
// route for global configuration, passes the link from the index resource to component // 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; 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 // bind config route to extension point
@@ -63,9 +63,36 @@ class ConfigurationBinder {
// route for global configuration, passes the current repository to component // route for global configuration, passes the current repository to component
const RepoRoute = ({url, repository}) => { const RepoRoute = ({url, repository, ...additionalProps}) => {
const link = repository._links[linkName].href const link = repository._links[linkName].href;
return this.route(url + to, <RepositoryComponent repository={repository} link={link}/>); 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 // bind config route to extension point

View File

@@ -10,7 +10,8 @@ type Props = {
buttonLabel: string, buttonLabel: string,
fieldLabel: string, fieldLabel: string,
errorMessage: string, errorMessage: string,
helpText?: string helpText?: string,
validateEntry?: string => boolean
}; };
type State = { 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() { render() {
const { const {
disabled, disabled,
@@ -39,7 +49,7 @@ class AddEntryToTableField extends React.Component<Props, State> {
label={fieldLabel} label={fieldLabel}
errorMessage={errorMessage} errorMessage={errorMessage}
onChange={this.handleAddEntryChange} onChange={this.handleAddEntryChange}
validationError={false} validationError={!this.isValid()}
value={this.state.entryToAdd} value={this.state.entryToAdd}
onReturnPressed={this.appendEntry} onReturnPressed={this.appendEntry}
disabled={disabled} disabled={disabled}
@@ -48,7 +58,7 @@ class AddEntryToTableField extends React.Component<Props, State> {
<AddButton <AddButton
label={buttonLabel} label={buttonLabel}
action={this.addButtonClicked} action={this.addButtonClicked}
disabled={disabled} disabled={disabled || this.state.entryToAdd ==="" || !this.isValid()}
/> />
</div> </div>
); );

View File

@@ -28,7 +28,7 @@ class NavLink extends React.Component<Props> {
let showIcon = null; let showIcon = null;
if (icon) { if (icon) {
showIcon = (<><i className={icon}></i>{" "}</>); showIcon = (<><i className={icon} />{" "}</>);
} }
return ( return (

View File

@@ -50,8 +50,19 @@ class PrimaryNavigation extends React.Component<Props> {
createNavigationItems = () => { createNavigationItems = () => {
const navigationItems = []; const navigationItems = [];
const { t, links } = this.props;
const props = {
links,
label: t("primary-navigation.first-menu")
};
const append = this.createNavigationAppender(navigationItems); 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("/repos", "/(repo|repos)", "primary-navigation.repositories", "repositories");
append("/users", "/(user|users)", "primary-navigation.users", "users"); append("/users", "/(user|users)", "primary-navigation.users", "users");
append("/groups", "/(group|groups)", "primary-navigation.groups", "groups"); append("/groups", "/(group|groups)", "primary-navigation.groups", "groups");

View File

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

View File

@@ -3,6 +3,7 @@
export { default as NavAction } from "./NavAction.js"; export { default as NavAction } from "./NavAction.js";
export { default as NavLink } from "./NavLink.js"; export { default as NavLink } from "./NavLink.js";
export { default as Navigation } from "./Navigation.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 PrimaryNavigation } from "./PrimaryNavigation.js";
export { default as PrimaryNavigationLink } from "./PrimaryNavigationLink.js"; export { default as PrimaryNavigationLink } from "./PrimaryNavigationLink.js";
export { default as Section } from "./Section.js"; export { default as Section } from "./Section.js";

View File

@@ -25,7 +25,7 @@ class ChangesetDiff extends React.Component<Props> {
render() { render() {
const { changeset, t } = this.props; const { changeset, t } = this.props;
if (!this.isDiffSupported(changeset)) { if (!this.isDiffSupported(changeset)) {
return <Notification type="danger">{t("changesets.diff.not-supported")}</Notification>; return <Notification type="danger">{t("changesets.changeset.diffNotSupported")}</Notification>;
} else { } else {
const url = this.createUrl(changeset); const url = this.createUrl(changeset);
return <LoadingDiff url={url} />; return <LoadingDiff url={url} />;

View File

@@ -21,7 +21,7 @@ class ChangesetList extends React.Component<Props> {
/> />
); );
}); });
return <div className="box">{content}</div>; return <>{content}</>;
} }
} }

View File

@@ -1,7 +1,7 @@
{ {
"login": { "login": {
"title": "Anmeldung", "title": "Anmeldung",
"subtitle": "Bitte anmelden um fortzufahren.", "subtitle": "Bitte anmelden, um fortzufahren.",
"logo-alt": "SCM-Manager", "logo-alt": "SCM-Manager",
"username-placeholder": "Benutzername", "username-placeholder": "Benutzername",
"password-placeholder": "Passwort", "password-placeholder": "Passwort",
@@ -10,13 +10,13 @@
"logout": { "logout": {
"error": { "error": {
"title": "Abmeldung fehlgeschlagen", "title": "Abmeldung fehlgeschlagen",
"subtitle": "Während der Abmeldung ist ein Fehler aufgetreten" "subtitle": "Während der Abmeldung ist ein Fehler aufgetreten."
} }
}, },
"app": { "app": {
"error": { "error": {
"title": "Fehler", "title": "Fehler",
"subtitle": "Ein unbekannter Fehler ist aufgetreten" "subtitle": "Ein unbekannter Fehler ist aufgetreten."
} }
}, },
"error-notification": { "error-notification": {
@@ -43,8 +43,10 @@
"previous": "Zurück" "previous": "Zurück"
}, },
"profile": { "profile": {
"navigation-label": "Navigation", "navigationLabel": "Profil Navigation",
"actions-label": "Aktionen", "informationNavLink": "Information",
"changePasswordNavLink": "Passwort ändern",
"settingsNavLink": "Einstellungen",
"username": "Benutzername", "username": "Benutzername",
"displayName": "Anzeigename", "displayName": "Anzeigename",
"mail": "E-Mail", "mail": "E-Mail",
@@ -52,21 +54,21 @@
"information": "Informationen", "information": "Informationen",
"change-password": "Passwort ändern", "change-password": "Passwort ändern",
"error-title": "Fehler", "error-title": "Fehler",
"error-subtitle": "Das Profil kann nicht angezeigt werden", "error-subtitle": "Das Profil kann nicht angezeigt werden.",
"error": "Fehler", "error": "Fehler",
"error-message": "'me' ist nicht definiert" "error-message": "'me' ist nicht definiert"
}, },
"password": { "password": {
"label": "Passwort", "label": "Passwort",
"newPassword": "Neues Passwort", "newPassword": "Neues Passwort",
"passwordHelpText": "Plaintext Passwort des Benutzers.", "passwordHelpText": "Klartext Passwort des Benutzers.",
"passwordConfirmHelpText": "Passwort zur Bestätigen wiederholen.", "passwordConfirmHelpText": "Passwort zur Bestätigen wiederholen.",
"currentPassword": "Aktuelles Passwort", "currentPassword": "Aktuelles Passwort",
"currentPasswordHelpText": "Dieses Passwort wird momentan bereits verwendet.", "currentPasswordHelpText": "Dieses Passwort wird momentan bereits verwendet.",
"confirmPassword": "Passwort wiederholen", "confirmPassword": "Passwort wiederholen",
"passwordInvalid": "Das Passwort muss zwischen 6 und 32 Zeichen lang sein", "passwordInvalid": "Das Passwort muss zwischen 6 und 32 Zeichen lang sein!",
"passwordConfirmFailed": "Passwörter müssen identisch sein", "passwordConfirmFailed": "Passwörter müssen identisch sein!",
"submit": "Speichern", "submit": "Speichern",
"changedSuccessfully": "Passwort erfolgreich geändert" "changedSuccessfully": "Passwort erfolgreich geändert!"
} }
} }

View File

@@ -1,12 +1,10 @@
{ {
"config": { "config": {
"navigation-title": "Navigation" "navigationLabel": "Einstellungs Navigation",
}, "globalConfigurationNavLink": "Globale Einstellungen",
"global-config": {
"title": "Einstellungen", "title": "Einstellungen",
"navigation-label": "Globale Einstellungen", "errorTitle": "Fehler",
"error-title": "Fehler", "errorSubtitle": "Unbekannter Einstellungen Fehler"
"error-subtitle": "Unbekannter Einstellungen Fehler"
}, },
"config-form": { "config-form": {
"submit": "Speichern", "submit": "Speichern",
@@ -47,12 +45,12 @@
"login-attempt": { "login-attempt": {
"name": "Anmeldeversuche", "name": "Anmeldeversuche",
"login-attempt-limit": "Limit für 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": { "general-settings": {
"realm-description": "Realm Beschreibung", "realm-description": "Realm Beschreibung",
"enable-repository-archive": "Repository Archiv aktivieren", "enable-repository-archive": "Repository Archiv aktivieren",
"disable-grouping-grid": "Gruppen deaktiviern", "disable-grouping-grid": "Gruppen deaktivieren",
"date-format": "Datumsformat", "date-format": "Datumsformat",
"anonymous-access-enabled": "Anonyme Zugriffe erlauben", "anonymous-access-enabled": "Anonyme Zugriffe erlauben",
"skip-failed-authenticators": "Fehlgeschlagene Authentifizierer überspringen", "skip-failed-authenticators": "Fehlgeschlagene Authentifizierer überspringen",
@@ -67,27 +65,27 @@
"plugin-url-invalid": "Dies ist keine gültige URL" "plugin-url-invalid": "Dies ist keine gültige URL"
}, },
"help": { "help": {
"realmDescriptionHelpText": "Beschreibung des authentication realm", "realmDescriptionHelpText": "Beschreibung des Authentication Realm.",
"dateFormatHelpText": "Moments Datumsformat. Zulässige Formate sind in der momentjs Dokumentation beschrieben.", "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", "pluginRepositoryHelpText": "Die URL des Plugin Repositories. Beschreibung der Platzhalter: version = SCM-Manager Version; os = Betriebssystem; arch = Architektur",
"enableForwardingHelpText": "mod_proxy Port Weiterleitung aktivieren.", "enableForwardingHelpText": "mod_proxy Port Weiterleitung aktivieren.",
"enableRepositoryArchiveHelpText": "Repository Archive aktivieren. Nach einer Änderung an dieser Einstellung muss die Seite komplett neu geladen werden.", "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.", "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.", "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.", "adminGroupsHelpText": "Namen von Gruppen mit Admin-Berechtigungen.",
"adminUsersHelpText": "Namen von Benutzern 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.", "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", "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.", "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.", "loginAttemptLimitTimeoutHelpText": "Timeout in Sekunden für Benutzer, die vorübergehend wegen zu vieler fehlgeschlagener Anmeldeversuche, deaktiviert wurden.",
"enableProxyHelpText": "Proxy aktiviern", "enableProxyHelpText": "Proxy aktivieren",
"proxyPortHelpText": "Der Proxy Port", "proxyPortHelpText": "Der Proxy Port",
"proxyPasswordHelpText": "Das Passwort für die Proxy Server Anmeldung.", "proxyPasswordHelpText": "Das Passwort für die Proxy Server Anmeldung.",
"proxyServerHelpText": "Der Proxy Server", "proxyServerHelpText": "Der Proxy Server",
"proxyUserHelpText": "Der Benutzername für die Proxy Server Anmeldung.", "proxyUserHelpText": "Der Benutzername für die Proxy Server Anmeldung.",
"proxyExcludesHelpText": "Glob patterns für Hostnamen, die von den Proxy-Einstellungen ausgeschlossen werden sollen.", "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.", "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."
} }
} }

View File

@@ -11,20 +11,23 @@
"title": "Gruppen", "title": "Gruppen",
"subtitle": "Verwaltung der Gruppen" "subtitle": "Verwaltung der Gruppen"
}, },
"single-group": { "singleGroup": {
"error-title": "Fehler", "errorTitle": "Fehler",
"error-subtitle": "Unbekannter Gruppen Fehler", "errorSubtitle": "Unbekannter Gruppen Fehler",
"navigation-label": "Navigation", "menu": {
"actions-label": "Aktionen", "navigationLabel": "Gruppen Navigation",
"information-label": "Informationen", "informationNavLink": "Informationen",
"back-label": "Zurück" "settingsNavLink": "Einstellungen",
"generalNavLink": "Generell",
"setPermissionsNavLink": "Berechtigungen"
}
}, },
"add-group": { "add-group": {
"title": "Gruppe erstellen", "title": "Gruppe erstellen",
"subtitle": "Erstllen einer neuen Gruppe" "subtitle": "Erstellen einer neuen Gruppe"
}, },
"create-group-button": { "create-group-button": {
"label": "Erstellen" "label": "Gruppe erstellen"
}, },
"edit-group-button": { "edit-group-button": {
"label": "Bearbeiten" "label": "Bearbeiten"
@@ -41,30 +44,28 @@
}, },
"add-member-autocomplete": { "add-member-autocomplete": {
"placeholder": "Benutzername eingeben", "placeholder": "Benutzername eingeben",
"loading": "suche...", "loading": "Suche...",
"no-options": "Kein Vorschlag für Benutzername verfügbar" "no-options": "Kein Vorschlag für Benutzername verfügbar"
}, },
"groupForm": {
"group-form": { "subtitle": "Gruppe bearbeiten",
"submit": "Speichern", "submit": "Speichern",
"name-error": "Name ist ungültig", "nameError": "Name ist ungültig",
"description-error": "Beschreibung ist ungültig", "descriptionError": "Beschreibung ist ungültig",
"help": { "help": {
"nameHelpText": "Einzigartiger Name der Gruppe", "nameHelpText": "Eindeutiger Name der Gruppe",
"descriptionHelpText": "Eine kurze Beschreibung der Gruppe", "descriptionHelpText": "Eine kurze Beschreibung der Gruppe",
"memberHelpText": "Benutzername des Mitglieds der Gruppe" "memberHelpText": "Benutzername des Mitglieds der Gruppe"
} }
}, },
"delete-group-button": { "deleteGroup": {
"label": "Löschen", "subtitle": "Gruppe löschen",
"confirm-alert": { "button": "Löschen",
"confirmAlert": {
"title": "Gruppe löschen", "title": "Gruppe löschen",
"message": "Soll die Gruppe wirklich gelöscht werden?", "message": "Soll die Gruppe wirklich gelöscht werden?",
"submit": "Ja", "submit": "Ja",
"cancel": "Nein" "cancel": "Nein"
} }
},
"set-permissions-button": {
"label": "Berechtigungen ändern"
} }
} }

Some files were not shown because too many files have changed in this diff Show More