mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-07 14:05:44 +01:00
merge
This commit is contained in:
5
Jenkinsfile
vendored
5
Jenkinsfile
vendored
@@ -50,6 +50,11 @@ node('docker') {
|
||||
def dockerImageTag = "2.0.0-dev-${commitHash.substring(0,7)}-${BUILD_NUMBER}"
|
||||
|
||||
if (isMainBranch()) {
|
||||
|
||||
stage('Lifecycle') {
|
||||
nexusPolicyEvaluation iqApplication: selectedApplication('scm'), iqScanPatterns: [[scanPattern: 'scm-server/target/scm-server-app.zip']], iqStage: 'build'
|
||||
}
|
||||
|
||||
stage('Archive') {
|
||||
archiveArtifacts 'scm-webapp/target/scm-webapp.war'
|
||||
archiveArtifacts 'scm-server/target/scm-server-app.*'
|
||||
|
||||
21
pom.xml
21
pom.xml
@@ -351,21 +351,6 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- utils -->
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-beanutils</groupId>
|
||||
<artifactId>commons-beanutils</artifactId>
|
||||
<version>1.9.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-collections</groupId>
|
||||
<artifactId>commons-collections</artifactId>
|
||||
<version>3.2.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- http -->
|
||||
|
||||
<dependency>
|
||||
@@ -825,11 +810,11 @@
|
||||
<logback.version>1.2.3</logback.version>
|
||||
<servlet.version>3.0.1</servlet.version>
|
||||
|
||||
<jaxrs.version>2.0.1</jaxrs.version>
|
||||
<resteasy.version>3.1.3.Final</resteasy.version>
|
||||
<jaxrs.version>2.1.1</jaxrs.version>
|
||||
<resteasy.version>3.6.2.Final</resteasy.version>
|
||||
<jersey-client.version>1.19.4</jersey-client.version>
|
||||
<enunciate.version>2.11.1</enunciate.version>
|
||||
<jackson.version>2.8.6</jackson.version>
|
||||
<jackson.version>2.9.8</jackson.version>
|
||||
<guice.version>4.0</guice.version>
|
||||
<jaxb.version>2.3.0</jaxb.version>
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
<dependency>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -235,7 +236,6 @@
|
||||
<links>
|
||||
<link>http://download.oracle.com/javase/6/docs/api/</link>
|
||||
<link>http://download.oracle.com/docs/cd/E17802_01/products/products/servlet/2.5/docs/servlet-2_5-mr2/</link>
|
||||
<link>http://jersey.java.net/nonav/apidocs/${jersey.version}/jersey/</link>
|
||||
<link>https://google.github.io/guice/api-docs/${guice.version}/javadoc</link>
|
||||
<link>http://www.slf4j.org/api/</link>
|
||||
<link>http://shiro.apache.org/static/${shiro.version}/apidocs/</link>
|
||||
|
||||
@@ -36,7 +36,6 @@ package sonia.scm;
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* The base class of all handlers.
|
||||
|
||||
@@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import org.mapstruct.Mapping;
|
||||
|
||||
public abstract class BaseMapper<T, D extends HalRepresentation> extends LinkAppenderMapper implements InstantAttributeMapper {
|
||||
public abstract class BaseMapper<T, D extends HalRepresentation> extends HalAppenderMapper implements InstantAttributeMapper {
|
||||
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
public abstract D map(T modelObject);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
@@ -7,7 +8,6 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -34,16 +34,7 @@ public class ChangesetDto extends HalRepresentation {
|
||||
*/
|
||||
private String description;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
public ChangesetDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation withEmbedded(String rel, List<? extends HalRepresentation> halRepresentations) {
|
||||
return super.withEmbedded(rel, halRepresentations);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import org.mapstruct.Context;
|
||||
import org.mapstruct.Mapping;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
public interface ChangesetToChangesetDtoMapper {
|
||||
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
ChangesetDto map(Changeset changeset, @Context Repository repository);
|
||||
|
||||
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
|
||||
/**
|
||||
* The {@link LinkAppender} can be used within an {@link LinkEnricher} to append hateoas links to a json response.
|
||||
* The {@link HalAppender} can be used within an {@link HalEnricher} to append hateoas links to a json response.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface LinkAppender {
|
||||
public interface HalAppender {
|
||||
|
||||
/**
|
||||
* Appends one link to the json response.
|
||||
@@ -14,7 +16,7 @@ public interface LinkAppender {
|
||||
* @param rel name of relation
|
||||
* @param href link uri
|
||||
*/
|
||||
void appendOne(String rel, String href);
|
||||
void appendLink(String rel, String href);
|
||||
|
||||
/**
|
||||
* Returns a builder which is able to append an array of links to the resource.
|
||||
@@ -22,8 +24,15 @@ public interface LinkAppender {
|
||||
* @param rel name of link relation
|
||||
* @return multi link builder
|
||||
*/
|
||||
LinkArrayBuilder arrayBuilder(String rel);
|
||||
LinkArrayBuilder linkArrayBuilder(String rel);
|
||||
|
||||
/**
|
||||
* Appends one embedded object to the json response.
|
||||
*
|
||||
* @param rel name of relation
|
||||
* @param embeddedItem embedded object
|
||||
*/
|
||||
void appendEmbedded(String rel, HalRepresentation embeddedItem);
|
||||
|
||||
/**
|
||||
* Builder for link arrays.
|
||||
@@ -31,7 +40,7 @@ public interface LinkAppender {
|
||||
interface LinkArrayBuilder {
|
||||
|
||||
/**
|
||||
* Append an link to the array.
|
||||
* Append a link to the array.
|
||||
*
|
||||
* @param name name of link
|
||||
* @param href link target
|
||||
@@ -4,17 +4,17 @@ import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class LinkAppenderMapper {
|
||||
public class HalAppenderMapper {
|
||||
|
||||
@Inject
|
||||
private LinkEnricherRegistry registry;
|
||||
private HalEnricherRegistry registry;
|
||||
|
||||
@VisibleForTesting
|
||||
void setRegistry(LinkEnricherRegistry registry) {
|
||||
void setRegistry(HalEnricherRegistry registry) {
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
protected void appendLinks(LinkAppender appender, Object source, Object... contextEntries) {
|
||||
protected void applyEnrichers(HalAppender appender, Object source, Object... contextEntries) {
|
||||
// null check is only their to not break existing tests
|
||||
if (registry != null) {
|
||||
|
||||
@@ -24,10 +24,10 @@ public class LinkAppenderMapper {
|
||||
ctx[i + 1] = contextEntries[i];
|
||||
}
|
||||
|
||||
LinkEnricherContext context = LinkEnricherContext.of(ctx);
|
||||
HalEnricherContext context = HalEnricherContext.of(ctx);
|
||||
|
||||
Iterable<LinkEnricher> enrichers = registry.allByType(source.getClass());
|
||||
for (LinkEnricher enricher : enrichers) {
|
||||
Iterable<HalEnricher> enrichers = registry.allByType(source.getClass());
|
||||
for (HalEnricher enricher : enrichers) {
|
||||
enricher.enrich(context, appender);
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@ package sonia.scm.api.v2.resources;
|
||||
import sonia.scm.plugin.ExtensionPoint;
|
||||
|
||||
/**
|
||||
* A {@link LinkEnricher} can be used to append hateoas links to a specific json response.
|
||||
* To register an enricher use the {@link Enrich} annotation or the {@link LinkEnricherRegistry} which is available
|
||||
* A {@link HalEnricher} can be used to append hal specific attributes, such as links, to the json response.
|
||||
* To register an enricher use the {@link Enrich} annotation or the {@link HalEnricherRegistry} which is available
|
||||
* via injection.
|
||||
*
|
||||
* <b>Warning:</b> enrichers are always registered as singletons.
|
||||
@@ -14,13 +14,13 @@ import sonia.scm.plugin.ExtensionPoint;
|
||||
*/
|
||||
@ExtensionPoint
|
||||
@FunctionalInterface
|
||||
public interface LinkEnricher {
|
||||
public interface HalEnricher {
|
||||
|
||||
/**
|
||||
* Enriches the response with hateoas links.
|
||||
* Enriches the response with hal specific attributes.
|
||||
*
|
||||
* @param context contains the source for the json mapping and related objects
|
||||
* @param appender can be used to append links to the json response
|
||||
* @param appender can be used to append links or embedded objects to the json response
|
||||
*/
|
||||
void enrich(LinkEnricherContext context, LinkAppender appender);
|
||||
void enrich(HalEnricherContext context, HalAppender appender);
|
||||
}
|
||||
@@ -7,17 +7,17 @@ import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Context object for the {@link LinkEnricher}. The context holds the source object for the json and all related
|
||||
* objects, which can be useful for the link creation.
|
||||
* Context object for the {@link HalEnricher}. The context holds the source object for the json and all related
|
||||
* objects, which can be useful for the enrichment.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public final class LinkEnricherContext {
|
||||
public final class HalEnricherContext {
|
||||
|
||||
private final Map<Class, Object> instanceMap;
|
||||
|
||||
private LinkEnricherContext(Map<Class,Object> instanceMap) {
|
||||
private HalEnricherContext(Map<Class,Object> instanceMap) {
|
||||
this.instanceMap = instanceMap;
|
||||
}
|
||||
|
||||
@@ -28,12 +28,12 @@ public final class LinkEnricherContext {
|
||||
*
|
||||
* @return context of given entries
|
||||
*/
|
||||
public static LinkEnricherContext of(Object... instances) {
|
||||
public static HalEnricherContext of(Object... instances) {
|
||||
ImmutableMap.Builder<Class, Object> builder = ImmutableMap.builder();
|
||||
for (Object instance : instances) {
|
||||
builder.put(instance.getClass(), instance);
|
||||
}
|
||||
return new LinkEnricherContext(builder.build());
|
||||
return new HalEnricherContext(builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -7,34 +7,34 @@ import sonia.scm.plugin.Extension;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* The {@link LinkEnricherRegistry} is responsible for binding {@link LinkEnricher} instances to their source types.
|
||||
* The {@link HalEnricherRegistry} is responsible for binding {@link HalEnricher} instances to their source types.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Extension
|
||||
@Singleton
|
||||
public final class LinkEnricherRegistry {
|
||||
public final class HalEnricherRegistry {
|
||||
|
||||
private final Multimap<Class, LinkEnricher> enrichers = HashMultimap.create();
|
||||
private final Multimap<Class, HalEnricher> enrichers = HashMultimap.create();
|
||||
|
||||
/**
|
||||
* Registers a new {@link LinkEnricher} for the given source type.
|
||||
* Registers a new {@link HalEnricher} for the given source type.
|
||||
*
|
||||
* @param sourceType type of json mapping source
|
||||
* @param enricher link enricher instance
|
||||
*/
|
||||
public void register(Class sourceType, LinkEnricher enricher) {
|
||||
public void register(Class sourceType, HalEnricher enricher) {
|
||||
enrichers.put(sourceType, enricher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all registered {@link LinkEnricher} for the given type.
|
||||
* Returns all registered {@link HalEnricher} for the given type.
|
||||
*
|
||||
* @param sourceType type of json mapping source
|
||||
* @return all registered enrichers
|
||||
*/
|
||||
public Iterable<LinkEnricher> allByType(Class sourceType) {
|
||||
public Iterable<HalEnricher> allByType(Class sourceType) {
|
||||
return enrichers.get(sourceType);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
/**
|
||||
* The {@link Index} object can be used to register a {@link LinkEnricher} for the index resource.
|
||||
* The {@link Index} object can be used to register a {@link HalEnricher} for the index resource.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
/**
|
||||
* The {@link Me} object can be used to register a {@link LinkEnricher} for the me resource.
|
||||
* The {@link Me} object can be used to register a {@link HalEnricher} for the me resource.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
|
||||
@@ -1,24 +1,59 @@
|
||||
package sonia.scm.filter;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sonia.scm.util.WebUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.container.ContainerResponseContext;
|
||||
import javax.ws.rs.container.ContainerResponseFilter;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.ext.WriterInterceptor;
|
||||
import javax.ws.rs.ext.WriterInterceptorContext;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Locale;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
@Provider
|
||||
@Slf4j
|
||||
public class GZipResponseFilter implements ContainerResponseFilter {
|
||||
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
|
||||
if (WebUtil.isGzipSupported(requestContext::getHeaderString)) {
|
||||
log.trace("compress output with gzip");
|
||||
GZIPOutputStream wrappedResponse = new GZIPOutputStream(responseContext.getEntityStream());
|
||||
responseContext.getHeaders().add("Content-Encoding", "gzip");
|
||||
responseContext.setEntityStream(wrappedResponse);
|
||||
@javax.ws.rs.ext.Provider
|
||||
public class GZipResponseFilter implements WriterInterceptor {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GZipResponseFilter.class);
|
||||
|
||||
private final Provider<HttpServletRequest> requestProvider;
|
||||
|
||||
@Inject
|
||||
public GZipResponseFilter(Provider<HttpServletRequest> requestProvider) {
|
||||
this.requestProvider = requestProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
|
||||
if (isGZipSupported()) {
|
||||
LOG.trace("compress output with gzip");
|
||||
encodeWithGZip(context);
|
||||
} else {
|
||||
context.proceed();
|
||||
}
|
||||
}
|
||||
|
||||
private void encodeWithGZip(WriterInterceptorContext context) throws IOException {
|
||||
context.getHeaders().remove(HttpHeaders.CONTENT_LENGTH);
|
||||
context.getHeaders().add(HttpHeaders.CONTENT_ENCODING, "gzip");
|
||||
|
||||
OutputStream outputStream = context.getOutputStream();
|
||||
GZIPOutputStream compressedOutputStream = new GZIPOutputStream(outputStream);
|
||||
context.setOutputStream(compressedOutputStream);
|
||||
try {
|
||||
context.proceed();
|
||||
} finally {
|
||||
compressedOutputStream.finish();
|
||||
context.setOutputStream(outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isGZipSupported() {
|
||||
Object encoding = requestProvider.get().getHeader(HttpHeaders.ACCEPT_ENCODING);
|
||||
return encoding != null && encoding.toString().toLowerCase(Locale.ENGLISH).contains("gzip");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ package sonia.scm.net.ahc;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.LinkedHashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
|
||||
@@ -35,8 +35,6 @@ package sonia.scm.plugin;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.Module;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.ConfigurationException;
|
||||
import sonia.scm.io.CommandResult;
|
||||
import sonia.scm.io.ExtendedCommand;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
|
||||
import java.io.File;
|
||||
@@ -67,11 +68,14 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
|
||||
LoggerFactory.getLogger(AbstractSimpleRepositoryHandler.class);
|
||||
|
||||
private final RepositoryLocationResolver repositoryLocationResolver;
|
||||
private final PluginLoader pluginLoader;
|
||||
|
||||
public AbstractSimpleRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
||||
RepositoryLocationResolver repositoryLocationResolver) {
|
||||
RepositoryLocationResolver repositoryLocationResolver,
|
||||
PluginLoader pluginLoader) {
|
||||
super(storeFactory);
|
||||
this.repositoryLocationResolver = repositoryLocationResolver;
|
||||
this.pluginLoader = pluginLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -155,7 +159,7 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
|
||||
String content = defaultContent;
|
||||
|
||||
try {
|
||||
URL url = Resources.getResource(resource);
|
||||
URL url = pluginLoader.getUberClassLoader().getResource(resource);
|
||||
|
||||
if (url != null) {
|
||||
content = Resources.toString(url, Charsets.UTF_8);
|
||||
|
||||
@@ -37,7 +37,6 @@ package sonia.scm.repository;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Objects;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import sonia.scm.security.PermissionObject;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
@@ -47,9 +46,10 @@ import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.unmodifiableCollection;
|
||||
import static java.util.Collections.unmodifiableSet;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
@@ -68,7 +68,7 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
||||
private boolean groupPermission = false;
|
||||
private String name;
|
||||
@XmlElement(name = "verb")
|
||||
private Collection<String> verbs;
|
||||
private Set<String> verbs;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link RepositoryPermission}.
|
||||
@@ -79,7 +79,7 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
||||
public RepositoryPermission(String name, Collection<String> verbs, boolean groupPermission)
|
||||
{
|
||||
this.name = name;
|
||||
this.verbs = unmodifiableCollection(new LinkedHashSet<>(verbs));
|
||||
this.verbs = unmodifiableSet(new LinkedHashSet<>(verbs));
|
||||
this.groupPermission = groupPermission;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,8 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
||||
final RepositoryPermission other = (RepositoryPermission) obj;
|
||||
|
||||
return Objects.equal(name, other.name)
|
||||
&& CollectionUtils.isEqualCollection(verbs, other.verbs)
|
||||
&& verbs.containsAll(other.verbs)
|
||||
&& verbs.size() == other.verbs.size()
|
||||
&& Objects.equal(groupPermission, other.groupPermission);
|
||||
}
|
||||
|
||||
@@ -209,6 +210,6 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
||||
*/
|
||||
public void setVerbs(Collection<String> verbs)
|
||||
{
|
||||
this.verbs = verbs;
|
||||
this.verbs = unmodifiableSet(new LinkedHashSet<>(verbs));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.google.common.base.Objects;
|
||||
import com.google.common.base.Strings;
|
||||
import sonia.scm.Validateable;
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ import com.google.common.base.Objects;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
@@ -34,8 +34,6 @@ package sonia.scm.security;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
88
scm-core/src/main/java/sonia/scm/util/Comparables.java
Normal file
88
scm-core/src/main/java/sonia/scm/util/Comparables.java
Normal file
@@ -0,0 +1,88 @@
|
||||
package sonia.scm.util;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
public final class Comparables {
|
||||
|
||||
private static final CacheLoader<Class, BeanInfo> beanInfoCacheLoader = new CacheLoader<Class, BeanInfo>() {
|
||||
@Override
|
||||
public BeanInfo load(Class type) throws IntrospectionException {
|
||||
return Introspector.getBeanInfo(type);
|
||||
}
|
||||
};
|
||||
|
||||
private static final LoadingCache<Class, BeanInfo> beanInfoCache = CacheBuilder.newBuilder()
|
||||
.maximumSize(50) // limit the cache to avoid consuming to much memory on miss usage
|
||||
.build(beanInfoCacheLoader);
|
||||
|
||||
private Comparables() {
|
||||
}
|
||||
|
||||
public static <T> Comparator<T> comparator(Class<T> type, String sortBy) {
|
||||
BeanInfo info = createBeanInfo(type);
|
||||
PropertyDescriptor propertyDescriptor = findPropertyDescriptor(sortBy, info);
|
||||
|
||||
Method readMethod = propertyDescriptor.getReadMethod();
|
||||
checkIfPropertyIsComparable(readMethod, sortBy);
|
||||
|
||||
return new MethodComparator<>(readMethod);
|
||||
}
|
||||
|
||||
private static void checkIfPropertyIsComparable(Method readMethod, String sortBy) {
|
||||
checkArgument(isReturnTypeComparable(readMethod), "property %s is not comparable", sortBy);
|
||||
}
|
||||
|
||||
private static boolean isReturnTypeComparable(Method readMethod) {
|
||||
return Comparable.class.isAssignableFrom(readMethod.getReturnType());
|
||||
}
|
||||
|
||||
private static PropertyDescriptor findPropertyDescriptor(String sortBy, BeanInfo info) {
|
||||
PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors();
|
||||
|
||||
Optional<PropertyDescriptor> sortByPropertyDescriptor = Arrays.stream(propertyDescriptors)
|
||||
.filter(p -> p.getName().equals(sortBy))
|
||||
.findFirst();
|
||||
|
||||
return sortByPropertyDescriptor.orElseThrow(() -> new IllegalArgumentException("could not find property " + sortBy));
|
||||
}
|
||||
|
||||
private static <T> BeanInfo createBeanInfo(Class<T> type) {
|
||||
return beanInfoCache.getUnchecked(type);
|
||||
}
|
||||
|
||||
private static class MethodComparator<T> implements Comparator<T> {
|
||||
|
||||
private final Method readMethod;
|
||||
|
||||
private MethodComparator(Method readMethod) {
|
||||
this.readMethod = readMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public int compare(T left, T right) {
|
||||
try {
|
||||
Comparable leftResult = (Comparable) readMethod.invoke(left);
|
||||
Comparable rightResult = (Comparable) readMethod.invoke(right);
|
||||
return leftResult.compareTo(rightResult);
|
||||
} catch (IllegalAccessException | InvocationTargetException ex) {
|
||||
throw new IllegalArgumentException("failed to invoke read method", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,51 +11,51 @@ import java.util.Optional;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LinkAppenderMapperTest {
|
||||
class HalAppenderMapperTest {
|
||||
|
||||
@Mock
|
||||
private LinkAppender appender;
|
||||
private HalAppender appender;
|
||||
|
||||
private LinkEnricherRegistry registry;
|
||||
private LinkAppenderMapper mapper;
|
||||
private HalEnricherRegistry registry;
|
||||
private HalAppenderMapper mapper;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
registry = new LinkEnricherRegistry();
|
||||
mapper = new LinkAppenderMapper();
|
||||
registry = new HalEnricherRegistry();
|
||||
mapper = new HalAppenderMapper();
|
||||
mapper.setRegistry(registry);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendSimpleLink() {
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendOne("42", "https://hitchhiker.com"));
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendLink("42", "https://hitchhiker.com"));
|
||||
|
||||
mapper.appendLinks(appender, "hello");
|
||||
mapper.applyEnrichers(appender, "hello");
|
||||
|
||||
verify(appender).appendOne("42", "https://hitchhiker.com");
|
||||
verify(appender).appendLink("42", "https://hitchhiker.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCallMultipleEnrichers() {
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendOne("42", "https://hitchhiker.com"));
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendOne("21", "https://scm.hitchhiker.com"));
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendLink("42", "https://hitchhiker.com"));
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendLink("21", "https://scm.hitchhiker.com"));
|
||||
|
||||
mapper.appendLinks(appender, "hello");
|
||||
mapper.applyEnrichers(appender, "hello");
|
||||
|
||||
verify(appender).appendOne("42", "https://hitchhiker.com");
|
||||
verify(appender).appendOne("21", "https://scm.hitchhiker.com");
|
||||
verify(appender).appendLink("42", "https://hitchhiker.com");
|
||||
verify(appender).appendLink("21", "https://scm.hitchhiker.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendLinkByUsingSourceFromContext() {
|
||||
registry.register(String.class, (ctx, appender) -> {
|
||||
Optional<String> rel = ctx.oneByType(String.class);
|
||||
appender.appendOne(rel.get(), "https://hitchhiker.com");
|
||||
appender.appendLink(rel.get(), "https://hitchhiker.com");
|
||||
});
|
||||
|
||||
mapper.appendLinks(appender, "42");
|
||||
mapper.applyEnrichers(appender, "42");
|
||||
|
||||
verify(appender).appendOne("42", "https://hitchhiker.com");
|
||||
verify(appender).appendLink("42", "https://hitchhiker.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -63,12 +63,12 @@ class LinkAppenderMapperTest {
|
||||
registry.register(Integer.class, (ctx, appender) -> {
|
||||
Optional<Integer> rel = ctx.oneByType(Integer.class);
|
||||
Optional<String> href = ctx.oneByType(String.class);
|
||||
appender.appendOne(String.valueOf(rel.get()), href.get());
|
||||
appender.appendLink(String.valueOf(rel.get()), href.get());
|
||||
});
|
||||
|
||||
mapper.appendLinks(appender, Integer.valueOf(42), "https://hitchhiker.com");
|
||||
mapper.applyEnrichers(appender, Integer.valueOf(42), "https://hitchhiker.com");
|
||||
|
||||
verify(appender).appendOne("42", "https://hitchhiker.com");
|
||||
verify(appender).appendLink("42", "https://hitchhiker.com");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,17 +7,17 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
class LinkEnricherContextTest {
|
||||
class HalEnricherContextTest {
|
||||
|
||||
@Test
|
||||
void shouldCreateContextFromSingleObject() {
|
||||
LinkEnricherContext context = LinkEnricherContext.of("hello");
|
||||
HalEnricherContext context = HalEnricherContext.of("hello");
|
||||
assertThat(context.oneByType(String.class)).contains("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateContextFromMultipleObjects() {
|
||||
LinkEnricherContext context = LinkEnricherContext.of("hello", Integer.valueOf(42), Long.valueOf(21L));
|
||||
HalEnricherContext context = HalEnricherContext.of("hello", Integer.valueOf(42), Long.valueOf(21L));
|
||||
assertThat(context.oneByType(String.class)).contains("hello");
|
||||
assertThat(context.oneByType(Integer.class)).contains(42);
|
||||
assertThat(context.oneByType(Long.class)).contains(21L);
|
||||
@@ -25,19 +25,19 @@ class LinkEnricherContextTest {
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyOptionalForUnknownTypes() {
|
||||
LinkEnricherContext context = LinkEnricherContext.of();
|
||||
HalEnricherContext context = HalEnricherContext.of();
|
||||
assertThat(context.oneByType(String.class)).isNotPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnRequiredObject() {
|
||||
LinkEnricherContext context = LinkEnricherContext.of("hello");
|
||||
HalEnricherContext context = HalEnricherContext.of("hello");
|
||||
assertThat(context.oneRequireByType(String.class)).isEqualTo("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowAnNoSuchElementExceptionForUnknownTypes() {
|
||||
LinkEnricherContext context = LinkEnricherContext.of();
|
||||
HalEnricherContext context = HalEnricherContext.of();
|
||||
assertThrows(NoSuchElementException.class, () -> context.oneRequireByType(String.class));
|
||||
}
|
||||
|
||||
@@ -5,54 +5,54 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class LinkEnricherRegistryTest {
|
||||
class HalEnricherRegistryTest {
|
||||
|
||||
private LinkEnricherRegistry registry;
|
||||
private HalEnricherRegistry registry;
|
||||
|
||||
@BeforeEach
|
||||
void setUpObjectUnderTest() {
|
||||
registry = new LinkEnricherRegistry();
|
||||
registry = new HalEnricherRegistry();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterTheEnricher() {
|
||||
SampleLinkEnricher enricher = new SampleLinkEnricher();
|
||||
SampleHalEnricher enricher = new SampleHalEnricher();
|
||||
registry.register(String.class, enricher);
|
||||
|
||||
Iterable<LinkEnricher> enrichers = registry.allByType(String.class);
|
||||
Iterable<HalEnricher> enrichers = registry.allByType(String.class);
|
||||
assertThat(enrichers).containsOnly(enricher);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterMultipleEnrichers() {
|
||||
SampleLinkEnricher one = new SampleLinkEnricher();
|
||||
SampleHalEnricher one = new SampleHalEnricher();
|
||||
registry.register(String.class, one);
|
||||
|
||||
SampleLinkEnricher two = new SampleLinkEnricher();
|
||||
SampleHalEnricher two = new SampleHalEnricher();
|
||||
registry.register(String.class, two);
|
||||
|
||||
Iterable<LinkEnricher> enrichers = registry.allByType(String.class);
|
||||
Iterable<HalEnricher> enrichers = registry.allByType(String.class);
|
||||
assertThat(enrichers).containsOnly(one, two);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterEnrichersForDifferentTypes() {
|
||||
SampleLinkEnricher one = new SampleLinkEnricher();
|
||||
SampleHalEnricher one = new SampleHalEnricher();
|
||||
registry.register(String.class, one);
|
||||
|
||||
SampleLinkEnricher two = new SampleLinkEnricher();
|
||||
SampleHalEnricher two = new SampleHalEnricher();
|
||||
registry.register(Integer.class, two);
|
||||
|
||||
Iterable<LinkEnricher> enrichers = registry.allByType(String.class);
|
||||
Iterable<HalEnricher> enrichers = registry.allByType(String.class);
|
||||
assertThat(enrichers).containsOnly(one);
|
||||
|
||||
enrichers = registry.allByType(Integer.class);
|
||||
assertThat(enrichers).containsOnly(two);
|
||||
}
|
||||
|
||||
private static class SampleLinkEnricher implements LinkEnricher {
|
||||
private static class SampleHalEnricher implements HalEnricher {
|
||||
@Override
|
||||
public void enrich(LinkEnricherContext context, LinkAppender appender) {
|
||||
public void enrich(HalEnricherContext context, HalAppender appender) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package sonia.scm.filter;
|
||||
|
||||
import com.google.inject.util.Providers;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.ext.WriterInterceptorContext;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GZipResponseFilterTest {
|
||||
|
||||
@Mock
|
||||
private HttpServletRequest request;
|
||||
|
||||
@Mock
|
||||
private WriterInterceptorContext context;
|
||||
|
||||
@Mock
|
||||
private MultivaluedMap<String,Object> headers;
|
||||
|
||||
private GZipResponseFilter filter;
|
||||
|
||||
@BeforeEach
|
||||
void setupObjectUnderTest() {
|
||||
filter = new GZipResponseFilter(Providers.of(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipGZipCompression() throws IOException {
|
||||
when(request.getHeader(HttpHeaders.ACCEPT_ENCODING)).thenReturn("deflate, br");
|
||||
|
||||
filter.aroundWriteTo(context);
|
||||
|
||||
verifySkipped();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipGZipCompressionWithoutAcceptEncodingHeader() throws IOException {
|
||||
filter.aroundWriteTo(context);
|
||||
|
||||
verifySkipped();
|
||||
}
|
||||
|
||||
private void verifySkipped() throws IOException {
|
||||
verify(context, never()).getOutputStream();
|
||||
verify(context).proceed();
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class AcceptGZipEncoding {
|
||||
|
||||
@BeforeEach
|
||||
void setUpContext() {
|
||||
when(request.getHeader(HttpHeaders.ACCEPT_ENCODING)).thenReturn("gzip, deflate, br");
|
||||
when(context.getHeaders()).thenReturn(headers);
|
||||
when(context.getOutputStream()).thenReturn(new ByteArrayOutputStream());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEncode() throws IOException {
|
||||
filter.aroundWriteTo(context);
|
||||
|
||||
verify(headers).remove(HttpHeaders.CONTENT_LENGTH);
|
||||
verify(headers).add(HttpHeaders.CONTENT_ENCODING, "gzip");
|
||||
|
||||
verify(context).setOutputStream(any(GZIPOutputStream.class));
|
||||
verify(context, times(2)).setOutputStream(any(OutputStream.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -36,7 +36,7 @@ import com.google.common.io.ByteSource;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
@@ -46,4 +46,13 @@ class RepositoryPermissionTest {
|
||||
|
||||
assertThat(permission1).isNotEqualTo(permission2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBeEqualWithRedundantVerbs() {
|
||||
RepositoryPermission permission1 = new RepositoryPermission("name1", asList("one", "two"), false);
|
||||
RepositoryPermission permission2 = new RepositoryPermission("name1", asList("one", "two"), false);
|
||||
permission2.setVerbs(asList("one", "two", "two"));
|
||||
|
||||
assertThat(permission1).isEqualTo(permission2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package sonia.scm.security;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.apache.shiro.authc.AuthenticationInfo;
|
||||
import org.apache.shiro.authc.DisabledAccountException;
|
||||
import org.apache.shiro.authc.UnknownAccountException;
|
||||
|
||||
57
scm-core/src/test/java/sonia/scm/util/ComparablesTest.java
Normal file
57
scm-core/src/test/java/sonia/scm/util/ComparablesTest.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package sonia.scm.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import static org.assertj.core.api.Java6Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class ComparablesTest {
|
||||
|
||||
@Test
|
||||
void shouldCompare() {
|
||||
One a = new One("a");
|
||||
One b = new One("b");
|
||||
|
||||
Comparator<One> comparable = Comparables.comparator(One.class, "value");
|
||||
assertThat(comparable.compare(a, b)).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowAnExceptionForNonExistingField() {
|
||||
assertThrows(IllegalArgumentException.class, () -> Comparables.comparator(One.class, "awesome"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowAnExceptionForNonComparableField() {
|
||||
assertThrows(IllegalArgumentException.class, () -> Comparables.comparator(One.class, "nonComparable"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowAnExceptionIfTheFieldHasNoGetter() {
|
||||
assertThrows(IllegalArgumentException.class, () -> Comparables.comparator(One.class, "incredible"));
|
||||
}
|
||||
|
||||
private static class One {
|
||||
|
||||
private String value;
|
||||
private String incredible;
|
||||
private NonComparable nonComparable;
|
||||
|
||||
One(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public NonComparable getNonComparable() {
|
||||
return nonComparable;
|
||||
}
|
||||
}
|
||||
|
||||
private static class NonComparable {}
|
||||
|
||||
}
|
||||
@@ -37,9 +37,6 @@ package sonia.scm.store;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.security.KeyGenerator;
|
||||
|
||||
@@ -38,7 +38,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.GenericDAO;
|
||||
import sonia.scm.ModelObject;
|
||||
import sonia.scm.group.xml.XmlGroupDAO;
|
||||
import sonia.scm.store.ConfigurationStore;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package sonia.scm.xml;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ package sonia.scm.it;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
@@ -5,10 +5,8 @@ import io.restassured.response.Response;
|
||||
import org.junit.Assert;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -18,8 +18,6 @@ import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
|
||||
/**
|
||||
* RESTful Web Service Resource to manage the configuration of the git plugin.
|
||||
*/
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.repository.spi.GitRepositoryServiceProvider;
|
||||
import sonia.scm.schedule.Scheduler;
|
||||
import sonia.scm.schedule.Task;
|
||||
@@ -103,9 +104,10 @@ public class GitRepositoryHandler
|
||||
public GitRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
||||
Scheduler scheduler,
|
||||
RepositoryLocationResolver repositoryLocationResolver,
|
||||
GitWorkdirFactory workdirFactory)
|
||||
GitWorkdirFactory workdirFactory,
|
||||
PluginLoader pluginLoader)
|
||||
{
|
||||
super(storeFactory, repositoryLocationResolver);
|
||||
super(storeFactory, repositoryLocationResolver, pluginLoader);
|
||||
this.scheduler = scheduler;
|
||||
this.workdirFactory = workdirFactory;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ package sonia.scm.web;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.inject.Inject;
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import React from "react";
|
||||
|
||||
import {apiClient, BranchSelector, ErrorPage, Loading, SubmitButton} from "@scm-manager/ui-components";
|
||||
import {apiClient, BranchSelector, ErrorPage, Loading, Subtitle, SubmitButton} from "@scm-manager/ui-components";
|
||||
import type {Branch, Repository} from "@scm-manager/ui-types";
|
||||
import {translate} from "react-i18next";
|
||||
|
||||
@@ -113,6 +113,7 @@ class RepositoryConfig extends React.Component<Props, State> {
|
||||
if (!(loadingBranches || loadingDefaultBranch)) {
|
||||
return (
|
||||
<>
|
||||
<Subtitle subtitle={t("scm-git-plugin.repo-config.title")}/>
|
||||
{this.renderBranchChangedNotification()}
|
||||
<form onSubmit={this.submit}>
|
||||
<BranchSelector
|
||||
@@ -127,6 +128,7 @@ class RepositoryConfig extends React.Component<Props, State> {
|
||||
disabled={!this.state.selectedBranchName}
|
||||
/>
|
||||
</form>
|
||||
<hr />
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -27,14 +27,9 @@ binder.bind(
|
||||
);
|
||||
binder.bind("repos.repository-avatar", GitAvatar, gitPredicate);
|
||||
|
||||
cfgBinder.bindRepository(
|
||||
"/configuration",
|
||||
"scm-git-plugin.repo-config.link",
|
||||
"configuration",
|
||||
RepositoryConfig
|
||||
);
|
||||
// global config
|
||||
binder.bind("repo-config.route", RepositoryConfig, gitPredicate);
|
||||
|
||||
// global config
|
||||
cfgBinder.bindGlobal(
|
||||
"/git",
|
||||
"scm-git-plugin.config.link",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
},
|
||||
"repo-config": {
|
||||
"link": "Konfiguration",
|
||||
"title": "Git Einstellungen",
|
||||
"default-branch": "Standard Branch",
|
||||
"submit": "Speichern",
|
||||
"error": {
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
},
|
||||
"repo-config": {
|
||||
"link": "Configuration",
|
||||
"title": "Git Settings",
|
||||
"default-branch": "Default branch",
|
||||
"submit": "Submit",
|
||||
"error": {
|
||||
|
||||
@@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.GitConfig;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.mockito.Captor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.GitConfig;
|
||||
import sonia.scm.repository.GitRepositoryConfig;
|
||||
import sonia.scm.repository.GitRepositoryHandler;
|
||||
@@ -29,6 +29,7 @@ import sonia.scm.store.ConfigurationStoreFactory;
|
||||
import sonia.scm.web.GitVndMediaType;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
@@ -100,7 +101,7 @@ public class GitConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readWrite")
|
||||
public void shouldGetGitConfig() throws URISyntaxException {
|
||||
public void shouldGetGitConfig() throws URISyntaxException, UnsupportedEncodingException {
|
||||
MockHttpResponse response = get();
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
@@ -115,7 +116,7 @@ public class GitConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readWrite")
|
||||
public void shouldGetGitConfigEvenWhenItsEmpty() throws URISyntaxException {
|
||||
public void shouldGetGitConfigEvenWhenItsEmpty() throws URISyntaxException, UnsupportedEncodingException {
|
||||
when(repositoryHandler.getConfig()).thenReturn(null);
|
||||
|
||||
MockHttpResponse response = get();
|
||||
@@ -126,7 +127,7 @@ public class GitConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldGetGitConfigWithoutUpdateLink() throws URISyntaxException {
|
||||
public void shouldGetGitConfigWithoutUpdateLink() throws URISyntaxException, UnsupportedEncodingException {
|
||||
MockHttpResponse response = get();
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
@@ -159,7 +160,7 @@ public class GitConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldReadDefaultRepositoryConfig() throws URISyntaxException {
|
||||
public void shouldReadDefaultRepositoryConfig() throws URISyntaxException, UnsupportedEncodingException {
|
||||
when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(new Repository("id", "git", "space", "X"));
|
||||
|
||||
MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2 + "/space/X");
|
||||
@@ -176,7 +177,7 @@ public class GitConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldNotHaveUpdateLinkForReadOnlyUser() throws URISyntaxException {
|
||||
public void shouldNotHaveUpdateLinkForReadOnlyUser() throws URISyntaxException, UnsupportedEncodingException {
|
||||
when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(new Repository("id", "git", "space", "X"));
|
||||
|
||||
MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2 + "/space/X");
|
||||
@@ -193,7 +194,7 @@ public class GitConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldReadStoredRepositoryConfig() throws URISyntaxException {
|
||||
public void shouldReadStoredRepositoryConfig() throws URISyntaxException, UnsupportedEncodingException {
|
||||
when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(new Repository("id", "git", "space", "X"));
|
||||
GitRepositoryConfig gitRepositoryConfig = new GitRepositoryConfig();
|
||||
gitRepositoryConfig.setDefaultBranch("test");
|
||||
|
||||
@@ -11,10 +11,9 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.GitConfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -14,9 +14,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryManager;
|
||||
import sonia.scm.repository.api.Command;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
import sonia.scm.web.JsonEnricherContext;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -94,7 +94,7 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
||||
RepositoryLocationResolver locationResolver,
|
||||
File directory) {
|
||||
GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory,
|
||||
scheduler, locationResolver, gitWorkdirFactory);
|
||||
scheduler, locationResolver, gitWorkdirFactory, null);
|
||||
repositoryHandler.init(contextProvider);
|
||||
|
||||
GitConfig config = new GitConfig();
|
||||
@@ -108,7 +108,7 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
||||
@Test
|
||||
public void getDirectory() {
|
||||
GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory,
|
||||
scheduler, locationResolver, gitWorkdirFactory);
|
||||
scheduler, locationResolver, gitWorkdirFactory, null);
|
||||
GitConfig config = new GitConfig();
|
||||
config.setDisabled(false);
|
||||
config.setGcExpression("gc exp");
|
||||
|
||||
@@ -35,10 +35,8 @@ package sonia.scm.repository.spi;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||
import sonia.scm.repository.GitRepositoryConfig;
|
||||
import sonia.scm.store.InMemoryConfigurationStore;
|
||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,11 +35,9 @@ package sonia.scm.repository.spi;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import org.junit.Test;
|
||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||
import sonia.scm.repository.BlameLine;
|
||||
import sonia.scm.repository.BlameResult;
|
||||
import sonia.scm.repository.GitRepositoryConfig;
|
||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
@@ -32,11 +32,9 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import org.junit.Test;
|
||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||
import sonia.scm.repository.BrowserResult;
|
||||
import sonia.scm.repository.FileObject;
|
||||
import sonia.scm.repository.GitRepositoryConfig;
|
||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -36,10 +36,8 @@ package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import org.junit.Test;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.ChangesetPagingResult;
|
||||
import sonia.scm.repository.ClearRepositoryCacheEvent;
|
||||
import sonia.scm.repository.GitRepositoryConfig;
|
||||
import sonia.scm.repository.Modifications;
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.GitConfig;
|
||||
import sonia.scm.repository.GitRepositoryHandler;
|
||||
|
||||
|
||||
@@ -35,14 +35,12 @@ package sonia.scm.installer;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.util.IOUtil;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,7 +39,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.io.INIConfiguration;
|
||||
import sonia.scm.io.INIConfigurationReader;
|
||||
import sonia.scm.io.INIConfigurationWriter;
|
||||
import sonia.scm.io.INISection;
|
||||
import sonia.scm.util.ValidationUtil;
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ import sonia.scm.io.INIConfigurationReader;
|
||||
import sonia.scm.io.INIConfigurationWriter;
|
||||
import sonia.scm.io.INISection;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.repository.spi.HgRepositoryServiceProvider;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
import sonia.scm.util.IOUtil;
|
||||
@@ -111,9 +112,10 @@ public class HgRepositoryHandler
|
||||
@Inject
|
||||
public HgRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
||||
Provider<HgContext> hgContextProvider,
|
||||
RepositoryLocationResolver repositoryLocationResolver)
|
||||
RepositoryLocationResolver repositoryLocationResolver,
|
||||
PluginLoader pluginLoader)
|
||||
{
|
||||
super(storeFactory, repositoryLocationResolver);
|
||||
super(storeFactory, repositoryLocationResolver, pluginLoader);
|
||||
this.hgContextProvider = hgContextProvider;
|
||||
|
||||
try
|
||||
|
||||
@@ -41,7 +41,6 @@ import com.aragost.javahg.internals.AbstractCommand;
|
||||
import com.aragost.javahg.internals.HgInputStream;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import sonia.scm.repository.FileObject;
|
||||
import sonia.scm.repository.SubRepository;
|
||||
@@ -52,7 +51,6 @@ import java.io.IOException;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Mercurial command to list files of a repository.
|
||||
|
||||
@@ -44,7 +44,6 @@ import sonia.scm.web.filter.PermissionFilter;
|
||||
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.web.HgVndMediaType;
|
||||
|
||||
@@ -3,11 +3,9 @@ package sonia.scm.api.v2.resources;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.installer.HgPackage;
|
||||
import sonia.scm.installer.HgPackageReader;
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.installer.HgPackage;
|
||||
import sonia.scm.installer.HgPackages;
|
||||
|
||||
|
||||
@@ -16,15 +16,15 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.web.HgVndMediaType;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
@@ -99,7 +99,7 @@ public class HgConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readWrite")
|
||||
public void shouldGetHgConfigEvenWhenItsEmpty() throws URISyntaxException {
|
||||
public void shouldGetHgConfigEvenWhenItsEmpty() throws URISyntaxException, UnsupportedEncodingException {
|
||||
when(repositoryHandler.getConfig()).thenReturn(null);
|
||||
|
||||
MockHttpResponse response = get();
|
||||
@@ -110,7 +110,7 @@ public class HgConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldGetHgConfigWithoutUpdateLink() throws URISyntaxException {
|
||||
public void shouldGetHgConfigWithoutUpdateLink() throws URISyntaxException, UnsupportedEncodingException {
|
||||
MockHttpResponse response = get();
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
|
||||
@@ -3,8 +3,6 @@ package sonia.scm.api.v2.resources;
|
||||
import sonia.scm.installer.HgPackage;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
@@ -77,7 +77,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
||||
|
||||
@Override
|
||||
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) {
|
||||
HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver);
|
||||
HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver, null);
|
||||
|
||||
handler.init(contextProvider);
|
||||
HgTestUtil.checkForSkip(handler);
|
||||
@@ -87,7 +87,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
||||
|
||||
@Test
|
||||
public void getDirectory() {
|
||||
HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver);
|
||||
HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver, null);
|
||||
|
||||
HgConfig hgConfig = new HgConfig();
|
||||
hgConfig.setHgBinary("hg");
|
||||
|
||||
@@ -105,7 +105,7 @@ public final class HgTestUtil
|
||||
|
||||
RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(context, repoDao, new InitialRepositoryLocationResolver());
|
||||
HgRepositoryHandler handler =
|
||||
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver);
|
||||
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null);
|
||||
Path repoDir = directory.toPath();
|
||||
when(repoDao.getPath(any())).thenReturn(repoDir);
|
||||
handler.init(context);
|
||||
|
||||
@@ -8,7 +8,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@@ -48,8 +48,6 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static sonia.scm.web.WireProtocolRequestMockFactory.CMDS_HEADS_KNOWN_NODES;
|
||||
import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.BOOKMARKS;
|
||||
import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.PHASES;
|
||||
|
||||
@@ -37,7 +37,7 @@ import com.google.common.collect.Lists;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@@ -53,9 +53,9 @@ import sonia.scm.io.INIConfigurationWriter;
|
||||
import sonia.scm.io.INISection;
|
||||
import sonia.scm.logging.SVNKitLogger;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.repository.spi.HookEventFacade;
|
||||
import sonia.scm.repository.spi.SvnRepositoryServiceProvider;
|
||||
import sonia.scm.store.ConfigurationStore;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
@@ -97,9 +97,10 @@ public class SvnRepositoryHandler
|
||||
@Inject
|
||||
public SvnRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
||||
HookEventFacade eventFacade,
|
||||
RepositoryLocationResolver repositoryLocationResolver)
|
||||
RepositoryLocationResolver repositoryLocationResolver,
|
||||
PluginLoader pluginLoader)
|
||||
{
|
||||
super(storeFactory, repositoryLocationResolver);
|
||||
super(storeFactory, repositoryLocationResolver, pluginLoader);
|
||||
|
||||
// register logger
|
||||
SVNDebugLog.setDefaultLog(new SVNKitLogger());
|
||||
|
||||
@@ -36,7 +36,6 @@ package sonia.scm.repository.spi;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.tmatesoft.svn.core.SVNDirEntry;
|
||||
@@ -53,7 +52,6 @@ import sonia.scm.repository.SvnUtil;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.tmatesoft.svn.core.SVNException;
|
||||
import org.tmatesoft.svn.core.SVNLogEntry;
|
||||
import org.tmatesoft.svn.core.io.SVNRepository;
|
||||
import org.tmatesoft.svn.core.wc.SVNClientManager;
|
||||
import org.tmatesoft.svn.core.wc.admin.SVNLookClient;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.Repository;
|
||||
@@ -19,23 +21,45 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif
|
||||
super(context, repository);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Modifications getModifications(String revision) {
|
||||
Modifications modifications = null;
|
||||
log.debug("get modifications {}", revision);
|
||||
public Modifications getModifications(String revisionOrTransactionId) {
|
||||
Modifications modifications;
|
||||
try {
|
||||
long revisionNumber = SvnUtil.parseRevision(revision, repository);
|
||||
if (SvnUtil.isTransactionEntryId(revisionOrTransactionId)) {
|
||||
modifications = getModificationsFromTransaction(SvnUtil.getTransactionId(revisionOrTransactionId));
|
||||
} else {
|
||||
modifications = getModificationFromRevision(revisionOrTransactionId);
|
||||
}
|
||||
return modifications;
|
||||
} catch (SVNException ex) {
|
||||
throw new InternalRepositoryException(
|
||||
repository,
|
||||
"failed to get svn modifications for " + revisionOrTransactionId,
|
||||
ex
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Modifications getModificationFromRevision(String revision) throws SVNException {
|
||||
log.debug("get svn modifications from revision: {}", revision);
|
||||
long revisionNumber = SvnUtil.getRevisionNumber(revision, repository);
|
||||
SVNRepository repo = open();
|
||||
Collection<SVNLogEntry> entries = repo.log(null, null, revisionNumber,
|
||||
revisionNumber, true, true);
|
||||
if (Util.isNotEmpty(entries)) {
|
||||
modifications = SvnUtil.createModifications(entries.iterator().next(), revision);
|
||||
return SvnUtil.createModifications(entries.iterator().next(), revision);
|
||||
}
|
||||
} catch (SVNException ex) {
|
||||
throw new InternalRepositoryException(repository, "could not open repository", ex);
|
||||
return null;
|
||||
}
|
||||
|
||||
private Modifications getModificationsFromTransaction(String transaction) throws SVNException {
|
||||
log.debug("get svn modifications from transaction: {}", transaction);
|
||||
final Modifications modifications = new Modifications();
|
||||
SVNLookClient client = SVNClientManager.newInstance().getLookClient();
|
||||
client.doGetChanged(context.getDirectory(), transaction,
|
||||
e -> SvnUtil.appendModification(modifications, e.getType(), e.getPath()), true);
|
||||
|
||||
return modifications;
|
||||
}
|
||||
|
||||
@@ -44,5 +68,4 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif
|
||||
return getModifications(request.getRevision());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.Compatibility;
|
||||
import sonia.scm.repository.SvnConfig;
|
||||
|
||||
|
||||
@@ -16,14 +16,14 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.SvnConfig;
|
||||
import sonia.scm.repository.SvnRepositoryHandler;
|
||||
import sonia.scm.web.SvnVndMediaType;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
@@ -98,7 +98,7 @@ public class SvnConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldGetSvnConfigWithoutUpdateLink() throws URISyntaxException {
|
||||
public void shouldGetSvnConfigWithoutUpdateLink() throws URISyntaxException, UnsupportedEncodingException {
|
||||
MockHttpResponse response = get();
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
|
||||
@@ -11,11 +11,10 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.Compatibility;
|
||||
import sonia.scm.repository.SvnConfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -32,14 +32,10 @@
|
||||
package sonia.scm.repository;
|
||||
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.api.HookContextFactory;
|
||||
import sonia.scm.repository.spi.HookEventFacade;
|
||||
import sonia.scm.store.ConfigurationStore;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
|
||||
import java.io.File;
|
||||
@@ -47,7 +43,7 @@ import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
@@ -93,7 +89,7 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
||||
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory,
|
||||
RepositoryLocationResolver locationResolver,
|
||||
File directory) {
|
||||
SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, locationResolver);
|
||||
SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, locationResolver, null);
|
||||
|
||||
handler.init(contextProvider);
|
||||
|
||||
@@ -109,7 +105,7 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
||||
public void getDirectory() {
|
||||
when(factory.withType(any())).thenCallRealMethod();
|
||||
SvnRepositoryHandler repositoryHandler = new SvnRepositoryHandler(factory,
|
||||
facade, locationResolver);
|
||||
facade, locationResolver, null);
|
||||
|
||||
SvnConfig svnConfig = new SvnConfig();
|
||||
repositoryHandler.setConfig(svnConfig);
|
||||
|
||||
@@ -39,8 +39,6 @@ import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.ChangesetPagingResult;
|
||||
import sonia.scm.repository.Modifications;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@@ -59,7 +59,7 @@ public class DummyRepositoryHandler
|
||||
private final Set<String> existingRepoNames = new HashSet<>();
|
||||
|
||||
public DummyRepositoryHandler(ConfigurationStoreFactory storeFactory, RepositoryLocationResolver repositoryLocationResolver) {
|
||||
super(storeFactory, repositoryLocationResolver);
|
||||
super(storeFactory, repositoryLocationResolver, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -13,10 +13,11 @@ const styles = {
|
||||
minWidthOfLabel: {
|
||||
minWidth: "4.5rem"
|
||||
},
|
||||
wrapper: {
|
||||
padding: "1rem 1.5rem",
|
||||
border: "1px solid #eee",
|
||||
borderRadius: "5px 5px 0 0"
|
||||
labelSizing: {
|
||||
fontSize: "1rem !important"
|
||||
},
|
||||
noBottomMargin: {
|
||||
marginBottom: "0 !important"
|
||||
}
|
||||
};
|
||||
|
||||
@@ -52,9 +53,9 @@ class BranchSelector extends React.Component<Props, State> {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"has-background-light field",
|
||||
"field",
|
||||
"is-horizontal",
|
||||
classes.wrapper
|
||||
classes.noBottomMargin
|
||||
)}
|
||||
>
|
||||
<div
|
||||
@@ -65,10 +66,14 @@ class BranchSelector extends React.Component<Props, State> {
|
||||
classes.minWidthOfLabel
|
||||
)}
|
||||
>
|
||||
<label className="label">{label}</label>
|
||||
<label className={classNames("label", classes.labelSizing)}>
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
<div className="field-body">
|
||||
<div className="field is-narrow">
|
||||
<div
|
||||
className={classNames("field is-narrow", classes.noBottomMargin)}
|
||||
>
|
||||
<div className="control">
|
||||
<DropDown
|
||||
className="is-fullwidth"
|
||||
|
||||
@@ -2,31 +2,40 @@
|
||||
import React from "react";
|
||||
import moment from "moment";
|
||||
import { translate } from "react-i18next";
|
||||
import injectSheet from "react-jss";
|
||||
|
||||
const styles = {
|
||||
date: {
|
||||
borderBottom: "1px dotted rgba(219, 219, 219)",
|
||||
cursor: "help"
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
date?: string,
|
||||
|
||||
// context props
|
||||
classes: any,
|
||||
i18n: any
|
||||
};
|
||||
|
||||
class DateFromNow extends React.Component<Props> {
|
||||
static format(locale: string, date?: string) {
|
||||
let fromNow = "";
|
||||
if (date) {
|
||||
fromNow = moment(date)
|
||||
.locale(locale)
|
||||
.fromNow();
|
||||
}
|
||||
return fromNow;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { i18n, date } = this.props;
|
||||
const { i18n, date, classes } = this.props;
|
||||
|
||||
const fromNow = DateFromNow.format(i18n.language, date);
|
||||
return <span>{fromNow}</span>;
|
||||
if (date) {
|
||||
const dateWithLocale = moment(date).locale(i18n.language);
|
||||
|
||||
return (
|
||||
<time title={dateWithLocale.format()} className={classes.date}>
|
||||
{dateWithLocale.fromNow()}
|
||||
</time>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate()(DateFromNow);
|
||||
export default injectSheet(styles)(translate()(DateFromNow));
|
||||
|
||||
@@ -4,21 +4,27 @@ import classNames from "classnames";
|
||||
|
||||
type Props = {
|
||||
message: string,
|
||||
className: string,
|
||||
className?: string,
|
||||
location: string,
|
||||
children: React.Node
|
||||
};
|
||||
|
||||
class Tooltip extends React.Component<Props> {
|
||||
|
||||
static defaultProps = {
|
||||
location: "right"
|
||||
};
|
||||
|
||||
render() {
|
||||
const { className, message, children } = this.props;
|
||||
const { className, message, location, children } = this.props;
|
||||
const multiline = message.length > 60 ? "is-tooltip-multiline" : "";
|
||||
return (
|
||||
<div
|
||||
className={classNames("tooltip", "is-tooltip-right", multiline, className)}
|
||||
<span
|
||||
className={classNames("tooltip", "is-tooltip-" + location, multiline, className)}
|
||||
data-tooltip={message}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import * as React from "react";
|
||||
import classNames from "classnames";
|
||||
import { withRouter } from "react-router-dom";
|
||||
|
||||
export type ButtonProps = {
|
||||
label: string,
|
||||
label?: string,
|
||||
loading?: boolean,
|
||||
disabled?: boolean,
|
||||
action?: (event: Event) => void,
|
||||
link?: string,
|
||||
fullWidth?: boolean,
|
||||
className?: string,
|
||||
children?: React.Node,
|
||||
classes: any
|
||||
};
|
||||
|
||||
@@ -45,6 +46,7 @@ class Button extends React.Component<Props> {
|
||||
type,
|
||||
color,
|
||||
fullWidth,
|
||||
children,
|
||||
className
|
||||
} = this.props;
|
||||
const loadingClass = loading ? "is-loading" : "";
|
||||
@@ -62,7 +64,7 @@ class Button extends React.Component<Props> {
|
||||
className
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{label} {children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,41 +1,30 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import Button from "./Button";
|
||||
import * as React from "react";
|
||||
|
||||
type Props = {
|
||||
firstlabel: string,
|
||||
secondlabel: string,
|
||||
firstAction?: (event: Event) => void,
|
||||
secondAction?: (event: Event) => void,
|
||||
firstIsSelected: boolean
|
||||
addons?: boolean,
|
||||
className?: string,
|
||||
children: React.Node
|
||||
};
|
||||
|
||||
class ButtonGroup extends React.Component<Props> {
|
||||
|
||||
static defaultProps = {
|
||||
addons: true
|
||||
};
|
||||
|
||||
render() {
|
||||
const { firstlabel, secondlabel, firstAction, secondAction, firstIsSelected } = this.props;
|
||||
|
||||
let showFirstColor = "";
|
||||
let showSecondColor = "";
|
||||
|
||||
if (firstIsSelected) {
|
||||
showFirstColor += "link is-selected";
|
||||
} else {
|
||||
showSecondColor += "link is-selected";
|
||||
const { addons, className, children } = this.props;
|
||||
let styleClasses = "buttons";
|
||||
if (addons) {
|
||||
styleClasses += " has-addons";
|
||||
}
|
||||
if (className) {
|
||||
styleClasses += " " + className;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="buttons has-addons">
|
||||
<Button
|
||||
label={firstlabel}
|
||||
color={showFirstColor}
|
||||
action={firstAction}
|
||||
/>
|
||||
<Button
|
||||
label={secondlabel}
|
||||
color={showSecondColor}
|
||||
action={secondAction}
|
||||
/>
|
||||
<div className={styleClasses}>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,14 +3,17 @@ import React from "react";
|
||||
|
||||
type Props = {
|
||||
displayName: string,
|
||||
url: string
|
||||
url: string,
|
||||
disabled: boolean,
|
||||
onClick?: () => void
|
||||
};
|
||||
|
||||
class DownloadButton extends React.Component<Props> {
|
||||
render() {
|
||||
const { displayName, url } = this.props;
|
||||
const { displayName, url, disabled, onClick } = this.props;
|
||||
const onClickOrDefault = !!onClick ? onClick : () => {};
|
||||
return (
|
||||
<a className="button is-large is-link" href={url}>
|
||||
<a className="button is-large is-link" href={url} disabled={disabled} onClick={onClickOrDefault}>
|
||||
<span className="icon is-medium">
|
||||
<i className="fas fa-arrow-circle-down" />
|
||||
</span>
|
||||
|
||||
@@ -72,6 +72,33 @@ class ConfigurationBinder {
|
||||
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
|
||||
binder.bind("repository.route", RepoRoute, repoPredicate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new ConfigurationBinder();
|
||||
|
||||
@@ -10,7 +10,8 @@ type Props = {
|
||||
buttonLabel: string,
|
||||
fieldLabel: string,
|
||||
errorMessage: string,
|
||||
helpText?: string
|
||||
helpText?: string,
|
||||
validateEntry?: string => boolean
|
||||
};
|
||||
|
||||
type State = {
|
||||
@@ -25,6 +26,15 @@ class AddEntryToTableField extends React.Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
isValid = () => {
|
||||
const {validateEntry} = this.props;
|
||||
if (!this.state.entryToAdd || this.state.entryToAdd === "" || !validateEntry) {
|
||||
return true;
|
||||
} else {
|
||||
return validateEntry(this.state.entryToAdd);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
disabled,
|
||||
@@ -39,7 +49,7 @@ class AddEntryToTableField extends React.Component<Props, State> {
|
||||
label={fieldLabel}
|
||||
errorMessage={errorMessage}
|
||||
onChange={this.handleAddEntryChange}
|
||||
validationError={false}
|
||||
validationError={!this.isValid()}
|
||||
value={this.state.entryToAdd}
|
||||
onReturnPressed={this.appendEntry}
|
||||
disabled={disabled}
|
||||
@@ -48,7 +58,7 @@ class AddEntryToTableField extends React.Component<Props, State> {
|
||||
<AddButton
|
||||
label={buttonLabel}
|
||||
action={this.addButtonClicked}
|
||||
disabled={disabled || this.state.entryToAdd ===""}
|
||||
disabled={disabled || this.state.entryToAdd ==="" || !this.isValid()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
/*modified from https://github.com/GA-MO/react-confirm-alert*/
|
||||
.react-confirm-alert-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 99;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
display: -webkit-flex;
|
||||
display: -moz-flex;
|
||||
display: -ms-flex;
|
||||
display: -o-flex;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
-ms-align-items: center;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
-webkit-animation: react-confirm-alert-fadeIn 0.5s 0.2s forwards;
|
||||
-moz-animation: react-confirm-alert-fadeIn 0.5s 0.2s forwards;
|
||||
-o-animation: react-confirm-alert-fadeIn 0.5s 0.2s forwards;
|
||||
animation: react-confirm-alert-fadeIn 0.5s 0.2s forwards;
|
||||
}
|
||||
|
||||
.react-confirm-alert-body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
width: 400px;
|
||||
padding: 30px;
|
||||
text-align: left;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 20px 75px rgba(0, 0, 0, 0.13);
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.react-confirm-alert-body > h1 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.react-confirm-alert-body > h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.react-confirm-alert-button-group {
|
||||
display: -webkit-flex;
|
||||
display: -moz-flex;
|
||||
display: -ms-flex;
|
||||
display: -o-flex;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.react-confirm-alert-button-group > button {
|
||||
outline: none;
|
||||
background: #333;
|
||||
border: none;
|
||||
display: inline-block;
|
||||
padding: 6px 18px;
|
||||
color: #eee;
|
||||
margin-right: 10px;
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@-webkit-keyframes react-confirm-alert-fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes react-confirm-alert-fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes react-confirm-alert-fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes react-confirm-alert-fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
// @flow
|
||||
//modified from https://github.com/GA-MO/react-confirm-alert
|
||||
|
||||
import * as React from "react";
|
||||
import { render, unmountComponentAtNode } from "react-dom";
|
||||
import "./ConfirmAlert.css";
|
||||
import ReactDOM from "react-dom";
|
||||
import Modal from "./Modal";
|
||||
|
||||
type Button = {
|
||||
label: string,
|
||||
@@ -25,59 +23,47 @@ class ConfirmAlert extends React.Component<Props> {
|
||||
};
|
||||
|
||||
close = () => {
|
||||
removeElementReconfirm();
|
||||
ReactDOM.unmountComponentAtNode(document.getElementById("modalRoot"));
|
||||
};
|
||||
|
||||
render() {
|
||||
const { title, message, buttons } = this.props;
|
||||
|
||||
return (
|
||||
<div className="react-confirm-alert-overlay">
|
||||
<div className="react-confirm-alert">
|
||||
{
|
||||
<div className="react-confirm-alert-body">
|
||||
{title && <h1>{title}</h1>}
|
||||
{message}
|
||||
<div className="react-confirm-alert-button-group">
|
||||
const body = <>{message}</>;
|
||||
|
||||
const footer = (
|
||||
<div className="field is-grouped">
|
||||
{buttons.map((button, i) => (
|
||||
<button
|
||||
<p className="control">
|
||||
<a
|
||||
className="button is-info"
|
||||
key={i}
|
||||
onClick={() => this.handleClickButton(button)}
|
||||
href="javascript:void(0);"
|
||||
>
|
||||
{button.label}
|
||||
</button>
|
||||
</a>
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={title}
|
||||
closeFunction={() => this.close()}
|
||||
body={body}
|
||||
active={true}
|
||||
footer={footer}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function createElementReconfirm(properties: Props) {
|
||||
const divTarget = document.createElement("div");
|
||||
divTarget.id = "react-confirm-alert";
|
||||
if (document.body) {
|
||||
document.body.appendChild(divTarget);
|
||||
render(<ConfirmAlert {...properties} />, divTarget);
|
||||
}
|
||||
}
|
||||
|
||||
function removeElementReconfirm() {
|
||||
const target = document.getElementById("react-confirm-alert");
|
||||
if (target) {
|
||||
unmountComponentAtNode(target);
|
||||
if (target.parentNode) {
|
||||
target.parentNode.removeChild(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function confirmAlert(properties: Props) {
|
||||
createElementReconfirm(properties);
|
||||
const root = document.getElementById("modalRoot");
|
||||
if (root) {
|
||||
ReactDOM.render(<ConfirmAlert {...properties} />, root);
|
||||
}
|
||||
}
|
||||
|
||||
export default ConfirmAlert;
|
||||
|
||||
54
scm-ui-components/packages/ui-components/src/modals/Modal.js
Normal file
54
scm-ui-components/packages/ui-components/src/modals/Modal.js
Normal file
@@ -0,0 +1,54 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import classNames from "classnames";
|
||||
import injectSheet from "react-jss";
|
||||
|
||||
type Props = {
|
||||
title: string,
|
||||
closeFunction: () => void,
|
||||
body: any,
|
||||
footer?: any,
|
||||
active: boolean,
|
||||
classes: any
|
||||
};
|
||||
|
||||
const styles = {
|
||||
resize: {
|
||||
maxWidth: "100%",
|
||||
width: "auto !important",
|
||||
display: "inline-block"
|
||||
}
|
||||
};
|
||||
|
||||
class Modal extends React.Component<Props> {
|
||||
render() {
|
||||
const { title, closeFunction, body, footer, active, classes } = this.props;
|
||||
|
||||
const isActive = active ? "is-active" : null;
|
||||
|
||||
let showFooter = null;
|
||||
if (footer) {
|
||||
showFooter = <footer className="modal-card-foot">{footer}</footer>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames("modal", isActive)}>
|
||||
<div className="modal-background" />
|
||||
<div className={classNames("modal-card", classes.resize)}>
|
||||
<header className="modal-card-head">
|
||||
<p className="modal-card-title">{title}</p>
|
||||
<button
|
||||
className="delete"
|
||||
aria-label="close"
|
||||
onClick={closeFunction}
|
||||
/>
|
||||
</header>
|
||||
<section className="modal-card-body">{body}</section>
|
||||
{showFooter}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectSheet(styles)(Modal);
|
||||
@@ -1,4 +1,5 @@
|
||||
// @create-index
|
||||
|
||||
export { default as ConfirmAlert, confirmAlert } from "./ConfirmAlert.js";
|
||||
export { default as Modal } from "./Modal.js";
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class NavLink extends React.Component<Props> {
|
||||
|
||||
let showIcon = null;
|
||||
if (icon) {
|
||||
showIcon = (<><i className={icon}></i>{" "}</>);
|
||||
showIcon = (<><i className={icon} />{" "}</>);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -50,8 +50,19 @@ class PrimaryNavigation extends React.Component<Props> {
|
||||
|
||||
createNavigationItems = () => {
|
||||
const navigationItems = [];
|
||||
const { t, links } = this.props;
|
||||
|
||||
const props = {
|
||||
links,
|
||||
label: t("primary-navigation.first-menu")
|
||||
};
|
||||
|
||||
const append = this.createNavigationAppender(navigationItems);
|
||||
if (binder.hasExtension("primary-navigation.first-menu", props)) {
|
||||
navigationItems.push(
|
||||
<ExtensionPoint name="primary-navigation.first-menu" props={props} />
|
||||
);
|
||||
}
|
||||
append("/repos", "/(repo|repos)", "primary-navigation.repositories", "repositories");
|
||||
append("/users", "/(user|users)", "primary-navigation.users", "users");
|
||||
append("/groups", "/(group|groups)", "primary-navigation.groups", "groups");
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
//@flow
|
||||
import * as React from "react";
|
||||
import { Link, Route } from "react-router-dom";
|
||||
|
||||
type Props = {
|
||||
to: string,
|
||||
icon?: string,
|
||||
label: string,
|
||||
activeOnlyWhenExact?: boolean,
|
||||
activeWhenMatch?: (route: any) => boolean,
|
||||
children?: React.Node
|
||||
};
|
||||
|
||||
class SubNavigation extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
activeOnlyWhenExact: false
|
||||
};
|
||||
|
||||
isActive(route: any) {
|
||||
const { activeWhenMatch } = this.props;
|
||||
return route.match || (activeWhenMatch && activeWhenMatch(route));
|
||||
}
|
||||
|
||||
renderLink = (route: any) => {
|
||||
const { to, icon, label } = this.props;
|
||||
|
||||
let defaultIcon = "fas fa-cog";
|
||||
if (icon) {
|
||||
defaultIcon = icon;
|
||||
}
|
||||
|
||||
let children = null;
|
||||
if (this.isActive(route)) {
|
||||
children = <ul className="sub-menu">{this.props.children}</ul>;
|
||||
}
|
||||
|
||||
return (
|
||||
<li>
|
||||
<Link className={this.isActive(route) ? "is-active" : ""} to={to}>
|
||||
<i className={defaultIcon} /> {label}
|
||||
</Link>
|
||||
{children}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { to, activeOnlyWhenExact } = this.props;
|
||||
|
||||
// removes last part of url
|
||||
let parents = to.split("/");
|
||||
parents.splice(-1, 1);
|
||||
let parent = parents.join("/");
|
||||
|
||||
return (
|
||||
<Route
|
||||
path={parent}
|
||||
exact={activeOnlyWhenExact}
|
||||
children={this.renderLink}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SubNavigation;
|
||||
@@ -3,6 +3,7 @@
|
||||
export { default as NavAction } from "./NavAction.js";
|
||||
export { default as NavLink } from "./NavLink.js";
|
||||
export { default as Navigation } from "./Navigation.js";
|
||||
export { default as SubNavigation } from "./SubNavigation.js";
|
||||
export { default as PrimaryNavigation } from "./PrimaryNavigation.js";
|
||||
export { default as PrimaryNavigationLink } from "./PrimaryNavigationLink.js";
|
||||
export { default as Section } from "./Section.js";
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Changeset } from "@scm-manager/ui-types";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import {translate} from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
changeset: Changeset
|
||||
changeset: Changeset,
|
||||
|
||||
// context props
|
||||
t: (string) => string
|
||||
};
|
||||
|
||||
class ChangesetAuthor extends React.Component<Props> {
|
||||
@@ -13,26 +18,35 @@ class ChangesetAuthor extends React.Component<Props> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { name } = changeset.author;
|
||||
return (
|
||||
<>
|
||||
{name} {this.renderMail()}
|
||||
</>
|
||||
);
|
||||
const { name, mail } = changeset.author;
|
||||
if (mail) {
|
||||
return this.withExtensionPoint(this.renderWithMail(name, mail));
|
||||
}
|
||||
return this.withExtensionPoint(<>{name}</>);
|
||||
}
|
||||
|
||||
renderMail() {
|
||||
const { mail } = this.props.changeset.author;
|
||||
if (mail) {
|
||||
renderWithMail(name: string, mail: string) {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<a className="is-hidden-mobile" href={"mailto:" + mail}>
|
||||
<
|
||||
{mail}
|
||||
>
|
||||
<a href={"mailto: " + mail} title={t("changeset.author.mailto") + " " + mail}>
|
||||
{name}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
withExtensionPoint(child: any) {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<>
|
||||
{t("changeset.author.prefix")} {child}
|
||||
<ExtensionPoint
|
||||
name="changesets.author.suffix"
|
||||
props={{ changeset: this.props.changeset }}
|
||||
renderAll={true}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChangesetAuthor;
|
||||
export default translate("repos")(ChangesetAuthor);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user