diff --git a/scm-core/pom.xml b/scm-core/pom.xml index 17ca03b115..a3fa037c7b 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -32,6 +32,13 @@ scm-annotations 2.0.0-SNAPSHOT + + + org.projectlombok + lombok + provided + + diff --git a/scm-core/src/main/java/sonia/scm/plugin/WebResourceLoader.java b/scm-core/src/main/java/sonia/scm/plugin/WebResourceLoader.java index 61466c0e2c..454003c922 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/WebResourceLoader.java +++ b/scm-core/src/main/java/sonia/scm/plugin/WebResourceLoader.java @@ -33,9 +33,8 @@ package sonia.scm.plugin; //~--- JDK imports ------------------------------------------------------------ -import java.net.URL; - import javax.servlet.ServletContext; +import java.net.URL; /** * The WebResourceLoader is able to load web resources. The resources are loaded diff --git a/scm-core/src/main/java/sonia/scm/repository/Changeset.java b/scm-core/src/main/java/sonia/scm/repository/Changeset.java index 18ede493f6..7397fecabe 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Changeset.java +++ b/scm-core/src/main/java/sonia/scm/repository/Changeset.java @@ -41,7 +41,6 @@ import sonia.scm.util.ValidationUtil; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import java.util.ArrayList; import java.util.Date; @@ -84,12 +83,6 @@ public class Changeset extends BasicPropertiesAware implements ModelObject { */ private String id; - /** - * List of files changed by this changeset - */ - @XmlElement(name = "modifications") - private Modifications modifications; - /** * parent changeset ids */ @@ -137,7 +130,6 @@ public class Changeset extends BasicPropertiesAware implements ModelObject { && Objects.equal(parents, other.parents) && Objects.equal(tags, other.tags) && Objects.equal(branches, other.branches) - && Objects.equal(modifications, other.modifications) && Objects.equal(properties, other.properties); //J+ } @@ -152,7 +144,7 @@ public class Changeset extends BasicPropertiesAware implements ModelObject { public int hashCode() { return Objects.hashCode(id, date, author, description, parents, tags, - branches, modifications, properties); + branches, properties); } /** @@ -184,11 +176,6 @@ public class Changeset extends BasicPropertiesAware implements ModelObject { out.append("branches: ").append(Util.toString(branches)).append("\n"); out.append("tags: ").append(Util.toString(tags)).append("\n"); - if (modifications != null) - { - out.append("modifications: \n").append(modifications); - } - return out.toString(); } @@ -285,21 +272,6 @@ public class Changeset extends BasicPropertiesAware implements ModelObject { } - /** - * Returns the file modifications, which was done with this changeset. - * - * - * @return file modifications - */ - public Modifications getModifications() - { - if (modifications == null) - { - modifications = new Modifications(); - } - - return modifications; - } /** * Return the ids of the parent changesets. @@ -402,17 +374,6 @@ public class Changeset extends BasicPropertiesAware implements ModelObject { this.id = id; } - /** - * Sets the file modification of the changeset. - * - * - * @param modifications file modifications - */ - public void setModifications(Modifications modifications) - { - this.modifications = modifications; - } - /** * Sets the parents of the changeset. * diff --git a/scm-core/src/main/java/sonia/scm/repository/EscapeUtil.java b/scm-core/src/main/java/sonia/scm/repository/EscapeUtil.java index a06472256d..979ea0056d 100644 --- a/scm-core/src/main/java/sonia/scm/repository/EscapeUtil.java +++ b/scm-core/src/main/java/sonia/scm/repository/EscapeUtil.java @@ -36,15 +36,13 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.collect.Lists; - import org.apache.commons.lang.StringEscapeUtils; - import sonia.scm.util.Util; -//~--- JDK imports ------------------------------------------------------------ - import java.util.List; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -200,4 +198,10 @@ public final class EscapeUtil return values; } + + public static void escape(Modifications modifications) { + modifications.setAdded(escapeList(modifications.getAdded())); + modifications.setModified(escapeList(modifications.getModified())); + modifications.setRemoved(escapeList(modifications.getRemoved())); + } } diff --git a/scm-core/src/main/java/sonia/scm/repository/Modifications.java b/scm-core/src/main/java/sonia/scm/repository/Modifications.java index 4079d8f574..a679b0429f 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Modifications.java +++ b/scm-core/src/main/java/sonia/scm/repository/Modifications.java @@ -67,7 +67,8 @@ public class Modifications implements Serializable * Constructs ... * */ - public Modifications() {} + public Modifications() { + } /** * Constructs ... @@ -218,6 +219,10 @@ public class Modifications implements Serializable return removed; } + public String getRevision() { + return revision; + } + //~--- set methods ---------------------------------------------------------- /** @@ -253,8 +258,14 @@ public class Modifications implements Serializable this.removed = removed; } + public void setRevision(String revision) { + this.revision = revision; + } + //~--- fields --------------------------------------------------------------- + private String revision; + /** list of added files */ @XmlElement(name = "added") @XmlElementWrapper(name = "added") diff --git a/scm-core/src/main/java/sonia/scm/repository/ModificationsPreProcessor.java b/scm-core/src/main/java/sonia/scm/repository/ModificationsPreProcessor.java new file mode 100644 index 0000000000..e5870e91aa --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/ModificationsPreProcessor.java @@ -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 { + + /** + * Process the given modifications. + * + * @param modifications modifications to process + */ + @Override + void process(Modifications modifications); +} diff --git a/scm-core/src/main/java/sonia/scm/repository/ModificationsPreProcessorFactory.java b/scm-core/src/main/java/sonia/scm/repository/ModificationsPreProcessorFactory.java new file mode 100644 index 0000000000..71a6a38efa --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/ModificationsPreProcessorFactory.java @@ -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 { + + /** + * Create a new {@link ModificationsPreProcessor} for the given repository. + * + * + * @param repository repository + * + * @return {@link ModificationsPreProcessor} for the given repository + */ + @Override + ModificationsPreProcessor createPreProcessor(Repository repository); + +} diff --git a/scm-core/src/main/java/sonia/scm/repository/PreProcessorUtil.java b/scm-core/src/main/java/sonia/scm/repository/PreProcessorUtil.java index b44da13ad1..98730c2039 100644 --- a/scm-core/src/main/java/sonia/scm/repository/PreProcessorUtil.java +++ b/scm-core/src/main/java/sonia/scm/repository/PreProcessorUtil.java @@ -36,17 +36,15 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.util.Util; -//~--- JDK imports ------------------------------------------------------------ - import java.util.Collection; import java.util.Set; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -73,14 +71,18 @@ public class PreProcessorUtil * @param fileObjectPreProcessorFactorySet * @param blameLinePreProcessorSet * @param blameLinePreProcessorFactorySet + * @param modificationsPreProcessorFactorySet + * @param modificationsPreProcessorSet */ @Inject public PreProcessorUtil(Set changesetPreProcessorSet, - Set changesetPreProcessorFactorySet, - Set fileObjectPreProcessorSet, - Set fileObjectPreProcessorFactorySet, - Set blameLinePreProcessorSet, - Set blameLinePreProcessorFactorySet) + Set changesetPreProcessorFactorySet, + Set fileObjectPreProcessorSet, + Set fileObjectPreProcessorFactorySet, + Set blameLinePreProcessorSet, + Set blameLinePreProcessorFactorySet, + Set modificationsPreProcessorFactorySet, + Set modificationsPreProcessorSet) { this.changesetPreProcessorSet = changesetPreProcessorSet; this.changesetPreProcessorFactorySet = changesetPreProcessorFactorySet; @@ -88,6 +90,8 @@ public class PreProcessorUtil this.fileObjectPreProcessorFactorySet = fileObjectPreProcessorFactorySet; this.blameLinePreProcessorSet = blameLinePreProcessorSet; this.blameLinePreProcessorFactorySet = blameLinePreProcessorFactorySet; + this.modificationsPreProcessorFactorySet = modificationsPreProcessorFactorySet; + this.modificationsPreProcessorSet = modificationsPreProcessorSet; } //~--- methods -------------------------------------------------------------- @@ -108,13 +112,7 @@ public class PreProcessorUtil } EscapeUtil.escape(blameLine); - - PreProcessorHandler handler = - new PreProcessorHandler(blameLinePreProcessorFactorySet, - blameLinePreProcessorSet, repository); - - handler.callPreProcessors(blameLine); - handler.callPreProcessorFactories(blameLine); + handlePreProcess(repository,blameLine,blameLinePreProcessorFactorySet, blameLinePreProcessorSet); } /** @@ -152,13 +150,7 @@ public class PreProcessorUtil { EscapeUtil.escape(blameResult); } - - PreProcessorHandler handler = - new PreProcessorHandler(blameLinePreProcessorFactorySet, - blameLinePreProcessorSet, repository); - - handler.callPreProcessors(blameResult.getBlameLines()); - handler.callPreProcessorFactories(blameResult.getBlameLines()); + handlePreProcessForIterable(repository, blameResult.getBlameLines(),blameLinePreProcessorFactorySet, blameLinePreProcessorSet); } /** @@ -183,26 +175,20 @@ public class PreProcessorUtil * * @since 1.35 */ - public void prepareForReturn(Repository repository, Changeset changeset, - boolean escape) - { - if (logger.isTraceEnabled()) - { - logger.trace("prepare changeset {} of repository {} for return", - changeset.getId(), repository.getName()); - } - - if (escape) - { + public void prepareForReturn(Repository repository, Changeset changeset, boolean escape){ + logger.trace("prepare changeset {} of repository {} for return", changeset.getId(), repository.getName()); + if (escape) { EscapeUtil.escape(changeset); } + handlePreProcess(repository, changeset, changesetPreProcessorFactorySet, changesetPreProcessorSet); + } - PreProcessorHandler handler = - new PreProcessorHandler(changesetPreProcessorFactorySet, - changesetPreProcessorSet, repository); - - handler.callPreProcessors(changeset); - handler.callPreProcessorFactories(changeset); + public void prepareForReturn(Repository repository, Modifications modifications, boolean escape) { + logger.trace("prepare modifications {} of repository {} for return", modifications, repository.getName()); + if (escape) { + EscapeUtil.escape(modifications); + } + handlePreProcess(repository, modifications, modificationsPreProcessorFactorySet, modificationsPreProcessorSet); } /** @@ -240,13 +226,7 @@ public class PreProcessorUtil { EscapeUtil.escape(result); } - - PreProcessorHandler handler = - new PreProcessorHandler(fileObjectPreProcessorFactorySet, - fileObjectPreProcessorSet, repository); - - handler.callPreProcessors(result); - handler.callPreProcessorFactories(result); + handlePreProcessForIterable(repository, result,fileObjectPreProcessorFactorySet, fileObjectPreProcessorSet); } /** @@ -272,13 +252,7 @@ public class PreProcessorUtil { EscapeUtil.escape(result); } - - PreProcessorHandler handler = - new PreProcessorHandler(changesetPreProcessorFactorySet, - changesetPreProcessorSet, repository); - - handler.callPreProcessors(result); - handler.callPreProcessorFactories(result); + handlePreProcessForIterable(repository,result,changesetPreProcessorFactorySet, changesetPreProcessorSet); } /** @@ -294,6 +268,23 @@ public class PreProcessorUtil prepareForReturn(repository, result, true); } + + private , P extends PreProcessor> void handlePreProcess(Repository repository, T processedObject, + Collection factories, + Collection

preProcessors) { + PreProcessorHandler handler = new PreProcessorHandler(factories, preProcessors, repository); + handler.callPreProcessors(processedObject); + handler.callPreProcessorFactories(processedObject); + } + + private , F extends PreProcessorFactory, P extends PreProcessor> void handlePreProcessForIterable(Repository repository, I processedObjects, + Collection factories, + Collection

preProcessors) { + PreProcessorHandler handler = new PreProcessorHandler(factories, preProcessors, repository); + handler.callPreProcessors(processedObjects); + handler.callPreProcessorFactories(processedObjects); + } + //~--- inner classes -------------------------------------------------------- /** @@ -454,6 +445,10 @@ public class PreProcessorUtil /** Field description */ private final Collection changesetPreProcessorSet; + private final Collection modificationsPreProcessorFactorySet; + + private final Collection modificationsPreProcessorSet; + /** Field description */ private final Collection fileObjectPreProcessorFactorySet; diff --git a/scm-core/src/main/java/sonia/scm/repository/api/Command.java b/scm-core/src/main/java/sonia/scm/repository/api/Command.java index ccb0d8c2c0..2f844cfbfb 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/Command.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/Command.java @@ -61,5 +61,11 @@ public enum Command /** * @since 1.43 */ - BUNDLE, UNBUNDLE; + BUNDLE, UNBUNDLE, + + /** + * @since 2.0 + */ + MODIFICATIONS + } diff --git a/scm-core/src/main/java/sonia/scm/repository/api/ModificationsCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/ModificationsCommandBuilder.java new file mode 100644 index 0000000000..d81088bd33 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/api/ModificationsCommandBuilder.java @@ -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. + *

+ * 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 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; + } + } + +} diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java index 27eed6becd..9eb7427a4f 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java @@ -250,6 +250,18 @@ public final class RepositoryService implements Closeable { 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. * diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnModificationHandler.java b/scm-core/src/main/java/sonia/scm/repository/spi/ModificationsCommand.java similarity index 57% rename from scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnModificationHandler.java rename to scm-core/src/main/java/sonia/scm/repository/spi/ModificationsCommand.java index f7cb60e3df..e9b40e8a17 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnModificationHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/ModificationsCommand.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010, Sebastian Sdorra * 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; - -//~--- 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; +import java.io.IOException; /** + * 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 { - /** - * Constructs ... - * - * - * @param changeset - */ - public SvnModificationHandler(Changeset changeset) - { - this.changeset = changeset; - } + Modifications getModifications(String revision) throws IOException, RevisionNotFoundException; - //~--- 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; } diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/ModificationsCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/ModificationsCommandRequest.java new file mode 100644 index 0000000000..8a910a3396 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/spi/ModificationsCommandRequest.java @@ -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; + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java b/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java index 976f38fffb..2f436836cd 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java @@ -39,14 +39,13 @@ import sonia.scm.repository.Feature; import sonia.scm.repository.api.Command; import sonia.scm.repository.api.CommandNotSupportedException; -//~--- JDK imports ------------------------------------------------------------ - import java.io.Closeable; import java.io.IOException; - import java.util.Collections; import java.util.Set; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -173,6 +172,16 @@ public abstract class RepositoryServiceProvider implements Closeable throw new CommandNotSupportedException(Command.LOG); } + /** + * Get the corresponding {@link ModificationsCommand} implemented from the Plugins + * + * @return the corresponding {@link ModificationsCommand} implemented from the Plugins + * @throws CommandNotSupportedException if there is no Implementation + */ + public ModificationsCommand getModificationsCommand() { + throw new CommandNotSupportedException(Command.MODIFICATIONS); + } + /** * Method description * diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index ddcce9f4e8..d9a6846795 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -21,6 +21,7 @@ public class VndMediaType { public static final String PERMISSION = PREFIX + "permission" + SUFFIX; public static final String CHANGESET = PREFIX + "changeset" + SUFFIX; public static final String CHANGESET_COLLECTION = PREFIX + "changesetCollection" + SUFFIX; + public static final String MODIFICATIONS = PREFIX + "modifications" + SUFFIX;; public static final String TAG = PREFIX + "tag" + SUFFIX; public static final String TAG_COLLECTION = PREFIX + "tagCollection" + SUFFIX; public static final String BRANCH = PREFIX + "branch" + SUFFIX; diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java index fc095828b3..398921a692 100644 --- a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java @@ -3,6 +3,8 @@ package sonia.scm.it; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; import org.apache.http.HttpStatus; +import org.assertj.core.util.Lists; +import org.assertj.core.util.Maps; import org.junit.Assume; import org.junit.Before; import org.junit.Rule; @@ -17,8 +19,11 @@ import sonia.scm.web.VndMediaType; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static java.lang.Thread.sleep; import static org.assertj.core.api.Assertions.assertThat; @@ -312,5 +317,163 @@ public class RepositoryAccessITCase { } ); } + + @Test + @SuppressWarnings("unchecked") + public void shouldFindAddedModifications() throws IOException { + RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder); + String fileName = "a.txt"; + Changeset changeset = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "a"); + String revision = changeset.getId(); + repositoryGetRequest + .usingRepositoryResponse() + .requestChangesets() + .assertStatusCode(HttpStatus.SC_OK) + .usingChangesetsResponse() + .requestModifications(revision) + .assertStatusCode(HttpStatus.SC_OK) + .usingModificationsResponse() + .assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision)) + .assertAdded(addedFiles -> assertThat(addedFiles) + .hasSize(1) + .containsExactly(fileName)) + .assertRemoved(removedFiles -> assertThat(removedFiles) + .hasSize(0)) + .assertModified(files -> assertThat(files) + .hasSize(0)); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldFindRemovedModifications() throws IOException { + RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder); + String fileName = "a.txt"; + RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "a"); + Changeset changeset = RepositoryUtil.removeAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName); + + String revision = changeset.getId(); + repositoryGetRequest + .usingRepositoryResponse() + .requestChangesets() + .assertStatusCode(HttpStatus.SC_OK) + .usingChangesetsResponse() + .requestModifications(revision) + .assertStatusCode(HttpStatus.SC_OK) + .usingModificationsResponse() + .assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision)) + .assertRemoved(removedFiles -> assertThat(removedFiles) + .hasSize(1) + .containsExactly(fileName)) + .assertAdded(addedFiles -> assertThat(addedFiles) + .hasSize(0)) + .assertModified(files -> assertThat(files) + .hasSize(0)); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldFindUpdateModifications() throws IOException { + RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder); + String fileName = "a.txt"; + RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "a"); + Changeset changeset = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "new Content"); + + String revision = changeset.getId(); + repositoryGetRequest + .usingRepositoryResponse() + .requestChangesets() + .assertStatusCode(HttpStatus.SC_OK) + .usingChangesetsResponse() + .requestModifications(revision) + .assertStatusCode(HttpStatus.SC_OK) + .usingModificationsResponse() + .assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision)) + .assertModified(modifiedFiles -> assertThat(modifiedFiles) + .hasSize(1) + .containsExactly(fileName)) + .assertRemoved(removedFiles -> assertThat(removedFiles) + .hasSize(0)) + .assertAdded(addedFiles -> assertThat(addedFiles) + .hasSize(0)); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldFindMultipleModifications() throws IOException { + RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder); + RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "b.txt", "b"); + RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "c.txt", "c"); + RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "d.txt", "d"); + Map addedFiles = new HashMap() + {{ + put("a.txt", "bla bla"); + }}; + Map modifiedFiles = new HashMap() + {{ + put("b.txt", "new content"); + }}; + ArrayList 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 addedFiles = new HashMap() + {{ + put("aaa/aa/a.txt", "bla bla"); + }}; + Map modifiedFiles = new HashMap() + {{ + put("bbb/bb/b.txt", "new content"); + }}; + ArrayList 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")); + } } diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoryRequests.java b/scm-it/src/test/java/sonia/scm/it/RepositoryRequests.java index 41fa2a6a3d..79300d7b45 100644 --- a/scm-it/src/test/java/sonia/scm/it/RepositoryRequests.java +++ b/scm-it/src/test/java/sonia/scm/it/RepositoryRequests.java @@ -156,7 +156,7 @@ public class RepositoryRequests { return new AppliedGetSourcesRequest(getResponseFromLink(repositoryResponse, "_links.sources.href")); } - AppliedGetChangesetsRequest requestChangesets(String fileName) { + AppliedGetChangesetsRequest requestChangesets() { return new AppliedGetChangesetsRequest(getResponseFromLink(repositoryResponse, "_links.changesets.href")); } } @@ -189,6 +189,9 @@ public class RepositoryRequests { return new AppliedGetDiffRequest(getResponseFromLink(changesetsResponse, "_embedded.changesets.find{it.id=='" + revision + "'}._links.diff.href")); } + public AppliedGetModificationsRequest requestModifications(String revision) { + return new AppliedGetModificationsRequest(getResponseFromLink(changesetsResponse, "_embedded.changesets.find{it.id=='" + revision + "'}._links.modifications.href")); + } } class AppliedGetSourcesRequest extends AppliedGetRequest { @@ -246,4 +249,45 @@ public class RepositoryRequests { return new GivenWithUrlAndAuth(); } } + + class AppliedGetModificationsRequest extends AppliedGetRequest { + 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 assertRevision) { + String revision = resource.then().extract().path("revision"); + assertRevision.accept(revision); + return this; + } + + ModificationsResponse assertAdded(Consumer> assertAdded) { + List added = resource.then().extract().path("added"); + assertAdded.accept(added); + return this; + } + + ModificationsResponse assertRemoved(Consumer> assertRemoved) { + List removed = resource.then().extract().path("removed"); + assertRemoved.accept(removed); + return this; + } + + ModificationsResponse assertModified(Consumer> assertModified) { + List modified = resource.then().extract().path("modified"); + assertModified.accept(modified); + return this; + } + + } } diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoryUtil.java b/scm-it/src/test/java/sonia/scm/it/RepositoryUtil.java index 0e2eb24ca9..96b92a0bed 100644 --- a/scm-it/src/test/java/sonia/scm/it/RepositoryUtil.java +++ b/scm-it/src/test/java/sonia/scm/it/RepositoryUtil.java @@ -14,6 +14,8 @@ import sonia.scm.repository.client.api.RepositoryClientFactory; import java.io.File; import java.io.IOException; +import java.util.List; +import java.util.Map; import java.util.UUID; public class RepositoryUtil { @@ -42,11 +44,53 @@ public class RepositoryUtil { } static Changeset createAndCommitFile(RepositoryClient repositoryClient, String username, String fileName, String content) throws IOException { + writeAndAddFile(repositoryClient, fileName, content); + return commit(repositoryClient, username, "added " + fileName); + } + + /** + * Bundle multiple File modification in one changeset + * + * @param repositoryClient + * @param username + * @param addedFiles map.key: path of the file, value: the file content + * @param modifiedFiles map.key: path of the file, value: the file content + * @param removedFiles list of file paths to be removed + * @return the changeset with all modifications + * @throws IOException + */ + static Changeset commitMultipleFileModifications(RepositoryClient repositoryClient, String username, Map addedFiles, Map modifiedFiles, List removedFiles) throws IOException { + for (String fileName : addedFiles.keySet()) { + writeAndAddFile(repositoryClient, fileName, addedFiles.get(fileName)); + } + for (String fileName : modifiedFiles.keySet()) { + writeAndAddFile(repositoryClient, fileName, modifiedFiles.get(fileName)); + } + for (String fileName : removedFiles) { + deleteFileAndApplyRemoveCommand(repositoryClient, fileName); + } + return commit(repositoryClient, username, "multiple file modifications" ); + } + + private static File writeAndAddFile(RepositoryClient repositoryClient, String fileName, String content) throws IOException { File file = new File(repositoryClient.getWorkingCopy(), fileName); Files.createParentDirs(file); Files.write(content, file, Charsets.UTF_8); addWithParentDirectories(repositoryClient, file); - return commit(repositoryClient, username, "added " + fileName); + return file; + } + + static Changeset removeAndCommitFile(RepositoryClient repositoryClient, String username, String fileName) throws IOException { + deleteFileAndApplyRemoveCommand(repositoryClient, fileName); + return commit(repositoryClient, username, "removed " + fileName); + } + + private static void deleteFileAndApplyRemoveCommand(RepositoryClient repositoryClient, String fileName) throws IOException { + File file = new File(repositoryClient.getWorkingCopy(), fileName); + if (repositoryClient.isCommandSupported(ClientCommand.REMOVE)) { + repositoryClient.getRemoveCommand().remove(fileName); + } + file.delete(); } private static String addWithParentDirectories(RepositoryClient repositoryClient, File file) throws IOException { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java index a59e3b5754..6936c51269 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java @@ -37,32 +37,25 @@ package sonia.scm.repository; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; - -import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.treewalk.EmptyTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.util.Util; -//~--- JDK imports ------------------------------------------------------------ - import java.io.Closeable; import java.io.IOException; - import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -224,13 +217,6 @@ public class GitChangesetConverter implements Closeable changeset.setParents(parentList); } - Modifications modifications = createModifications(treeWalk, commit); - - if (modifications != null) - { - changeset.setModifications(modifications); - } - Collection tagCollection = tags.get(commit.getId()); if (Util.isNotEmpty(tagCollection)) @@ -245,108 +231,7 @@ public class GitChangesetConverter implements Closeable return changeset; } - /** - * TODO: copy and rename - * - * - * @param modifications - * @param entry - */ - private void appendModification(Modifications modifications, DiffEntry entry) - { - switch (entry.getChangeType()) - { - case ADD : - modifications.getAdded().add(entry.getNewPath()); - break; - - case MODIFY : - modifications.getModified().add(entry.getNewPath()); - - break; - - case DELETE : - modifications.getRemoved().add(entry.getOldPath()); - - break; - } - } - - /** - * Method description - * - * - * @param treeWalk - * @param commit - * - * @return - * - * @throws IOException - */ - private Modifications createModifications(TreeWalk treeWalk, RevCommit commit) - throws IOException - { - Modifications modifications = null; - - treeWalk.reset(); - treeWalk.setRecursive(true); - - if (commit.getParentCount() > 0) - { - RevCommit parent = commit.getParent(0); - RevTree tree = parent.getTree(); - - if ((tree == null) && (revWalk != null)) - { - revWalk.parseHeaders(parent); - tree = parent.getTree(); - } - - if (tree != null) - { - treeWalk.addTree(tree); - } - else - { - if (logger.isTraceEnabled()) - { - logger.trace("no parent tree at position 0 for commit {}", - commit.getName()); - } - - treeWalk.addTree(new EmptyTreeIterator()); - } - } - else - { - if (logger.isTraceEnabled()) - { - logger.trace("no parent available for commit {}", commit.getName()); - } - - treeWalk.addTree(new EmptyTreeIterator()); - } - - treeWalk.addTree(commit.getTree()); - - List 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 --------------------------------------------------------------- diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModificationsCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModificationsCommand.java new file mode 100644 index 0000000000..2b35ba74f6 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModificationsCommand.java @@ -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 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)); + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java index 7ea26de83f..29744a8e9b 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java @@ -36,17 +36,15 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.collect.ImmutableSet; - import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.Repository; import sonia.scm.repository.api.Command; -//~--- JDK imports ------------------------------------------------------------ - import java.io.IOException; - import java.util.Set; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -188,6 +186,11 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider return new GitLogCommand(context, repository); } + @Override + public ModificationsCommand getModificationsCommand() { + return new GitModificationsCommand(context,repository); + } + /** * Method description * diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/UnsupportedModificationTypeException.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/UnsupportedModificationTypeException.java new file mode 100644 index 0000000000..5081a29d21 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/UnsupportedModificationTypeException.java @@ -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); + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractRemoteCommandTestBase.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractRemoteCommandTestBase.java index e2a401bf7d..97e09c0708 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractRemoteCommandTestBase.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractRemoteCommandTestBase.java @@ -85,14 +85,14 @@ public class AbstractRemoteCommandTestBase outgoingDirectory = tempFolder.newFile("outgoing"); outgoingDirectory.delete(); - incomgingRepository = new Repository("1", "git", "space", "incoming"); + incomingRepository = new Repository("1", "git", "space", "incoming"); outgoingRepository = new Repository("2", "git", "space", "outgoing"); incoming = Git.init().setDirectory(incomingDirectory).setBare(false).call(); outgoing = Git.init().setDirectory(outgoingDirectory).setBare(false).call(); handler = mock(GitRepositoryHandler.class); - when(handler.getDirectory(incomgingRepository)).thenReturn( + when(handler.getDirectory(incomingRepository)).thenReturn( incomingDirectory); when(handler.getDirectory(outgoingRepository)).thenReturn( outgoingDirectory); @@ -211,7 +211,7 @@ public class AbstractRemoteCommandTestBase protected GitRepositoryHandler handler; /** Field description */ - protected Repository incomgingRepository; + protected Repository incomingRepository; /** Field description */ protected Git incoming; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java index 6aa852b12c..e3d36601e7 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java @@ -105,7 +105,7 @@ public class GitIncomingCommandTest commit(outgoing, "added a"); - GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory), incomgingRepository); + GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory), incomingRepository); PullCommandRequest req = new PullCommandRequest(); req.setRemoteRepository(outgoingRepository); pull.pull(req); @@ -192,6 +192,6 @@ public class GitIncomingCommandTest private GitIncomingCommand createCommand() { return new GitIncomingCommand(handler, new GitContext(incomingDirectory), - incomgingRepository); + incomingRepository); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java index 51df1298e5..d6e6ac98d8 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java @@ -168,21 +168,23 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase Changeset c = command.getChangeset("435df2f061add3589cb3"); assertNotNull(c); - assertEquals("435df2f061add3589cb326cc64be9b9c3897ceca", c.getId()); + String revision = "435df2f061add3589cb326cc64be9b9c3897ceca"; + assertEquals(revision, c.getId()); assertEquals("added a and b files", c.getDescription()); checkDate(c.getDate()); assertEquals("Douglas Adams", c.getAuthor().getName()); assertEquals("douglas.adams@hitchhiker.com", c.getAuthor().getMail()); assertEquals("added a and b files", c.getDescription()); - Modifications mods = c.getModifications(); + GitModificationsCommand gitModificationsCommand = new GitModificationsCommand(createContext(), repository); + Modifications modifications = gitModificationsCommand.getModifications(revision); - assertNotNull(mods); - assertTrue("modified list should be empty", mods.getModified().isEmpty()); - assertTrue("removed list should be empty", mods.getRemoved().isEmpty()); - assertFalse("added list should not be empty", mods.getAdded().isEmpty()); - assertEquals(2, mods.getAdded().size()); - assertThat(mods.getAdded(), contains("a.txt", "b.txt")); + assertNotNull(modifications); + assertTrue("modified list should be empty", modifications.getModified().isEmpty()); + assertTrue("removed list should be empty", modifications.getRemoved().isEmpty()); + assertFalse("added list should not be empty", modifications.getAdded().isEmpty()); + assertEquals(2, modifications.getAdded().size()); + assertThat(modifications.getAdded(), contains("a.txt", "b.txt")); } @Test diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModificationsCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModificationsCommandTest.java new file mode 100644 index 0000000000..fb982f6f0c --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModificationsCommandTest.java @@ -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 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 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 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 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 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 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); + }; + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java index 58f70109ef..3510650fb4 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java @@ -78,7 +78,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase GitOutgoingCommand cmd = createCommand(); OutgoingCommandRequest request = new OutgoingCommandRequest(); - request.setRemoteRepository(incomgingRepository); + request.setRemoteRepository(incomingRepository); ChangesetPagingResult cpr = cmd.getOutgoingChangesets(request); @@ -98,7 +98,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase * @throws RepositoryException */ @Test - public void testGetOutgoingChangesetsWithAllreadyPushedChanges() + public void testGetOutgoingChangesetsWithAlreadyPushedChanges() throws IOException, GitAPIException { write(outgoing, outgoingDirectory, "a.txt", "content of a.txt"); @@ -110,7 +110,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase outgoingRepository); PushCommandRequest req = new PushCommandRequest(); - req.setRemoteRepository(incomgingRepository); + req.setRemoteRepository(incomingRepository); push.push(req); write(outgoing, outgoingDirectory, "b.txt", "content of b.txt"); @@ -120,7 +120,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase GitOutgoingCommand cmd = createCommand(); OutgoingCommandRequest request = new OutgoingCommandRequest(); - request.setRemoteRepository(incomgingRepository); + request.setRemoteRepository(incomingRepository); ChangesetPagingResult cpr = cmd.getOutgoingChangesets(request); @@ -144,7 +144,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase GitOutgoingCommand cmd = createCommand(); OutgoingCommandRequest request = new OutgoingCommandRequest(); - request.setRemoteRepository(incomgingRepository); + request.setRemoteRepository(incomingRepository); ChangesetPagingResult cpr = cmd.getOutgoingChangesets(request); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitPushCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitPushCommandTest.java index 91fad57880..4f3d7e933d 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitPushCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitPushCommandTest.java @@ -78,7 +78,7 @@ public class GitPushCommandTest extends AbstractRemoteCommandTestBase GitPushCommand cmd = createCommand(); PushCommandRequest request = new PushCommandRequest(); - request.setRemoteRepository(incomgingRepository); + request.setRemoteRepository(incomingRepository); PushResponse response = cmd.push(request); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModificationsCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModificationsCommand.java new file mode 100644 index 0000000000..c67b9ff5d9 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModificationsCommand.java @@ -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()); + } + + +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java index 4295bf45e4..7749b7e40a 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java @@ -42,6 +42,7 @@ import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.Repository; import sonia.scm.repository.api.Command; +import sonia.scm.repository.api.CommandNotSupportedException; //~--- JDK imports ------------------------------------------------------------ @@ -201,6 +202,16 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider return new HgLogCommand(context, repository); } + /** + * Get the corresponding {@link ModificationsCommand} implemented from the Plugins + * + * @return the corresponding {@link ModificationsCommand} implemented from the Plugins + * @throws CommandNotSupportedException if there is no Implementation + */ + public ModificationsCommand getModificationsCommand() { + return new HgModificationsCommand(context,repository); + } + /** * Method description * diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/AbstractChangesetCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/AbstractChangesetCommand.java index 827f86ded1..6466eb6d11 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/AbstractChangesetCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/AbstractChangesetCommand.java @@ -41,21 +41,18 @@ import com.aragost.javahg.internals.AbstractCommand; import com.aragost.javahg.internals.HgInputStream; import com.aragost.javahg.internals.RuntimeIOException; import com.aragost.javahg.internals.Utils; - import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; - import sonia.scm.repository.Changeset; import sonia.scm.repository.HgConfig; import sonia.scm.repository.Modifications; import sonia.scm.repository.Person; -//~--- JDK imports ------------------------------------------------------------ - import java.io.IOException; - import java.util.List; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -251,33 +248,14 @@ public abstract class AbstractChangesetCommand extends AbstractCommand changeset.getProperties().put(PROPERTY_CLOSE, "true"); } - Modifications modifications = changeset.getModifications(); - String line = in.textUpTo('\n'); - - while (line.length() > 0) - { - - if (line.startsWith("a ")) - { - modifications.getAdded().add(line.substring(2)); - } - else if (line.startsWith("m ")) - { - modifications.getModified().add(line.substring(2)); - } - else if (line.startsWith("d ")) - { - modifications.getRemoved().add(line.substring(2)); - } - else if (line.startsWith("t ")) + while (line.length() > 0) { + if (line.startsWith("t ")) { changeset.getTags().add(line.substring(2)); } - line = in.textUpTo('\n'); } - String message = in.textUpTo('\0'); changeset.setDescription(message); @@ -285,6 +263,36 @@ public abstract class AbstractChangesetCommand extends AbstractCommand return changeset; } + protected Modifications readModificationsFromStream(HgInputStream in) { + try { + boolean found = in.find(CHANGESET_PATTERN); + if (found) { + while (!in.match(CHANGESET_PATTERN)) { + return extractModifications(in); + } + } + } catch (IOException e) { + throw new RuntimeIOException(e); + } + return null; + } + + private Modifications extractModifications(HgInputStream in) throws IOException { + Modifications modifications = new Modifications(); + String line = in.textUpTo('\n'); + while (line.length() > 0) { + if (line.startsWith("a ")) { + modifications.getAdded().add(line.substring(2)); + } else if (line.startsWith("m ")) { + modifications.getModified().add(line.substring(2)); + } else if (line.startsWith("d ")) { + modifications.getRemoved().add(line.substring(2)); + } + line = in.textUpTo('\n'); + } + return modifications; + } + /** * Method description * diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgLogChangesetCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgLogChangesetCommand.java index 4c62fb4f93..f57c2a63d9 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgLogChangesetCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgLogChangesetCommand.java @@ -38,14 +38,14 @@ package sonia.scm.repository.spi.javahg; import com.aragost.javahg.Repository; import com.aragost.javahg.internals.HgInputStream; import com.aragost.javahg.internals.Utils; - import sonia.scm.repository.Changeset; import sonia.scm.repository.HgConfig; - -//~--- JDK imports ------------------------------------------------------------ +import sonia.scm.repository.Modifications; import java.util.List; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -106,11 +106,22 @@ public class HgLogChangesetCommand extends AbstractChangesetCommand */ public List 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 loadRevisions(String... files) { - cmdAppend("--style", CHANGESET_LAZY_STYLE_PATH); - - HgInputStream stream = launchStream(files); - - return loadRevisionsFromStream(stream); + return loadRevisionsFromStream(getHgInputStream(files, CHANGESET_LAZY_STYLE_PATH)); } /** diff --git a/scm-plugins/scm-hg-plugin/src/main/js/HgAvatar.js b/scm-plugins/scm-hg-plugin/src/main/js/HgAvatar.js index b2348b54cc..ce10ec293c 100644 --- a/scm-plugins/scm-hg-plugin/src/main/js/HgAvatar.js +++ b/scm-plugins/scm-hg-plugin/src/main/js/HgAvatar.js @@ -1,6 +1,6 @@ //@flow import React from "react"; -import { Image } from "@scm-manager/ui-components"; +import {Image} from "@scm-manager/ui-components"; type Props = { }; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/client/spi/HgCommitCommand.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/client/spi/HgCommitCommand.java index aa611a8ba4..b8967ed15b 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/client/spi/HgCommitCommand.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/client/spi/HgCommitCommand.java @@ -32,11 +32,11 @@ package sonia.scm.repository.client.spi; import com.aragost.javahg.Repository; import com.google.common.collect.Lists; -import java.io.IOException; import sonia.scm.repository.Changeset; -import sonia.scm.repository.Modifications; import sonia.scm.repository.Person; +import java.io.IOException; + /** * Mercurial implementation of the {@link CommitCommand}. * @@ -70,9 +70,6 @@ public class HgCommitCommand implements CommitCommand changeset.setBranches(Lists.newArrayList(c.getBranch())); changeset.setTags(c.tags()); - changeset.setModifications( - new Modifications(c.getAddedFiles(), c.getModifiedFiles(), c.getDeletedFiles()) - ); return changeset; } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgLogCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgLogCommandTest.java index c4d49ba8b5..99e9fc191a 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgLogCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgLogCommandTest.java @@ -39,6 +39,9 @@ import org.junit.Test; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.Modifications; +import sonia.scm.repository.RevisionNotFoundException; + +import java.io.IOException; import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertEquals; @@ -133,27 +136,28 @@ public class HgLogCommandTest extends AbstractHgCommandTestBase } @Test - public void testGetCommit() { + public void testGetCommit() throws IOException, RevisionNotFoundException { HgLogCommand command = createComamnd(); + String revision = "a9bacaf1b7fa0cebfca71fed4e59ed69a6319427"; Changeset c = - command.getChangeset("a9bacaf1b7fa0cebfca71fed4e59ed69a6319427"); + command.getChangeset(revision); assertNotNull(c); - assertEquals("a9bacaf1b7fa0cebfca71fed4e59ed69a6319427", c.getId()); + assertEquals(revision, c.getId()); assertEquals("added a and b files", c.getDescription()); checkDate(c.getDate()); assertEquals("Douglas Adams", c.getAuthor().getName()); assertEquals("douglas.adams@hitchhiker.com", c.getAuthor().getMail()); assertEquals("added a and b files", c.getDescription()); + ModificationsCommand modificationsCommand = new HgModificationsCommand(cmdContext, repository); + Modifications modifications = modificationsCommand.getModifications(revision); - Modifications mods = c.getModifications(); - - assertNotNull(mods); - assertTrue("modified list should be empty", mods.getModified().isEmpty()); - assertTrue("removed list should be empty", mods.getRemoved().isEmpty()); - assertFalse("added list should not be empty", mods.getAdded().isEmpty()); - assertEquals(2, mods.getAdded().size()); - assertThat(mods.getAdded(), contains("a.txt", "b.txt")); + assertNotNull(modifications); + assertTrue("modified list should be empty", modifications.getModified().isEmpty()); + assertTrue("removed list should be empty", modifications.getRemoved().isEmpty()); + assertFalse("added list should not be empty", modifications.getAdded().isEmpty()); + assertEquals(2, modifications.getAdded().size()); + assertThat(modifications.getAdded(), contains("a.txt", "b.txt")); } @Test diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModificationsCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModificationsCommandTest.java new file mode 100644 index 0000000000..eae8319b89 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModificationsCommandTest.java @@ -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 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 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 assertModifications = assertRemovedFiles(fileName); + assertModifications.accept(outgoingModificationsCommand.getModifications(revision)); + } + + + Consumer 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 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 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); + }; + } +} diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnUtil.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnUtil.java index eb613e6144..480026c27a 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnUtil.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnUtil.java @@ -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.io.SVNRepository; import org.tmatesoft.svn.core.wc.SVNClientManager; -import org.tmatesoft.svn.core.wc.admin.SVNChangeEntry; import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; @@ -103,30 +102,37 @@ public final class SvnUtil //~--- methods -------------------------------------------------------------- - /** - * TODO: type replaced - * - * - * @param modifications - * @param entry - */ - public static void appendModification(Modifications modifications, - SVNLogEntryPath entry) - { - appendModification(modifications, entry.getType(), entry.getPath()); + public static 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; } - /** - * Method description - * - * - * @param modifications - * @param entry - */ - public static void appendModification(Modifications modifications, - SVNChangeEntry entry) - { - appendModification(modifications, entry.getType(), entry.getPath()); + + public static Modifications createModifications(SVNLogEntry entry, String revision) { + Modifications modifications = new Modifications(); + modifications.setRevision(revision); + Map changeMap = entry.getChangedPaths(); + + if (Util.isNotEmpty(changeMap)) { + + for (SVNLogEntryPath e : changeMap.values()) { + appendModification(modifications, e.getType(), e.getPath()); + } + } + return modifications; } /** @@ -210,19 +216,6 @@ public final class SvnUtil { changeset.getParents().add(String.valueOf(revision - 1)); } - - Map changeMap = entry.getChangedPaths(); - - if (Util.isNotEmpty(changeMap)) - { - Modifications modifications = changeset.getModifications(); - - for (SVNLogEntryPath e : changeMap.values()) - { - appendModification(modifications, e); - } - } - return changeset; } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLogCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLogCommand.java index ededb4e1f0..332dcb55a6 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLogCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLogCommand.java @@ -54,6 +54,8 @@ import sonia.scm.util.Util; import java.util.Collection; import java.util.List; +import static sonia.scm.repository.SvnUtil.parseRevision; + //~--- JDK imports ------------------------------------------------------------ public class SvnLogCommand extends AbstractSvnCommand implements LogCommand @@ -144,25 +146,6 @@ public class SvnLogCommand extends AbstractSvnCommand implements LogCommand 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 ---------------------------------------------------------- diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java new file mode 100644 index 0000000000..e6cedc8ebf --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java @@ -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 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()); + } + + +} diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnPreReceiveHookChangesetProvier.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnPreReceiveHookChangesetProvier.java index 93fb88f841..e4efdb71ab 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnPreReceiveHookChangesetProvier.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnPreReceiveHookChangesetProvier.java @@ -37,22 +37,19 @@ package sonia.scm.repository.spi; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.tmatesoft.svn.core.SVNLogEntry; import org.tmatesoft.svn.core.wc.ISVNOptions; import org.tmatesoft.svn.core.wc.SVNClientManager; import org.tmatesoft.svn.core.wc.SVNWCUtil; import org.tmatesoft.svn.core.wc.admin.SVNLookClient; - import sonia.scm.repository.Changeset; import sonia.scm.repository.RepositoryHookType; -import sonia.scm.repository.SvnModificationHandler; import sonia.scm.repository.SvnUtil; -//~--- JDK imports ------------------------------------------------------------ - import java.io.File; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -123,10 +120,6 @@ public class SvnPreReceiveHookChangesetProvier { changeset = SvnUtil.createChangeset(entry); changeset.setId(SvnUtil.createTransactionEntryId(transaction)); - - clientManager.doGetChanged(repositoryDirectory, transaction, - new SvnModificationHandler(changeset), true); - } else if (logger.isWarnEnabled()) { diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java index 24180bfe9e..1fda21f0d8 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnRepositoryServiceProvider.java @@ -167,6 +167,10 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider return new SvnLogCommand(context, repository); } + public ModificationsCommand getModificationsCommand() { + return new SvnModificationsCommand(context, repository); + } + /** * Method description * diff --git a/scm-plugins/scm-svn-plugin/src/main/js/SvnAvatar.js b/scm-plugins/scm-svn-plugin/src/main/js/SvnAvatar.js index 9996cbbfe0..731c9ddf33 100644 --- a/scm-plugins/scm-svn-plugin/src/main/js/SvnAvatar.js +++ b/scm-plugins/scm-svn-plugin/src/main/js/SvnAvatar.js @@ -1,6 +1,6 @@ //@flow import React from "react"; -import { Image } from "@scm-manager/ui-components"; +import {Image} from "@scm-manager/ui-components"; type Props = { }; diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/client/spi/SvnChangeWorker.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/client/spi/SvnChangeWorker.java index b54e51c4d6..302ef3e8aa 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/client/spi/SvnChangeWorker.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/client/spi/SvnChangeWorker.java @@ -70,13 +70,15 @@ class SvnChangeWorker { SVNWCClient wClient = client.getWCClient(); // add files - try { - wClient.doAdd(addedFiles.toArray(new File[0]), true, false, false, - SVNDepth.INFINITY, false, false, false); - addedFiles.clear(); + if (!addedFiles.isEmpty()){ + try { + wClient.doAdd(addedFiles.toArray(new File[0]), true, false, false, + SVNDepth.INFINITY, false, false, false); + addedFiles.clear(); - } catch (SVNException ex) { - throw new RepositoryClientException("failed to add files", ex); + } catch (SVNException ex) { + throw new RepositoryClientException("failed to add files", ex); + } } // remove files diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLogCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLogCommandTest.java index 941687190c..a55138f151 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLogCommandTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLogCommandTest.java @@ -40,6 +40,8 @@ import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.Modifications; import sonia.scm.repository.RevisionNotFoundException; +import java.io.IOException; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -128,7 +130,7 @@ public class SvnLogCommandTest extends AbstractSvnCommandTestBase } @Test - public void testGetCommit() throws RevisionNotFoundException { + public void testGetCommit() throws RevisionNotFoundException, IOException { Changeset c = createCommand().getChangeset("3"); assertNotNull(c); @@ -137,15 +139,15 @@ public class SvnLogCommandTest extends AbstractSvnCommandTestBase checkDate(c.getDate()); assertEquals("perfect", c.getAuthor().getName()); assertNull("douglas.adams@hitchhiker.com", c.getAuthor().getMail()); + SvnModificationsCommand modificationsCommand = new SvnModificationsCommand(createContext(), repository); + Modifications modifications = modificationsCommand.getModifications("3"); - Modifications mods = c.getModifications(); - - assertNotNull(mods); - assertEquals(1, mods.getModified().size()); - assertEquals(1, mods.getRemoved().size()); - assertTrue("added list should be empty", mods.getAdded().isEmpty()); - assertEquals("a.txt", mods.getModified().get(0)); - assertEquals("b.txt", mods.getRemoved().get(0)); + assertNotNull(modifications); + assertEquals(1, modifications.getModified().size()); + assertEquals(1, modifications.getRemoved().size()); + assertTrue("added list should be empty", modifications.getAdded().isEmpty()); + assertEquals("a.txt", modifications.getModified().get(0)); + assertEquals("b.txt", modifications.getRemoved().get(0)); } @Test diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 761a28730c..da01814152 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -254,7 +254,6 @@ tika-core 1.18 - diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java index a8dc3f3968..a189dcec97 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java @@ -65,7 +65,8 @@ public abstract class ChangesetToChangesetDtoMapper implements InstantAttributeM Links.Builder linksBuilder = linkingTo() .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()); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java index 9cadbfb6ff..bebd7def23 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java @@ -34,6 +34,7 @@ public class MapperModule extends AbstractModule { bind(TagToTagDtoMapper.class).to(Mappers.getMapper(TagToTagDtoMapper.class).getClass()); bind(FileObjectToFileObjectDtoMapper.class).to(Mappers.getMapper(FileObjectToFileObjectDtoMapper.class).getClass()); + bind(ModificationsToDtoMapper.class).to(Mappers.getMapper(ModificationsToDtoMapper.class).getClass()); // no mapstruct required bind(UIPluginDtoMapper.class); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ModificationsDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ModificationsDto.java new file mode 100644 index 0000000000..9ea0359157 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ModificationsDto.java @@ -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 added; + + /** + * list of modified files + */ + private List modified; + + /** + * list of removed files + */ + private List removed; + + @Override + @SuppressWarnings("squid:S1185") // We want to have this method available in this package + protected HalRepresentation add(Links links) { + return super.add(links); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ModificationsRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ModificationsRootResource.java new file mode 100644 index 0000000000..28f855f40c --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ModificationsRootResource.java @@ -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(); + } + } + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ModificationsToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ModificationsToDtoMapper.java new file mode 100644 index 0000000000..422d8fc4d9 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ModificationsToDtoMapper.java @@ -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()); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java index 1585065b91..f9328525a9 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java @@ -40,6 +40,7 @@ public class RepositoryResource { private final Provider contentResource; private final Provider permissionRootResource; private final Provider diffRootResource; + private final Provider modificationsRootResource; private final Provider fileHistoryRootResource; @Inject @@ -52,7 +53,9 @@ public class RepositoryResource { Provider sourceRootResource, Provider contentResource, Provider permissionRootResource, Provider diffRootResource, - Provider fileHistoryRootResource) { + Provider modificationsRootResource, + Provider fileHistoryRootResource + ) { this.dtoToRepositoryMapper = dtoToRepositoryMapper; this.manager = manager; this.repositoryToDtoMapper = repositoryToDtoMapper; @@ -64,6 +67,7 @@ public class RepositoryResource { this.contentResource = contentResource; this.permissionRootResource = permissionRootResource; this.diffRootResource = diffRootResource; + this.modificationsRootResource = modificationsRootResource; this.fileHistoryRootResource = fileHistoryRootResource; } @@ -188,6 +192,9 @@ public class RepositoryResource { return permissionRootResource.get(); } + @Path("modifications/") + public ModificationsRootResource modifications() {return modificationsRootResource.get(); } + private Optional handleNotArchived(Throwable throwable) { if (throwable instanceof RepositoryIsNotArchivedException) { return Optional.of(Response.status(Response.Status.PRECONDITION_FAILED).build()); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index 3d61083033..2e7202e981 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -311,6 +311,21 @@ class ResourceLinks { } } + public ModificationsLinks modifications() { + return new ModificationsLinks(uriInfoStore.get()); + } + + static class ModificationsLinks { + private final LinkBuilder modificationsLinkBuilder; + + ModificationsLinks(UriInfo 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() { return new FileHistoryLinks(uriInfoStore.get()); } diff --git a/scm-webapp/src/test/java/sonia/scm/WebResourceServletTest.java b/scm-webapp/src/test/java/sonia/scm/WebResourceServletTest.java index b3864d894f..7da9c7b263 100644 --- a/scm-webapp/src/test/java/sonia/scm/WebResourceServletTest.java +++ b/scm-webapp/src/test/java/sonia/scm/WebResourceServletTest.java @@ -19,8 +19,15 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +import static org.junit.Assert.assertEquals; +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) public class WebResourceServletTest { diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java index 0a209e905f..c8d25ae82d 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java @@ -44,7 +44,7 @@ import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) @Slf4j -public class BranchRootResourceTest { +public class BranchRootResourceTest extends RepositoryTestBase { public static final String BRANCH_PATH = "space/repo/branches/master"; public static final String BRANCH_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + BRANCH_PATH; @@ -92,9 +92,8 @@ public class BranchRootResourceTest { changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks); BranchCollectionToDtoMapper branchCollectionToDtoMapper = new BranchCollectionToDtoMapper(branchToDtoMapper, resourceLinks); branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper, changesetCollectionToDtoMapper); - RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider.of(new RepositoryResource(null, null, null, null, MockProvider.of(branchRootResource), null, null, null, null, null, null)), null); - dispatcher.getRegistry().addSingletonResource(repositoryRootResource); - + super.branchRootResource = MockProvider.of(branchRootResource); + dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource()); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); when(serviceFactory.create(any(Repository.class))).thenReturn(service); when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java index a7bad96534..e4e991a7d4 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java @@ -44,7 +44,7 @@ import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) @Slf4j -public class ChangesetRootResourceTest { +public class ChangesetRootResourceTest extends RepositoryTestBase { public static final String CHANGESET_PATH = "space/repo/changesets/"; @@ -79,10 +79,8 @@ public class ChangesetRootResourceTest { public void prepareEnvironment() throws Exception { changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks); changesetRootResource = new ChangesetRootResource(serviceFactory, changesetCollectionToDtoMapper, changesetToChangesetDtoMapper); - RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider - .of(new RepositoryResource(null, null, null, null, null, - MockProvider.of(changesetRootResource), null, null, null, null, null)), null); - dispatcher.getRegistry().addSingletonResource(repositoryRootResource); + super.changesetRootResource = MockProvider.of(changesetRootResource); + 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")); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java index 2613292e0b..f6a8fa4e09 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java @@ -37,7 +37,7 @@ import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) @Slf4j -public class DiffResourceTest { +public class DiffResourceTest extends RepositoryTestBase { public static final String DIFF_PATH = "space/repo/diff/"; @@ -63,10 +63,8 @@ public class DiffResourceTest { @Before public void prepareEnvironment() throws Exception { diffRootResource = new DiffRootResource(serviceFactory); - RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider - .of(new RepositoryResource(null, null, null, null, null, - null, null, null, null, MockProvider.of(diffRootResource),null)), null); - dispatcher.getRegistry().addSingletonResource(repositoryRootResource); + super.diffRootResource = MockProvider.of(diffRootResource); + dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource()); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); when(serviceFactory.create(any(Repository.class))).thenReturn(service); when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java index 4d4ed435df..3505d00dc6 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java @@ -14,6 +14,7 @@ public class DispatcherMock { dispatcher.getProviderFactory().registerProvider(AlreadyExistsExceptionMapper.class); dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); dispatcher.getProviderFactory().registerProvider(ConcurrentModificationExceptionMapper.class); + dispatcher.getProviderFactory().registerProvider(InternalRepositoryExceptionMapper.class); return dispatcher; } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java index f89a92f124..778682f62d 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java @@ -47,7 +47,7 @@ import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) @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_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + FILE_HISTORY_PATH; @@ -81,10 +81,8 @@ public class FileHistoryResourceTest { public void prepareEnvironment() throws Exception { fileHistoryCollectionToDtoMapper = new FileHistoryCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks); fileHistoryRootResource = new FileHistoryRootResource(serviceFactory, fileHistoryCollectionToDtoMapper); - RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider - .of(new RepositoryResource(null, null, null, null, null, - null, null, null, null, null, MockProvider.of(fileHistoryRootResource))), null); - dispatcher.getRegistry().addSingletonResource(repositoryRootResource); + super.fileHistoryRootResource = MockProvider.of(fileHistoryRootResource); + dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource()); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); when(serviceFactory.create(any(Repository.class))).thenReturn(service); when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java new file mode 100644 index 0000000000..f8b555f180 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java @@ -0,0 +1,149 @@ +package sonia.scm.api.v2.resources; + +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 = MockProvider.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))); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java index 3f2ea9b317..ac5ad07cf8 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java @@ -64,7 +64,7 @@ import static sonia.scm.api.v2.resources.PermissionDto.GROUP_PREFIX; password = "secret", 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_NAME = "repo"; private static final String PERMISSION_WRITE = "repository:permissionWrite:" + REPOSITORY_NAME; @@ -137,9 +137,8 @@ public class PermissionRootResourceTest { initMocks(this); permissionCollectionToDtoMapper = new PermissionCollectionToDtoMapper(permissionToPermissionDtoMapper, resourceLinks); permissionRootResource = new PermissionRootResource(permissionDtoToPermissionMapper, permissionToPermissionDtoMapper, permissionCollectionToDtoMapper, resourceLinks, repositoryManager); - RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider - .of(new RepositoryResource(null, null, null, null, null, null, null, null, MockProvider.of(permissionRootResource), null, null)), null); - dispatcher = createDispatcher(repositoryRootResource); + super.permissionRootResource = MockProvider.of(permissionRootResource); + dispatcher = createDispatcher(getRepositoryRootResource()); subjectThreadState.bind(); ThreadContext.bind(subject); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java index 48ca62089f..7c6deedf65 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java @@ -55,7 +55,7 @@ import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher; password = "secret", configuration = "classpath:sonia/scm/repository/shiro.ini" ) -public class RepositoryRootResourceTest { +public class RepositoryRootResourceTest extends RepositoryTestBase { private Dispatcher dispatcher; @@ -79,11 +79,12 @@ public class RepositoryRootResourceTest { @Before public void prepareEnvironment() { 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); - RepositoryCollectionResource repositoryCollectionResource = new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks); - RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider.of(repositoryResource), MockProvider.of(repositoryCollectionResource)); - dispatcher = createDispatcher(repositoryRootResource); + super.repositoryCollectionResource = MockProvider.of(new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks)); + dispatcher = createDispatcher(getRepositoryRootResource()); } @Test diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java new file mode 100644 index 0000000000..c3cc56958a --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java @@ -0,0 +1,42 @@ +package sonia.scm.api.v2.resources; + +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; + protected Provider branchRootResource; + protected Provider changesetRootResource; + protected Provider sourceRootResource; + protected Provider contentResource; + protected Provider permissionRootResource; + protected Provider diffRootResource; + protected Provider modificationsRootResource; + protected Provider fileHistoryRootResource; + protected Provider repositoryCollectionResource; + + + RepositoryRootResource getRepositoryRootResource() { + return new RepositoryRootResource(MockProvider.of(new RepositoryResource( + repositoryToDtoMapper, + dtoToRepositoryMapper, + manager, + tagRootResource, + branchRootResource, + changesetRootResource, + sourceRootResource, + contentResource, + permissionRootResource, + diffRootResource, + modificationsRootResource, + fileHistoryRootResource)), repositoryCollectionResource); + } + + +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java index 018797a7a3..1d0fac68e3 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java @@ -28,6 +28,7 @@ public class ResourceLinksMock { when(resourceLinks.config()).thenReturn(new ResourceLinks.ConfigLinks(uriInfo)); when(resourceLinks.branch()).thenReturn(new ResourceLinks.BranchLinks(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.repositoryTypeCollection()).thenReturn(new ResourceLinks.RepositoryTypeCollectionLinks(uriInfo)); when(resourceLinks.uiPluginCollection()).thenReturn(new ResourceLinks.UIPluginCollectionLinks(uriInfo)); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java index 5f35ce9cf2..4759e1ebd7 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java @@ -32,7 +32,7 @@ import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher; @RunWith(MockitoJUnitRunner.Silent.class) -public class SourceRootResourceTest { +public class SourceRootResourceTest extends RepositoryTestBase { private Dispatcher dispatcher; private final URI baseUri = URI.create("/"); @@ -63,20 +63,8 @@ public class SourceRootResourceTest { when(fileObjectToFileObjectDtoMapper.map(any(FileObject.class), any(NamespaceAndName.class), anyString())).thenReturn(dto); SourceRootResource sourceRootResource = new SourceRootResource(serviceFactory, browserResultToBrowserResultDtoMapper); - RepositoryRootResource repositoryRootResource = - new RepositoryRootResource(MockProvider.of(new RepositoryResource(null, - null, - null, - null, - null, - null, - MockProvider.of(sourceRootResource), - null, - null, - null, - null)), - null); - dispatcher = createDispatcher(repositoryRootResource); + super.sourceRootResource = MockProvider.of(sourceRootResource); + dispatcher = createDispatcher(getRepositoryRootResource()); } @Test diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java index ad4b396101..3149182d98 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java @@ -7,7 +7,6 @@ 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; @@ -35,14 +34,15 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher; @Slf4j @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_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + TAG_PATH; - private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private Dispatcher dispatcher ; private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); @@ -55,7 +55,6 @@ public class TagRootResourceTest { @Mock private TagsCommandBuilder tagsCommandBuilder; - private TagCollectionToDtoMapper tagCollectionToDtoMapper; @InjectMocks @@ -72,10 +71,8 @@ public class TagRootResourceTest { public void prepareEnvironment() throws Exception { tagCollectionToDtoMapper = new TagCollectionToDtoMapper(resourceLinks, tagToTagDtoMapper); tagRootResource = new TagRootResource(serviceFactory, tagCollectionToDtoMapper, tagToTagDtoMapper); - RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider - .of(new RepositoryResource(null, null, null, MockProvider.of(tagRootResource), null, - null, null, null, null, null, null)), null); - dispatcher.getRegistry().addSingletonResource(repositoryRootResource); + super.tagRootResource = MockProvider.of(tagRootResource); + dispatcher = createDispatcher(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"));