From 1d7d415097dba8e794f9c07be8113809ff0afff0 Mon Sep 17 00:00:00 2001 From: Julia Beliaeva Date: Tue, 11 Apr 2017 19:45:34 +0300 Subject: [PATCH] [vcs-log] write paths changed in all of the parents for merge commits into index Data for all of the parents is required for correct file history in case of file renamed in one of the branch. Also, since we are changing index format anyway, keep information which name was before and which was after for renames. This will help to simplify file history. --- .../vcs/log/data/index/VcsLogPathsIndex.java | 254 +++++++++++++----- 1 file changed, 185 insertions(+), 69 deletions(-) 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); } }