/** * This file is part of git-as-svn. It is subject to the license terms * in the LICENSE file found in the top-level directory of this distribution * and at http://www.gnu.org/licenses/gpl-2.0.html. No part of git-as-svn, * including this file, may be copied, modified, propagated, or distributed * except according to the terms contained in the LICENSE file. */ package svnserver.repository.git; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.tmatesoft.svn.core.SVNException; import svnserver.StringHelper; import svnserver.repository.SvnForbiddenException; import java.io.IOException; import java.util.*; /** * Class for collecting changes in revision. * * @author Artem V. Navrotskiy <bozaro@users.noreply.github.com> */ public final class ChangeHelper { private ChangeHelper() { } @NotNull public static Map<String, GitLogPair> collectChanges(@Nullable GitFile oldTree, @NotNull GitFile newTree, boolean fullRemoved) throws IOException, SVNException { final Map<String, GitLogPair> changes = new HashMap<>(); final GitLogPair logEntry = new GitLogPair(oldTree, newTree); if (oldTree == null || logEntry.isModified()) { changes.put("/", logEntry); } final Queue<TreeCompareEntry> queue = new ArrayDeque<>(); queue.add(new TreeCompareEntry("", oldTree, newTree)); while (!queue.isEmpty()) { collectChanges(changes, queue, queue.remove(), fullRemoved); } return changes; } private static void collectChanges(@NotNull Map<String, GitLogPair> changes, Queue<TreeCompareEntry> queue, @NotNull TreeCompareEntry compareEntry, boolean fullRemoved) throws IOException, SVNException { for (GitLogPair pair : compareEntry) { final GitFile newEntry = pair.getNewEntry(); final GitFile oldEntry = pair.getOldEntry(); if (newEntry == null && oldEntry == null) { throw new IllegalStateException(); } if (newEntry != null) { if (!newEntry.equals(oldEntry)) { final String fullPath = StringHelper.joinPath(compareEntry.path, newEntry.getFileName()); if (newEntry.isDirectory()) { final GitLogPair oldChange = changes.put(fullPath, pair); if (oldChange != null) { changes.put(fullPath, new GitLogPair(oldChange.getOldEntry(), newEntry)); } queue.add(new TreeCompareEntry(fullPath, ((oldEntry != null) && oldEntry.isDirectory()) ? oldEntry : null, newEntry)); } else if (oldEntry == null || pair.isModified()) { final GitLogPair oldChange = changes.put(fullPath, pair); if (oldChange != null) { changes.put(fullPath, new GitLogPair(oldChange.getOldEntry(), newEntry)); } } } } else { final String fullPath = StringHelper.joinPath(compareEntry.path, oldEntry.getFileName()); final GitLogPair oldChange = changes.put(fullPath, pair); if (oldChange != null) { changes.put(fullPath, new GitLogPair(oldEntry, oldChange.getNewEntry())); } } if (fullRemoved && oldEntry != null && oldEntry.isDirectory()) { final String fullPath = StringHelper.joinPath(compareEntry.path, oldEntry.getFileName()); if (newEntry == null || (!newEntry.isDirectory())) { queue.add(new TreeCompareEntry(fullPath, oldEntry, null)); } } } } private static class TreeCompareEntry implements Iterable<GitLogPair> { @NotNull private final String path; @NotNull private final Iterable<GitFile> oldTree; @NotNull private final Iterable<GitFile> newTree; private TreeCompareEntry(@NotNull String path, @Nullable GitFile oldTree, @Nullable GitFile newTree) throws IOException, SVNException { this.path = path; this.oldTree = getIterable(oldTree); this.newTree = getIterable(newTree); } @NotNull private static Iterable<GitFile> getIterable(@Nullable GitFile tree) throws IOException, SVNException { try { return tree != null ? tree.getEntries() : Collections.emptyList(); } catch (SvnForbiddenException e) { // todo: Need some additional logic for missing Git objects return Collections.emptyList(); } } @Override public Iterator<GitLogPair> iterator() { return new LogPairIterator(oldTree, newTree); } } private static class LogPairIterator implements Iterator<GitLogPair> { @NotNull private final Iterator<GitFile> oldIter; @NotNull private final Iterator<GitFile> newIter; @Nullable private GitFile oldItem; @Nullable private GitFile newItem; private LogPairIterator(@NotNull Iterable<GitFile> oldTree, @NotNull Iterable<GitFile> newTree) { oldIter = oldTree.iterator(); newIter = newTree.iterator(); oldItem = nextItem(oldIter); newItem = nextItem(newIter); } @Nullable private static GitFile nextItem(@NotNull Iterator<GitFile> iter) { return iter.hasNext() ? iter.next() : null; } @Override public boolean hasNext() { return (oldItem != null) || (newItem != null); } @Override public GitLogPair next() { final int compare; if (newItem == null) { compare = -1; } else if (oldItem == null) { compare = 1; } else { final GitTreeEntry oldTreeEntry = oldItem.getTreeEntry(); final GitTreeEntry newTreeEntry = newItem.getTreeEntry(); if (oldTreeEntry == null || newTreeEntry == null) { throw new IllegalStateException("Tree entry can be null only for revision tree root."); } compare = oldTreeEntry.compareTo(newTreeEntry); } final GitFile oldEntry; final GitFile newEntry; if (compare <= 0) { oldEntry = oldItem; oldItem = nextItem(oldIter); } else { oldEntry = null; } if (compare >= 0) { newEntry = newItem; newItem = nextItem(newIter); } else { newEntry = null; } return new GitLogPair(oldEntry, newEntry); } } }