diff --git a/Jenkinsfile b/Jenkinsfile index 36135cf4a4..6a9fa7e044 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -7,7 +7,7 @@ import com.cloudogu.ces.cesbuildlib.* node('docker') { // Change this as when we go back to default - necessary for proper SonarQube analysis - mainBranch = '2.0.0-m3' + mainBranch = 'default' properties([ // Keep only the last 10 build to preserve space diff --git a/package.json b/package.json index b2f14826c5..8d9a9c931b 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "test": "lerna run --scope '@scm-manager/ui-*' test", "typecheck": "lerna run --scope '@scm-manager/ui-*' typecheck", "serve": "webpack-dev-server --mode=development --config=scm-ui/ui-scripts/src/webpack.config.js", - "deploy": "ui-scripts publish" + "deploy": "ui-scripts publish", + "set-version": "ui-scripts version" }, "devDependencies": { "babel-plugin-reflow": "^0.2.7", diff --git a/scm-annotation-processor/src/main/java/sonia/scm/annotation/ScmAnnotationProcessor.java b/scm-annotation-processor/src/main/java/sonia/scm/annotation/ScmAnnotationProcessor.java index c548f30d45..38961ccaf8 100644 --- a/scm-annotation-processor/src/main/java/sonia/scm/annotation/ScmAnnotationProcessor.java +++ b/scm-annotation-processor/src/main/java/sonia/scm/annotation/ScmAnnotationProcessor.java @@ -99,8 +99,9 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; +import static javax.lang.model.util.ElementFilter.methodsIn; + /** - * * @author Sebastian Sdorra */ @SupportedAnnotationTypes("*") @@ -372,6 +373,18 @@ public final class ScmAnnotationProcessor extends AbstractProcessor { attributes.put(entry.getKey().getSimpleName().toString(), getValue(entry.getValue())); } + + // add default values + for (ExecutableElement meth : methodsIn(annotationMirror.getAnnotationType().asElement().getEnclosedElements())) { + String attribute = meth.getSimpleName().toString(); + AnnotationValue defaultValue = meth.getDefaultValue(); + if (defaultValue != null && !attributes.containsKey(attribute)) { + String value = getValue(defaultValue); + if (value != null && !value.isEmpty()) { + attributes.put(attribute, value); + } + } + } } } diff --git a/scm-annotations/pom.xml b/scm-annotations/pom.xml index ba8922e0b6..c5a892af95 100644 --- a/scm-annotations/pom.xml +++ b/scm-annotations/pom.xml @@ -8,8 +8,7 @@ scm 2.0.0-SNAPSHOT - - sonia.scm + scm-annotations 2.0.0-SNAPSHOT scm-annotations diff --git a/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java b/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java index d566859b4c..990d05181b 100644 --- a/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java +++ b/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java @@ -17,7 +17,7 @@ public class ConcurrentModificationException extends ExceptionWithContext { this(Collections.singletonList(new ContextEntry(type, id))); } - private ConcurrentModificationException(List context) { + public ConcurrentModificationException(List context) { super(context, createMessage(context)); } @@ -32,3 +32,4 @@ public class ConcurrentModificationException extends ExceptionWithContext { .collect(joining(" in ", "", " has been modified concurrently")); } } + diff --git a/scm-core/src/main/java/sonia/scm/SCMContext.java b/scm-core/src/main/java/sonia/scm/SCMContext.java index 42d311ed2a..f5367625bb 100644 --- a/scm-core/src/main/java/sonia/scm/SCMContext.java +++ b/scm-core/src/main/java/sonia/scm/SCMContext.java @@ -59,7 +59,7 @@ public final class SCMContext */ public static final User ANONYMOUS = new User(USER_ANONYMOUS, "SCM Anonymous", - "scm-anonymous@scm-manager.com"); + "scm-anonymous@scm-manager.org"); /** Singleton instance of {@link SCMContextProvider} */ private static volatile SCMContextProvider provider; diff --git a/scm-core/src/main/java/sonia/scm/collect/IterableQueue.java b/scm-core/src/main/java/sonia/scm/collect/IterableQueue.java index 9d3c85bb90..6b6af9fd0f 100644 --- a/scm-core/src/main/java/sonia/scm/collect/IterableQueue.java +++ b/scm-core/src/main/java/sonia/scm/collect/IterableQueue.java @@ -134,7 +134,7 @@ public final class IterableQueue implements Iterable else { logger.trace("create queue iterator"); - iterator = new QueueIterator(this); + iterator = new QueueIterator<>(this); } return iterator; diff --git a/scm-core/src/main/java/sonia/scm/collect/LimitedSortedSet.java b/scm-core/src/main/java/sonia/scm/collect/LimitedSortedSet.java index fe99ee7a1c..681b8ef07c 100644 --- a/scm-core/src/main/java/sonia/scm/collect/LimitedSortedSet.java +++ b/scm-core/src/main/java/sonia/scm/collect/LimitedSortedSet.java @@ -63,7 +63,7 @@ public class LimitedSortedSet extends ForwardingSortedSet */ public LimitedSortedSet(int maxSize) { - this.sortedSet = new TreeSet(); + this.sortedSet = new TreeSet<>(); this.maxSize = maxSize; } diff --git a/scm-core/src/main/java/sonia/scm/io/AbstractResourceProcessor.java b/scm-core/src/main/java/sonia/scm/io/AbstractResourceProcessor.java index 8b5c1d1dd7..6c00a319c9 100644 --- a/scm-core/src/main/java/sonia/scm/io/AbstractResourceProcessor.java +++ b/scm-core/src/main/java/sonia/scm/io/AbstractResourceProcessor.java @@ -183,5 +183,5 @@ public abstract class AbstractResourceProcessor implements ResourceProcessor //~--- fields --------------------------------------------------------------- /** Field description */ - private Map variableMap = new HashMap(); + private Map variableMap = new HashMap<>(); } diff --git a/scm-core/src/main/java/sonia/scm/io/INIConfiguration.java b/scm-core/src/main/java/sonia/scm/io/INIConfiguration.java index 9f3cce03f6..c9695b7ca6 100644 --- a/scm-core/src/main/java/sonia/scm/io/INIConfiguration.java +++ b/scm-core/src/main/java/sonia/scm/io/INIConfiguration.java @@ -52,7 +52,7 @@ public class INIConfiguration */ public INIConfiguration() { - this.sectionMap = new LinkedHashMap(); + this.sectionMap = new LinkedHashMap<>(); } //~--- methods -------------------------------------------------------------- diff --git a/scm-core/src/main/java/sonia/scm/io/INISection.java b/scm-core/src/main/java/sonia/scm/io/INISection.java index 03768a84ca..0ce6ea6bcf 100644 --- a/scm-core/src/main/java/sonia/scm/io/INISection.java +++ b/scm-core/src/main/java/sonia/scm/io/INISection.java @@ -55,7 +55,7 @@ public class INISection public INISection(String name) { this.name = name; - this.parameters = new LinkedHashMap(); + this.parameters = new LinkedHashMap<>(); } /** diff --git a/scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java b/scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java index db8d96ca15..c05e970e50 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java +++ b/scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java @@ -25,7 +25,7 @@ public class AvailablePlugin implements Plugin { return pending; } - public AvailablePlugin install() { + AvailablePlugin install() { Preconditions.checkState(!pending, "installation is already pending"); return new AvailablePlugin(pluginDescriptor, true); } diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java index 1f1e7cfefb..63d4638ab8 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java @@ -113,7 +113,6 @@ public abstract class AbstactImportHandler implements AdvancedImportHandler Repository repository = new Repository(); repository.setName(repositoryName); - repository.setPublicReadable(false); repository.setType(getTypeName()); return repository; diff --git a/scm-core/src/main/java/sonia/scm/repository/Repository.java b/scm-core/src/main/java/sonia/scm/repository/Repository.java index 463085a7ea..e35e12bbb0 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Repository.java +++ b/scm-core/src/main/java/sonia/scm/repository/Repository.java @@ -83,8 +83,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per private String name; @XmlElement(name = "permission") private Set permissions = new HashSet<>(); - @XmlElement(name = "public") - private boolean publicReadable = false; private String type; @@ -225,15 +223,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per return Util.isEmpty(healthCheckFailures); } - /** - * Returns true if the {@link Repository} is public readable. - * - * @return true if the {@link Repository} is public readable - */ - public boolean isPublicReadable() { - return publicReadable; - } - /** * Returns true if the {@link Repository} is valid. *
    @@ -292,10 +281,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per return this.permissions.remove(permission); } - public void setPublicReadable(boolean publicReadable) { - this.publicReadable = publicReadable; - } - public void setType(String type) { this.type = type; } @@ -332,7 +317,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per repository.setLastModified(lastModified); repository.setDescription(description); repository.setPermissions(permissions); - repository.setPublicReadable(publicReadable); // do not copy health check results } @@ -360,7 +344,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per && Objects.equal(name, other.name) && Objects.equal(contact, other.contact) && Objects.equal(description, other.description) - && Objects.equal(publicReadable, other.publicReadable) && Objects.equal(permissions, other.permissions) && Objects.equal(type, other.type) && Objects.equal(creationDate, other.creationDate) @@ -371,7 +354,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per @Override public int hashCode() { - return Objects.hashCode(id, namespace, name, contact, description, publicReadable, + return Objects.hashCode(id, namespace, name, contact, description, permissions, type, creationDate, lastModified, properties, healthCheckFailures); } @@ -384,7 +367,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per .add("name", name) .add("contact", contact) .add("description", description) - .add("publicReadable", publicReadable) .add("permissions", permissions) .add("type", type) .add("lastModified", lastModified) diff --git a/scm-core/src/main/java/sonia/scm/util/Base32.java b/scm-core/src/main/java/sonia/scm/util/Base32.java index cadbab3d8f..5ff3576078 100644 --- a/scm-core/src/main/java/sonia/scm/util/Base32.java +++ b/scm-core/src/main/java/sonia/scm/util/Base32.java @@ -46,7 +46,7 @@ public final class Base32 extends AbstractBase { /** base value */ - private static final BigInteger BASE = BigInteger.valueOf(32l); + private static final BigInteger BASE = BigInteger.valueOf(32L); /** char table */ private static final String CHARS = "0123456789bcdefghjkmnpqrstuvwxyz"; diff --git a/scm-core/src/main/java/sonia/scm/util/Base62.java b/scm-core/src/main/java/sonia/scm/util/Base62.java index 04fd1657d6..b9c9fd6a54 100644 --- a/scm-core/src/main/java/sonia/scm/util/Base62.java +++ b/scm-core/src/main/java/sonia/scm/util/Base62.java @@ -46,7 +46,7 @@ public final class Base62 extends AbstractBase { /** base value */ - private static final BigInteger BASE = BigInteger.valueOf(62l); + private static final BigInteger BASE = BigInteger.valueOf(62L); /** char table */ private static final String CHARS = diff --git a/scm-core/src/main/java/sonia/scm/util/LinkTextParser.java b/scm-core/src/main/java/sonia/scm/util/LinkTextParser.java index 2037beeeff..67ca800bcb 100644 --- a/scm-core/src/main/java/sonia/scm/util/LinkTextParser.java +++ b/scm-core/src/main/java/sonia/scm/util/LinkTextParser.java @@ -79,7 +79,7 @@ public final class LinkTextParser public static String parseText(String content) { Matcher m = REGEX_URL.matcher(content); - List tokens = new ArrayList(); + List tokens = new ArrayList<>(); int position = 0; String tokenContent = null; diff --git a/scm-core/src/main/java/sonia/scm/util/ServiceUtil.java b/scm-core/src/main/java/sonia/scm/util/ServiceUtil.java index 138345bb05..04bdfb6dea 100644 --- a/scm-core/src/main/java/sonia/scm/util/ServiceUtil.java +++ b/scm-core/src/main/java/sonia/scm/util/ServiceUtil.java @@ -119,7 +119,7 @@ public final class ServiceUtil */ public static List getServices(Class type) { - List result = new ArrayList(); + List result = new ArrayList<>(); try { diff --git a/scm-core/src/main/java/sonia/scm/util/Util.java b/scm-core/src/main/java/sonia/scm/util/Util.java index 5439cc77ec..cb64b75aff 100644 --- a/scm-core/src/main/java/sonia/scm/util/Util.java +++ b/scm-core/src/main/java/sonia/scm/util/Util.java @@ -38,23 +38,12 @@ package sonia.scm.util; import com.google.common.base.Strings; import com.google.common.collect.Multimap; -//~--- JDK imports ------------------------------------------------------------ - import java.math.BigInteger; - import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.*; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.TimeZone; +//~--- JDK imports ------------------------------------------------------------ /** * @@ -273,12 +262,12 @@ public final class Util Comparator comparator, CollectionAppender appender, int start, int limit) { - List result = new ArrayList(); - List valueList = new ArrayList(values); + List result = new ArrayList<>(); + List valueList = new ArrayList<>(values); if (comparator != null) { - Collections.sort(valueList, comparator); + valueList.sort(comparator); } int length = valueList.size(); @@ -506,12 +495,10 @@ public final class Util { StringBuilder buffer = new StringBuilder(); - for (int i = 0; i < byteValue.length; i++) - { - int x = byteValue[i] & 0xff; + for (final byte aByteValue : byteValue) { + int x = aByteValue & 0xff; - if (x < 16) - { + if (x < 16) { buffer.append('0'); } diff --git a/scm-core/src/main/java/sonia/scm/web/cgi/EnvList.java b/scm-core/src/main/java/sonia/scm/web/cgi/EnvList.java index 796d6125d8..541ae9ba23 100644 --- a/scm-core/src/main/java/sonia/scm/web/cgi/EnvList.java +++ b/scm-core/src/main/java/sonia/scm/web/cgi/EnvList.java @@ -66,7 +66,7 @@ public class EnvList */ public EnvList() { - envMap = new HashMap(); + envMap = new HashMap<>(); } /** @@ -77,7 +77,7 @@ public class EnvList */ public EnvList(EnvList l) { - envMap = new HashMap(l.envMap); + envMap = new HashMap<>(l.envMap); } //~--- methods -------------------------------------------------------------- diff --git a/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java index 87209ce409..3b64e6b5ac 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java @@ -127,7 +127,7 @@ public class AuthenticationFilter extends HttpFilter logger.trace("user is already authenticated"); processChain(request, response, chain, subject); } - else if (isAnonymousAccessEnabled()) + else if (isAnonymousAccessEnabled() && !HttpUtil.isWUIRequest(request)) { logger.trace("anonymous access granted"); subject.login(new AnonymousToken()); diff --git a/scm-core/src/main/java/sonia/scm/web/filter/BufferedHttpServletResponse.java b/scm-core/src/main/java/sonia/scm/web/filter/BufferedHttpServletResponse.java index b91f2b62c9..ff174c4fe8 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/BufferedHttpServletResponse.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/BufferedHttpServletResponse.java @@ -470,13 +470,13 @@ public class BufferedHttpServletResponse extends HttpServletResponseWrapper private ByteArrayPrintWriter pw = null; /** Field description */ - private Set cookies = new HashSet(); + private Set cookies = new HashSet<>(); /** Field description */ private int statusCode = HttpServletResponse.SC_OK; /** Field description */ - private Map headers = new LinkedHashMap(); + private Map headers = new LinkedHashMap<>(); /** Field description */ private String statusMessage; diff --git a/scm-core/src/main/java/sonia/scm/web/proxy/ProxyServlet.java b/scm-core/src/main/java/sonia/scm/web/proxy/ProxyServlet.java index 1d865dc4bc..06059c2926 100644 --- a/scm-core/src/main/java/sonia/scm/web/proxy/ProxyServlet.java +++ b/scm-core/src/main/java/sonia/scm/web/proxy/ProxyServlet.java @@ -40,28 +40,20 @@ import com.google.common.io.ByteStreams; import com.google.common.io.Closer; import com.google.inject.Inject; import com.google.inject.Singleton; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -//~--- JDK imports ------------------------------------------------------------ - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; import java.net.HttpURLConnection; - import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +//~--- JDK imports ------------------------------------------------------------ /** * @@ -175,10 +167,8 @@ public class ProxyServlet extends HttpServlet private void copyContent(HttpURLConnection con, HttpServletResponse response) throws IOException { - Closer closer = Closer.create(); - try - { + try (Closer closer = Closer.create()) { InputStream webToProxyBuf = closer.register(new BufferedInputStream(con.getInputStream())); OutputStream proxyToClientBuf = @@ -188,10 +178,6 @@ public class ProxyServlet extends HttpServlet logger.trace("copied {} bytes for proxy", bytes); } - finally - { - closer.close(); - } } /** diff --git a/scm-core/src/main/java/sonia/scm/xml/XmlMapStringAdapter.java b/scm-core/src/main/java/sonia/scm/xml/XmlMapStringAdapter.java index ab6cd83ccc..04d04fae60 100644 --- a/scm-core/src/main/java/sonia/scm/xml/XmlMapStringAdapter.java +++ b/scm-core/src/main/java/sonia/scm/xml/XmlMapStringAdapter.java @@ -102,7 +102,7 @@ public class XmlMapStringAdapter public Map unmarshal(XmlMapStringElement[] elements) throws Exception { - Map map = new HashMap(); + Map map = new HashMap<>(); if (elements != null) { diff --git a/scm-core/src/main/java/sonia/scm/xml/XmlSetStringAdapter.java b/scm-core/src/main/java/sonia/scm/xml/XmlSetStringAdapter.java index 708159bfac..8d088aa205 100644 --- a/scm-core/src/main/java/sonia/scm/xml/XmlSetStringAdapter.java +++ b/scm-core/src/main/java/sonia/scm/xml/XmlSetStringAdapter.java @@ -90,7 +90,7 @@ public class XmlSetStringAdapter extends XmlAdapter> @Override public Set unmarshal(String rawString) throws Exception { - Set tokens = new HashSet(); + Set tokens = new HashSet<>(); for (String token : rawString.split(",")) { diff --git a/scm-core/src/test/java/sonia/scm/collect/IterableQueueTest.java b/scm-core/src/test/java/sonia/scm/collect/IterableQueueTest.java index ade4f3247b..130924fcd4 100644 --- a/scm-core/src/test/java/sonia/scm/collect/IterableQueueTest.java +++ b/scm-core/src/test/java/sonia/scm/collect/IterableQueueTest.java @@ -63,7 +63,7 @@ public class IterableQueueTest @Test(expected = IllegalStateException.class) public void testDuplicatedEndReached() { - IterableQueue queue = new IterableQueue(); + IterableQueue queue = new IterableQueue<>(); queue.endReached(); queue.endReached(); @@ -76,7 +76,7 @@ public class IterableQueueTest @Test public void testIterator() { - IterableQueue queue = new IterableQueue(); + IterableQueue queue = new IterableQueue<>(); assertEquals(QueueIterator.class, queue.iterator().getClass()); queue.endReached(); @@ -120,7 +120,7 @@ public class IterableQueueTest @Test(expected = IllegalStateException.class) public void testPushEndReached() { - IterableQueue queue = new IterableQueue(); + IterableQueue queue = new IterableQueue<>(); queue.push("a"); queue.endReached(); @@ -134,7 +134,7 @@ public class IterableQueueTest @Test public void testSingleConsumer() { - final IterableQueue queue = new IterableQueue(); + final IterableQueue queue = new IterableQueue<>(); new Thread(new IntegerProducer(queue, false, 100)).start(); assertResult(Lists.newArrayList(queue), 100); @@ -176,12 +176,12 @@ public class IterableQueueTest ExecutorService executor = Executors.newFixedThreadPool(threads); List>> futures = Lists.newArrayList(); - final IterableQueue queue = new IterableQueue(); + final IterableQueue queue = new IterableQueue<>(); for (int i = 0; i < consumer; i++) { Future> future = - executor.submit(new CallableQueueCollector(queue)); + executor.submit(new CallableQueueCollector<>(queue)); futures.add(future); } diff --git a/scm-core/src/test/java/sonia/scm/plugin/ScmModuleTest.java b/scm-core/src/test/java/sonia/scm/plugin/ScmModuleTest.java index 45c490d15e..1629562132 100644 --- a/scm-core/src/test/java/sonia/scm/plugin/ScmModuleTest.java +++ b/scm-core/src/test/java/sonia/scm/plugin/ScmModuleTest.java @@ -71,8 +71,8 @@ public class ScmModuleTest assertThat( module.getExtensions(), containsInAnyOrder( - (Class) String.class, - (Class) Integer.class + String.class, + Integer.class ) ); assertThat( @@ -86,8 +86,8 @@ public class ScmModuleTest assertThat( module.getEvents(), containsInAnyOrder( - (Class) String.class, - (Class) Boolean.class + String.class, + Boolean.class ) ); assertThat( @@ -100,15 +100,15 @@ public class ScmModuleTest assertThat( module.getRestProviders(), containsInAnyOrder( - (Class) Integer.class, - (Class) Long.class + Integer.class, + Long.class ) ); assertThat( module.getRestResources(), containsInAnyOrder( - (Class) Float.class, - (Class) Double.class + Float.class, + Double.class ) ); //J+ diff --git a/scm-core/src/test/java/sonia/scm/template/TemplateEngineFactoryTest.java b/scm-core/src/test/java/sonia/scm/template/TemplateEngineFactoryTest.java index b676b4bbaf..775f9f0814 100644 --- a/scm-core/src/test/java/sonia/scm/template/TemplateEngineFactoryTest.java +++ b/scm-core/src/test/java/sonia/scm/template/TemplateEngineFactoryTest.java @@ -128,7 +128,7 @@ public class TemplateEngineFactoryTest assertTrue(engines.contains(engine1)); assertTrue(engines.contains(engine2)); - Set ce = new HashSet(); + Set ce = new HashSet<>(); ce.add(engine1); factory = new TemplateEngineFactory(ce, engine2); diff --git a/scm-core/src/test/java/sonia/scm/util/UrlBuilderTest.java b/scm-core/src/test/java/sonia/scm/util/UrlBuilderTest.java index 16d4b59ffc..eb0af7926c 100644 --- a/scm-core/src/test/java/sonia/scm/util/UrlBuilderTest.java +++ b/scm-core/src/test/java/sonia/scm/util/UrlBuilderTest.java @@ -56,7 +56,7 @@ public class UrlBuilderTest UrlBuilder builder = new UrlBuilder("http://www.short.de"); builder.appendParameter("i", 123).appendParameter("s", "abc"); - builder.appendParameter("b", true).appendParameter("l", 321l); + builder.appendParameter("b", true).appendParameter("l", 321L); assertEquals("http://www.short.de?i=123&s=abc&b=true&l=321", builder.toString()); builder.appendParameter("c", "a b"); assertEquals("http://www.short.de?i=123&s=abc&b=true&l=321&c=a%20b", builder.toString()); diff --git a/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupDatabase.java b/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupDatabase.java index 431d53594a..e4d436283c 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupDatabase.java +++ b/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupDatabase.java @@ -199,7 +199,7 @@ public class XmlGroupDatabase implements XmlDatabase /** Field description */ @XmlJavaTypeAdapter(XmlGroupMapAdapter.class) @XmlElement(name = "groups") - private Map groupMap = new LinkedHashMap(); + private Map groupMap = new LinkedHashMap<>(); /** Field description */ private Long lastModified; diff --git a/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupList.java b/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupList.java index da14968d30..c889944fc4 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupList.java +++ b/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupList.java @@ -72,7 +72,7 @@ public class XmlGroupList implements Iterable */ public XmlGroupList(Map groupMap) { - this.groups = new LinkedList(groupMap.values()); + this.groups = new LinkedList<>(groupMap.values()); } //~--- methods -------------------------------------------------------------- diff --git a/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupMapAdapter.java b/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupMapAdapter.java index b79c8836d2..430d0a84a8 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupMapAdapter.java +++ b/scm-dao-xml/src/main/java/sonia/scm/group/xml/XmlGroupMapAdapter.java @@ -81,7 +81,7 @@ public class XmlGroupMapAdapter @Override public Map unmarshal(XmlGroupList groups) throws Exception { - Map groupMap = new LinkedHashMap(); + Map groupMap = new LinkedHashMap<>(); for (Group group : groups) { diff --git a/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserDatabase.java b/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserDatabase.java index 579856fce7..2306fd588b 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserDatabase.java +++ b/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserDatabase.java @@ -202,5 +202,5 @@ public class XmlUserDatabase implements XmlDatabase /** Field description */ @XmlJavaTypeAdapter(XmlUserMapAdapter.class) @XmlElement(name = "users") - private Map userMap = new LinkedHashMap(); + private Map userMap = new LinkedHashMap<>(); } diff --git a/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserList.java b/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserList.java index e76f008b92..6877d8544e 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserList.java +++ b/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserList.java @@ -72,7 +72,7 @@ public class XmlUserList implements Iterable */ public XmlUserList(Map userMap) { - this.users = new LinkedList(userMap.values()); + this.users = new LinkedList<>(userMap.values()); } //~--- methods -------------------------------------------------------------- diff --git a/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserMapAdapter.java b/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserMapAdapter.java index f5573310d5..0c2386f37c 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserMapAdapter.java +++ b/scm-dao-xml/src/main/java/sonia/scm/user/xml/XmlUserMapAdapter.java @@ -81,7 +81,7 @@ public class XmlUserMapAdapter @Override public Map unmarshal(XmlUserList users) throws Exception { - Map userMap = new LinkedHashMap(); + Map userMap = new LinkedHashMap<>(); for (User user : users) { diff --git a/scm-dao-xml/src/main/java/sonia/scm/xml/XmlStreams.java b/scm-dao-xml/src/main/java/sonia/scm/xml/XmlStreams.java index 4b3d9b0f28..c8c5240f88 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/xml/XmlStreams.java +++ b/scm-dao-xml/src/main/java/sonia/scm/xml/XmlStreams.java @@ -52,7 +52,9 @@ public final class XmlStreams { } private static XMLStreamReader createReader(Reader reader) throws XMLStreamException { - return XMLInputFactory.newInstance().createXMLStreamReader(reader); + XMLInputFactory factory = XMLInputFactory.newInstance(); + factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE); + return factory.createXMLStreamReader(reader); } diff --git a/scm-it/src/test/java/sonia/scm/it/DiffITCase.java b/scm-it/src/test/java/sonia/scm/it/DiffITCase.java index acd2816422..a9d99deab2 100644 --- a/scm-it/src/test/java/sonia/scm/it/DiffITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/DiffITCase.java @@ -1,6 +1,7 @@ package sonia.scm.it; import org.apache.http.HttpStatus; +import org.assertj.core.api.AbstractCharSequenceAssert; import org.assertj.core.util.Lists; import org.junit.Before; import org.junit.Ignore; @@ -28,6 +29,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static sonia.scm.it.utils.RestUtil.ADMIN_PASSWORD; import static sonia.scm.it.utils.RestUtil.ADMIN_USERNAME; @@ -94,8 +96,7 @@ public class DiffITCase { String gitDiff = getDiff(RepositoryUtil.createAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt", "content of a"), gitRepositoryResponse); String expected = getGitDiffWithoutIndexLine(gitDiff); - assertThat(svnDiff) - .isEqualTo(expected); + assertDiffsAreEqual(svnDiff, expected); } @Test @@ -107,8 +108,7 @@ public class DiffITCase { String gitDiff = getDiff(RepositoryUtil.removeAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt"), gitRepositoryResponse); String expected = getGitDiffWithoutIndexLine(gitDiff); - assertThat(svnDiff) - .isEqualTo(expected); + assertDiffsAreEqual(svnDiff, expected); } @Test @@ -120,8 +120,7 @@ public class DiffITCase { String gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt", "the updated content of a"), gitRepositoryResponse); String expected = getGitDiffWithoutIndexLine(gitDiff); - assertThat(svnDiff) - .isEqualTo(expected); + assertDiffsAreEqual(svnDiff, expected); } @Test @@ -161,21 +160,17 @@ public class DiffITCase { String fileContent = getFileContent("/diff/largefile/original/SvnDiffGenerator_forTest"); String svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse); String gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse); - assertThat(svnDiff) - .isEqualTo(getGitDiffWithoutIndexLine(gitDiff)); + assertDiffsAreEqual(svnDiff, getGitDiffWithoutIndexLine(gitDiff)); fileContent = getFileContent("/diff/largefile/modified/v1/SvnDiffGenerator_forTest"); svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse); gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse); - assertThat(svnDiff) - .isEqualTo(getGitDiffWithoutIndexLine(gitDiff)); + assertDiffsAreEqual(svnDiff, getGitDiffWithoutIndexLine(gitDiff)); fileContent = getFileContent("/diff/largefile/modified/v2/SvnDiffGenerator_forTest"); svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse); gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse); - assertThat(svnDiff) - .isEqualTo(getGitDiffWithoutIndexLine(gitDiff)); - + assertDiffsAreEqual(svnDiff, getGitDiffWithoutIndexLine(gitDiff)); } /** @@ -196,8 +191,7 @@ public class DiffITCase { Changeset commit1 = RepositoryUtil.addFileAndCommit(gitRepositoryClient, fileName, ADMIN_USERNAME, ""); String svnDiff = getDiff(commit, svnRepositoryResponse); String gitDiff = getDiff(commit1, gitRepositoryResponse); - assertThat(svnDiff) - .isEqualTo(getGitDiffWithoutIndexLine(gitDiff)); + assertDiffsAreEqual(svnDiff, getGitDiffWithoutIndexLine(gitDiff)); } @@ -218,8 +212,7 @@ public class DiffITCase { String gitDiff = getDiff(RepositoryUtil.addFileAndCommit(gitRepositoryClient, newFileName, ADMIN_USERNAME, "renamed file"), gitRepositoryResponse); String expected = getGitDiffWithoutIndexLine(gitDiff); - assertThat(svnDiff) - .isEqualTo(expected); + assertDiffsAreEqual(svnDiff, expected); } public String getFileContent(String name) throws URISyntaxException, IOException { @@ -242,6 +235,12 @@ public class DiffITCase { return gitDiff.replaceAll(".*(index.*\n)", ""); } + private void assertDiffsAreEqual(String svnDiff, String gitDiff) { + assertThat(svnDiff) + .as("diffs are different\n\nsvn:\n==================================================\n\n%s\n\ngit:\n==================================================\n\n%s)", svnDiff, gitDiff) + .isEqualTo(gitDiff); + } + private String getDiff(Changeset svnChangeset, ScmRequests.RepositoryResponse svnRepositoryResponse) { return svnRepositoryResponse.requestChangesets() .requestDiffInGitFormat(svnChangeset.getId()) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java index 5ec69cccdd..2048d13dea 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java @@ -213,8 +213,14 @@ public class GitBrowseCommand extends AbstractGitCommand if (lfsPointer.isPresent()) { BlobStore lfsBlobStore = lfsBlobStoreFactory.getLfsBlobStore(repository); - Blob blob = lfsBlobStore.get(lfsPointer.get().getOid().getName()); - file.setLength(blob.getSize()); + String oid = lfsPointer.get().getOid().getName(); + Blob blob = lfsBlobStore.get(oid); + if (blob == null) { + logger.error("lfs blob for lob id {} not found in lfs store of repository {}", oid, repository.getNamespaceAndName()); + file.setLength(-1); + } else { + file.setLength(blob.getSize()); + } } else { file.setLength(loader.getSize()); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java index 35ff4d6ac2..193ab5bbc8 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java @@ -145,7 +145,12 @@ public class GitCatCommand extends AbstractGitCommand implements CatCommand { private Loader loadFromLfsStore(TreeWalk treeWalk, RevWalk revWalk, LfsPointer lfsPointer) throws IOException { BlobStore lfsBlobStore = lfsBlobStoreFactory.getLfsBlobStore(repository); - Blob blob = lfsBlobStore.get(lfsPointer.getOid().getName()); + String oid = lfsPointer.getOid().getName(); + Blob blob = lfsBlobStore.get(oid); + if (blob == null) { + logger.error("lfs blob for lob id {} not found in lfs store of repository {}", oid, repository.getNamespaceAndName()); + throw notFound(entity("LFS", oid).in(repository)); + } GitUtil.release(revWalk); GitUtil.release(treeWalk); return new BlobLoader(blob); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java index 1ac64c1b5e..dce4d0622f 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java @@ -33,13 +33,18 @@ package sonia.scm.repository.spi; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffFormatter; +import org.eclipse.jgit.util.QuotedString; import sonia.scm.repository.Repository; import sonia.scm.repository.api.DiffCommandBuilder; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; + +import static java.nio.charset.StandardCharsets.UTF_8; /** - * * @author Sebastian Sdorra */ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand { @@ -51,12 +56,12 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand { @Override public DiffCommandBuilder.OutputStreamConsumer getDiffResult(DiffCommandRequest request) throws IOException { @SuppressWarnings("squid:S2095") // repository will be closed with the RepositoryService - org.eclipse.jgit.lib.Repository repository = open(); + org.eclipse.jgit.lib.Repository repository = open(); Differ.Diff diff = Differ.diff(repository, request); return output -> { - try (DiffFormatter formatter = new DiffFormatter(output)) { + try (DiffFormatter formatter = new DiffFormatter(new DequoteOutputStream(output))) { formatter.setRepository(repository); for (DiffEntry e : diff.getEntries()) { @@ -70,4 +75,116 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand { }; } + static class DequoteOutputStream extends OutputStream { + + private static final String[] DEQUOTE_STARTS = { + "--- ", + "+++ ", + "diff --git " + }; + + private final OutputStream target; + + private boolean afterNL = true; + private boolean writeToBuffer = false; + private int numberOfPotentialBeginning = -1; + private int potentialBeginningCharCount = 0; + private boolean inPotentialQuotedLine = false; + + private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + DequoteOutputStream(OutputStream target) { + this.target = new BufferedOutputStream(target); + } + + @Override + public void write(int i) throws IOException { + if (i == (int) '\n') { + handleNewLine(i); + return; + } + + if (afterNL) { + afterNL = false; + if (foundPotentialBeginning(i)) { + return; + } + numberOfPotentialBeginning = -1; + inPotentialQuotedLine = false; + } + + if (inPotentialQuotedLine && i == '"') { + handleQuote(); + return; + } + + if (numberOfPotentialBeginning > -1 && checkForFurtherBeginning(i)) { + return; + } + + if (writeToBuffer) { + buffer.write(i); + } else { + target.write(i); + } + } + + private boolean checkForFurtherBeginning(int i) throws IOException { + if (i == DEQUOTE_STARTS[numberOfPotentialBeginning].charAt(potentialBeginningCharCount)) { + if (potentialBeginningCharCount + 1 < DEQUOTE_STARTS[numberOfPotentialBeginning].length()) { + ++potentialBeginningCharCount; + } else { + inPotentialQuotedLine = true; + } + target.write(i); + return true; + } else { + numberOfPotentialBeginning = -1; + } + return false; + } + + private boolean foundPotentialBeginning(int i) throws IOException { + for (int n = 0; n < DEQUOTE_STARTS.length; ++n) { + if (i == DEQUOTE_STARTS[n].charAt(0)) { + numberOfPotentialBeginning = n; + potentialBeginningCharCount = 1; + target.write(i); + return true; + } + } + return false; + } + + private void handleQuote() throws IOException { + if (writeToBuffer) { + buffer.write('"'); + dequoteBuffer(); + } else { + writeToBuffer = true; + buffer.reset(); + buffer.write('"'); + } + } + + private void handleNewLine(int i) throws IOException { + afterNL = true; + if (writeToBuffer) { + dequoteBuffer(); + } + target.write(i); + } + + private void dequoteBuffer() throws IOException { + byte[] bytes = buffer.toByteArray(); + String dequote = QuotedString.GIT_PATH.dequote(bytes, 0, bytes.length); + target.write(dequote.getBytes(UTF_8)); + writeToBuffer = false; + } + + @Override + public void flush() throws IOException { + target.flush(); + } + } } diff --git a/scm-plugins/scm-git-plugin/src/main/js/ProtocolInformation.tsx b/scm-plugins/scm-git-plugin/src/main/js/ProtocolInformation.tsx index f871ab8f2f..9f36abf302 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/ProtocolInformation.tsx +++ b/scm-plugins/scm-git-plugin/src/main/js/ProtocolInformation.tsx @@ -14,6 +14,12 @@ const Switcher = styled(ButtonAddons)` right: 0; `; +const SmallButton = styled(Button).attrs(props => ({ + className: "is-small" +}))` + height: inherit; +`; + type Props = { repository: Repository; }; @@ -62,9 +68,9 @@ export default class ProtocolInformation extends React.Component { } return ( - + ); }; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommand_DequoteOutputStreamTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommand_DequoteOutputStreamTest.java new file mode 100644 index 0000000000..6067356a09 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommand_DequoteOutputStreamTest.java @@ -0,0 +1,35 @@ +package sonia.scm.repository.spi; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class GitDiffCommand_DequoteOutputStreamTest { + + @Test + void shouldDequoteText() throws IOException { + String s = "diff --git \"a/file \\303\\272\\303\\274\\303\\276\\303\\253\\303\\251\\303\\245\\303\\253\\303\\245\\303\\251 a\" \"b/file \\303\\272\\303\\274\\303\\276\\303\\253\\303\\251\\303\\245\\303\\253\\303\\245\\303\\251 b\"\n" + + "new file mode 100644\n" + + "index 0000000..8cb0607\n" + + "--- /dev/null\n" + + "+++ \"b/\\303\\272\\303\\274\\303\\276\\303\\253\\303\\251\\303\\245\\303\\253\\303\\245\\303\\251 \\303\\245g\\303\\260f\\303\\237\"\n" + + "@@ -0,0 +1 @@\n" + + "+String s = \"quotes shall be kept\";"; + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + GitDiffCommand.DequoteOutputStream stream = new GitDiffCommand.DequoteOutputStream(buffer); + byte[] bytes = s.getBytes(); + stream.write(bytes, 0, bytes.length); + stream.flush(); + + Assertions.assertThat(buffer.toString()).isEqualTo("diff --git a/file úüþëéåëåé a b/file úüþëéåëåé b\n" + + "new file mode 100644\n" + + "index 0000000..8cb0607\n" + + "--- /dev/null\n" + + "+++ b/úüþëéåëåé ågðfß\n" + + "@@ -0,0 +1 @@\n" + + "+String s = \"quotes shall be kept\";"); + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageReader.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageReader.java index d16f056889..cb09c32870 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageReader.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageReader.java @@ -145,7 +145,7 @@ public class HgPackageReader */ private void filterPackage(HgPackages packages) { - List pkgList = new ArrayList(); + List pkgList = new ArrayList<>(); for (HgPackage pkg : packages) { @@ -228,7 +228,7 @@ public class HgPackageReader if (packages == null) { packages = new HgPackages(); - packages.setPackages(new ArrayList()); + packages.setPackages(new ArrayList<>()); } return packages; diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java index 48772ad5e5..b3e8e89a5f 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java @@ -77,7 +77,9 @@ public class HgBrowseCommand extends AbstractCommand implements BrowseCommand String revision = MoreObjects.firstNonNull(request.getRevision(), "tip"); Changeset c = LogCommand.on(getContext().open()).rev(revision).limit(1).single(); - cmd.rev(c.getNode()); + if (c != null) { + cmd.rev(c.getNode()); + } if (!Strings.isNullOrEmpty(request.getPath())) { @@ -100,6 +102,6 @@ public class HgBrowseCommand extends AbstractCommand implements BrowseCommand } FileObject file = cmd.execute(); - return new BrowserResult(c.getNode(), revision, file); + return new BrowserResult(c == null? "tip": c.getNode(), revision, file); } } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/logging/SVNKitLogger.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/logging/SVNKitLogger.java index e156defaf5..177bc05069 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/logging/SVNKitLogger.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/logging/SVNKitLogger.java @@ -211,15 +211,7 @@ public class SVNKitLogger extends SVNDebugLogAdapter */ private Logger getLogger(SVNLogType type) { - Logger logger = loggerMap.get(type); - - if (logger == null) - { - logger = LoggerFactory.getLogger(parseName(type.getName())); - loggerMap.put(type, logger); - } - - return logger; + return loggerMap.computeIfAbsent(type, t -> LoggerFactory.getLogger(parseName(t.getName()))); } //~--- fields --------------------------------------------------------------- diff --git a/scm-test/src/main/java/sonia/scm/cache/MapCacheManager.java b/scm-test/src/main/java/sonia/scm/cache/MapCacheManager.java index 0e4faddb4a..1f606fc908 100644 --- a/scm-test/src/main/java/sonia/scm/cache/MapCacheManager.java +++ b/scm-test/src/main/java/sonia/scm/cache/MapCacheManager.java @@ -81,15 +81,7 @@ public class MapCacheManager @Override public synchronized MapCache getCache(String name) { - MapCache cache = cacheMap.get(name); - - if (cache == null) - { - cache = new MapCache(); - cacheMap.put(name, cache); - } - - return cache; + return (MapCache) cacheMap.computeIfAbsent(name, k -> new MapCache()); } //~--- fields --------------------------------------------------------------- diff --git a/scm-ui/pom.xml b/scm-ui/pom.xml index ffc727f10d..e8277fae6e 100644 --- a/scm-ui/pom.xml +++ b/scm-ui/pom.xml @@ -17,6 +17,7 @@ scm-ui + build typescript ui-extensions/src,ui-components/src,ui-webapp/src **/*.test.js,src/tests/** @@ -68,7 +69,7 @@ run - + @@ -118,4 +119,21 @@ + + + + dev + + + + development + + + + + build:dev + + + + diff --git a/scm-ui/prettier-config/package.json b/scm-ui/prettier-config/package.json index 584081645c..097b07ee2b 100644 --- a/scm-ui/prettier-config/package.json +++ b/scm-ui/prettier-config/package.json @@ -7,7 +7,7 @@ "private": false, "main": "index.js", "dependencies": { - "prettier": "^1.18.2" + "prettier": "^1.19.1" }, "publishConfig": { "access": "public" diff --git a/scm-ui/tsconfig/package.json b/scm-ui/tsconfig/package.json index 4ee8613469..0ddbfc5975 100644 --- a/scm-ui/tsconfig/package.json +++ b/scm-ui/tsconfig/package.json @@ -7,7 +7,7 @@ "private": false, "main": "tsconfig.json", "dependencies": { - "typescript": "^3.6.4" + "typescript": "^3.7.2" }, "publishConfig": { "access": "public" diff --git a/scm-ui/ui-components/package.json b/scm-ui/ui-components/package.json index e7266aae6f..946416ca96 100644 --- a/scm-ui/ui-components/package.json +++ b/scm-ui/ui-components/package.json @@ -42,7 +42,7 @@ "raf": "^3.4.0", "react-test-renderer": "^16.10.2", "storybook-addon-i18next": "^1.2.1", - "typescript": "^3.6.4" + "typescript": "^3.7.2" }, "dependencies": { "@scm-manager/ui-extensions": "^2.0.0-SNAPSHOT", diff --git a/scm-ui/ui-components/src/Autocomplete.tsx b/scm-ui/ui-components/src/Autocomplete.tsx index 7618464b57..e7fff461e2 100644 --- a/scm-ui/ui-components/src/Autocomplete.tsx +++ b/scm-ui/ui-components/src/Autocomplete.tsx @@ -1,4 +1,5 @@ import React from "react"; +import classNames from "classnames"; import { Async, AsyncCreatable } from "react-select"; import { SelectValue } from "@scm-manager/ui-types"; import LabelWithHelpIcon from "./forms/LabelWithHelpIcon"; @@ -7,13 +8,14 @@ import { ActionMeta, ValueType } from "react-select/lib/types"; type Props = { loadSuggestions: (p: string) => Promise; valueSelected: (p: SelectValue) => void; - label: string; + label?: string; helpText?: string; value?: SelectValue; placeholder: string; loadingMessage: string; noOptionsMessage: string; creatable?: boolean; + className?: string; }; type State = {}; @@ -53,10 +55,11 @@ class Autocomplete extends React.Component { loadingMessage, noOptionsMessage, loadSuggestions, - creatable + creatable, + className } = this.props; return ( -
    +
    {creatable ? ( diff --git a/scm-ui/ui-components/src/UserGroupAutocomplete.tsx b/scm-ui/ui-components/src/UserGroupAutocomplete.tsx index 5c047b9353..f4e445eba9 100644 --- a/scm-ui/ui-components/src/UserGroupAutocomplete.tsx +++ b/scm-ui/ui-components/src/UserGroupAutocomplete.tsx @@ -1,6 +1,7 @@ import React from "react"; import { SelectValue, AutocompleteObject } from "@scm-manager/ui-types"; import Autocomplete from "./Autocomplete"; +import { apiClient } from "./apiclient"; export type AutocompleteProps = { autocompleteLink?: string; @@ -19,7 +20,8 @@ export default class UserGroupAutocomplete extends React.Component { loadSuggestions = (inputValue: string): Promise => { const url = this.props.autocompleteLink; const link = url + "?q="; - return fetch(link + inputValue) + return apiClient + .get(link + inputValue) .then(response => response.json()) .then((json: AutocompleteObject[]) => { return json.map(element => { diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index 0350b44219..ac759241d2 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -336,7 +336,7 @@ exports[`Storyshots DateFromNow Default 1`] = ` exports[`Storyshots Forms|Checkbox Default 1`] = `
    `; + +exports[`Storyshots Table|Table Default 1`] = ` + + + + + + + + + + + + + + + + + + + + +
    + First Name + + Last Name + + + E-Mail +
    +

    + Tricia +

    +
    + + McMillan + + + + tricia@hitchhiker.com + +
    +

    + Arthur +

    +
    + + Dent + + + + arthur@hitchhiker.com + +
    +`; + +exports[`Storyshots Table|Table Empty 1`] = ` +
    + + No data found. +
    +`; + +exports[`Storyshots Table|Table TextColumn 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Id + + + Name + + + Description + +
    + 21 + + Pommes + + Fried potato sticks +
    + 42 + + Quarter-Pounder + + Big burger +
    + -84 + + Icecream + + Cold dessert +
    +`; diff --git a/scm-ui/ui-components/src/apiclient.test.ts b/scm-ui/ui-components/src/apiclient.test.ts index 93f535728f..871d8089b7 100644 --- a/scm-ui/ui-components/src/apiclient.test.ts +++ b/scm-ui/ui-components/src/apiclient.test.ts @@ -1,4 +1,4 @@ -import { apiClient, createUrl } from "./apiclient"; +import { apiClient, createUrl, extractXsrfTokenFromCookie } from "./apiclient"; import fetchMock from "fetch-mock"; import { BackendError } from "./errors"; @@ -70,3 +70,22 @@ describe("error handling tests", () => { }); }); }); + +describe("extract xsrf token", () => { + it("should return undefined if no cookie exists", () => { + const token = extractXsrfTokenFromCookie(undefined); + expect(token).toBeUndefined(); + }); + + it("should return undefined without X-Bearer-Token exists", () => { + const token = extractXsrfTokenFromCookie("a=b; c=d; e=f"); + expect(token).toBeUndefined(); + }); + + it("should return xsrf token", () => { + const cookie = + "a=b; X-Bearer-Token=eyJhbGciOiJIUzI1NiJ9.eyJ4c3JmIjoiYjE0NDRmNWEtOWI5Mi00ZDA0LWFkMzMtMTAxYjY3MWQ1YTc0Iiwic3ViIjoic2NtYWRtaW4iLCJqdGkiOiI2RFJpQVphNWwxIiwiaWF0IjoxNTc0MDcyNDQ4LCJleHAiOjE1NzQwNzYwNDgsInNjbS1tYW5hZ2VyLnJlZnJlc2hFeHBpcmF0aW9uIjoxNTc0MTE1NjQ4OTU5LCJzY20tbWFuYWdlci5wYXJlbnRUb2tlbklkIjoiNkRSaUFaYTVsMSJ9.VUJtKeWUn3xtHCEbG51r7ceXZ8CF3cmN8J-eb9EDY_U; c=d"; + const token = extractXsrfTokenFromCookie(cookie); + expect(token).toBe("b1444f5a-9b92-4d04-ad33-101b671d5a74"); + }); +}); diff --git a/scm-ui/ui-components/src/apiclient.ts b/scm-ui/ui-components/src/apiclient.ts index 60789ff5dd..bd7f4eb6ab 100644 --- a/scm-ui/ui-components/src/apiclient.ts +++ b/scm-ui/ui-components/src/apiclient.ts @@ -9,14 +9,52 @@ const sessionId = ( .substr(2, 5) ).toUpperCase(); +const extractXsrfTokenFromJwt = (jwt: string) => { + const parts = jwt.split("."); + if (parts.length === 3) { + return JSON.parse(atob(parts[1])).xsrf; + } +}; + +// @VisibleForTesting +export const extractXsrfTokenFromCookie = (cookieString?: string) => { + if (cookieString) { + const cookies = cookieString.split(";"); + for (const c of cookies) { + const parts = c.trim().split("="); + if (parts[0] === "X-Bearer-Token") { + return extractXsrfTokenFromJwt(parts[1]); + } + } + } +}; + +const extractXsrfToken = () => { + return extractXsrfTokenFromCookie(document.cookie); +}; + const applyFetchOptions: (p: RequestInit) => RequestInit = o => { + if (!o.headers) { + o.headers = {}; + } + + // @ts-ignore We are sure that here we only get headers of type Record + const headers: Record = o.headers; + headers["Cache"] = "no-cache"; + // identify the request as ajax request + headers["X-Requested-With"] = "XMLHttpRequest"; + // identify the web interface + headers["X-SCM-Client"] = "WUI"; + // identify the window session + headers["X-SCM-Session-ID"] = sessionId + + const xsrf = extractXsrfToken(); + if (xsrf) { + headers["X-XSRF-Token"] = xsrf; + } + o.credentials = "same-origin"; - o.headers = { - Cache: "no-cache", - // identify the request as ajax request - "X-Requested-With": "XMLHttpRequest", - "X-SCM-Session-ID": sessionId - }; + o.headers = headers; return o; }; @@ -55,23 +93,32 @@ class ApiClient { return fetch(createUrl(url), applyFetchOptions({})).then(handleFailure); } - post(url: string, payload: any, contentType = "application/json") { - return this.httpRequestWithJSONBody("POST", url, contentType, payload); + post(url: string, payload?: any, contentType = "application/json", additionalHeaders: Record = {}) { + return this.httpRequestWithJSONBody("POST", url, contentType, additionalHeaders, payload); } - postBinary(url: string, fileAppender: (p: FormData) => void) { + postText(url: string, payload: string, additionalHeaders: Record = {}) { + return this.httpRequestWithTextBody("POST", url, additionalHeaders, payload); + } + + putText(url: string, payload: string, additionalHeaders: Record = {}) { + return this.httpRequestWithTextBody("PUT", url, additionalHeaders, payload); + } + + postBinary(url: string, fileAppender: (p: FormData) => void, additionalHeaders: Record = {}) { const formData = new FormData(); fileAppender(formData); const options: RequestInit = { method: "POST", - body: formData + body: formData, + headers: additionalHeaders }; return this.httpRequestWithBinaryBody(options, url); } - put(url: string, payload: any, contentType = "application/json") { - return this.httpRequestWithJSONBody("PUT", url, contentType, payload); + put(url: string, payload: any, contentType = "application/json", additionalHeaders: Record = {}) { + return this.httpRequestWithJSONBody("PUT", url, contentType, additionalHeaders, payload); } head(url: string) { @@ -90,21 +137,44 @@ class ApiClient { return fetch(createUrl(url), options).then(handleFailure); } - httpRequestWithJSONBody(method: string, url: string, contentType: string, payload: any): Promise { + httpRequestWithJSONBody( + method: string, + url: string, + contentType: string, + additionalHeaders: Record, + payload?: any + ): Promise { const options: RequestInit = { method: method, - body: JSON.stringify(payload) + headers: additionalHeaders }; + if (payload) { + options.body = JSON.stringify(payload); + } return this.httpRequestWithBinaryBody(options, url, contentType); } + httpRequestWithTextBody( + method: string, + url: string, + additionalHeaders: Record = {}, + payload: string + ) { + const options: RequestInit = { + method: method, + headers: additionalHeaders + }; + options.body = payload; + return this.httpRequestWithBinaryBody(options, url, "text/plain"); + } + httpRequestWithBinaryBody(options: RequestInit, url: string, contentType?: string) { options = applyFetchOptions(options); if (contentType) { if (!options.headers) { - options.headers = new Headers(); + options.headers = {}; } - // @ts-ignore + // @ts-ignore We are sure that here we only get headers of type Record options.headers["Content-Type"] = contentType; } diff --git a/scm-ui/ui-components/src/buttons/Button.tsx b/scm-ui/ui-components/src/buttons/Button.tsx index 8c1004ac52..3507ba7a06 100644 --- a/scm-ui/ui-components/src/buttons/Button.tsx +++ b/scm-ui/ui-components/src/buttons/Button.tsx @@ -18,6 +18,7 @@ export type ButtonProps = { type Props = ButtonProps & RouteComponentProps & { + title?: string; type?: "button" | "submit" | "reset"; color?: string; }; @@ -38,7 +39,19 @@ class Button extends React.Component { }; render() { - const { label, loading, disabled, type, color, className, icon, fullWidth, reducedMobile, children } = this.props; + const { + label, + title, + loading, + disabled, + type, + color, + className, + icon, + fullWidth, + reducedMobile, + children + } = this.props; const loadingClass = loading ? "is-loading" : ""; const fullWidthClass = fullWidth ? "is-fullwidth" : ""; const reducedMobileClass = reducedMobile ? "is-reduced-mobile" : ""; @@ -46,6 +59,7 @@ class Button extends React.Component { return (