mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-02 19:45:51 +01:00
Modifications command between two revisions (#1761)
Adds the option to compute the modifications between two revisions unsing the modifications command.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collector;
|
||||
|
||||
import static java.util.Collections.emptySet;
|
||||
import static java.util.Collections.unmodifiableCollection;
|
||||
import static java.util.Collections.unmodifiableSet;
|
||||
|
||||
/**
|
||||
* This class can be used to "consolidate" modifications for a stream of commit based modifications.
|
||||
* Single modifications will be changed or ignored if subsequent modifications effect them. An "added"
|
||||
* for a file for example will be ignored, if the same file is marked as "removed" later on. Another
|
||||
* example would be a "modification" of a file, that was "added" beforehand, because in summary this
|
||||
* simply is still an "added" file.
|
||||
*/
|
||||
class ConsolidatingModificationCollector implements Collector<Modification, ConsolidatedModifications, Collection<Modification>> {
|
||||
|
||||
static ConsolidatingModificationCollector consolidate() {
|
||||
return new ConsolidatingModificationCollector();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<ConsolidatedModifications> supplier() {
|
||||
return ConsolidatedModifications::new;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiConsumer<ConsolidatedModifications, Modification> accumulator() {
|
||||
return (existingModifications, currentModification) -> {
|
||||
if (currentModification instanceof Copied) {
|
||||
existingModifications.added(new Added(((Copied) currentModification).getTargetPath()));
|
||||
} else if (currentModification instanceof Removed) {
|
||||
existingModifications.removed((Removed) currentModification);
|
||||
} else if (currentModification instanceof Renamed) {
|
||||
Renamed renameModification = (Renamed) currentModification;
|
||||
existingModifications.removed(new Removed(renameModification.getOldPath()));
|
||||
existingModifications.added(new Added(renameModification.getNewPath()));
|
||||
} else if (currentModification instanceof Added) {
|
||||
existingModifications.added((Added) currentModification);
|
||||
} else if (currentModification instanceof Modified) {
|
||||
existingModifications.modified((Modified) currentModification);
|
||||
} else {
|
||||
throw new IllegalStateException("cannot handle modification of unknown type " + currentModification.getClass());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinaryOperator<ConsolidatedModifications> combiner() {
|
||||
return null; // Combiner not needed because we do not support Collector.Characteristics#CONCURRENT
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<ConsolidatedModifications, Collection<Modification>> finisher() {
|
||||
return ConsolidatedModifications::getModifications;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Characteristics> characteristics() {
|
||||
return emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
class ConsolidatedModifications {
|
||||
private final Map<String, Modification> modifications = new HashMap<>();
|
||||
|
||||
Set<String> getPaths() {
|
||||
return unmodifiableSet(modifications.keySet());
|
||||
}
|
||||
|
||||
Collection<Modification> getModifications() {
|
||||
return unmodifiableCollection(modifications.values());
|
||||
}
|
||||
|
||||
void added(Added added) {
|
||||
Modification earlierModification = modifications.get(added.getPath());
|
||||
if (earlierModification instanceof Removed) {
|
||||
modifications.put(added.getPath(), new Modified(added.getPath()));
|
||||
} else {
|
||||
modifications.put(added.getPath(), added);
|
||||
}
|
||||
}
|
||||
|
||||
void modified(Modified modified) {
|
||||
Modification earlierModification = modifications.get(modified.getPath());
|
||||
if (!(earlierModification instanceof Added)) { // added should still be added
|
||||
modified.getEffectedPaths().forEach(path -> modifications.put(path, modified));
|
||||
}
|
||||
}
|
||||
|
||||
void removed(Removed removed) {
|
||||
Modification earlierModification = modifications.get(removed.getPath());
|
||||
if (earlierModification instanceof Added) {
|
||||
modifications.remove(removed.getPath());
|
||||
} else {
|
||||
modifications.put(removed.getPath(), removed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,15 +47,17 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
import static sonia.scm.repository.ConsolidatingModificationCollector.consolidate;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
@@ -118,22 +120,29 @@ public final class SvnUtil
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Modifications createModifications(String startRevision, String endRevision, Collection<SVNLogEntry> entries) {
|
||||
Collection<Modification> consolidatedModifications =
|
||||
entries.stream()
|
||||
.flatMap(SvnUtil::createModificationStream)
|
||||
.collect(consolidate());
|
||||
return new Modifications(startRevision, endRevision, consolidatedModifications);
|
||||
}
|
||||
|
||||
public static Modifications createModifications(SVNLogEntry entry, String revision) {
|
||||
return new Modifications(revision, createModificationStream(entry).collect(toList()));
|
||||
}
|
||||
|
||||
private static Stream<Modification> createModificationStream(SVNLogEntry entry) {
|
||||
Map<String, SVNLogEntryPath> changeMap = entry.getChangedPaths();
|
||||
|
||||
List<Modification> modificationList;
|
||||
if (Util.isNotEmpty(changeMap)) {
|
||||
modificationList = changeMap.values().stream()
|
||||
return changeMap.values().stream()
|
||||
.map(e -> asModification(e.getType(), e.getPath()))
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.collect(Collectors.toList());
|
||||
.map(Optional::get);
|
||||
} else {
|
||||
modificationList = emptyList();
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
return new Modifications(revision, modificationList);
|
||||
}
|
||||
|
||||
public static Optional<Modification> asModification(char type, String path) {
|
||||
@@ -383,4 +392,5 @@ public final class SvnUtil
|
||||
{
|
||||
return Strings.nullToEmpty(id).startsWith(ID_TRANSACTION_PREFIX);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,14 +48,12 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif
|
||||
|
||||
@Override
|
||||
public Modifications getModifications(String revisionOrTransactionId) {
|
||||
Modifications modifications;
|
||||
try {
|
||||
if (SvnUtil.isTransactionEntryId(revisionOrTransactionId)) {
|
||||
modifications = getModificationsFromTransaction(SvnUtil.getTransactionId(revisionOrTransactionId));
|
||||
return getModificationsFromTransaction(SvnUtil.getTransactionId(revisionOrTransactionId));
|
||||
} else {
|
||||
modifications = getModificationFromRevision(revisionOrTransactionId);
|
||||
return getModificationFromRevision(revisionOrTransactionId, revisionOrTransactionId);
|
||||
}
|
||||
return modifications;
|
||||
} catch (SVNException ex) {
|
||||
throw new InternalRepositoryException(
|
||||
repository,
|
||||
@@ -65,15 +63,33 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifications getModifications(String baseRevision, String revision) {
|
||||
try {
|
||||
return getModificationFromRevision(baseRevision, revision);
|
||||
} catch (SVNException ex) {
|
||||
throw new InternalRepositoryException(
|
||||
repository,
|
||||
"failed to get svn modifications from " + baseRevision + " to " + revision,
|
||||
ex
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Modifications getModificationFromRevision(String revision) throws SVNException {
|
||||
log.debug("get svn modifications from revision: {}", revision);
|
||||
long revisionNumber = SvnUtil.getRevisionNumber(revision, repository);
|
||||
private Modifications getModificationFromRevision(String startRevision, String endRevision) throws SVNException {
|
||||
log.debug("get svn modifications from revision {} to {}", startRevision, endRevision);
|
||||
long startRevisionNumber = SvnUtil.getRevisionNumber(startRevision, repository);
|
||||
long endRevisionNumber = SvnUtil.getRevisionNumber(endRevision, repository);
|
||||
SVNRepository repo = open();
|
||||
Collection<SVNLogEntry> entries = repo.log(null, null, revisionNumber,
|
||||
revisionNumber, true, true);
|
||||
Collection<SVNLogEntry> entries = repo.log(null, null, startRevisionNumber,
|
||||
endRevisionNumber, true, true);
|
||||
if (Util.isNotEmpty(entries)) {
|
||||
return SvnUtil.createModifications(entries.iterator().next(), revision);
|
||||
if (startRevision.equals(endRevision)) {
|
||||
return SvnUtil.createModifications(entries.iterator().next(), endRevision);
|
||||
} else {
|
||||
return SvnUtil.createModifications(startRevision, endRevision, entries);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -87,10 +103,4 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif
|
||||
|
||||
return new Modifications(null, modificationList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifications getModifications(ModificationsCommandRequest request) {
|
||||
return getModifications(request.getRevision());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.io.Closeables;
|
||||
import sonia.scm.repository.Feature;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.SvnRepositoryHandler;
|
||||
import sonia.scm.repository.SvnWorkingCopyFactory;
|
||||
@@ -34,6 +35,7 @@ import sonia.scm.repository.api.HookContextFactory;
|
||||
|
||||
import javax.net.ssl.TrustManager;
|
||||
import java.io.IOException;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -41,7 +43,6 @@ import java.util.Set;
|
||||
*/
|
||||
public class SvnRepositoryServiceProvider extends RepositoryServiceProvider {
|
||||
|
||||
//J-
|
||||
public static final Set<Command> COMMANDS = ImmutableSet.of(
|
||||
Command.BLAME,
|
||||
Command.BROWSE,
|
||||
@@ -55,8 +56,10 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider {
|
||||
Command.FULL_HEALTH_CHECK,
|
||||
Command.MIRROR
|
||||
);
|
||||
//J+
|
||||
|
||||
public static final Set<Feature> FEATURES = EnumSet.of(
|
||||
Feature.MODIFICATIONS_BETWEEN_REVISIONS
|
||||
);
|
||||
|
||||
private final SvnContext context;
|
||||
private final SvnWorkingCopyFactory workingCopyFactory;
|
||||
@@ -129,6 +132,11 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider {
|
||||
return COMMANDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Feature> getSupportedFeatures() {
|
||||
return FEATURES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnbundleCommand getUnbundleCommand() {
|
||||
return new SvnUnbundleCommand(context, hookContextFactory, new SvnLogCommand(context));
|
||||
|
||||
Reference in New Issue
Block a user