mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-12 16:35:45 +01:00
Merge with 2.0.0-m3
This commit is contained in:
1
.mvn/wrapper/maven-wrapper.properties
vendored
1
.mvn/wrapper/maven-wrapper.properties
vendored
@@ -1 +1,2 @@
|
||||
# Keep this version number in sync with Jenkinsfile
|
||||
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip
|
||||
|
||||
13
Jenkinsfile
vendored
13
Jenkinsfile
vendored
@@ -11,16 +11,14 @@ node() { // No specific label
|
||||
|
||||
properties([
|
||||
// Keep only the last 10 build to preserve space
|
||||
buildDiscarder(logRotator(numToKeepStr: '10')),
|
||||
buildDiscarder(logRotator(numToKeepStr: '10'))
|
||||
])
|
||||
|
||||
timeout(activity: true, time: 20, unit: 'MINUTES') {
|
||||
|
||||
catchError {
|
||||
|
||||
Maven mvn = setupMavenBuild()
|
||||
// Maven build specified it must be 1.8.0-101 or newer
|
||||
def javaHome = tool 'JDK-1.8.0-101+'
|
||||
|
||||
withEnv(["JAVA_HOME=${javaHome}", "PATH=${env.JAVA_HOME}/bin:${env.PATH}"]) {
|
||||
|
||||
stage('Checkout') {
|
||||
checkout scm
|
||||
@@ -47,7 +45,6 @@ node() { // No specific label
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Archive Unit and integration test results, if any
|
||||
junit allowEmptyResults: true, testResults: '**/target/failsafe-reports/TEST-*.xml,**/target/surefire-reports/TEST-*.xml,**/target/jest-reports/TEST-*.xml'
|
||||
@@ -56,12 +53,14 @@ node() { // No specific label
|
||||
warnings consoleParsers: [[parserName: 'Maven']], canRunOnFailed: true
|
||||
|
||||
mailIfStatusChanged(commitAuthorEmail)
|
||||
}
|
||||
}
|
||||
|
||||
String mainBranch
|
||||
|
||||
Maven setupMavenBuild() {
|
||||
Maven mvn = new MavenWrapper(this)
|
||||
// Keep this version number in sync with .mvn/maven-wrapper.properties
|
||||
Maven mvn = new MavenInDocker(this, "3.5.2-jdk-8")
|
||||
|
||||
if (mainBranch.equals(env.BRANCH_NAME)) {
|
||||
// Release starts javadoc, which takes very long, so do only for certain branches
|
||||
|
||||
4
pom.xml
4
pom.xml
@@ -430,7 +430,9 @@
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>animal-sniffer-maven-plugin</artifactId>
|
||||
<version>1.17</version>
|
||||
<!-- Note: 1.17 seems to have problems with JDK8. When updating, use > 1.17, if available!
|
||||
https://github.com/mojohaus/animal-sniffer/issues/53 -->
|
||||
<version>1.16</version>
|
||||
<configuration>
|
||||
<signature>
|
||||
<groupId>org.codehaus.mojo.signature</groupId>
|
||||
|
||||
@@ -33,6 +33,13 @@
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- logging -->
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 1.17
|
||||
*/
|
||||
public class ArgumentIsInvalidException extends IllegalStateException
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*/
|
||||
public ArgumentIsInvalidException()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param s
|
||||
*/
|
||||
public ArgumentIsInvalidException(String s)
|
||||
{
|
||||
super(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param cause
|
||||
*/
|
||||
public ArgumentIsInvalidException(Throwable cause)
|
||||
{
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param message
|
||||
* @param cause
|
||||
*/
|
||||
public ArgumentIsInvalidException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,8 @@ package sonia.scm.api.v2.resources;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import org.mapstruct.Mapping;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public abstract class BaseMapper<T, D extends HalRepresentation> {
|
||||
public abstract class BaseMapper<T, D extends HalRepresentation> implements InstantAttributeMapper {
|
||||
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
public abstract D map(T modelObject);
|
||||
|
||||
protected Instant mapTime(Long epochMilli) {
|
||||
return epochMilli == null? null: Instant.ofEpochMilli(epochMilli);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public interface InstantAttributeMapper {
|
||||
default Instant mapTime(Long epochMilli) {
|
||||
return epochMilli == null? null: Instant.ofEpochMilli(epochMilli);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package sonia.scm.api.v2.resources;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -14,7 +13,7 @@ import java.util.Arrays;
|
||||
* builder for each method.
|
||||
*
|
||||
* <pre>
|
||||
* LinkBuilder builder = new LinkBuilder(uriInfo, MainResource.class, SubResource.class);
|
||||
* LinkBuilder builder = new LinkBuilder(pathInfo, MainResource.class, SubResource.class);
|
||||
* Link link = builder
|
||||
* .method("sub")
|
||||
* .parameters("param")
|
||||
@@ -25,16 +24,16 @@ import java.util.Arrays;
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Non-public will result in IllegalAccessError for plugins
|
||||
public class LinkBuilder {
|
||||
private final UriInfo uriInfo;
|
||||
private final ScmPathInfo pathInfo;
|
||||
private final Class[] classes;
|
||||
private final ImmutableList<Call> calls;
|
||||
|
||||
public LinkBuilder(UriInfo uriInfo, Class... classes) {
|
||||
this(uriInfo, classes, ImmutableList.of());
|
||||
public LinkBuilder(ScmPathInfo pathInfo, Class... classes) {
|
||||
this(pathInfo, classes, ImmutableList.of());
|
||||
}
|
||||
|
||||
private LinkBuilder(UriInfo uriInfo, Class[] classes, ImmutableList<Call> calls) {
|
||||
this.uriInfo = uriInfo;
|
||||
private LinkBuilder(ScmPathInfo pathInfo, Class[] classes, ImmutableList<Call> calls) {
|
||||
this.pathInfo = pathInfo;
|
||||
this.classes = classes;
|
||||
this.calls = calls;
|
||||
}
|
||||
@@ -51,7 +50,7 @@ public class LinkBuilder {
|
||||
throw new IllegalStateException("not enough methods for all classes");
|
||||
}
|
||||
|
||||
URI baseUri = uriInfo.getBaseUri();
|
||||
URI baseUri = pathInfo.getApiRestUri();
|
||||
URI relativeUri = createRelativeUri();
|
||||
return baseUri.resolve(relativeUri);
|
||||
}
|
||||
@@ -61,7 +60,7 @@ public class LinkBuilder {
|
||||
}
|
||||
|
||||
private LinkBuilder add(String method, String[] parameters) {
|
||||
return new LinkBuilder(uriInfo, classes, appendNewCall(method, parameters));
|
||||
return new LinkBuilder(pathInfo, classes, appendNewCall(method, parameters));
|
||||
}
|
||||
|
||||
private ImmutableList<Call> appendNewCall(String method, String[] parameters) {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
public interface ScmPathInfo {
|
||||
|
||||
String REST_API_PATH = "/api/rest";
|
||||
|
||||
URI getApiRestUri();
|
||||
|
||||
default URI getRootUri() {
|
||||
return getApiRestUri().resolve("../..");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
public class ScmPathInfoStore {
|
||||
|
||||
private ScmPathInfo pathInfo;
|
||||
|
||||
public ScmPathInfo get() {
|
||||
return pathInfo;
|
||||
}
|
||||
|
||||
public void set(ScmPathInfo info) {
|
||||
if (this.pathInfo != null) {
|
||||
throw new IllegalStateException("UriInfo already set");
|
||||
}
|
||||
this.pathInfo = info;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
public class UriInfoStore {
|
||||
|
||||
private UriInfo uriInfo;
|
||||
|
||||
public UriInfo get() {
|
||||
return uriInfo;
|
||||
}
|
||||
|
||||
public void set(UriInfo uriInfo) {
|
||||
if (this.uriInfo != null) {
|
||||
throw new IllegalStateException("UriInfo already set");
|
||||
}
|
||||
this.uriInfo = uriInfo;
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,8 @@
|
||||
|
||||
package sonia.scm.filter;
|
||||
|
||||
import static sonia.scm.api.v2.resources.ScmPathInfo.REST_API_PATH;
|
||||
|
||||
/**
|
||||
* Useful constants for filter implementations.
|
||||
*
|
||||
@@ -44,26 +46,26 @@ public final class Filters
|
||||
public static final String PATTERN_ALL = "/*";
|
||||
|
||||
/** Field description */
|
||||
public static final String PATTERN_CONFIG = "/api/rest/config*";
|
||||
public static final String PATTERN_CONFIG = REST_API_PATH + "/config*";
|
||||
|
||||
/** Field description */
|
||||
public static final String PATTERN_DEBUG = "/debug.html";
|
||||
|
||||
/** Field description */
|
||||
public static final String PATTERN_GROUPS = "/api/rest/groups*";
|
||||
public static final String PATTERN_GROUPS = REST_API_PATH + "/groups*";
|
||||
|
||||
/** Field description */
|
||||
public static final String PATTERN_PLUGINS = "/api/rest/plugins*";
|
||||
public static final String PATTERN_PLUGINS = REST_API_PATH + "/plugins*";
|
||||
|
||||
/** Field description */
|
||||
public static final String PATTERN_RESOURCE_REGEX =
|
||||
"^/(?:resources|api|plugins|index)[\\./].*(?:html|\\.css|\\.js|\\.xml|\\.json|\\.txt)";
|
||||
|
||||
/** Field description */
|
||||
public static final String PATTERN_RESTAPI = "/api/rest/*";
|
||||
public static final String PATTERN_RESTAPI = REST_API_PATH + "/*";
|
||||
|
||||
/** Field description */
|
||||
public static final String PATTERN_USERS = "/api/rest/users*";
|
||||
public static final String PATTERN_USERS = REST_API_PATH + "/users*";
|
||||
|
||||
/** authentication priority */
|
||||
public static final int PRIORITY_AUTHENTICATION = 5000;
|
||||
|
||||
@@ -33,9 +33,8 @@ package sonia.scm.plugin;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* The WebResourceLoader is able to load web resources. The resources are loaded
|
||||
|
||||
@@ -41,7 +41,6 @@ import sonia.scm.util.ValidationUtil;
|
||||
|
||||
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.util.ArrayList;
|
||||
import java.util.Date;
|
||||
@@ -84,12 +83,6 @@ public class Changeset extends BasicPropertiesAware implements ModelObject {
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* List of files changed by this changeset
|
||||
*/
|
||||
@XmlElement(name = "modifications")
|
||||
private Modifications modifications;
|
||||
|
||||
/**
|
||||
* parent changeset ids
|
||||
*/
|
||||
@@ -137,7 +130,6 @@ public class Changeset extends BasicPropertiesAware implements ModelObject {
|
||||
&& Objects.equal(parents, other.parents)
|
||||
&& Objects.equal(tags, other.tags)
|
||||
&& Objects.equal(branches, other.branches)
|
||||
&& Objects.equal(modifications, other.modifications)
|
||||
&& Objects.equal(properties, other.properties);
|
||||
//J+
|
||||
}
|
||||
@@ -152,7 +144,7 @@ public class Changeset extends BasicPropertiesAware implements ModelObject {
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hashCode(id, date, author, description, parents, tags,
|
||||
branches, modifications, properties);
|
||||
branches, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,11 +176,6 @@ public class Changeset extends BasicPropertiesAware implements ModelObject {
|
||||
out.append("branches: ").append(Util.toString(branches)).append("\n");
|
||||
out.append("tags: ").append(Util.toString(tags)).append("\n");
|
||||
|
||||
if (modifications != null)
|
||||
{
|
||||
out.append("modifications: \n").append(modifications);
|
||||
}
|
||||
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
@@ -285,21 +272,6 @@ public class Changeset extends BasicPropertiesAware implements ModelObject {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the file modifications, which was done with this changeset.
|
||||
*
|
||||
*
|
||||
* @return file modifications
|
||||
*/
|
||||
public Modifications getModifications()
|
||||
{
|
||||
if (modifications == null)
|
||||
{
|
||||
modifications = new Modifications();
|
||||
}
|
||||
|
||||
return modifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ids of the parent changesets.
|
||||
@@ -402,17 +374,6 @@ public class Changeset extends BasicPropertiesAware implements ModelObject {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file modification of the changeset.
|
||||
*
|
||||
*
|
||||
* @param modifications file modifications
|
||||
*/
|
||||
public void setModifications(Modifications modifications)
|
||||
{
|
||||
this.modifications = modifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parents of the changeset.
|
||||
*
|
||||
|
||||
@@ -36,15 +36,13 @@ package sonia.scm.repository;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import org.apache.commons.lang.StringEscapeUtils;
|
||||
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.List;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
@@ -200,4 +198,10 @@ public final class EscapeUtil
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
public static void escape(Modifications modifications) {
|
||||
modifications.setAdded(escapeList(modifications.getAdded()));
|
||||
modifications.setModified(escapeList(modifications.getModified()));
|
||||
modifications.setRemoved(escapeList(modifications.getRemoved()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,8 @@ public class Modifications implements Serializable
|
||||
* Constructs ...
|
||||
*
|
||||
*/
|
||||
public Modifications() {}
|
||||
public Modifications() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
@@ -218,6 +219,10 @@ public class Modifications implements Serializable
|
||||
return removed;
|
||||
}
|
||||
|
||||
public String getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
//~--- set methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
@@ -253,8 +258,14 @@ public class Modifications implements Serializable
|
||||
this.removed = removed;
|
||||
}
|
||||
|
||||
public void setRevision(String revision) {
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
private String revision;
|
||||
|
||||
/** list of added files */
|
||||
@XmlElement(name = "added")
|
||||
@XmlElementWrapper(name = "added")
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package sonia.scm.repository;
|
||||
|
||||
import sonia.scm.plugin.ExtensionPoint;
|
||||
|
||||
|
||||
/**
|
||||
* A pre processor for {@link Modifications} objects. A pre processor is able to
|
||||
* modify the object before it is delivered to the user interface.
|
||||
*
|
||||
* @author Mohamed Karray
|
||||
* @since 2.0
|
||||
*/
|
||||
@ExtensionPoint
|
||||
public interface ModificationsPreProcessor extends PreProcessor<Modifications> {
|
||||
|
||||
/**
|
||||
* Process the given modifications.
|
||||
*
|
||||
* @param modifications modifications to process
|
||||
*/
|
||||
@Override
|
||||
void process(Modifications modifications);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package sonia.scm.repository;
|
||||
|
||||
import sonia.scm.plugin.ExtensionPoint;
|
||||
|
||||
|
||||
/**
|
||||
* This factory create a {@link ModificationsPreProcessor}
|
||||
*
|
||||
* @author Mohamed Karray
|
||||
* @since 2.0
|
||||
*/
|
||||
@ExtensionPoint
|
||||
public interface ModificationsPreProcessorFactory extends PreProcessorFactory<Modifications> {
|
||||
|
||||
/**
|
||||
* Create a new {@link ModificationsPreProcessor} for the given repository.
|
||||
*
|
||||
*
|
||||
* @param repository repository
|
||||
*
|
||||
* @return {@link ModificationsPreProcessor} for the given repository
|
||||
*/
|
||||
@Override
|
||||
ModificationsPreProcessor createPreProcessor(Repository repository);
|
||||
|
||||
}
|
||||
@@ -36,17 +36,15 @@ package sonia.scm.repository;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
@@ -73,6 +71,8 @@ public class PreProcessorUtil
|
||||
* @param fileObjectPreProcessorFactorySet
|
||||
* @param blameLinePreProcessorSet
|
||||
* @param blameLinePreProcessorFactorySet
|
||||
* @param modificationsPreProcessorFactorySet
|
||||
* @param modificationsPreProcessorSet
|
||||
*/
|
||||
@Inject
|
||||
public PreProcessorUtil(Set<ChangesetPreProcessor> changesetPreProcessorSet,
|
||||
@@ -80,7 +80,9 @@ public class PreProcessorUtil
|
||||
Set<FileObjectPreProcessor> fileObjectPreProcessorSet,
|
||||
Set<FileObjectPreProcessorFactory> fileObjectPreProcessorFactorySet,
|
||||
Set<BlameLinePreProcessor> blameLinePreProcessorSet,
|
||||
Set<BlameLinePreProcessorFactory> blameLinePreProcessorFactorySet)
|
||||
Set<BlameLinePreProcessorFactory> blameLinePreProcessorFactorySet,
|
||||
Set<ModificationsPreProcessorFactory> modificationsPreProcessorFactorySet,
|
||||
Set<ModificationsPreProcessor> modificationsPreProcessorSet)
|
||||
{
|
||||
this.changesetPreProcessorSet = changesetPreProcessorSet;
|
||||
this.changesetPreProcessorFactorySet = changesetPreProcessorFactorySet;
|
||||
@@ -88,6 +90,8 @@ public class PreProcessorUtil
|
||||
this.fileObjectPreProcessorFactorySet = fileObjectPreProcessorFactorySet;
|
||||
this.blameLinePreProcessorSet = blameLinePreProcessorSet;
|
||||
this.blameLinePreProcessorFactorySet = blameLinePreProcessorFactorySet;
|
||||
this.modificationsPreProcessorFactorySet = modificationsPreProcessorFactorySet;
|
||||
this.modificationsPreProcessorSet = modificationsPreProcessorSet;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
@@ -108,13 +112,7 @@ public class PreProcessorUtil
|
||||
}
|
||||
|
||||
EscapeUtil.escape(blameLine);
|
||||
|
||||
PreProcessorHandler<BlameLine> handler =
|
||||
new PreProcessorHandler<BlameLine>(blameLinePreProcessorFactorySet,
|
||||
blameLinePreProcessorSet, repository);
|
||||
|
||||
handler.callPreProcessors(blameLine);
|
||||
handler.callPreProcessorFactories(blameLine);
|
||||
handlePreProcess(repository,blameLine,blameLinePreProcessorFactorySet, blameLinePreProcessorSet);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,13 +150,7 @@ public class PreProcessorUtil
|
||||
{
|
||||
EscapeUtil.escape(blameResult);
|
||||
}
|
||||
|
||||
PreProcessorHandler<BlameLine> handler =
|
||||
new PreProcessorHandler<BlameLine>(blameLinePreProcessorFactorySet,
|
||||
blameLinePreProcessorSet, repository);
|
||||
|
||||
handler.callPreProcessors(blameResult.getBlameLines());
|
||||
handler.callPreProcessorFactories(blameResult.getBlameLines());
|
||||
handlePreProcessForIterable(repository, blameResult.getBlameLines(),blameLinePreProcessorFactorySet, blameLinePreProcessorSet);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,26 +175,20 @@ public class PreProcessorUtil
|
||||
*
|
||||
* @since 1.35
|
||||
*/
|
||||
public void prepareForReturn(Repository repository, Changeset changeset,
|
||||
boolean escape)
|
||||
{
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("prepare changeset {} of repository {} for return",
|
||||
changeset.getId(), repository.getName());
|
||||
}
|
||||
|
||||
if (escape)
|
||||
{
|
||||
public void prepareForReturn(Repository repository, Changeset changeset, boolean escape){
|
||||
logger.trace("prepare changeset {} of repository {} for return", changeset.getId(), repository.getName());
|
||||
if (escape) {
|
||||
EscapeUtil.escape(changeset);
|
||||
}
|
||||
handlePreProcess(repository, changeset, changesetPreProcessorFactorySet, changesetPreProcessorSet);
|
||||
}
|
||||
|
||||
PreProcessorHandler<Changeset> handler =
|
||||
new PreProcessorHandler<Changeset>(changesetPreProcessorFactorySet,
|
||||
changesetPreProcessorSet, repository);
|
||||
|
||||
handler.callPreProcessors(changeset);
|
||||
handler.callPreProcessorFactories(changeset);
|
||||
public void prepareForReturn(Repository repository, Modifications modifications, boolean escape) {
|
||||
logger.trace("prepare modifications {} of repository {} for return", modifications, repository.getName());
|
||||
if (escape) {
|
||||
EscapeUtil.escape(modifications);
|
||||
}
|
||||
handlePreProcess(repository, modifications, modificationsPreProcessorFactorySet, modificationsPreProcessorSet);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,13 +226,7 @@ public class PreProcessorUtil
|
||||
{
|
||||
EscapeUtil.escape(result);
|
||||
}
|
||||
|
||||
PreProcessorHandler<FileObject> handler =
|
||||
new PreProcessorHandler<FileObject>(fileObjectPreProcessorFactorySet,
|
||||
fileObjectPreProcessorSet, repository);
|
||||
|
||||
handler.callPreProcessors(result);
|
||||
handler.callPreProcessorFactories(result);
|
||||
handlePreProcessForIterable(repository, result,fileObjectPreProcessorFactorySet, fileObjectPreProcessorSet);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,13 +252,7 @@ public class PreProcessorUtil
|
||||
{
|
||||
EscapeUtil.escape(result);
|
||||
}
|
||||
|
||||
PreProcessorHandler<Changeset> handler =
|
||||
new PreProcessorHandler<Changeset>(changesetPreProcessorFactorySet,
|
||||
changesetPreProcessorSet, repository);
|
||||
|
||||
handler.callPreProcessors(result);
|
||||
handler.callPreProcessorFactories(result);
|
||||
handlePreProcessForIterable(repository,result,changesetPreProcessorFactorySet, changesetPreProcessorSet);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,6 +268,23 @@ public class PreProcessorUtil
|
||||
prepareForReturn(repository, result, true);
|
||||
}
|
||||
|
||||
|
||||
private <T, F extends PreProcessorFactory<T>, P extends PreProcessor<T>> void handlePreProcess(Repository repository, T processedObject,
|
||||
Collection<F> factories,
|
||||
Collection<P> preProcessors) {
|
||||
PreProcessorHandler<T> handler = new PreProcessorHandler<T>(factories, preProcessors, repository);
|
||||
handler.callPreProcessors(processedObject);
|
||||
handler.callPreProcessorFactories(processedObject);
|
||||
}
|
||||
|
||||
private <T, I extends Iterable<T>, F extends PreProcessorFactory<T>, P extends PreProcessor<T>> void handlePreProcessForIterable(Repository repository, I processedObjects,
|
||||
Collection<F> factories,
|
||||
Collection<P> preProcessors) {
|
||||
PreProcessorHandler<T> handler = new PreProcessorHandler<T>(factories, preProcessors, repository);
|
||||
handler.callPreProcessors(processedObjects);
|
||||
handler.callPreProcessorFactories(processedObjects);
|
||||
}
|
||||
|
||||
//~--- inner classes --------------------------------------------------------
|
||||
|
||||
/**
|
||||
@@ -454,6 +445,10 @@ public class PreProcessorUtil
|
||||
/** Field description */
|
||||
private final Collection<ChangesetPreProcessor> changesetPreProcessorSet;
|
||||
|
||||
private final Collection<ModificationsPreProcessorFactory> modificationsPreProcessorFactorySet;
|
||||
|
||||
private final Collection<ModificationsPreProcessor> modificationsPreProcessorSet;
|
||||
|
||||
/** Field description */
|
||||
private final Collection<FileObjectPreProcessorFactory> fileObjectPreProcessorFactorySet;
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Lists;
|
||||
import sonia.scm.BasicPropertiesAware;
|
||||
import sonia.scm.ModelObject;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.util.Util;
|
||||
import sonia.scm.util.ValidationUtil;
|
||||
|
||||
@@ -349,17 +348,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
||||
// do not copy health check results
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the url of the repository.
|
||||
*
|
||||
* @param baseUrl base url of the server including the context path
|
||||
* @return url of the repository
|
||||
* @since 1.17
|
||||
*/
|
||||
public String createUrl(String baseUrl) {
|
||||
return HttpUtil.concatenate(baseUrl, type, namespace, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the {@link Repository} is the same as the obj argument.
|
||||
*
|
||||
|
||||
@@ -38,7 +38,6 @@ package sonia.scm.repository;
|
||||
import sonia.scm.AlreadyExistsException;
|
||||
import sonia.scm.TypeManager;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -99,29 +98,6 @@ public interface RepositoryManager
|
||||
*/
|
||||
public Collection<RepositoryType> getConfiguredTypes();
|
||||
|
||||
/**
|
||||
* Returns the {@link Repository} associated to the request uri.
|
||||
*
|
||||
*
|
||||
* @param request the current http request
|
||||
*
|
||||
* @return associated to the request uri
|
||||
* @since 1.9
|
||||
*/
|
||||
public Repository getFromRequest(HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* Returns the {@link Repository} associated to the request uri.
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param uri request uri without context path
|
||||
*
|
||||
* @return associated to the request uri
|
||||
* @since 1.9
|
||||
*/
|
||||
public Repository getFromUri(String uri);
|
||||
|
||||
/**
|
||||
* Returns a {@link RepositoryHandler} by the given type (hg, git, svn ...).
|
||||
*
|
||||
|
||||
@@ -39,7 +39,6 @@ import sonia.scm.AlreadyExistsException;
|
||||
import sonia.scm.ManagerDecorator;
|
||||
import sonia.scm.Type;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -120,34 +119,6 @@ public class RepositoryManagerDecorator
|
||||
return decorated;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
*
|
||||
* @param request
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Repository getFromRequest(HttpServletRequest request)
|
||||
{
|
||||
return decorated.getFromRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
*
|
||||
* @param uri
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Repository getFromUri(String uri)
|
||||
{
|
||||
return decorated.getFromUri(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
|
||||
@@ -44,8 +44,8 @@ import sonia.scm.NotFoundException;
|
||||
public class RepositoryNotFoundException extends NotFoundException
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
private static final long serialVersionUID = -6583078808900520166L;
|
||||
private static final String TYPE_REPOSITORY = "repository";
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
@@ -55,10 +55,14 @@ public class RepositoryNotFoundException extends NotFoundException
|
||||
*
|
||||
*/
|
||||
public RepositoryNotFoundException(Repository repository) {
|
||||
super("repository", repository.getName() + "/" + repository.getNamespace());
|
||||
super(TYPE_REPOSITORY, repository.getName() + "/" + repository.getNamespace());
|
||||
}
|
||||
|
||||
public RepositoryNotFoundException(String repositoryId) {
|
||||
super("repository", repositoryId);
|
||||
super(TYPE_REPOSITORY, repositoryId);
|
||||
}
|
||||
|
||||
public RepositoryNotFoundException(NamespaceAndName namespaceAndName) {
|
||||
super(TYPE_REPOSITORY, namespaceAndName.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,35 +26,21 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.throwingproviders.CheckedProvider;
|
||||
|
||||
import sonia.scm.security.ScmSecurityException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 1.10
|
||||
*/
|
||||
public interface RepositoryProvider extends CheckedProvider<Repository>
|
||||
{
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @throws ScmSecurityException
|
||||
*/
|
||||
public interface RepositoryProvider extends CheckedProvider<Repository> {
|
||||
@Override
|
||||
public Repository get() throws ScmSecurityException;
|
||||
Repository get();
|
||||
}
|
||||
|
||||
@@ -61,5 +61,11 @@ public enum Command
|
||||
/**
|
||||
* @since 1.43
|
||||
*/
|
||||
BUNDLE, UNBUNDLE;
|
||||
BUNDLE, UNBUNDLE,
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
*/
|
||||
MODIFICATIONS
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sonia.scm.cache.Cache;
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.PreProcessorUtil;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryCacheKey;
|
||||
import sonia.scm.repository.RevisionNotFoundException;
|
||||
import sonia.scm.repository.spi.ModificationsCommand;
|
||||
import sonia.scm.repository.spi.ModificationsCommandRequest;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Get the modifications applied to files in a revision.
|
||||
* <p>
|
||||
* Modifications are for example: Add, Update and Delete
|
||||
*
|
||||
* @author Mohamed Karray
|
||||
* @since 2.0
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Accessors(fluent = true)
|
||||
public final class ModificationsCommandBuilder {
|
||||
static final String CACHE_NAME = "sonia.cache.cmd.modifications";
|
||||
|
||||
private final ModificationsCommand modificationsCommand;
|
||||
|
||||
private final ModificationsCommandRequest request = new ModificationsCommandRequest();
|
||||
|
||||
private final Repository repository;
|
||||
|
||||
private final Cache<ModificationsCommandBuilder.CacheKey, Modifications> cache;
|
||||
|
||||
private final PreProcessorUtil preProcessorUtil;
|
||||
|
||||
@Setter
|
||||
private boolean disableEscaping = false;
|
||||
|
||||
@Setter
|
||||
private boolean disableCache = false;
|
||||
|
||||
@Setter
|
||||
private boolean disablePreProcessors = false;
|
||||
|
||||
public ModificationsCommandBuilder revision(String revision){
|
||||
request.setRevision(revision);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset each parameter to its default value.
|
||||
*
|
||||
*
|
||||
* @return {@code this}
|
||||
*/
|
||||
public ModificationsCommandBuilder reset() {
|
||||
request.reset();
|
||||
this.disableCache = false;
|
||||
this.disablePreProcessors = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Modifications getModifications() throws IOException, RevisionNotFoundException {
|
||||
Modifications modifications;
|
||||
if (disableCache) {
|
||||
log.info("Get modifications for {} with disabled cache", request);
|
||||
modifications = modificationsCommand.getModifications(request);
|
||||
} else {
|
||||
ModificationsCommandBuilder.CacheKey key = new ModificationsCommandBuilder.CacheKey(repository.getId(), request);
|
||||
if (cache.contains(key)) {
|
||||
modifications = cache.get(key);
|
||||
log.debug("Get modifications for {} from the cache", request);
|
||||
} else {
|
||||
log.info("Get modifications for {} with enabled cache", request);
|
||||
modifications = modificationsCommand.getModifications(request);
|
||||
if (modifications != null) {
|
||||
cache.put(key, modifications);
|
||||
log.debug("Modifications for {} added to the cache with key {}", request, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!disablePreProcessors && (modifications != null)) {
|
||||
preProcessorUtil.prepareForReturn(repository, modifications, !disableEscaping);
|
||||
}
|
||||
return modifications;
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
class CacheKey implements RepositoryCacheKey {
|
||||
private final String repositoryId;
|
||||
private final ModificationsCommandRequest request;
|
||||
|
||||
@Override
|
||||
public String getRepositoryId() {
|
||||
return repositoryId;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.cache.CacheManager;
|
||||
@@ -42,6 +43,8 @@ import sonia.scm.repository.spi.RepositoryServiceProvider;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* From the {@link RepositoryService} it is possible to access all commands for
|
||||
@@ -78,30 +81,32 @@ import java.io.IOException;
|
||||
* @apiviz.uses sonia.scm.repository.api.UnbundleCommandBuilder
|
||||
* @since 1.17
|
||||
*/
|
||||
@Slf4j
|
||||
public final class RepositoryService implements Closeable {
|
||||
private CacheManager cacheManager;
|
||||
private PreProcessorUtil preProcessorUtil;
|
||||
private RepositoryServiceProvider provider;
|
||||
private Repository repository;
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(RepositoryService.class);
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RepositoryService.class);
|
||||
|
||||
private final CacheManager cacheManager;
|
||||
private final PreProcessorUtil preProcessorUtil;
|
||||
private final RepositoryServiceProvider provider;
|
||||
private final Repository repository;
|
||||
private final Set<ScmProtocolProvider> protocolProviders;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link RepositoryService}. This constructor should only
|
||||
* be called from the {@link RepositoryServiceFactory}.
|
||||
*
|
||||
* @param cacheManager cache manager
|
||||
* @param provider implementation for {@link RepositoryServiceProvider}
|
||||
* @param repository the repository
|
||||
* @param preProcessorUtil
|
||||
*/
|
||||
RepositoryService(CacheManager cacheManager,
|
||||
RepositoryServiceProvider provider, Repository repository,
|
||||
PreProcessorUtil preProcessorUtil) {
|
||||
PreProcessorUtil preProcessorUtil, Set<ScmProtocolProvider> protocolProviders) {
|
||||
this.cacheManager = cacheManager;
|
||||
this.provider = provider;
|
||||
this.repository = repository;
|
||||
this.preProcessorUtil = preProcessorUtil;
|
||||
this.protocolProviders = protocolProviders;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +130,7 @@ public final class RepositoryService implements Closeable {
|
||||
try {
|
||||
provider.close();
|
||||
} catch (IOException ex) {
|
||||
logger.error("Could not close repository service provider", ex);
|
||||
log.error("Could not close repository service provider", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +143,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public BlameCommandBuilder getBlameCommand() {
|
||||
logger.debug("create blame command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new BlameCommandBuilder(cacheManager, provider.getBlameCommand(),
|
||||
repository, preProcessorUtil);
|
||||
@@ -153,7 +158,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public BranchesCommandBuilder getBranchesCommand() {
|
||||
logger.debug("create branches command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new BranchesCommandBuilder(cacheManager,
|
||||
provider.getBranchesCommand(), repository);
|
||||
@@ -168,7 +173,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public BrowseCommandBuilder getBrowseCommand() {
|
||||
logger.debug("create browse command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new BrowseCommandBuilder(cacheManager, provider.getBrowseCommand(),
|
||||
repository, preProcessorUtil);
|
||||
@@ -184,7 +189,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public BundleCommandBuilder getBundleCommand() {
|
||||
logger.debug("create bundle command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new BundleCommandBuilder(provider.getBundleCommand(), repository);
|
||||
}
|
||||
@@ -198,7 +203,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public CatCommandBuilder getCatCommand() {
|
||||
logger.debug("create cat command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new CatCommandBuilder(provider.getCatCommand());
|
||||
}
|
||||
@@ -213,7 +218,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public DiffCommandBuilder getDiffCommand() {
|
||||
logger.debug("create diff command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new DiffCommandBuilder(provider.getDiffCommand());
|
||||
}
|
||||
@@ -229,7 +234,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public IncomingCommandBuilder getIncomingCommand() {
|
||||
logger.debug("create incoming command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new IncomingCommandBuilder(cacheManager,
|
||||
provider.getIncomingCommand(), repository, preProcessorUtil);
|
||||
@@ -244,12 +249,24 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public LogCommandBuilder getLogCommand() {
|
||||
logger.debug("create log command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new LogCommandBuilder(cacheManager, provider.getLogCommand(),
|
||||
repository, preProcessorUtil);
|
||||
}
|
||||
|
||||
/**
|
||||
* The modification command shows file modifications in a revision.
|
||||
*
|
||||
* @return instance of {@link ModificationsCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
* by the implementation of the repository service provider.
|
||||
*/
|
||||
public ModificationsCommandBuilder getModificationsCommand() {
|
||||
logger.debug("create modifications command for repository {}", repository.getNamespaceAndName());
|
||||
return new ModificationsCommandBuilder(provider.getModificationsCommand(),repository, cacheManager.getCache(ModificationsCommandBuilder.CACHE_NAME), preProcessorUtil);
|
||||
}
|
||||
|
||||
/**
|
||||
* The outgoing command show {@link Changeset}s not found in a remote repository.
|
||||
*
|
||||
@@ -260,7 +277,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public OutgoingCommandBuilder getOutgoingCommand() {
|
||||
logger.debug("create outgoing command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new OutgoingCommandBuilder(cacheManager,
|
||||
provider.getOutgoingCommand(), repository, preProcessorUtil);
|
||||
@@ -276,7 +293,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public PullCommandBuilder getPullCommand() {
|
||||
logger.debug("create pull command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new PullCommandBuilder(provider.getPullCommand(), repository);
|
||||
}
|
||||
@@ -291,7 +308,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public PushCommandBuilder getPushCommand() {
|
||||
logger.debug("create push command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new PushCommandBuilder(provider.getPushCommand());
|
||||
}
|
||||
@@ -314,7 +331,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public TagsCommandBuilder getTagsCommand() {
|
||||
logger.debug("create tags command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new TagsCommandBuilder(cacheManager, provider.getTagsCommand(),
|
||||
repository);
|
||||
@@ -330,7 +347,7 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
public UnbundleCommandBuilder getUnbundleCommand() {
|
||||
logger.debug("create unbundle command for repository {}",
|
||||
repository.getName());
|
||||
repository.getNamespaceAndName());
|
||||
|
||||
return new UnbundleCommandBuilder(provider.getUnbundleCommand(),
|
||||
repository);
|
||||
@@ -357,5 +374,20 @@ public final class RepositoryService implements Closeable {
|
||||
return provider.getSupportedFeatures().contains(feature);
|
||||
}
|
||||
|
||||
public <T extends ScmProtocol> Stream<T> getSupportedProtocols() {
|
||||
return protocolProviders.stream()
|
||||
.filter(protocolProvider -> protocolProvider.getType().equals(getRepository().getType()))
|
||||
.map(this::<T>createProviderInstanceForRepository);
|
||||
}
|
||||
|
||||
private <T extends ScmProtocol> T createProviderInstanceForRepository(ScmProtocolProvider<T> protocolProvider) {
|
||||
return protocolProvider.get(repository);
|
||||
}
|
||||
|
||||
public <T extends ScmProtocol> T getProtocol(Class<T> clazz) {
|
||||
return this.<T>getSupportedProtocols()
|
||||
.filter(scmProtocol -> clazz.isAssignableFrom(scmProtocol.getClass()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException(String.format("no implementation for %s and repository type %s", clazz.getName(),getRepository().getType())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,13 +137,15 @@ public final class RepositoryServiceFactory
|
||||
@Inject
|
||||
public RepositoryServiceFactory(ScmConfiguration configuration,
|
||||
CacheManager cacheManager, RepositoryManager repositoryManager,
|
||||
Set<RepositoryServiceResolver> resolvers, PreProcessorUtil preProcessorUtil)
|
||||
Set<RepositoryServiceResolver> resolvers, PreProcessorUtil preProcessorUtil,
|
||||
Set<ScmProtocolProvider> protocolProviders)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
this.cacheManager = cacheManager;
|
||||
this.repositoryManager = repositoryManager;
|
||||
this.resolvers = resolvers;
|
||||
this.preProcessorUtil = preProcessorUtil;
|
||||
this.protocolProviders = protocolProviders;
|
||||
|
||||
ScmEventBus.getInstance().register(new CacheClearHook(cacheManager));
|
||||
}
|
||||
@@ -208,9 +210,7 @@ public final class RepositoryServiceFactory
|
||||
|
||||
if (repository == null)
|
||||
{
|
||||
String msg = "could not find a repository with namespace/name " + namespaceAndName;
|
||||
|
||||
throw new RepositoryNotFoundException(msg);
|
||||
throw new RepositoryNotFoundException(namespaceAndName);
|
||||
}
|
||||
|
||||
return create(repository);
|
||||
@@ -254,7 +254,7 @@ public final class RepositoryServiceFactory
|
||||
}
|
||||
|
||||
service = new RepositoryService(cacheManager, provider, repository,
|
||||
preProcessorUtil);
|
||||
preProcessorUtil, protocolProviders);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -369,4 +369,6 @@ public final class RepositoryServiceFactory
|
||||
|
||||
/** service resolvers */
|
||||
private final Set<RepositoryServiceResolver> resolvers;
|
||||
|
||||
private Set<ScmProtocolProvider> protocolProviders;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
/**
|
||||
* An ScmProtocol represents a concrete protocol provided by the SCM-Manager instance
|
||||
* to interact with a repository depending on its type. There may be multiple protocols
|
||||
* available for a repository type (eg. http and ssh).
|
||||
*/
|
||||
public interface ScmProtocol {
|
||||
|
||||
/**
|
||||
* The type of the concrete protocol, eg. "http" or "ssh".
|
||||
*/
|
||||
String getType();
|
||||
|
||||
/**
|
||||
* The URL to access the repository providing this protocol.
|
||||
*/
|
||||
String getUrl();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import sonia.scm.plugin.ExtensionPoint;
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
@ExtensionPoint(multi = true)
|
||||
public interface ScmProtocolProvider<T extends ScmProtocol> {
|
||||
|
||||
String getType();
|
||||
|
||||
T get(Repository repository);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.api.ScmProtocol;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
public abstract class HttpScmProtocol implements ScmProtocol {
|
||||
|
||||
private final Repository repository;
|
||||
private final String basePath;
|
||||
|
||||
public HttpScmProtocol(Repository repository, String basePath) {
|
||||
this.repository = repository;
|
||||
this.basePath = basePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return URI.create(basePath + "/").resolve(String.format("repo/%s/%s", repository.getNamespace(), repository.getName())).toASCIIString();
|
||||
}
|
||||
|
||||
public final void serve(HttpServletRequest request, HttpServletResponse response, ServletConfig config) throws ServletException, IOException {
|
||||
serve(request, response, repository, config);
|
||||
}
|
||||
|
||||
protected abstract void serve(HttpServletRequest request, HttpServletResponse response, Repository repository, ServletConfig config) throws ServletException, IOException;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sonia.scm.api.v2.resources.ScmPathInfoStore;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.api.ScmProtocolProvider;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
|
||||
@Slf4j
|
||||
public abstract class InitializingHttpScmProtocolWrapper implements ScmProtocolProvider<HttpScmProtocol> {
|
||||
|
||||
private final Provider<? extends ScmProviderHttpServlet> delegateProvider;
|
||||
private final Provider<ScmPathInfoStore> pathInfoStore;
|
||||
private final ScmConfiguration scmConfiguration;
|
||||
|
||||
private volatile boolean isInitialized = false;
|
||||
|
||||
|
||||
protected InitializingHttpScmProtocolWrapper(Provider<? extends ScmProviderHttpServlet> delegateProvider, Provider<ScmPathInfoStore> pathInfoStore, ScmConfiguration scmConfiguration) {
|
||||
this.delegateProvider = delegateProvider;
|
||||
this.pathInfoStore = pathInfoStore;
|
||||
this.scmConfiguration = scmConfiguration;
|
||||
}
|
||||
|
||||
protected void initializeServlet(ServletConfig config, ScmProviderHttpServlet httpServlet) throws ServletException {
|
||||
httpServlet.init(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpScmProtocol get(Repository repository) {
|
||||
if (!repository.getType().equals(getType())) {
|
||||
throw new IllegalArgumentException(String.format("cannot handle repository with type %s with protocol for type %s", repository.getType(), getType()));
|
||||
}
|
||||
return new ProtocolWrapper(repository, computeBasePath());
|
||||
}
|
||||
|
||||
private String computeBasePath() {
|
||||
return getPathFromScmPathInfoIfAvailable().orElse(getPathFromConfiguration());
|
||||
}
|
||||
|
||||
private Optional<String> getPathFromScmPathInfoIfAvailable() {
|
||||
try {
|
||||
ScmPathInfoStore scmPathInfoStore = pathInfoStore.get();
|
||||
if (scmPathInfoStore != null && scmPathInfoStore.get() != null) {
|
||||
return of(scmPathInfoStore.get().getRootUri().toASCIIString());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("could not get ScmPathInfoStore from context", e);
|
||||
}
|
||||
return empty();
|
||||
}
|
||||
|
||||
private String getPathFromConfiguration() {
|
||||
log.debug("using base path from configuration: {}", scmConfiguration.getBaseUrl());
|
||||
return scmConfiguration.getBaseUrl();
|
||||
}
|
||||
|
||||
private class ProtocolWrapper extends HttpScmProtocol {
|
||||
|
||||
public ProtocolWrapper(Repository repository, String basePath) {
|
||||
super(repository, basePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serve(HttpServletRequest request, HttpServletResponse response, Repository repository, ServletConfig config) throws ServletException, IOException {
|
||||
if (!isInitialized) {
|
||||
synchronized (InitializingHttpScmProtocolWrapper.this) {
|
||||
if (!isInitialized) {
|
||||
ScmProviderHttpServlet httpServlet = delegateProvider.get();
|
||||
initializeServlet(config, httpServlet);
|
||||
isInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegateProvider.get().service(request, response, repository);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* Copyright (c) 2010, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -29,32 +29,25 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
package sonia.scm.repository;
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.RevisionNotFoundException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Command to get the modifications applied to files in a revision.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class RepositoryTest
|
||||
{
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Modifications are for example: Add, Update, Delete
|
||||
*
|
||||
* @author Mohamed Karray
|
||||
* @since 2.0
|
||||
*/
|
||||
@Test
|
||||
public void testCreateUrl()
|
||||
{
|
||||
Repository repository = new Repository("123", "hg", "test", "repo");
|
||||
public interface ModificationsCommand {
|
||||
|
||||
Modifications getModifications(String revision) throws IOException, RevisionNotFoundException;
|
||||
|
||||
Modifications getModifications(ModificationsCommandRequest request) throws IOException, RevisionNotFoundException;
|
||||
|
||||
assertEquals("http://localhost:8080/scm/hg/test/repo",
|
||||
repository.createUrl("http://localhost:8080/scm"));
|
||||
assertEquals("http://localhost:8080/scm/hg/test/repo",
|
||||
repository.createUrl("http://localhost:8080/scm/"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ModificationsCommandRequest implements Resetable {
|
||||
private String revision;
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
revision = null;
|
||||
}
|
||||
}
|
||||
@@ -33,20 +33,17 @@
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import sonia.scm.repository.Feature;
|
||||
import sonia.scm.repository.api.Command;
|
||||
import sonia.scm.repository.api.CommandNotSupportedException;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
@@ -173,6 +170,16 @@ public abstract class RepositoryServiceProvider implements Closeable
|
||||
throw new CommandNotSupportedException(Command.LOG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the corresponding {@link ModificationsCommand} implemented from the Plugins
|
||||
*
|
||||
* @return the corresponding {@link ModificationsCommand} implemented from the Plugins
|
||||
* @throws CommandNotSupportedException if there is no Implementation
|
||||
*/
|
||||
public ModificationsCommand getModificationsCommand() {
|
||||
throw new CommandNotSupportedException(Command.MODIFICATIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
public interface ScmProviderHttpServlet {
|
||||
|
||||
void service(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException;
|
||||
|
||||
void init(ServletConfig config) throws ServletException;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
public class ScmProviderHttpServletDecorator implements ScmProviderHttpServlet {
|
||||
|
||||
private final ScmProviderHttpServlet object;
|
||||
|
||||
public ScmProviderHttpServletDecorator(ScmProviderHttpServlet object) {
|
||||
this.object = object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void service(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException {
|
||||
object.service(request, response, repository);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(ServletConfig config) throws ServletException {
|
||||
object.init(config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import sonia.scm.DecoratorFactory;
|
||||
import sonia.scm.plugin.ExtensionPoint;
|
||||
|
||||
@ExtensionPoint
|
||||
public interface ScmProviderHttpServletDecoratorFactory extends DecoratorFactory<ScmProviderHttpServlet> {
|
||||
/**
|
||||
* Has to return <code>true</code> if this factory provides a decorator for the given scm type (eg. "git", "hg" or
|
||||
* "svn").
|
||||
* @param type The current scm type this factory can provide a decorator for.
|
||||
* @return <code>true</code> when the provided decorator should be used for the given scm type.
|
||||
*/
|
||||
boolean handlesScmType(String type);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import sonia.scm.util.Decorators;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
public abstract class ScmProviderHttpServletProvider implements Provider<ScmProviderHttpServlet> {
|
||||
|
||||
@Inject(optional = true)
|
||||
private Set<ScmProviderHttpServletDecoratorFactory> decoratorFactories;
|
||||
|
||||
private final String type;
|
||||
|
||||
protected ScmProviderHttpServletProvider(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScmProviderHttpServlet get() {
|
||||
return Decorators.decorate(getRootServlet(), getDecoratorsForType());
|
||||
}
|
||||
|
||||
private List<ScmProviderHttpServletDecoratorFactory> getDecoratorsForType() {
|
||||
return decoratorFactories.stream().filter(d -> d.handlesScmType(type)).collect(toList());
|
||||
}
|
||||
|
||||
protected abstract ScmProviderHttpServlet getRootServlet();
|
||||
}
|
||||
@@ -37,7 +37,6 @@ package sonia.scm.util;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.DecoratorFactory;
|
||||
|
||||
/**
|
||||
@@ -21,6 +21,7 @@ public class VndMediaType {
|
||||
public static final String PERMISSION = PREFIX + "permission" + SUFFIX;
|
||||
public static final String CHANGESET = PREFIX + "changeset" + SUFFIX;
|
||||
public static final String CHANGESET_COLLECTION = PREFIX + "changesetCollection" + SUFFIX;
|
||||
public static final String MODIFICATIONS = PREFIX + "modifications" + SUFFIX;;
|
||||
public static final String TAG = PREFIX + "tag" + SUFFIX;
|
||||
public static final String TAG_COLLECTION = PREFIX + "tagCollection" + SUFFIX;
|
||||
public static final String BRANCH = PREFIX + "branch" + SUFFIX;
|
||||
|
||||
@@ -33,39 +33,32 @@
|
||||
|
||||
package sonia.scm.web.filter;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authz.AuthorizationException;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.ArgumentIsInvalidException;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryPermissions;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServletDecorator;
|
||||
import sonia.scm.security.Role;
|
||||
import sonia.scm.security.ScmSecurityException;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Abstract http filter to check repository permissions.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public abstract class PermissionFilter extends HttpFilter
|
||||
public abstract class PermissionFilter extends ScmProviderHttpServletDecorator
|
||||
{
|
||||
|
||||
/** the logger for PermissionFilter */
|
||||
@@ -81,23 +74,14 @@ public abstract class PermissionFilter extends HttpFilter
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
public PermissionFilter(ScmConfiguration configuration)
|
||||
protected PermissionFilter(ScmConfiguration configuration, ScmProviderHttpServlet delegate)
|
||||
{
|
||||
super(delegate);
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the requested repository.
|
||||
*
|
||||
*
|
||||
* @param request current http request
|
||||
*
|
||||
* @return requested repository
|
||||
*/
|
||||
protected abstract Repository getRepository(HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* Returns true if the current request is a write request.
|
||||
*
|
||||
@@ -117,23 +101,18 @@ public abstract class PermissionFilter extends HttpFilter
|
||||
*
|
||||
* @param request http request
|
||||
* @param response http response
|
||||
* @param chain filter chain
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws ServletException
|
||||
*/
|
||||
@Override
|
||||
protected void doFilter(HttpServletRequest request,
|
||||
HttpServletResponse response, FilterChain chain)
|
||||
public void service(HttpServletRequest request,
|
||||
HttpServletResponse response, Repository repository)
|
||||
throws IOException, ServletException
|
||||
{
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
|
||||
try
|
||||
{
|
||||
Repository repository = getRepository(request);
|
||||
|
||||
if (repository != null)
|
||||
{
|
||||
boolean writeRequest = isWriteRequest(request);
|
||||
|
||||
@@ -143,7 +122,7 @@ public abstract class PermissionFilter extends HttpFilter
|
||||
getActionAsString(writeRequest), repository.getName(),
|
||||
getUserName(subject));
|
||||
|
||||
chain.doFilter(request, response);
|
||||
super.service(request, response, repository);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -154,29 +133,6 @@ public abstract class PermissionFilter extends HttpFilter
|
||||
sendAccessDenied(request, response, subject);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug("repository not found");
|
||||
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
catch (ArgumentIsInvalidException ex)
|
||||
{
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace(
|
||||
"wrong request at ".concat(request.getRequestURI()).concat(
|
||||
" send redirect"), ex);
|
||||
}
|
||||
else if (logger.isWarnEnabled())
|
||||
{
|
||||
logger.warn("wrong request at {} send redirect",
|
||||
request.getRequestURI());
|
||||
}
|
||||
|
||||
response.sendRedirect(getRepositoryRootHelpUrl(request));
|
||||
}
|
||||
catch (ScmSecurityException | AuthorizationException ex)
|
||||
{
|
||||
logger.warn("user " + subject.getPrincipal() + " has not enough permissions", ex);
|
||||
@@ -217,29 +173,6 @@ public abstract class PermissionFilter extends HttpFilter
|
||||
HttpUtil.sendUnauthorized(response, configuration.getRealmDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the type of the repositroy from url.
|
||||
*
|
||||
*
|
||||
* @param request http request
|
||||
*
|
||||
* @return type of repository
|
||||
*/
|
||||
private String extractType(HttpServletRequest request)
|
||||
{
|
||||
Iterator<String> it = Splitter.on(
|
||||
HttpUtil.SEPARATOR_PATH).omitEmptyStrings().split(
|
||||
request.getRequestURI()).iterator();
|
||||
String type = it.next();
|
||||
|
||||
if (Util.isNotEmpty(request.getContextPath()))
|
||||
{
|
||||
type = it.next();
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send access denied to the servlet response.
|
||||
*
|
||||
@@ -280,25 +213,6 @@ public abstract class PermissionFilter extends HttpFilter
|
||||
: "read";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the repository root help url.
|
||||
*
|
||||
*
|
||||
* @param request current http request
|
||||
*
|
||||
* @return repository root help url
|
||||
*/
|
||||
private String getRepositoryRootHelpUrl(HttpServletRequest request)
|
||||
{
|
||||
String type = extractType(request);
|
||||
String helpUrl = HttpUtil.getCompleteUrl(request,
|
||||
"/api/rest/help/repository-root/");
|
||||
|
||||
helpUrl = helpUrl.concat(type).concat(".html");
|
||||
|
||||
return helpUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username from the given subject or anonymous.
|
||||
*
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package sonia.scm.web.filter;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.inject.ProvisionException;
|
||||
|
||||
import org.apache.shiro.authz.AuthorizationException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryProvider;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 1.9
|
||||
*/
|
||||
public abstract class ProviderPermissionFilter extends PermissionFilter
|
||||
{
|
||||
|
||||
/**
|
||||
* the logger for ProviderPermissionFilter
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(ProviderPermissionFilter.class);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param configuration
|
||||
* @param repositoryProvider
|
||||
* @since 1.21
|
||||
*/
|
||||
public ProviderPermissionFilter(ScmConfiguration configuration,
|
||||
RepositoryProvider repositoryProvider)
|
||||
{
|
||||
super(configuration);
|
||||
this.repositoryProvider = repositoryProvider;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param request
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
protected Repository getRepository(HttpServletRequest request)
|
||||
{
|
||||
Repository repository = null;
|
||||
|
||||
try
|
||||
{
|
||||
repository = repositoryProvider.get();
|
||||
}
|
||||
catch (ProvisionException ex)
|
||||
{
|
||||
Throwables.propagateIfPossible(ex.getCause(),
|
||||
IllegalStateException.class, AuthorizationException.class);
|
||||
logger.error("could not get repository from request", ex);
|
||||
}
|
||||
|
||||
return repository;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private final RepositoryProvider repositoryProvider;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import org.junit.Test;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.spi.HttpScmProtocol;
|
||||
import sonia.scm.repository.spi.RepositoryServiceProvider;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.assertj.core.util.IterableUtil.sizeOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class RepositoryServiceTest {
|
||||
|
||||
private final RepositoryServiceProvider provider = mock(RepositoryServiceProvider.class);
|
||||
private final Repository repository = new Repository("", "git", "space", "repo");
|
||||
|
||||
@Test
|
||||
public void shouldReturnMatchingProtocolsFromProvider() {
|
||||
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()));
|
||||
Stream<ScmProtocol> supportedProtocols = repositoryService.getSupportedProtocols();
|
||||
|
||||
assertThat(sizeOf(supportedProtocols.collect(Collectors.toList()))).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindKnownProtocol() {
|
||||
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()));
|
||||
|
||||
HttpScmProtocol protocol = repositoryService.getProtocol(HttpScmProtocol.class);
|
||||
|
||||
assertThat(protocol).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFailForUnknownProtocol() {
|
||||
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
repositoryService.getProtocol(UnknownScmProtocol.class);
|
||||
});
|
||||
}
|
||||
|
||||
private static class DummyHttpProtocol extends HttpScmProtocol {
|
||||
public DummyHttpProtocol(Repository repository) {
|
||||
super(repository, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serve(HttpServletRequest request, HttpServletResponse response, Repository repository, ServletConfig config) {
|
||||
}
|
||||
}
|
||||
|
||||
private static class DummyScmProtocolProvider implements ScmProtocolProvider {
|
||||
@Override
|
||||
public String getType() {
|
||||
return "git";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScmProtocol get(Repository repository) {
|
||||
return new DummyHttpProtocol(repository);
|
||||
}
|
||||
}
|
||||
|
||||
private interface UnknownScmProtocol extends ScmProtocol {}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class HttpScmProtocolTest {
|
||||
|
||||
@TestFactory
|
||||
Stream<DynamicTest> shouldCreateCorrectUrlsWithContextPath() {
|
||||
return Stream.of("http://localhost/scm", "http://localhost/scm/")
|
||||
.map(url -> assertResultingUrl(url, "http://localhost/scm/repo/space/name"));
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
Stream<DynamicTest> shouldCreateCorrectUrlsWithPort() {
|
||||
return Stream.of("http://localhost:8080", "http://localhost:8080/")
|
||||
.map(url -> assertResultingUrl(url, "http://localhost:8080/repo/space/name"));
|
||||
}
|
||||
|
||||
DynamicTest assertResultingUrl(String baseUrl, String expectedUrl) {
|
||||
String actualUrl = createInstanceOfHttpScmProtocol(baseUrl).getUrl();
|
||||
return DynamicTest.dynamicTest(baseUrl + " -> " + expectedUrl, () -> assertThat(actualUrl).isEqualTo(expectedUrl));
|
||||
}
|
||||
|
||||
private HttpScmProtocol createInstanceOfHttpScmProtocol(String baseUrl) {
|
||||
return new HttpScmProtocol(new Repository("", "", "space", "name"), baseUrl) {
|
||||
@Override
|
||||
protected void serve(HttpServletRequest request, HttpServletResponse response, Repository repository, ServletConfig config) {
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.inject.ProvisionException;
|
||||
import com.google.inject.util.Providers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.stubbing.OngoingStubbing;
|
||||
import sonia.scm.api.v2.resources.ScmPathInfo;
|
||||
import sonia.scm.api.v2.resources.ScmPathInfoStore;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
|
||||
public class InitializingHttpScmProtocolWrapperTest {
|
||||
|
||||
private static final Repository REPOSITORY = new Repository("", "git", "space", "name");
|
||||
|
||||
@Mock
|
||||
private ScmProviderHttpServlet delegateServlet;
|
||||
@Mock
|
||||
private ScmPathInfoStore pathInfoStore;
|
||||
@Mock
|
||||
private ScmConfiguration scmConfiguration;
|
||||
private Provider<ScmPathInfoStore> pathInfoStoreProvider;
|
||||
|
||||
@Mock
|
||||
private HttpServletRequest request;
|
||||
@Mock
|
||||
private HttpServletResponse response;
|
||||
@Mock
|
||||
private ServletConfig servletConfig;
|
||||
|
||||
private InitializingHttpScmProtocolWrapper wrapper;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
initMocks(this);
|
||||
pathInfoStoreProvider = mock(Provider.class);
|
||||
when(pathInfoStoreProvider.get()).thenReturn(pathInfoStore);
|
||||
|
||||
wrapper = new InitializingHttpScmProtocolWrapper(Providers.of(this.delegateServlet), pathInfoStoreProvider, scmConfiguration) {
|
||||
@Override
|
||||
public String getType() {
|
||||
return "git";
|
||||
}
|
||||
};
|
||||
when(scmConfiguration.getBaseUrl()).thenReturn("http://example.com/scm");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUsePathFromPathInfo() {
|
||||
mockSetPathInfo();
|
||||
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
assertEquals("http://example.com/scm/repo/space/name", httpScmProtocol.getUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUseConfigurationWhenPathInfoNotSet() {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
assertEquals("http://example.com/scm/repo/space/name", httpScmProtocol.getUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUseConfigurationWhenNotInRequestScope() {
|
||||
when(pathInfoStoreProvider.get()).thenThrow(new ProvisionException("test"));
|
||||
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
assertEquals("http://example.com/scm/repo/space/name", httpScmProtocol.getUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldInitializeAndDelegateRequestThroughFilter() throws ServletException, IOException {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
httpScmProtocol.serve(request, response, servletConfig);
|
||||
|
||||
verify(delegateServlet).init(servletConfig);
|
||||
verify(delegateServlet).service(request, response, REPOSITORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldInitializeOnlyOnce() throws ServletException, IOException {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
|
||||
|
||||
httpScmProtocol.serve(request, response, servletConfig);
|
||||
httpScmProtocol.serve(request, response, servletConfig);
|
||||
|
||||
verify(delegateServlet, times(1)).init(servletConfig);
|
||||
verify(delegateServlet, times(2)).service(request, response, REPOSITORY);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldFailForIllegalScmType() {
|
||||
HttpScmProtocol httpScmProtocol = wrapper.get(new Repository("", "other", "space", "name"));
|
||||
}
|
||||
|
||||
private OngoingStubbing<ScmPathInfo> mockSetPathInfo() {
|
||||
return when(pathInfoStore.get()).thenReturn(() -> URI.create("http://example.com/scm/api/rest/"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package sonia.scm.web.filter;
|
||||
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@SubjectAware(configuration = "classpath:sonia/scm/shiro.ini")
|
||||
public class PermissionFilterTest {
|
||||
|
||||
public static final Repository REPOSITORY = new Repository("1", "git", "space", "name");
|
||||
|
||||
@Rule
|
||||
public final ShiroRule shiroRule = new ShiroRule();
|
||||
|
||||
private final ScmProviderHttpServlet delegateServlet = mock(ScmProviderHttpServlet.class);
|
||||
|
||||
private final PermissionFilter permissionFilter = new PermissionFilter(new ScmConfiguration(), delegateServlet) {
|
||||
@Override
|
||||
protected boolean isWriteRequest(HttpServletRequest request) {
|
||||
return writeRequest;
|
||||
}
|
||||
};
|
||||
|
||||
private final HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
private final HttpServletResponse response = mock(HttpServletResponse.class);
|
||||
|
||||
private boolean writeRequest = false;
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "reader", password = "secret")
|
||||
public void shouldPassForReaderOnReadRequest() throws IOException, ServletException {
|
||||
writeRequest = false;
|
||||
|
||||
permissionFilter.service(request, response, REPOSITORY);
|
||||
|
||||
verify(delegateServlet).service(request, response, REPOSITORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "reader", password = "secret")
|
||||
public void shouldBlockForReaderOnWriteRequest() throws IOException, ServletException {
|
||||
writeRequest = true;
|
||||
|
||||
permissionFilter.service(request, response, REPOSITORY);
|
||||
|
||||
verify(response).sendError(eq(401), anyString());
|
||||
verify(delegateServlet, never()).service(request, response, REPOSITORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writer", password = "secret")
|
||||
public void shouldPassForWriterOnWriteRequest() throws IOException, ServletException {
|
||||
writeRequest = true;
|
||||
|
||||
permissionFilter.service(request, response, REPOSITORY);
|
||||
|
||||
verify(delegateServlet).service(request, response, REPOSITORY);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
[users]
|
||||
trillian = secret, user
|
||||
admin = secret, admin
|
||||
writer = secret, repo_write
|
||||
reader = secret, repo_read
|
||||
unpriv = secret
|
||||
|
||||
[roles]
|
||||
admin = *
|
||||
user = something:*
|
||||
repo_read = "repository:read:1"
|
||||
repo_write = "repository:push:1"
|
||||
|
||||
@@ -3,6 +3,8 @@ package sonia.scm.it;
|
||||
import io.restassured.response.ExtractableResponse;
|
||||
import io.restassured.response.Response;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.assertj.core.util.Lists;
|
||||
import org.assertj.core.util.Maps;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -17,8 +19,11 @@ import sonia.scm.web.VndMediaType;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.lang.Thread.sleep;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -312,5 +317,163 @@ public class RepositoryAccessITCase {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void shouldFindAddedModifications() throws IOException {
|
||||
RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder);
|
||||
String fileName = "a.txt";
|
||||
Changeset changeset = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "a");
|
||||
String revision = changeset.getId();
|
||||
repositoryGetRequest
|
||||
.usingRepositoryResponse()
|
||||
.requestChangesets()
|
||||
.assertStatusCode(HttpStatus.SC_OK)
|
||||
.usingChangesetsResponse()
|
||||
.requestModifications(revision)
|
||||
.assertStatusCode(HttpStatus.SC_OK)
|
||||
.usingModificationsResponse()
|
||||
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
||||
.assertAdded(addedFiles -> assertThat(addedFiles)
|
||||
.hasSize(1)
|
||||
.containsExactly(fileName))
|
||||
.assertRemoved(removedFiles -> assertThat(removedFiles)
|
||||
.hasSize(0))
|
||||
.assertModified(files -> assertThat(files)
|
||||
.hasSize(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void shouldFindRemovedModifications() throws IOException {
|
||||
RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder);
|
||||
String fileName = "a.txt";
|
||||
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "a");
|
||||
Changeset changeset = RepositoryUtil.removeAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName);
|
||||
|
||||
String revision = changeset.getId();
|
||||
repositoryGetRequest
|
||||
.usingRepositoryResponse()
|
||||
.requestChangesets()
|
||||
.assertStatusCode(HttpStatus.SC_OK)
|
||||
.usingChangesetsResponse()
|
||||
.requestModifications(revision)
|
||||
.assertStatusCode(HttpStatus.SC_OK)
|
||||
.usingModificationsResponse()
|
||||
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
||||
.assertRemoved(removedFiles -> assertThat(removedFiles)
|
||||
.hasSize(1)
|
||||
.containsExactly(fileName))
|
||||
.assertAdded(addedFiles -> assertThat(addedFiles)
|
||||
.hasSize(0))
|
||||
.assertModified(files -> assertThat(files)
|
||||
.hasSize(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void shouldFindUpdateModifications() throws IOException {
|
||||
RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder);
|
||||
String fileName = "a.txt";
|
||||
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "a");
|
||||
Changeset changeset = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "new Content");
|
||||
|
||||
String revision = changeset.getId();
|
||||
repositoryGetRequest
|
||||
.usingRepositoryResponse()
|
||||
.requestChangesets()
|
||||
.assertStatusCode(HttpStatus.SC_OK)
|
||||
.usingChangesetsResponse()
|
||||
.requestModifications(revision)
|
||||
.assertStatusCode(HttpStatus.SC_OK)
|
||||
.usingModificationsResponse()
|
||||
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
||||
.assertModified(modifiedFiles -> assertThat(modifiedFiles)
|
||||
.hasSize(1)
|
||||
.containsExactly(fileName))
|
||||
.assertRemoved(removedFiles -> assertThat(removedFiles)
|
||||
.hasSize(0))
|
||||
.assertAdded(addedFiles -> assertThat(addedFiles)
|
||||
.hasSize(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void shouldFindMultipleModifications() throws IOException {
|
||||
RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder);
|
||||
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "b.txt", "b");
|
||||
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "c.txt", "c");
|
||||
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "d.txt", "d");
|
||||
Map<String, String> addedFiles = new HashMap<String, String>()
|
||||
{{
|
||||
put("a.txt", "bla bla");
|
||||
}};
|
||||
Map<String, String> modifiedFiles = new HashMap<String, String>()
|
||||
{{
|
||||
put("b.txt", "new content");
|
||||
}};
|
||||
ArrayList<String> removedFiles = Lists.newArrayList("c.txt", "d.txt");
|
||||
Changeset changeset = RepositoryUtil.commitMultipleFileModifications(repositoryClient, ADMIN_USERNAME, addedFiles, modifiedFiles, removedFiles);
|
||||
|
||||
String revision = changeset.getId();
|
||||
repositoryGetRequest
|
||||
.usingRepositoryResponse()
|
||||
.requestChangesets()
|
||||
.assertStatusCode(HttpStatus.SC_OK)
|
||||
.usingChangesetsResponse()
|
||||
.requestModifications(revision)
|
||||
.assertStatusCode(HttpStatus.SC_OK)
|
||||
.usingModificationsResponse()
|
||||
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
||||
.assertAdded(a -> assertThat(a)
|
||||
.hasSize(1)
|
||||
.containsExactly("a.txt"))
|
||||
.assertModified(m-> assertThat(m)
|
||||
.hasSize(1)
|
||||
.containsExactly("b.txt"))
|
||||
.assertRemoved(r -> assertThat(r)
|
||||
.hasSize(2)
|
||||
.containsExactly("c.txt", "d.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void svnShouldCreateOneModificationPerFolder() throws IOException {
|
||||
Assume.assumeThat(repositoryType, equalTo("svn"));
|
||||
RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder);
|
||||
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "bbb/bb/b.txt", "b");
|
||||
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "ccc/cc/c.txt", "c");
|
||||
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "ddd/dd/d.txt", "d");
|
||||
Map<String, String> addedFiles = new HashMap<String, String>()
|
||||
{{
|
||||
put("aaa/aa/a.txt", "bla bla");
|
||||
}};
|
||||
Map<String, String> modifiedFiles = new HashMap<String, String>()
|
||||
{{
|
||||
put("bbb/bb/b.txt", "new content");
|
||||
}};
|
||||
ArrayList<String> removedFiles = Lists.newArrayList("ccc/cc/c.txt", "ddd/dd/d.txt");
|
||||
Changeset changeset = RepositoryUtil.commitMultipleFileModifications(repositoryClient, ADMIN_USERNAME, addedFiles, modifiedFiles, removedFiles);
|
||||
|
||||
String revision = changeset.getId();
|
||||
repositoryGetRequest
|
||||
.usingRepositoryResponse()
|
||||
.requestChangesets()
|
||||
.assertStatusCode(HttpStatus.SC_OK)
|
||||
.usingChangesetsResponse()
|
||||
.requestModifications(revision)
|
||||
.assertStatusCode(HttpStatus.SC_OK)
|
||||
.usingModificationsResponse()
|
||||
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
||||
.assertAdded(a -> assertThat(a)
|
||||
.hasSize(3)
|
||||
.containsExactly("aaa/aa/a.txt", "aaa", "aaa/aa"))
|
||||
.assertModified(m-> assertThat(m)
|
||||
.hasSize(1)
|
||||
.containsExactly("bbb/bb/b.txt"))
|
||||
.assertRemoved(r -> assertThat(r)
|
||||
.hasSize(2)
|
||||
.containsExactly("ccc/cc/c.txt", "ddd/dd/d.txt"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ public class RepositoryRequests {
|
||||
return new AppliedGetSourcesRequest(getResponseFromLink(repositoryResponse, "_links.sources.href"));
|
||||
}
|
||||
|
||||
AppliedGetChangesetsRequest requestChangesets(String fileName) {
|
||||
AppliedGetChangesetsRequest requestChangesets() {
|
||||
return new AppliedGetChangesetsRequest(getResponseFromLink(repositoryResponse, "_links.changesets.href"));
|
||||
}
|
||||
}
|
||||
@@ -189,6 +189,9 @@ public class RepositoryRequests {
|
||||
return new AppliedGetDiffRequest(getResponseFromLink(changesetsResponse, "_embedded.changesets.find{it.id=='" + revision + "'}._links.diff.href"));
|
||||
}
|
||||
|
||||
public AppliedGetModificationsRequest requestModifications(String revision) {
|
||||
return new AppliedGetModificationsRequest(getResponseFromLink(changesetsResponse, "_embedded.changesets.find{it.id=='" + revision + "'}._links.modifications.href"));
|
||||
}
|
||||
}
|
||||
|
||||
class AppliedGetSourcesRequest extends AppliedGetRequest<AppliedGetSourcesRequest> {
|
||||
@@ -246,4 +249,45 @@ public class RepositoryRequests {
|
||||
return new GivenWithUrlAndAuth();
|
||||
}
|
||||
}
|
||||
|
||||
class AppliedGetModificationsRequest extends AppliedGetRequest<AppliedGetModificationsRequest> {
|
||||
public AppliedGetModificationsRequest(Response response) { super(response); }
|
||||
ModificationsResponse usingModificationsResponse() {
|
||||
return new ModificationsResponse(super.response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ModificationsResponse {
|
||||
private Response resource;
|
||||
|
||||
public ModificationsResponse(Response resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
ModificationsResponse assertRevision(Consumer<String> assertRevision) {
|
||||
String revision = resource.then().extract().path("revision");
|
||||
assertRevision.accept(revision);
|
||||
return this;
|
||||
}
|
||||
|
||||
ModificationsResponse assertAdded(Consumer<List<String>> assertAdded) {
|
||||
List<String > added = resource.then().extract().path("added");
|
||||
assertAdded.accept(added);
|
||||
return this;
|
||||
}
|
||||
|
||||
ModificationsResponse assertRemoved(Consumer<List<String>> assertRemoved) {
|
||||
List<String > removed = resource.then().extract().path("removed");
|
||||
assertRemoved.accept(removed);
|
||||
return this;
|
||||
}
|
||||
|
||||
ModificationsResponse assertModified(Consumer<List<String>> assertModified) {
|
||||
List<String > modified = resource.then().extract().path("modified");
|
||||
assertModified.accept(modified);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import sonia.scm.repository.client.api.RepositoryClientFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class RepositoryUtil {
|
||||
@@ -29,7 +31,7 @@ public class RepositoryUtil {
|
||||
static RepositoryClient createRepositoryClient(String repositoryType, File folder, String username, String password) throws IOException {
|
||||
String httpProtocolUrl = TestData.callRepository(username, password, repositoryType, HttpStatus.SC_OK)
|
||||
.extract()
|
||||
.path("_links.httpProtocol.href");
|
||||
.path("_links.protocol.find{it.name=='http'}.href");
|
||||
|
||||
return REPOSITORY_CLIENT_FACTORY.create(repositoryType, httpProtocolUrl, username, password, folder);
|
||||
}
|
||||
@@ -42,11 +44,53 @@ public class RepositoryUtil {
|
||||
}
|
||||
|
||||
static Changeset createAndCommitFile(RepositoryClient repositoryClient, String username, String fileName, String content) throws IOException {
|
||||
writeAndAddFile(repositoryClient, fileName, content);
|
||||
return commit(repositoryClient, username, "added " + fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundle multiple File modification in one changeset
|
||||
*
|
||||
* @param repositoryClient
|
||||
* @param username
|
||||
* @param addedFiles map.key: path of the file, value: the file content
|
||||
* @param modifiedFiles map.key: path of the file, value: the file content
|
||||
* @param removedFiles list of file paths to be removed
|
||||
* @return the changeset with all modifications
|
||||
* @throws IOException
|
||||
*/
|
||||
static Changeset commitMultipleFileModifications(RepositoryClient repositoryClient, String username, Map<String, String> addedFiles, Map<String, String> modifiedFiles, List<String> removedFiles) throws IOException {
|
||||
for (String fileName : addedFiles.keySet()) {
|
||||
writeAndAddFile(repositoryClient, fileName, addedFiles.get(fileName));
|
||||
}
|
||||
for (String fileName : modifiedFiles.keySet()) {
|
||||
writeAndAddFile(repositoryClient, fileName, modifiedFiles.get(fileName));
|
||||
}
|
||||
for (String fileName : removedFiles) {
|
||||
deleteFileAndApplyRemoveCommand(repositoryClient, fileName);
|
||||
}
|
||||
return commit(repositoryClient, username, "multiple file modifications" );
|
||||
}
|
||||
|
||||
private static File writeAndAddFile(RepositoryClient repositoryClient, String fileName, String content) throws IOException {
|
||||
File file = new File(repositoryClient.getWorkingCopy(), fileName);
|
||||
Files.createParentDirs(file);
|
||||
Files.write(content, file, Charsets.UTF_8);
|
||||
addWithParentDirectories(repositoryClient, file);
|
||||
return commit(repositoryClient, username, "added " + fileName);
|
||||
return file;
|
||||
}
|
||||
|
||||
static Changeset removeAndCommitFile(RepositoryClient repositoryClient, String username, String fileName) throws IOException {
|
||||
deleteFileAndApplyRemoveCommand(repositoryClient, fileName);
|
||||
return commit(repositoryClient, username, "removed " + fileName);
|
||||
}
|
||||
|
||||
private static void deleteFileAndApplyRemoveCommand(RepositoryClient repositoryClient, String fileName) throws IOException {
|
||||
File file = new File(repositoryClient.getWorkingCopy(), fileName);
|
||||
if (repositoryClient.isCommandSupported(ClientCommand.REMOVE)) {
|
||||
repositoryClient.getRemoveCommand().remove(fileName);
|
||||
}
|
||||
file.delete();
|
||||
}
|
||||
|
||||
private static String addWithParentDirectories(RepositoryClient repositoryClient, File file) throws IOException {
|
||||
|
||||
@@ -18,7 +18,7 @@ import static de.otto.edison.hal.Links.linkingTo;
|
||||
public abstract class GitConfigToGitConfigDtoMapper extends BaseMapper<GitConfig, GitConfigDto> {
|
||||
|
||||
@Inject
|
||||
private UriInfoStore uriInfoStore;
|
||||
private ScmPathInfoStore scmPathInfoStore;
|
||||
|
||||
@AfterMapping
|
||||
void appendLinks(GitConfig config, @MappingTarget GitConfigDto target) {
|
||||
@@ -30,12 +30,12 @@ public abstract class GitConfigToGitConfigDtoMapper extends BaseMapper<GitConfig
|
||||
}
|
||||
|
||||
private String self() {
|
||||
LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), GitConfigResource.class);
|
||||
LinkBuilder linkBuilder = new LinkBuilder(scmPathInfoStore.get(), GitConfigResource.class);
|
||||
return linkBuilder.method("get").parameters().href();
|
||||
}
|
||||
|
||||
private String update() {
|
||||
LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), GitConfigResource.class);
|
||||
LinkBuilder linkBuilder = new LinkBuilder(scmPathInfoStore.get(), GitConfigResource.class);
|
||||
return linkBuilder.method("update").parameters().href();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,32 +37,25 @@ package sonia.scm.repository;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import org.eclipse.jgit.diff.DiffEntry;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.PersonIdent;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevTree;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
|
||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
@@ -224,13 +217,6 @@ public class GitChangesetConverter implements Closeable
|
||||
changeset.setParents(parentList);
|
||||
}
|
||||
|
||||
Modifications modifications = createModifications(treeWalk, commit);
|
||||
|
||||
if (modifications != null)
|
||||
{
|
||||
changeset.setModifications(modifications);
|
||||
}
|
||||
|
||||
Collection<String> tagCollection = tags.get(commit.getId());
|
||||
|
||||
if (Util.isNotEmpty(tagCollection))
|
||||
@@ -245,108 +231,7 @@ public class GitChangesetConverter implements Closeable
|
||||
return changeset;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: copy and rename
|
||||
*
|
||||
*
|
||||
* @param modifications
|
||||
* @param entry
|
||||
*/
|
||||
private void appendModification(Modifications modifications, DiffEntry entry)
|
||||
{
|
||||
switch (entry.getChangeType())
|
||||
{
|
||||
case ADD :
|
||||
modifications.getAdded().add(entry.getNewPath());
|
||||
|
||||
break;
|
||||
|
||||
case MODIFY :
|
||||
modifications.getModified().add(entry.getNewPath());
|
||||
|
||||
break;
|
||||
|
||||
case DELETE :
|
||||
modifications.getRemoved().add(entry.getOldPath());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param treeWalk
|
||||
* @param commit
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private Modifications createModifications(TreeWalk treeWalk, RevCommit commit)
|
||||
throws IOException
|
||||
{
|
||||
Modifications modifications = null;
|
||||
|
||||
treeWalk.reset();
|
||||
treeWalk.setRecursive(true);
|
||||
|
||||
if (commit.getParentCount() > 0)
|
||||
{
|
||||
RevCommit parent = commit.getParent(0);
|
||||
RevTree tree = parent.getTree();
|
||||
|
||||
if ((tree == null) && (revWalk != null))
|
||||
{
|
||||
revWalk.parseHeaders(parent);
|
||||
tree = parent.getTree();
|
||||
}
|
||||
|
||||
if (tree != null)
|
||||
{
|
||||
treeWalk.addTree(tree);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("no parent tree at position 0 for commit {}",
|
||||
commit.getName());
|
||||
}
|
||||
|
||||
treeWalk.addTree(new EmptyTreeIterator());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("no parent available for commit {}", commit.getName());
|
||||
}
|
||||
|
||||
treeWalk.addTree(new EmptyTreeIterator());
|
||||
}
|
||||
|
||||
treeWalk.addTree(commit.getTree());
|
||||
|
||||
List<DiffEntry> entries = DiffEntry.scan(treeWalk);
|
||||
|
||||
for (DiffEntry e : entries)
|
||||
{
|
||||
if (!e.getOldId().equals(e.getNewId()))
|
||||
{
|
||||
if (modifications == null)
|
||||
{
|
||||
modifications = new Modifications();
|
||||
}
|
||||
|
||||
appendModification(modifications, e);
|
||||
}
|
||||
}
|
||||
|
||||
return modifications;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.jgit.diff.DiffEntry;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevTree;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
|
||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Slf4j
|
||||
public class GitModificationsCommand extends AbstractGitCommand implements ModificationsCommand {
|
||||
|
||||
protected GitModificationsCommand(GitContext context, Repository repository) {
|
||||
super(context, repository);
|
||||
}
|
||||
|
||||
private Modifications createModifications(TreeWalk treeWalk, RevCommit commit, RevWalk revWalk, String revision)
|
||||
throws IOException, UnsupportedModificationTypeException {
|
||||
treeWalk.reset();
|
||||
treeWalk.setRecursive(true);
|
||||
if (commit.getParentCount() > 0) {
|
||||
RevCommit parent = commit.getParent(0);
|
||||
RevTree tree = parent.getTree();
|
||||
if ((tree == null) && (revWalk != null)) {
|
||||
revWalk.parseHeaders(parent);
|
||||
tree = parent.getTree();
|
||||
}
|
||||
if (tree != null) {
|
||||
treeWalk.addTree(tree);
|
||||
} else {
|
||||
log.trace("no parent tree at position 0 for commit {}", commit.getName());
|
||||
treeWalk.addTree(new EmptyTreeIterator());
|
||||
}
|
||||
} else {
|
||||
log.trace("no parent available for commit {}", commit.getName());
|
||||
treeWalk.addTree(new EmptyTreeIterator());
|
||||
}
|
||||
treeWalk.addTree(commit.getTree());
|
||||
List<DiffEntry> entries = DiffEntry.scan(treeWalk);
|
||||
Modifications modifications = new Modifications();
|
||||
for (DiffEntry e : entries) {
|
||||
if (!e.getOldId().equals(e.getNewId())) {
|
||||
appendModification(modifications, e);
|
||||
}
|
||||
}
|
||||
modifications.setRevision(revision);
|
||||
return modifications;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifications getModifications(String revision) {
|
||||
org.eclipse.jgit.lib.Repository gitRepository = null;
|
||||
RevWalk revWalk = null;
|
||||
try {
|
||||
gitRepository = open();
|
||||
if (!gitRepository.getAllRefs().isEmpty()) {
|
||||
revWalk = new RevWalk(gitRepository);
|
||||
ObjectId id = GitUtil.getRevisionId(gitRepository, revision);
|
||||
RevCommit commit = revWalk.parseCommit(id);
|
||||
TreeWalk treeWalk = new TreeWalk(gitRepository);
|
||||
return createModifications(treeWalk, commit, revWalk, revision);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
log.error("could not open repository", ex);
|
||||
throw new InternalRepositoryException(ex);
|
||||
|
||||
} catch (UnsupportedModificationTypeException ex) {
|
||||
log.error("Unsupported modification type", ex);
|
||||
throw new InternalRepositoryException(ex);
|
||||
|
||||
} finally {
|
||||
GitUtil.release(revWalk);
|
||||
GitUtil.close(gitRepository);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifications getModifications(ModificationsCommandRequest request) {
|
||||
return getModifications(request.getRevision());
|
||||
}
|
||||
|
||||
private void appendModification(Modifications modifications, DiffEntry entry) throws UnsupportedModificationTypeException {
|
||||
DiffEntry.ChangeType type = entry.getChangeType();
|
||||
if (type == DiffEntry.ChangeType.ADD) {
|
||||
modifications.getAdded().add(entry.getNewPath());
|
||||
} else if (type == DiffEntry.ChangeType.MODIFY) {
|
||||
modifications.getModified().add(entry.getNewPath());
|
||||
} else if (type == DiffEntry.ChangeType.DELETE) {
|
||||
modifications.getRemoved().add(entry.getOldPath());
|
||||
} else {
|
||||
throw new UnsupportedModificationTypeException(MessageFormat.format("The modification type: {0} is not supported.", type));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,20 +33,16 @@
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import sonia.scm.repository.GitRepositoryHandler;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.api.Command;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
@@ -73,19 +69,10 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param handler
|
||||
* @param repository
|
||||
*/
|
||||
public GitRepositoryServiceProvider(GitRepositoryHandler handler,
|
||||
Repository repository)
|
||||
{
|
||||
public GitRepositoryServiceProvider(GitRepositoryHandler handler, Repository repository) {
|
||||
this.handler = handler;
|
||||
this.repository = repository;
|
||||
context = new GitContext(handler.getDirectory(repository));
|
||||
this.context = new GitContext(handler.getDirectory(repository));
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
@@ -188,6 +175,11 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
|
||||
return new GitLogCommand(context, repository);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModificationsCommand getModificationsCommand() {
|
||||
return new GitModificationsCommand(context,repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
|
||||
@@ -35,7 +35,6 @@ package sonia.scm.repository.spi;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.GitRepositoryHandler;
|
||||
import sonia.scm.repository.Repository;
|
||||
@@ -45,51 +44,23 @@ import sonia.scm.repository.Repository;
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Extension
|
||||
public class GitRepositoryServiceResolver implements RepositoryServiceResolver
|
||||
{
|
||||
public class GitRepositoryServiceResolver implements RepositoryServiceResolver {
|
||||
|
||||
/** Field description */
|
||||
public static final String TYPE = "git";
|
||||
private final GitRepositoryHandler handler;
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param handler
|
||||
*/
|
||||
@Inject
|
||||
public GitRepositoryServiceResolver(GitRepositoryHandler handler)
|
||||
{
|
||||
public GitRepositoryServiceResolver(GitRepositoryHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param repository
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public GitRepositoryServiceProvider resolve(Repository repository)
|
||||
{
|
||||
public GitRepositoryServiceProvider resolve(Repository repository) {
|
||||
GitRepositoryServiceProvider provider = null;
|
||||
|
||||
if (TYPE.equalsIgnoreCase(repository.getType()))
|
||||
{
|
||||
if (GitRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
|
||||
provider = new GitRepositoryServiceProvider(handler, repository);
|
||||
}
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private GitRepositoryHandler handler;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
|
||||
public class UnsupportedModificationTypeException extends InternalRepositoryException {
|
||||
public UnsupportedModificationTypeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010, Sebastian Sdorra All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer. 2. Redistributions in
|
||||
* binary form must reproduce the above copyright notice, this list of
|
||||
* conditions and the following disclaimer in the documentation and/or other
|
||||
* materials provided with the distribution. 3. Neither the name of SCM-Manager;
|
||||
* nor the names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import sonia.scm.Priority;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.filter.Filters;
|
||||
import sonia.scm.filter.WebElement;
|
||||
import sonia.scm.web.filter.AuthenticationFilter;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* Handles git specific basic authentication.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Priority(Filters.PRIORITY_AUTHENTICATION)
|
||||
@WebElement(value = GitServletModule.PATTERN_GIT)
|
||||
public class GitBasicAuthenticationFilter extends AuthenticationFilter
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs a new instance.
|
||||
*
|
||||
* @param configuration scm-manager main configuration
|
||||
* @param webTokenGenerators web token generators
|
||||
*/
|
||||
@Inject
|
||||
public GitBasicAuthenticationFilter(ScmConfiguration configuration,
|
||||
Set<WebTokenGenerator> webTokenGenerators)
|
||||
{
|
||||
super(configuration, webTokenGenerators);
|
||||
}
|
||||
}
|
||||
@@ -33,38 +33,24 @@
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.eclipse.jgit.http.server.GitSmartHttpTools;
|
||||
|
||||
import sonia.scm.ClientMessages;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.RepositoryProvider;
|
||||
import sonia.scm.web.filter.ProviderPermissionFilter;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.IOException;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||
import sonia.scm.web.filter.PermissionFilter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import sonia.scm.Priority;
|
||||
import sonia.scm.filter.Filters;
|
||||
import sonia.scm.filter.WebElement;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* GitPermissionFilter decides if a git request requires write or read privileges.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Priority(Filters.PRIORITY_AUTHORIZATION)
|
||||
@WebElement(value = GitServletModule.PATTERN_GIT)
|
||||
public class GitPermissionFilter extends ProviderPermissionFilter
|
||||
public class GitPermissionFilter extends PermissionFilter
|
||||
{
|
||||
|
||||
private static final String PARAMETER_SERVICE = "service";
|
||||
@@ -83,11 +69,9 @@ public class GitPermissionFilter extends ProviderPermissionFilter
|
||||
* Constructs a new instance of the GitPermissionFilter.
|
||||
*
|
||||
* @param configuration scm main configuration
|
||||
* @param repositoryProvider repository provider
|
||||
*/
|
||||
@Inject
|
||||
public GitPermissionFilter(ScmConfiguration configuration, RepositoryProvider repositoryProvider) {
|
||||
super(configuration, repositoryProvider);
|
||||
public GitPermissionFilter(ScmConfiguration configuration, ScmProviderHttpServlet delegate) {
|
||||
super(configuration, delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,7 +87,7 @@ public class GitPermissionFilter extends ProviderPermissionFilter
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isWriteRequest(HttpServletRequest request) {
|
||||
public boolean isWriteRequest(HttpServletRequest request) {
|
||||
return isReceivePackRequest(request) ||
|
||||
isReceiveServiceRequest(request) ||
|
||||
isLfsFileUpload(request);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package sonia.scm.web;
|
||||
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.GitRepositoryHandler;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServletDecoratorFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Extension
|
||||
public class GitPermissionFilterFactory implements ScmProviderHttpServletDecoratorFactory {
|
||||
|
||||
private final ScmConfiguration configuration;
|
||||
|
||||
@Inject
|
||||
public GitPermissionFilterFactory(ScmConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handlesScmType(String type) {
|
||||
return GitRepositoryHandler.TYPE_NAME.equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScmProviderHttpServlet createDecorator(ScmProviderHttpServlet delegate) {
|
||||
return new GitPermissionFilter(configuration, delegate);
|
||||
}
|
||||
}
|
||||
@@ -125,8 +125,9 @@ public class GitRepositoryResolver implements RepositoryResolver<HttpServletRequ
|
||||
throw new ServiceNotEnabledException();
|
||||
}
|
||||
}
|
||||
catch (RuntimeException | IOException e)
|
||||
catch (IOException e)
|
||||
{
|
||||
// REVIEW
|
||||
throw new RepositoryNotFoundException(repositoryName, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package sonia.scm.web;
|
||||
|
||||
import sonia.scm.api.v2.resources.ScmPathInfoStore;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.GitRepositoryHandler;
|
||||
import sonia.scm.repository.spi.InitializingHttpScmProtocolWrapper;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
@Extension
|
||||
public class GitScmProtocolProviderWrapper extends InitializingHttpScmProtocolWrapper {
|
||||
@Inject
|
||||
public GitScmProtocolProviderWrapper(ScmGitServletProvider servletProvider, Provider<ScmPathInfoStore> uriInfoStore, ScmConfiguration scmConfiguration) {
|
||||
super(servletProvider, uriInfoStore, scmConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return GitRepositoryHandler.TYPE_NAME;
|
||||
}
|
||||
}
|
||||
@@ -51,18 +51,6 @@ import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||
public class GitServletModule extends ServletModule
|
||||
{
|
||||
|
||||
public static final String GIT_PATH = "/git";
|
||||
|
||||
/** Field description */
|
||||
public static final String PATTERN_GIT = GIT_PATH + "/*";
|
||||
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
protected void configureServlets()
|
||||
{
|
||||
@@ -75,8 +63,5 @@ public class GitServletModule extends ServletModule
|
||||
|
||||
bind(GitConfigDtoToGitConfigMapper.class).to(Mappers.getMapper(GitConfigDtoToGitConfigMapper.class).getClass());
|
||||
bind(GitConfigToGitConfigDtoMapper.class).to(Mappers.getMapper(GitConfigToGitConfigDtoMapper.class).getClass());
|
||||
|
||||
// serlvelts and filters
|
||||
serve(PATTERN_GIT).with(ScmGitServlet.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,6 @@
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
@@ -42,8 +40,8 @@ import org.eclipse.jgit.http.server.GitServlet;
|
||||
import org.eclipse.jgit.lfs.lib.Constants;
|
||||
import org.slf4j.Logger;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryProvider;
|
||||
import sonia.scm.repository.RepositoryRequestListenerUtil;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.web.lfs.servlet.LfsServletFactory;
|
||||
|
||||
@@ -57,19 +55,18 @@ import java.util.regex.Pattern;
|
||||
import static org.eclipse.jgit.lfs.lib.Constants.CONTENT_TYPE_GIT_LFS_JSON;
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Singleton
|
||||
public class ScmGitServlet extends GitServlet
|
||||
public class ScmGitServlet extends GitServlet implements ScmProviderHttpServlet
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
public static final String REPO_PATH = "/repo";
|
||||
|
||||
public static final Pattern REGEX_GITHTTPBACKEND = Pattern.compile(
|
||||
"(?x)^/git/(.*/(HEAD|info/refs|objects/(info/[^/]+|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\\.(pack|idx))|git-(upload|receive)-pack))$"
|
||||
"(?x)^/repo/(.*/(HEAD|info/refs|objects/(info/[^/]+|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\\.(pack|idx))|git-(upload|receive)-pack))$"
|
||||
);
|
||||
|
||||
/** Field description */
|
||||
@@ -88,7 +85,6 @@ public class ScmGitServlet extends GitServlet
|
||||
* @param repositoryResolver
|
||||
* @param receivePackFactory
|
||||
* @param repositoryViewer
|
||||
* @param repositoryProvider
|
||||
* @param repositoryRequestListenerUtil
|
||||
* @param lfsServletFactory
|
||||
*/
|
||||
@@ -96,11 +92,9 @@ public class ScmGitServlet extends GitServlet
|
||||
public ScmGitServlet(GitRepositoryResolver repositoryResolver,
|
||||
GitReceivePackFactory receivePackFactory,
|
||||
GitRepositoryViewer repositoryViewer,
|
||||
RepositoryProvider repositoryProvider,
|
||||
RepositoryRequestListenerUtil repositoryRequestListenerUtil,
|
||||
LfsServletFactory lfsServletFactory)
|
||||
{
|
||||
this.repositoryProvider = repositoryProvider;
|
||||
this.repositoryViewer = repositoryViewer;
|
||||
this.repositoryRequestListenerUtil = repositoryRequestListenerUtil;
|
||||
this.lfsServletFactory = lfsServletFactory;
|
||||
@@ -122,44 +116,9 @@ public class ScmGitServlet extends GitServlet
|
||||
* @throws ServletException
|
||||
*/
|
||||
@Override
|
||||
protected void service(HttpServletRequest request,
|
||||
HttpServletResponse response)
|
||||
public void service(HttpServletRequest request, HttpServletResponse response, Repository repository)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
Repository repository = repositoryProvider.get();
|
||||
if (repository != null) {
|
||||
handleRequest(request, response, repository);
|
||||
} else {
|
||||
// logger
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides the type request being currently made and delegates it accordingly.
|
||||
* <ul>
|
||||
* <li>Batch API:</li>
|
||||
* <ul>
|
||||
* <li>used to provide the client with information on how handle the large files of a repository.</li>
|
||||
* <li>response contains the information where to perform the actual upload and download of the large objects.</li>
|
||||
* </ul>
|
||||
* <li>Transfer API:</li>
|
||||
* <ul>
|
||||
* <li>receives and provides the actual large objects (resolves the pointer placed in the file of the working copy).</li>
|
||||
* <li>invoked only after the Batch API has been questioned about what to do with the large files</li>
|
||||
* </ul>
|
||||
* <li>Regular Git Http API:</li>
|
||||
* <ul>
|
||||
* <li>regular git http wire protocol, use by normal git clients.</li>
|
||||
* </ul>
|
||||
* <li>Browser Overview:<li>
|
||||
* <ul>
|
||||
* <li>short repository overview for browser clients.</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
private void handleRequest(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException {
|
||||
String repoPath = repository.getNamespace() + "/" + repository.getName();
|
||||
logger.trace("handle git repository at {}", repoPath);
|
||||
if (isLfsBatchApiRequest(request, repoPath)) {
|
||||
@@ -210,7 +169,7 @@ public class ScmGitServlet extends GitServlet
|
||||
* @throws IOException
|
||||
* @throws ServletException
|
||||
*/
|
||||
private void handleBrowserRequest(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException {
|
||||
private void handleBrowserRequest(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException {
|
||||
try {
|
||||
repositoryViewer.handleRequest(request, response, repository);
|
||||
} catch (IOException ex) {
|
||||
@@ -229,7 +188,7 @@ public class ScmGitServlet extends GitServlet
|
||||
*/
|
||||
private static boolean isLfsFileTransferRequest(HttpServletRequest request, String repository) {
|
||||
|
||||
String regex = String.format("^%s%s/%s(\\.git)?/info/lfs/objects/[a-z0-9]{64}$", request.getContextPath(), GitServletModule.GIT_PATH, repository);
|
||||
String regex = String.format("^%s%s/%s(\\.git)?/info/lfs/objects/[a-z0-9]{64}$", request.getContextPath(), REPO_PATH, repository);
|
||||
boolean pathMatches = request.getRequestURI().matches(regex);
|
||||
|
||||
boolean methodMatches = request.getMethod().equals("PUT") || request.getMethod().equals("GET");
|
||||
@@ -248,7 +207,7 @@ public class ScmGitServlet extends GitServlet
|
||||
*/
|
||||
private static boolean isLfsBatchApiRequest(HttpServletRequest request, String repository) {
|
||||
|
||||
String regex = String.format("^%s%s/%s(\\.git)?/info/lfs/objects/batch$", request.getContextPath(), GitServletModule.GIT_PATH, repository);
|
||||
String regex = String.format("^%s%s/%s(\\.git)?/info/lfs/objects/batch$", request.getContextPath(), REPO_PATH, repository);
|
||||
boolean pathMatches = request.getRequestURI().matches(regex);
|
||||
|
||||
boolean methodMatches = "POST".equals(request.getMethod());
|
||||
@@ -284,12 +243,8 @@ public class ScmGitServlet extends GitServlet
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private final RepositoryProvider repositoryProvider;
|
||||
|
||||
/** Field description */
|
||||
private final RepositoryRequestListenerUtil repositoryRequestListenerUtil;
|
||||
|
||||
@@ -299,5 +254,4 @@ public class ScmGitServlet extends GitServlet
|
||||
private final GitRepositoryViewer repositoryViewer;
|
||||
|
||||
private final LfsServletFactory lfsServletFactory;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package sonia.scm.web;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import sonia.scm.repository.GitRepositoryHandler;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServletProvider;
|
||||
|
||||
import javax.inject.Provider;
|
||||
|
||||
public class ScmGitServletProvider extends ScmProviderHttpServletProvider {
|
||||
|
||||
@Inject
|
||||
private Provider<ScmGitServlet> servletProvider;
|
||||
|
||||
public ScmGitServletProvider() {
|
||||
super(GitRepositoryHandler.TYPE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ScmProviderHttpServlet getRootServlet() {
|
||||
return servletProvider.get();
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ public class LfsServletFactory {
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String buildBaseUri(Repository repository, HttpServletRequest request) {
|
||||
return String.format("%s/git/%s/%s.git/info/lfs/objects/", HttpUtil.getCompleteUrl(request), repository.getNamespace(), repository.getName());
|
||||
return String.format("%s/repo/%s/%s.git/info/lfs/objects/", HttpUtil.getCompleteUrl(request), repository.getNamespace(), repository.getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public class GitConfigResourceTest {
|
||||
private GitConfigDtoToGitConfigMapperImpl dtoToConfigMapper;
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private UriInfoStore uriInfoStore;
|
||||
private ScmPathInfoStore scmPathInfoStore;
|
||||
|
||||
@InjectMocks
|
||||
private GitConfigToGitConfigDtoMapperImpl configToDtoMapper;
|
||||
@@ -67,7 +67,7 @@ public class GitConfigResourceTest {
|
||||
when(repositoryHandler.getConfig()).thenReturn(gitConfig);
|
||||
GitConfigResource gitConfigResource = new GitConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler);
|
||||
dispatcher.getRegistry().addSingletonResource(gitConfigResource);
|
||||
when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri);
|
||||
when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -28,7 +28,7 @@ public class GitConfigToGitConfigDtoMapperTest {
|
||||
private URI baseUri = URI.create("http://example.com/base/");
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private UriInfoStore uriInfoStore;
|
||||
private ScmPathInfoStore scmPathInfoStore;
|
||||
|
||||
@InjectMocks
|
||||
private GitConfigToGitConfigDtoMapperImpl mapper;
|
||||
@@ -40,7 +40,7 @@ public class GitConfigToGitConfigDtoMapperTest {
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri);
|
||||
when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri);
|
||||
expectedBaseUri = baseUri.resolve(GitConfigResource.GIT_CONFIG_PATH_V2);
|
||||
subjectThreadState.bind();
|
||||
ThreadContext.bind(subject);
|
||||
|
||||
@@ -85,14 +85,14 @@ public class AbstractRemoteCommandTestBase
|
||||
outgoingDirectory = tempFolder.newFile("outgoing");
|
||||
outgoingDirectory.delete();
|
||||
|
||||
incomgingRepository = new Repository("1", "git", "space", "incoming");
|
||||
incomingRepository = new Repository("1", "git", "space", "incoming");
|
||||
outgoingRepository = new Repository("2", "git", "space", "outgoing");
|
||||
|
||||
incoming = Git.init().setDirectory(incomingDirectory).setBare(false).call();
|
||||
outgoing = Git.init().setDirectory(outgoingDirectory).setBare(false).call();
|
||||
|
||||
handler = mock(GitRepositoryHandler.class);
|
||||
when(handler.getDirectory(incomgingRepository)).thenReturn(
|
||||
when(handler.getDirectory(incomingRepository)).thenReturn(
|
||||
incomingDirectory);
|
||||
when(handler.getDirectory(outgoingRepository)).thenReturn(
|
||||
outgoingDirectory);
|
||||
@@ -211,7 +211,7 @@ public class AbstractRemoteCommandTestBase
|
||||
protected GitRepositoryHandler handler;
|
||||
|
||||
/** Field description */
|
||||
protected Repository incomgingRepository;
|
||||
protected Repository incomingRepository;
|
||||
|
||||
/** Field description */
|
||||
protected Git incoming;
|
||||
|
||||
@@ -105,7 +105,7 @@ public class GitIncomingCommandTest
|
||||
|
||||
commit(outgoing, "added a");
|
||||
|
||||
GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory), incomgingRepository);
|
||||
GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory), incomingRepository);
|
||||
PullCommandRequest req = new PullCommandRequest();
|
||||
req.setRemoteRepository(outgoingRepository);
|
||||
pull.pull(req);
|
||||
@@ -192,6 +192,6 @@ public class GitIncomingCommandTest
|
||||
private GitIncomingCommand createCommand()
|
||||
{
|
||||
return new GitIncomingCommand(handler, new GitContext(incomingDirectory),
|
||||
incomgingRepository);
|
||||
incomingRepository);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,21 +168,23 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase
|
||||
Changeset c = command.getChangeset("435df2f061add3589cb3");
|
||||
|
||||
assertNotNull(c);
|
||||
assertEquals("435df2f061add3589cb326cc64be9b9c3897ceca", c.getId());
|
||||
String revision = "435df2f061add3589cb326cc64be9b9c3897ceca";
|
||||
assertEquals(revision, c.getId());
|
||||
assertEquals("added a and b files", c.getDescription());
|
||||
checkDate(c.getDate());
|
||||
assertEquals("Douglas Adams", c.getAuthor().getName());
|
||||
assertEquals("douglas.adams@hitchhiker.com", c.getAuthor().getMail());
|
||||
assertEquals("added a and b files", c.getDescription());
|
||||
|
||||
Modifications mods = c.getModifications();
|
||||
GitModificationsCommand gitModificationsCommand = new GitModificationsCommand(createContext(), repository);
|
||||
Modifications modifications = gitModificationsCommand.getModifications(revision);
|
||||
|
||||
assertNotNull(mods);
|
||||
assertTrue("modified list should be empty", mods.getModified().isEmpty());
|
||||
assertTrue("removed list should be empty", mods.getRemoved().isEmpty());
|
||||
assertFalse("added list should not be empty", mods.getAdded().isEmpty());
|
||||
assertEquals(2, mods.getAdded().size());
|
||||
assertThat(mods.getAdded(), contains("a.txt", "b.txt"));
|
||||
assertNotNull(modifications);
|
||||
assertTrue("modified list should be empty", modifications.getModified().isEmpty());
|
||||
assertTrue("removed list should be empty", modifications.getRemoved().isEmpty());
|
||||
assertFalse("added list should not be empty", modifications.getAdded().isEmpty());
|
||||
assertEquals(2, modifications.getAdded().size());
|
||||
assertThat(modifications.getAdded(), contains("a.txt", "b.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import sonia.scm.repository.Modifications;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.assertj.core.api.Java6Assertions.assertThat;
|
||||
|
||||
public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
|
||||
|
||||
private GitModificationsCommand incomingModificationsCommand;
|
||||
private GitModificationsCommand outgoingModificationsCommand;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
incomingModificationsCommand = new GitModificationsCommand(new GitContext(incomingDirectory), incomingRepository);
|
||||
outgoingModificationsCommand = new GitModificationsCommand(new GitContext(outgoingDirectory), outgoingRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReadAddedFiles() throws Exception {
|
||||
write(outgoing, outgoingDirectory, "a.txt", "bal bla");
|
||||
RevCommit addedFileCommit = commit(outgoing, "add file");
|
||||
String revision = addedFileCommit.getName();
|
||||
Consumer<Modifications> assertModifications = assertAddedFiles("a.txt");
|
||||
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
||||
pushOutgoingAndPullIncoming();
|
||||
assertModifications.accept(incomingModificationsCommand.getModifications(revision));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReadModifiedFiles() throws Exception {
|
||||
write(outgoing, outgoingDirectory, "a.txt", "bal bla");
|
||||
commit(outgoing, "add file");
|
||||
write(outgoing, outgoingDirectory, "a.txt", "modified content");
|
||||
RevCommit modifiedFileCommit = commit(outgoing, "modify file");
|
||||
String revision = modifiedFileCommit.getName();
|
||||
Consumer<Modifications> assertModifications = assertModifiedFiles("a.txt");
|
||||
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
||||
pushOutgoingAndPullIncoming();
|
||||
assertModifications.accept(incomingModificationsCommand.getModifications(revision));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReadRemovedFiles() throws Exception {
|
||||
String fileName = "a.txt";
|
||||
write(outgoing, outgoingDirectory, fileName, "bal bla");
|
||||
commit(outgoing, "add file");
|
||||
File file = new File(outgoingDirectory, fileName);
|
||||
file.delete();
|
||||
outgoing.rm().addFilepattern(fileName).call();
|
||||
RevCommit removedFileCommit = commit(outgoing, "remove file");
|
||||
String revision = removedFileCommit.getName();
|
||||
Consumer<Modifications> assertModifications = assertRemovedFiles(fileName);
|
||||
pushOutgoingAndPullIncoming();
|
||||
assertModifications.accept(incomingModificationsCommand.getModifications(revision));
|
||||
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
||||
}
|
||||
|
||||
void pushOutgoingAndPullIncoming() throws IOException {
|
||||
GitPushCommand cmd = new GitPushCommand(handler, new GitContext(outgoingDirectory),
|
||||
outgoingRepository);
|
||||
PushCommandRequest request = new PushCommandRequest();
|
||||
request.setRemoteRepository(incomingRepository);
|
||||
cmd.push(request);
|
||||
GitPullCommand pullCommand = new GitPullCommand(handler, new GitContext(incomingDirectory),
|
||||
incomingRepository);
|
||||
PullCommandRequest pullRequest = new PullCommandRequest();
|
||||
pullRequest.setRemoteRepository(incomingRepository);
|
||||
pullCommand.pull(pullRequest);
|
||||
}
|
||||
|
||||
Consumer<Modifications> assertRemovedFiles(String fileName) {
|
||||
return (modifications) -> {
|
||||
assertThat(modifications).isNotNull();
|
||||
assertThat(modifications.getAdded())
|
||||
.as("added files modifications")
|
||||
.hasSize(0);
|
||||
assertThat(modifications.getModified())
|
||||
.as("modified files modifications")
|
||||
.hasSize(0);
|
||||
assertThat(modifications.getRemoved())
|
||||
.as("removed files modifications")
|
||||
.hasSize(1)
|
||||
.containsOnly(fileName);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Consumer<Modifications> assertModifiedFiles(String file) {
|
||||
return (modifications) -> {
|
||||
assertThat(modifications).isNotNull();
|
||||
assertThat(modifications.getAdded())
|
||||
.as("added files modifications")
|
||||
.hasSize(0);
|
||||
assertThat(modifications.getModified())
|
||||
.as("modified files modifications")
|
||||
.hasSize(1)
|
||||
.containsOnly(file);
|
||||
assertThat(modifications.getRemoved())
|
||||
.as("removed files modifications")
|
||||
.hasSize(0);
|
||||
};
|
||||
}
|
||||
|
||||
Consumer<Modifications> assertAddedFiles(String file) {
|
||||
return (modifications) -> {
|
||||
assertThat(modifications).isNotNull();
|
||||
assertThat(modifications.getAdded())
|
||||
.as("added files modifications")
|
||||
.hasSize(1)
|
||||
.containsOnly(file);
|
||||
assertThat(modifications.getModified())
|
||||
.as("modified files modifications")
|
||||
.hasSize(0);
|
||||
assertThat(modifications.getRemoved())
|
||||
.as("removed files modifications")
|
||||
.hasSize(0);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
||||
GitOutgoingCommand cmd = createCommand();
|
||||
OutgoingCommandRequest request = new OutgoingCommandRequest();
|
||||
|
||||
request.setRemoteRepository(incomgingRepository);
|
||||
request.setRemoteRepository(incomingRepository);
|
||||
|
||||
ChangesetPagingResult cpr = cmd.getOutgoingChangesets(request);
|
||||
|
||||
@@ -98,7 +98,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
||||
* @throws RepositoryException
|
||||
*/
|
||||
@Test
|
||||
public void testGetOutgoingChangesetsWithAllreadyPushedChanges()
|
||||
public void testGetOutgoingChangesetsWithAlreadyPushedChanges()
|
||||
throws IOException, GitAPIException
|
||||
{
|
||||
write(outgoing, outgoingDirectory, "a.txt", "content of a.txt");
|
||||
@@ -110,7 +110,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
||||
outgoingRepository);
|
||||
PushCommandRequest req = new PushCommandRequest();
|
||||
|
||||
req.setRemoteRepository(incomgingRepository);
|
||||
req.setRemoteRepository(incomingRepository);
|
||||
push.push(req);
|
||||
|
||||
write(outgoing, outgoingDirectory, "b.txt", "content of b.txt");
|
||||
@@ -120,7 +120,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
||||
GitOutgoingCommand cmd = createCommand();
|
||||
OutgoingCommandRequest request = new OutgoingCommandRequest();
|
||||
|
||||
request.setRemoteRepository(incomgingRepository);
|
||||
request.setRemoteRepository(incomingRepository);
|
||||
|
||||
ChangesetPagingResult cpr = cmd.getOutgoingChangesets(request);
|
||||
|
||||
@@ -144,7 +144,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
||||
GitOutgoingCommand cmd = createCommand();
|
||||
OutgoingCommandRequest request = new OutgoingCommandRequest();
|
||||
|
||||
request.setRemoteRepository(incomgingRepository);
|
||||
request.setRemoteRepository(incomingRepository);
|
||||
|
||||
ChangesetPagingResult cpr = cmd.getOutgoingChangesets(request);
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ public class GitPushCommandTest extends AbstractRemoteCommandTestBase
|
||||
GitPushCommand cmd = createCommand();
|
||||
PushCommandRequest request = new PushCommandRequest();
|
||||
|
||||
request.setRemoteRepository(incomgingRepository);
|
||||
request.setRemoteRepository(incomingRepository);
|
||||
|
||||
PushResponse response = cmd.push(request);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.RepositoryProvider;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
@@ -29,12 +29,7 @@ import static org.mockito.Mockito.when;
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class GitPermissionFilterTest {
|
||||
|
||||
@Mock
|
||||
private RepositoryProvider repositoryProvider;
|
||||
|
||||
private final GitPermissionFilter permissionFilter = new GitPermissionFilter(
|
||||
new ScmConfiguration(), repositoryProvider
|
||||
);
|
||||
private final GitPermissionFilter permissionFilter = new GitPermissionFilter(new ScmConfiguration(), mock(ScmProviderHttpServlet.class));
|
||||
|
||||
@Mock
|
||||
private HttpServletResponse response;
|
||||
|
||||
@@ -23,12 +23,12 @@ public class LfsServletFactoryTest {
|
||||
String repositoryName = "git-lfs-demo";
|
||||
|
||||
String result = LfsServletFactory.buildBaseUri(new Repository("", "GIT", repositoryNamespace, repositoryName), RequestWithUri(repositoryName, true));
|
||||
assertThat(result, is(equalTo("http://localhost:8081/scm/git/space/git-lfs-demo.git/info/lfs/objects/")));
|
||||
assertThat(result, is(equalTo("http://localhost:8081/scm/repo/space/git-lfs-demo.git/info/lfs/objects/")));
|
||||
|
||||
|
||||
//result will be with dot-git suffix, ide
|
||||
result = LfsServletFactory.buildBaseUri(new Repository("", "GIT", repositoryNamespace, repositoryName), RequestWithUri(repositoryName, false));
|
||||
assertThat(result, is(equalTo("http://localhost:8081/scm/git/space/git-lfs-demo.git/info/lfs/objects/")));
|
||||
assertThat(result, is(equalTo("http://localhost:8081/scm/repo/space/git-lfs-demo.git/info/lfs/objects/")));
|
||||
}
|
||||
|
||||
private HttpServletRequest RequestWithUri(String repositoryName, boolean withDotGitSuffix) {
|
||||
@@ -44,12 +44,10 @@ public class LfsServletFactoryTest {
|
||||
|
||||
//build from valid live request data
|
||||
when(mockedRequest.getRequestURL()).thenReturn(
|
||||
new StringBuffer(String.format("http://localhost:8081/scm/git/%s%s/info/lfs/objects/batch", repositoryName, suffix)));
|
||||
when(mockedRequest.getRequestURI()).thenReturn(String.format("/scm/git/%s%s/info/lfs/objects/batch", repositoryName, suffix));
|
||||
new StringBuffer(String.format("http://localhost:8081/scm/repo/%s%s/info/lfs/objects/batch", repositoryName, suffix)));
|
||||
when(mockedRequest.getRequestURI()).thenReturn(String.format("/scm/repo/%s%s/info/lfs/objects/batch", repositoryName, suffix));
|
||||
when(mockedRequest.getContextPath()).thenReturn("/scm");
|
||||
|
||||
return mockedRequest;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ import static de.otto.edison.hal.Links.linkingTo;
|
||||
|
||||
public class HgConfigInstallationsToDtoMapper {
|
||||
|
||||
private UriInfoStore uriInfoStore;
|
||||
private ScmPathInfoStore scmPathInfoStore;
|
||||
|
||||
@Inject
|
||||
public HgConfigInstallationsToDtoMapper(UriInfoStore uriInfoStore) {
|
||||
this.uriInfoStore = uriInfoStore;
|
||||
public HgConfigInstallationsToDtoMapper(ScmPathInfoStore scmPathInfoStore) {
|
||||
this.scmPathInfoStore = scmPathInfoStore;
|
||||
}
|
||||
|
||||
public HgConfigInstallationsDto map(List<String> installations, String path) {
|
||||
@@ -20,7 +20,7 @@ public class HgConfigInstallationsToDtoMapper {
|
||||
}
|
||||
|
||||
private String createSelfLink(String path) {
|
||||
LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigResource.class);
|
||||
LinkBuilder linkBuilder = new LinkBuilder(scmPathInfoStore.get(), HgConfigResource.class);
|
||||
return linkBuilder.method("getInstallationsResource").parameters().href() + '/' + path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import static de.otto.edison.hal.Links.linkingTo;
|
||||
public abstract class HgConfigPackagesToDtoMapper {
|
||||
|
||||
@Inject
|
||||
private UriInfoStore uriInfoStore;
|
||||
private ScmPathInfoStore scmPathInfoStore;
|
||||
|
||||
public HgConfigPackagesDto map(HgPackages hgpackages) {
|
||||
return map(new HgPackagesNonIterable(hgpackages));
|
||||
@@ -40,7 +40,7 @@ public abstract class HgConfigPackagesToDtoMapper {
|
||||
}
|
||||
|
||||
private String createSelfLink() {
|
||||
LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigResource.class);
|
||||
LinkBuilder linkBuilder = new LinkBuilder(scmPathInfoStore.get(), HgConfigResource.class);
|
||||
return linkBuilder.method("getPackagesResource").parameters().href();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import static de.otto.edison.hal.Links.linkingTo;
|
||||
public abstract class HgConfigToHgConfigDtoMapper extends BaseMapper<HgConfig, HgConfigDto> {
|
||||
|
||||
@Inject
|
||||
private UriInfoStore uriInfoStore;
|
||||
private ScmPathInfoStore scmPathInfoStore;
|
||||
|
||||
@AfterMapping
|
||||
void appendLinks(HgConfig config, @MappingTarget HgConfigDto target) {
|
||||
@@ -30,12 +30,12 @@ public abstract class HgConfigToHgConfigDtoMapper extends BaseMapper<HgConfig, H
|
||||
}
|
||||
|
||||
private String self() {
|
||||
LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigResource.class);
|
||||
LinkBuilder linkBuilder = new LinkBuilder(scmPathInfoStore.get(), HgConfigResource.class);
|
||||
return linkBuilder.method("get").parameters().href();
|
||||
}
|
||||
|
||||
private String update() {
|
||||
LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigResource.class);
|
||||
LinkBuilder linkBuilder = new LinkBuilder(scmPathInfoStore.get(), HgConfigResource.class);
|
||||
return linkBuilder.method("update").parameters().href();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.spi.javahg.HgLogChangesetCommand;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
|
||||
public class HgModificationsCommand extends AbstractCommand implements ModificationsCommand {
|
||||
|
||||
HgModificationsCommand(HgCommandContext context, Repository repository) {
|
||||
super(context, repository);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Modifications getModifications(String revision) {
|
||||
com.aragost.javahg.Repository repository = open();
|
||||
HgLogChangesetCommand hgLogChangesetCommand = HgLogChangesetCommand.on(repository, getContext().getConfig());
|
||||
int hgRevision = hgLogChangesetCommand.rev(revision).singleRevision();
|
||||
Modifications modifications = hgLogChangesetCommand.rev(MessageFormat.format("{0}:{0}", hgRevision)).extractModifications();
|
||||
modifications.setRevision(revision);
|
||||
return modifications;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifications getModifications(ModificationsCommandRequest request) {
|
||||
return getModifications(request.getRevision());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -33,21 +33,16 @@
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.io.Closeables;
|
||||
|
||||
import sonia.scm.repository.Feature;
|
||||
import sonia.scm.repository.HgHookManager;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.api.Command;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
import sonia.scm.repository.api.CommandNotSupportedException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -81,16 +76,6 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param hookManager
|
||||
* @param handler
|
||||
* @param repository
|
||||
*/
|
||||
HgRepositoryServiceProvider(HgRepositoryHandler handler,
|
||||
HgHookManager hookManager, Repository repository)
|
||||
{
|
||||
@@ -201,6 +186,16 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
|
||||
return new HgLogCommand(context, repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the corresponding {@link ModificationsCommand} implemented from the Plugins
|
||||
*
|
||||
* @return the corresponding {@link ModificationsCommand} implemented from the Plugins
|
||||
* @throws CommandNotSupportedException if there is no Implementation
|
||||
*/
|
||||
public ModificationsCommand getModificationsCommand() {
|
||||
return new HgModificationsCommand(context,repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
|
||||
@@ -33,10 +33,7 @@
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.HgHookManager;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
@@ -50,19 +47,9 @@ import sonia.scm.repository.Repository;
|
||||
public class HgRepositoryServiceResolver implements RepositoryServiceResolver
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
private static final String TYPE = "hg";
|
||||
private HgRepositoryHandler handler;
|
||||
private HgHookManager hookManager;
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param hookManager
|
||||
* @param handler
|
||||
*/
|
||||
@Inject
|
||||
public HgRepositoryServiceResolver(HgRepositoryHandler handler,
|
||||
HgHookManager hookManager)
|
||||
@@ -71,35 +58,14 @@ public class HgRepositoryServiceResolver implements RepositoryServiceResolver
|
||||
this.hookManager = hookManager;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param repository
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public HgRepositoryServiceProvider resolve(Repository repository)
|
||||
{
|
||||
public HgRepositoryServiceProvider resolve(Repository repository) {
|
||||
HgRepositoryServiceProvider provider = null;
|
||||
|
||||
if (TYPE.equalsIgnoreCase(repository.getType()))
|
||||
{
|
||||
provider = new HgRepositoryServiceProvider(handler, hookManager,
|
||||
repository);
|
||||
if (HgRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
|
||||
provider = new HgRepositoryServiceProvider(handler, hookManager, repository);
|
||||
}
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private HgRepositoryHandler handler;
|
||||
|
||||
/** Field description */
|
||||
private HgHookManager hookManager;
|
||||
}
|
||||
|
||||
@@ -41,21 +41,18 @@ import com.aragost.javahg.internals.AbstractCommand;
|
||||
import com.aragost.javahg.internals.HgInputStream;
|
||||
import com.aragost.javahg.internals.RuntimeIOException;
|
||||
import com.aragost.javahg.internals.Utils;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.Person;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
@@ -251,33 +248,14 @@ public abstract class AbstractChangesetCommand extends AbstractCommand
|
||||
changeset.getProperties().put(PROPERTY_CLOSE, "true");
|
||||
}
|
||||
|
||||
Modifications modifications = changeset.getModifications();
|
||||
|
||||
String line = in.textUpTo('\n');
|
||||
|
||||
while (line.length() > 0)
|
||||
{
|
||||
|
||||
if (line.startsWith("a "))
|
||||
{
|
||||
modifications.getAdded().add(line.substring(2));
|
||||
}
|
||||
else if (line.startsWith("m "))
|
||||
{
|
||||
modifications.getModified().add(line.substring(2));
|
||||
}
|
||||
else if (line.startsWith("d "))
|
||||
{
|
||||
modifications.getRemoved().add(line.substring(2));
|
||||
}
|
||||
else if (line.startsWith("t "))
|
||||
while (line.length() > 0) {
|
||||
if (line.startsWith("t "))
|
||||
{
|
||||
changeset.getTags().add(line.substring(2));
|
||||
}
|
||||
|
||||
line = in.textUpTo('\n');
|
||||
}
|
||||
|
||||
String message = in.textUpTo('\0');
|
||||
|
||||
changeset.setDescription(message);
|
||||
@@ -285,6 +263,36 @@ public abstract class AbstractChangesetCommand extends AbstractCommand
|
||||
return changeset;
|
||||
}
|
||||
|
||||
protected Modifications readModificationsFromStream(HgInputStream in) {
|
||||
try {
|
||||
boolean found = in.find(CHANGESET_PATTERN);
|
||||
if (found) {
|
||||
while (!in.match(CHANGESET_PATTERN)) {
|
||||
return extractModifications(in);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Modifications extractModifications(HgInputStream in) throws IOException {
|
||||
Modifications modifications = new Modifications();
|
||||
String line = in.textUpTo('\n');
|
||||
while (line.length() > 0) {
|
||||
if (line.startsWith("a ")) {
|
||||
modifications.getAdded().add(line.substring(2));
|
||||
} else if (line.startsWith("m ")) {
|
||||
modifications.getModified().add(line.substring(2));
|
||||
} else if (line.startsWith("d ")) {
|
||||
modifications.getRemoved().add(line.substring(2));
|
||||
}
|
||||
line = in.textUpTo('\n');
|
||||
}
|
||||
return modifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
|
||||
@@ -38,14 +38,14 @@ package sonia.scm.repository.spi.javahg;
|
||||
import com.aragost.javahg.Repository;
|
||||
import com.aragost.javahg.internals.HgInputStream;
|
||||
import com.aragost.javahg.internals.Utils;
|
||||
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
import sonia.scm.repository.Modifications;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
@@ -106,11 +106,22 @@ public class HgLogChangesetCommand extends AbstractChangesetCommand
|
||||
*/
|
||||
public List<Changeset> execute(String... files)
|
||||
{
|
||||
cmdAppend("--style", CHANGESET_EAGER_STYLE_PATH);
|
||||
return readListFromStream(getHgInputStream(files, CHANGESET_EAGER_STYLE_PATH));
|
||||
}
|
||||
|
||||
HgInputStream stream = launchStream(files);
|
||||
/**
|
||||
* Extract Modifications from the Repository files
|
||||
*
|
||||
* @param files repo files
|
||||
* @return modifications
|
||||
*/
|
||||
public Modifications extractModifications(String... files) {
|
||||
return readModificationsFromStream(getHgInputStream(files, CHANGESET_EAGER_STYLE_PATH));
|
||||
}
|
||||
|
||||
return readListFromStream(stream);
|
||||
HgInputStream getHgInputStream(String[] files, String changesetStylePath) {
|
||||
cmdAppend("--style", changesetStylePath);
|
||||
return launchStream(files);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,11 +149,7 @@ public class HgLogChangesetCommand extends AbstractChangesetCommand
|
||||
*/
|
||||
public List<Integer> loadRevisions(String... files)
|
||||
{
|
||||
cmdAppend("--style", CHANGESET_LAZY_STYLE_PATH);
|
||||
|
||||
HgInputStream stream = launchStream(files);
|
||||
|
||||
return loadRevisionsFromStream(stream);
|
||||
return loadRevisionsFromStream(getHgInputStream(files, CHANGESET_LAZY_STYLE_PATH));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010, Sebastian Sdorra All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer. 2. Redistributions in
|
||||
* binary form must reproduce the above copyright notice, this list of
|
||||
* conditions and the following disclaimer in the documentation and/or other
|
||||
* materials provided with the distribution. 3. Neither the name of SCM-Manager;
|
||||
* nor the names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import sonia.scm.Priority;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.filter.Filters;
|
||||
import sonia.scm.filter.WebElement;
|
||||
import sonia.scm.web.filter.AuthenticationFilter;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Priority(Filters.PRIORITY_AUTHENTICATION)
|
||||
@WebElement(value = HgServletModule.MAPPING_HG)
|
||||
public class HgBasicAuthenticationFilter extends AuthenticationFilter
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param configuration
|
||||
* @param webTokenGenerators
|
||||
*/
|
||||
@Inject
|
||||
public HgBasicAuthenticationFilter(ScmConfiguration configuration,
|
||||
Set<WebTokenGenerator> webTokenGenerators)
|
||||
{
|
||||
super(configuration, webTokenGenerators);
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
protected void sendFailedAuthenticationError(HttpServletRequest request,
|
||||
HttpServletResponse response)
|
||||
throws IOException
|
||||
{
|
||||
if (HgUtil.isHgClient(request)
|
||||
&& (configuration.isLoginAttemptLimitEnabled()))
|
||||
{
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
}
|
||||
else
|
||||
{
|
||||
super.sendFailedAuthenticationError(request, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,6 @@
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.base.Stopwatch;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.inject.Inject;
|
||||
@@ -49,8 +47,8 @@ import sonia.scm.repository.HgHookManager;
|
||||
import sonia.scm.repository.HgPythonScript;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryProvider;
|
||||
import sonia.scm.repository.RepositoryRequestListenerUtil;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||
import sonia.scm.security.CipherUtil;
|
||||
import sonia.scm.util.AssertUtil;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
@@ -68,14 +66,12 @@ import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
import java.util.Enumeration;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Singleton
|
||||
public class HgCGIServlet extends HttpServlet
|
||||
public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
@@ -108,20 +104,18 @@ public class HgCGIServlet extends HttpServlet
|
||||
*
|
||||
* @param cgiExecutorFactory
|
||||
* @param configuration
|
||||
* @param repositoryProvider
|
||||
* @param handler
|
||||
* @param hookManager
|
||||
* @param requestListenerUtil
|
||||
*/
|
||||
@Inject
|
||||
public HgCGIServlet(CGIExecutorFactory cgiExecutorFactory,
|
||||
ScmConfiguration configuration, RepositoryProvider repositoryProvider,
|
||||
ScmConfiguration configuration,
|
||||
HgRepositoryHandler handler, HgHookManager hookManager,
|
||||
RepositoryRequestListenerUtil requestListenerUtil)
|
||||
{
|
||||
this.cgiExecutorFactory = cgiExecutorFactory;
|
||||
this.configuration = configuration;
|
||||
this.repositoryProvider = repositoryProvider;
|
||||
this.handler = handler;
|
||||
this.hookManager = hookManager;
|
||||
this.requestListenerUtil = requestListenerUtil;
|
||||
@@ -131,46 +125,11 @@ public class HgCGIServlet extends HttpServlet
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @throws ServletException
|
||||
*/
|
||||
@Override
|
||||
public void init() throws ServletException
|
||||
public void service(HttpServletRequest request,
|
||||
HttpServletResponse response, Repository repository)
|
||||
{
|
||||
|
||||
super.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws ServletException
|
||||
*/
|
||||
@Override
|
||||
protected void service(HttpServletRequest request,
|
||||
HttpServletResponse response)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
Repository repository = repositoryProvider.get();
|
||||
|
||||
if (repository == null)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("no hg repository found at {}", request.getRequestURI());
|
||||
}
|
||||
|
||||
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
}
|
||||
else if (!handler.isConfigured())
|
||||
if (!handler.isConfigured())
|
||||
{
|
||||
exceptionHandler.sendFormattedError(request, response,
|
||||
HgCGIExceptionHandler.ERROR_NOT_CONFIGURED);
|
||||
@@ -379,9 +338,6 @@ public class HgCGIServlet extends HttpServlet
|
||||
/** Field description */
|
||||
private final HgHookManager hookManager;
|
||||
|
||||
/** Field description */
|
||||
private final RepositoryProvider repositoryProvider;
|
||||
|
||||
/** Field description */
|
||||
private final RepositoryRequestListenerUtil requestListenerUtil;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package sonia.scm.web;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServletProvider;
|
||||
|
||||
import javax.inject.Provider;
|
||||
|
||||
public class HgCGIServletProvider extends ScmProviderHttpServletProvider {
|
||||
|
||||
@Inject
|
||||
private Provider<HgCGIServlet> servletProvider;
|
||||
|
||||
public HgCGIServletProvider() {
|
||||
super(HgRepositoryHandler.TYPE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ScmProviderHttpServlet getRootServlet() {
|
||||
return servletProvider.get();
|
||||
}
|
||||
}
|
||||
@@ -33,53 +33,31 @@
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import sonia.scm.Priority;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.filter.Filters;
|
||||
import sonia.scm.filter.WebElement;
|
||||
import sonia.scm.repository.RepositoryProvider;
|
||||
import sonia.scm.web.filter.ProviderPermissionFilter;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.Set;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||
import sonia.scm.web.filter.PermissionFilter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Permission filter for mercurial repositories.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Priority(Filters.PRIORITY_AUTHORIZATION)
|
||||
@WebElement(value = HgServletModule.MAPPING_HG)
|
||||
public class HgPermissionFilter extends ProviderPermissionFilter
|
||||
public class HgPermissionFilter extends PermissionFilter
|
||||
{
|
||||
|
||||
private static final Set<String> READ_METHODS = ImmutableSet.of("GET", "HEAD", "OPTIONS", "TRACE");
|
||||
|
||||
/**
|
||||
* Constructs a new instance.
|
||||
*
|
||||
* @param configuration scm configuration
|
||||
* @param repositoryProvider repository provider
|
||||
*/
|
||||
@Inject
|
||||
public HgPermissionFilter(ScmConfiguration configuration,
|
||||
RepositoryProvider repositoryProvider)
|
||||
public HgPermissionFilter(ScmConfiguration configuration, ScmProviderHttpServlet delegate)
|
||||
{
|
||||
super(configuration, repositoryProvider);
|
||||
super(configuration, delegate);
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected boolean isWriteRequest(HttpServletRequest request)
|
||||
public boolean isWriteRequest(HttpServletRequest request)
|
||||
{
|
||||
return !READ_METHODS.contains(request.getMethod());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package sonia.scm.web;
|
||||
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServletDecoratorFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Extension
|
||||
public class HgPermissionFilterFactory implements ScmProviderHttpServletDecoratorFactory {
|
||||
|
||||
private final ScmConfiguration configuration;
|
||||
|
||||
@Inject
|
||||
public HgPermissionFilterFactory(ScmConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handlesScmType(String type) {
|
||||
return HgRepositoryHandler.TYPE_NAME.equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScmProviderHttpServlet createDecorator(ScmProviderHttpServlet delegate) {
|
||||
return new HgPermissionFilter(configuration, delegate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package sonia.scm.web;
|
||||
|
||||
import sonia.scm.api.v2.resources.ScmPathInfoStore;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.spi.InitializingHttpScmProtocolWrapper;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
@Extension
|
||||
public class HgScmProtocolProviderWrapper extends InitializingHttpScmProtocolWrapper {
|
||||
@Inject
|
||||
public HgScmProtocolProviderWrapper(HgCGIServletProvider servletProvider, Provider<ScmPathInfoStore> uriInfoStore, ScmConfiguration scmConfiguration) {
|
||||
super(servletProvider, uriInfoStore, scmConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return HgRepositoryHandler.TYPE_NAME;
|
||||
}
|
||||
}
|
||||
@@ -81,8 +81,5 @@ public class HgServletModule extends ServletModule
|
||||
|
||||
// bind servlets
|
||||
serve(MAPPING_HOOK).with(HgHookCallbackServlet.class);
|
||||
|
||||
// register hg cgi servlet
|
||||
serve(MAPPING_HG).with(HgCGIServlet.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { Image } from "@scm-manager/ui-components";
|
||||
import {Image} from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ public class HgConfigInstallationsResourceTest {
|
||||
private final URI baseUri = URI.create("/");
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private UriInfoStore uriInfoStore;
|
||||
private ScmPathInfoStore scmPathInfoStore;
|
||||
|
||||
@InjectMocks
|
||||
private HgConfigInstallationsToDtoMapper mapper;
|
||||
@@ -61,7 +61,7 @@ public class HgConfigInstallationsResourceTest {
|
||||
new HgConfigResource(null, null, null, null,
|
||||
null, resourceProvider));
|
||||
|
||||
when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri);
|
||||
when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -23,7 +23,7 @@ public class HgConfigInstallationsToDtoMapperTest {
|
||||
private URI baseUri = URI.create("http://example.com/base/");
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private UriInfoStore uriInfoStore;
|
||||
private ScmPathInfoStore scmPathInfoStore;
|
||||
|
||||
@InjectMocks
|
||||
private HgConfigInstallationsToDtoMapper mapper;
|
||||
@@ -34,7 +34,7 @@ public class HgConfigInstallationsToDtoMapperTest {
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri);
|
||||
when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri);
|
||||
expectedBaseUri = baseUri.resolve(HgConfigResource.HG_CONFIG_PATH_V2 + "/installations/" + expectedPath);
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ public class HgConfigPackageResourceTest {
|
||||
private final URI baseUri = java.net.URI.create("/");
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private UriInfoStore uriInfoStore;
|
||||
private ScmPathInfoStore scmPathInfoStore;
|
||||
|
||||
@InjectMocks
|
||||
private HgConfigPackagesToDtoMapperImpl mapper;
|
||||
@@ -81,7 +81,7 @@ public class HgConfigPackageResourceTest {
|
||||
public void prepareEnvironment() {
|
||||
setupResources();
|
||||
|
||||
when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri);
|
||||
when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri);
|
||||
|
||||
when(hgPackageReader.getPackages().getPackages()).thenReturn(createPackages());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -13,12 +12,10 @@ import sonia.scm.installer.HgPackages;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static sonia.scm.api.v2.resources.HgConfigTests.assertEqualsPackage;
|
||||
import static sonia.scm.api.v2.resources.HgConfigTests.createPackage;
|
||||
@@ -29,7 +26,7 @@ public class HgConfigPackagesToDtoMapperTest {
|
||||
private URI baseUri = URI.create("http://example.com/base/");
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private UriInfoStore uriInfoStore;
|
||||
private ScmPathInfoStore scmPathInfoStore;
|
||||
|
||||
@InjectMocks
|
||||
private HgConfigPackagesToDtoMapperImpl mapper;
|
||||
@@ -38,7 +35,7 @@ public class HgConfigPackagesToDtoMapperTest {
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri);
|
||||
when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri);
|
||||
expectedBaseUri = baseUri.resolve(HgConfigResource.HG_CONFIG_PATH_V2 + "/packages");
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ public class HgConfigResourceTest {
|
||||
private HgConfigDtoToHgConfigMapperImpl dtoToConfigMapper;
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private UriInfoStore uriInfoStore;
|
||||
private ScmPathInfoStore scmPathInfoStore;
|
||||
|
||||
@InjectMocks
|
||||
private HgConfigToHgConfigDtoMapperImpl configToDtoMapper;
|
||||
@@ -79,7 +79,7 @@ public class HgConfigResourceTest {
|
||||
new HgConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler, packagesResource,
|
||||
autoconfigResource, installationsResource);
|
||||
dispatcher.getRegistry().addSingletonResource(gitConfigResource);
|
||||
when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri);
|
||||
when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -29,7 +29,7 @@ public class HgConfigToHgConfigDtoMapperTest {
|
||||
private URI baseUri = URI.create("http://example.com/base/");
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private UriInfoStore uriInfoStore;
|
||||
private ScmPathInfoStore scmPathInfoStore;
|
||||
|
||||
@InjectMocks
|
||||
private HgConfigToHgConfigDtoMapperImpl mapper;
|
||||
@@ -41,7 +41,7 @@ public class HgConfigToHgConfigDtoMapperTest {
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri);
|
||||
when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri);
|
||||
expectedBaseUri = baseUri.resolve(HgConfigResource.HG_CONFIG_PATH_V2);
|
||||
subjectThreadState.bind();
|
||||
ThreadContext.bind(subject);
|
||||
|
||||
@@ -32,11 +32,11 @@ package sonia.scm.repository.client.spi;
|
||||
|
||||
import com.aragost.javahg.Repository;
|
||||
import com.google.common.collect.Lists;
|
||||
import java.io.IOException;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.Person;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Mercurial implementation of the {@link CommitCommand}.
|
||||
*
|
||||
@@ -70,9 +70,6 @@ public class HgCommitCommand implements CommitCommand
|
||||
|
||||
changeset.setBranches(Lists.newArrayList(c.getBranch()));
|
||||
changeset.setTags(c.tags());
|
||||
changeset.setModifications(
|
||||
new Modifications(c.getAddedFiles(), c.getModifiedFiles(), c.getDeletedFiles())
|
||||
);
|
||||
return changeset;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ import org.junit.Test;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.ChangesetPagingResult;
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.RevisionNotFoundException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -133,27 +136,28 @@ public class HgLogCommandTest extends AbstractHgCommandTestBase
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCommit() {
|
||||
public void testGetCommit() throws IOException, RevisionNotFoundException {
|
||||
HgLogCommand command = createComamnd();
|
||||
String revision = "a9bacaf1b7fa0cebfca71fed4e59ed69a6319427";
|
||||
Changeset c =
|
||||
command.getChangeset("a9bacaf1b7fa0cebfca71fed4e59ed69a6319427");
|
||||
command.getChangeset(revision);
|
||||
|
||||
assertNotNull(c);
|
||||
assertEquals("a9bacaf1b7fa0cebfca71fed4e59ed69a6319427", c.getId());
|
||||
assertEquals(revision, c.getId());
|
||||
assertEquals("added a and b files", c.getDescription());
|
||||
checkDate(c.getDate());
|
||||
assertEquals("Douglas Adams", c.getAuthor().getName());
|
||||
assertEquals("douglas.adams@hitchhiker.com", c.getAuthor().getMail());
|
||||
assertEquals("added a and b files", c.getDescription());
|
||||
ModificationsCommand modificationsCommand = new HgModificationsCommand(cmdContext, repository);
|
||||
Modifications modifications = modificationsCommand.getModifications(revision);
|
||||
|
||||
Modifications mods = c.getModifications();
|
||||
|
||||
assertNotNull(mods);
|
||||
assertTrue("modified list should be empty", mods.getModified().isEmpty());
|
||||
assertTrue("removed list should be empty", mods.getRemoved().isEmpty());
|
||||
assertFalse("added list should not be empty", mods.getAdded().isEmpty());
|
||||
assertEquals(2, mods.getAdded().size());
|
||||
assertThat(mods.getAdded(), contains("a.txt", "b.txt"));
|
||||
assertNotNull(modifications);
|
||||
assertTrue("modified list should be empty", modifications.getModified().isEmpty());
|
||||
assertTrue("removed list should be empty", modifications.getRemoved().isEmpty());
|
||||
assertFalse("added list should not be empty", modifications.getAdded().isEmpty());
|
||||
assertEquals(2, modifications.getAdded().size());
|
||||
assertThat(modifications.getAdded(), contains("a.txt", "b.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user