merge with 2.0.0-m3

This commit is contained in:
Sebastian Sdorra
2019-08-28 07:34:56 +02:00
248 changed files with 5931 additions and 4573 deletions

22
Jenkinsfile vendored
View File

@@ -7,12 +7,15 @@ import com.cloudogu.ces.cesbuildlib.*
node('docker') { node('docker') {
// Change this as when we go back to default - necessary for proper SonarQube analysis // Change this as when we go back to default - necessary for proper SonarQube analysis
mainBranch = "2.0.0-m3" mainBranch = '2.0.0-m3'
properties([ properties([
// Keep only the last 10 build to preserve space // Keep only the last 10 build to preserve space
buildDiscarder(logRotator(numToKeepStr: '10')), buildDiscarder(logRotator(numToKeepStr: '10')),
disableConcurrentBuilds() disableConcurrentBuilds(),
parameters([
string(name: 'dockerTag', trim: true, defaultValue: 'latest', description: 'Extra Docker Tag for cloudogu/scm-manager image')
])
]) ])
timeout(activity: true, time: 30, unit: 'MINUTES') { timeout(activity: true, time: 30, unit: 'MINUTES') {
@@ -51,9 +54,9 @@ node('docker') {
if (isMainBranch()) { if (isMainBranch()) {
// stage('Lifecycle') { stage('Lifecycle') {
// nexusPolicyEvaluation iqApplication: selectedApplication('scm'), iqScanPatterns: [[scanPattern: 'scm-server/target/scm-server-app.zip']], iqStage: 'build' nexusPolicyEvaluation iqApplication: selectedApplication('scm'), iqScanPatterns: [[scanPattern: 'scm-server/target/scm-server-app.zip']], iqStage: 'build'
// } }
stage('Archive') { stage('Archive') {
archiveArtifacts 'scm-webapp/target/scm-webapp.war' archiveArtifacts 'scm-webapp/target/scm-webapp.war'
@@ -66,6 +69,13 @@ node('docker') {
docker.withRegistry('', 'hub.docker.com-cesmarvin') { docker.withRegistry('', 'hub.docker.com-cesmarvin') {
image.push(dockerImageTag) image.push(dockerImageTag)
image.push('latest') image.push('latest')
if (!'latest'.equals(params.dockerTag)) {
image.push(params.dockerTag)
def newDockerTag = "2.0.0-${commitHash.substring(0,7)}-dev-${params.dockerTag}"
currentBuild.description = newDockerTag
image.push(newDockerTag)
}
} }
} }
@@ -92,7 +102,7 @@ String mainBranch
Maven setupMavenBuild() { Maven setupMavenBuild() {
// Keep this version number in sync with .mvn/maven-wrapper.properties // Keep this version number in sync with .mvn/maven-wrapper.properties
Maven mvn = new MavenInDocker(this, "3.5.2-jdk-8") Maven mvn = new MavenInDocker(this, '3.5.2-jdk-8')
if (isMainBranch()) { if (isMainBranch()) {
// Release starts javadoc, which takes very long, so do only for certain branches // Release starts javadoc, which takes very long, so do only for certain branches

View File

@@ -29,46 +29,28 @@
<!ELEMENT scm-version (#PCDATA)> <!ELEMENT scm-version (#PCDATA)>
<!--- contains informations of the plugin for the plugin backend --> <!--- contains informations of the plugin for the plugin backend -->
<!ELEMENT information (author|artifactId|category|tags|description|groupId|name|screenshots|url|version|wiki)*> <!ELEMENT information (author|category|description|name|version|displayName|avatarUrl)*>
<!--- plugin author --> <!--- plugin author -->
<!ELEMENT author (#PCDATA)> <!ELEMENT author (#PCDATA)>
<!--- maven artifact id -->
<!ELEMENT artifactId (#PCDATA)>
<!--- category of the plugin --> <!--- category of the plugin -->
<!ELEMENT category (#PCDATA)> <!ELEMENT category (#PCDATA)>
<!--- tags of the plugin -->
<!ELEMENT tags (tag)*>
<!--- single tag -->
<!ELEMENT tag (#PCDATA)>
<!--- description of the plugin --> <!--- description of the plugin -->
<!ELEMENT description (#PCDATA)> <!ELEMENT description (#PCDATA)>
<!--- maven groupId id -->
<!ELEMENT groupId (#PCDATA)>
<!--- name of the plugin or the name of the os condition --> <!--- name of the plugin or the name of the os condition -->
<!ELEMENT name (#PCDATA)> <!ELEMENT name (#PCDATA)>
<!--- contains screenshots of the plugin -->
<!ELEMENT screenshots (screenshot)*>
<!--- single screenshot of the plugin -->
<!ELEMENT screenshot (#PCDATA)>
<!--- the url of the plugin homepage -->
<!ELEMENT url (#PCDATA)>
<!--- the current version of the plugin --> <!--- the current version of the plugin -->
<!ELEMENT version (#PCDATA)> <!ELEMENT version (#PCDATA)>
<!--- the url of a wiki page --> <!--- plugin displayName -->
<!ELEMENT wiki (#PCDATA)> <!ELEMENT displayName (#PCDATA)>
<!--- url of the plugin avatar -->
<!ELEMENT avatarUrl (#PCDATA)>
<!--- true if the plugin should load child classes first, the default is false --> <!--- true if the plugin should load child classes first, the default is false -->
<!ELEMENT child-first-classloader (#PCDATA)> <!ELEMENT child-first-classloader (#PCDATA)>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
%!PS-Adobe-2.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

10
pom.xml
View File

@@ -437,8 +437,15 @@
<plugin> <plugin>
<groupId>sonia.scm.maven</groupId> <groupId>sonia.scm.maven</groupId>
<artifactId>smp-maven-plugin</artifactId> <artifactId>smp-maven-plugin</artifactId>
<version>1.0.0-alpha-4</version> <version>1.0.0-alpha-6</version>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins> </plugins>
</pluginManagement> </pluginManagement>
@@ -633,7 +640,6 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId> <artifactId>maven-deploy-plugin</artifactId>
<version>2.7</version>
</plugin> </plugin>
<plugin> <plugin>

View File

@@ -0,0 +1,21 @@
package sonia.scm;
import java.util.Collections;
public class IllegalIdentifierChangeException extends BadRequestException {
private static final String CODE = "thbsUFokjk";
public IllegalIdentifierChangeException(ContextEntry.ContextBuilder context, String message) {
super(context.build(), message);
}
public IllegalIdentifierChangeException(String message) {
super(Collections.emptyList(), message);
}
@Override
public String getCode() {
return CODE;
}
}

View File

@@ -2,6 +2,8 @@ package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.HalRepresentation;
import java.util.List;
/** /**
* The {@link HalAppender} can be used within an {@link HalEnricher} to append hateoas links to a json response. * The {@link HalAppender} can be used within an {@link HalEnricher} to append hateoas links to a json response.
* *
@@ -34,6 +36,14 @@ public interface HalAppender {
*/ */
void appendEmbedded(String rel, HalRepresentation embeddedItem); void appendEmbedded(String rel, HalRepresentation embeddedItem);
/**
* Appends a list of embedded objects to the json response.
*
* @param rel name of relation
* @param embeddedItems embedded objects
*/
void appendEmbedded(String rel, List<HalRepresentation> embeddedItems);
/** /**
* Builder for link arrays. * Builder for link arrays.
*/ */

View File

@@ -73,7 +73,12 @@ public class ScmConfiguration implements Configuration {
* Default plugin url * Default plugin url
*/ */
public static final String DEFAULT_PLUGINURL = public static final String DEFAULT_PLUGINURL =
"http://plugins.scm-manager.org/scm-plugin-backend/api/{version}/plugins?os={os}&arch={arch}&snapshot=false"; "http://download.scm-manager.org/api/v2/plugins.json?os={os}&arch={arch}&snapshot=false&version={version}";
/**
* Default url for login information (plugin and feature tips on the login page).
*/
public static final String DEFAULT_LOGIN_INFO_URL = "https://login-info.scm-manager.org/api/v1/login-info";
/** /**
* Default plugin url from version 1.0 * Default plugin url from version 1.0
@@ -156,7 +161,6 @@ public class ScmConfiguration implements Configuration {
* Authentication realm for basic authentication. * Authentication realm for basic authentication.
*/ */
private String realmDescription = HttpUtil.AUTHENTICATION_REALM; private String realmDescription = HttpUtil.AUTHENTICATION_REALM;
private boolean enableRepositoryArchive = false;
private boolean disableGroupingGrid = false; private boolean disableGroupingGrid = false;
/** /**
* JavaScript date format from moment.js * JavaScript date format from moment.js
@@ -177,6 +181,9 @@ public class ScmConfiguration implements Configuration {
@XmlElement(name = "namespace-strategy") @XmlElement(name = "namespace-strategy")
private String namespaceStrategy = "UsernameNamespaceStrategy"; private String namespaceStrategy = "UsernameNamespaceStrategy";
@XmlElement(name = "login-info-url")
private String loginInfoUrl = DEFAULT_LOGIN_INFO_URL;
/** /**
* Calls the {@link sonia.scm.ConfigChangedListener#configChanged(Object)} * Calls the {@link sonia.scm.ConfigChangedListener#configChanged(Object)}
@@ -210,12 +217,12 @@ public class ScmConfiguration implements Configuration {
this.forceBaseUrl = other.forceBaseUrl; this.forceBaseUrl = other.forceBaseUrl;
this.baseUrl = other.baseUrl; this.baseUrl = other.baseUrl;
this.disableGroupingGrid = other.disableGroupingGrid; this.disableGroupingGrid = other.disableGroupingGrid;
this.enableRepositoryArchive = other.enableRepositoryArchive;
this.skipFailedAuthenticators = other.skipFailedAuthenticators; this.skipFailedAuthenticators = other.skipFailedAuthenticators;
this.loginAttemptLimit = other.loginAttemptLimit; this.loginAttemptLimit = other.loginAttemptLimit;
this.loginAttemptLimitTimeout = other.loginAttemptLimitTimeout; this.loginAttemptLimitTimeout = other.loginAttemptLimitTimeout;
this.enabledXsrfProtection = other.enabledXsrfProtection; this.enabledXsrfProtection = other.enabledXsrfProtection;
this.namespaceStrategy = other.namespaceStrategy; this.namespaceStrategy = other.namespaceStrategy;
this.loginInfoUrl = other.loginInfoUrl;
} }
/** /**
@@ -334,10 +341,6 @@ public class ScmConfiguration implements Configuration {
return enableProxy; return enableProxy;
} }
public boolean isEnableRepositoryArchive() {
return enableRepositoryArchive;
}
public boolean isForceBaseUrl() { public boolean isForceBaseUrl() {
return forceBaseUrl; return forceBaseUrl;
} }
@@ -350,6 +353,9 @@ public class ScmConfiguration implements Configuration {
return namespaceStrategy; return namespaceStrategy;
} }
public String getLoginInfoUrl() {
return loginInfoUrl;
}
/** /**
* Returns true if failed authenticators are skipped. * Returns true if failed authenticators are skipped.
@@ -381,16 +387,6 @@ public class ScmConfiguration implements Configuration {
this.enableProxy = enableProxy; this.enableProxy = enableProxy;
} }
/**
* Enable or disable the repository archive. Default is disabled.
*
* @param enableRepositoryArchive true to disable the repository archive
* @since 1.14
*/
public void setEnableRepositoryArchive(boolean enableRepositoryArchive) {
this.enableRepositoryArchive = enableRepositoryArchive;
}
public void setForceBaseUrl(boolean forceBaseUrl) { public void setForceBaseUrl(boolean forceBaseUrl) {
this.forceBaseUrl = forceBaseUrl; this.forceBaseUrl = forceBaseUrl;
} }
@@ -477,6 +473,10 @@ public class ScmConfiguration implements Configuration {
this.namespaceStrategy = namespaceStrategy; this.namespaceStrategy = namespaceStrategy;
} }
public void setLoginInfoUrl(String loginInfoUrl) {
this.loginInfoUrl = loginInfoUrl;
}
@Override @Override
// Only for permission checks, don't serialize to XML // Only for permission checks, don't serialize to XML
@XmlTransient @XmlTransient

View File

@@ -1,22 +0,0 @@
package sonia.scm.group;
import java.util.Collection;
/**
* This class represents all associated groups which are provided by external systems for a certain user.
*
* @author Sebastian Sdorra
* @since 2.0.0
*/
public class ExternalGroupNames extends GroupNames {
public ExternalGroupNames() {
}
public ExternalGroupNames(String groupName, String... groupNames) {
super(groupName, groupNames);
}
public ExternalGroupNames(Collection<String> collection) {
super(collection);
}
}

View File

@@ -0,0 +1,10 @@
package sonia.scm.group;
import java.util.Set;
public interface GroupCollector {
String AUTHENTICATED = "_authenticated";
Set<String> collect(String principal);
}

View File

@@ -1,187 +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.group;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
//~--- JDK imports ------------------------------------------------------------
/**
* This class represents all associated groups for a user.
*
* @author Sebastian Sdorra
* @since 1.21
*/
public class GroupNames implements Serializable, Iterable<String>
{
/**
* Group for all authenticated users
* @since 1.31
*/
public static final String AUTHENTICATED = "_authenticated";
/** Field description */
private static final long serialVersionUID = 8615685985213897947L;
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*/
public GroupNames()
{
this(Collections.emptyList());
}
/**
* Constructs ...
*
*
* @param groupName
* @param groupNames
*/
public GroupNames(String groupName, String... groupNames)
{
this(Lists.asList(groupName, groupNames));
}
/**
* Constructs ...
*
*
* @param collection
*/
public GroupNames(Collection<String> collection)
{
this.collection = Collections.unmodifiableCollection(collection);
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param groupName
*
* @return
*/
public boolean contains(String groupName)
{
return collection.contains(groupName);
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final GroupNames other = (GroupNames) obj;
return Objects.equal(collection, other.collection);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode()
{
return Objects.hashCode(collection);
}
/**
* Method description
*
*
* @return
*/
@Override
public Iterator<String> iterator()
{
return collection.iterator();
}
/**
* Method description
*
*
* @return
*/
@Override
public String toString()
{
return Joiner.on(", ").join(collection);
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public Collection<String> getCollection()
{
return collection;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final Collection<String> collection;
}

View File

@@ -0,0 +1,10 @@
package sonia.scm.group;
import sonia.scm.plugin.ExtensionPoint;
import java.util.Set;
@ExtensionPoint
public interface GroupResolver {
Set<String> resolve(String principal);
}

View File

@@ -0,0 +1,32 @@
package sonia.scm.plugin;
import com.google.common.base.Preconditions;
public class AvailablePlugin implements Plugin {
private final AvailablePluginDescriptor pluginDescriptor;
private final boolean pending;
public AvailablePlugin(AvailablePluginDescriptor pluginDescriptor) {
this(pluginDescriptor, false);
}
private AvailablePlugin(AvailablePluginDescriptor pluginDescriptor, boolean pending) {
this.pluginDescriptor = pluginDescriptor;
this.pending = pending;
}
@Override
public AvailablePluginDescriptor getDescriptor() {
return pluginDescriptor;
}
public boolean isPending() {
return pending;
}
public AvailablePlugin install() {
Preconditions.checkState(!pending, "installation is already pending");
return new AvailablePlugin(pluginDescriptor, true);
}
}

View File

@@ -0,0 +1,47 @@
package sonia.scm.plugin;
import java.util.Optional;
import java.util.Set;
/**
* @since 2.0.0
*/
public class AvailablePluginDescriptor implements PluginDescriptor {
private final PluginInformation information;
private final PluginCondition condition;
private final Set<String> dependencies;
private final String url;
private final String checksum;
public AvailablePluginDescriptor(PluginInformation information, PluginCondition condition, Set<String> dependencies, String url, String checksum) {
this.information = information;
this.condition = condition;
this.dependencies = dependencies;
this.url = url;
this.checksum = checksum;
}
public String getUrl() {
return url;
}
public Optional<String> getChecksum() {
return Optional.ofNullable(checksum);
}
@Override
public PluginInformation getInformation() {
return information;
}
@Override
public PluginCondition getCondition() {
return condition;
}
@Override
public Set<String> getDependencies() {
return dependencies;
}
}

View File

@@ -36,27 +36,27 @@ package sonia.scm.plugin;
import java.nio.file.Path; import java.nio.file.Path;
/** /**
* Wrapper for a {@link Plugin}. The wrapper holds the directory, * Wrapper for a {@link InstalledPluginDescriptor}. The wrapper holds the directory,
* {@link ClassLoader} and {@link WebResourceLoader} of a plugin. * {@link ClassLoader} and {@link WebResourceLoader} of a plugin.
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
* @since 2.0.0 * @since 2.0.0
*/ */
public final class PluginWrapper public final class InstalledPlugin implements Plugin
{ {
/** /**
* Constructs a new plugin wrapper. * Constructs a new plugin wrapper.
* *
* @param plugin wrapped plugin * @param descriptor wrapped plugin
* @param classLoader plugin class loader * @param classLoader plugin class loader
* @param webResourceLoader web resource loader * @param webResourceLoader web resource loader
* @param directory plugin directory * @param directory plugin directory
*/ */
public PluginWrapper(Plugin plugin, ClassLoader classLoader, public InstalledPlugin(InstalledPluginDescriptor descriptor, ClassLoader classLoader,
WebResourceLoader webResourceLoader, Path directory) WebResourceLoader webResourceLoader, Path directory)
{ {
this.plugin = plugin; this.descriptor = descriptor;
this.classLoader = classLoader; this.classLoader = classLoader;
this.webResourceLoader = webResourceLoader; this.webResourceLoader = webResourceLoader;
this.directory = directory; this.directory = directory;
@@ -94,18 +94,19 @@ public final class PluginWrapper
*/ */
public String getId() public String getId()
{ {
return plugin.getInformation().getId(); return descriptor.getInformation().getId();
} }
/** /**
* Returns the plugin. * Returns the plugin descriptor.
* *
* *
* @return plugin * @return plugin descriptor
*/ */
public Plugin getPlugin() @Override
public InstalledPluginDescriptor getDescriptor()
{ {
return plugin; return descriptor;
} }
/** /**
@@ -128,7 +129,7 @@ public final class PluginWrapper
private final Path directory; private final Path directory;
/** plugin */ /** plugin */
private final Plugin plugin; private final InstalledPluginDescriptor descriptor;
/** plugin web resource loader */ /** plugin web resource loader */
private final WebResourceLoader webResourceLoader; private final WebResourceLoader webResourceLoader;

View File

@@ -0,0 +1,259 @@
/**
* 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.plugin;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.Set;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
*/
@XmlRootElement(name = "plugin")
@XmlAccessorType(XmlAccessType.FIELD)
public final class InstalledPluginDescriptor extends ScmModule implements PluginDescriptor
{
/**
* Constructs ...
*
*/
InstalledPluginDescriptor() {}
/**
* Constructs ...
*
*
* @param scmVersion
* @param information
* @param resources
* @param condition
* @param childFirstClassLoader
* @param dependencies
*/
public InstalledPluginDescriptor(int scmVersion, PluginInformation information,
PluginResources resources, PluginCondition condition,
boolean childFirstClassLoader, Set<String> dependencies)
{
this.scmVersion = scmVersion;
this.information = information;
this.resources = resources;
this.condition = condition;
this.childFirstClassLoader = childFirstClassLoader;
this.dependencies = dependencies;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param obj
*
* @return
*/
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final InstalledPluginDescriptor other = (InstalledPluginDescriptor) obj;
return Objects.equal(scmVersion, other.scmVersion)
&& Objects.equal(condition, other.condition)
&& Objects.equal(information, other.information)
&& Objects.equal(resources, other.resources)
&& Objects.equal(childFirstClassLoader, other.childFirstClassLoader)
&& Objects.equal(dependencies, other.dependencies);
}
/**
* Method description
*
*
* @return
*/
@Override
public int hashCode()
{
return Objects.hashCode(scmVersion, condition, information, resources,
childFirstClassLoader, dependencies);
}
/**
* Method description
*
*
* @return
*/
@Override
public String toString()
{
//J-
return MoreObjects.toStringHelper(this)
.add("scmVersion", scmVersion)
.add("condition", condition)
.add("information", information)
.add("resources", resources)
.add("childFirstClassLoader", childFirstClassLoader)
.add("dependencies", dependencies)
.toString();
//J+
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public PluginCondition getCondition()
{
return condition;
}
/**
* Method description
*
*
* @return
*
* @since 2.0.0
*/
@Override
public Set<String> getDependencies()
{
if (dependencies == null)
{
dependencies = ImmutableSet.of();
}
return dependencies;
}
/**
* Method description
*
*
* @return
*/
@Override
public PluginInformation getInformation()
{
return information;
}
/**
* Method description
*
*
* @return
*/
public PluginResources getResources()
{
return resources;
}
/**
* Method description
*
*
* @return
*/
public int getScmVersion()
{
return scmVersion;
}
/**
* Method description
*
*
* @return
*/
public boolean isChildFirstClassLoader()
{
return childFirstClassLoader;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
@XmlElement(name = "child-first-classloader")
private boolean childFirstClassLoader;
/** Field description */
@XmlElement(name = "conditions")
private PluginCondition condition;
/** Field description */
@XmlElement(name = "dependency")
@XmlElementWrapper(name = "dependencies")
private Set<String> dependencies;
/** Field description */
@XmlElement(name = "information")
private PluginInformation information;
/** Field description */
private PluginResources resources;
/** Field description */
@XmlElement(name = "scm-version")
private int scmVersion = 1;
}

View File

@@ -1,255 +1,6 @@
/**
* 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.plugin; package sonia.scm.plugin;
//~--- non-JDK imports -------------------------------------------------------- public interface Plugin {
import com.google.common.base.MoreObjects; PluginDescriptor getDescriptor();
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.Set;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
*/
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public final class Plugin extends ScmModule
{
/**
* Constructs ...
*
*/
Plugin() {}
/**
* Constructs ...
*
*
* @param scmVersion
* @param information
* @param resources
* @param condition
* @param childFirstClassLoader
* @param dependencies
*/
public Plugin(int scmVersion, PluginInformation information,
PluginResources resources, PluginCondition condition,
boolean childFirstClassLoader, Set<String> dependencies)
{
this.scmVersion = scmVersion;
this.information = information;
this.resources = resources;
this.condition = condition;
this.childFirstClassLoader = childFirstClassLoader;
this.dependencies = dependencies;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param obj
*
* @return
*/
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final Plugin other = (Plugin) obj;
return Objects.equal(scmVersion, other.scmVersion)
&& Objects.equal(condition, other.condition)
&& Objects.equal(information, other.information)
&& Objects.equal(resources, other.resources)
&& Objects.equal(childFirstClassLoader, other.childFirstClassLoader)
&& Objects.equal(dependencies, other.dependencies);
}
/**
* Method description
*
*
* @return
*/
@Override
public int hashCode()
{
return Objects.hashCode(scmVersion, condition, information, resources,
childFirstClassLoader, dependencies);
}
/**
* Method description
*
*
* @return
*/
@Override
public String toString()
{
//J-
return MoreObjects.toStringHelper(this)
.add("scmVersion", scmVersion)
.add("condition", condition)
.add("information", information)
.add("resources", resources)
.add("childFirstClassLoader", childFirstClassLoader)
.add("dependencies", dependencies)
.toString();
//J+
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public PluginCondition getCondition()
{
return condition;
}
/**
* Method description
*
*
* @return
*
* @since 2.0.0
*/
public Set<String> getDependencies()
{
if (dependencies == null)
{
dependencies = ImmutableSet.of();
}
return dependencies;
}
/**
* Method description
*
*
* @return
*/
public PluginInformation getInformation()
{
return information;
}
/**
* Method description
*
*
* @return
*/
public PluginResources getResources()
{
return resources;
}
/**
* Method description
*
*
* @return
*/
public int getScmVersion()
{
return scmVersion;
}
/**
* Method description
*
*
* @return
*/
public boolean isChildFirstClassLoader()
{
return childFirstClassLoader;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
@XmlElement(name = "child-first-classloader")
private boolean childFirstClassLoader;
/** Field description */
@XmlElement(name = "conditions")
private PluginCondition condition;
/** Field description */
@XmlElement(name = "dependency")
@XmlElementWrapper(name = "dependencies")
private Set<String> dependencies;
/** Field description */
private PluginInformation information;
/** Field description */
private PluginResources resources;
/** Field description */
@XmlElement(name = "scm-version")
private int scmVersion = 1;
} }

View File

@@ -1,120 +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.plugin;
//~--- JDK imports ------------------------------------------------------------
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
/**
*
* @author Sebastian Sdorra
*/
@XmlRootElement(name = "plugin-center")
@XmlAccessorType(XmlAccessType.FIELD)
public class PluginCenter implements Serializable
{
/** Field description */
private static final long serialVersionUID = -6414175308610267397L;
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public Set<PluginInformation> getPlugins()
{
return plugins;
}
/**
* Method description
*
*
* @return
*/
public Set<PluginRepository> getRepositories()
{
return repositories;
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param plugins
*/
public void setPlugins(Set<PluginInformation> plugins)
{
this.plugins = plugins;
}
/**
* Method description
*
*
* @param repositories
*/
public void setRepositories(Set<PluginRepository> repositories)
{
this.repositories = repositories;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
@XmlElement(name = "plugin")
@XmlElementWrapper(name = "plugins")
private Set<PluginInformation> plugins = new HashSet<PluginInformation>();
/** Field description */
@XmlElement(name = "repository")
@XmlElementWrapper(name = "repositories")
private Set<PluginRepository> repositories = new HashSet<PluginRepository>();
}

View File

@@ -0,0 +1,13 @@
package sonia.scm.plugin;
import java.util.Set;
public interface PluginDescriptor {
PluginInformation getInformation();
PluginCondition getCondition();
Set<String> getDependencies();
}

View File

@@ -1,10 +1,10 @@
/** /**
* Copyright (c) 2010, Sebastian Sdorra * Copyright (c) 2010, Sebastian Sdorra
* All rights reserved. * All rights reserved.
* * <p>
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met: * modification, are permitted provided that the following conditions are met:
* * <p>
* 1. Redistributions of source code must retain the above copyright notice, * 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. * this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, * 2. Redistributions in binary form must reproduce the above copyright notice,
@@ -13,7 +13,7 @@
* 3. Neither the name of SCM-Manager; nor the names of its * 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this * contributors may be used to endorse or promote products derived from this
* software without specific prior written permission. * software without specific prior written permission.
* * <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@@ -24,558 +24,82 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * 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 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* * <p>
* http://bitbucket.org/sdorra/scm-manager * http://bitbucket.org/sdorra/scm-manager
*
*/ */
package sonia.scm.plugin; package sonia.scm.plugin;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.github.sdorra.ssp.PermissionObject; import com.github.sdorra.ssp.PermissionObject;
import com.github.sdorra.ssp.StaticPermissions; import com.github.sdorra.ssp.StaticPermissions;
import com.google.common.base.MoreObjects; import lombok.Data;
import com.google.common.base.Objects;
import sonia.scm.Validateable; import sonia.scm.Validateable;
import sonia.scm.util.Util; import sonia.scm.util.Util;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
/** /**
*
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
@Data
@StaticPermissions( @StaticPermissions(
value = "plugin", value = "plugin",
generatedClass = "PluginPermissions", generatedClass = "PluginPermissions",
permissions = {}, permissions = {},
globalPermissions = { "read", "manage" }, globalPermissions = {"read", "manage"},
custom = true, customGlobal = true custom = true, customGlobal = true
) )
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "plugin-information") @XmlRootElement(name = "plugin-information")
public class PluginInformation public class PluginInformation implements PermissionObject, Validateable, Cloneable, Serializable {
implements PermissionObject, Validateable, Cloneable, Serializable
{
/** Field description */
private static final long serialVersionUID = 461382048865977206L; private static final long serialVersionUID = 461382048865977206L;
//~--- methods -------------------------------------------------------------- private String name;
private String version;
private String displayName;
private String description;
private String author;
private String category;
private String avatarUrl;
/**
* Method description
*
*
* @return
*
* @since 1.11
*/
@Override @Override
public PluginInformation clone() public PluginInformation clone() {
{
PluginInformation clone = new PluginInformation(); PluginInformation clone = new PluginInformation();
clone.setName(name);
clone.setArtifactId(artifactId); clone.setVersion(version);
clone.setDisplayName(displayName);
clone.setDescription(description);
clone.setAuthor(author); clone.setAuthor(author);
clone.setCategory(category); clone.setCategory(category);
clone.setTags(tags); clone.setAvatarUrl(avatarUrl);
if (condition != null)
{
clone.setCondition(condition.clone());
}
clone.setDescription(description);
clone.setGroupId(groupId);
clone.setName(name);
if (Util.isNotEmpty(screenshots))
{
clone.setScreenshots(new ArrayList<String>(screenshots));
}
clone.setState(state);
clone.setUrl(url);
clone.setVersion(version);
clone.setWiki(wiki);
return clone; return clone;
} }
/**
* Method description
*
*
* @param obj
*
* @return
*/
@Override @Override
public boolean equals(Object obj) public String getId() {
{ return getName(true);
if (obj == null)
{
return false;
} }
if (getClass() != obj.getClass()) public String getName(boolean withVersion) {
{ StringBuilder id = new StringBuilder(name);
return false;
}
final PluginInformation other = (PluginInformation) obj; if (withVersion) {
//J-
return Objects.equal(artifactId, other.artifactId)
&& Objects.equal(author, other.author)
&& Objects.equal(category, other.category)
&& Objects.equal(tags, other.tags)
&& Objects.equal(condition, other.condition)
&& Objects.equal(description, other.description)
&& Objects.equal(groupId, other.groupId)
&& Objects.equal(name, other.name)
&& Objects.equal(screenshots, other.screenshots)
&& Objects.equal(state, other.state)
&& Objects.equal(url, other.url)
&& Objects.equal(version, other.version)
&& Objects.equal(wiki, other.wiki);
//J+
}
/**
* Method description
*
*
* @return
*/
@Override
public int hashCode()
{
return Objects.hashCode(artifactId, author, category, tags, condition,
description, groupId, name, screenshots, state, url, version, wiki);
}
/**
* Method description
*
*
* @return
*/
@Override
public String toString()
{
//J-
return MoreObjects.toStringHelper(this)
.add("artifactId", artifactId)
.add("author", author)
.add("category", category)
.add("tags", tags)
.add("condition", condition)
.add("description", description)
.add("groupId", groupId)
.add("name", name)
.add("screenshots", screenshots)
.add("state", state)
.add("url", url)
.add("version", version)
.add("wiki", wiki)
.toString();
//J+
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public String getArtifactId()
{
return artifactId;
}
/**
* Method description
*
*
* @return
*/
public String getAuthor()
{
return author;
}
/**
* Method description
*
*
* @return
*/
public String getCategory()
{
return category;
}
/**
* Method description
*
*
* @return
*/
public PluginCondition getCondition()
{
return condition;
}
/**
* Method description
*
*
* @return
*/
public String getDescription()
{
return description;
}
/**
* Method description
*
*
* @return
*/
public String getGroupId()
{
return groupId;
}
/**
* Method description
*
*
* @return
*/
@Override
public String getId()
{
return getId(true);
}
/**
* Method description
*
*
* @param withVersion
*
* @return
* @since 1.21
*/
public String getId(boolean withVersion)
{
StringBuilder id = new StringBuilder(groupId);
id.append(":").append(artifactId);
if (withVersion)
{
id.append(":").append(version); id.append(":").append(version);
} }
return id.toString(); return id.toString();
} }
/**
* Method description
*
*
* @return
*/
public String getName()
{
return name;
}
/**
* Method description
*
*
* @return
*/
public List<String> getScreenshots()
{
return screenshots;
}
/**
* Method description
*
*
* @return
*/
public PluginState getState()
{
return state;
}
/**
* Method description
*
*
* @return
*/
public List<String> getTags()
{
return tags;
}
/**
* Method description
*
*
* @return
*/
public String getUrl()
{
return url;
}
/**
* Method description
*
*
* @return
*/
public String getVersion()
{
return version;
}
/**
* Method description
*
*
* @return
*/
public String getWiki()
{
return wiki;
}
/**
* Method description
*
*
* @return
*/
@Override @Override
public boolean isValid() public boolean isValid() {
{ return Util.isNotEmpty(name) && Util.isNotEmpty(version);
return Util.isNotEmpty(groupId) && Util.isNotEmpty(artifactId)
&& Util.isNotEmpty(name) && Util.isNotEmpty(version);
} }
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param artifactId
*/
public void setArtifactId(String artifactId)
{
this.artifactId = artifactId;
}
/**
* Method description
*
*
* @param author
*/
public void setAuthor(String author)
{
this.author = author;
}
/**
* Method description
*
*
* @param category
*/
public void setCategory(String category)
{
this.category = category;
}
/**
* Method description
*
*
* @param condition
*/
public void setCondition(PluginCondition condition)
{
this.condition = condition;
}
/**
* Method description
*
*
* @param description
*/
public void setDescription(String description)
{
this.description = description;
}
/**
* Method description
*
*
* @param groupId
*/
public void setGroupId(String groupId)
{
this.groupId = groupId;
}
/**
* Method description
*
*
* @param name
*/
public void setName(String name)
{
this.name = name;
}
/**
* Method description
*
*
* @param screenshots
*/
public void setScreenshots(List<String> screenshots)
{
this.screenshots = screenshots;
}
/**
* Method description
*
*
* @param state
*/
public void setState(PluginState state)
{
this.state = state;
}
/**
* Method description
*
*
* @param tags
*/
public void setTags(List<String> tags)
{
this.tags = tags;
}
/**
* Method description
*
*
* @param url
*/
public void setUrl(String url)
{
this.url = url;
}
/**
* Method description
*
*
* @param version
*/
public void setVersion(String version)
{
this.version = version;
}
/**
* Method description
*
*
* @param wiki
*/
public void setWiki(String wiki)
{
this.wiki = wiki;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private String artifactId;
/** Field description */
private String author;
/** Field description */
private String category;
/** Field description */
private PluginCondition condition;
/** Field description */
private String description;
/** Field description */
private String groupId;
/** Field description */
private String name;
/** Field description */
@XmlElement(name = "screenshot")
@XmlElementWrapper(name = "screenshots")
private List<String> screenshots;
/** Field description */
private PluginState state;
/** Field description */
@XmlElement(name = "tag")
@XmlElementWrapper(name = "tags")
private List<String> tags;
/** Field description */
private String url;
/** Field description */
private String version;
/** Field description */
private String wiki;
} }

View File

@@ -1,106 +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.plugin;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import java.io.Serializable;
import java.util.Comparator;
/**
*
* @author Sebastian Sdorra
* @since 1.6
*/
public class PluginInformationComparator
implements Comparator<PluginInformation>, Serializable
{
/** Field description */
public static final PluginInformationComparator INSTANCE =
new PluginInformationComparator();
/** Field description */
private static final long serialVersionUID = -8339752498853225668L;
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param plugin
* @param other
*
* @return
*/
@Override
public int compare(PluginInformation plugin, PluginInformation other)
{
int result = 0;
result = Util.compare(plugin.getGroupId(), other.getGroupId());
if (result == 0)
{
result = Util.compare(plugin.getArtifactId(), other.getArtifactId());
if (result == 0)
{
PluginState state = plugin.getState();
PluginState otherState = other.getState();
if ((state != null) && (otherState != null))
{
result = state.getCompareValue() - otherState.getCompareValue();
}
else if ((state == null) && (otherState != null))
{
result = 1;
}
else if ((state != null) && (otherState == null))
{
result = -1;
}
}
}
return result;
}
}

View File

@@ -68,7 +68,7 @@ public interface PluginLoader
* *
* @return * @return
*/ */
public Collection<PluginWrapper> getInstalledPlugins(); public Collection<InstalledPlugin> getInstalledPlugins();
/** /**
* Returns a {@link ClassLoader} which is able to load classes and resources * Returns a {@link ClassLoader} which is able to load classes and resources

View File

@@ -33,113 +33,56 @@
package sonia.scm.plugin; package sonia.scm.plugin;
//~--- JDK imports ------------------------------------------------------------ import java.util.List;
import java.util.Optional;
import com.google.common.base.Predicate;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
/** /**
* The plugin manager is responsible for plugin related tasks, such as install, uninstall or updating.
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
public interface PluginManager public interface PluginManager {
{
/** /**
* Method description * Returns the available plugin with the given name.
* * @param name of plugin
* @return optional available plugin.
*/ */
public void clearCache(); Optional<AvailablePlugin> getAvailable(String name);
/** /**
* Method description * Returns the installed plugin with the given name.
* * @param name of plugin
* * @return optional installed plugin.
* @param id
*/ */
public void install(String id); Optional<InstalledPlugin> getInstalled(String name);
/** /**
* Installs a plugin package from a inputstream. * Returns all installed plugins.
* *
* * @return a list of installed plugins.
* @param packageStream package input stream
*
* @throws IOException
* @since 1.21
*/ */
public void installPackage(InputStream packageStream) throws IOException; List<InstalledPlugin> getInstalled();
/** /**
* Method description * Returns all available plugins. The list contains the plugins which are loaded from the plugin center, but without
* the installed plugins.
* *
* * @return a list of available plugins.
* @param id
*/ */
public void uninstall(String id); List<AvailablePlugin> getAvailable();
/** /**
* Method description * Installs the plugin with the given name from the list of available plugins.
* *
* * @param name plugin name
* @param id * @param restartAfterInstallation restart context after plugin installation
*/ */
public void update(String id); void install(String name, boolean restartAfterInstallation);
//~--- get methods ----------------------------------------------------------
/** /**
* Method description * Install all pending plugins and restart the scm context.
*
*
* @param id
*
* @return
*/ */
public PluginInformation get(String id); void installPendingAndRestart();
/**
* Method description
*
*
* @param filter
*
* @return
*/
public Collection<PluginInformation> get(Predicate<PluginInformation> filter);
/**
* Method description
*
*
* @return
*/
public Collection<PluginInformation> getAll();
/**
* Method description
*
*
* @return
*/
public Collection<PluginInformation> getAvailable();
/**
* Method description
*
*
* @return
*/
public Collection<PluginInformation> getAvailableUpdates();
/**
* Method description
*
*
* @return
*/
public Collection<PluginInformation> getInstalled();
} }

View File

@@ -1,160 +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.plugin;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import java.io.Serializable;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
*/
public class PluginRepository implements Serializable
{
/** Field description */
private static final long serialVersionUID = -9504354306304731L;
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*/
PluginRepository() {}
/**
* Constructs ...
*
*
* @param id
* @param url
*/
public PluginRepository(String id, String url)
{
this.id = id;
this.url = url;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param obj
*
* @return
*/
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final PluginRepository other = (PluginRepository) obj;
return Objects.equal(id, other.id) && Objects.equal(url, other.url);
}
/**
* Method description
*
*
* @return
*/
@Override
public int hashCode()
{
return Objects.hashCode(id, url);
}
/**
* Method description
*
*
* @return
*/
@Override
public String toString()
{
return MoreObjects.toStringHelper(this).add("id", id).add("url",
url).toString();
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public String getId()
{
return id;
}
/**
* Method description
*
*
* @return
*/
public String getUrl()
{
return url;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private String id;
/** Field description */
private String url;
}

View File

@@ -1,74 +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.plugin;
/**
*
* @author Sebastian Sdorra
*/
public enum PluginState
{
CORE(100), AVAILABLE(60), INSTALLED(80), NEWER_VERSION_INSTALLED(20),
UPDATE_AVAILABLE(40);
/**
* Constructs ...
*
*
* @param compareValue
*/
private PluginState(int compareValue)
{
this.compareValue = compareValue;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @since 1.6
* @return
*/
public int getCompareValue()
{
return compareValue;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final int compareValue;
}

View File

@@ -65,7 +65,7 @@ public final class Plugins
{ {
try try
{ {
context = JAXBContext.newInstance(Plugin.class, ScmModule.class); context = JAXBContext.newInstance(InstalledPluginDescriptor.class, ScmModule.class);
} }
catch (JAXBException ex) catch (JAXBException ex)
{ {
@@ -91,7 +91,7 @@ public final class Plugins
* *
* @return * @return
*/ */
public static Plugin parsePluginDescriptor(Path path) public static InstalledPluginDescriptor parsePluginDescriptor(Path path)
{ {
return parsePluginDescriptor(Files.asByteSource(path.toFile())); return parsePluginDescriptor(Files.asByteSource(path.toFile()));
} }
@@ -104,15 +104,15 @@ public final class Plugins
* *
* @return * @return
*/ */
public static Plugin parsePluginDescriptor(ByteSource data) public static InstalledPluginDescriptor parsePluginDescriptor(ByteSource data)
{ {
Preconditions.checkNotNull(data, "data parameter is required"); Preconditions.checkNotNull(data, "data parameter is required");
Plugin plugin; InstalledPluginDescriptor plugin;
try (InputStream stream = data.openStream()) try (InputStream stream = data.openStream())
{ {
plugin = (Plugin) context.createUnmarshaller().unmarshal(stream); plugin = (InstalledPluginDescriptor) context.createUnmarshaller().unmarshal(stream);
} }
catch (JAXBException ex) catch (JAXBException ex)
{ {

View File

@@ -206,7 +206,7 @@ public final class SmpArchive
* *
* @throws IOException * @throws IOException
*/ */
public Plugin getPlugin() throws IOException public InstalledPluginDescriptor getPlugin() throws IOException
{ {
if (plugin == null) if (plugin == null)
{ {
@@ -219,16 +219,10 @@ public final class SmpArchive
throw new PluginException("could not find information section"); throw new PluginException("could not find information section");
} }
if (Strings.isNullOrEmpty(info.getGroupId())) if (Strings.isNullOrEmpty(info.getName()))
{ {
throw new PluginException( throw new PluginException(
"could not find groupId in plugin descriptor"); "could not find name in plugin descriptor");
}
if (Strings.isNullOrEmpty(info.getArtifactId()))
{
throw new PluginException(
"could not find artifactId in plugin descriptor");
} }
if (Strings.isNullOrEmpty(info.getVersion())) if (Strings.isNullOrEmpty(info.getVersion()))
@@ -251,9 +245,9 @@ public final class SmpArchive
* *
* @throws IOException * @throws IOException
*/ */
private Plugin createPlugin() throws IOException private InstalledPluginDescriptor createPlugin() throws IOException
{ {
Plugin p = null; InstalledPluginDescriptor p = null;
NonClosingZipInputStream zis = null; NonClosingZipInputStream zis = null;
try try
@@ -418,5 +412,5 @@ public final class SmpArchive
private final ByteSource archive; private final ByteSource archive;
/** Field description */ /** Field description */
private Plugin plugin; private InstalledPluginDescriptor plugin;
} }

View File

@@ -1,78 +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.plugin;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Predicate;
/**
*
* @author Sebastian Sdorra
*/
public class StatePluginPredicate implements Predicate<PluginInformation>
{
/**
* Constructs ...
*
*
* @param state
*/
public StatePluginPredicate(PluginState state)
{
this.state = state;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param plugin
*
* @return
*/
@Override
public boolean apply(PluginInformation plugin)
{
return state == plugin.getState();
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final PluginState state;
}

View File

@@ -82,10 +82,9 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
private String namespace; private String namespace;
private String name; private String name;
@XmlElement(name = "permission") @XmlElement(name = "permission")
private final Set<RepositoryPermission> permissions = new HashSet<>(); private Set<RepositoryPermission> permissions = new HashSet<>();
@XmlElement(name = "public") @XmlElement(name = "public")
private boolean publicReadable = false; private boolean publicReadable = false;
private boolean archived = false;
private String type; private String type;
@@ -216,16 +215,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
return type; return type;
} }
/**
* Returns true if the repository is archived.
*
* @return true if the repository is archived
* @since 1.14
*/
public boolean isArchived() {
return archived;
}
/** /**
* Returns {@code true} if the repository is healthy. * Returns {@code true} if the repository is healthy.
* *
@@ -264,16 +253,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
&& ((Util.isEmpty(contact)) || ValidationUtil.isMailAddressValid(contact)); && ((Util.isEmpty(contact)) || ValidationUtil.isMailAddressValid(contact));
} }
/**
* Archive or un archive this repository.
*
* @param archived true to enable archive
* @since 1.14
*/
public void setArchived(boolean archived) {
this.archived = archived;
}
public void setContact(String contact) { public void setContact(String contact) {
this.contact = contact; this.contact = contact;
} }
@@ -331,6 +310,8 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
try { try {
repository = (Repository) super.clone(); repository = (Repository) super.clone();
// fix permission reference on clone
repository.permissions = new HashSet<>(permissions);
} catch (CloneNotSupportedException ex) { } catch (CloneNotSupportedException ex) {
throw new RuntimeException(ex); throw new RuntimeException(ex);
} }
@@ -352,7 +333,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
repository.setDescription(description); repository.setDescription(description);
repository.setPermissions(permissions); repository.setPermissions(permissions);
repository.setPublicReadable(publicReadable); repository.setPublicReadable(publicReadable);
repository.setArchived(archived);
// do not copy health check results // do not copy health check results
} }
@@ -381,7 +361,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
&& Objects.equal(contact, other.contact) && Objects.equal(contact, other.contact)
&& Objects.equal(description, other.description) && Objects.equal(description, other.description)
&& Objects.equal(publicReadable, other.publicReadable) && Objects.equal(publicReadable, other.publicReadable)
&& Objects.equal(archived, other.archived)
&& Objects.equal(permissions, other.permissions) && Objects.equal(permissions, other.permissions)
&& Objects.equal(type, other.type) && Objects.equal(type, other.type)
&& Objects.equal(creationDate, other.creationDate) && Objects.equal(creationDate, other.creationDate)
@@ -393,7 +372,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hashCode(id, namespace, name, contact, description, publicReadable, return Objects.hashCode(id, namespace, name, contact, description, publicReadable,
archived, permissions, type, creationDate, lastModified, properties, permissions, type, creationDate, lastModified, properties,
healthCheckFailures); healthCheckFailures);
} }
@@ -406,7 +385,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
.add("contact", contact) .add("contact", contact)
.add("description", description) .add("description", description)
.add("publicReadable", publicReadable) .add("publicReadable", publicReadable)
.add("archived", archived)
.add("permissions", permissions) .add("permissions", permissions)
.add("type", type) .add("type", type)
.add("lastModified", lastModified) .add("lastModified", lastModified)

View File

@@ -1,48 +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.repository;
/**
*
* @author Sebastian Sdorra
*
* @since 1.14
*/
public class RepositoryIsNotArchivedException extends RuntimeException {
private static final long serialVersionUID = 7728748133123987511L;
public RepositoryIsNotArchivedException() {
super("Repository could not be deleted, because it is not archived.");
}
}

View File

@@ -0,0 +1,68 @@
package sonia.scm.repository.api;
import sonia.scm.FeatureNotSupportedException;
import sonia.scm.repository.Feature;
import sonia.scm.repository.spi.DiffCommandRequest;
import java.util.Set;
abstract class AbstractDiffCommandBuilder <T extends AbstractDiffCommandBuilder> {
/** request for the diff command implementation */
final DiffCommandRequest request = new DiffCommandRequest();
private final Set<Feature> supportedFeatures;
AbstractDiffCommandBuilder(Set<Feature> supportedFeatures) {
this.supportedFeatures = supportedFeatures;
}
/**
* Compute the incoming changes of the branch set with {@link #setRevision(String)} in respect to the changeset given
* here. In other words: What changes would be new to the ancestor changeset given here when the branch would
* be merged into it. Requires feature {@link sonia.scm.repository.Feature#INCOMING_REVISION}!
*
* @return {@code this}
*/
public T setAncestorChangeset(String revision)
{
if (!supportedFeatures.contains(Feature.INCOMING_REVISION)) {
throw new FeatureNotSupportedException(Feature.INCOMING_REVISION.name());
}
request.setAncestorChangeset(revision);
return self();
}
/**
* Show the difference only for the given path.
*
*
* @param path path for difference
*
* @return {@code this}
*/
public T setPath(String path)
{
request.setPath(path);
return self();
}
/**
* Show the difference only for the given revision or (using {@link #setAncestorChangeset(String)}) between this
* and another revision.
*
*
* @param revision revision for difference
*
* @return {@code this}
*/
public T setRevision(String revision)
{
request.setRevision(revision);
return self();
}
abstract T self();
}

View File

@@ -53,11 +53,6 @@ public enum Command
*/ */
BRANCHES, BRANCHES,
/**
* @since 2.0
*/
BRANCH,
/** /**
* @since 1.31 * @since 1.31
*/ */
@@ -71,10 +66,5 @@ public enum Command
/** /**
* @since 2.0 * @since 2.0
*/ */
MODIFICATIONS, MODIFICATIONS, MERGE, DIFF_RESULT, BRANCH;
/**
* @since 2.0
*/
MERGE
} }

View File

@@ -38,10 +38,8 @@ package sonia.scm.repository.api;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.FeatureNotSupportedException;
import sonia.scm.repository.Feature; import sonia.scm.repository.Feature;
import sonia.scm.repository.spi.DiffCommand; import sonia.scm.repository.spi.DiffCommand;
import sonia.scm.repository.spi.DiffCommandRequest;
import sonia.scm.util.IOUtil; import sonia.scm.util.IOUtil;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@@ -72,7 +70,7 @@ import java.util.Set;
* @author Sebastian Sdorra * @author Sebastian Sdorra
* @since 1.17 * @since 1.17
*/ */
public final class DiffCommandBuilder public final class DiffCommandBuilder extends AbstractDiffCommandBuilder<DiffCommandBuilder>
{ {
/** /**
@@ -81,6 +79,9 @@ public final class DiffCommandBuilder
private static final Logger logger = private static final Logger logger =
LoggerFactory.getLogger(DiffCommandBuilder.class); LoggerFactory.getLogger(DiffCommandBuilder.class);
/** implementation of the diff command */
private final DiffCommand diffCommand;
//~--- constructors --------------------------------------------------------- //~--- constructors ---------------------------------------------------------
/** /**
@@ -92,8 +93,8 @@ public final class DiffCommandBuilder
*/ */
DiffCommandBuilder(DiffCommand diffCommand, Set<Feature> supportedFeatures) DiffCommandBuilder(DiffCommand diffCommand, Set<Feature> supportedFeatures)
{ {
super(supportedFeatures);
this.diffCommand = diffCommand; this.diffCommand = diffCommand;
this.supportedFeatures = supportedFeatures;
} }
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
@@ -162,54 +163,6 @@ public final class DiffCommandBuilder
return this; return this;
} }
/**
* Show the difference only for the given path.
*
*
* @param path path for difference
*
* @return {@code this}
*/
public DiffCommandBuilder setPath(String path)
{
request.setPath(path);
return this;
}
/**
* Show the difference only for the given revision or (using {@link #setAncestorChangeset(String)}) between this
* and another revision.
*
*
* @param revision revision for difference
*
* @return {@code this}
*/
public DiffCommandBuilder setRevision(String revision)
{
request.setRevision(revision);
return this;
}
/**
* Compute the incoming changes of the branch set with {@link #setRevision(String)} in respect to the changeset given
* here. In other words: What changes would be new to the ancestor changeset given here when the branch would
* be merged into it. Requires feature {@link sonia.scm.repository.Feature#INCOMING_REVISION}!
*
* @return {@code this}
*/
public DiffCommandBuilder setAncestorChangeset(String revision)
{
if (!supportedFeatures.contains(Feature.INCOMING_REVISION)) {
throw new FeatureNotSupportedException(Feature.INCOMING_REVISION.name());
}
request.setAncestorChangeset(revision);
return this;
}
//~--- get methods ---------------------------------------------------------- //~--- get methods ----------------------------------------------------------
/** /**
@@ -233,12 +186,8 @@ public final class DiffCommandBuilder
diffCommand.getDiffResult(request, outputStream); diffCommand.getDiffResult(request, outputStream);
} }
//~--- fields --------------------------------------------------------------- @Override
DiffCommandBuilder self() {
/** implementation of the diff command */ return this;
private final DiffCommand diffCommand; }
private Set<Feature> supportedFeatures;
/** request for the diff command implementation */
private final DiffCommandRequest request = new DiffCommandRequest();
} }

View File

@@ -0,0 +1,12 @@
package sonia.scm.repository.api;
public interface DiffFile extends Iterable<Hunk> {
String getOldRevision();
String getNewRevision();
String getOldPath();
String getNewPath();
}

View File

@@ -0,0 +1,12 @@
package sonia.scm.repository.api;
import java.util.OptionalInt;
public interface DiffLine {
OptionalInt getOldLineNumber();
OptionalInt getNewLineNumber();
String getContent();
}

View File

@@ -0,0 +1,8 @@
package sonia.scm.repository.api;
public interface DiffResult extends Iterable<DiffFile> {
String getOldRevision();
String getNewRevision();
}

View File

@@ -0,0 +1,41 @@
package sonia.scm.repository.api;
import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.Feature;
import sonia.scm.repository.spi.DiffResultCommand;
import java.io.IOException;
import java.util.Set;
public class DiffResultCommandBuilder extends AbstractDiffCommandBuilder<DiffResultCommandBuilder> {
private static final Logger LOG = LoggerFactory.getLogger(DiffResultCommandBuilder.class);
private final DiffResultCommand diffResultCommand;
DiffResultCommandBuilder(DiffResultCommand diffResultCommand, Set<Feature> supportedFeatures) {
super(supportedFeatures);
this.diffResultCommand = diffResultCommand;
}
/**
* Returns the content of the difference as parsed objects.
*
* @return content of the difference
*/
public DiffResult getDiffResult() throws IOException {
Preconditions.checkArgument(request.isValid(),
"path and/or revision is required");
LOG.debug("create diff result for {}", request);
return diffResultCommand.getDiffResult(request);
}
@Override
DiffResultCommandBuilder self() {
return this;
}
}

View File

@@ -0,0 +1,24 @@
package sonia.scm.repository.api;
public interface Hunk extends Iterable<DiffLine> {
default String getRawHeader() {
return String.format("@@ -%s +%s @@", getLineMarker(getOldStart(), getOldLineCount()), getLineMarker(getNewStart(), getNewLineCount()));
}
default String getLineMarker(int start, int lineCount) {
if (lineCount == 1) {
return Integer.toString(start);
} else {
return String.format("%s,%s", start, lineCount);
}
}
int getOldStart();
int getOldLineCount();
int getNewStart();
int getNewLineCount();
}

View File

@@ -31,7 +31,6 @@
package sonia.scm.repository.api; package sonia.scm.repository.api;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.cache.CacheManager; import sonia.scm.cache.CacheManager;
@@ -239,6 +238,21 @@ public final class RepositoryService implements Closeable {
return new DiffCommandBuilder(provider.getDiffCommand(), provider.getSupportedFeatures()); return new DiffCommandBuilder(provider.getDiffCommand(), provider.getSupportedFeatures());
} }
/**
* The diff command shows differences between revisions for a specified file
* or the entire revision.
*
* @return instance of {@link DiffResultCommandBuilder}
* @throws CommandNotSupportedException if the command is not supported
* by the implementation of the repository service provider.
*/
public DiffResultCommandBuilder getDiffResultCommand() {
LOG.debug("create diff result command for repository {}",
repository.getNamespaceAndName());
return new DiffResultCommandBuilder(provider.getDiffResultCommand(), provider.getSupportedFeatures());
}
/** /**
* The incoming command shows new {@link Changeset}s found in a different * The incoming command shows new {@link Changeset}s found in a different
* repository location. * repository location.

View File

@@ -0,0 +1,9 @@
package sonia.scm.repository.spi;
import sonia.scm.repository.api.DiffResult;
import java.io.IOException;
public interface DiffResultCommand {
DiffResult getDiffResult(DiffCommandRequest request) throws IOException;
}

View File

@@ -158,6 +158,11 @@ public abstract class RepositoryServiceProvider implements Closeable
throw new CommandNotSupportedException(Command.DIFF); throw new CommandNotSupportedException(Command.DIFF);
} }
public DiffResultCommand getDiffResultCommand()
{
throw new CommandNotSupportedException(Command.DIFF_RESULT);
}
/** /**
* Method description * Method description
* *

View File

@@ -99,15 +99,6 @@ public interface AccessTokenBuilder {
*/ */
AccessTokenBuilder scope(Scope scope); AccessTokenBuilder scope(Scope scope);
/**
* Define the logged in user as member of the given groups.
*
* @param groups group names
*
* @return {@code this}
*/
AccessTokenBuilder groups(String... groups);
/** /**
* Creates a new {@link AccessToken} with the provided settings. * Creates a new {@link AccessToken} with the provided settings.
* *

View File

@@ -45,7 +45,6 @@ import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.subject.SimplePrincipalCollection; import org.apache.shiro.subject.SimplePrincipalCollection;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.group.GroupDAO;
import sonia.scm.user.User; import sonia.scm.user.User;
import sonia.scm.user.UserDAO; import sonia.scm.user.UserDAO;
@@ -71,8 +70,6 @@ public final class DAORealmHelper {
private final UserDAO userDAO; private final UserDAO userDAO;
private final GroupCollector groupCollector;
private final String realm; private final String realm;
//~--- constructors --------------------------------------------------------- //~--- constructors ---------------------------------------------------------
@@ -83,14 +80,12 @@ public final class DAORealmHelper {
* *
* @param loginAttemptHandler login attempt handler for wrapping credentials matcher * @param loginAttemptHandler login attempt handler for wrapping credentials matcher
* @param userDAO user dao * @param userDAO user dao
* @param groupCollector collect groups for a principal
* @param realm name of realm * @param realm name of realm
*/ */
public DAORealmHelper(LoginAttemptHandler loginAttemptHandler, UserDAO userDAO, GroupCollector groupCollector, String realm) { public DAORealmHelper(LoginAttemptHandler loginAttemptHandler, UserDAO userDAO, String realm) {
this.loginAttemptHandler = loginAttemptHandler; this.loginAttemptHandler = loginAttemptHandler;
this.realm = realm; this.realm = realm;
this.userDAO = userDAO; this.userDAO = userDAO;
this.groupCollector = groupCollector;
} }
//~--- get methods ---------------------------------------------------------- //~--- get methods ----------------------------------------------------------
@@ -120,7 +115,7 @@ public final class DAORealmHelper {
UsernamePasswordToken upt = (UsernamePasswordToken) token; UsernamePasswordToken upt = (UsernamePasswordToken) token;
String principal = upt.getUsername(); String principal = upt.getUsername();
return getAuthenticationInfo(principal, null, null, Collections.emptySet()); return getAuthenticationInfo(principal, null, null);
} }
/** /**
@@ -135,7 +130,7 @@ public final class DAORealmHelper {
} }
private AuthenticationInfo getAuthenticationInfo(String principal, String credentials, Scope scope, Iterable<String> groups) { private AuthenticationInfo getAuthenticationInfo(String principal, String credentials, Scope scope) {
checkArgument(!Strings.isNullOrEmpty(principal), "username is required"); checkArgument(!Strings.isNullOrEmpty(principal), "username is required");
LOG.debug("try to authenticate {}", principal); LOG.debug("try to authenticate {}", principal);
@@ -153,7 +148,6 @@ public final class DAORealmHelper {
collection.add(principal, realm); collection.add(principal, realm);
collection.add(user, realm); collection.add(user, realm);
collection.add(groupCollector.collect(principal, groups), realm);
collection.add(MoreObjects.firstNonNull(scope, Scope.empty()), realm); collection.add(MoreObjects.firstNonNull(scope, Scope.empty()), realm);
String creds = credentials; String creds = credentials;
@@ -207,17 +201,17 @@ public final class DAORealmHelper {
return this; return this;
} }
/** // /**
* With groups adds extra groups, besides those which come from the {@link GroupDAO}, to the authentication info. // * With groups adds extra groups, besides those which come from the {@link GroupDAO}, to the authentication info.
* // *
* @param groups extra groups // * @param groups extra groups
* // *
* @return {@code this} // * @return {@code this}
*/ // */
public AuthenticationInfoBuilder withGroups(Iterable<String> groups) { // public AuthenticationInfoBuilder withGroups(Iterable<String> groups) {
this.groups = groups; // this.groups = groups;
return this; // return this;
} // }
/** /**
* Build creates the authentication info from the given information. * Build creates the authentication info from the given information.
@@ -225,7 +219,7 @@ public final class DAORealmHelper {
* @return authentication info * @return authentication info
*/ */
public AuthenticationInfo build() { public AuthenticationInfo build() {
return getAuthenticationInfo(principal, credentials, scope, groups); return getAuthenticationInfo(principal, credentials, scope);
} }
} }

View File

@@ -30,7 +30,7 @@
*/ */
package sonia.scm.security; package sonia.scm.security;
import sonia.scm.group.GroupDAO; import sonia.scm.cache.CacheManager;
import sonia.scm.user.UserDAO; import sonia.scm.user.UserDAO;
import javax.inject.Inject; import javax.inject.Inject;
@@ -45,20 +45,19 @@ public final class DAORealmHelperFactory {
private final LoginAttemptHandler loginAttemptHandler; private final LoginAttemptHandler loginAttemptHandler;
private final UserDAO userDAO; private final UserDAO userDAO;
private final GroupCollector groupCollector; private final CacheManager cacheManager;
/** /**
* Constructs a new instance. * Constructs a new instance.
*
* @param loginAttemptHandler login attempt handler * @param loginAttemptHandler login attempt handler
* @param userDAO user dao * @param userDAO user dao
* @param groupDAO group dao * @param cacheManager
*/ */
@Inject @Inject
public DAORealmHelperFactory(LoginAttemptHandler loginAttemptHandler, UserDAO userDAO, GroupDAO groupDAO) { public DAORealmHelperFactory(LoginAttemptHandler loginAttemptHandler, UserDAO userDAO, CacheManager cacheManager) {
this.loginAttemptHandler = loginAttemptHandler; this.loginAttemptHandler = loginAttemptHandler;
this.userDAO = userDAO; this.userDAO = userDAO;
this.groupCollector = new GroupCollector(groupDAO); this.cacheManager = cacheManager;
} }
/** /**
@@ -69,7 +68,7 @@ public final class DAORealmHelperFactory {
* @return new {@link DAORealmHelper} instance. * @return new {@link DAORealmHelper} instance.
*/ */
public DAORealmHelper create(String realm) { public DAORealmHelper create(String realm) {
return new DAORealmHelper(loginAttemptHandler, userDAO, groupCollector, realm); return new DAORealmHelper(loginAttemptHandler, userDAO, realm);
} }
} }

View File

@@ -1,43 +0,0 @@
package sonia.scm.security;
import com.google.common.collect.ImmutableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.group.Group;
import sonia.scm.group.GroupDAO;
import sonia.scm.group.GroupNames;
/**
* Collect groups for a certain principal.
* <strong>Warning</strong>: The class is only for internal use and should never used directly.
*/
class GroupCollector {
private static final Logger LOG = LoggerFactory.getLogger(GroupCollector.class);
private final GroupDAO groupDAO;
GroupCollector(GroupDAO groupDAO) {
this.groupDAO = groupDAO;
}
GroupNames collect(String principal, Iterable<String> groupNames) {
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
builder.add(GroupNames.AUTHENTICATED);
for (String group : groupNames) {
builder.add(group);
}
for (Group group : groupDAO.getAll()) {
if (group.isMember(principal)) {
builder.add(group.getName());
}
}
GroupNames groups = new GroupNames(builder.build());
LOG.debug("collected following groups for principal {}: {}", principal, groups);
return groups;
}
}

View File

@@ -32,24 +32,15 @@ import com.google.inject.Inject;
import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.subject.SimplePrincipalCollection; import org.apache.shiro.subject.SimplePrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.AlreadyExistsException; import sonia.scm.AlreadyExistsException;
import sonia.scm.NotFoundException; import sonia.scm.NotFoundException;
import sonia.scm.group.ExternalGroupNames;
import sonia.scm.group.Group; import sonia.scm.group.Group;
import sonia.scm.group.GroupDAO;
import sonia.scm.group.GroupManager; import sonia.scm.group.GroupManager;
import sonia.scm.plugin.Extension; import sonia.scm.plugin.Extension;
import sonia.scm.user.User; import sonia.scm.user.User;
import sonia.scm.user.UserManager; import sonia.scm.user.UserManager;
import sonia.scm.web.security.AdministrationContext; import sonia.scm.web.security.AdministrationContext;
import java.util.Collection;
import java.util.Collections;
import static java.util.Arrays.asList;
/** /**
* Helper class for syncing realms. The class should simplify the creation of realms, which are syncing authenticated * Helper class for syncing realms. The class should simplify the creation of realms, which are syncing authenticated
* users with the local database. * users with the local database.
@@ -60,12 +51,9 @@ import static java.util.Arrays.asList;
@Extension @Extension
public final class SyncingRealmHelper { public final class SyncingRealmHelper {
private static final Logger LOG = LoggerFactory.getLogger(SyncingRealmHelper.class);
private final AdministrationContext ctx; private final AdministrationContext ctx;
private final UserManager userManager; private final UserManager userManager;
private final GroupManager groupManager; private final GroupManager groupManager;
private final GroupCollector groupCollector;
/** /**
* Constructs a new SyncingRealmHelper. * Constructs a new SyncingRealmHelper.
@@ -73,134 +61,28 @@ public final class SyncingRealmHelper {
* @param ctx administration context * @param ctx administration context
* @param userManager user manager * @param userManager user manager
* @param groupManager group manager * @param groupManager group manager
* @param groupDAO group dao
*/ */
@Inject @Inject
public SyncingRealmHelper(AdministrationContext ctx, UserManager userManager, GroupManager groupManager, GroupDAO groupDAO) { public SyncingRealmHelper(AdministrationContext ctx, UserManager userManager, GroupManager groupManager) {
this.ctx = ctx; this.ctx = ctx;
this.userManager = userManager; this.userManager = userManager;
this.groupManager = groupManager; this.groupManager = groupManager;
this.groupCollector = new GroupCollector(groupDAO);
} }
/**
* Create {@link AuthenticationInfo} from user and groups.
*/
public AuthenticationInfoBuilder.ForRealm authenticationInfo() {
return new AuthenticationInfoBuilder().new ForRealm();
}
public class AuthenticationInfoBuilder {
private String realm;
private User user;
private Collection<String> groups = Collections.emptySet();
private Collection<String> externalGroups = Collections.emptySet();
private AuthenticationInfo build() {
return SyncingRealmHelper.this.createAuthenticationInfo(realm, user, groups, externalGroups);
}
public class ForRealm {
private ForRealm() {
}
/**
* Sets the realm.
* @param realm name of the realm
*/
public ForUser forRealm(String realm) {
AuthenticationInfoBuilder.this.realm = realm;
return AuthenticationInfoBuilder.this.new ForUser();
}
}
public class ForUser {
private ForUser() {
}
/**
* Sets the user.
* @param user authenticated user
*/
public AuthenticationInfoBuilder.WithGroups andUser(User user) {
AuthenticationInfoBuilder.this.user = user;
return AuthenticationInfoBuilder.this.new WithGroups();
}
}
public class WithGroups {
private WithGroups() {
}
/**
* Set the internal groups for the user.
* @param groups groups of the authenticated user
* @return builder step for groups
*/
public WithGroups withGroups(String... groups) {
return withGroups(asList(groups));
}
/**
* Set the internal groups for the user.
* @param groups groups of the authenticated user
* @return builder step for groups
*/
public WithGroups withGroups(Collection<String> groups) {
AuthenticationInfoBuilder.this.groups = groups;
return this;
}
/**
* Set the external groups for the user.
* @param externalGroups external groups of the authenticated user
* @return builder step for groups
*/
public WithGroups withExternalGroups(String... externalGroups) {
return withExternalGroups(asList(externalGroups));
}
/**
* Set the external groups for the user.
* @param externalGroups external groups of the authenticated user
* @return builder step for groups
*/
public WithGroups withExternalGroups(Collection<String> externalGroups) {
AuthenticationInfoBuilder.this.externalGroups = externalGroups;
return this;
}
/**
* Builds the {@link AuthenticationInfo} from the given options.
*
* @return complete autentication info
*/
public AuthenticationInfo build() {
return AuthenticationInfoBuilder.this.build();
}
}
}
//~--- methods --------------------------------------------------------------
/** /**
* Create {@link AuthenticationInfo} from user and groups. * Create {@link AuthenticationInfo} from user and groups.
* *
* *
* @param realm name of the realm * @param realm name of the realm
* @param user authenticated user * @param user authenticated user
* @param groups groups of the authenticated user
* *
* @return authentication info * @return authentication info
*/ */
private AuthenticationInfo createAuthenticationInfo(String realm, User user, public AuthenticationInfo createAuthenticationInfo(String realm, User user) {
Collection<String> groups, Collection<String> externalGroups) {
SimplePrincipalCollection collection = new SimplePrincipalCollection(); SimplePrincipalCollection collection = new SimplePrincipalCollection();
collection.add(user.getId(), realm); collection.add(user.getId(), realm);
collection.add(user, realm); collection.add(user, realm);
collection.add(groupCollector.collect(user.getId(), groups), realm);
collection.add(new ExternalGroupNames(externalGroups), realm);
return new SimpleAuthenticationInfo(collection, user.getPassword()); return new SimpleAuthenticationInfo(collection, user.getPassword());
} }

View File

@@ -37,14 +37,11 @@ package sonia.scm.util;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.io.Command; import sonia.scm.io.Command;
import sonia.scm.io.CommandResult; import sonia.scm.io.CommandResult;
import sonia.scm.io.SimpleCommand; import sonia.scm.io.SimpleCommand;
import sonia.scm.io.ZipUnArchiver; import sonia.scm.io.ZipUnArchiver;
//~--- JDK imports ------------------------------------------------------------
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.Closeable; import java.io.Closeable;
import java.io.File; import java.io.File;
@@ -55,12 +52,13 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Reader; import java.io.Reader;
import java.io.Writer; import java.io.Writer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
//~--- JDK imports ------------------------------------------------------------
/** /**
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra

View File

@@ -1,16 +1,11 @@
package sonia.scm.web.filter; package sonia.scm.web.filter;
import sonia.scm.Priority;
import sonia.scm.config.ScmConfiguration; import sonia.scm.config.ScmConfiguration;
import sonia.scm.filter.Filters;
import sonia.scm.filter.WebElement;
import sonia.scm.util.HttpUtil; import sonia.scm.util.HttpUtil;
import sonia.scm.web.UserAgent; import sonia.scm.web.UserAgent;
import sonia.scm.web.UserAgentParser; import sonia.scm.web.UserAgentParser;
import sonia.scm.web.WebTokenGenerator; import sonia.scm.web.WebTokenGenerator;
import sonia.scm.web.protocol.HttpProtocolServlet;
import javax.inject.Inject;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@@ -18,14 +13,11 @@ import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.util.Set; import java.util.Set;
@Priority(Filters.PRIORITY_AUTHENTICATION) public class HttpProtocolServletAuthenticationFilterBase extends AuthenticationFilter {
@WebElement(value = HttpProtocolServlet.PATTERN)
public class HttpProtocolServletAuthenticationFilter extends AuthenticationFilter {
private final UserAgentParser userAgentParser; private final UserAgentParser userAgentParser;
@Inject protected HttpProtocolServletAuthenticationFilterBase(
public HttpProtocolServletAuthenticationFilter(
ScmConfiguration configuration, ScmConfiguration configuration,
Set<WebTokenGenerator> tokenGenerators, Set<WebTokenGenerator> tokenGenerators,
UserAgentParser userAgentParser) { UserAgentParser userAgentParser) {

View File

@@ -0,0 +1,32 @@
package sonia.scm.plugin;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
@ExtendWith(MockitoExtension.class)
class AvailablePluginTest {
@Mock
private AvailablePluginDescriptor descriptor;
@Test
void shouldReturnNewPendingPluginOnInstall() {
AvailablePlugin plugin = new AvailablePlugin(descriptor);
assertThat(plugin.isPending()).isFalse();
AvailablePlugin installed = plugin.install();
assertThat(installed.isPending()).isTrue();
}
@Test
void shouldThrowIllegalStateExceptionIfAlreadyPending() {
AvailablePlugin plugin = new AvailablePlugin(descriptor).install();
assertThrows(IllegalStateException.class, () -> plugin.install());
}
}

View File

@@ -85,7 +85,7 @@ public class SmpArchiveTest
public void testExtract() public void testExtract()
throws IOException, ParserConfigurationException, SAXException throws IOException, ParserConfigurationException, SAXException
{ {
File archive = createArchive("sonia.sample", "sample", "1.0"); File archive = createArchive("sonia.sample", "1.0");
File target = tempFolder.newFolder(); File target = tempFolder.newFolder();
IOUtil.mkdirs(target); IOUtil.mkdirs(target);
@@ -112,8 +112,8 @@ public class SmpArchiveTest
@Test @Test
public void testGetPlugin() throws IOException public void testGetPlugin() throws IOException
{ {
File archive = createArchive("sonia.sample", "sample", "1.0"); File archive = createArchive("sonia.sample", "1.0");
Plugin plugin = SmpArchive.create(archive).getPlugin(); InstalledPluginDescriptor plugin = SmpArchive.create(archive).getPlugin();
assertNotNull(plugin); assertNotNull(plugin);
@@ -121,8 +121,7 @@ public class SmpArchiveTest
assertNotNull(info); assertNotNull(info);
assertEquals("sonia.sample", info.getGroupId()); assertEquals("sonia.sample", info.getName());
assertEquals("sample", info.getArtifactId());
assertEquals("1.0", info.getVersion()); assertEquals("1.0", info.getVersion());
} }
@@ -132,22 +131,9 @@ public class SmpArchiveTest
* @throws IOException * @throws IOException
*/ */
@Test(expected = PluginException.class) @Test(expected = PluginException.class)
public void testWithMissingArtifactId() throws IOException public void testWithMissingName() throws IOException
{ {
File archive = createArchive("sonia.sample", null, "1.0"); File archive = createArchive( null, "1.0");
SmpArchive.create(archive).getPlugin();
}
/**
* Method description
*
* @throws IOException
*/
@Test(expected = PluginException.class)
public void testWithMissingGroupId() throws IOException
{
File archive = createArchive(null, "sample", "1.0");
SmpArchive.create(archive).getPlugin(); SmpArchive.create(archive).getPlugin();
} }
@@ -160,7 +146,7 @@ public class SmpArchiveTest
@Test(expected = PluginException.class) @Test(expected = PluginException.class)
public void testWithMissingVersion() throws IOException public void testWithMissingVersion() throws IOException
{ {
File archive = createArchive("sonia.sample", "sample", null); File archive = createArchive("sonia.sample", null);
SmpArchive.create(archive).getPlugin(); SmpArchive.create(archive).getPlugin();
} }
@@ -169,13 +155,12 @@ public class SmpArchiveTest
* Method description * Method description
* *
* *
* @param groupId * @param name
* @param artifactId
* @param version * @param version
* *
* @return * @return
*/ */
private File createArchive(String groupId, String artifactId, String version) private File createArchive(String name, String version)
{ {
File archiveFile; File archiveFile;
@@ -183,7 +168,7 @@ public class SmpArchiveTest
{ {
File descriptor = tempFolder.newFile(); File descriptor = tempFolder.newFile();
writeDescriptor(descriptor, groupId, artifactId, version); writeDescriptor(descriptor, name, version);
archiveFile = tempFolder.newFile(); archiveFile = tempFolder.newFile();
try (ZipOutputStream zos = try (ZipOutputStream zos =
@@ -229,14 +214,13 @@ public class SmpArchiveTest
* *
* *
* @param descriptor * @param descriptor
* @param groupId * @param name
* @param artifactId
* @param version * @param version
* *
* @throws IOException * @throws IOException
*/ */
private void writeDescriptor(File descriptor, String groupId, private void writeDescriptor(File descriptor, String name,
String artifactId, String version) String version)
throws IOException throws IOException
{ {
try try
@@ -252,8 +236,7 @@ public class SmpArchiveTest
writer.writeStartDocument(); writer.writeStartDocument();
writer.writeStartElement("plugin"); writer.writeStartElement("plugin");
writer.writeStartElement("information"); writer.writeStartElement("information");
writeElement(writer, "groupId", groupId); writeElement(writer, "name", name);
writeElement(writer, "artifactId", artifactId);
writeElement(writer, "version", version); writeElement(writer, "version", version);
writer.writeEndElement(); writer.writeEndElement();

View File

@@ -0,0 +1,22 @@
package sonia.scm.repository;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import static org.assertj.core.api.Assertions.assertThat;
class RepositoryTest {
@Test
void shouldCreateNewPermissionOnClone() {
Repository repository = new Repository();
repository.setPermissions(Arrays.asList(new RepositoryPermission("one", "role", false)));
Repository cloned = repository.clone();
cloned.setPermissions(Arrays.asList(new RepositoryPermission("two", "role", false)));
assertThat(repository.getPermissions()).extracting(r -> r.getName()).containsOnly("one");
}
}

View File

@@ -0,0 +1,53 @@
package sonia.scm.repository.api;
import org.junit.jupiter.api.Test;
import java.util.Iterator;
import static org.assertj.core.api.Assertions.assertThat;
class HunkTest {
@Test
void shouldGetComplexHeader() {
String rawHeader = createHunk(2, 3, 4, 5).getRawHeader();
assertThat(rawHeader).isEqualTo("@@ -2,3 +4,5 @@");
}
@Test
void shouldReturnSingleNumberForOne() {
String rawHeader = createHunk(42, 1, 5, 1).getRawHeader();
assertThat(rawHeader).isEqualTo("@@ -42 +5 @@");
}
private Hunk createHunk(int oldStart, int oldLineCount, int newStart, int newLineCount) {
return new Hunk() {
@Override
public int getOldStart() {
return oldStart;
}
@Override
public int getOldLineCount() {
return oldLineCount;
}
@Override
public int getNewStart() {
return newStart;
}
@Override
public int getNewLineCount() {
return newLineCount;
}
@Override
public Iterator<DiffLine> iterator() {
return null;
}
};
}
}

View File

@@ -1,20 +1,16 @@
package sonia.scm.security; package sonia.scm.security;
import com.google.common.collect.ImmutableList;
import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.DisabledAccountException; import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.PrincipalCollection;
import org.junit.Ignore;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.group.Group;
import sonia.scm.group.GroupDAO; import sonia.scm.group.GroupDAO;
import sonia.scm.group.GroupNames;
import sonia.scm.user.User; import sonia.scm.user.User;
import sonia.scm.user.UserDAO; import sonia.scm.user.UserDAO;
@@ -38,7 +34,7 @@ class DAORealmHelperTest {
@BeforeEach @BeforeEach
void setUpObjectUnderTest() { void setUpObjectUnderTest() {
helper = new DAORealmHelper(loginAttemptHandler, userDAO, new GroupCollector(groupDAO), "hitchhiker"); helper = new DAORealmHelper(loginAttemptHandler, userDAO, "hitchhiker");
} }
@Test @Test
@@ -73,29 +69,9 @@ class DAORealmHelperTest {
AuthenticationInfo authenticationInfo = helper.authenticationInfoBuilder("trillian").build(); AuthenticationInfo authenticationInfo = helper.authenticationInfoBuilder("trillian").build();
PrincipalCollection principals = authenticationInfo.getPrincipals(); PrincipalCollection principals = authenticationInfo.getPrincipals();
assertThat(principals.oneByType(User.class)).isSameAs(user); assertThat(principals.oneByType(User.class)).isSameAs(user);
assertThat(principals.oneByType(GroupNames.class)).containsOnly("_authenticated");
assertThat(principals.oneByType(Scope.class)).isEmpty(); assertThat(principals.oneByType(Scope.class)).isEmpty();
} }
@Test
@Ignore
void shouldReturnAuthenticationInfoWithGroups() {
User user = new User("trillian");
when(userDAO.get("trillian")).thenReturn(user);
Group one = new Group("xml", "one", "trillian");
Group two = new Group("xml", "two", "trillian");
Group six = new Group("xml", "six", "dent");
when(groupDAO.getAll()).thenReturn(ImmutableList.of(one, two, six));
AuthenticationInfo authenticationInfo = helper.authenticationInfoBuilder("trillian")
.withGroups(ImmutableList.of("three"))
.build();
PrincipalCollection principals = authenticationInfo.getPrincipals();
assertThat(principals.oneByType(GroupNames.class)).containsOnly("_authenticated", "one", "two", "three");
}
@Test @Test
void shouldReturnAuthenticationInfoWithScope() { void shouldReturnAuthenticationInfoWithScope() {
User user = new User("trillian"); User user = new User("trillian");
@@ -148,7 +124,6 @@ class DAORealmHelperTest {
PrincipalCollection principals = authenticationInfo.getPrincipals(); PrincipalCollection principals = authenticationInfo.getPrincipals();
assertThat(principals.oneByType(User.class)).isSameAs(user); assertThat(principals.oneByType(User.class)).isSameAs(user);
assertThat(principals.oneByType(GroupNames.class)).containsOnly("_authenticated");
assertThat(principals.oneByType(Scope.class)).isEmpty(); assertThat(principals.oneByType(Scope.class)).isEmpty();
assertThat(authenticationInfo.getCredentials()).isNull(); assertThat(authenticationInfo.getCredentials()).isNull();

View File

@@ -1,64 +0,0 @@
package sonia.scm.security;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.group.Group;
import sonia.scm.group.GroupDAO;
import sonia.scm.group.GroupNames;
import java.util.Collections;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class GroupCollectorTest {
@Mock
private GroupDAO groupDAO;
@InjectMocks
private GroupCollector collector;
@Test
void shouldAlwaysReturnAuthenticatedGroup() {
GroupNames groupNames = collector.collect("trillian", Collections.emptySet());
assertThat(groupNames).containsOnly("_authenticated");
}
@Nested
class WithGroupsFromDao {
@BeforeEach
void setUpGroupsDao() {
List<Group> groups = Lists.newArrayList(
new Group("xml", "heartOfGold", "trillian"),
new Group("xml", "g42", "dent", "prefect"),
new Group("xml", "fjordsOfAfrican", "dent", "trillian")
);
when(groupDAO.getAll()).thenReturn(groups);
}
@Test
void shouldReturnGroupsFromDao() {
GroupNames groupNames = collector.collect("trillian", Collections.emptySet());
assertThat(groupNames).contains("_authenticated", "heartOfGold", "fjordsOfAfrican");
}
@Test
void shouldCombineGivenWithDao() {
GroupNames groupNames = collector.collect("trillian", ImmutableList.of("awesome", "incredible"));
assertThat(groupNames).contains("_authenticated", "heartOfGold", "fjordsOfAfrican", "awesome", "incredible");
}
}
}

View File

@@ -36,31 +36,30 @@ package sonia.scm.security;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationInfo;
import org.assertj.core.api.Assertions;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.AlreadyExistsException; import sonia.scm.AlreadyExistsException;
import sonia.scm.group.ExternalGroupNames;
import sonia.scm.group.Group; import sonia.scm.group.Group;
import sonia.scm.group.GroupDAO;
import sonia.scm.group.GroupManager; import sonia.scm.group.GroupManager;
import sonia.scm.group.GroupNames;
import sonia.scm.user.User; import sonia.scm.user.User;
import sonia.scm.user.UserManager; import sonia.scm.user.UserManager;
import sonia.scm.web.security.AdministrationContext; import sonia.scm.web.security.AdministrationContext;
import sonia.scm.web.security.PrivilegedAction; import sonia.scm.web.security.PrivilegedAction;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasItem;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
@@ -78,9 +77,6 @@ public class SyncingRealmHelperTest {
@Mock @Mock
private UserManager userManager; private UserManager userManager;
@Mock
private GroupDAO groupDAO;
private SyncingRealmHelper helper; private SyncingRealmHelper helper;
/** /**
@@ -106,7 +102,7 @@ public class SyncingRealmHelperTest {
} }
}; };
helper = new SyncingRealmHelper(ctx, userManager, groupManager, groupDAO); helper = new SyncingRealmHelper(ctx, userManager, groupManager);
} }
/** /**
@@ -183,67 +179,15 @@ public class SyncingRealmHelperTest {
verify(userManager, times(1)).modify(user); verify(userManager, times(1)).modify(user);
} }
@Test
public void builderShouldSetInternalGroups() {
AuthenticationInfo authenticationInfo = helper
.authenticationInfo()
.forRealm("unit-test")
.andUser(new User("ziltoid"))
.withGroups("internal")
.build();
GroupNames groupNames = authenticationInfo.getPrincipals().oneByType(GroupNames.class);
Assertions.assertThat(groupNames.getCollection()).contains("_authenticated", "internal");
}
@Test
public void builderShouldSetExternalGroups() {
AuthenticationInfo authenticationInfo = helper
.authenticationInfo()
.forRealm("unit-test")
.andUser(new User("ziltoid"))
.withExternalGroups("external")
.build();
ExternalGroupNames groupNames = authenticationInfo.getPrincipals().oneByType(ExternalGroupNames.class);
Assertions.assertThat(groupNames.getCollection()).containsOnly("external");
}
@Test @Test
public void builderShouldSetValues() { public void builderShouldSetValues() {
User user = new User("ziltoid"); User user = new User("ziltoid");
AuthenticationInfo authInfo = helper AuthenticationInfo authInfo = helper.createAuthenticationInfo("unit-test", user);
.authenticationInfo()
.forRealm("unit-test")
.andUser(user)
.build();
assertNotNull(authInfo); assertNotNull(authInfo);
assertEquals("ziltoid", authInfo.getPrincipals().getPrimaryPrincipal()); assertEquals("ziltoid", authInfo.getPrincipals().getPrimaryPrincipal());
assertThat(authInfo.getPrincipals().getRealmNames(), hasItem("unit-test")); assertThat(authInfo.getPrincipals().getRealmNames(), hasItem("unit-test"));
assertEquals(user, authInfo.getPrincipals().oneByType(User.class)); assertEquals(user, authInfo.getPrincipals().oneByType(User.class));
} }
@Test
public void shouldReturnCombinedGroupNames() {
User user = new User("tricia");
List<Group> groups = Lists.newArrayList(new Group("xml", "heartOfGold", "tricia"));
when(groupDAO.getAll()).thenReturn(groups);
AuthenticationInfo authInfo = helper
.authenticationInfo()
.forRealm("unit-test")
.andUser(user)
.withGroups("fjordsOfAfrican")
.withExternalGroups("g42")
.build();
GroupNames groupNames = authInfo.getPrincipals().oneByType(GroupNames.class);
Assertions.assertThat(groupNames).contains("_authenticated", "heartOfGold", "fjordsOfAfrican");
ExternalGroupNames externalGroupNames = authInfo.getPrincipals().oneByType(ExternalGroupNames.class);
Assertions.assertThat(externalGroupNames).contains("g42");
}
} }

View File

@@ -23,7 +23,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class HttpProtocolServletAuthenticationFilterTest { class HttpProtocolServletAuthenticationFilterBaseTest {
private ScmConfiguration configuration = new ScmConfiguration(); private ScmConfiguration configuration = new ScmConfiguration();
@@ -32,7 +32,7 @@ class HttpProtocolServletAuthenticationFilterTest {
@Mock @Mock
private UserAgentParser userAgentParser; private UserAgentParser userAgentParser;
private HttpProtocolServletAuthenticationFilter authenticationFilter; private HttpProtocolServletAuthenticationFilterBase authenticationFilter;
@Mock @Mock
private HttpServletRequest request; private HttpServletRequest request;
@@ -48,7 +48,7 @@ class HttpProtocolServletAuthenticationFilterTest {
@BeforeEach @BeforeEach
void setUpObjectUnderTest() { void setUpObjectUnderTest() {
authenticationFilter = new HttpProtocolServletAuthenticationFilter(configuration, tokenGenerators, userAgentParser); authenticationFilter = new HttpProtocolServletAuthenticationFilterBase(configuration, tokenGenerators, userAgentParser);
} }
@Test @Test

View File

@@ -0,0 +1 @@
mock-maker-inline

View File

@@ -5,6 +5,5 @@
<namespace>space</namespace> <namespace>space</namespace>
<name>existing</name> <name>existing</name>
<public>false</public> <public>false</public>
<archived>false</archived>
<type>xml</type> <type>xml</type>
</repositories> </repositories>

View File

@@ -123,23 +123,6 @@ public class TestData {
; ;
} }
public static void createUserPermission(String username, String roleName, String repositoryType) {
String defaultPermissionUrl = TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType);
LOG.info("create permission with name {} and role {} using the endpoint: {}", username, roleName, defaultPermissionUrl);
given(VndMediaType.REPOSITORY_PERMISSION)
.when()
.content("{\n" +
"\t\"role\": " + roleName + ",\n" +
"\t\"name\": \"" + username + "\",\n" +
"\t\"groupPermission\": false\n" +
"\t\n" +
"}")
.post(defaultPermissionUrl)
.then()
.statusCode(HttpStatus.SC_CREATED)
;
}
public static List<Map> getUserPermissions(String username, String password, String repositoryType) { public static List<Map> getUserPermissions(String username, String password, String repositoryType) {
return callUserPermissions(username, password, repositoryType, HttpStatus.SC_OK) return callUserPermissions(username, password, repositoryType, HttpStatus.SC_OK)
.extract() .extract()
@@ -245,7 +228,6 @@ public class TestData {
.add("contact", "zaphod.beeblebrox@hitchhiker.com") .add("contact", "zaphod.beeblebrox@hitchhiker.com")
.add("description", "Heart of Gold") .add("description", "Heart of Gold")
.add("name", getDefaultRepoName(repositoryType)) .add("name", getDefaultRepoName(repositoryType))
.add("archived", false)
.add("type", repositoryType) .add("type", repositoryType)
.build().toString(); .build().toString();
} }

View File

@@ -10,7 +10,6 @@
</parent> </parent>
<artifactId>scm-git-plugin</artifactId> <artifactId>scm-git-plugin</artifactId>
<name>scm-git-plugin</name>
<packaging>smp</packaging> <packaging>smp</packaging>
<url>https://bitbucket.org/sdorra/scm-manager</url> <url>https://bitbucket.org/sdorra/scm-manager</url>
<description>Plugin for the version control system Git</description> <description>Plugin for the version control system Git</description>

View File

@@ -43,6 +43,7 @@ import org.eclipse.jgit.api.FetchCommand;
import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
@@ -441,7 +442,7 @@ public final class GitUtil
* *
* @return * @return
*/ */
public static String getId(ObjectId objectId) public static String getId(AnyObjectId objectId)
{ {
String id = Util.EMPTY_STRING; String id = Util.EMPTY_STRING;

View File

@@ -0,0 +1,118 @@
package sonia.scm.repository.spi;
import com.google.common.base.Strings;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
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.eclipse.jgit.treewalk.filter.PathFilter;
import sonia.scm.repository.GitUtil;
import sonia.scm.util.Util;
import java.io.IOException;
import java.util.List;
final class Differ implements AutoCloseable {
private final RevWalk walk;
private final TreeWalk treeWalk;
private final RevCommit commit;
private Differ(RevCommit commit, RevWalk walk, TreeWalk treeWalk) {
this.commit = commit;
this.walk = walk;
this.treeWalk = treeWalk;
}
static Diff diff(Repository repository, DiffCommandRequest request) throws IOException {
try (Differ differ = create(repository, request)) {
return differ.diff();
}
}
private static Differ create(Repository repository, DiffCommandRequest request) throws IOException {
RevWalk walk = new RevWalk(repository);
ObjectId revision = repository.resolve(request.getRevision());
RevCommit commit = walk.parseCommit(revision);
walk.markStart(commit);
commit = walk.next();
TreeWalk treeWalk = new TreeWalk(repository);
treeWalk.reset();
treeWalk.setRecursive(true);
if (Util.isNotEmpty(request.getPath()))
{
treeWalk.setFilter(PathFilter.create(request.getPath()));
}
if (!Strings.isNullOrEmpty(request.getAncestorChangeset()))
{
ObjectId otherRevision = repository.resolve(request.getAncestorChangeset());
ObjectId ancestorId = computeCommonAncestor(repository, revision, otherRevision);
RevTree tree = walk.parseCommit(ancestorId).getTree();
treeWalk.addTree(tree);
}
else if (commit.getParentCount() > 0)
{
RevTree tree = commit.getParent(0).getTree();
if (tree != null)
{
treeWalk.addTree(tree);
}
else
{
treeWalk.addTree(new EmptyTreeIterator());
}
}
else
{
treeWalk.addTree(new EmptyTreeIterator());
}
treeWalk.addTree(commit.getTree());
return new Differ(commit, walk, treeWalk);
}
private static ObjectId computeCommonAncestor(org.eclipse.jgit.lib.Repository repository, ObjectId revision1, ObjectId revision2) throws IOException {
return GitUtil.computeCommonAncestor(repository, revision1, revision2);
}
private Diff diff() throws IOException {
List<DiffEntry> entries = DiffEntry.scan(treeWalk);
return new Diff(commit, entries);
}
@Override
public void close() {
GitUtil.release(walk);
GitUtil.release(treeWalk);
}
public static class Diff {
private final RevCommit commit;
private final List<DiffEntry> entries;
private Diff(RevCommit commit, List<DiffEntry> entries) {
this.commit = commit;
this.entries = entries;
}
public RevCommit getCommit() {
return commit;
}
public List<DiffEntry> getEntries() {
return entries;
}
}
}

View File

@@ -0,0 +1,20 @@
package sonia.scm.repository.spi;
public class FileRange {
private final int start;
private final int lineCount;
public FileRange(int start, int lineCount) {
this.start = start;
this.lineCount = lineCount;
}
public int getStart() {
return start;
}
public int getLineCount() {
return lineCount;
}
}

View File

@@ -1,10 +1,10 @@
/** /**
* Copyright (c) 2010, Sebastian Sdorra * Copyright (c) 2010, Sebastian Sdorra
* All rights reserved. * All rights reserved.
* * <p>
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met: * modification, are permitted provided that the following conditions are met:
* * <p>
* 1. Redistributions of source code must retain the above copyright notice, * 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. * this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, * 2. Redistributions in binary form must reproduce the above copyright notice,
@@ -13,7 +13,7 @@
* 3. Neither the name of SCM-Manager; nor the names of its * 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this * contributors may be used to endorse or promote products derived from this
* software without specific prior written permission. * software without specific prior written permission.
* * <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@@ -24,9 +24,8 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * 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 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* * <p>
* http://bitbucket.org/sdorra/scm-manager * http://bitbucket.org/sdorra/scm-manager
*
*/ */
@@ -34,148 +33,41 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Strings;
import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.diff.DiffFormatter;
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 org.eclipse.jgit.treewalk.filter.PathFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.GitUtil;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.util.Util;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.List;
/** /**
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
public class GitDiffCommand extends AbstractGitCommand implements DiffCommand public class GitDiffCommand extends AbstractGitCommand implements DiffCommand {
{
/** GitDiffCommand(GitContext context, Repository repository) {
* the logger for GitDiffCommand
*/
private static final Logger logger =
LoggerFactory.getLogger(GitDiffCommand.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
*
* @param context
* @param repository
*/
public GitDiffCommand(GitContext context, Repository repository)
{
super(context, repository); super(context, repository);
} }
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param request
* @param output
*/
@Override @Override
public void getDiffResult(DiffCommandRequest request, OutputStream output) public void getDiffResult(DiffCommandRequest request, OutputStream output) throws IOException {
{ @SuppressWarnings("squid:S2095") // repository will be closed with the RepositoryService
RevWalk walk = null; org.eclipse.jgit.lib.Repository repository = open();
TreeWalk treeWalk = null; try (DiffFormatter formatter = new DiffFormatter(new BufferedOutputStream(output))) {
DiffFormatter formatter = null; formatter.setRepository(repository);
try Differ.Diff diff = Differ.diff(repository, request);
{
org.eclipse.jgit.lib.Repository gr = open();
walk = new RevWalk(gr); for (DiffEntry e : diff.getEntries()) {
if (!e.getOldId().equals(e.getNewId())) {
ObjectId revision = gr.resolve(request.getRevision());
RevCommit commit = walk.parseCommit(revision);
walk.markStart(commit);
commit = walk.next();
treeWalk = new TreeWalk(gr);
treeWalk.reset();
treeWalk.setRecursive(true);
if (Util.isNotEmpty(request.getPath()))
{
treeWalk.setFilter(PathFilter.create(request.getPath()));
}
if (!Strings.isNullOrEmpty(request.getAncestorChangeset()))
{
ObjectId otherRevision = gr.resolve(request.getAncestorChangeset());
ObjectId ancestorId = computeCommonAncestor(gr, revision, otherRevision);
RevTree tree = walk.parseCommit(ancestorId).getTree();
treeWalk.addTree(tree);
}
else if (commit.getParentCount() > 0)
{
RevTree tree = commit.getParent(0).getTree();
if (tree != null)
{
treeWalk.addTree(tree);
}
else
{
treeWalk.addTree(new EmptyTreeIterator());
}
}
else
{
treeWalk.addTree(new EmptyTreeIterator());
}
treeWalk.addTree(commit.getTree());
formatter = new DiffFormatter(new BufferedOutputStream(output));
formatter.setRepository(gr);
List<DiffEntry> entries = DiffEntry.scan(treeWalk);
for (DiffEntry e : entries)
{
if (!e.getOldId().equals(e.getNewId()))
{
formatter.format(e); formatter.format(e);
} }
} }
formatter.flush(); formatter.flush();
} }
catch (Exception ex)
{
// TODO throw exception
logger.error("could not create diff", ex);
}
finally
{
GitUtil.release(walk);
GitUtil.release(treeWalk);
GitUtil.release(formatter);
}
}
private ObjectId computeCommonAncestor(org.eclipse.jgit.lib.Repository repository, ObjectId revision1, ObjectId revision2) throws IOException {
return GitUtil.computeCommonAncestor(repository, revision1, revision2);
} }
} }

View File

@@ -0,0 +1,107 @@
package sonia.scm.repository.spi;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import sonia.scm.repository.GitUtil;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.Repository;
import sonia.scm.repository.api.DiffFile;
import sonia.scm.repository.api.DiffResult;
import sonia.scm.repository.api.Hunk;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.stream.Collectors;
public class GitDiffResultCommand extends AbstractGitCommand implements DiffResultCommand {
GitDiffResultCommand(GitContext context, Repository repository) {
super(context, repository);
}
public DiffResult getDiffResult(DiffCommandRequest diffCommandRequest) throws IOException {
org.eclipse.jgit.lib.Repository repository = open();
return new GitDiffResult(repository, Differ.diff(repository, diffCommandRequest));
}
private class GitDiffResult implements DiffResult {
private final org.eclipse.jgit.lib.Repository repository;
private final Differ.Diff diff;
private GitDiffResult(org.eclipse.jgit.lib.Repository repository, Differ.Diff diff) {
this.repository = repository;
this.diff = diff;
}
@Override
public String getOldRevision() {
return GitUtil.getId(diff.getCommit().getParent(0).getId());
}
@Override
public String getNewRevision() {
return GitUtil.getId(diff.getCommit().getId());
}
@Override
public Iterator<DiffFile> iterator() {
return diff.getEntries()
.stream()
.map(diffEntry -> new GitDiffFile(repository, diffEntry))
.collect(Collectors.<DiffFile>toList())
.iterator();
}
}
private class GitDiffFile implements DiffFile {
private final org.eclipse.jgit.lib.Repository repository;
private final DiffEntry diffEntry;
private GitDiffFile(org.eclipse.jgit.lib.Repository repository, DiffEntry diffEntry) {
this.repository = repository;
this.diffEntry = diffEntry;
}
@Override
public String getOldRevision() {
return GitUtil.getId(diffEntry.getOldId().toObjectId());
}
@Override
public String getNewRevision() {
return GitUtil.getId(diffEntry.getNewId().toObjectId());
}
@Override
public String getOldPath() {
return diffEntry.getOldPath();
}
@Override
public String getNewPath() {
return diffEntry.getNewPath();
}
@Override
public Iterator<Hunk> iterator() {
String content = format(repository, diffEntry);
GitHunkParser parser = new GitHunkParser();
return parser.parse(content).iterator();
}
private String format(org.eclipse.jgit.lib.Repository repository, DiffEntry entry) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); DiffFormatter formatter = new DiffFormatter(baos)) {
formatter.setRepository(repository);
formatter.format(entry);
return baos.toString();
} catch (IOException ex) {
throw new InternalRepositoryException(GitDiffResultCommand.this.repository, "failed to format diff entry", ex);
}
}
}
}

View File

@@ -0,0 +1,48 @@
package sonia.scm.repository.spi;
import sonia.scm.repository.api.DiffLine;
import sonia.scm.repository.api.Hunk;
import java.util.Iterator;
import java.util.List;
public class GitHunk implements Hunk {
private final FileRange oldFileRange;
private final FileRange newFileRange;
private List<DiffLine> lines;
public GitHunk(FileRange oldFileRange, FileRange newFileRange) {
this.oldFileRange = oldFileRange;
this.newFileRange = newFileRange;
}
@Override
public int getOldStart() {
return oldFileRange.getStart();
}
@Override
public int getOldLineCount() {
return oldFileRange.getLineCount();
}
@Override
public int getNewStart() {
return newFileRange.getStart();
}
@Override
public int getNewLineCount() {
return newFileRange.getLineCount();
}
@Override
public Iterator<DiffLine> iterator() {
return lines.iterator();
}
void setLines(List<DiffLine> lines) {
this.lines = lines;
}
}

View File

@@ -0,0 +1,176 @@
package sonia.scm.repository.spi;
import sonia.scm.repository.api.DiffLine;
import sonia.scm.repository.api.Hunk;
import java.util.ArrayList;
import java.util.List;
import java.util.OptionalInt;
import java.util.Scanner;
import static java.util.OptionalInt.of;
final class GitHunkParser {
private static final int HEADER_PREFIX_LENGTH = "@@ -".length();
private static final int HEADER_SUFFIX_LENGTH = " @@".length();
private GitHunk currentGitHunk = null;
private List<DiffLine> collectedLines = null;
private int oldLineCounter = 0;
private int newLineCounter = 0;
GitHunkParser() {
}
public List<Hunk> parse(String content) {
List<Hunk> hunks = new ArrayList<>();
try (Scanner scanner = new Scanner(content)) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if (line.startsWith("@@")) {
parseHeader(hunks, line);
} else if (currentGitHunk != null) {
parseDiffLine(line);
}
}
}
if (currentGitHunk != null) {
currentGitHunk.setLines(collectedLines);
}
return hunks;
}
private void parseHeader(List<Hunk> hunks, String line) {
if (currentGitHunk != null) {
currentGitHunk.setLines(collectedLines);
}
String hunkHeader = line.substring(HEADER_PREFIX_LENGTH, line.length() - HEADER_SUFFIX_LENGTH);
String[] split = hunkHeader.split("\\s");
FileRange oldFileRange = createFileRange(split[0]);
// TODO merge contains two two block which starts with "-" e.g. -1,3 -2,4 +3,6
// check if it is relevant for our use case
FileRange newFileRange = createFileRange(split[1]);
currentGitHunk = new GitHunk(oldFileRange, newFileRange);
hunks.add(currentGitHunk);
collectedLines = new ArrayList<>();
oldLineCounter = currentGitHunk.getOldStart();
newLineCounter = currentGitHunk.getNewStart();
}
private void parseDiffLine(String line) {
String content = line.substring(1);
switch (line.charAt(0)) {
case ' ':
collectedLines.add(new UnchangedGitDiffLine(newLineCounter, oldLineCounter, content));
++newLineCounter;
++oldLineCounter;
break;
case '+':
collectedLines.add(new AddedGitDiffLine(newLineCounter, content));
++newLineCounter;
break;
case '-':
collectedLines.add(new RemovedGitDiffLine(oldLineCounter, content));
++oldLineCounter;
break;
default:
throw new IllegalStateException("cannot handle diff line: " + line);
}
}
private static class AddedGitDiffLine implements DiffLine {
private final int newLineNumber;
private final String content;
private AddedGitDiffLine(int newLineNumber, String content) {
this.newLineNumber = newLineNumber;
this.content = content;
}
@Override
public OptionalInt getOldLineNumber() {
return OptionalInt.empty();
}
@Override
public OptionalInt getNewLineNumber() {
return of(newLineNumber);
}
@Override
public String getContent() {
return content;
}
}
private static class RemovedGitDiffLine implements DiffLine {
private final int oldLineNumber;
private final String content;
private RemovedGitDiffLine(int oldLineNumber, String content) {
this.oldLineNumber = oldLineNumber;
this.content = content;
}
@Override
public OptionalInt getOldLineNumber() {
return of(oldLineNumber);
}
@Override
public OptionalInt getNewLineNumber() {
return OptionalInt.empty();
}
@Override
public String getContent() {
return content;
}
}
private static class UnchangedGitDiffLine implements DiffLine {
private final int newLineNumber;
private final int oldLineNumber;
private final String content;
private UnchangedGitDiffLine(int newLineNumber, int oldLineNumber, String content) {
this.newLineNumber = newLineNumber;
this.oldLineNumber = oldLineNumber;
this.content = content;
}
@Override
public OptionalInt getOldLineNumber() {
return of(oldLineNumber);
}
@Override
public OptionalInt getNewLineNumber() {
return of(newLineNumber);
}
@Override
public String getContent() {
return content;
}
}
private static FileRange createFileRange(String fileRangeString) {
int start;
int lineCount = 1;
int commaIndex = fileRangeString.indexOf(',');
if (commaIndex > 0) {
start = Integer.parseInt(fileRangeString.substring(0, commaIndex));
lineCount = Integer.parseInt(fileRangeString.substring(commaIndex + 1));
} else {
start = Integer.parseInt(fileRangeString);
}
return new FileRange(start, lineCount);
}
}

View File

@@ -60,6 +60,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
Command.BROWSE, Command.BROWSE,
Command.CAT, Command.CAT,
Command.DIFF, Command.DIFF,
Command.DIFF_RESULT,
Command.LOG, Command.LOG,
Command.TAGS, Command.TAGS,
Command.BRANCHES, Command.BRANCHES,
@@ -168,6 +169,11 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
return new GitDiffCommand(context, repository); return new GitDiffCommand(context, repository);
} }
@Override
public DiffResultCommand getDiffResultCommand() {
return new GitDiffResultCommand(context, repository);
}
/** /**
* Method description * Method description
* *

View File

@@ -46,14 +46,10 @@
<scm-version>2</scm-version> <scm-version>2</scm-version>
<information> <information>
<author>Sebastian Sdorra</author> <displayName>Git</displayName>
<category>Git</category> <author>Cloudogu GmbH</author>
<tags> <category>Source Code Management</category>
<tag>git</tag> <avatarUrl>/images/git-logo.png</avatarUrl>
<tag>scm</tag>
<tag>vcs</tag>
<tag>dvcs</tag>
</tags>
</information> </information>
<conditions> <conditions>

View File

@@ -3,6 +3,7 @@ package sonia.scm.repository.spi;
import org.junit.Test; import org.junit.Test;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -38,7 +39,7 @@ public class GitDiffCommandTest extends AbstractGitCommandTestBase {
"+f\n"; "+f\n";
@Test @Test
public void diffForOneRevisionShouldCreateDiff() { public void diffForOneRevisionShouldCreateDiff() throws IOException {
GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext(), repository); GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext(), repository);
DiffCommandRequest diffCommandRequest = new DiffCommandRequest(); DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
diffCommandRequest.setRevision("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4"); diffCommandRequest.setRevision("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
@@ -48,7 +49,7 @@ public class GitDiffCommandTest extends AbstractGitCommandTestBase {
} }
@Test @Test
public void diffForOneBranchShouldCreateDiff() { public void diffForOneBranchShouldCreateDiff() throws IOException {
GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext(), repository); GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext(), repository);
DiffCommandRequest diffCommandRequest = new DiffCommandRequest(); DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
diffCommandRequest.setRevision("test-branch"); diffCommandRequest.setRevision("test-branch");
@@ -58,7 +59,7 @@ public class GitDiffCommandTest extends AbstractGitCommandTestBase {
} }
@Test @Test
public void diffForPathShouldCreateLimitedDiff() { public void diffForPathShouldCreateLimitedDiff() throws IOException {
GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext(), repository); GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext(), repository);
DiffCommandRequest diffCommandRequest = new DiffCommandRequest(); DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
diffCommandRequest.setRevision("test-branch"); diffCommandRequest.setRevision("test-branch");
@@ -69,7 +70,7 @@ public class GitDiffCommandTest extends AbstractGitCommandTestBase {
} }
@Test @Test
public void diffBetweenTwoBranchesShouldCreateDiff() { public void diffBetweenTwoBranchesShouldCreateDiff() throws IOException {
GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext(), repository); GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext(), repository);
DiffCommandRequest diffCommandRequest = new DiffCommandRequest(); DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
diffCommandRequest.setRevision("master"); diffCommandRequest.setRevision("master");
@@ -80,7 +81,7 @@ public class GitDiffCommandTest extends AbstractGitCommandTestBase {
} }
@Test @Test
public void diffBetweenTwoBranchesForPathShouldCreateLimitedDiff() { public void diffBetweenTwoBranchesForPathShouldCreateLimitedDiff() throws IOException {
GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext(), repository); GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext(), repository);
DiffCommandRequest diffCommandRequest = new DiffCommandRequest(); DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
diffCommandRequest.setRevision("master"); diffCommandRequest.setRevision("master");

View File

@@ -0,0 +1,89 @@
package sonia.scm.repository.spi;
import org.junit.Test;
import sonia.scm.repository.api.DiffFile;
import sonia.scm.repository.api.DiffResult;
import sonia.scm.repository.api.Hunk;
import java.io.IOException;
import java.util.Iterator;
import static org.assertj.core.api.Assertions.assertThat;
public class GitDiffResultCommandTest extends AbstractGitCommandTestBase {
@Test
public void shouldReturnOldAndNewRevision() throws IOException {
DiffResult diffResult = createDiffResult("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
assertThat(diffResult.getNewRevision()).isEqualTo("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
assertThat(diffResult.getOldRevision()).isEqualTo("592d797cd36432e591416e8b2b98154f4f163411");
}
@Test
public void shouldReturnFilePaths() throws IOException {
DiffResult diffResult = createDiffResult("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
Iterator<DiffFile> iterator = diffResult.iterator();
DiffFile a = iterator.next();
assertThat(a.getNewPath()).isEqualTo("a.txt");
assertThat(a.getOldPath()).isEqualTo("a.txt");
DiffFile b = iterator.next();
assertThat(b.getOldPath()).isEqualTo("b.txt");
assertThat(b.getNewPath()).isEqualTo("/dev/null");
}
@Test
public void shouldReturnFileRevisions() throws IOException {
DiffResult diffResult = createDiffResult("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
Iterator<DiffFile> iterator = diffResult.iterator();
DiffFile a = iterator.next();
assertThat(a.getOldRevision()).isEqualTo("78981922613b2afb6025042ff6bd878ac1994e85");
assertThat(a.getNewRevision()).isEqualTo("1dc60c7504f4326bc83b9b628c384ec8d7e57096");
DiffFile b = iterator.next();
assertThat(b.getOldRevision()).isEqualTo("61780798228d17af2d34fce4cfbdf35556832472");
assertThat(b.getNewRevision()).isEqualTo("0000000000000000000000000000000000000000");
}
@Test
public void shouldReturnFileHunks() throws IOException {
DiffResult diffResult = createDiffResult("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
Iterator<DiffFile> iterator = diffResult.iterator();
DiffFile a = iterator.next();
Iterator<Hunk> hunks = a.iterator();
Hunk hunk = hunks.next();
assertThat(hunk.getOldStart()).isEqualTo(1);
assertThat(hunk.getOldLineCount()).isEqualTo(1);
assertThat(hunk.getNewStart()).isEqualTo(1);
assertThat(hunk.getNewLineCount()).isEqualTo(1);
}
@Test
public void shouldReturnFileHunksWithFullFileRange() throws IOException {
DiffResult diffResult = createDiffResult("fcd0ef1831e4002ac43ea539f4094334c79ea9ec");
Iterator<DiffFile> iterator = diffResult.iterator();
DiffFile a = iterator.next();
Iterator<Hunk> hunks = a.iterator();
Hunk hunk = hunks.next();
assertThat(hunk.getOldStart()).isEqualTo(1);
assertThat(hunk.getOldLineCount()).isEqualTo(1);
assertThat(hunk.getNewStart()).isEqualTo(1);
assertThat(hunk.getNewLineCount()).isEqualTo(2);
}
private DiffResult createDiffResult(String s) throws IOException {
GitDiffResultCommand gitDiffResultCommand = new GitDiffResultCommand(createContext(), repository);
DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
diffCommandRequest.setRevision(s);
return gitDiffResultCommand.getDiffResult(diffCommandRequest);
}
}

View File

@@ -0,0 +1,138 @@
package sonia.scm.repository.spi;
import org.junit.jupiter.api.Test;
import sonia.scm.repository.api.DiffLine;
import sonia.scm.repository.api.Hunk;
import java.util.Iterator;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
class GitHunkParserTest {
private static final String DIFF_001 = "diff --git a/a.txt b/a.txt\n" +
"index 7898192..2f8bc28 100644\n" +
"--- a/a.txt\n" +
"+++ b/a.txt\n" +
"@@ -1 +1,2 @@\n" +
" a\n" +
"+added line\n";
private static final String DIFF_002 = "diff --git a/file b/file\n" +
"index 5e89957..e8823e1 100644\n" +
"--- a/file\n" +
"+++ b/file\n" +
"@@ -2,6 +2,9 @@\n" +
" 2\n" +
" 3\n" +
" 4\n" +
"+5\n" +
"+6\n" +
"+7\n" +
" 8\n" +
" 9\n" +
" 10\n" +
"@@ -15,14 +18,13 @@\n" +
" 18\n" +
" 19\n" +
" 20\n" +
"+21\n" +
"+22\n" +
" 23\n" +
" 24\n" +
" 25\n" +
" 26\n" +
" 27\n" +
"-a\n" +
"-b\n" +
"-c\n" +
" 28\n" +
" 29\n" +
" 30";
private static final String DIFF_003 = "diff --git a/a.txt b/a.txt\n" +
"index 7898192..2f8bc28 100644\n" +
"--- a/a.txt\n" +
"+++ b/a.txt\n" +
"@@ -1,2 +1 @@\n" +
" a\n" +
"-removed line\n";
private static final String ILLEGAL_DIFF = "diff --git a/a.txt b/a.txt\n" +
"index 7898192..2f8bc28 100644\n" +
"--- a/a.txt\n" +
"+++ b/a.txt\n" +
"@@ -1,2 +1 @@\n" +
" a\n" +
"~illegal line\n";
@Test
void shouldParseHunks() {
List<Hunk> hunks = new GitHunkParser().parse(DIFF_001);
assertThat(hunks).hasSize(1);
assertHunk(hunks.get(0), 1, 1, 1, 2);
}
@Test
void shouldParseMultipleHunks() {
List<Hunk> hunks = new GitHunkParser().parse(DIFF_002);
assertThat(hunks).hasSize(2);
assertHunk(hunks.get(0), 2, 6, 2, 9);
assertHunk(hunks.get(1), 15, 14, 18, 13);
}
@Test
void shouldParseAddedHunkLines() {
List<Hunk> hunks = new GitHunkParser().parse(DIFF_001);
Hunk hunk = hunks.get(0);
Iterator<DiffLine> lines = hunk.iterator();
DiffLine line1 = lines.next();
assertThat(line1.getOldLineNumber()).hasValue(1);
assertThat(line1.getNewLineNumber()).hasValue(1);
assertThat(line1.getContent()).isEqualTo("a");
DiffLine line2 = lines.next();
assertThat(line2.getOldLineNumber()).isEmpty();
assertThat(line2.getNewLineNumber()).hasValue(2);
assertThat(line2.getContent()).isEqualTo("added line");
}
@Test
void shouldParseRemovedHunkLines() {
List<Hunk> hunks = new GitHunkParser().parse(DIFF_003);
Hunk hunk = hunks.get(0);
Iterator<DiffLine> lines = hunk.iterator();
DiffLine line1 = lines.next();
assertThat(line1.getOldLineNumber()).hasValue(1);
assertThat(line1.getNewLineNumber()).hasValue(1);
assertThat(line1.getContent()).isEqualTo("a");
DiffLine line2 = lines.next();
assertThat(line2.getOldLineNumber()).hasValue(2);
assertThat(line2.getNewLineNumber()).isEmpty();
assertThat(line2.getContent()).isEqualTo("removed line");
}
@Test
void shouldFailForIllegalLine() {
assertThrows(IllegalStateException.class, () -> new GitHunkParser().parse(ILLEGAL_DIFF));
}
private void assertHunk(Hunk hunk, int oldStart, int oldLineCount, int newStart, int newLineCount) {
assertThat(hunk.getOldStart()).isEqualTo(oldStart);
assertThat(hunk.getOldLineCount()).isEqualTo(oldLineCount);
assertThat(hunk.getNewStart()).isEqualTo(newStart);
assertThat(hunk.getNewLineCount()).isEqualTo(newLineCount);
}
}

View File

@@ -10,7 +10,6 @@
</parent> </parent>
<artifactId>scm-hg-plugin</artifactId> <artifactId>scm-hg-plugin</artifactId>
<name>scm-hg-plugin</name>
<packaging>smp</packaging> <packaging>smp</packaging>
<url>https://bitbucket.org/sdorra/scm-manager</url> <url>https://bitbucket.org/sdorra/scm-manager</url>
<description>Plugin for the version control system Mercurial</description> <description>Plugin for the version control system Mercurial</description>

View File

@@ -40,11 +40,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.web.HgUtil; import sonia.scm.web.HgUtil;
//~--- JDK imports ------------------------------------------------------------ import javax.servlet.http.HttpServletRequest;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest; //~--- JDK imports ------------------------------------------------------------
/** /**
* *

View File

@@ -46,15 +46,10 @@ jo
<scm-version>2</scm-version> <scm-version>2</scm-version>
<information> <information>
<author>Sebastian Sdorra</author> <displayName>Mercurial</displayName>
<category>Mercurial</category> <author>Cloudogu GmbH</author>
<tags> <category>Source Code Management</category>
<tag>mercurial</tag> <avatarUrl>/images/hg-logo.png</avatarUrl>
<tag>hg</tag>
<tag>scm</tag>
<tag>vcs</tag>
<tag>dvcs</tag>
</tags>
</information> </information>
<conditions> <conditions>

View File

@@ -6,8 +6,9 @@
<artifactId>scm-plugins</artifactId> <artifactId>scm-plugins</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.0-SNAPSHOT</version>
</parent> </parent>
<groupId>sonia.scm.plugins</groupId>
<artifactId>scm-legacy-plugin</artifactId> <artifactId>scm-legacy-plugin</artifactId>
<description>Support migrated repository urls and v1 passwords</description>
<version>2.0.0-SNAPSHOT</version> <version>2.0.0-SNAPSHOT</version>
<packaging>smp</packaging> <packaging>smp</packaging>
@@ -21,6 +22,7 @@
<version>${servlet.version}</version> <version>${servlet.version}</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>javax.ws.rs</groupId> <groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId> <artifactId>jsr311-api</artifactId>

View File

@@ -0,0 +1,22 @@
package sonia.scm.legacy;
import sonia.scm.Priority;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.filter.Filters;
import sonia.scm.filter.WebElement;
import sonia.scm.web.UserAgentParser;
import sonia.scm.web.WebTokenGenerator;
import sonia.scm.web.filter.HttpProtocolServletAuthenticationFilterBase;
import javax.inject.Inject;
import java.util.Set;
@Priority(Filters.PRIORITY_AUTHENTICATION)
@WebElement(value = "/git/*", morePatterns = {"/hg/*", "/svn/*"})
public class LegacyProtocolServletAuthenticationFilter extends HttpProtocolServletAuthenticationFilterBase {
@Inject
public LegacyProtocolServletAuthenticationFilter(ScmConfiguration configuration, Set<WebTokenGenerator> tokenGenerators, UserAgentParser userAgentParser) {
super(configuration, tokenGenerators, userAgentParser);
}
}

View File

@@ -1,6 +1,6 @@
//@flow //@flow
import React from "react"; import React from "react";
import { withRouter } from "react-router-dom"; import {withRouter} from "react-router-dom";
class DummyComponent extends React.Component<Props, State> { class DummyComponent extends React.Component<Props, State> {
render() { render() {

View File

@@ -1,14 +1,9 @@
// @flow // @flow
import React from "react"; import React from "react";
import { withRouter } from "react-router-dom"; import {withRouter} from "react-router-dom";
import { binder } from "@scm-manager/ui-extensions"; import {binder} from "@scm-manager/ui-extensions";
import { import {apiClient, ErrorBoundary, ErrorNotification, ProtectedRoute} from "@scm-manager/ui-components";
ProtectedRoute,
apiClient,
ErrorNotification,
ErrorBoundary
} from "@scm-manager/ui-components";
import DummyComponent from "./DummyComponent"; import DummyComponent from "./DummyComponent";
import type {Links} from "@scm-manager/ui-types"; import type {Links} from "@scm-manager/ui-types";

View File

@@ -46,7 +46,9 @@
<scm-version>2</scm-version> <scm-version>2</scm-version>
<information> <information>
<author>Sebastian Sdorra</author> <displayName>Legacy</displayName>
<author>Cloudogu GmbH</author>
<category>Legacy Support</category>
</information> </information>
<conditions> <conditions>

View File

@@ -10,7 +10,7 @@ import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryManager;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.*; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)

View File

@@ -10,7 +10,6 @@
</parent> </parent>
<artifactId>scm-svn-plugin</artifactId> <artifactId>scm-svn-plugin</artifactId>
<name>scm-svn-plugin</name>
<packaging>smp</packaging> <packaging>smp</packaging>
<url>https://bitbucket.org/sdorra/scm-manager</url> <url>https://bitbucket.org/sdorra/scm-manager</url>
<description>Plugin for the version control system Subversion</description> <description>Plugin for the version control system Subversion</description>

View File

@@ -46,14 +46,10 @@
<scm-version>2</scm-version> <scm-version>2</scm-version>
<information> <information>
<author>Sebastian Sdorra</author> <displayName>Subversion</displayName>
<category>Subversion</category> <author>Cloudogu GmbH</author>
<tags> <category>Source Code Management</category>
<tag>subversion</tag> <avatarUrl>/images/svn-logo.gif</avatarUrl>
<tag>scm</tag>
<tag>vcs</tag>
<tag>svn</tag>
</tags>
</information> </information>
<conditions> <conditions>

View File

@@ -1,7 +1,7 @@
// @flow // @flow
import React from "react"; import React from "react";
import { AsyncCreatable, Async } from "react-select"; import {Async, AsyncCreatable} from "react-select";
import type { AutocompleteObject, SelectValue } from "@scm-manager/ui-types"; import type {AutocompleteObject, SelectValue} from "@scm-manager/ui-types";
import LabelWithHelpIcon from "./forms/LabelWithHelpIcon"; import LabelWithHelpIcon from "./forms/LabelWithHelpIcon";
type Props = { type Props = {

View File

@@ -25,12 +25,17 @@ const styles = {
}, },
content: { content: {
display: "flex", display: "flex",
flexGrow: 1 flexGrow: 1,
alignItems: "center",
justifyContent: "space-between"
}, },
footer: { footer: {
display: "flex", display: "flex",
marginTop: "auto", marginTop: "auto",
paddingBottom: "1.5rem" paddingBottom: "1.5rem"
},
noBottomMargin: {
marginBottom: "0 !important"
} }
}; };
@@ -38,24 +43,37 @@ type Props = {
title: string, title: string,
description: string, description: string,
avatar: React.Node, avatar: React.Node,
contentRight?: React.Node,
footerLeft: React.Node, footerLeft: React.Node,
footerRight: React.Node, footerRight: React.Node,
link: string, link?: string,
action?: () => void,
// context props // context props
classes: any classes: any
}; };
class CardColumn extends React.Component<Props> { class CardColumn extends React.Component<Props> {
createLink = () => { createLink = () => {
const { link } = this.props; const { link, action } = this.props;
if (link) { if (link) {
return <Link className="overlay-column" to={link} />; return <Link className="overlay-column" to={link} />;
} else if (action) {
return <a className="overlay-column" onClick={e => {e.preventDefault(); action();}} href="#" />;
} }
return null; return null;
}; };
render() { render() {
const { avatar, title, description, footerLeft, footerRight, classes } = this.props; const {
avatar,
title,
description,
contentRight,
footerLeft,
footerRight,
classes
} = this.props;
const link = this.createLink(); const link = this.createLink();
return ( return (
<> <>
@@ -64,16 +82,29 @@ class CardColumn extends React.Component<Props> {
<figure className={classNames(classes.centerImage, "media-left")}> <figure className={classNames(classes.centerImage, "media-left")}>
{avatar} {avatar}
</figure> </figure>
<div className={classNames("media-content", "text-box", classes.flexFullHeight)}> <div
className={classNames(
"media-content",
"text-box",
classes.flexFullHeight
)}
>
<div className={classes.content}> <div className={classes.content}>
<div className="content shorten-text"> <div
className={classNames(
"content",
"shorten-text",
classes.noBottomMargin
)}
>
<p className="is-marginless"> <p className="is-marginless">
<strong>{title}</strong> <strong>{title}</strong>
</p> </p>
<p className="shorten-text">{description}</p> <p className="shorten-text">{description}</p>
</div> </div>
{contentRight && contentRight}
</div> </div>
<div className={classNames(classes.footer, "level")}> <div className={classNames("level", classes.footer)}>
<div className="level-left is-hidden-mobile">{footerLeft}</div> <div className="level-left is-hidden-mobile">{footerLeft}</div>
<div className="level-right is-mobile">{footerRight}</div> <div className="level-right is-mobile">{footerRight}</div>
</div> </div>

View File

@@ -1,7 +1,7 @@
//@flow //@flow
import React from "react"; import React from "react";
import { translate } from "react-i18next"; import {translate} from "react-i18next";
import { BackendError, ForbiddenError, UnauthorizedError } from "./errors"; import {BackendError, ForbiddenError, UnauthorizedError} from "./errors";
import Notification from "./Notification"; import Notification from "./Notification";
import BackendErrorNotification from "./BackendErrorNotification"; import BackendErrorNotification from "./BackendErrorNotification";

View File

@@ -1,6 +1,6 @@
// @flow // @flow
import React from "react"; import React from "react";
import { translate } from "react-i18next"; import {translate} from "react-i18next";
import type AutocompleteProps from "./UserGroupAutocomplete"; import type AutocompleteProps from "./UserGroupAutocomplete";
import UserGroupAutocomplete from "./UserGroupAutocomplete"; import UserGroupAutocomplete from "./UserGroupAutocomplete";

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