diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPathsIndex.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPathsIndex.java index 36ba498509d0..bee21ca99b1b 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPathsIndex.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPathsIndex.java @@ -38,20 +38,19 @@ import com.intellij.vcsUtil.VcsUtil; import gnu.trove.THashMap; import gnu.trove.TIntHashSet; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.DataInput; import java.io.DataOutput; import java.io.File; import java.io.IOException; -import java.util.Collection; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.ObjIntConsumer; import static com.intellij.util.containers.ContainerUtil.newTroveSet; import static com.intellij.vcs.log.data.index.VcsLogPersistentIndex.getVersion; -public class VcsLogPathsIndex extends VcsLogFullDetailsIndex { +public class VcsLogPathsIndex extends VcsLogFullDetailsIndex> { private static final Logger LOG = Logger.getInstance(VcsLogPathsIndex.class); public static final String PATHS = "paths"; public static final String INDEX_PATHS_IDS = "paths-ids"; @@ -63,7 +62,7 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex { @NotNull FatalErrorHandler fatalErrorHandler, @NotNull Disposable disposableParent) throws IOException { super(logId, PATHS, getVersion(), new PathsIndexer(createPathsEnumerator(logId), roots), - new NullableIntKeyDescriptor(), fatalErrorHandler, disposableParent); + new ChangeDataListKeyDescriptor(), fatalErrorHandler, disposableParent); myPathsIndexer = (PathsIndexer)myIndexer; myPathsIndexer.setFatalErrorConsumer(e -> fatalErrorHandler.consume(this, e)); @@ -120,14 +119,17 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex { outer: while (!startIds.isEmpty()) { for (int currentPathId : startIds) { - boolean foundCommit = !iterateCommitIdsAndValues(currentPathId, (renamedPathId, commitId) -> { + boolean foundCommit = !iterateCommitIdsAndValues(currentPathId, (changesList, commitId) -> { + Set otherNames = getOtherNames(changesList); if (commitId == commit) { resultIds.add(currentPathId); - if (renamedPathId != null) resultIds.add(renamedPathId); + resultIds.addAll(otherNames); return false; } - if (renamedPathId != null && !allIds.contains(renamedPathId)) { - newIds.add(renamedPathId); + for (Integer otherPath : otherNames) { + if (!allIds.contains(otherPath)) { + newIds.add(otherPath); + } } return true; }); @@ -154,20 +156,23 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex { while (!startIds.isEmpty()) { for (int currentPathId : startIds) { FilePath currentPath = VcsUtil.getFilePath(myPathsIndexer.myPathsEnumerator.valueOf(currentPathId)); - iterateCommitIdsAndValues(currentPathId, (renamedPathId, commitId) -> { - FilePath renamedPath = null; - if (renamedPathId != null) { - if (!allIds.contains(renamedPathId)) { - newIds.add(renamedPathId); + iterateCommitIdsAndValues(currentPathId, (changesList, commitId) -> { + Set otherNames = getOtherNames(changesList); + for (int renamed : otherNames) { + if (!allIds.contains(renamed)) { + newIds.add(renamed); } try { - renamedPath = VcsUtil.getFilePath(myPathsIndexer.myPathsEnumerator.valueOf(renamedPathId)); + FilePath renamedPath = VcsUtil.getFilePath(myPathsIndexer.myPathsEnumerator.valueOf(renamed)); + consumer.accept(Couple.of(currentPath, renamedPath), commitId); } catch (IOException e) { LOG.error(e); } } - consumer.accept(Couple.of(currentPath, renamedPath), commitId); + if (otherNames.isEmpty()) { + consumer.accept(Couple.of(currentPath, null), commitId); + } }); } startIds = ContainerUtil.newHashSet(newIds); @@ -185,9 +190,7 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex { for (Integer key : newPathIds) { iterateCommitIdsAndValues(key, (value, commit) -> { commits.add(commit); - if (value != null && !allPathIds.contains(value)) { - renames.add(value); - } + renames.addAll(ContainerUtil.filter(getOtherNames(value), r -> !allPathIds.contains(r))); }); } return renames; @@ -204,7 +207,18 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex { } } - private static class PathsIndexer implements DataIndexer { + @NotNull + private static Set getOtherNames(@NotNull List changesList) { + Set otherNames = ContainerUtil.newHashSet(); + for (ChangeData data : changesList) { + if (data != null && data.otherPath != -1) { + otherNames.add(data.otherPath); + } + } + return otherNames; + } + + private static class PathsIndexer implements DataIndexer, VcsFullCommitDetails> { @NotNull private final PersistentEnumeratorBase myPathsEnumerator; @NotNull private final Set myRoots; @NotNull private Consumer myFatalErrorConsumer = LOG::error; @@ -223,52 +237,83 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex { @NotNull @Override - public Map map(@NotNull VcsFullCommitDetails inputData) { - Map result = new THashMap<>(); + public Map> map(@NotNull VcsFullCommitDetails inputData) { + Map> result = new THashMap<>(); - - Collection> moves; - Collection changedPaths; - if (inputData instanceof VcsIndexableDetails) { - changedPaths = ((VcsIndexableDetails)inputData).getModifiedPaths(0); - moves = ((VcsIndexableDetails)inputData).getRenamedPaths(0); - } - else { - moves = ContainerUtil.newHashSet(); - changedPaths = ContainerUtil.newHashSet(); - for (Change change : inputData.getChanges()) { - if (change.getAfterRevision() != null) changedPaths.add(change.getAfterRevision().getFile().getPath()); - if (change.getBeforeRevision() != null) changedPaths.add(change.getBeforeRevision().getFile().getPath()); - if (change.getType().equals(Change.Type.MOVED)) { - moves.add(Couple.of(change.getBeforeRevision().getFile().getPath(), change.getAfterRevision().getFile().getPath())); + for (int parent = 0; parent < inputData.getParents().size(); parent++) { + Collection> moves; + Collection changedPaths; + if (inputData instanceof VcsIndexableDetails) { + changedPaths = ((VcsIndexableDetails)inputData).getModifiedPaths(parent); + moves = ((VcsIndexableDetails)inputData).getRenamedPaths(parent); + } + else { + moves = ContainerUtil.newHashSet(); + changedPaths = ContainerUtil.newHashSet(); + for (Change change : inputData.getChanges()) { + if (change.getAfterRevision() != null) changedPaths.add(change.getAfterRevision().getFile().getPath()); + if (change.getBeforeRevision() != null) changedPaths.add(change.getBeforeRevision().getFile().getPath()); + if (change.getType().equals(Change.Type.MOVED)) { + moves.add(Couple.of(change.getBeforeRevision().getFile().getPath(), change.getAfterRevision().getFile().getPath())); + } } } + + int finalParent = parent; + getParentPaths(changedPaths).forEach(changedPath -> { + try { + addChangeToResult(result, finalParent, changedPath, null); + } + catch (IOException e) { + myFatalErrorConsumer.consume(e); + } + }); + moves.forEach(renamedPaths -> { + try { + addChangeToResult(result, finalParent, renamedPaths.second, renamedPaths.first); + } + catch (IOException e) { + myFatalErrorConsumer.consume(e); + } + }); } - getParentPaths(changedPaths).forEach(changedPath -> { - try { - result.put(myPathsEnumerator.enumerate(changedPath), null); - } - catch (IOException e) { - myFatalErrorConsumer.consume(e); - } - }); - moves.forEach(renamedPaths -> { - try { - int beforeId = myPathsEnumerator.enumerate(renamedPaths.first); - int afterId = myPathsEnumerator.enumerate(renamedPaths.second); - - result.put(beforeId, afterId); - result.put(afterId, beforeId); - } - catch (IOException e) { - myFatalErrorConsumer.consume(e); - } - }); + for (int pathId : result.keySet()) { + fillDataWithNulls(result, inputData.getParents().size(), pathId); + } return result; } + private void addChangeToResult(@NotNull Map> result, int parent, + @NotNull String afterPath, @Nullable String beforePath) throws IOException { + int afterId = myPathsEnumerator.enumerate(afterPath); + List data = fillDataWithNulls(result, parent, afterId); + if (beforePath == null) { + data.add(null); + } + else { + int beforeId = myPathsEnumerator.enumerate(beforePath); + data.add(new ChangeData(ChangeKind.RENAMED_TO, beforeId)); + List beforeData = fillDataWithNulls(result, parent, beforeId); + beforeData.add(new ChangeData(ChangeKind.RENAMED_FROM, afterId)); + } + } + + @NotNull + private static List fillDataWithNulls(@NotNull Map> result, + int parent, int pathId) { + List data = result.get(pathId); + if (data == null) { + data = ContainerUtil.newSmartList(); + result.put(pathId, data); + } + for (int i = data.size(); i < parent; i++) { + data.add(null); + } + return data; + } + @NotNull private Collection getParentPaths(@NotNull Collection paths) { Set result = ContainerUtil.newHashSet(); @@ -289,24 +334,95 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex { } } - private static class NullableIntKeyDescriptor implements DataExternalizer { + private static class ChangeDataListKeyDescriptor implements DataExternalizer> { @Override - public void save(@NotNull DataOutput out, Integer value) throws IOException { - if (value == null) { - out.writeBoolean(false); - } - else { - out.writeBoolean(true); - out.writeInt(value); + public void save(@NotNull DataOutput out, List value) throws IOException { + DataInputOutputUtil.writeINT(out, value.size()); + for (ChangeData data : value) { + if (data == null) { + out.writeBoolean(false); + } + else { + out.writeBoolean(true); + out.writeByte(data.kind.id); + if (data.kind == ChangeKind.RENAMED_TO || data.kind == ChangeKind.RENAMED_FROM) { + out.writeInt(data.otherPath); + } + } } } @Override - public Integer read(@NotNull DataInput in) throws IOException { - if (in.readBoolean()) { - return in.readInt(); + public List read(@NotNull DataInput in) throws IOException { + List value = ContainerUtil.newSmartList(); + + int size = DataInputOutputUtil.readINT(in); + for (int i = 0; i < size; i++) { + if (in.readBoolean()) { + ChangeKind kind = ChangeKind.getKind(in.readByte()); + int otherPath; + if (kind == ChangeKind.RENAMED_TO || kind == ChangeKind.RENAMED_FROM) { + otherPath = in.readInt(); + } + else { + otherPath = -1; + } + value.add(new ChangeData(kind, otherPath)); + } + else { + value.add(null); + } } - return null; + + return value; + } + } + + public static class ChangeData { + @NotNull public final ChangeKind kind; + public final int otherPath; + + public ChangeData(@NotNull ChangeKind kind, int otherPath) { + this.kind = kind; + this.otherPath = otherPath; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ChangeData data = (ChangeData)o; + return otherPath == data.otherPath && + kind == data.kind; + } + + @Override + public int hashCode() { + return Objects.hash(kind, otherPath); + } + } + + public enum ChangeKind { + MODIFIED((byte)0), + RENAMED_FROM((byte)1), + RENAMED_TO((byte)2); + + public final byte id; + + ChangeKind(byte id) { + this.id = id; + } + + public static ChangeKind getKind(byte id) { + switch (id) { + case (0): + return MODIFIED; + case (1): + return RENAMED_FROM; + case (2): + return RENAMED_TO; + } + throw new IllegalArgumentException("No change kind with id " + id); } }