mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-02 03:25:56 +01:00
Merge with 2.0.0-m3
This commit is contained in:
@@ -32,6 +32,13 @@
|
|||||||
<artifactId>scm-annotations</artifactId>
|
<artifactId>scm-annotations</artifactId>
|
||||||
<version>2.0.0-SNAPSHOT</version>
|
<version>2.0.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<!-- logging -->
|
<!-- logging -->
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,8 @@ package sonia.scm.api.v2.resources;
|
|||||||
import de.otto.edison.hal.HalRepresentation;
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
import org.mapstruct.Mapping;
|
import org.mapstruct.Mapping;
|
||||||
|
|
||||||
import java.time.Instant;
|
public abstract class BaseMapper<T, D extends HalRepresentation> implements InstantAttributeMapper {
|
||||||
|
|
||||||
public abstract class BaseMapper<T, D extends HalRepresentation> {
|
|
||||||
|
|
||||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||||
public abstract D map(T modelObject);
|
public abstract D map(T modelObject);
|
||||||
|
|
||||||
protected Instant mapTime(Long epochMilli) {
|
|
||||||
return epochMilli == null? null: Instant.ofEpochMilli(epochMilli);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
public interface InstantAttributeMapper {
|
||||||
|
default Instant mapTime(Long epochMilli) {
|
||||||
|
return epochMilli == null? null: Instant.ofEpochMilli(epochMilli);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,9 +33,8 @@ package sonia.scm.plugin;
|
|||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The WebResourceLoader is able to load web resources. The resources are loaded
|
* The WebResourceLoader is able to load web resources. The resources are loaded
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ import sonia.scm.util.ValidationUtil;
|
|||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
import javax.xml.bind.annotation.XmlElement;
|
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -84,12 +83,6 @@ public class Changeset extends BasicPropertiesAware implements ModelObject {
|
|||||||
*/
|
*/
|
||||||
private String id;
|
private String id;
|
||||||
|
|
||||||
/**
|
|
||||||
* List of files changed by this changeset
|
|
||||||
*/
|
|
||||||
@XmlElement(name = "modifications")
|
|
||||||
private Modifications modifications;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* parent changeset ids
|
* parent changeset ids
|
||||||
*/
|
*/
|
||||||
@@ -137,7 +130,6 @@ public class Changeset extends BasicPropertiesAware implements ModelObject {
|
|||||||
&& Objects.equal(parents, other.parents)
|
&& Objects.equal(parents, other.parents)
|
||||||
&& Objects.equal(tags, other.tags)
|
&& Objects.equal(tags, other.tags)
|
||||||
&& Objects.equal(branches, other.branches)
|
&& Objects.equal(branches, other.branches)
|
||||||
&& Objects.equal(modifications, other.modifications)
|
|
||||||
&& Objects.equal(properties, other.properties);
|
&& Objects.equal(properties, other.properties);
|
||||||
//J+
|
//J+
|
||||||
}
|
}
|
||||||
@@ -152,7 +144,7 @@ public class Changeset extends BasicPropertiesAware implements ModelObject {
|
|||||||
public int hashCode()
|
public int hashCode()
|
||||||
{
|
{
|
||||||
return Objects.hashCode(id, date, author, description, parents, tags,
|
return Objects.hashCode(id, date, author, description, parents, tags,
|
||||||
branches, modifications, properties);
|
branches, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -184,11 +176,6 @@ public class Changeset extends BasicPropertiesAware implements ModelObject {
|
|||||||
out.append("branches: ").append(Util.toString(branches)).append("\n");
|
out.append("branches: ").append(Util.toString(branches)).append("\n");
|
||||||
out.append("tags: ").append(Util.toString(tags)).append("\n");
|
out.append("tags: ").append(Util.toString(tags)).append("\n");
|
||||||
|
|
||||||
if (modifications != null)
|
|
||||||
{
|
|
||||||
out.append("modifications: \n").append(modifications);
|
|
||||||
}
|
|
||||||
|
|
||||||
return out.toString();
|
return out.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,21 +272,6 @@ public class Changeset extends BasicPropertiesAware implements ModelObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the file modifications, which was done with this changeset.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return file modifications
|
|
||||||
*/
|
|
||||||
public Modifications getModifications()
|
|
||||||
{
|
|
||||||
if (modifications == null)
|
|
||||||
{
|
|
||||||
modifications = new Modifications();
|
|
||||||
}
|
|
||||||
|
|
||||||
return modifications;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the ids of the parent changesets.
|
* Return the ids of the parent changesets.
|
||||||
@@ -402,17 +374,6 @@ public class Changeset extends BasicPropertiesAware implements ModelObject {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the file modification of the changeset.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param modifications file modifications
|
|
||||||
*/
|
|
||||||
public void setModifications(Modifications modifications)
|
|
||||||
{
|
|
||||||
this.modifications = modifications;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the parents of the changeset.
|
* Sets the parents of the changeset.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -36,15 +36,13 @@ package sonia.scm.repository;
|
|||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringEscapeUtils;
|
import org.apache.commons.lang.StringEscapeUtils;
|
||||||
|
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
@@ -200,4 +198,10 @@ public final class EscapeUtil
|
|||||||
|
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void escape(Modifications modifications) {
|
||||||
|
modifications.setAdded(escapeList(modifications.getAdded()));
|
||||||
|
modifications.setModified(escapeList(modifications.getModified()));
|
||||||
|
modifications.setRemoved(escapeList(modifications.getRemoved()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ public class Modifications implements Serializable
|
|||||||
* Constructs ...
|
* Constructs ...
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public Modifications() {}
|
public Modifications() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs ...
|
* Constructs ...
|
||||||
@@ -218,6 +219,10 @@ public class Modifications implements Serializable
|
|||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRevision() {
|
||||||
|
return revision;
|
||||||
|
}
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
//~--- set methods ----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -253,8 +258,14 @@ public class Modifications implements Serializable
|
|||||||
this.removed = removed;
|
this.removed = removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRevision(String revision) {
|
||||||
|
this.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
|
private String revision;
|
||||||
|
|
||||||
/** list of added files */
|
/** list of added files */
|
||||||
@XmlElement(name = "added")
|
@XmlElement(name = "added")
|
||||||
@XmlElementWrapper(name = "added")
|
@XmlElementWrapper(name = "added")
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import sonia.scm.plugin.ExtensionPoint;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pre processor for {@link Modifications} objects. A pre processor is able to
|
||||||
|
* modify the object before it is delivered to the user interface.
|
||||||
|
*
|
||||||
|
* @author Mohamed Karray
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
@ExtensionPoint
|
||||||
|
public interface ModificationsPreProcessor extends PreProcessor<Modifications> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the given modifications.
|
||||||
|
*
|
||||||
|
* @param modifications modifications to process
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
void process(Modifications modifications);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import sonia.scm.plugin.ExtensionPoint;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This factory create a {@link ModificationsPreProcessor}
|
||||||
|
*
|
||||||
|
* @author Mohamed Karray
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
@ExtensionPoint
|
||||||
|
public interface ModificationsPreProcessorFactory extends PreProcessorFactory<Modifications> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link ModificationsPreProcessor} for the given repository.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param repository repository
|
||||||
|
*
|
||||||
|
* @return {@link ModificationsPreProcessor} for the given repository
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
ModificationsPreProcessor createPreProcessor(Repository repository);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -36,17 +36,15 @@ package sonia.scm.repository;
|
|||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
@@ -73,14 +71,18 @@ public class PreProcessorUtil
|
|||||||
* @param fileObjectPreProcessorFactorySet
|
* @param fileObjectPreProcessorFactorySet
|
||||||
* @param blameLinePreProcessorSet
|
* @param blameLinePreProcessorSet
|
||||||
* @param blameLinePreProcessorFactorySet
|
* @param blameLinePreProcessorFactorySet
|
||||||
|
* @param modificationsPreProcessorFactorySet
|
||||||
|
* @param modificationsPreProcessorSet
|
||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
public PreProcessorUtil(Set<ChangesetPreProcessor> changesetPreProcessorSet,
|
public PreProcessorUtil(Set<ChangesetPreProcessor> changesetPreProcessorSet,
|
||||||
Set<ChangesetPreProcessorFactory> changesetPreProcessorFactorySet,
|
Set<ChangesetPreProcessorFactory> changesetPreProcessorFactorySet,
|
||||||
Set<FileObjectPreProcessor> fileObjectPreProcessorSet,
|
Set<FileObjectPreProcessor> fileObjectPreProcessorSet,
|
||||||
Set<FileObjectPreProcessorFactory> fileObjectPreProcessorFactorySet,
|
Set<FileObjectPreProcessorFactory> fileObjectPreProcessorFactorySet,
|
||||||
Set<BlameLinePreProcessor> blameLinePreProcessorSet,
|
Set<BlameLinePreProcessor> blameLinePreProcessorSet,
|
||||||
Set<BlameLinePreProcessorFactory> blameLinePreProcessorFactorySet)
|
Set<BlameLinePreProcessorFactory> blameLinePreProcessorFactorySet,
|
||||||
|
Set<ModificationsPreProcessorFactory> modificationsPreProcessorFactorySet,
|
||||||
|
Set<ModificationsPreProcessor> modificationsPreProcessorSet)
|
||||||
{
|
{
|
||||||
this.changesetPreProcessorSet = changesetPreProcessorSet;
|
this.changesetPreProcessorSet = changesetPreProcessorSet;
|
||||||
this.changesetPreProcessorFactorySet = changesetPreProcessorFactorySet;
|
this.changesetPreProcessorFactorySet = changesetPreProcessorFactorySet;
|
||||||
@@ -88,6 +90,8 @@ public class PreProcessorUtil
|
|||||||
this.fileObjectPreProcessorFactorySet = fileObjectPreProcessorFactorySet;
|
this.fileObjectPreProcessorFactorySet = fileObjectPreProcessorFactorySet;
|
||||||
this.blameLinePreProcessorSet = blameLinePreProcessorSet;
|
this.blameLinePreProcessorSet = blameLinePreProcessorSet;
|
||||||
this.blameLinePreProcessorFactorySet = blameLinePreProcessorFactorySet;
|
this.blameLinePreProcessorFactorySet = blameLinePreProcessorFactorySet;
|
||||||
|
this.modificationsPreProcessorFactorySet = modificationsPreProcessorFactorySet;
|
||||||
|
this.modificationsPreProcessorSet = modificationsPreProcessorSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
@@ -108,13 +112,7 @@ public class PreProcessorUtil
|
|||||||
}
|
}
|
||||||
|
|
||||||
EscapeUtil.escape(blameLine);
|
EscapeUtil.escape(blameLine);
|
||||||
|
handlePreProcess(repository,blameLine,blameLinePreProcessorFactorySet, blameLinePreProcessorSet);
|
||||||
PreProcessorHandler<BlameLine> handler =
|
|
||||||
new PreProcessorHandler<BlameLine>(blameLinePreProcessorFactorySet,
|
|
||||||
blameLinePreProcessorSet, repository);
|
|
||||||
|
|
||||||
handler.callPreProcessors(blameLine);
|
|
||||||
handler.callPreProcessorFactories(blameLine);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,13 +150,7 @@ public class PreProcessorUtil
|
|||||||
{
|
{
|
||||||
EscapeUtil.escape(blameResult);
|
EscapeUtil.escape(blameResult);
|
||||||
}
|
}
|
||||||
|
handlePreProcessForIterable(repository, blameResult.getBlameLines(),blameLinePreProcessorFactorySet, blameLinePreProcessorSet);
|
||||||
PreProcessorHandler<BlameLine> handler =
|
|
||||||
new PreProcessorHandler<BlameLine>(blameLinePreProcessorFactorySet,
|
|
||||||
blameLinePreProcessorSet, repository);
|
|
||||||
|
|
||||||
handler.callPreProcessors(blameResult.getBlameLines());
|
|
||||||
handler.callPreProcessorFactories(blameResult.getBlameLines());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -183,26 +175,20 @@ public class PreProcessorUtil
|
|||||||
*
|
*
|
||||||
* @since 1.35
|
* @since 1.35
|
||||||
*/
|
*/
|
||||||
public void prepareForReturn(Repository repository, Changeset changeset,
|
public void prepareForReturn(Repository repository, Changeset changeset, boolean escape){
|
||||||
boolean escape)
|
logger.trace("prepare changeset {} of repository {} for return", changeset.getId(), repository.getName());
|
||||||
{
|
if (escape) {
|
||||||
if (logger.isTraceEnabled())
|
|
||||||
{
|
|
||||||
logger.trace("prepare changeset {} of repository {} for return",
|
|
||||||
changeset.getId(), repository.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (escape)
|
|
||||||
{
|
|
||||||
EscapeUtil.escape(changeset);
|
EscapeUtil.escape(changeset);
|
||||||
}
|
}
|
||||||
|
handlePreProcess(repository, changeset, changesetPreProcessorFactorySet, changesetPreProcessorSet);
|
||||||
|
}
|
||||||
|
|
||||||
PreProcessorHandler<Changeset> handler =
|
public void prepareForReturn(Repository repository, Modifications modifications, boolean escape) {
|
||||||
new PreProcessorHandler<Changeset>(changesetPreProcessorFactorySet,
|
logger.trace("prepare modifications {} of repository {} for return", modifications, repository.getName());
|
||||||
changesetPreProcessorSet, repository);
|
if (escape) {
|
||||||
|
EscapeUtil.escape(modifications);
|
||||||
handler.callPreProcessors(changeset);
|
}
|
||||||
handler.callPreProcessorFactories(changeset);
|
handlePreProcess(repository, modifications, modificationsPreProcessorFactorySet, modificationsPreProcessorSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -240,13 +226,7 @@ public class PreProcessorUtil
|
|||||||
{
|
{
|
||||||
EscapeUtil.escape(result);
|
EscapeUtil.escape(result);
|
||||||
}
|
}
|
||||||
|
handlePreProcessForIterable(repository, result,fileObjectPreProcessorFactorySet, fileObjectPreProcessorSet);
|
||||||
PreProcessorHandler<FileObject> handler =
|
|
||||||
new PreProcessorHandler<FileObject>(fileObjectPreProcessorFactorySet,
|
|
||||||
fileObjectPreProcessorSet, repository);
|
|
||||||
|
|
||||||
handler.callPreProcessors(result);
|
|
||||||
handler.callPreProcessorFactories(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -272,13 +252,7 @@ public class PreProcessorUtil
|
|||||||
{
|
{
|
||||||
EscapeUtil.escape(result);
|
EscapeUtil.escape(result);
|
||||||
}
|
}
|
||||||
|
handlePreProcessForIterable(repository,result,changesetPreProcessorFactorySet, changesetPreProcessorSet);
|
||||||
PreProcessorHandler<Changeset> handler =
|
|
||||||
new PreProcessorHandler<Changeset>(changesetPreProcessorFactorySet,
|
|
||||||
changesetPreProcessorSet, repository);
|
|
||||||
|
|
||||||
handler.callPreProcessors(result);
|
|
||||||
handler.callPreProcessorFactories(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -294,6 +268,23 @@ public class PreProcessorUtil
|
|||||||
prepareForReturn(repository, result, true);
|
prepareForReturn(repository, result, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private <T, F extends PreProcessorFactory<T>, P extends PreProcessor<T>> void handlePreProcess(Repository repository, T processedObject,
|
||||||
|
Collection<F> factories,
|
||||||
|
Collection<P> preProcessors) {
|
||||||
|
PreProcessorHandler<T> handler = new PreProcessorHandler<T>(factories, preProcessors, repository);
|
||||||
|
handler.callPreProcessors(processedObject);
|
||||||
|
handler.callPreProcessorFactories(processedObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T, I extends Iterable<T>, F extends PreProcessorFactory<T>, P extends PreProcessor<T>> void handlePreProcessForIterable(Repository repository, I processedObjects,
|
||||||
|
Collection<F> factories,
|
||||||
|
Collection<P> preProcessors) {
|
||||||
|
PreProcessorHandler<T> handler = new PreProcessorHandler<T>(factories, preProcessors, repository);
|
||||||
|
handler.callPreProcessors(processedObjects);
|
||||||
|
handler.callPreProcessorFactories(processedObjects);
|
||||||
|
}
|
||||||
|
|
||||||
//~--- inner classes --------------------------------------------------------
|
//~--- inner classes --------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -454,6 +445,10 @@ public class PreProcessorUtil
|
|||||||
/** Field description */
|
/** Field description */
|
||||||
private final Collection<ChangesetPreProcessor> changesetPreProcessorSet;
|
private final Collection<ChangesetPreProcessor> changesetPreProcessorSet;
|
||||||
|
|
||||||
|
private final Collection<ModificationsPreProcessorFactory> modificationsPreProcessorFactorySet;
|
||||||
|
|
||||||
|
private final Collection<ModificationsPreProcessor> modificationsPreProcessorSet;
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private final Collection<FileObjectPreProcessorFactory> fileObjectPreProcessorFactorySet;
|
private final Collection<FileObjectPreProcessorFactory> fileObjectPreProcessorFactorySet;
|
||||||
|
|
||||||
|
|||||||
@@ -61,5 +61,11 @@ public enum Command
|
|||||||
/**
|
/**
|
||||||
* @since 1.43
|
* @since 1.43
|
||||||
*/
|
*/
|
||||||
BUNDLE, UNBUNDLE;
|
BUNDLE, UNBUNDLE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
MODIFICATIONS
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package sonia.scm.repository.api;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import sonia.scm.cache.Cache;
|
||||||
|
import sonia.scm.repository.Modifications;
|
||||||
|
import sonia.scm.repository.PreProcessorUtil;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryCacheKey;
|
||||||
|
import sonia.scm.repository.RevisionNotFoundException;
|
||||||
|
import sonia.scm.repository.spi.ModificationsCommand;
|
||||||
|
import sonia.scm.repository.spi.ModificationsCommandRequest;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the modifications applied to files in a revision.
|
||||||
|
* <p>
|
||||||
|
* Modifications are for example: Add, Update and Delete
|
||||||
|
*
|
||||||
|
* @author Mohamed Karray
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Accessors(fluent = true)
|
||||||
|
public final class ModificationsCommandBuilder {
|
||||||
|
static final String CACHE_NAME = "sonia.cache.cmd.modifications";
|
||||||
|
|
||||||
|
private final ModificationsCommand modificationsCommand;
|
||||||
|
|
||||||
|
private final ModificationsCommandRequest request = new ModificationsCommandRequest();
|
||||||
|
|
||||||
|
private final Repository repository;
|
||||||
|
|
||||||
|
private final Cache<ModificationsCommandBuilder.CacheKey, Modifications> cache;
|
||||||
|
|
||||||
|
private final PreProcessorUtil preProcessorUtil;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private boolean disableEscaping = false;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private boolean disableCache = false;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private boolean disablePreProcessors = false;
|
||||||
|
|
||||||
|
public ModificationsCommandBuilder revision(String revision){
|
||||||
|
request.setRevision(revision);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset each parameter to its default value.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public ModificationsCommandBuilder reset() {
|
||||||
|
request.reset();
|
||||||
|
this.disableCache = false;
|
||||||
|
this.disablePreProcessors = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Modifications getModifications() throws IOException, RevisionNotFoundException {
|
||||||
|
Modifications modifications;
|
||||||
|
if (disableCache) {
|
||||||
|
log.info("Get modifications for {} with disabled cache", request);
|
||||||
|
modifications = modificationsCommand.getModifications(request);
|
||||||
|
} else {
|
||||||
|
ModificationsCommandBuilder.CacheKey key = new ModificationsCommandBuilder.CacheKey(repository.getId(), request);
|
||||||
|
if (cache.contains(key)) {
|
||||||
|
modifications = cache.get(key);
|
||||||
|
log.debug("Get modifications for {} from the cache", request);
|
||||||
|
} else {
|
||||||
|
log.info("Get modifications for {} with enabled cache", request);
|
||||||
|
modifications = modificationsCommand.getModifications(request);
|
||||||
|
if (modifications != null) {
|
||||||
|
cache.put(key, modifications);
|
||||||
|
log.debug("Modifications for {} added to the cache with key {}", request, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!disablePreProcessors && (modifications != null)) {
|
||||||
|
preProcessorUtil.prepareForReturn(repository, modifications, !disableEscaping);
|
||||||
|
}
|
||||||
|
return modifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@ToString
|
||||||
|
class CacheKey implements RepositoryCacheKey {
|
||||||
|
private final String repositoryId;
|
||||||
|
private final ModificationsCommandRequest request;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRepositoryId() {
|
||||||
|
return repositoryId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -253,6 +253,18 @@ public final class RepositoryService implements Closeable {
|
|||||||
repository, preProcessorUtil);
|
repository, preProcessorUtil);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The modification command shows file modifications in a revision.
|
||||||
|
*
|
||||||
|
* @return instance of {@link ModificationsCommandBuilder}
|
||||||
|
* @throws CommandNotSupportedException if the command is not supported
|
||||||
|
* by the implementation of the repository service provider.
|
||||||
|
*/
|
||||||
|
public ModificationsCommandBuilder getModificationsCommand() {
|
||||||
|
logger.debug("create modifications command for repository {}",repository.getNamespaceAndName());
|
||||||
|
return new ModificationsCommandBuilder(provider.getModificationsCommand(),repository, cacheManager.getCache(ModificationsCommandBuilder.CACHE_NAME), preProcessorUtil);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The outgoing command show {@link Changeset}s not found in a remote repository.
|
* The outgoing command show {@link Changeset}s not found in a remote repository.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
@@ -29,60 +29,25 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import sonia.scm.repository.Modifications;
|
||||||
|
import sonia.scm.repository.RevisionNotFoundException;
|
||||||
|
|
||||||
package sonia.scm.repository;
|
import java.io.IOException;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import org.tmatesoft.svn.core.SVNException;
|
|
||||||
import org.tmatesoft.svn.core.wc.admin.ISVNChangeEntryHandler;
|
|
||||||
import org.tmatesoft.svn.core.wc.admin.SVNChangeEntry;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Command to get the modifications applied to files in a revision.
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* Modifications are for example: Add, Update, Delete
|
||||||
|
*
|
||||||
|
* @author Mohamed Karray
|
||||||
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public class SvnModificationHandler implements ISVNChangeEntryHandler
|
public interface ModificationsCommand {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
Modifications getModifications(String revision) throws IOException, RevisionNotFoundException;
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param changeset
|
|
||||||
*/
|
|
||||||
public SvnModificationHandler(Changeset changeset)
|
|
||||||
{
|
|
||||||
this.changeset = changeset;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
Modifications getModifications(ModificationsCommandRequest request) throws IOException, RevisionNotFoundException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param entry
|
|
||||||
*
|
|
||||||
* @throws SVNException
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void handleEntry(SVNChangeEntry entry) throws SVNException
|
|
||||||
{
|
|
||||||
Modifications modification = changeset.getModifications();
|
|
||||||
|
|
||||||
if (modification == null)
|
|
||||||
{
|
|
||||||
modification = new Modifications();
|
|
||||||
changeset.setModifications(modification);
|
|
||||||
}
|
|
||||||
|
|
||||||
SvnUtil.appendModification(modification, entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private Changeset changeset;
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@ToString
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ModificationsCommandRequest implements Resetable {
|
||||||
|
private String revision;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
revision = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,6 +42,8 @@ import java.io.IOException;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
@@ -168,6 +170,16 @@ public abstract class RepositoryServiceProvider implements Closeable
|
|||||||
throw new CommandNotSupportedException(Command.LOG);
|
throw new CommandNotSupportedException(Command.LOG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the corresponding {@link ModificationsCommand} implemented from the Plugins
|
||||||
|
*
|
||||||
|
* @return the corresponding {@link ModificationsCommand} implemented from the Plugins
|
||||||
|
* @throws CommandNotSupportedException if there is no Implementation
|
||||||
|
*/
|
||||||
|
public ModificationsCommand getModificationsCommand() {
|
||||||
|
throw new CommandNotSupportedException(Command.MODIFICATIONS);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public class VndMediaType {
|
|||||||
public static final String PERMISSION = PREFIX + "permission" + SUFFIX;
|
public static final String PERMISSION = PREFIX + "permission" + SUFFIX;
|
||||||
public static final String CHANGESET = PREFIX + "changeset" + SUFFIX;
|
public static final String CHANGESET = PREFIX + "changeset" + SUFFIX;
|
||||||
public static final String CHANGESET_COLLECTION = PREFIX + "changesetCollection" + SUFFIX;
|
public static final String CHANGESET_COLLECTION = PREFIX + "changesetCollection" + SUFFIX;
|
||||||
|
public static final String MODIFICATIONS = PREFIX + "modifications" + SUFFIX;;
|
||||||
public static final String TAG = PREFIX + "tag" + SUFFIX;
|
public static final String TAG = PREFIX + "tag" + SUFFIX;
|
||||||
public static final String TAG_COLLECTION = PREFIX + "tagCollection" + SUFFIX;
|
public static final String TAG_COLLECTION = PREFIX + "tagCollection" + SUFFIX;
|
||||||
public static final String BRANCH = PREFIX + "branch" + SUFFIX;
|
public static final String BRANCH = PREFIX + "branch" + SUFFIX;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package sonia.scm.it;
|
|||||||
import io.restassured.response.ExtractableResponse;
|
import io.restassured.response.ExtractableResponse;
|
||||||
import io.restassured.response.Response;
|
import io.restassured.response.Response;
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
|
import org.assertj.core.util.Lists;
|
||||||
|
import org.assertj.core.util.Maps;
|
||||||
import org.junit.Assume;
|
import org.junit.Assume;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
@@ -17,8 +19,11 @@ import sonia.scm.web.VndMediaType;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static java.lang.Thread.sleep;
|
import static java.lang.Thread.sleep;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
@@ -312,5 +317,163 @@ public class RepositoryAccessITCase {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void shouldFindAddedModifications() throws IOException {
|
||||||
|
RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder);
|
||||||
|
String fileName = "a.txt";
|
||||||
|
Changeset changeset = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "a");
|
||||||
|
String revision = changeset.getId();
|
||||||
|
repositoryGetRequest
|
||||||
|
.usingRepositoryResponse()
|
||||||
|
.requestChangesets()
|
||||||
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
|
.usingChangesetsResponse()
|
||||||
|
.requestModifications(revision)
|
||||||
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
|
.usingModificationsResponse()
|
||||||
|
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
||||||
|
.assertAdded(addedFiles -> assertThat(addedFiles)
|
||||||
|
.hasSize(1)
|
||||||
|
.containsExactly(fileName))
|
||||||
|
.assertRemoved(removedFiles -> assertThat(removedFiles)
|
||||||
|
.hasSize(0))
|
||||||
|
.assertModified(files -> assertThat(files)
|
||||||
|
.hasSize(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void shouldFindRemovedModifications() throws IOException {
|
||||||
|
RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder);
|
||||||
|
String fileName = "a.txt";
|
||||||
|
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "a");
|
||||||
|
Changeset changeset = RepositoryUtil.removeAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName);
|
||||||
|
|
||||||
|
String revision = changeset.getId();
|
||||||
|
repositoryGetRequest
|
||||||
|
.usingRepositoryResponse()
|
||||||
|
.requestChangesets()
|
||||||
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
|
.usingChangesetsResponse()
|
||||||
|
.requestModifications(revision)
|
||||||
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
|
.usingModificationsResponse()
|
||||||
|
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
||||||
|
.assertRemoved(removedFiles -> assertThat(removedFiles)
|
||||||
|
.hasSize(1)
|
||||||
|
.containsExactly(fileName))
|
||||||
|
.assertAdded(addedFiles -> assertThat(addedFiles)
|
||||||
|
.hasSize(0))
|
||||||
|
.assertModified(files -> assertThat(files)
|
||||||
|
.hasSize(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void shouldFindUpdateModifications() throws IOException {
|
||||||
|
RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder);
|
||||||
|
String fileName = "a.txt";
|
||||||
|
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "a");
|
||||||
|
Changeset changeset = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "new Content");
|
||||||
|
|
||||||
|
String revision = changeset.getId();
|
||||||
|
repositoryGetRequest
|
||||||
|
.usingRepositoryResponse()
|
||||||
|
.requestChangesets()
|
||||||
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
|
.usingChangesetsResponse()
|
||||||
|
.requestModifications(revision)
|
||||||
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
|
.usingModificationsResponse()
|
||||||
|
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
||||||
|
.assertModified(modifiedFiles -> assertThat(modifiedFiles)
|
||||||
|
.hasSize(1)
|
||||||
|
.containsExactly(fileName))
|
||||||
|
.assertRemoved(removedFiles -> assertThat(removedFiles)
|
||||||
|
.hasSize(0))
|
||||||
|
.assertAdded(addedFiles -> assertThat(addedFiles)
|
||||||
|
.hasSize(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void shouldFindMultipleModifications() throws IOException {
|
||||||
|
RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder);
|
||||||
|
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "b.txt", "b");
|
||||||
|
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "c.txt", "c");
|
||||||
|
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "d.txt", "d");
|
||||||
|
Map<String, String> addedFiles = new HashMap<String, String>()
|
||||||
|
{{
|
||||||
|
put("a.txt", "bla bla");
|
||||||
|
}};
|
||||||
|
Map<String, String> modifiedFiles = new HashMap<String, String>()
|
||||||
|
{{
|
||||||
|
put("b.txt", "new content");
|
||||||
|
}};
|
||||||
|
ArrayList<String> removedFiles = Lists.newArrayList("c.txt", "d.txt");
|
||||||
|
Changeset changeset = RepositoryUtil.commitMultipleFileModifications(repositoryClient, ADMIN_USERNAME, addedFiles, modifiedFiles, removedFiles);
|
||||||
|
|
||||||
|
String revision = changeset.getId();
|
||||||
|
repositoryGetRequest
|
||||||
|
.usingRepositoryResponse()
|
||||||
|
.requestChangesets()
|
||||||
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
|
.usingChangesetsResponse()
|
||||||
|
.requestModifications(revision)
|
||||||
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
|
.usingModificationsResponse()
|
||||||
|
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
||||||
|
.assertAdded(a -> assertThat(a)
|
||||||
|
.hasSize(1)
|
||||||
|
.containsExactly("a.txt"))
|
||||||
|
.assertModified(m-> assertThat(m)
|
||||||
|
.hasSize(1)
|
||||||
|
.containsExactly("b.txt"))
|
||||||
|
.assertRemoved(r -> assertThat(r)
|
||||||
|
.hasSize(2)
|
||||||
|
.containsExactly("c.txt", "d.txt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void svnShouldCreateOneModificationPerFolder() throws IOException {
|
||||||
|
Assume.assumeThat(repositoryType, equalTo("svn"));
|
||||||
|
RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder);
|
||||||
|
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "bbb/bb/b.txt", "b");
|
||||||
|
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "ccc/cc/c.txt", "c");
|
||||||
|
RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "ddd/dd/d.txt", "d");
|
||||||
|
Map<String, String> addedFiles = new HashMap<String, String>()
|
||||||
|
{{
|
||||||
|
put("aaa/aa/a.txt", "bla bla");
|
||||||
|
}};
|
||||||
|
Map<String, String> modifiedFiles = new HashMap<String, String>()
|
||||||
|
{{
|
||||||
|
put("bbb/bb/b.txt", "new content");
|
||||||
|
}};
|
||||||
|
ArrayList<String> removedFiles = Lists.newArrayList("ccc/cc/c.txt", "ddd/dd/d.txt");
|
||||||
|
Changeset changeset = RepositoryUtil.commitMultipleFileModifications(repositoryClient, ADMIN_USERNAME, addedFiles, modifiedFiles, removedFiles);
|
||||||
|
|
||||||
|
String revision = changeset.getId();
|
||||||
|
repositoryGetRequest
|
||||||
|
.usingRepositoryResponse()
|
||||||
|
.requestChangesets()
|
||||||
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
|
.usingChangesetsResponse()
|
||||||
|
.requestModifications(revision)
|
||||||
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
|
.usingModificationsResponse()
|
||||||
|
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
||||||
|
.assertAdded(a -> assertThat(a)
|
||||||
|
.hasSize(3)
|
||||||
|
.containsExactly("aaa/aa/a.txt", "aaa", "aaa/aa"))
|
||||||
|
.assertModified(m-> assertThat(m)
|
||||||
|
.hasSize(1)
|
||||||
|
.containsExactly("bbb/bb/b.txt"))
|
||||||
|
.assertRemoved(r -> assertThat(r)
|
||||||
|
.hasSize(2)
|
||||||
|
.containsExactly("ccc/cc/c.txt", "ddd/dd/d.txt"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ public class RepositoryRequests {
|
|||||||
return new AppliedGetSourcesRequest(getResponseFromLink(repositoryResponse, "_links.sources.href"));
|
return new AppliedGetSourcesRequest(getResponseFromLink(repositoryResponse, "_links.sources.href"));
|
||||||
}
|
}
|
||||||
|
|
||||||
AppliedGetChangesetsRequest requestChangesets(String fileName) {
|
AppliedGetChangesetsRequest requestChangesets() {
|
||||||
return new AppliedGetChangesetsRequest(getResponseFromLink(repositoryResponse, "_links.changesets.href"));
|
return new AppliedGetChangesetsRequest(getResponseFromLink(repositoryResponse, "_links.changesets.href"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,6 +189,9 @@ public class RepositoryRequests {
|
|||||||
return new AppliedGetDiffRequest(getResponseFromLink(changesetsResponse, "_embedded.changesets.find{it.id=='" + revision + "'}._links.diff.href"));
|
return new AppliedGetDiffRequest(getResponseFromLink(changesetsResponse, "_embedded.changesets.find{it.id=='" + revision + "'}._links.diff.href"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AppliedGetModificationsRequest requestModifications(String revision) {
|
||||||
|
return new AppliedGetModificationsRequest(getResponseFromLink(changesetsResponse, "_embedded.changesets.find{it.id=='" + revision + "'}._links.modifications.href"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppliedGetSourcesRequest extends AppliedGetRequest<AppliedGetSourcesRequest> {
|
class AppliedGetSourcesRequest extends AppliedGetRequest<AppliedGetSourcesRequest> {
|
||||||
@@ -246,4 +249,45 @@ public class RepositoryRequests {
|
|||||||
return new GivenWithUrlAndAuth();
|
return new GivenWithUrlAndAuth();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AppliedGetModificationsRequest extends AppliedGetRequest<AppliedGetModificationsRequest> {
|
||||||
|
public AppliedGetModificationsRequest(Response response) { super(response); }
|
||||||
|
ModificationsResponse usingModificationsResponse() {
|
||||||
|
return new ModificationsResponse(super.response);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModificationsResponse {
|
||||||
|
private Response resource;
|
||||||
|
|
||||||
|
public ModificationsResponse(Response resource) {
|
||||||
|
this.resource = resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModificationsResponse assertRevision(Consumer<String> assertRevision) {
|
||||||
|
String revision = resource.then().extract().path("revision");
|
||||||
|
assertRevision.accept(revision);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModificationsResponse assertAdded(Consumer<List<String>> assertAdded) {
|
||||||
|
List<String > added = resource.then().extract().path("added");
|
||||||
|
assertAdded.accept(added);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModificationsResponse assertRemoved(Consumer<List<String>> assertRemoved) {
|
||||||
|
List<String > removed = resource.then().extract().path("removed");
|
||||||
|
assertRemoved.accept(removed);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModificationsResponse assertModified(Consumer<List<String>> assertModified) {
|
||||||
|
List<String > modified = resource.then().extract().path("modified");
|
||||||
|
assertModified.accept(modified);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import sonia.scm.repository.client.api.RepositoryClientFactory;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class RepositoryUtil {
|
public class RepositoryUtil {
|
||||||
@@ -42,11 +44,53 @@ public class RepositoryUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Changeset createAndCommitFile(RepositoryClient repositoryClient, String username, String fileName, String content) throws IOException {
|
static Changeset createAndCommitFile(RepositoryClient repositoryClient, String username, String fileName, String content) throws IOException {
|
||||||
|
writeAndAddFile(repositoryClient, fileName, content);
|
||||||
|
return commit(repositoryClient, username, "added " + fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bundle multiple File modification in one changeset
|
||||||
|
*
|
||||||
|
* @param repositoryClient
|
||||||
|
* @param username
|
||||||
|
* @param addedFiles map.key: path of the file, value: the file content
|
||||||
|
* @param modifiedFiles map.key: path of the file, value: the file content
|
||||||
|
* @param removedFiles list of file paths to be removed
|
||||||
|
* @return the changeset with all modifications
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
static Changeset commitMultipleFileModifications(RepositoryClient repositoryClient, String username, Map<String, String> addedFiles, Map<String, String> modifiedFiles, List<String> removedFiles) throws IOException {
|
||||||
|
for (String fileName : addedFiles.keySet()) {
|
||||||
|
writeAndAddFile(repositoryClient, fileName, addedFiles.get(fileName));
|
||||||
|
}
|
||||||
|
for (String fileName : modifiedFiles.keySet()) {
|
||||||
|
writeAndAddFile(repositoryClient, fileName, modifiedFiles.get(fileName));
|
||||||
|
}
|
||||||
|
for (String fileName : removedFiles) {
|
||||||
|
deleteFileAndApplyRemoveCommand(repositoryClient, fileName);
|
||||||
|
}
|
||||||
|
return commit(repositoryClient, username, "multiple file modifications" );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File writeAndAddFile(RepositoryClient repositoryClient, String fileName, String content) throws IOException {
|
||||||
File file = new File(repositoryClient.getWorkingCopy(), fileName);
|
File file = new File(repositoryClient.getWorkingCopy(), fileName);
|
||||||
Files.createParentDirs(file);
|
Files.createParentDirs(file);
|
||||||
Files.write(content, file, Charsets.UTF_8);
|
Files.write(content, file, Charsets.UTF_8);
|
||||||
addWithParentDirectories(repositoryClient, file);
|
addWithParentDirectories(repositoryClient, file);
|
||||||
return commit(repositoryClient, username, "added " + fileName);
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Changeset removeAndCommitFile(RepositoryClient repositoryClient, String username, String fileName) throws IOException {
|
||||||
|
deleteFileAndApplyRemoveCommand(repositoryClient, fileName);
|
||||||
|
return commit(repositoryClient, username, "removed " + fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void deleteFileAndApplyRemoveCommand(RepositoryClient repositoryClient, String fileName) throws IOException {
|
||||||
|
File file = new File(repositoryClient.getWorkingCopy(), fileName);
|
||||||
|
if (repositoryClient.isCommandSupported(ClientCommand.REMOVE)) {
|
||||||
|
repositoryClient.getRemoveCommand().remove(fileName);
|
||||||
|
}
|
||||||
|
file.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String addWithParentDirectories(RepositoryClient repositoryClient, File file) throws IOException {
|
private static String addWithParentDirectories(RepositoryClient repositoryClient, File file) throws IOException {
|
||||||
|
|||||||
@@ -37,32 +37,25 @@ package sonia.scm.repository;
|
|||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
|
|
||||||
import org.eclipse.jgit.diff.DiffEntry;
|
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
import org.eclipse.jgit.lib.Ref;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevTree;
|
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
|
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
@@ -224,13 +217,6 @@ public class GitChangesetConverter implements Closeable
|
|||||||
changeset.setParents(parentList);
|
changeset.setParents(parentList);
|
||||||
}
|
}
|
||||||
|
|
||||||
Modifications modifications = createModifications(treeWalk, commit);
|
|
||||||
|
|
||||||
if (modifications != null)
|
|
||||||
{
|
|
||||||
changeset.setModifications(modifications);
|
|
||||||
}
|
|
||||||
|
|
||||||
Collection<String> tagCollection = tags.get(commit.getId());
|
Collection<String> tagCollection = tags.get(commit.getId());
|
||||||
|
|
||||||
if (Util.isNotEmpty(tagCollection))
|
if (Util.isNotEmpty(tagCollection))
|
||||||
@@ -245,108 +231,7 @@ public class GitChangesetConverter implements Closeable
|
|||||||
return changeset;
|
return changeset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: copy and rename
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param modifications
|
|
||||||
* @param entry
|
|
||||||
*/
|
|
||||||
private void appendModification(Modifications modifications, DiffEntry entry)
|
|
||||||
{
|
|
||||||
switch (entry.getChangeType())
|
|
||||||
{
|
|
||||||
case ADD :
|
|
||||||
modifications.getAdded().add(entry.getNewPath());
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MODIFY :
|
|
||||||
modifications.getModified().add(entry.getNewPath());
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DELETE :
|
|
||||||
modifications.getRemoved().add(entry.getOldPath());
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param treeWalk
|
|
||||||
* @param commit
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
private Modifications createModifications(TreeWalk treeWalk, RevCommit commit)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
Modifications modifications = null;
|
|
||||||
|
|
||||||
treeWalk.reset();
|
|
||||||
treeWalk.setRecursive(true);
|
|
||||||
|
|
||||||
if (commit.getParentCount() > 0)
|
|
||||||
{
|
|
||||||
RevCommit parent = commit.getParent(0);
|
|
||||||
RevTree tree = parent.getTree();
|
|
||||||
|
|
||||||
if ((tree == null) && (revWalk != null))
|
|
||||||
{
|
|
||||||
revWalk.parseHeaders(parent);
|
|
||||||
tree = parent.getTree();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tree != null)
|
|
||||||
{
|
|
||||||
treeWalk.addTree(tree);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (logger.isTraceEnabled())
|
|
||||||
{
|
|
||||||
logger.trace("no parent tree at position 0 for commit {}",
|
|
||||||
commit.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
treeWalk.addTree(new EmptyTreeIterator());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (logger.isTraceEnabled())
|
|
||||||
{
|
|
||||||
logger.trace("no parent available for commit {}", commit.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
treeWalk.addTree(new EmptyTreeIterator());
|
|
||||||
}
|
|
||||||
|
|
||||||
treeWalk.addTree(commit.getTree());
|
|
||||||
|
|
||||||
List<DiffEntry> entries = DiffEntry.scan(treeWalk);
|
|
||||||
|
|
||||||
for (DiffEntry e : entries)
|
|
||||||
{
|
|
||||||
if (!e.getOldId().equals(e.getNewId()))
|
|
||||||
{
|
|
||||||
if (modifications == null)
|
|
||||||
{
|
|
||||||
modifications = new Modifications();
|
|
||||||
}
|
|
||||||
|
|
||||||
appendModification(modifications, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return modifications;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.eclipse.jgit.diff.DiffEntry;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.eclipse.jgit.revwalk.RevTree;
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
|
||||||
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
|
import sonia.scm.repository.GitUtil;
|
||||||
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
|
import sonia.scm.repository.Modifications;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class GitModificationsCommand extends AbstractGitCommand implements ModificationsCommand {
|
||||||
|
|
||||||
|
protected GitModificationsCommand(GitContext context, Repository repository) {
|
||||||
|
super(context, repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Modifications createModifications(TreeWalk treeWalk, RevCommit commit, RevWalk revWalk, String revision)
|
||||||
|
throws IOException, UnsupportedModificationTypeException {
|
||||||
|
treeWalk.reset();
|
||||||
|
treeWalk.setRecursive(true);
|
||||||
|
if (commit.getParentCount() > 0) {
|
||||||
|
RevCommit parent = commit.getParent(0);
|
||||||
|
RevTree tree = parent.getTree();
|
||||||
|
if ((tree == null) && (revWalk != null)) {
|
||||||
|
revWalk.parseHeaders(parent);
|
||||||
|
tree = parent.getTree();
|
||||||
|
}
|
||||||
|
if (tree != null) {
|
||||||
|
treeWalk.addTree(tree);
|
||||||
|
} else {
|
||||||
|
log.trace("no parent tree at position 0 for commit {}", commit.getName());
|
||||||
|
treeWalk.addTree(new EmptyTreeIterator());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.trace("no parent available for commit {}", commit.getName());
|
||||||
|
treeWalk.addTree(new EmptyTreeIterator());
|
||||||
|
}
|
||||||
|
treeWalk.addTree(commit.getTree());
|
||||||
|
List<DiffEntry> entries = DiffEntry.scan(treeWalk);
|
||||||
|
Modifications modifications = new Modifications();
|
||||||
|
for (DiffEntry e : entries) {
|
||||||
|
if (!e.getOldId().equals(e.getNewId())) {
|
||||||
|
appendModification(modifications, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modifications.setRevision(revision);
|
||||||
|
return modifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Modifications getModifications(String revision) {
|
||||||
|
org.eclipse.jgit.lib.Repository gitRepository = null;
|
||||||
|
RevWalk revWalk = null;
|
||||||
|
try {
|
||||||
|
gitRepository = open();
|
||||||
|
if (!gitRepository.getAllRefs().isEmpty()) {
|
||||||
|
revWalk = new RevWalk(gitRepository);
|
||||||
|
ObjectId id = GitUtil.getRevisionId(gitRepository, revision);
|
||||||
|
RevCommit commit = revWalk.parseCommit(id);
|
||||||
|
TreeWalk treeWalk = new TreeWalk(gitRepository);
|
||||||
|
return createModifications(treeWalk, commit, revWalk, revision);
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
log.error("could not open repository", ex);
|
||||||
|
throw new InternalRepositoryException(ex);
|
||||||
|
|
||||||
|
} catch (UnsupportedModificationTypeException ex) {
|
||||||
|
log.error("Unsupported modification type", ex);
|
||||||
|
throw new InternalRepositoryException(ex);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
GitUtil.release(revWalk);
|
||||||
|
GitUtil.close(gitRepository);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Modifications getModifications(ModificationsCommandRequest request) {
|
||||||
|
return getModifications(request.getRevision());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendModification(Modifications modifications, DiffEntry entry) throws UnsupportedModificationTypeException {
|
||||||
|
DiffEntry.ChangeType type = entry.getChangeType();
|
||||||
|
if (type == DiffEntry.ChangeType.ADD) {
|
||||||
|
modifications.getAdded().add(entry.getNewPath());
|
||||||
|
} else if (type == DiffEntry.ChangeType.MODIFY) {
|
||||||
|
modifications.getModified().add(entry.getNewPath());
|
||||||
|
} else if (type == DiffEntry.ChangeType.DELETE) {
|
||||||
|
modifications.getRemoved().add(entry.getOldPath());
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedModificationTypeException(MessageFormat.format("The modification type: {0} is not supported.", type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,8 @@ import sonia.scm.repository.api.Command;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
@@ -173,6 +175,11 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
return new GitLogCommand(context, repository);
|
return new GitLogCommand(context, repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModificationsCommand getModificationsCommand() {
|
||||||
|
return new GitModificationsCommand(context,repository);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
|
|
||||||
|
public class UnsupportedModificationTypeException extends InternalRepositoryException {
|
||||||
|
public UnsupportedModificationTypeException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,14 +85,14 @@ public class AbstractRemoteCommandTestBase
|
|||||||
outgoingDirectory = tempFolder.newFile("outgoing");
|
outgoingDirectory = tempFolder.newFile("outgoing");
|
||||||
outgoingDirectory.delete();
|
outgoingDirectory.delete();
|
||||||
|
|
||||||
incomgingRepository = new Repository("1", "git", "space", "incoming");
|
incomingRepository = new Repository("1", "git", "space", "incoming");
|
||||||
outgoingRepository = new Repository("2", "git", "space", "outgoing");
|
outgoingRepository = new Repository("2", "git", "space", "outgoing");
|
||||||
|
|
||||||
incoming = Git.init().setDirectory(incomingDirectory).setBare(false).call();
|
incoming = Git.init().setDirectory(incomingDirectory).setBare(false).call();
|
||||||
outgoing = Git.init().setDirectory(outgoingDirectory).setBare(false).call();
|
outgoing = Git.init().setDirectory(outgoingDirectory).setBare(false).call();
|
||||||
|
|
||||||
handler = mock(GitRepositoryHandler.class);
|
handler = mock(GitRepositoryHandler.class);
|
||||||
when(handler.getDirectory(incomgingRepository)).thenReturn(
|
when(handler.getDirectory(incomingRepository)).thenReturn(
|
||||||
incomingDirectory);
|
incomingDirectory);
|
||||||
when(handler.getDirectory(outgoingRepository)).thenReturn(
|
when(handler.getDirectory(outgoingRepository)).thenReturn(
|
||||||
outgoingDirectory);
|
outgoingDirectory);
|
||||||
@@ -211,7 +211,7 @@ public class AbstractRemoteCommandTestBase
|
|||||||
protected GitRepositoryHandler handler;
|
protected GitRepositoryHandler handler;
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
protected Repository incomgingRepository;
|
protected Repository incomingRepository;
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
protected Git incoming;
|
protected Git incoming;
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ public class GitIncomingCommandTest
|
|||||||
|
|
||||||
commit(outgoing, "added a");
|
commit(outgoing, "added a");
|
||||||
|
|
||||||
GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory), incomgingRepository);
|
GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory), incomingRepository);
|
||||||
PullCommandRequest req = new PullCommandRequest();
|
PullCommandRequest req = new PullCommandRequest();
|
||||||
req.setRemoteRepository(outgoingRepository);
|
req.setRemoteRepository(outgoingRepository);
|
||||||
pull.pull(req);
|
pull.pull(req);
|
||||||
@@ -192,6 +192,6 @@ public class GitIncomingCommandTest
|
|||||||
private GitIncomingCommand createCommand()
|
private GitIncomingCommand createCommand()
|
||||||
{
|
{
|
||||||
return new GitIncomingCommand(handler, new GitContext(incomingDirectory),
|
return new GitIncomingCommand(handler, new GitContext(incomingDirectory),
|
||||||
incomgingRepository);
|
incomingRepository);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,21 +168,23 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase
|
|||||||
Changeset c = command.getChangeset("435df2f061add3589cb3");
|
Changeset c = command.getChangeset("435df2f061add3589cb3");
|
||||||
|
|
||||||
assertNotNull(c);
|
assertNotNull(c);
|
||||||
assertEquals("435df2f061add3589cb326cc64be9b9c3897ceca", c.getId());
|
String revision = "435df2f061add3589cb326cc64be9b9c3897ceca";
|
||||||
|
assertEquals(revision, c.getId());
|
||||||
assertEquals("added a and b files", c.getDescription());
|
assertEquals("added a and b files", c.getDescription());
|
||||||
checkDate(c.getDate());
|
checkDate(c.getDate());
|
||||||
assertEquals("Douglas Adams", c.getAuthor().getName());
|
assertEquals("Douglas Adams", c.getAuthor().getName());
|
||||||
assertEquals("douglas.adams@hitchhiker.com", c.getAuthor().getMail());
|
assertEquals("douglas.adams@hitchhiker.com", c.getAuthor().getMail());
|
||||||
assertEquals("added a and b files", c.getDescription());
|
assertEquals("added a and b files", c.getDescription());
|
||||||
|
|
||||||
Modifications mods = c.getModifications();
|
GitModificationsCommand gitModificationsCommand = new GitModificationsCommand(createContext(), repository);
|
||||||
|
Modifications modifications = gitModificationsCommand.getModifications(revision);
|
||||||
|
|
||||||
assertNotNull(mods);
|
assertNotNull(modifications);
|
||||||
assertTrue("modified list should be empty", mods.getModified().isEmpty());
|
assertTrue("modified list should be empty", modifications.getModified().isEmpty());
|
||||||
assertTrue("removed list should be empty", mods.getRemoved().isEmpty());
|
assertTrue("removed list should be empty", modifications.getRemoved().isEmpty());
|
||||||
assertFalse("added list should not be empty", mods.getAdded().isEmpty());
|
assertFalse("added list should not be empty", modifications.getAdded().isEmpty());
|
||||||
assertEquals(2, mods.getAdded().size());
|
assertEquals(2, modifications.getAdded().size());
|
||||||
assertThat(mods.getAdded(), contains("a.txt", "b.txt"));
|
assertThat(modifications.getAdded(), contains("a.txt", "b.txt"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import sonia.scm.repository.Modifications;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Java6Assertions.assertThat;
|
||||||
|
|
||||||
|
public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
|
||||||
|
|
||||||
|
private GitModificationsCommand incomingModificationsCommand;
|
||||||
|
private GitModificationsCommand outgoingModificationsCommand;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
incomingModificationsCommand = new GitModificationsCommand(new GitContext(incomingDirectory), incomingRepository);
|
||||||
|
outgoingModificationsCommand = new GitModificationsCommand(new GitContext(outgoingDirectory), outgoingRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReadAddedFiles() throws Exception {
|
||||||
|
write(outgoing, outgoingDirectory, "a.txt", "bal bla");
|
||||||
|
RevCommit addedFileCommit = commit(outgoing, "add file");
|
||||||
|
String revision = addedFileCommit.getName();
|
||||||
|
Consumer<Modifications> assertModifications = assertAddedFiles("a.txt");
|
||||||
|
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
||||||
|
pushOutgoingAndPullIncoming();
|
||||||
|
assertModifications.accept(incomingModificationsCommand.getModifications(revision));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReadModifiedFiles() throws Exception {
|
||||||
|
write(outgoing, outgoingDirectory, "a.txt", "bal bla");
|
||||||
|
commit(outgoing, "add file");
|
||||||
|
write(outgoing, outgoingDirectory, "a.txt", "modified content");
|
||||||
|
RevCommit modifiedFileCommit = commit(outgoing, "modify file");
|
||||||
|
String revision = modifiedFileCommit.getName();
|
||||||
|
Consumer<Modifications> assertModifications = assertModifiedFiles("a.txt");
|
||||||
|
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
||||||
|
pushOutgoingAndPullIncoming();
|
||||||
|
assertModifications.accept(incomingModificationsCommand.getModifications(revision));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReadRemovedFiles() throws Exception {
|
||||||
|
String fileName = "a.txt";
|
||||||
|
write(outgoing, outgoingDirectory, fileName, "bal bla");
|
||||||
|
commit(outgoing, "add file");
|
||||||
|
File file = new File(outgoingDirectory, fileName);
|
||||||
|
file.delete();
|
||||||
|
outgoing.rm().addFilepattern(fileName).call();
|
||||||
|
RevCommit removedFileCommit = commit(outgoing, "remove file");
|
||||||
|
String revision = removedFileCommit.getName();
|
||||||
|
Consumer<Modifications> assertModifications = assertRemovedFiles(fileName);
|
||||||
|
pushOutgoingAndPullIncoming();
|
||||||
|
assertModifications.accept(incomingModificationsCommand.getModifications(revision));
|
||||||
|
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
||||||
|
}
|
||||||
|
|
||||||
|
void pushOutgoingAndPullIncoming() throws IOException {
|
||||||
|
GitPushCommand cmd = new GitPushCommand(handler, new GitContext(outgoingDirectory),
|
||||||
|
outgoingRepository);
|
||||||
|
PushCommandRequest request = new PushCommandRequest();
|
||||||
|
request.setRemoteRepository(incomingRepository);
|
||||||
|
cmd.push(request);
|
||||||
|
GitPullCommand pullCommand = new GitPullCommand(handler, new GitContext(incomingDirectory),
|
||||||
|
incomingRepository);
|
||||||
|
PullCommandRequest pullRequest = new PullCommandRequest();
|
||||||
|
pullRequest.setRemoteRepository(incomingRepository);
|
||||||
|
pullCommand.pull(pullRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
Consumer<Modifications> assertRemovedFiles(String fileName) {
|
||||||
|
return (modifications) -> {
|
||||||
|
assertThat(modifications).isNotNull();
|
||||||
|
assertThat(modifications.getAdded())
|
||||||
|
.as("added files modifications")
|
||||||
|
.hasSize(0);
|
||||||
|
assertThat(modifications.getModified())
|
||||||
|
.as("modified files modifications")
|
||||||
|
.hasSize(0);
|
||||||
|
assertThat(modifications.getRemoved())
|
||||||
|
.as("removed files modifications")
|
||||||
|
.hasSize(1)
|
||||||
|
.containsOnly(fileName);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Consumer<Modifications> assertModifiedFiles(String file) {
|
||||||
|
return (modifications) -> {
|
||||||
|
assertThat(modifications).isNotNull();
|
||||||
|
assertThat(modifications.getAdded())
|
||||||
|
.as("added files modifications")
|
||||||
|
.hasSize(0);
|
||||||
|
assertThat(modifications.getModified())
|
||||||
|
.as("modified files modifications")
|
||||||
|
.hasSize(1)
|
||||||
|
.containsOnly(file);
|
||||||
|
assertThat(modifications.getRemoved())
|
||||||
|
.as("removed files modifications")
|
||||||
|
.hasSize(0);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Consumer<Modifications> assertAddedFiles(String file) {
|
||||||
|
return (modifications) -> {
|
||||||
|
assertThat(modifications).isNotNull();
|
||||||
|
assertThat(modifications.getAdded())
|
||||||
|
.as("added files modifications")
|
||||||
|
.hasSize(1)
|
||||||
|
.containsOnly(file);
|
||||||
|
assertThat(modifications.getModified())
|
||||||
|
.as("modified files modifications")
|
||||||
|
.hasSize(0);
|
||||||
|
assertThat(modifications.getRemoved())
|
||||||
|
.as("removed files modifications")
|
||||||
|
.hasSize(0);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,7 +78,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
GitOutgoingCommand cmd = createCommand();
|
GitOutgoingCommand cmd = createCommand();
|
||||||
OutgoingCommandRequest request = new OutgoingCommandRequest();
|
OutgoingCommandRequest request = new OutgoingCommandRequest();
|
||||||
|
|
||||||
request.setRemoteRepository(incomgingRepository);
|
request.setRemoteRepository(incomingRepository);
|
||||||
|
|
||||||
ChangesetPagingResult cpr = cmd.getOutgoingChangesets(request);
|
ChangesetPagingResult cpr = cmd.getOutgoingChangesets(request);
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
* @throws RepositoryException
|
* @throws RepositoryException
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetOutgoingChangesetsWithAllreadyPushedChanges()
|
public void testGetOutgoingChangesetsWithAlreadyPushedChanges()
|
||||||
throws IOException, GitAPIException
|
throws IOException, GitAPIException
|
||||||
{
|
{
|
||||||
write(outgoing, outgoingDirectory, "a.txt", "content of a.txt");
|
write(outgoing, outgoingDirectory, "a.txt", "content of a.txt");
|
||||||
@@ -110,7 +110,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
outgoingRepository);
|
outgoingRepository);
|
||||||
PushCommandRequest req = new PushCommandRequest();
|
PushCommandRequest req = new PushCommandRequest();
|
||||||
|
|
||||||
req.setRemoteRepository(incomgingRepository);
|
req.setRemoteRepository(incomingRepository);
|
||||||
push.push(req);
|
push.push(req);
|
||||||
|
|
||||||
write(outgoing, outgoingDirectory, "b.txt", "content of b.txt");
|
write(outgoing, outgoingDirectory, "b.txt", "content of b.txt");
|
||||||
@@ -120,7 +120,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
GitOutgoingCommand cmd = createCommand();
|
GitOutgoingCommand cmd = createCommand();
|
||||||
OutgoingCommandRequest request = new OutgoingCommandRequest();
|
OutgoingCommandRequest request = new OutgoingCommandRequest();
|
||||||
|
|
||||||
request.setRemoteRepository(incomgingRepository);
|
request.setRemoteRepository(incomingRepository);
|
||||||
|
|
||||||
ChangesetPagingResult cpr = cmd.getOutgoingChangesets(request);
|
ChangesetPagingResult cpr = cmd.getOutgoingChangesets(request);
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
GitOutgoingCommand cmd = createCommand();
|
GitOutgoingCommand cmd = createCommand();
|
||||||
OutgoingCommandRequest request = new OutgoingCommandRequest();
|
OutgoingCommandRequest request = new OutgoingCommandRequest();
|
||||||
|
|
||||||
request.setRemoteRepository(incomgingRepository);
|
request.setRemoteRepository(incomingRepository);
|
||||||
|
|
||||||
ChangesetPagingResult cpr = cmd.getOutgoingChangesets(request);
|
ChangesetPagingResult cpr = cmd.getOutgoingChangesets(request);
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ public class GitPushCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
GitPushCommand cmd = createCommand();
|
GitPushCommand cmd = createCommand();
|
||||||
PushCommandRequest request = new PushCommandRequest();
|
PushCommandRequest request = new PushCommandRequest();
|
||||||
|
|
||||||
request.setRemoteRepository(incomgingRepository);
|
request.setRemoteRepository(incomingRepository);
|
||||||
|
|
||||||
PushResponse response = cmd.push(request);
|
PushResponse response = cmd.push(request);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import sonia.scm.repository.Modifications;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.spi.javahg.HgLogChangesetCommand;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
|
||||||
|
public class HgModificationsCommand extends AbstractCommand implements ModificationsCommand {
|
||||||
|
|
||||||
|
HgModificationsCommand(HgCommandContext context, Repository repository) {
|
||||||
|
super(context, repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Modifications getModifications(String revision) {
|
||||||
|
com.aragost.javahg.Repository repository = open();
|
||||||
|
HgLogChangesetCommand hgLogChangesetCommand = HgLogChangesetCommand.on(repository, getContext().getConfig());
|
||||||
|
int hgRevision = hgLogChangesetCommand.rev(revision).singleRevision();
|
||||||
|
Modifications modifications = hgLogChangesetCommand.rev(MessageFormat.format("{0}:{0}", hgRevision)).extractModifications();
|
||||||
|
modifications.setRevision(revision);
|
||||||
|
return modifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Modifications getModifications(ModificationsCommandRequest request) {
|
||||||
|
return getModifications(request.getRevision());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -39,6 +39,7 @@ import sonia.scm.repository.HgHookManager;
|
|||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.api.Command;
|
import sonia.scm.repository.api.Command;
|
||||||
|
import sonia.scm.repository.api.CommandNotSupportedException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -185,6 +186,16 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
return new HgLogCommand(context, repository);
|
return new HgLogCommand(context, repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the corresponding {@link ModificationsCommand} implemented from the Plugins
|
||||||
|
*
|
||||||
|
* @return the corresponding {@link ModificationsCommand} implemented from the Plugins
|
||||||
|
* @throws CommandNotSupportedException if there is no Implementation
|
||||||
|
*/
|
||||||
|
public ModificationsCommand getModificationsCommand() {
|
||||||
|
return new HgModificationsCommand(context,repository);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -41,21 +41,18 @@ import com.aragost.javahg.internals.AbstractCommand;
|
|||||||
import com.aragost.javahg.internals.HgInputStream;
|
import com.aragost.javahg.internals.HgInputStream;
|
||||||
import com.aragost.javahg.internals.RuntimeIOException;
|
import com.aragost.javahg.internals.RuntimeIOException;
|
||||||
import com.aragost.javahg.internals.Utils;
|
import com.aragost.javahg.internals.Utils;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import sonia.scm.repository.Changeset;
|
import sonia.scm.repository.Changeset;
|
||||||
import sonia.scm.repository.HgConfig;
|
import sonia.scm.repository.HgConfig;
|
||||||
import sonia.scm.repository.Modifications;
|
import sonia.scm.repository.Modifications;
|
||||||
import sonia.scm.repository.Person;
|
import sonia.scm.repository.Person;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
@@ -251,33 +248,14 @@ public abstract class AbstractChangesetCommand extends AbstractCommand
|
|||||||
changeset.getProperties().put(PROPERTY_CLOSE, "true");
|
changeset.getProperties().put(PROPERTY_CLOSE, "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
Modifications modifications = changeset.getModifications();
|
|
||||||
|
|
||||||
String line = in.textUpTo('\n');
|
String line = in.textUpTo('\n');
|
||||||
|
while (line.length() > 0) {
|
||||||
while (line.length() > 0)
|
if (line.startsWith("t "))
|
||||||
{
|
|
||||||
|
|
||||||
if (line.startsWith("a "))
|
|
||||||
{
|
|
||||||
modifications.getAdded().add(line.substring(2));
|
|
||||||
}
|
|
||||||
else if (line.startsWith("m "))
|
|
||||||
{
|
|
||||||
modifications.getModified().add(line.substring(2));
|
|
||||||
}
|
|
||||||
else if (line.startsWith("d "))
|
|
||||||
{
|
|
||||||
modifications.getRemoved().add(line.substring(2));
|
|
||||||
}
|
|
||||||
else if (line.startsWith("t "))
|
|
||||||
{
|
{
|
||||||
changeset.getTags().add(line.substring(2));
|
changeset.getTags().add(line.substring(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
line = in.textUpTo('\n');
|
line = in.textUpTo('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
String message = in.textUpTo('\0');
|
String message = in.textUpTo('\0');
|
||||||
|
|
||||||
changeset.setDescription(message);
|
changeset.setDescription(message);
|
||||||
@@ -285,6 +263,36 @@ public abstract class AbstractChangesetCommand extends AbstractCommand
|
|||||||
return changeset;
|
return changeset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Modifications readModificationsFromStream(HgInputStream in) {
|
||||||
|
try {
|
||||||
|
boolean found = in.find(CHANGESET_PATTERN);
|
||||||
|
if (found) {
|
||||||
|
while (!in.match(CHANGESET_PATTERN)) {
|
||||||
|
return extractModifications(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeIOException(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Modifications extractModifications(HgInputStream in) throws IOException {
|
||||||
|
Modifications modifications = new Modifications();
|
||||||
|
String line = in.textUpTo('\n');
|
||||||
|
while (line.length() > 0) {
|
||||||
|
if (line.startsWith("a ")) {
|
||||||
|
modifications.getAdded().add(line.substring(2));
|
||||||
|
} else if (line.startsWith("m ")) {
|
||||||
|
modifications.getModified().add(line.substring(2));
|
||||||
|
} else if (line.startsWith("d ")) {
|
||||||
|
modifications.getRemoved().add(line.substring(2));
|
||||||
|
}
|
||||||
|
line = in.textUpTo('\n');
|
||||||
|
}
|
||||||
|
return modifications;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ package sonia.scm.repository.spi.javahg;
|
|||||||
import com.aragost.javahg.Repository;
|
import com.aragost.javahg.Repository;
|
||||||
import com.aragost.javahg.internals.HgInputStream;
|
import com.aragost.javahg.internals.HgInputStream;
|
||||||
import com.aragost.javahg.internals.Utils;
|
import com.aragost.javahg.internals.Utils;
|
||||||
|
|
||||||
import sonia.scm.repository.Changeset;
|
import sonia.scm.repository.Changeset;
|
||||||
import sonia.scm.repository.HgConfig;
|
import sonia.scm.repository.HgConfig;
|
||||||
|
import sonia.scm.repository.Modifications;
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
@@ -106,11 +106,22 @@ public class HgLogChangesetCommand extends AbstractChangesetCommand
|
|||||||
*/
|
*/
|
||||||
public List<Changeset> execute(String... files)
|
public List<Changeset> execute(String... files)
|
||||||
{
|
{
|
||||||
cmdAppend("--style", CHANGESET_EAGER_STYLE_PATH);
|
return readListFromStream(getHgInputStream(files, CHANGESET_EAGER_STYLE_PATH));
|
||||||
|
}
|
||||||
|
|
||||||
HgInputStream stream = launchStream(files);
|
/**
|
||||||
|
* Extract Modifications from the Repository files
|
||||||
|
*
|
||||||
|
* @param files repo files
|
||||||
|
* @return modifications
|
||||||
|
*/
|
||||||
|
public Modifications extractModifications(String... files) {
|
||||||
|
return readModificationsFromStream(getHgInputStream(files, CHANGESET_EAGER_STYLE_PATH));
|
||||||
|
}
|
||||||
|
|
||||||
return readListFromStream(stream);
|
HgInputStream getHgInputStream(String[] files, String changesetStylePath) {
|
||||||
|
cmdAppend("--style", changesetStylePath);
|
||||||
|
return launchStream(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,11 +149,7 @@ public class HgLogChangesetCommand extends AbstractChangesetCommand
|
|||||||
*/
|
*/
|
||||||
public List<Integer> loadRevisions(String... files)
|
public List<Integer> loadRevisions(String... files)
|
||||||
{
|
{
|
||||||
cmdAppend("--style", CHANGESET_LAZY_STYLE_PATH);
|
return loadRevisionsFromStream(getHgInputStream(files, CHANGESET_LAZY_STYLE_PATH));
|
||||||
|
|
||||||
HgInputStream stream = launchStream(files);
|
|
||||||
|
|
||||||
return loadRevisionsFromStream(stream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Image } from "@scm-manager/ui-components";
|
import {Image} from "@scm-manager/ui-components";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ package sonia.scm.repository.client.spi;
|
|||||||
|
|
||||||
import com.aragost.javahg.Repository;
|
import com.aragost.javahg.Repository;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import java.io.IOException;
|
|
||||||
import sonia.scm.repository.Changeset;
|
import sonia.scm.repository.Changeset;
|
||||||
import sonia.scm.repository.Modifications;
|
|
||||||
import sonia.scm.repository.Person;
|
import sonia.scm.repository.Person;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mercurial implementation of the {@link CommitCommand}.
|
* Mercurial implementation of the {@link CommitCommand}.
|
||||||
*
|
*
|
||||||
@@ -70,9 +70,6 @@ public class HgCommitCommand implements CommitCommand
|
|||||||
|
|
||||||
changeset.setBranches(Lists.newArrayList(c.getBranch()));
|
changeset.setBranches(Lists.newArrayList(c.getBranch()));
|
||||||
changeset.setTags(c.tags());
|
changeset.setTags(c.tags());
|
||||||
changeset.setModifications(
|
|
||||||
new Modifications(c.getAddedFiles(), c.getModifiedFiles(), c.getDeletedFiles())
|
|
||||||
);
|
|
||||||
return changeset;
|
return changeset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ import org.junit.Test;
|
|||||||
import sonia.scm.repository.Changeset;
|
import sonia.scm.repository.Changeset;
|
||||||
import sonia.scm.repository.ChangesetPagingResult;
|
import sonia.scm.repository.ChangesetPagingResult;
|
||||||
import sonia.scm.repository.Modifications;
|
import sonia.scm.repository.Modifications;
|
||||||
|
import sonia.scm.repository.RevisionNotFoundException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.contains;
|
import static org.hamcrest.Matchers.contains;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -133,27 +136,28 @@ public class HgLogCommandTest extends AbstractHgCommandTestBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCommit() {
|
public void testGetCommit() throws IOException, RevisionNotFoundException {
|
||||||
HgLogCommand command = createComamnd();
|
HgLogCommand command = createComamnd();
|
||||||
|
String revision = "a9bacaf1b7fa0cebfca71fed4e59ed69a6319427";
|
||||||
Changeset c =
|
Changeset c =
|
||||||
command.getChangeset("a9bacaf1b7fa0cebfca71fed4e59ed69a6319427");
|
command.getChangeset(revision);
|
||||||
|
|
||||||
assertNotNull(c);
|
assertNotNull(c);
|
||||||
assertEquals("a9bacaf1b7fa0cebfca71fed4e59ed69a6319427", c.getId());
|
assertEquals(revision, c.getId());
|
||||||
assertEquals("added a and b files", c.getDescription());
|
assertEquals("added a and b files", c.getDescription());
|
||||||
checkDate(c.getDate());
|
checkDate(c.getDate());
|
||||||
assertEquals("Douglas Adams", c.getAuthor().getName());
|
assertEquals("Douglas Adams", c.getAuthor().getName());
|
||||||
assertEquals("douglas.adams@hitchhiker.com", c.getAuthor().getMail());
|
assertEquals("douglas.adams@hitchhiker.com", c.getAuthor().getMail());
|
||||||
assertEquals("added a and b files", c.getDescription());
|
assertEquals("added a and b files", c.getDescription());
|
||||||
|
ModificationsCommand modificationsCommand = new HgModificationsCommand(cmdContext, repository);
|
||||||
|
Modifications modifications = modificationsCommand.getModifications(revision);
|
||||||
|
|
||||||
Modifications mods = c.getModifications();
|
assertNotNull(modifications);
|
||||||
|
assertTrue("modified list should be empty", modifications.getModified().isEmpty());
|
||||||
assertNotNull(mods);
|
assertTrue("removed list should be empty", modifications.getRemoved().isEmpty());
|
||||||
assertTrue("modified list should be empty", mods.getModified().isEmpty());
|
assertFalse("added list should not be empty", modifications.getAdded().isEmpty());
|
||||||
assertTrue("removed list should be empty", mods.getRemoved().isEmpty());
|
assertEquals(2, modifications.getAdded().size());
|
||||||
assertFalse("added list should not be empty", mods.getAdded().isEmpty());
|
assertThat(modifications.getAdded(), contains("a.txt", "b.txt"));
|
||||||
assertEquals(2, mods.getAdded().size());
|
|
||||||
assertThat(mods.getAdded(), contains("a.txt", "b.txt"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import com.aragost.javahg.Changeset;
|
||||||
|
import com.aragost.javahg.commands.RemoveCommand;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import sonia.scm.repository.HgTestUtil;
|
||||||
|
import sonia.scm.repository.Modifications;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Java6Assertions.assertThat;
|
||||||
|
|
||||||
|
public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
||||||
|
|
||||||
|
|
||||||
|
private HgModificationsCommand outgoingModificationsCommand;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
HgCommandContext outgoingContext = new HgCommandContext(HgTestUtil.createHookManager(), handler, outgoingRepository, outgoingDirectory);
|
||||||
|
outgoingModificationsCommand = new HgModificationsCommand(outgoingContext, outgoingRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReadAddedFiles() throws Exception {
|
||||||
|
String fileName = "a.txt";
|
||||||
|
writeNewFile(outgoing, outgoingDirectory, fileName, "bal bla");
|
||||||
|
Changeset changeset = commit(outgoing, "added a.txt");
|
||||||
|
String revision = String.valueOf(changeset.getRevision());
|
||||||
|
Consumer<Modifications> assertModifications = assertAddedFile(fileName);
|
||||||
|
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReadModifiedFiles() throws Exception {
|
||||||
|
String fileName = "a.txt";
|
||||||
|
writeNewFile(outgoing, outgoingDirectory, fileName, "bal bla");
|
||||||
|
commit(outgoing, "added a.txt");
|
||||||
|
writeNewFile(outgoing, outgoingDirectory, fileName, "new content");
|
||||||
|
Changeset changeset = commit(outgoing, "modified a.txt");
|
||||||
|
String revision = String.valueOf(changeset.getRevision());
|
||||||
|
Consumer<Modifications> assertModifications = assertModifiedFiles(fileName);
|
||||||
|
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReadRemovedFiles() throws Exception {
|
||||||
|
String fileName = "a.txt";
|
||||||
|
writeNewFile(outgoing, outgoingDirectory, fileName, "bal bla");
|
||||||
|
commit(outgoing, "added a.txt");
|
||||||
|
File file = new File(outgoingDirectory, fileName);
|
||||||
|
file.delete();
|
||||||
|
RemoveCommand.on(outgoing).execute(file);
|
||||||
|
Changeset changeset = commit(outgoing, "removed a.txt");
|
||||||
|
String revision = String.valueOf(changeset.getRevision());
|
||||||
|
Consumer<Modifications> assertModifications = assertRemovedFiles(fileName);
|
||||||
|
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Consumer<Modifications> assertRemovedFiles(String fileName) {
|
||||||
|
return (modifications) -> {
|
||||||
|
assertThat(modifications).isNotNull();
|
||||||
|
assertThat(modifications.getAdded())
|
||||||
|
.as("added files modifications")
|
||||||
|
.hasSize(0);
|
||||||
|
assertThat(modifications.getModified())
|
||||||
|
.as("modified files modifications")
|
||||||
|
.hasSize(0);
|
||||||
|
assertThat(modifications.getRemoved())
|
||||||
|
.as("removed files modifications")
|
||||||
|
.hasSize(1)
|
||||||
|
.containsOnly(fileName);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Consumer<Modifications> assertModifiedFiles(String file) {
|
||||||
|
return (modifications) -> {
|
||||||
|
assertThat(modifications).isNotNull();
|
||||||
|
assertThat(modifications.getAdded())
|
||||||
|
.as("added files modifications")
|
||||||
|
.hasSize(0);
|
||||||
|
assertThat(modifications.getModified())
|
||||||
|
.as("modified files modifications")
|
||||||
|
.hasSize(1)
|
||||||
|
.containsOnly(file);
|
||||||
|
assertThat(modifications.getRemoved())
|
||||||
|
.as("removed files modifications")
|
||||||
|
.hasSize(0);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Consumer<Modifications> assertAddedFile(String addedFile) {
|
||||||
|
return (modifications) -> {
|
||||||
|
assertThat(modifications).isNotNull();
|
||||||
|
assertThat(modifications.getAdded())
|
||||||
|
.as("added files modifications")
|
||||||
|
.hasSize(1)
|
||||||
|
.containsOnly(addedFile);
|
||||||
|
assertThat(modifications.getModified())
|
||||||
|
.as("modified files modifications")
|
||||||
|
.hasSize(0);
|
||||||
|
assertThat(modifications.getRemoved())
|
||||||
|
.as("removed files modifications")
|
||||||
|
.hasSize(0);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,7 +49,6 @@ import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
|
|||||||
import org.tmatesoft.svn.core.internal.util.SVNXMLUtil;
|
import org.tmatesoft.svn.core.internal.util.SVNXMLUtil;
|
||||||
import org.tmatesoft.svn.core.io.SVNRepository;
|
import org.tmatesoft.svn.core.io.SVNRepository;
|
||||||
import org.tmatesoft.svn.core.wc.SVNClientManager;
|
import org.tmatesoft.svn.core.wc.SVNClientManager;
|
||||||
import org.tmatesoft.svn.core.wc.admin.SVNChangeEntry;
|
|
||||||
import sonia.scm.util.HttpUtil;
|
import sonia.scm.util.HttpUtil;
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
@@ -103,30 +102,37 @@ public final class SvnUtil
|
|||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
public static long parseRevision(String v) throws RevisionNotFoundException {
|
||||||
* TODO: type replaced
|
long result = -1l;
|
||||||
*
|
|
||||||
*
|
if (!Strings.isNullOrEmpty(v))
|
||||||
* @param modifications
|
{
|
||||||
* @param entry
|
try
|
||||||
*/
|
{
|
||||||
public static void appendModification(Modifications modifications,
|
result = Long.parseLong(v);
|
||||||
SVNLogEntryPath entry)
|
}
|
||||||
{
|
catch (NumberFormatException ex)
|
||||||
appendModification(modifications, entry.getType(), entry.getPath());
|
{
|
||||||
|
throw new RevisionNotFoundException(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
public static Modifications createModifications(SVNLogEntry entry, String revision) {
|
||||||
*
|
Modifications modifications = new Modifications();
|
||||||
*
|
modifications.setRevision(revision);
|
||||||
* @param modifications
|
Map<String, SVNLogEntryPath> changeMap = entry.getChangedPaths();
|
||||||
* @param entry
|
|
||||||
*/
|
if (Util.isNotEmpty(changeMap)) {
|
||||||
public static void appendModification(Modifications modifications,
|
|
||||||
SVNChangeEntry entry)
|
for (SVNLogEntryPath e : changeMap.values()) {
|
||||||
{
|
appendModification(modifications, e.getType(), e.getPath());
|
||||||
appendModification(modifications, entry.getType(), entry.getPath());
|
}
|
||||||
|
}
|
||||||
|
return modifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -210,19 +216,6 @@ public final class SvnUtil
|
|||||||
{
|
{
|
||||||
changeset.getParents().add(String.valueOf(revision - 1));
|
changeset.getParents().add(String.valueOf(revision - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, SVNLogEntryPath> changeMap = entry.getChangedPaths();
|
|
||||||
|
|
||||||
if (Util.isNotEmpty(changeMap))
|
|
||||||
{
|
|
||||||
Modifications modifications = changeset.getModifications();
|
|
||||||
|
|
||||||
for (SVNLogEntryPath e : changeMap.values())
|
|
||||||
{
|
|
||||||
appendModification(modifications, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return changeset;
|
return changeset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ import sonia.scm.util.Util;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static sonia.scm.repository.SvnUtil.parseRevision;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
public class SvnLogCommand extends AbstractSvnCommand implements LogCommand
|
public class SvnLogCommand extends AbstractSvnCommand implements LogCommand
|
||||||
@@ -144,25 +146,6 @@ public class SvnLogCommand extends AbstractSvnCommand implements LogCommand
|
|||||||
return changesets;
|
return changesets;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
private long parseRevision(String v) throws RevisionNotFoundException {
|
|
||||||
long result = -1l;
|
|
||||||
|
|
||||||
if (!Strings.isNullOrEmpty(v))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result = Long.parseLong(v);
|
|
||||||
}
|
|
||||||
catch (NumberFormatException ex)
|
|
||||||
{
|
|
||||||
throw new RevisionNotFoundException(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.tmatesoft.svn.core.SVNException;
|
||||||
|
import org.tmatesoft.svn.core.SVNLogEntry;
|
||||||
|
import org.tmatesoft.svn.core.io.SVNRepository;
|
||||||
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
|
import sonia.scm.repository.Modifications;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RevisionNotFoundException;
|
||||||
|
import sonia.scm.repository.SvnUtil;
|
||||||
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class SvnModificationsCommand extends AbstractSvnCommand implements ModificationsCommand {
|
||||||
|
|
||||||
|
SvnModificationsCommand(SvnContext context, Repository repository) {
|
||||||
|
super(context, repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Modifications getModifications(String revision) throws IOException, RevisionNotFoundException {
|
||||||
|
Modifications modifications = null;
|
||||||
|
log.debug("get modifications {}", revision);
|
||||||
|
try {
|
||||||
|
long revisionNumber = SvnUtil.parseRevision(revision);
|
||||||
|
SVNRepository repo = open();
|
||||||
|
Collection<SVNLogEntry> entries = repo.log(null, null, revisionNumber,
|
||||||
|
revisionNumber, true, true);
|
||||||
|
if (Util.isNotEmpty(entries)) {
|
||||||
|
modifications = SvnUtil.createModifications(entries.iterator().next(), revision);
|
||||||
|
}
|
||||||
|
} catch (SVNException ex) {
|
||||||
|
throw new InternalRepositoryException("could not open repository", ex);
|
||||||
|
}
|
||||||
|
return modifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Modifications getModifications(ModificationsCommandRequest request) throws IOException, RevisionNotFoundException {
|
||||||
|
return getModifications(request.getRevision());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -37,22 +37,19 @@ package sonia.scm.repository.spi;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import org.tmatesoft.svn.core.SVNLogEntry;
|
import org.tmatesoft.svn.core.SVNLogEntry;
|
||||||
import org.tmatesoft.svn.core.wc.ISVNOptions;
|
import org.tmatesoft.svn.core.wc.ISVNOptions;
|
||||||
import org.tmatesoft.svn.core.wc.SVNClientManager;
|
import org.tmatesoft.svn.core.wc.SVNClientManager;
|
||||||
import org.tmatesoft.svn.core.wc.SVNWCUtil;
|
import org.tmatesoft.svn.core.wc.SVNWCUtil;
|
||||||
import org.tmatesoft.svn.core.wc.admin.SVNLookClient;
|
import org.tmatesoft.svn.core.wc.admin.SVNLookClient;
|
||||||
|
|
||||||
import sonia.scm.repository.Changeset;
|
import sonia.scm.repository.Changeset;
|
||||||
import sonia.scm.repository.RepositoryHookType;
|
import sonia.scm.repository.RepositoryHookType;
|
||||||
import sonia.scm.repository.SvnModificationHandler;
|
|
||||||
import sonia.scm.repository.SvnUtil;
|
import sonia.scm.repository.SvnUtil;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
@@ -123,10 +120,6 @@ public class SvnPreReceiveHookChangesetProvier
|
|||||||
{
|
{
|
||||||
changeset = SvnUtil.createChangeset(entry);
|
changeset = SvnUtil.createChangeset(entry);
|
||||||
changeset.setId(SvnUtil.createTransactionEntryId(transaction));
|
changeset.setId(SvnUtil.createTransactionEntryId(transaction));
|
||||||
|
|
||||||
clientManager.doGetChanged(repositoryDirectory, transaction,
|
|
||||||
new SvnModificationHandler(changeset), true);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (logger.isWarnEnabled())
|
else if (logger.isWarnEnabled())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -154,6 +154,10 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
return new SvnLogCommand(context, repository);
|
return new SvnLogCommand(context, repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ModificationsCommand getModificationsCommand() {
|
||||||
|
return new SvnModificationsCommand(context, repository);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Image } from "@scm-manager/ui-components";
|
import {Image} from "@scm-manager/ui-components";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -70,13 +70,15 @@ class SvnChangeWorker {
|
|||||||
SVNWCClient wClient = client.getWCClient();
|
SVNWCClient wClient = client.getWCClient();
|
||||||
|
|
||||||
// add files
|
// add files
|
||||||
try {
|
if (!addedFiles.isEmpty()){
|
||||||
wClient.doAdd(addedFiles.toArray(new File[0]), true, false, false,
|
try {
|
||||||
SVNDepth.INFINITY, false, false, false);
|
wClient.doAdd(addedFiles.toArray(new File[0]), true, false, false,
|
||||||
addedFiles.clear();
|
SVNDepth.INFINITY, false, false, false);
|
||||||
|
addedFiles.clear();
|
||||||
|
|
||||||
} catch (SVNException ex) {
|
} catch (SVNException ex) {
|
||||||
throw new RepositoryClientException("failed to add files", ex);
|
throw new RepositoryClientException("failed to add files", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove files
|
// remove files
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ import sonia.scm.repository.ChangesetPagingResult;
|
|||||||
import sonia.scm.repository.Modifications;
|
import sonia.scm.repository.Modifications;
|
||||||
import sonia.scm.repository.RevisionNotFoundException;
|
import sonia.scm.repository.RevisionNotFoundException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
@@ -128,7 +130,7 @@ public class SvnLogCommandTest extends AbstractSvnCommandTestBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCommit() throws RevisionNotFoundException {
|
public void testGetCommit() throws RevisionNotFoundException, IOException {
|
||||||
Changeset c = createCommand().getChangeset("3");
|
Changeset c = createCommand().getChangeset("3");
|
||||||
|
|
||||||
assertNotNull(c);
|
assertNotNull(c);
|
||||||
@@ -137,15 +139,15 @@ public class SvnLogCommandTest extends AbstractSvnCommandTestBase
|
|||||||
checkDate(c.getDate());
|
checkDate(c.getDate());
|
||||||
assertEquals("perfect", c.getAuthor().getName());
|
assertEquals("perfect", c.getAuthor().getName());
|
||||||
assertNull("douglas.adams@hitchhiker.com", c.getAuthor().getMail());
|
assertNull("douglas.adams@hitchhiker.com", c.getAuthor().getMail());
|
||||||
|
SvnModificationsCommand modificationsCommand = new SvnModificationsCommand(createContext(), repository);
|
||||||
|
Modifications modifications = modificationsCommand.getModifications("3");
|
||||||
|
|
||||||
Modifications mods = c.getModifications();
|
assertNotNull(modifications);
|
||||||
|
assertEquals(1, modifications.getModified().size());
|
||||||
assertNotNull(mods);
|
assertEquals(1, modifications.getRemoved().size());
|
||||||
assertEquals(1, mods.getModified().size());
|
assertTrue("added list should be empty", modifications.getAdded().isEmpty());
|
||||||
assertEquals(1, mods.getRemoved().size());
|
assertEquals("a.txt", modifications.getModified().get(0));
|
||||||
assertTrue("added list should be empty", mods.getAdded().isEmpty());
|
assertEquals("b.txt", modifications.getRemoved().get(0));
|
||||||
assertEquals("a.txt", mods.getModified().get(0));
|
|
||||||
assertEquals("b.txt", mods.getRemoved().get(0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -254,7 +254,6 @@
|
|||||||
<artifactId>tika-core</artifactId>
|
<artifactId>tika-core</artifactId>
|
||||||
<version>1.18</version>
|
<version>1.18</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- test scope -->
|
<!-- test scope -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -1,73 +1,21 @@
|
|||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
import de.otto.edison.hal.Embedded;
|
|
||||||
import de.otto.edison.hal.HalRepresentation;
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
import de.otto.edison.hal.Links;
|
|
||||||
import de.otto.edison.hal.paging.NumberedPaging;
|
|
||||||
import de.otto.edison.hal.paging.PagingRel;
|
|
||||||
import sonia.scm.ModelObject;
|
import sonia.scm.ModelObject;
|
||||||
import sonia.scm.PageResult;
|
import sonia.scm.PageResult;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import static com.damnhandy.uri.template.UriTemplate.fromTemplate;
|
public class BasicCollectionToDtoMapper<E extends ModelObject, D extends HalRepresentation, M extends BaseMapper<E, D>> extends PagedCollectionToDtoMapper<E, D> {
|
||||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
|
||||||
import static de.otto.edison.hal.Link.link;
|
|
||||||
import static de.otto.edison.hal.Links.linkingTo;
|
|
||||||
import static de.otto.edison.hal.paging.NumberedPaging.zeroBasedNumberedPaging;
|
|
||||||
import static java.util.stream.Collectors.toList;
|
|
||||||
|
|
||||||
abstract class BasicCollectionToDtoMapper<E extends ModelObject, D extends HalRepresentation, M extends BaseMapper<E, D>> {
|
|
||||||
|
|
||||||
private final String collectionName;
|
|
||||||
|
|
||||||
private final M entityToDtoMapper;
|
private final M entityToDtoMapper;
|
||||||
|
|
||||||
@Inject
|
|
||||||
public BasicCollectionToDtoMapper(String collectionName, M entityToDtoMapper) {
|
public BasicCollectionToDtoMapper(String collectionName, M entityToDtoMapper) {
|
||||||
this.collectionName = collectionName;
|
super(collectionName);
|
||||||
this.entityToDtoMapper = entityToDtoMapper;
|
this.entityToDtoMapper = entityToDtoMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionDto map(int pageNumber, int pageSize, PageResult<E> pageResult, String selfLink, Optional<String> createLink) {
|
CollectionDto map(int pageNumber, int pageSize, PageResult<E> pageResult, String selfLink, Optional<String> createLink) {
|
||||||
return map(pageNumber, pageSize, pageResult, selfLink, createLink, entityToDtoMapper::map);
|
return map(pageNumber, pageSize, pageResult, selfLink, createLink, entityToDtoMapper::map);
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionDto map(int pageNumber, int pageSize, PageResult<E> pageResult, String selfLink, Optional<String> createLink, Function<E, ? extends HalRepresentation> mapper) {
|
|
||||||
NumberedPaging paging = zeroBasedNumberedPaging(pageNumber, pageSize, pageResult.getOverallCount());
|
|
||||||
List<HalRepresentation> dtos = pageResult.getEntities().stream().map(mapper).collect(toList());
|
|
||||||
CollectionDto collectionDto = new CollectionDto(
|
|
||||||
createLinks(paging, selfLink, createLink),
|
|
||||||
embedDtos(dtos));
|
|
||||||
collectionDto.setPage(pageNumber);
|
|
||||||
collectionDto.setPageTotal(computePageTotal(pageSize, pageResult));
|
|
||||||
return collectionDto;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int computePageTotal(int pageSize, PageResult<E> pageResult) {
|
|
||||||
if (pageResult.getOverallCount() % pageSize > 0) {
|
|
||||||
return pageResult.getOverallCount() / pageSize + 1;
|
|
||||||
} else {
|
|
||||||
return pageResult.getOverallCount() / pageSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Links createLinks(NumberedPaging page, String selfLink, Optional<String> createLink) {
|
|
||||||
Links.Builder linksBuilder = linkingTo()
|
|
||||||
.with(page.links(
|
|
||||||
fromTemplate(selfLink + "{?page,pageSize}"),
|
|
||||||
EnumSet.allOf(PagingRel.class)));
|
|
||||||
createLink.ifPresent(link -> linksBuilder.single(link("create", link)));
|
|
||||||
return linksBuilder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Embedded embedDtos(List<HalRepresentation> dtos) {
|
|
||||||
return embeddedBuilder()
|
|
||||||
.with(collectionName, dtos)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ import javax.inject.Inject;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class ChangesetCollectionToDtoMapper extends BasicCollectionToDtoMapper<Changeset, ChangesetDto, ChangesetToChangesetDtoMapper> {
|
public class ChangesetCollectionToDtoMapper extends PagedCollectionToDtoMapper<Changeset, ChangesetDto> {
|
||||||
|
|
||||||
private final ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper;
|
private final ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper;
|
||||||
protected final ResourceLinks resourceLinks;
|
protected final ResourceLinks resourceLinks;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ChangesetCollectionToDtoMapper(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) {
|
public ChangesetCollectionToDtoMapper(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) {
|
||||||
super("changesets", changesetToChangesetDtoMapper);
|
super("changesets");
|
||||||
this.changesetToChangesetDtoMapper = changesetToChangesetDtoMapper;
|
this.changesetToChangesetDtoMapper = changesetToChangesetDtoMapper;
|
||||||
this.resourceLinks = resourceLinks;
|
this.resourceLinks = resourceLinks;
|
||||||
}
|
}
|
||||||
@@ -32,3 +32,4 @@ public class ChangesetCollectionToDtoMapper extends BasicCollectionToDtoMapper<C
|
|||||||
return resourceLinks.changeset().all(repository.getNamespace(), repository.getName());
|
return resourceLinks.changeset().all(repository.getNamespace(), repository.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import static de.otto.edison.hal.Link.link;
|
|||||||
import static de.otto.edison.hal.Links.linkingTo;
|
import static de.otto.edison.hal.Links.linkingTo;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public abstract class ChangesetToChangesetDtoMapper extends BaseMapper<Changeset, ChangesetDto> {
|
public abstract class ChangesetToChangesetDtoMapper implements InstantAttributeMapper {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private RepositoryServiceFactory serviceFactory;
|
private RepositoryServiceFactory serviceFactory;
|
||||||
@@ -65,7 +65,8 @@ public abstract class ChangesetToChangesetDtoMapper extends BaseMapper<Changeset
|
|||||||
|
|
||||||
Links.Builder linksBuilder = linkingTo()
|
Links.Builder linksBuilder = linkingTo()
|
||||||
.self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), target.getId()))
|
.self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), target.getId()))
|
||||||
.single(link("diff", resourceLinks.diff().self(namespace, name, target.getId())));
|
.single(link("diff", resourceLinks.diff().self(namespace, name, target.getId())))
|
||||||
|
.single(link("modifications", resourceLinks.modifications().self(namespace, name, target.getId())));
|
||||||
target.add(linksBuilder.build());
|
target.add(linksBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import static de.otto.edison.hal.Link.link;
|
|||||||
import static de.otto.edison.hal.Links.linkingTo;
|
import static de.otto.edison.hal.Links.linkingTo;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public abstract class ChangesetToParentDtoMapper extends BaseMapper<Changeset, ParentChangesetDto> {
|
public abstract class ChangesetToParentDtoMapper {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private ResourceLinks resourceLinks;
|
private ResourceLinks resourceLinks;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import de.otto.edison.hal.Links;
|
|||||||
import org.mapstruct.AfterMapping;
|
import org.mapstruct.AfterMapping;
|
||||||
import org.mapstruct.Context;
|
import org.mapstruct.Context;
|
||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
import org.mapstruct.MappingTarget;
|
import org.mapstruct.MappingTarget;
|
||||||
import sonia.scm.repository.FileObject;
|
import sonia.scm.repository.FileObject;
|
||||||
import sonia.scm.repository.NamespaceAndName;
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
@@ -14,11 +15,12 @@ import javax.inject.Inject;
|
|||||||
import static de.otto.edison.hal.Link.link;
|
import static de.otto.edison.hal.Link.link;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public abstract class FileObjectToFileObjectDtoMapper extends BaseMapper<FileObject, FileObjectDto> {
|
public abstract class FileObjectToFileObjectDtoMapper implements InstantAttributeMapper {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private ResourceLinks resourceLinks;
|
private ResourceLinks resourceLinks;
|
||||||
|
|
||||||
|
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||||
protected abstract FileObjectDto map(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context String revision);
|
protected abstract FileObjectDto map(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context String revision);
|
||||||
|
|
||||||
abstract SubRepositoryDto mapSubrepository(SubRepository subRepository);
|
abstract SubRepositoryDto mapSubrepository(SubRepository subRepository);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public class MapperModule extends AbstractModule {
|
|||||||
bind(TagToTagDtoMapper.class).to(Mappers.getMapper(TagToTagDtoMapper.class).getClass());
|
bind(TagToTagDtoMapper.class).to(Mappers.getMapper(TagToTagDtoMapper.class).getClass());
|
||||||
|
|
||||||
bind(FileObjectToFileObjectDtoMapper.class).to(Mappers.getMapper(FileObjectToFileObjectDtoMapper.class).getClass());
|
bind(FileObjectToFileObjectDtoMapper.class).to(Mappers.getMapper(FileObjectToFileObjectDtoMapper.class).getClass());
|
||||||
|
bind(ModificationsToDtoMapper.class).to(Mappers.getMapper(ModificationsToDtoMapper.class).getClass());
|
||||||
|
|
||||||
// no mapstruct required
|
// no mapstruct required
|
||||||
bind(UIPluginDtoMapper.class);
|
bind(UIPluginDtoMapper.class);
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
|
import de.otto.edison.hal.Links;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ModificationsDto extends HalRepresentation {
|
||||||
|
|
||||||
|
|
||||||
|
private String revision;
|
||||||
|
/**
|
||||||
|
* list of added files
|
||||||
|
*/
|
||||||
|
private List<String> added;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* list of modified files
|
||||||
|
*/
|
||||||
|
private List<String> modified;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* list of removed files
|
||||||
|
*/
|
||||||
|
private List<String> removed;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||||
|
protected HalRepresentation add(Links links) {
|
||||||
|
return super.add(links);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||||
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
|
import sonia.scm.repository.Modifications;
|
||||||
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
|
import sonia.scm.repository.RepositoryNotFoundException;
|
||||||
|
import sonia.scm.repository.RevisionNotFoundException;
|
||||||
|
import sonia.scm.repository.api.RepositoryService;
|
||||||
|
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||||
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class ModificationsRootResource {
|
||||||
|
|
||||||
|
private final RepositoryServiceFactory serviceFactory;
|
||||||
|
private final ModificationsToDtoMapper modificationsToDtoMapper;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ModificationsRootResource(RepositoryServiceFactory serviceFactory, ModificationsToDtoMapper modificationsToDtoMapper) {
|
||||||
|
this.serviceFactory = serviceFactory;
|
||||||
|
this.modificationsToDtoMapper = modificationsToDtoMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file modifications related to a revision.
|
||||||
|
* file modifications are for example: Modified, Added or Removed.
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@StatusCodes({
|
||||||
|
@ResponseCode(code = 200, condition = "success"),
|
||||||
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
|
@ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the modifications"),
|
||||||
|
@ResponseCode(code = 404, condition = "not found, no changeset with the specified id is available in the repository"),
|
||||||
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
|
})
|
||||||
|
@Produces(VndMediaType.MODIFICATIONS)
|
||||||
|
@TypeHint(ModificationsDto.class)
|
||||||
|
@Path("{revision}")
|
||||||
|
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws IOException, RevisionNotFoundException, RepositoryNotFoundException , InternalRepositoryException {
|
||||||
|
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||||
|
Modifications modifications = repositoryService.getModificationsCommand()
|
||||||
|
.revision(revision)
|
||||||
|
.getModifications();
|
||||||
|
ModificationsDto output = modificationsToDtoMapper.map(modifications, repositoryService.getRepository());
|
||||||
|
if (modifications != null ) {
|
||||||
|
return Response.ok(output).build();
|
||||||
|
} else {
|
||||||
|
return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import de.otto.edison.hal.Links;
|
||||||
|
import org.mapstruct.AfterMapping;
|
||||||
|
import org.mapstruct.Context;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
import org.mapstruct.MappingTarget;
|
||||||
|
import sonia.scm.repository.Modifications;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static de.otto.edison.hal.Links.linkingTo;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public abstract class ModificationsToDtoMapper {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ResourceLinks resourceLinks;
|
||||||
|
|
||||||
|
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||||
|
public abstract ModificationsDto map(Modifications modifications, @Context Repository repository);
|
||||||
|
|
||||||
|
@AfterMapping
|
||||||
|
void appendLinks(@MappingTarget ModificationsDto target, @Context Repository repository) {
|
||||||
|
Links.Builder linksBuilder = linkingTo()
|
||||||
|
.self(resourceLinks.modifications().self(repository.getNamespace(), repository.getName(), target.getRevision()));
|
||||||
|
target.add(linksBuilder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import de.otto.edison.hal.Embedded;
|
||||||
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
|
import de.otto.edison.hal.Links;
|
||||||
|
import de.otto.edison.hal.paging.NumberedPaging;
|
||||||
|
import de.otto.edison.hal.paging.PagingRel;
|
||||||
|
import sonia.scm.ModelObject;
|
||||||
|
import sonia.scm.PageResult;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static com.damnhandy.uri.template.UriTemplate.fromTemplate;
|
||||||
|
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||||
|
import static de.otto.edison.hal.Link.link;
|
||||||
|
import static de.otto.edison.hal.Links.linkingTo;
|
||||||
|
import static de.otto.edison.hal.paging.NumberedPaging.zeroBasedNumberedPaging;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
|
abstract class PagedCollectionToDtoMapper<E extends ModelObject, D extends HalRepresentation> {
|
||||||
|
|
||||||
|
private final String collectionName;
|
||||||
|
|
||||||
|
PagedCollectionToDtoMapper(String collectionName) {
|
||||||
|
this.collectionName = collectionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionDto map(int pageNumber, int pageSize, PageResult<E> pageResult, String selfLink, Optional<String> createLink, Function<E, ? extends HalRepresentation> mapper) {
|
||||||
|
NumberedPaging paging = zeroBasedNumberedPaging(pageNumber, pageSize, pageResult.getOverallCount());
|
||||||
|
List<HalRepresentation> dtos = pageResult.getEntities().stream().map(mapper).collect(toList());
|
||||||
|
CollectionDto collectionDto = new CollectionDto(
|
||||||
|
createLinks(paging, selfLink, createLink),
|
||||||
|
embedDtos(dtos));
|
||||||
|
collectionDto.setPage(pageNumber);
|
||||||
|
collectionDto.setPageTotal(computePageTotal(pageSize, pageResult));
|
||||||
|
return collectionDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int computePageTotal(int pageSize, PageResult<E> pageResult) {
|
||||||
|
if (pageResult.getOverallCount() % pageSize > 0) {
|
||||||
|
return pageResult.getOverallCount() / pageSize + 1;
|
||||||
|
} else {
|
||||||
|
return pageResult.getOverallCount() / pageSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Links createLinks(NumberedPaging page, String selfLink, Optional<String> createLink) {
|
||||||
|
Links.Builder linksBuilder = linkingTo()
|
||||||
|
.with(page.links(
|
||||||
|
fromTemplate(selfLink + "{?page,pageSize}"),
|
||||||
|
EnumSet.allOf(PagingRel.class)));
|
||||||
|
createLink.ifPresent(link -> linksBuilder.single(link("create", link)));
|
||||||
|
return linksBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Embedded embedDtos(List<HalRepresentation> dtos) {
|
||||||
|
return embeddedBuilder()
|
||||||
|
.with(collectionName, dtos)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,8 +10,6 @@ import java.util.Optional;
|
|||||||
import static java.util.Optional.empty;
|
import static java.util.Optional.empty;
|
||||||
import static java.util.Optional.of;
|
import static java.util.Optional.of;
|
||||||
|
|
||||||
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
|
|
||||||
@SuppressWarnings("squid:S3306")
|
|
||||||
public class RepositoryCollectionToDtoMapper extends BasicCollectionToDtoMapper<Repository, RepositoryDto, RepositoryToRepositoryDtoMapper> {
|
public class RepositoryCollectionToDtoMapper extends BasicCollectionToDtoMapper<Repository, RepositoryDto, RepositoryToRepositoryDtoMapper> {
|
||||||
|
|
||||||
private final ResourceLinks resourceLinks;
|
private final ResourceLinks resourceLinks;
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ public class RepositoryResource {
|
|||||||
private final Provider<ContentResource> contentResource;
|
private final Provider<ContentResource> contentResource;
|
||||||
private final Provider<PermissionRootResource> permissionRootResource;
|
private final Provider<PermissionRootResource> permissionRootResource;
|
||||||
private final Provider<DiffRootResource> diffRootResource;
|
private final Provider<DiffRootResource> diffRootResource;
|
||||||
|
private final Provider<ModificationsRootResource> modificationsRootResource;
|
||||||
private final Provider<FileHistoryRootResource> fileHistoryRootResource;
|
private final Provider<FileHistoryRootResource> fileHistoryRootResource;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -52,7 +53,9 @@ public class RepositoryResource {
|
|||||||
Provider<SourceRootResource> sourceRootResource, Provider<ContentResource> contentResource,
|
Provider<SourceRootResource> sourceRootResource, Provider<ContentResource> contentResource,
|
||||||
Provider<PermissionRootResource> permissionRootResource,
|
Provider<PermissionRootResource> permissionRootResource,
|
||||||
Provider<DiffRootResource> diffRootResource,
|
Provider<DiffRootResource> diffRootResource,
|
||||||
Provider<FileHistoryRootResource> fileHistoryRootResource) {
|
Provider<ModificationsRootResource> modificationsRootResource,
|
||||||
|
Provider<FileHistoryRootResource> fileHistoryRootResource
|
||||||
|
) {
|
||||||
this.dtoToRepositoryMapper = dtoToRepositoryMapper;
|
this.dtoToRepositoryMapper = dtoToRepositoryMapper;
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.repositoryToDtoMapper = repositoryToDtoMapper;
|
this.repositoryToDtoMapper = repositoryToDtoMapper;
|
||||||
@@ -64,6 +67,7 @@ public class RepositoryResource {
|
|||||||
this.contentResource = contentResource;
|
this.contentResource = contentResource;
|
||||||
this.permissionRootResource = permissionRootResource;
|
this.permissionRootResource = permissionRootResource;
|
||||||
this.diffRootResource = diffRootResource;
|
this.diffRootResource = diffRootResource;
|
||||||
|
this.modificationsRootResource = modificationsRootResource;
|
||||||
this.fileHistoryRootResource = fileHistoryRootResource;
|
this.fileHistoryRootResource = fileHistoryRootResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +192,9 @@ public class RepositoryResource {
|
|||||||
return permissionRootResource.get();
|
return permissionRootResource.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Path("modifications/")
|
||||||
|
public ModificationsRootResource modifications() {return modificationsRootResource.get(); }
|
||||||
|
|
||||||
private Optional<Response> handleNotArchived(Throwable throwable) {
|
private Optional<Response> handleNotArchived(Throwable throwable) {
|
||||||
if (throwable instanceof RepositoryIsNotArchivedException) {
|
if (throwable instanceof RepositoryIsNotArchivedException) {
|
||||||
return Optional.of(Response.status(Response.Status.PRECONDITION_FAILED).build());
|
return Optional.of(Response.status(Response.Status.PRECONDITION_FAILED).build());
|
||||||
|
|||||||
@@ -306,6 +306,21 @@ class ResourceLinks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ModificationsLinks modifications() {
|
||||||
|
return new ModificationsLinks(scmPathInfoStore.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ModificationsLinks {
|
||||||
|
private final LinkBuilder modificationsLinkBuilder;
|
||||||
|
|
||||||
|
ModificationsLinks(ScmPathInfo uriInfo) {
|
||||||
|
modificationsLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, ModificationsRootResource.class);
|
||||||
|
}
|
||||||
|
String self(String namespace, String name, String revision) {
|
||||||
|
return modificationsLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("modifications").parameters().method("get").parameters(revision).href();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public FileHistoryLinks fileHistory() {
|
public FileHistoryLinks fileHistory() {
|
||||||
return new FileHistoryLinks(scmPathInfoStore.get());
|
return new FileHistoryLinks(scmPathInfoStore.get());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import java.util.Optional;
|
|||||||
import static java.util.Optional.empty;
|
import static java.util.Optional.empty;
|
||||||
import static java.util.Optional.of;
|
import static java.util.Optional.of;
|
||||||
|
|
||||||
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
|
|
||||||
@SuppressWarnings("squid:S3306")
|
|
||||||
public class UserCollectionToDtoMapper extends BasicCollectionToDtoMapper<User, UserDto, UserToUserDtoMapper> {
|
public class UserCollectionToDtoMapper extends BasicCollectionToDtoMapper<User, UserDto, UserToUserDtoMapper> {
|
||||||
|
|
||||||
private final ResourceLinks resourceLinks;
|
private final ResourceLinks resourceLinks;
|
||||||
|
|||||||
@@ -19,8 +19,15 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.Mockito.anyInt;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||||
public class WebResourceServletTest {
|
public class WebResourceServletTest {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class BranchRootResourceTest {
|
public class BranchRootResourceTest extends RepositoryTestBase {
|
||||||
|
|
||||||
public static final String BRANCH_PATH = "space/repo/branches/master";
|
public static final String BRANCH_PATH = "space/repo/branches/master";
|
||||||
public static final String BRANCH_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + BRANCH_PATH;
|
public static final String BRANCH_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + BRANCH_PATH;
|
||||||
@@ -93,9 +93,8 @@ public class BranchRootResourceTest {
|
|||||||
changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
|
changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
|
||||||
BranchCollectionToDtoMapper branchCollectionToDtoMapper = new BranchCollectionToDtoMapper(branchToDtoMapper, resourceLinks);
|
BranchCollectionToDtoMapper branchCollectionToDtoMapper = new BranchCollectionToDtoMapper(branchToDtoMapper, resourceLinks);
|
||||||
branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper, changesetCollectionToDtoMapper);
|
branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper, changesetCollectionToDtoMapper);
|
||||||
RepositoryRootResource repositoryRootResource = new RepositoryRootResource(Providers.of(new RepositoryResource(null, null, null, null, Providers.of(branchRootResource), null, null, null, null, null, null)), null);
|
super.branchRootResource = Providers.of(branchRootResource);
|
||||||
dispatcher.getRegistry().addSingletonResource(repositoryRootResource);
|
dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource());
|
||||||
|
|
||||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
|
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
|
||||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||||
when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
|
when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ChangesetRootResourceTest {
|
public class ChangesetRootResourceTest extends RepositoryTestBase {
|
||||||
|
|
||||||
|
|
||||||
public static final String CHANGESET_PATH = "space/repo/changesets/";
|
public static final String CHANGESET_PATH = "space/repo/changesets/";
|
||||||
@@ -80,10 +80,8 @@ public class ChangesetRootResourceTest {
|
|||||||
public void prepareEnvironment() throws Exception {
|
public void prepareEnvironment() throws Exception {
|
||||||
changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
|
changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
|
||||||
changesetRootResource = new ChangesetRootResource(serviceFactory, changesetCollectionToDtoMapper, changesetToChangesetDtoMapper);
|
changesetRootResource = new ChangesetRootResource(serviceFactory, changesetCollectionToDtoMapper, changesetToChangesetDtoMapper);
|
||||||
RepositoryRootResource repositoryRootResource = new RepositoryRootResource(Providers
|
super.changesetRootResource = Providers.of(changesetRootResource);
|
||||||
.of(new RepositoryResource(null, null, null, null, null,
|
dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource());
|
||||||
Providers.of(changesetRootResource), null, null, null, null, null)), null);
|
|
||||||
dispatcher.getRegistry().addSingletonResource(repositoryRootResource);
|
|
||||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService);
|
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService);
|
||||||
when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService);
|
when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService);
|
||||||
when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
|
when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class DiffResourceTest {
|
public class DiffResourceTest extends RepositoryTestBase {
|
||||||
|
|
||||||
|
|
||||||
public static final String DIFF_PATH = "space/repo/diff/";
|
public static final String DIFF_PATH = "space/repo/diff/";
|
||||||
@@ -64,10 +64,8 @@ public class DiffResourceTest {
|
|||||||
@Before
|
@Before
|
||||||
public void prepareEnvironment() throws Exception {
|
public void prepareEnvironment() throws Exception {
|
||||||
diffRootResource = new DiffRootResource(serviceFactory);
|
diffRootResource = new DiffRootResource(serviceFactory);
|
||||||
RepositoryRootResource repositoryRootResource = new RepositoryRootResource(Providers
|
super.diffRootResource = Providers.of(diffRootResource);
|
||||||
.of(new RepositoryResource(null, null, null, null, null,
|
dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource());
|
||||||
null, null, null, null, Providers.of(diffRootResource),null)), null);
|
|
||||||
dispatcher.getRegistry().addSingletonResource(repositoryRootResource);
|
|
||||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
|
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
|
||||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||||
when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
|
when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ public class DispatcherMock {
|
|||||||
dispatcher.getProviderFactory().registerProvider(AlreadyExistsExceptionMapper.class);
|
dispatcher.getProviderFactory().registerProvider(AlreadyExistsExceptionMapper.class);
|
||||||
dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
|
dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
|
||||||
dispatcher.getProviderFactory().registerProvider(ConcurrentModificationExceptionMapper.class);
|
dispatcher.getProviderFactory().registerProvider(ConcurrentModificationExceptionMapper.class);
|
||||||
|
dispatcher.getProviderFactory().registerProvider(InternalRepositoryExceptionMapper.class);
|
||||||
return dispatcher;
|
return dispatcher;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class FileHistoryResourceTest {
|
public class FileHistoryResourceTest extends RepositoryTestBase {
|
||||||
|
|
||||||
public static final String FILE_HISTORY_PATH = "space/repo/history/";
|
public static final String FILE_HISTORY_PATH = "space/repo/history/";
|
||||||
public static final String FILE_HISTORY_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + FILE_HISTORY_PATH;
|
public static final String FILE_HISTORY_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + FILE_HISTORY_PATH;
|
||||||
@@ -82,10 +82,8 @@ public class FileHistoryResourceTest {
|
|||||||
public void prepareEnvironment() throws Exception {
|
public void prepareEnvironment() throws Exception {
|
||||||
fileHistoryCollectionToDtoMapper = new FileHistoryCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
|
fileHistoryCollectionToDtoMapper = new FileHistoryCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
|
||||||
fileHistoryRootResource = new FileHistoryRootResource(serviceFactory, fileHistoryCollectionToDtoMapper);
|
fileHistoryRootResource = new FileHistoryRootResource(serviceFactory, fileHistoryCollectionToDtoMapper);
|
||||||
RepositoryRootResource repositoryRootResource = new RepositoryRootResource(Providers
|
super.fileHistoryRootResource = Providers.of(fileHistoryRootResource);
|
||||||
.of(new RepositoryResource(null, null, null, null, null,
|
dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource());
|
||||||
null, null, null, null, null, Providers.of(fileHistoryRootResource))), null);
|
|
||||||
dispatcher.getRegistry().addSingletonResource(repositoryRootResource);
|
|
||||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
|
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
|
||||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||||
when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
|
when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.google.inject.util.Providers;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.subject.support.SubjectThreadState;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
|
import org.apache.shiro.util.ThreadState;
|
||||||
|
import org.assertj.core.util.Lists;
|
||||||
|
import org.jboss.resteasy.core.Dispatcher;
|
||||||
|
import org.jboss.resteasy.mock.MockDispatcherFactory;
|
||||||
|
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||||
|
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
import sonia.scm.api.rest.AuthorizationExceptionMapper;
|
||||||
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
|
import sonia.scm.repository.Modifications;
|
||||||
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.api.ModificationsCommandBuilder;
|
||||||
|
import sonia.scm.repository.api.RepositoryService;
|
||||||
|
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||||
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||||
|
public class ModificationsResourceTest extends RepositoryTestBase {
|
||||||
|
|
||||||
|
|
||||||
|
public static final String MODIFICATIONS_PATH = "space/repo/modifications/";
|
||||||
|
public static final String MODIFICATIONS_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + MODIFICATIONS_PATH;
|
||||||
|
private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
|
||||||
|
|
||||||
|
private final URI baseUri = URI.create("/");
|
||||||
|
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RepositoryServiceFactory serviceFactory;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RepositoryService repositoryService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ModificationsCommandBuilder modificationsCommandBuilder;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private ModificationsToDtoMapperImpl modificationsToDtoMapper;
|
||||||
|
|
||||||
|
private ModificationsRootResource modificationsRootResource;
|
||||||
|
|
||||||
|
|
||||||
|
private final Subject subject = mock(Subject.class);
|
||||||
|
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
|
||||||
|
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void prepareEnvironment() throws Exception {
|
||||||
|
modificationsRootResource = new ModificationsRootResource(serviceFactory, modificationsToDtoMapper);
|
||||||
|
super.modificationsRootResource = Providers.of(modificationsRootResource);
|
||||||
|
dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource());
|
||||||
|
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService);
|
||||||
|
when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService);
|
||||||
|
when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
|
||||||
|
dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class);
|
||||||
|
dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
|
||||||
|
dispatcher.getProviderFactory().registerProvider(InternalRepositoryExceptionMapper.class);
|
||||||
|
when(repositoryService.getModificationsCommand()).thenReturn(modificationsCommandBuilder);
|
||||||
|
subjectThreadState.bind();
|
||||||
|
ThreadContext.bind(subject);
|
||||||
|
when(subject.isPermitted(any(String.class))).thenReturn(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void cleanupContext() {
|
||||||
|
ThreadContext.unbindSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldGet404OnMissingModifications() throws Exception {
|
||||||
|
when(modificationsCommandBuilder.revision(any())).thenReturn(modificationsCommandBuilder);
|
||||||
|
when(modificationsCommandBuilder.getModifications()).thenReturn(null);
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.get(MODIFICATIONS_URL + "not_existing_revision")
|
||||||
|
.accept(VndMediaType.MODIFICATIONS);
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
assertEquals(404, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldGet500OnModificationsCommandError() throws Exception {
|
||||||
|
when(modificationsCommandBuilder.revision(any())).thenReturn(modificationsCommandBuilder);
|
||||||
|
when(modificationsCommandBuilder.getModifications()).thenThrow(InternalRepositoryException.class);
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.get(MODIFICATIONS_URL + "revision")
|
||||||
|
.accept(VndMediaType.MODIFICATIONS);
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
assertEquals(500, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldGetModifications() throws Exception {
|
||||||
|
Modifications modifications = new Modifications();
|
||||||
|
String revision = "revision";
|
||||||
|
String addedFile_1 = "a.txt";
|
||||||
|
String addedFile_2 = "b.txt";
|
||||||
|
String modifiedFile_1 = "d.txt";
|
||||||
|
String modifiedFile_2 = "c.txt";
|
||||||
|
String removedFile_1 = "e.txt";
|
||||||
|
String removedFile_2 = "f.txt";
|
||||||
|
modifications.setRevision(revision);
|
||||||
|
modifications.setAdded(Lists.newArrayList(addedFile_1, addedFile_2));
|
||||||
|
modifications.setModified(Lists.newArrayList(modifiedFile_1, modifiedFile_2));
|
||||||
|
modifications.setRemoved(Lists.newArrayList(removedFile_1, removedFile_2));
|
||||||
|
when(modificationsCommandBuilder.getModifications()).thenReturn(modifications);
|
||||||
|
when(modificationsCommandBuilder.revision(eq(revision))).thenReturn(modificationsCommandBuilder);
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.get(MODIFICATIONS_URL + revision)
|
||||||
|
.accept(VndMediaType.MODIFICATIONS);
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
assertEquals(200, response.getStatus());
|
||||||
|
log.info("the content: ", response.getContentAsString());
|
||||||
|
assertTrue(response.getContentAsString().contains(String.format("\"revision\":\"%s\"", revision)));
|
||||||
|
assertTrue(response.getContentAsString().contains(MessageFormat.format("\"added\":[\"{0}\",\"{1}\"]", addedFile_1, addedFile_2)));
|
||||||
|
assertTrue(response.getContentAsString().contains(MessageFormat.format("\"modified\":[\"{0}\",\"{1}\"]", modifiedFile_1, modifiedFile_2)));
|
||||||
|
assertTrue(response.getContentAsString().contains(MessageFormat.format("\"removed\":[\"{0}\",\"{1}\"]", removedFile_1, removedFile_2)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,7 +65,7 @@ import static sonia.scm.api.v2.resources.PermissionDto.GROUP_PREFIX;
|
|||||||
password = "secret",
|
password = "secret",
|
||||||
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
||||||
)
|
)
|
||||||
public class PermissionRootResourceTest {
|
public class PermissionRootResourceTest extends RepositoryTestBase {
|
||||||
private static final String REPOSITORY_NAMESPACE = "repo_namespace";
|
private static final String REPOSITORY_NAMESPACE = "repo_namespace";
|
||||||
private static final String REPOSITORY_NAME = "repo";
|
private static final String REPOSITORY_NAME = "repo";
|
||||||
private static final String PERMISSION_WRITE = "repository:permissionWrite:" + REPOSITORY_NAME;
|
private static final String PERMISSION_WRITE = "repository:permissionWrite:" + REPOSITORY_NAME;
|
||||||
@@ -138,9 +138,8 @@ public class PermissionRootResourceTest {
|
|||||||
initMocks(this);
|
initMocks(this);
|
||||||
permissionCollectionToDtoMapper = new PermissionCollectionToDtoMapper(permissionToPermissionDtoMapper, resourceLinks);
|
permissionCollectionToDtoMapper = new PermissionCollectionToDtoMapper(permissionToPermissionDtoMapper, resourceLinks);
|
||||||
permissionRootResource = new PermissionRootResource(permissionDtoToPermissionMapper, permissionToPermissionDtoMapper, permissionCollectionToDtoMapper, resourceLinks, repositoryManager);
|
permissionRootResource = new PermissionRootResource(permissionDtoToPermissionMapper, permissionToPermissionDtoMapper, permissionCollectionToDtoMapper, resourceLinks, repositoryManager);
|
||||||
RepositoryRootResource repositoryRootResource = new RepositoryRootResource(Providers
|
super.permissionRootResource = Providers.of(permissionRootResource);
|
||||||
.of(new RepositoryResource(null, null, null, null, null, null, null, null, Providers.of(permissionRootResource), null, null)), null);
|
dispatcher = createDispatcher(getRepositoryRootResource());
|
||||||
dispatcher = createDispatcher(repositoryRootResource);
|
|
||||||
subjectThreadState.bind();
|
subjectThreadState.bind();
|
||||||
ThreadContext.bind(subject);
|
ThreadContext.bind(subject);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
|
|||||||
password = "secret",
|
password = "secret",
|
||||||
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
||||||
)
|
)
|
||||||
public class RepositoryRootResourceTest {
|
public class RepositoryRootResourceTest extends RepositoryTestBase {
|
||||||
|
|
||||||
private Dispatcher dispatcher;
|
private Dispatcher dispatcher;
|
||||||
|
|
||||||
@@ -87,14 +87,15 @@ public class RepositoryRootResourceTest {
|
|||||||
@Before
|
@Before
|
||||||
public void prepareEnvironment() {
|
public void prepareEnvironment() {
|
||||||
initMocks(this);
|
initMocks(this);
|
||||||
RepositoryResource repositoryResource = new RepositoryResource(repositoryToDtoMapper, dtoToRepositoryMapper, repositoryManager, null, null, null, null, null, null, null, null);
|
super.repositoryToDtoMapper = repositoryToDtoMapper;
|
||||||
|
super.dtoToRepositoryMapper = dtoToRepositoryMapper;
|
||||||
|
super.manager = repositoryManager;
|
||||||
RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks);
|
RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks);
|
||||||
RepositoryCollectionResource repositoryCollectionResource = new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks);
|
super.repositoryCollectionResource = Providers.of(new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks));
|
||||||
RepositoryRootResource repositoryRootResource = new RepositoryRootResource(Providers.of(repositoryResource), Providers.of(repositoryCollectionResource));
|
dispatcher = createDispatcher(getRepositoryRootResource());
|
||||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||||
when(scmPathInfoStore.get()).thenReturn(uriInfo);
|
when(scmPathInfoStore.get()).thenReturn(uriInfo);
|
||||||
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y"));
|
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y"));
|
||||||
dispatcher = createDispatcher(repositoryRootResource);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.google.inject.util.Providers;
|
||||||
|
import sonia.scm.repository.RepositoryManager;
|
||||||
|
|
||||||
|
import javax.inject.Provider;
|
||||||
|
|
||||||
|
public abstract class RepositoryTestBase {
|
||||||
|
|
||||||
|
|
||||||
|
protected RepositoryToRepositoryDtoMapper repositoryToDtoMapper;
|
||||||
|
protected RepositoryDtoToRepositoryMapper dtoToRepositoryMapper;
|
||||||
|
protected RepositoryManager manager;
|
||||||
|
protected Provider<TagRootResource> tagRootResource;
|
||||||
|
protected Provider<BranchRootResource> branchRootResource;
|
||||||
|
protected Provider<ChangesetRootResource> changesetRootResource;
|
||||||
|
protected Provider<SourceRootResource> sourceRootResource;
|
||||||
|
protected Provider<ContentResource> contentResource;
|
||||||
|
protected Provider<PermissionRootResource> permissionRootResource;
|
||||||
|
protected Provider<DiffRootResource> diffRootResource;
|
||||||
|
protected Provider<ModificationsRootResource> modificationsRootResource;
|
||||||
|
protected Provider<FileHistoryRootResource> fileHistoryRootResource;
|
||||||
|
protected Provider<RepositoryCollectionResource> repositoryCollectionResource;
|
||||||
|
|
||||||
|
|
||||||
|
RepositoryRootResource getRepositoryRootResource() {
|
||||||
|
return new RepositoryRootResource(Providers.of(new RepositoryResource(
|
||||||
|
repositoryToDtoMapper,
|
||||||
|
dtoToRepositoryMapper,
|
||||||
|
manager,
|
||||||
|
tagRootResource,
|
||||||
|
branchRootResource,
|
||||||
|
changesetRootResource,
|
||||||
|
sourceRootResource,
|
||||||
|
contentResource,
|
||||||
|
permissionRootResource,
|
||||||
|
diffRootResource,
|
||||||
|
modificationsRootResource,
|
||||||
|
fileHistoryRootResource)), repositoryCollectionResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ public class ResourceLinksMock {
|
|||||||
when(resourceLinks.config()).thenReturn(new ResourceLinks.ConfigLinks(uriInfo));
|
when(resourceLinks.config()).thenReturn(new ResourceLinks.ConfigLinks(uriInfo));
|
||||||
when(resourceLinks.branch()).thenReturn(new ResourceLinks.BranchLinks(uriInfo));
|
when(resourceLinks.branch()).thenReturn(new ResourceLinks.BranchLinks(uriInfo));
|
||||||
when(resourceLinks.diff()).thenReturn(new ResourceLinks.DiffLinks(uriInfo));
|
when(resourceLinks.diff()).thenReturn(new ResourceLinks.DiffLinks(uriInfo));
|
||||||
|
when(resourceLinks.modifications()).thenReturn(new ResourceLinks.ModificationsLinks(uriInfo));
|
||||||
when(resourceLinks.repositoryType()).thenReturn(new ResourceLinks.RepositoryTypeLinks(uriInfo));
|
when(resourceLinks.repositoryType()).thenReturn(new ResourceLinks.RepositoryTypeLinks(uriInfo));
|
||||||
when(resourceLinks.repositoryTypeCollection()).thenReturn(new ResourceLinks.RepositoryTypeCollectionLinks(uriInfo));
|
when(resourceLinks.repositoryTypeCollection()).thenReturn(new ResourceLinks.RepositoryTypeCollectionLinks(uriInfo));
|
||||||
when(resourceLinks.uiPluginCollection()).thenReturn(new ResourceLinks.UIPluginCollectionLinks(uriInfo));
|
when(resourceLinks.uiPluginCollection()).thenReturn(new ResourceLinks.UIPluginCollectionLinks(uriInfo));
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
|
|||||||
|
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||||
public class SourceRootResourceTest {
|
public class SourceRootResourceTest extends RepositoryTestBase {
|
||||||
|
|
||||||
private Dispatcher dispatcher;
|
private Dispatcher dispatcher;
|
||||||
private final URI baseUri = URI.create("/");
|
private final URI baseUri = URI.create("/");
|
||||||
@@ -64,20 +64,8 @@ public class SourceRootResourceTest {
|
|||||||
|
|
||||||
when(fileObjectToFileObjectDtoMapper.map(any(FileObject.class), any(NamespaceAndName.class), anyString())).thenReturn(dto);
|
when(fileObjectToFileObjectDtoMapper.map(any(FileObject.class), any(NamespaceAndName.class), anyString())).thenReturn(dto);
|
||||||
SourceRootResource sourceRootResource = new SourceRootResource(serviceFactory, browserResultToBrowserResultDtoMapper);
|
SourceRootResource sourceRootResource = new SourceRootResource(serviceFactory, browserResultToBrowserResultDtoMapper);
|
||||||
RepositoryRootResource repositoryRootResource =
|
super.sourceRootResource = Providers.of(sourceRootResource);
|
||||||
new RepositoryRootResource(Providers.of(new RepositoryResource(null,
|
dispatcher = createDispatcher(getRepositoryRootResource());
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
Providers.of(sourceRootResource),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null)),
|
|
||||||
null);
|
|
||||||
dispatcher = createDispatcher(repositoryRootResource);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import org.apache.shiro.util.ThreadContext;
|
|||||||
import org.apache.shiro.util.ThreadState;
|
import org.apache.shiro.util.ThreadState;
|
||||||
import org.assertj.core.util.Lists;
|
import org.assertj.core.util.Lists;
|
||||||
import org.jboss.resteasy.core.Dispatcher;
|
import org.jboss.resteasy.core.Dispatcher;
|
||||||
import org.jboss.resteasy.mock.MockDispatcherFactory;
|
|
||||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||||
import org.jboss.resteasy.mock.MockHttpResponse;
|
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
@@ -36,14 +35,15 @@ import static org.junit.Assert.assertTrue;
|
|||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||||
public class TagRootResourceTest {
|
public class TagRootResourceTest extends RepositoryTestBase {
|
||||||
|
|
||||||
public static final String TAG_PATH = "space/repo/tags/";
|
public static final String TAG_PATH = "space/repo/tags/";
|
||||||
public static final String TAG_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + TAG_PATH;
|
public static final String TAG_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + TAG_PATH;
|
||||||
private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
|
private Dispatcher dispatcher ;
|
||||||
|
|
||||||
private final URI baseUri = URI.create("/");
|
private final URI baseUri = URI.create("/");
|
||||||
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||||
@@ -56,7 +56,6 @@ public class TagRootResourceTest {
|
|||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private TagsCommandBuilder tagsCommandBuilder;
|
private TagsCommandBuilder tagsCommandBuilder;
|
||||||
|
|
||||||
private TagCollectionToDtoMapper tagCollectionToDtoMapper;
|
private TagCollectionToDtoMapper tagCollectionToDtoMapper;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
@@ -73,10 +72,8 @@ public class TagRootResourceTest {
|
|||||||
public void prepareEnvironment() throws Exception {
|
public void prepareEnvironment() throws Exception {
|
||||||
tagCollectionToDtoMapper = new TagCollectionToDtoMapper(resourceLinks, tagToTagDtoMapper);
|
tagCollectionToDtoMapper = new TagCollectionToDtoMapper(resourceLinks, tagToTagDtoMapper);
|
||||||
tagRootResource = new TagRootResource(serviceFactory, tagCollectionToDtoMapper, tagToTagDtoMapper);
|
tagRootResource = new TagRootResource(serviceFactory, tagCollectionToDtoMapper, tagToTagDtoMapper);
|
||||||
RepositoryRootResource repositoryRootResource = new RepositoryRootResource(Providers
|
super.tagRootResource = Providers.of(tagRootResource);
|
||||||
.of(new RepositoryResource(null, null, null, Providers.of(tagRootResource), null,
|
dispatcher = createDispatcher(getRepositoryRootResource());
|
||||||
null, null, null, null, null, null)), null);
|
|
||||||
dispatcher.getRegistry().addSingletonResource(repositoryRootResource);
|
|
||||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService);
|
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService);
|
||||||
when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService);
|
when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService);
|
||||||
when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
|
when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
|
||||||
|
|||||||
Reference in New Issue
Block a user