From 35ffc5c4e20579bd4f899afd50361a4b4b0d5442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 14 May 2020 13:20:56 +0200 Subject: [PATCH] Introduce new API for modifications New modifications includes list of 'renames'. Therefore we introduce a new base class Modification. --- .../sonia/scm/repository/Modification.java | 94 +++++++++++++++++++ .../sonia/scm/repository/Modifications.java | 81 ++++++++-------- .../java/sonia/scm/repository/spi/Differ.java | 10 +- .../spi/GitModificationsCommand.java | 35 ++++--- .../scm/repository/spi/GitLogCommandTest.java | 10 +- .../repository/spi/GitMergeCommandTest.java | 3 +- .../spi/GitModificationsCommandTest.java | 66 +++++++++++-- .../spi/HgModificationsCommand.java | 8 +- .../spi/javahg/AbstractChangesetCommand.java | 17 ++-- .../spi/javahg/HgLogChangesetCommand.java | 7 +- .../scm/repository/spi/HgLogCommandTest.java | 9 +- .../spi/HgModificationsCommandTest.java | 5 +- .../java/sonia/scm/repository/SvnUtil.java | 48 ++++------ .../spi/SvnModificationsCommand.java | 8 +- .../scm/repository/spi/SvnLogCommandTest.java | 4 +- .../api/v2/resources/ModificationsDto.java | 13 ++- .../resources/ModificationsToDtoMapper.java | 17 +++- .../resources/ModificationsResourceTest.java | 18 ++-- 18 files changed, 326 insertions(+), 127 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/repository/Modification.java diff --git a/scm-core/src/main/java/sonia/scm/repository/Modification.java b/scm-core/src/main/java/sonia/scm/repository/Modification.java new file mode 100644 index 0000000000..94d74ae722 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/Modification.java @@ -0,0 +1,94 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.io.Serializable; + +public interface Modification extends EffectedPath, Serializable { + + @Getter + @AllArgsConstructor + class Added implements Modification { + private final String path; + + @Override + public String getEffectedPath() { + return path; + } + } + + @Getter + @AllArgsConstructor + class Removed implements Modification { + private final String path; + + @Override + public String getEffectedPath() { + return path; + } + } + + @Getter + @AllArgsConstructor + class Modified implements Modification { + private final String path; + + @Override + public String getEffectedPath() { + return path; + } + } + + @Getter + @AllArgsConstructor + class Renamed implements Modification { + private final String oldPath; + private final String newPath; + + @Override + public String getEffectedPath() { + return newPath; + } + } + + @Getter + @AllArgsConstructor + class Copied implements Modification { + private final String sourcePath; + private final String targetPath; + + @Override + public String getEffectedPath() { + return targetPath; + } + } +} + +interface EffectedPath { + String getEffectedPath(); +} 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 999f103d2a..ce43fe58f8 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Modifications.java +++ b/scm-core/src/main/java/sonia/scm/repository/Modifications.java @@ -24,71 +24,74 @@ package sonia.scm.repository; -import com.google.common.collect.Lists; +import com.google.common.collect.ImmutableList; +import lombok.Builder; import lombok.EqualsAndHashCode; -import lombok.Setter; +import lombok.Getter; import lombok.ToString; import java.io.Serializable; +import java.util.Collection; import java.util.List; +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; + @EqualsAndHashCode @ToString -@Setter +@Getter public class Modifications implements Serializable { private static final long serialVersionUID = -8902033326668658140L; - private String revision; - /** - * lists of changed files - */ - private List added; - private List modified; - private List removed; + private final String revision; + private final Collection modifications; - public Modifications() { + public Modifications(String revision, Modification... modifications) { + this(revision, asList(modifications)); } - public Modifications(List added) { - this(added, null, null); + public Modifications(String revision, Collection modifications) { + this.revision = revision; + this.modifications = ImmutableList.copyOf(modifications); } - public Modifications(List added, List modified) { - this(added, modified, null); + public List getEffectedPaths() { + return modifications.stream().map(Modification::getEffectedPath).collect(toList()); } - public Modifications(List added, List modified, List removed) { - this.added = added; - this.modified = modified; - this.removed = removed; + public List getAdded() { + return modifications.stream() + .filter(m -> m instanceof Modification.Added) + .map(m -> (Modification.Added) m) + .collect(toList()); } - public List getAdded() { - if (added == null) { - added = Lists.newArrayList(); - } - - return added; + public List getRemoved() { + return modifications.stream() + .filter(m -> m instanceof Modification.Removed) + .map(m -> (Modification.Removed) m) + .collect(toList()); } - public List getModified() { - if (modified == null) { - modified = Lists.newArrayList(); - } - - return modified; + public List getModified() { + return modifications.stream() + .filter(m -> m instanceof Modification.Modified) + .map(m -> (Modification.Modified) m) + .collect(toList()); } - public List getRemoved() { - if (removed == null) { - removed = Lists.newArrayList(); - } - - return removed; + public List getRenamed() { + return modifications.stream() + .filter(m -> m instanceof Modification.Renamed) + .map(m -> (Modification.Renamed) m) + .collect(toList()); } - public String getRevision() { - return revision; + public List getCopied() { + return modifications.stream() + .filter(m -> m instanceof Modification.Copied) + .map(m -> (Modification.Copied) m) + .collect(toList()); } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/Differ.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/Differ.java index c613e945f1..aa539b978c 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/Differ.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/Differ.java @@ -113,17 +113,23 @@ final class Differ implements AutoCloseable { } private Diff diff(Repository repository) throws IOException { + List entries = scanWithRename(repository, pathFilter, treeWalk); + return new Diff(commit, entries); + } + + static List scanWithRename(Repository repository, PathFilter pathFilter, TreeWalk treeWalk) throws IOException { + List entries; try (DiffFormatter diffFormatter = new DiffFormatter(null)) { diffFormatter.setRepository(repository); diffFormatter.setDetectRenames(true); if (pathFilter != null) { diffFormatter.setPathFilter(pathFilter); } - List entries = diffFormatter.scan( + entries = diffFormatter.scan( treeWalk.getTree(0, AbstractTreeIterator.class), treeWalk.getTree(1, AbstractTreeIterator.class)); - return new Diff(commit, entries); } + return entries; } @Override 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 index 3839c98959..d04e8293ca 100644 --- 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 @@ -34,10 +34,13 @@ 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.Modification; import sonia.scm.repository.Modifications; import java.io.IOException; import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import static sonia.scm.ContextEntry.ContextBuilder.entity; @@ -72,15 +75,14 @@ public class GitModificationsCommand extends AbstractGitCommand implements Modif treeWalk.addTree(new EmptyTreeIterator()); } treeWalk.addTree(commit.getTree()); - List entries = DiffEntry.scan(treeWalk); - Modifications modifications = new Modifications(); + List entries = Differ.scanWithRename(context.open(), null, treeWalk); + Collection modifications = new ArrayList<>(); for (DiffEntry e : entries) { - if (!e.getOldId().equals(e.getNewId())) { - appendModification(modifications, e); + if (!e.getOldId().equals(e.getNewId()) || !e.getOldPath().equals(e.getNewPath())) { + modifications.add(asModification(e)); } } - modifications.setRevision(revision); - return modifications; + return new Modifications(revision, modifications); } @Override @@ -111,16 +113,19 @@ public class GitModificationsCommand extends AbstractGitCommand implements Modif return getModifications(request.getRevision()); } - private void appendModification(Modifications modifications, DiffEntry entry) throws UnsupportedModificationTypeException { + private Modification asModification(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(entity(repository), MessageFormat.format("The modification type: {0} is not supported.", type)); + switch (type) { + case ADD: + return new Modification.Added(entry.getNewPath()); + case MODIFY: + return new Modification.Modified(entry.getNewPath()); + case DELETE: + return new Modification.Removed(entry.getOldPath()); + case RENAME: + return new Modification.Renamed(entry.getOldPath(), entry.getNewPath()); + default: + throw new UnsupportedModificationTypeException(entity(repository), MessageFormat.format("The modification type: {0} is not supported.", type)); } } } 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 38ab4e0ebb..61c2009405 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 @@ -39,11 +39,11 @@ import java.io.File; import java.io.IOException; import static java.nio.charset.Charset.defaultCharset; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; @@ -188,7 +188,9 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase 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")); + assertThat(modifications.getAdded()) + .extracting("path") + .containsExactly("a.txt", "b.txt"); } @Test @@ -198,14 +200,14 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase GitLogCommand command = createCommand(); Changeset c = command.getChangeset("435df2f061add3589cb3", request); - Assertions.assertThat(c.getBranches()).containsOnly("master"); + assertThat(c.getBranches()).containsOnly("master"); } @Test public void shouldNotReturnCommitFromDifferentBranch() { when(request.getBranch()).thenReturn("master"); Changeset changeset = createCommand().getChangeset("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4", request); - Assertions.assertThat(changeset).isNull(); + assertThat(changeset).isNull(); } @Test diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java index 8586b26cfb..f8e7be6fd4 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java @@ -40,6 +40,7 @@ import org.junit.jupiter.api.Assertions; import sonia.scm.NoChangesMadeException; import sonia.scm.NotFoundException; import sonia.scm.repository.GitWorkdirFactory; +import sonia.scm.repository.Modification; import sonia.scm.repository.Person; import sonia.scm.repository.api.MergeCommandResult; import sonia.scm.repository.api.MergeStrategy; @@ -318,7 +319,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { assertThat(message).isEqualTo("squash three commits"); GitModificationsCommand modificationsCommand = new GitModificationsCommand(createContext()); - List changes = modificationsCommand.getModifications("master").getAdded(); + List changes = modificationsCommand.getModifications("master").getAdded(); assertThat(changes.size()).isEqualTo(3); } 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 index e6ac22d32c..1657191d21 100644 --- 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 @@ -86,6 +86,25 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase { assertModifications.accept(outgoingModificationsCommand.getModifications(revision)); } + @Test + public void shouldReadRenamedFiles() throws Exception { + String originalFile = "a.txt"; + write(outgoing, outgoingDirectory, originalFile, "bal bla"); + commit(outgoing, "add file"); + write(outgoing, outgoingDirectory, "b.txt", "bal bla"); + File file = new File(outgoingDirectory, originalFile); + file.delete(); + outgoing.rm().addFilepattern(originalFile).call(); + + RevCommit modifiedFileCommit = commit(outgoing, "rename file"); + String revision = modifiedFileCommit.getName(); + + Consumer assertModifications = assertRenamedFiles("b.txt"); + assertModifications.accept(outgoingModificationsCommand.getModifications(revision)); + pushOutgoingAndPullIncoming(); + assertModifications.accept(incomingModificationsCommand.getModifications(revision)); + } + void pushOutgoingAndPullIncoming() throws IOException { GitPushCommand cmd = new GitPushCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, null)); PushCommandRequest request = new PushCommandRequest(); @@ -102,31 +121,62 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase { assertThat(modifications).isNotNull(); assertThat(modifications.getAdded()) .as("added files modifications") - .hasSize(0); + .asList() + .isEmpty(); assertThat(modifications.getModified()) .as("modified files modifications") - .hasSize(0); + .asList() + .isEmpty(); assertThat(modifications.getRemoved()) .as("removed files modifications") + .asList() .hasSize(1) + .extracting("path") .containsOnly(fileName); }; } + Consumer assertRenamedFiles(String fileName) { + return (modifications) -> { + assertThat(modifications).isNotNull(); + assertThat(modifications.getAdded()) + .as("added files modifications") + .asList() + .isEmpty(); + assertThat(modifications.getModified()) + .as("modified files modifications") + .asList() + .isEmpty(); + assertThat(modifications.getRemoved()) + .as("removed files modifications") + .asList() + .isEmpty(); + assertThat(modifications.getRenamed()) + .as("renamed files modifications") + .asList() + .hasSize(1) + .extracting("newPath") + .containsOnly(fileName); + }; + } Consumer assertModifiedFiles(String file) { return (modifications) -> { assertThat(modifications).isNotNull(); assertThat(modifications.getAdded()) .as("added files modifications") - .hasSize(0); + .asList() + .isEmpty(); assertThat(modifications.getModified()) .as("modified files modifications") + .asList() + .extracting("path") .hasSize(1) .containsOnly(file); assertThat(modifications.getRemoved()) .as("removed files modifications") - .hasSize(0); + .asList() + .isEmpty(); }; } @@ -135,14 +185,18 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase { assertThat(modifications).isNotNull(); assertThat(modifications.getAdded()) .as("added files modifications") + .asList() .hasSize(1) + .extracting("path") .containsOnly(file); assertThat(modifications.getModified()) .as("modified files modifications") - .hasSize(0); + .asList() + .isEmpty(); assertThat(modifications.getRemoved()) .as("removed files modifications") - .hasSize(0); + .asList() + .isEmpty(); }; } } 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 index f9c7a3e1d1..157529baf5 100644 --- 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 @@ -24,9 +24,12 @@ package sonia.scm.repository.spi; +import sonia.scm.repository.Modification; import sonia.scm.repository.Modifications; import sonia.scm.repository.spi.javahg.HgLogChangesetCommand; +import java.util.Collection; + public class HgModificationsCommand extends AbstractCommand implements ModificationsCommand { HgModificationsCommand(HgCommandContext context) { @@ -38,9 +41,8 @@ public class HgModificationsCommand extends AbstractCommand implements Modificat public Modifications getModifications(String revision) { com.aragost.javahg.Repository repository = open(); HgLogChangesetCommand hgLogChangesetCommand = HgLogChangesetCommand.on(repository, getContext().getConfig()); - Modifications modifications = hgLogChangesetCommand.rev(revision).extractModifications(); - modifications.setRevision(revision); - return modifications; + Collection modifications = hgLogChangesetCommand.rev(revision).extractModifications(); + return new Modifications(revision, modifications); } @Override 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 4de460d20a..be2172f2fd 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 @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi.javahg; //~--- non-JDK imports -------------------------------------------------------- @@ -36,10 +36,13 @@ 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.Modification; import sonia.scm.repository.Modifications; import sonia.scm.repository.Person; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; //~--- JDK imports ------------------------------------------------------------ @@ -251,7 +254,7 @@ public abstract class AbstractChangesetCommand extends AbstractCommand return changeset; } - protected Modifications readModificationsFromStream(HgInputStream in) { + protected Collection readModificationsFromStream(HgInputStream in) { try { boolean found = in.find(CHANGESET_PATTERN); if (found) { @@ -265,16 +268,16 @@ public abstract class AbstractChangesetCommand extends AbstractCommand return null; } - private Modifications extractModifications(HgInputStream in) throws IOException { - Modifications modifications = new Modifications(); + private Collection extractModifications(HgInputStream in) throws IOException { + Collection modifications = new ArrayList<>(); String line = in.textUpTo('\n'); while (line.length() > 0) { if (line.startsWith("a ")) { - modifications.getAdded().add(line.substring(2)); + modifications.add(new Modification.Added(line.substring(2))); } else if (line.startsWith("m ")) { - modifications.getModified().add(line.substring(2)); + modifications.add(new Modification.Modified(line.substring(2))); } else if (line.startsWith("d ")) { - modifications.getRemoved().add(line.substring(2)); + modifications.add(new Modification.Removed(line.substring(2))); } line = in.textUpTo('\n'); } 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 418772b39f..923132fe07 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 @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi.javahg; import com.aragost.javahg.Repository; @@ -31,9 +31,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.Changeset; import sonia.scm.repository.HgConfig; -import sonia.scm.repository.Modifications; +import sonia.scm.repository.Modification; import java.io.IOException; +import java.util.Collection; import java.util.List; /** @@ -64,7 +65,7 @@ public class HgLogChangesetCommand extends AbstractChangesetCommand { return readListFromStream(getHgInputStream(files, CHANGESET_EAGER_STYLE_PATH)); } - public Modifications extractModifications(String... files) { + public Collection extractModifications(String... files) { HgInputStream hgInputStream = getHgInputStream(files, CHANGESET_EAGER_STYLE_PATH); try { return readModificationsFromStream(hgInputStream); 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 efb3919455..d548ebf75e 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 @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- @@ -33,11 +33,10 @@ import sonia.scm.repository.Modifications; import java.io.IOException; -import static org.hamcrest.Matchers.contains; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; //~--- JDK imports ------------------------------------------------------------ @@ -162,7 +161,9 @@ public class HgLogCommandTest extends AbstractHgCommandTestBase 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")); + assertThat(modifications.getAdded()) + .extracting("path") + .containsExactly("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 index c257dc611a..993b8a6fb9 100644 --- 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 @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi; import com.aragost.javahg.Changeset; @@ -96,6 +96,7 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase { assertThat(modifications.getRemoved()) .as("removed files modifications") .hasSize(1) + .extracting("path") .containsOnly(fileName); }; } @@ -110,6 +111,7 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase { assertThat(modifications.getModified()) .as("modified files modifications") .hasSize(1) + .extracting("path") .containsOnly(file); assertThat(modifications.getRemoved()) .as("removed files modifications") @@ -123,6 +125,7 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase { assertThat(modifications.getAdded()) .as("added files modifications") .hasSize(1) + .extracting("path") .containsOnly(addedFile); assertThat(modifications.getModified()) .as("modified files modifications") 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 58cc319e2a..40a5ee5a69 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 @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- @@ -49,7 +49,11 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import static java.util.Collections.emptyList; +import static java.util.Optional.empty; import static sonia.scm.ContextEntry.ContextBuilder.entity; import static sonia.scm.NotFoundException.notFound; @@ -116,30 +120,23 @@ public final class SvnUtil public static Modifications createModifications(SVNLogEntry entry, String revision) { - Modifications modifications = new Modifications(); - modifications.setRevision(revision); Map changeMap = entry.getChangedPaths(); + List modificationList; if (Util.isNotEmpty(changeMap)) { - - for (SVNLogEntryPath e : changeMap.values()) { - appendModification(modifications, e.getType(), e.getPath()); - } + modificationList = changeMap.values().stream() + .map(e -> asModification(e.getType(), e.getPath())) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + } else { + modificationList = emptyList(); } - return modifications; + + return new Modifications(revision, modificationList); } - /** - * Method description - * - * - * @param modifications - * @param type - * @param path - */ - public static void appendModification(Modifications modifications, char type, - String path) - { + public static Optional asModification(char type, String path) { if (path.startsWith("/")) { path = path.substring(1); @@ -148,23 +145,18 @@ public final class SvnUtil switch (type) { case SVNLogEntryPath.TYPE_ADDED : - modifications.getAdded().add(path); - - break; + return Optional.of(new Modification.Added(path)); case SVNLogEntryPath.TYPE_DELETED : - modifications.getRemoved().add(path); - - break; + return Optional.of(new Modification.Removed(path)); case TYPE_UPDATED : case SVNLogEntryPath.TYPE_MODIFIED : - modifications.getModified().add(path); - - break; + return Optional.of(new Modification.Modified(path)); default : logger.debug("unknown modification type {}", type); + return empty(); } } 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 index 97c44ce747..8daebf9245 100644 --- 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 @@ -31,10 +31,12 @@ import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.SVNClientManager; import org.tmatesoft.svn.core.wc.admin.SVNLookClient; import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.Modification; import sonia.scm.repository.Modifications; import sonia.scm.repository.SvnUtil; import sonia.scm.util.Util; +import java.util.ArrayList; import java.util.Collection; @Slf4j @@ -78,12 +80,12 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif private Modifications getModificationsFromTransaction(String transaction) throws SVNException { log.debug("get svn modifications from transaction: {}", transaction); - final Modifications modifications = new Modifications(); SVNLookClient client = SVNClientManager.newInstance().getLookClient(); + Collection modificationList = new ArrayList<>(); client.doGetChanged(context.getDirectory(), transaction, - e -> SvnUtil.appendModification(modifications, e.getType(), e.getPath()), true); + e -> SvnUtil.asModification(e.getType(), e.getPath()).ifPresent(modificationList::add), true); - return modifications; + return new Modifications(null, modificationList); } @Override 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 52162419fa..cab001573b 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 @@ -143,8 +143,8 @@ public class SvnLogCommandTest extends AbstractSvnCommandTestBase 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)); + assertEquals("a.txt", modifications.getModified().get(0).getPath()); + assertEquals("b.txt", modifications.getRemoved().get(0).getPath()); } @Test 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 index 9dbfb5707d..d826ece42c 100644 --- 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 @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.api.v2.resources; import de.otto.edison.hal.HalRepresentation; @@ -54,10 +54,21 @@ public class ModificationsDto extends HalRepresentation { */ private List removed; + /** + * Mapping of renamed files + */ + private List renamed; + @Override @SuppressWarnings("squid:S1185") // We want to have this method available in this package protected HalRepresentation add(Links links) { return super.add(links); } + @Getter + @Setter + public static class RenamedDto { + private String oldPath; + private String newPath; + } } 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 index e393265c26..0e9f169f53 100644 --- 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 @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.api.v2.resources; import de.otto.edison.hal.Links; @@ -30,6 +30,7 @@ import org.mapstruct.Context; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; +import sonia.scm.repository.Modification; import sonia.scm.repository.Modifications; import sonia.scm.repository.Repository; @@ -52,4 +53,18 @@ public abstract class ModificationsToDtoMapper { .self(resourceLinks.modifications().self(repository.getNamespace(), repository.getName(), target.getRevision())); target.add(linksBuilder.build()); } + + String map(Modification.Added added) { + return added.getPath(); + } + + String map(Modification.Removed removed) { + return removed.getPath(); + } + + String map(Modification.Modified modified) { + return modified.getPath(); + } + + abstract ModificationsDto.RenamedDto map(Modification.Renamed renamed); } 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 index 23893f9f95..3697330587 100644 --- 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 @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.api.v2.resources; import com.google.inject.util.Providers; @@ -30,7 +30,6 @@ 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.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.After; @@ -41,6 +40,9 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.Modification.Added; +import sonia.scm.repository.Modification.Modified; +import sonia.scm.repository.Modification.Removed; import sonia.scm.repository.Modifications; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; @@ -141,7 +143,6 @@ public class ModificationsResourceTest extends RepositoryTestBase { @Test public void shouldGetModifications() throws Exception { - Modifications modifications = new Modifications(); String revision = "revision"; String addedFile_1 = "a.txt"; String addedFile_2 = "b.txt"; @@ -149,10 +150,13 @@ public class ModificationsResourceTest extends RepositoryTestBase { 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)); + Modifications modifications = new Modifications(revision, + new Added(addedFile_1), + new Added(addedFile_2), + new Modified(modifiedFile_1), + new Modified(modifiedFile_2), + new Removed(removedFile_1), + new Removed(removedFile_2)); when(modificationsCommandBuilder.getModifications()).thenReturn(modifications); when(modificationsCommandBuilder.revision(eq(revision))).thenReturn(modificationsCommandBuilder);