mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-01-18 05:22:10 +01:00
Merge pull request #1157 from scm-manager/feature/detect_renames
Feature/detect renames
This commit is contained in:
@@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
### Added
|
||||
- Detect renamed files in git and hg diffs ([#1157](https://github.com/scm-manager/scm-manager/pull/1157))
|
||||
|
||||
### Fixed
|
||||
- Resolved conflicting dependencies for scm-webapp ([#1159](https://github.com/scm-manager/scm-manager/pull/1159))
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"gitdiff-parser": "https://github.com/scm-manager/gitdiff-parser#ed3fe7de73dbb0a06c3e6adbbdf22dbae6e66351",
|
||||
"gitdiff-parser": "https://github.com/scm-manager/gitdiff-parser#617747460280bf4522bb84d217a9064ac8eb6d3d",
|
||||
"lowlight": "1.13.1"
|
||||
},
|
||||
"babel": {
|
||||
|
||||
41
scm-core/src/main/java/sonia/scm/repository/Added.java
Normal file
41
scm-core/src/main/java/sonia/scm/repository/Added.java
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.Value;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.stream.Stream.of;
|
||||
|
||||
@Value
|
||||
public class Added extends Modification {
|
||||
private final String path;
|
||||
|
||||
@Override
|
||||
Stream<String> getEffectedPaths() {
|
||||
return of(path);
|
||||
}
|
||||
}
|
||||
42
scm-core/src/main/java/sonia/scm/repository/Copied.java
Normal file
42
scm-core/src/main/java/sonia/scm/repository/Copied.java
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.Value;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.stream.Stream.of;
|
||||
|
||||
@Value
|
||||
public class Copied extends Modification {
|
||||
private final String sourcePath;
|
||||
private final String targetPath;
|
||||
|
||||
@Override
|
||||
Stream<String> getEffectedPaths() {
|
||||
return of(targetPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 java.util.stream.Stream;
|
||||
|
||||
abstract class EffectedPath {
|
||||
abstract Stream<String> getEffectedPaths();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 java.io.Serializable;
|
||||
|
||||
public abstract class Modification extends EffectedPath implements Serializable {
|
||||
}
|
||||
@@ -24,71 +24,78 @@
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
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 java.util.stream.Stream;
|
||||
|
||||
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<String> added;
|
||||
private List<String> modified;
|
||||
private List<String> removed;
|
||||
private final String revision;
|
||||
private final Collection<Modification> modifications;
|
||||
|
||||
public Modifications() {
|
||||
public Modifications(String revision, Modification... modifications) {
|
||||
this(revision, asList(modifications));
|
||||
}
|
||||
|
||||
public Modifications(List<String> added) {
|
||||
this(added, null, null);
|
||||
public Modifications(String revision, Collection<Modification> modifications) {
|
||||
this.revision = revision;
|
||||
this.modifications = ImmutableList.copyOf(modifications);
|
||||
}
|
||||
|
||||
public Modifications(List<String> added, List<String> modified) {
|
||||
this(added, modified, null);
|
||||
public List<String> getEffectedPaths() {
|
||||
return effectedPathsStream().collect(toList());
|
||||
}
|
||||
|
||||
public Modifications(List<String> added, List<String> modified, List<String> removed) {
|
||||
this.added = added;
|
||||
this.modified = modified;
|
||||
this.removed = removed;
|
||||
public Stream<String> effectedPathsStream() {
|
||||
return modifications.stream().flatMap(Modification::getEffectedPaths);
|
||||
}
|
||||
|
||||
public List<String> getAdded() {
|
||||
if (added == null) {
|
||||
added = Lists.newArrayList();
|
||||
}
|
||||
|
||||
return added;
|
||||
public List<Added> getAdded() {
|
||||
return modifications.stream()
|
||||
.filter(m -> m instanceof Added)
|
||||
.map(m -> (Added) m)
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
public List<String> getModified() {
|
||||
if (modified == null) {
|
||||
modified = Lists.newArrayList();
|
||||
}
|
||||
|
||||
return modified;
|
||||
public List<Removed> getRemoved() {
|
||||
return modifications.stream()
|
||||
.filter(m -> m instanceof Removed)
|
||||
.map(m -> (Removed) m)
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
public List<String> getRemoved() {
|
||||
if (removed == null) {
|
||||
removed = Lists.newArrayList();
|
||||
}
|
||||
|
||||
return removed;
|
||||
public List<Modified> getModified() {
|
||||
return modifications.stream()
|
||||
.filter(m -> m instanceof Modified)
|
||||
.map(m -> (Modified) m)
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
public String getRevision() {
|
||||
return revision;
|
||||
public List<Renamed> getRenamed() {
|
||||
return modifications.stream()
|
||||
.filter(m -> m instanceof Renamed)
|
||||
.map(m -> (Renamed) m)
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
public List<Copied> getCopied() {
|
||||
return modifications.stream()
|
||||
.filter(m -> m instanceof Copied)
|
||||
.map(m -> (Copied) m)
|
||||
.collect(toList());
|
||||
}
|
||||
}
|
||||
|
||||
41
scm-core/src/main/java/sonia/scm/repository/Modified.java
Normal file
41
scm-core/src/main/java/sonia/scm/repository/Modified.java
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.Value;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.stream.Stream.of;
|
||||
|
||||
@Value
|
||||
public class Modified extends Modification {
|
||||
private final String path;
|
||||
|
||||
@Override
|
||||
Stream<String> getEffectedPaths() {
|
||||
return of(path);
|
||||
}
|
||||
}
|
||||
41
scm-core/src/main/java/sonia/scm/repository/Removed.java
Normal file
41
scm-core/src/main/java/sonia/scm/repository/Removed.java
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.Value;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.stream.Stream.of;
|
||||
|
||||
@Value
|
||||
public class Removed extends Modification {
|
||||
private final String path;
|
||||
|
||||
@Override
|
||||
Stream<String> getEffectedPaths() {
|
||||
return of(path);
|
||||
}
|
||||
}
|
||||
42
scm-core/src/main/java/sonia/scm/repository/Renamed.java
Normal file
42
scm-core/src/main/java/sonia/scm/repository/Renamed.java
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.Value;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.stream.Stream.of;
|
||||
|
||||
@Value
|
||||
public class Renamed extends Modification {
|
||||
private final String oldPath;
|
||||
private final String newPath;
|
||||
|
||||
@Override
|
||||
Stream<String> getEffectedPaths() {
|
||||
return of(oldPath, newPath);
|
||||
}
|
||||
}
|
||||
@@ -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.api;
|
||||
|
||||
public interface DiffFile extends Iterable<Hunk> {
|
||||
@@ -33,4 +33,10 @@ public interface DiffFile extends Iterable<Hunk> {
|
||||
String getOldPath();
|
||||
|
||||
String getNewPath();
|
||||
|
||||
ChangeType getChangeType();
|
||||
|
||||
enum ChangeType {
|
||||
ADD, MODIFY, DELETE, RENAME, COPY
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ModificationsTest {
|
||||
|
||||
public static final Modifications MODIFICATIONS = new Modifications("123",
|
||||
new Added("added"),
|
||||
new Removed("removed"),
|
||||
new Modified("modified"),
|
||||
new Renamed("rename from", "rename to"),
|
||||
new Copied("copy from", "copy to")
|
||||
);
|
||||
|
||||
@Test
|
||||
void shouldFindAddedFilesAsEffected() {
|
||||
assertThat(MODIFICATIONS.getEffectedPaths())
|
||||
.contains("added");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindRemovedFilesAsEffected() {
|
||||
assertThat(MODIFICATIONS.getEffectedPaths())
|
||||
.contains("removed");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindModifiedFilesAsEffected() {
|
||||
assertThat(MODIFICATIONS.getEffectedPaths())
|
||||
.contains("modified");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindRenamedFilesAsEffected() {
|
||||
assertThat(MODIFICATIONS.getEffectedPaths())
|
||||
.contains("rename from", "rename to");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindTargetOfCopiedFilesAsEffected() {
|
||||
assertThat(MODIFICATIONS.getEffectedPaths())
|
||||
.contains("copy to")
|
||||
.doesNotContain("copy from");
|
||||
}
|
||||
}
|
||||
@@ -21,17 +21,19 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import org.eclipse.jgit.diff.DiffEntry;
|
||||
import org.eclipse.jgit.diff.DiffFormatter;
|
||||
import org.eclipse.jgit.errors.MissingObjectException;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevTree;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
|
||||
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
|
||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||
import org.eclipse.jgit.treewalk.filter.PathFilter;
|
||||
@@ -48,16 +50,18 @@ final class Differ implements AutoCloseable {
|
||||
private final RevWalk walk;
|
||||
private final TreeWalk treeWalk;
|
||||
private final RevCommit commit;
|
||||
private final PathFilter pathFilter;
|
||||
|
||||
private Differ(RevCommit commit, RevWalk walk, TreeWalk treeWalk) {
|
||||
private Differ(RevCommit commit, RevWalk walk, TreeWalk treeWalk, PathFilter pathFilter) {
|
||||
this.commit = commit;
|
||||
this.walk = walk;
|
||||
this.treeWalk = treeWalk;
|
||||
this.pathFilter = pathFilter;
|
||||
}
|
||||
|
||||
static Diff diff(Repository repository, DiffCommandRequest request) throws IOException {
|
||||
try (Differ differ = create(repository, request)) {
|
||||
return differ.diff();
|
||||
return differ.diff(repository);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,11 +85,11 @@ final class Differ implements AutoCloseable {
|
||||
treeWalk.reset();
|
||||
treeWalk.setRecursive(true);
|
||||
|
||||
PathFilter pathFilter = null;
|
||||
if (Util.isNotEmpty(request.getPath())) {
|
||||
treeWalk.setFilter(PathFilter.create(request.getPath()));
|
||||
pathFilter = PathFilter.create(request.getPath());
|
||||
}
|
||||
|
||||
|
||||
if (!Strings.isNullOrEmpty(request.getAncestorChangeset())) {
|
||||
ObjectId otherRevision = repository.resolve(request.getAncestorChangeset());
|
||||
ObjectId ancestorId = GitUtil.computeCommonAncestor(repository, revision, otherRevision);
|
||||
@@ -105,14 +109,29 @@ final class Differ implements AutoCloseable {
|
||||
|
||||
treeWalk.addTree(commit.getTree());
|
||||
|
||||
return new Differ(commit, walk, treeWalk);
|
||||
return new Differ(commit, walk, treeWalk, pathFilter);
|
||||
}
|
||||
|
||||
private Diff diff() throws IOException {
|
||||
List<DiffEntry> entries = DiffEntry.scan(treeWalk);
|
||||
private Diff diff(Repository repository) throws IOException {
|
||||
List<DiffEntry> entries = scanWithRename(repository, pathFilter, treeWalk);
|
||||
return new Diff(commit, entries);
|
||||
}
|
||||
|
||||
static List<DiffEntry> scanWithRename(Repository repository, PathFilter pathFilter, TreeWalk treeWalk) throws IOException {
|
||||
List<DiffEntry> entries;
|
||||
try (DiffFormatter diffFormatter = new DiffFormatter(null)) {
|
||||
diffFormatter.setRepository(repository);
|
||||
diffFormatter.setDetectRenames(true);
|
||||
if (pathFilter != null) {
|
||||
diffFormatter.setPathFilter(pathFilter);
|
||||
}
|
||||
entries = diffFormatter.scan(
|
||||
treeWalk.getTree(0, AbstractTreeIterator.class),
|
||||
treeWalk.getTree(1, AbstractTreeIterator.class));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
GitUtil.release(walk);
|
||||
|
||||
@@ -57,7 +57,7 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand {
|
||||
formatter.setRepository(repository);
|
||||
|
||||
for (DiffEntry e : diff.getEntries()) {
|
||||
if (!e.getOldId().equals(e.getNewId())) {
|
||||
if (idOrPathChanged(e)) {
|
||||
formatter.format(e);
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,10 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand {
|
||||
};
|
||||
}
|
||||
|
||||
private boolean idOrPathChanged(DiffEntry e) {
|
||||
return !e.getOldId().equals(e.getNewId()) || !e.getNewPath().equals(e.getOldPath());
|
||||
}
|
||||
|
||||
static class DequoteOutputStream extends OutputStream {
|
||||
|
||||
private static final String[] DEQUOTE_STARTS = {
|
||||
|
||||
@@ -108,6 +108,24 @@ public class GitDiffResultCommand extends AbstractGitCommand implements DiffResu
|
||||
return diffEntry.getNewPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangeType getChangeType() {
|
||||
switch (diffEntry.getChangeType()) {
|
||||
case ADD:
|
||||
return ChangeType.ADD;
|
||||
case MODIFY:
|
||||
return ChangeType.MODIFY;
|
||||
case RENAME:
|
||||
return ChangeType.RENAME;
|
||||
case DELETE:
|
||||
return ChangeType.DELETE;
|
||||
case COPY:
|
||||
return ChangeType.COPY;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown change type: " + diffEntry.getChangeType());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Hunk> iterator() {
|
||||
String content = format(repository, diffEntry);
|
||||
|
||||
@@ -32,12 +32,19 @@ 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.Added;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.Modification;
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.Modified;
|
||||
import sonia.scm.repository.Removed;
|
||||
import sonia.scm.repository.Renamed;
|
||||
|
||||
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 +79,14 @@ public class GitModificationsCommand extends AbstractGitCommand implements Modif
|
||||
treeWalk.addTree(new EmptyTreeIterator());
|
||||
}
|
||||
treeWalk.addTree(commit.getTree());
|
||||
List<DiffEntry> entries = DiffEntry.scan(treeWalk);
|
||||
Modifications modifications = new Modifications();
|
||||
List<DiffEntry> entries = Differ.scanWithRename(context.open(), null, treeWalk);
|
||||
Collection<Modification> 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 +117,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 Added(entry.getNewPath());
|
||||
case MODIFY:
|
||||
return new Modified(entry.getNewPath());
|
||||
case DELETE:
|
||||
return new Removed(entry.getOldPath());
|
||||
case RENAME:
|
||||
return new Renamed(entry.getOldPath(), entry.getNewPath());
|
||||
default:
|
||||
throw new UnsupportedModificationTypeException(entity(repository), MessageFormat.format("The modification type: {0} is not supported.", type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +24,13 @@
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class GitDiffCommandTest extends AbstractGitCommandTestBase {
|
||||
@@ -140,4 +142,19 @@ public class GitDiffCommandTest extends AbstractGitCommandTestBase {
|
||||
gitDiffCommand.getDiffResult(diffCommandRequest).accept(output);
|
||||
assertEquals(DIFF_FILE_PARTIAL_MERGE, output.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void diffBetweenTwoBranchesWithMovedFiles() throws IOException {
|
||||
GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext());
|
||||
DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
|
||||
diffCommandRequest.setRevision("rename");
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
gitDiffCommand.getDiffResult(diffCommandRequest).accept(output);
|
||||
assertThat(output.toString())
|
||||
.contains("similarity index 100%")
|
||||
.contains("rename from a.txt")
|
||||
.contains("rename to a-copy.txt")
|
||||
.contains("rename from b.txt")
|
||||
.contains("rename to b-copy.txt");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,22 @@ public class GitDiffResultCommandTest extends AbstractGitCommandTestBase {
|
||||
assertThat(hunk.getNewLineCount()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnRenames() throws IOException {
|
||||
DiffResult diffResult = createDiffResult("rename");
|
||||
|
||||
Iterator<DiffFile> fileIterator = diffResult.iterator();
|
||||
DiffFile renameA = fileIterator.next();
|
||||
assertThat(renameA.getOldPath()).isEqualTo("a.txt");
|
||||
assertThat(renameA.getNewPath()).isEqualTo("a-copy.txt");
|
||||
assertThat(renameA.iterator().hasNext()).isFalse();
|
||||
|
||||
DiffFile renameB = fileIterator.next();
|
||||
assertThat(renameB.getOldPath()).isEqualTo("b.txt");
|
||||
assertThat(renameB.getNewPath()).isEqualTo("b-copy.txt");
|
||||
assertThat(renameB.iterator().hasNext()).isFalse();
|
||||
}
|
||||
|
||||
private DiffResult createDiffResult(String s) throws IOException {
|
||||
GitDiffResultCommand gitDiffResultCommand = new GitDiffResultCommand(createContext());
|
||||
DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.junit.Test;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import sonia.scm.NoChangesMadeException;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.Added;
|
||||
import sonia.scm.repository.GitWorkdirFactory;
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.repository.api.MergeCommandResult;
|
||||
@@ -318,7 +319,7 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase {
|
||||
assertThat(message).isEqualTo("squash three commits");
|
||||
|
||||
GitModificationsCommand modificationsCommand = new GitModificationsCommand(createContext());
|
||||
List<String> changes = modificationsCommand.getModifications("master").getAdded();
|
||||
List<Added> changes = modificationsCommand.getModifications("master").getAdded();
|
||||
assertThat(changes.size()).isEqualTo(3);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Modifications> 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<Modifications> 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<Modifications> 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();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -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<Modification> modifications = hgLogChangesetCommand.rev(revision).extractModifications();
|
||||
return new Modifications(revision, modifications);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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,11 @@ 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.Modification;
|
||||
import sonia.scm.repository.Person;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
@@ -251,7 +252,7 @@ public abstract class AbstractChangesetCommand extends AbstractCommand
|
||||
return changeset;
|
||||
}
|
||||
|
||||
protected Modifications readModificationsFromStream(HgInputStream in) {
|
||||
protected Collection<Modification> readModificationsFromStream(HgInputStream in) {
|
||||
try {
|
||||
boolean found = in.find(CHANGESET_PATTERN);
|
||||
if (found) {
|
||||
@@ -265,20 +266,14 @@ public abstract class AbstractChangesetCommand extends AbstractCommand
|
||||
return null;
|
||||
}
|
||||
|
||||
private Modifications extractModifications(HgInputStream in) throws IOException {
|
||||
Modifications modifications = new Modifications();
|
||||
private Collection<Modification> extractModifications(HgInputStream in) throws IOException {
|
||||
HgModificationParser hgModificationParser = new HgModificationParser();
|
||||
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));
|
||||
}
|
||||
hgModificationParser.addLine(line);
|
||||
line = in.textUpTo('\n');
|
||||
}
|
||||
return modifications;
|
||||
return hgModificationParser.getModifications();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<Modification> extractModifications(String... files) {
|
||||
HgInputStream hgInputStream = getHgInputStream(files, CHANGESET_EAGER_STYLE_PATH);
|
||||
try {
|
||||
return readModificationsFromStream(hgInputStream);
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.spi.javahg;
|
||||
|
||||
import sonia.scm.repository.Added;
|
||||
import sonia.scm.repository.Copied;
|
||||
import sonia.scm.repository.Modification;
|
||||
import sonia.scm.repository.Modified;
|
||||
import sonia.scm.repository.Removed;
|
||||
import sonia.scm.repository.Renamed;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
class HgModificationParser {
|
||||
private final Collection<Modification> modifications = new LinkedHashSet<>();
|
||||
|
||||
void addLine(String line) {
|
||||
if (line.startsWith("a ")) {
|
||||
modifications.add(new Added(line.substring(2)));
|
||||
} else if (line.startsWith("m ")) {
|
||||
modifications.add(new Modified(line.substring(2)));
|
||||
} else if (line.startsWith("d ")) {
|
||||
modifications.add(new Removed(line.substring(2)));
|
||||
} else if (line.startsWith("c ")) {
|
||||
String sourceTarget = line.substring(2);
|
||||
int divider = sourceTarget.indexOf('\0');
|
||||
String source = sourceTarget.substring(0, divider);
|
||||
String target = sourceTarget.substring(divider + 1);
|
||||
modifications.remove(new Added(target));
|
||||
if (modifications.remove(new Removed(source))) {
|
||||
modifications.add(new Renamed(source, target));
|
||||
} else {
|
||||
modifications.add(new Copied(source, target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collection<Modification> getModifications() {
|
||||
return modifications;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
header = "%{pattern}"
|
||||
changeset = "{rev}:{node}{author}\n{date|hgdate}\n{branch}\n{parents}{extras}\n{tags}{file_adds}{file_mods}{file_dels}\n{desc}\0"
|
||||
changeset = "{rev}:{node}{author}\n{date|hgdate}\n{branch}\n{parents}{extras}\n{tags}{file_adds}{file_mods}{file_dels}{file_copies}\n{desc}\0"
|
||||
tag = "t {tag}\n"
|
||||
file_add = "a {file_add}\n"
|
||||
file_mod = "m {file_mod}\n"
|
||||
file_del = "d {file_del}\n"
|
||||
file_copy = "c {source}\0{name}\n"
|
||||
extra = "{key}={value|stringescape},"
|
||||
footer = "%{pattern}"
|
||||
footer = "%{pattern}"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -21,11 +21,13 @@
|
||||
* 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;
|
||||
import com.aragost.javahg.commands.CopyCommand;
|
||||
import com.aragost.javahg.commands.RemoveCommand;
|
||||
import com.aragost.javahg.commands.RenameCommand;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import sonia.scm.repository.HgTestUtil;
|
||||
@@ -34,7 +36,7 @@ import sonia.scm.repository.Modifications;
|
||||
import java.io.File;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.assertj.core.api.Java6Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
||||
|
||||
@@ -83,6 +85,31 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
||||
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReadRenamedFiles() throws Exception {
|
||||
String oldFileName = "a.txt";
|
||||
String newFileName = "b.txt";
|
||||
writeNewFile(outgoing, outgoingDirectory, oldFileName, "bal bla");
|
||||
commit(outgoing, "added a.txt");
|
||||
RenameCommand.on(outgoing).execute(oldFileName, newFileName);
|
||||
Changeset changeset = commit(outgoing, "rename a.txt to b.txt");
|
||||
String revision = String.valueOf(changeset.getRevision());
|
||||
Consumer<Modifications> assertModifications = assertRenamedFiles(oldFileName, newFileName);
|
||||
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReadCopiedFiles() throws Exception {
|
||||
String srcFileName = "a.txt";
|
||||
String newFileName = "b.txt";
|
||||
writeNewFile(outgoing, outgoingDirectory, srcFileName, "bal bla");
|
||||
commit(outgoing, "added a.txt");
|
||||
CopyCommand.on(outgoing).execute(srcFileName, newFileName);
|
||||
Changeset changeset = commit(outgoing, "copy a.txt to b.txt");
|
||||
String revision = String.valueOf(changeset.getRevision());
|
||||
Consumer<Modifications> assertModifications = assertCopiedFiles(srcFileName, newFileName);
|
||||
assertModifications.accept(outgoingModificationsCommand.getModifications(revision));
|
||||
}
|
||||
|
||||
Consumer<Modifications> assertRemovedFiles(String fileName) {
|
||||
return (modifications) -> {
|
||||
@@ -96,10 +123,66 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
||||
assertThat(modifications.getRemoved())
|
||||
.as("removed files modifications")
|
||||
.hasSize(1)
|
||||
.extracting("path")
|
||||
.containsOnly(fileName);
|
||||
assertThat(modifications.getRenamed())
|
||||
.as("renamed files modifications")
|
||||
.isEmpty();
|
||||
};
|
||||
}
|
||||
|
||||
Consumer<Modifications> assertRenamedFiles(String oldFileName, String newFileName) {
|
||||
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")
|
||||
.isEmpty();
|
||||
assertThat(modifications.getRenamed())
|
||||
.as("renamed files modifications")
|
||||
.hasSize(1)
|
||||
.extracting("oldPath")
|
||||
.containsOnly(oldFileName);
|
||||
assertThat(modifications.getRenamed())
|
||||
.as("renamed files modifications")
|
||||
.hasSize(1)
|
||||
.extracting("newPath")
|
||||
.containsOnly(newFileName);
|
||||
};
|
||||
}
|
||||
|
||||
Consumer<Modifications> assertCopiedFiles(String srcFileName, String newFileName) {
|
||||
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")
|
||||
.isEmpty();
|
||||
assertThat(modifications.getRenamed())
|
||||
.as("renamed files modifications")
|
||||
.isEmpty();
|
||||
assertThat(modifications.getCopied())
|
||||
.as("copied files modifications")
|
||||
.hasSize(1)
|
||||
.extracting("sourcePath")
|
||||
.containsOnly(srcFileName);
|
||||
assertThat(modifications.getCopied())
|
||||
.as("copied files modifications")
|
||||
.hasSize(1)
|
||||
.extracting("targetPath")
|
||||
.containsOnly(newFileName);
|
||||
};
|
||||
}
|
||||
|
||||
Consumer<Modifications> assertModifiedFiles(String file) {
|
||||
return (modifications) -> {
|
||||
@@ -110,10 +193,14 @@ 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")
|
||||
.hasSize(0);
|
||||
assertThat(modifications.getRenamed())
|
||||
.as("renamed files modifications")
|
||||
.hasSize(0);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -123,6 +210,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")
|
||||
@@ -130,6 +218,9 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
|
||||
assertThat(modifications.getRemoved())
|
||||
.as("removed files modifications")
|
||||
.hasSize(0);
|
||||
assertThat(modifications.getRenamed())
|
||||
.as("renamed files modifications")
|
||||
.hasSize(0);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.spi.javahg;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sonia.scm.repository.Added;
|
||||
import sonia.scm.repository.Copied;
|
||||
import sonia.scm.repository.Modified;
|
||||
import sonia.scm.repository.Removed;
|
||||
import sonia.scm.repository.Renamed;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class HgModificationParserTest {
|
||||
|
||||
HgModificationParser parser = new HgModificationParser();
|
||||
|
||||
@Test
|
||||
void shouldDetectAddedPath() {
|
||||
parser.addLine("a added/file");
|
||||
|
||||
assertThat(parser.getModifications())
|
||||
.containsExactly(new Added("added/file"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDetectModifiedPath() {
|
||||
parser.addLine("m modified/file");
|
||||
|
||||
assertThat(parser.getModifications())
|
||||
.containsExactly(new Modified("modified/file"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDetectRemovedPath() {
|
||||
parser.addLine("d removed/file");
|
||||
|
||||
assertThat(parser.getModifications())
|
||||
.containsExactly(new Removed("removed/file"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDetectRenamedPath() {
|
||||
parser.addLine("a new/path");
|
||||
parser.addLine("d old/path");
|
||||
parser.addLine("c old/path\0new/path");
|
||||
|
||||
assertThat(parser.getModifications())
|
||||
.containsExactly(new Renamed("old/path", "new/path"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCopiedRenamedPath() {
|
||||
parser.addLine("a new/path");
|
||||
parser.addLine("c old/path\0new/path");
|
||||
|
||||
assertThat(parser.getModifications())
|
||||
.containsExactly(new Copied("old/path", "new/path"));
|
||||
}
|
||||
}
|
||||
@@ -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<String, SVNLogEntryPath> changeMap = entry.getChangedPaths();
|
||||
|
||||
List<Modification> 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<Modification> 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 Added(path));
|
||||
|
||||
case SVNLogEntryPath.TYPE_DELETED :
|
||||
modifications.getRemoved().add(path);
|
||||
|
||||
break;
|
||||
return Optional.of(new Removed(path));
|
||||
|
||||
case TYPE_UPDATED :
|
||||
case SVNLogEntryPath.TYPE_MODIFIED :
|
||||
modifications.getModified().add(path);
|
||||
|
||||
break;
|
||||
return Optional.of(new Modified(path));
|
||||
|
||||
default :
|
||||
logger.debug("unknown modification type {}", type);
|
||||
return empty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Modification> 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -21,12 +21,11 @@
|
||||
* 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.github.sdorra.spotter.ContentTypes;
|
||||
import com.github.sdorra.spotter.Language;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.inject.Inject;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.api.DiffFile;
|
||||
@@ -42,7 +41,7 @@ import java.util.OptionalInt;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
|
||||
/**
|
||||
* TODO conflicts, copy and rename
|
||||
* TODO conflicts
|
||||
*/
|
||||
class DiffResultToDiffResultDtoMapper {
|
||||
|
||||
@@ -83,18 +82,29 @@ class DiffResultToDiffResultDtoMapper {
|
||||
String oldPath = file.getOldPath();
|
||||
|
||||
String path;
|
||||
if (isFilePath(newPath) && isFileNull(oldPath)) {
|
||||
path = newPath;
|
||||
dto.setType("add");
|
||||
} else if (isFileNull(newPath) && isFilePath(oldPath)) {
|
||||
path = oldPath;
|
||||
dto.setType("delete");
|
||||
} else if (isFilePath(newPath) && isFilePath(oldPath)) {
|
||||
path = newPath;
|
||||
dto.setType("modify");
|
||||
} else {
|
||||
// TODO copy and rename?
|
||||
throw new IllegalStateException("no file without path");
|
||||
switch (file.getChangeType()) {
|
||||
case ADD:
|
||||
path = newPath;
|
||||
dto.setType("add");
|
||||
break;
|
||||
case DELETE:
|
||||
path = oldPath;
|
||||
dto.setType("delete");
|
||||
break;
|
||||
case RENAME:
|
||||
path = newPath;
|
||||
dto.setType("rename");
|
||||
break;
|
||||
case MODIFY:
|
||||
path = newPath;
|
||||
dto.setType("modify");
|
||||
break;
|
||||
case COPY:
|
||||
path = newPath;
|
||||
dto.setType("copy");
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("unknown change type: " + file.getChangeType());
|
||||
}
|
||||
|
||||
dto.setNewPath(newPath);
|
||||
@@ -116,14 +126,6 @@ class DiffResultToDiffResultDtoMapper {
|
||||
return dto;
|
||||
}
|
||||
|
||||
private boolean isFilePath(String path) {
|
||||
return !isFileNull(path);
|
||||
}
|
||||
|
||||
private boolean isFileNull(String path) {
|
||||
return Strings.isNullOrEmpty(path) || "/dev/null".equals(path);
|
||||
}
|
||||
|
||||
private DiffResultDto.HunkDto mapHunk(Hunk hunk) {
|
||||
DiffResultDto.HunkDto dto = new DiffResultDto.HunkDto();
|
||||
dto.setContent(hunk.getRawHeader());
|
||||
|
||||
@@ -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<String> removed;
|
||||
|
||||
/**
|
||||
* Mapping of renamed files
|
||||
*/
|
||||
private List<RenamedDto> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,7 +30,11 @@ import org.mapstruct.Context;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import sonia.scm.repository.Added;
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.Modified;
|
||||
import sonia.scm.repository.Removed;
|
||||
import sonia.scm.repository.Renamed;
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -52,4 +56,18 @@ public abstract class ModificationsToDtoMapper {
|
||||
.self(resourceLinks.modifications().self(repository.getNamespace(), repository.getName(), target.getRevision()));
|
||||
target.add(linksBuilder.build());
|
||||
}
|
||||
|
||||
String map(Added added) {
|
||||
return added.getPath();
|
||||
}
|
||||
|
||||
String map(Removed removed) {
|
||||
return removed.getPath();
|
||||
}
|
||||
|
||||
String map(Modified modified) {
|
||||
return modified.getPath();
|
||||
}
|
||||
|
||||
abstract ModificationsDto.RenamedDto map(Renamed renamed);
|
||||
}
|
||||
|
||||
@@ -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.Link;
|
||||
@@ -35,11 +35,13 @@ import sonia.scm.repository.api.DiffResult;
|
||||
import sonia.scm.repository.api.Hunk;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
import static java.net.URI.create;
|
||||
import static java.util.Collections.emptyIterator;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -60,6 +62,8 @@ class DiffResultToDiffResultDtoMapperTest {
|
||||
assertAddedFile(files.get(0), "A.java", "abc", "java");
|
||||
assertModifiedFile(files.get(1), "B.ts", "abc", "def", "typescript");
|
||||
assertDeletedFile(files.get(2), "C.go", "ghi", "golang");
|
||||
assertRenamedFile(files.get(3), "typo.ts", "okay.ts", "def", "fixed", "typescript");
|
||||
assertCopiedFile(files.get(4), "good.ts", "better.ts", "def", "fixed", "typescript");
|
||||
|
||||
DiffResultDto.HunkDto hunk = files.get(1).getHunks().get(0);
|
||||
assertHunk(hunk, "@@ -3,4 1,2 @@", 1, 2, 3, 4);
|
||||
@@ -104,7 +108,9 @@ class DiffResultToDiffResultDtoMapperTest {
|
||||
deletedLine("c", 3)
|
||||
)
|
||||
),
|
||||
deletedFile("C.go", "ghi")
|
||||
deletedFile("C.go", "ghi"),
|
||||
renamedFile("okay.ts", "typo.ts", "fixed", "def"),
|
||||
copiedFile("better.ts", "good.ts", "fixed", "def")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -161,6 +167,24 @@ class DiffResultToDiffResultDtoMapperTest {
|
||||
assertThat(file.getLanguage()).isEqualTo(language);
|
||||
}
|
||||
|
||||
private void assertRenamedFile(DiffResultDto.FileDto file, String oldPath, String newPath, String oldRevision, String newRevision, String language) {
|
||||
assertThat(file.getOldPath()).isEqualTo(oldPath);
|
||||
assertThat(file.getNewPath()).isEqualTo(newPath);
|
||||
assertThat(file.getOldRevision()).isEqualTo(oldRevision);
|
||||
assertThat(file.getNewRevision()).isEqualTo(newRevision);
|
||||
assertThat(file.getType()).isEqualTo("rename");
|
||||
assertThat(file.getLanguage()).isEqualTo(language);
|
||||
}
|
||||
|
||||
private void assertCopiedFile(DiffResultDto.FileDto file, String oldPath, String newPath, String oldRevision, String newRevision, String language) {
|
||||
assertThat(file.getOldPath()).isEqualTo(oldPath);
|
||||
assertThat(file.getNewPath()).isEqualTo(newPath);
|
||||
assertThat(file.getOldRevision()).isEqualTo(oldRevision);
|
||||
assertThat(file.getNewRevision()).isEqualTo(newRevision);
|
||||
assertThat(file.getType()).isEqualTo("copy");
|
||||
assertThat(file.getLanguage()).isEqualTo(language);
|
||||
}
|
||||
|
||||
private DiffResult result(DiffFile... files) {
|
||||
DiffResult result = mock(DiffResult.class);
|
||||
when(result.iterator()).thenReturn(Arrays.asList(files).iterator());
|
||||
@@ -171,6 +195,7 @@ class DiffResultToDiffResultDtoMapperTest {
|
||||
DiffFile file = mock(DiffFile.class);
|
||||
when(file.getNewPath()).thenReturn(path);
|
||||
when(file.getNewRevision()).thenReturn(revision);
|
||||
when(file.getChangeType()).thenReturn(DiffFile.ChangeType.ADD);
|
||||
when(file.iterator()).thenReturn(Arrays.asList(hunks).iterator());
|
||||
return file;
|
||||
}
|
||||
@@ -179,6 +204,7 @@ class DiffResultToDiffResultDtoMapperTest {
|
||||
DiffFile file = mock(DiffFile.class);
|
||||
when(file.getOldPath()).thenReturn(path);
|
||||
when(file.getOldRevision()).thenReturn(revision);
|
||||
when(file.getChangeType()).thenReturn(DiffFile.ChangeType.DELETE);
|
||||
when(file.iterator()).thenReturn(Arrays.asList(hunks).iterator());
|
||||
return file;
|
||||
}
|
||||
@@ -189,10 +215,33 @@ class DiffResultToDiffResultDtoMapperTest {
|
||||
when(file.getNewRevision()).thenReturn(newRevision);
|
||||
when(file.getOldPath()).thenReturn(path);
|
||||
when(file.getOldRevision()).thenReturn(oldRevision);
|
||||
when(file.getChangeType()).thenReturn(DiffFile.ChangeType.MODIFY);
|
||||
when(file.iterator()).thenReturn(Arrays.asList(hunks).iterator());
|
||||
return file;
|
||||
}
|
||||
|
||||
private DiffFile renamedFile(String newPath, String oldPath, String newRevision, String oldRevision) {
|
||||
DiffFile file = mock(DiffFile.class);
|
||||
when(file.getNewPath()).thenReturn(newPath);
|
||||
when(file.getNewRevision()).thenReturn(newRevision);
|
||||
when(file.getOldPath()).thenReturn(oldPath);
|
||||
when(file.getOldRevision()).thenReturn(oldRevision);
|
||||
when(file.getChangeType()).thenReturn(DiffFile.ChangeType.RENAME);
|
||||
when(file.iterator()).thenReturn(emptyIterator());
|
||||
return file;
|
||||
}
|
||||
|
||||
private DiffFile copiedFile(String newPath, String oldPath, String newRevision, String oldRevision) {
|
||||
DiffFile file = mock(DiffFile.class);
|
||||
when(file.getNewPath()).thenReturn(newPath);
|
||||
when(file.getNewRevision()).thenReturn(newRevision);
|
||||
when(file.getOldPath()).thenReturn(oldPath);
|
||||
when(file.getOldRevision()).thenReturn(oldRevision);
|
||||
when(file.getChangeType()).thenReturn(DiffFile.ChangeType.COPY);
|
||||
when(file.iterator()).thenReturn(emptyIterator());
|
||||
return file;
|
||||
}
|
||||
|
||||
private Hunk hunk(String rawHeader, int newStart, int newLineCount, int oldStart, int oldLineCount, DiffLine... lines) {
|
||||
Hunk hunk = mock(Hunk.class);
|
||||
when(hunk.getRawHeader()).thenReturn(rawHeader);
|
||||
|
||||
@@ -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;
|
||||
@@ -40,9 +39,12 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.Added;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Removed;
|
||||
import sonia.scm.repository.Modified;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.api.ModificationsCommandBuilder;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user