mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[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.
This commit is contained in:
@@ -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<Integer> {
|
||||
public class VcsLogPathsIndex extends VcsLogFullDetailsIndex<List<VcsLogPathsIndex.ChangeData>> {
|
||||
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<Integer> {
|
||||
@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<Integer> {
|
||||
outer:
|
||||
while (!startIds.isEmpty()) {
|
||||
for (int currentPathId : startIds) {
|
||||
boolean foundCommit = !iterateCommitIdsAndValues(currentPathId, (renamedPathId, commitId) -> {
|
||||
boolean foundCommit = !iterateCommitIdsAndValues(currentPathId, (changesList, commitId) -> {
|
||||
Set<Integer> 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<Integer> {
|
||||
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<Integer> 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<Integer> {
|
||||
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<Integer> {
|
||||
}
|
||||
}
|
||||
|
||||
private static class PathsIndexer implements DataIndexer<Integer, Integer, VcsFullCommitDetails> {
|
||||
@NotNull
|
||||
private static Set<Integer> getOtherNames(@NotNull List<ChangeData> changesList) {
|
||||
Set<Integer> 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<Integer, List<ChangeData>, VcsFullCommitDetails> {
|
||||
@NotNull private final PersistentEnumeratorBase<String> myPathsEnumerator;
|
||||
@NotNull private final Set<String> myRoots;
|
||||
@NotNull private Consumer<Exception> myFatalErrorConsumer = LOG::error;
|
||||
@@ -223,52 +237,83 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex<Integer> {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Map<Integer, Integer> map(@NotNull VcsFullCommitDetails inputData) {
|
||||
Map<Integer, Integer> result = new THashMap<>();
|
||||
public Map<Integer, List<ChangeData>> map(@NotNull VcsFullCommitDetails inputData) {
|
||||
Map<Integer, List<ChangeData>> result = new THashMap<>();
|
||||
|
||||
|
||||
Collection<Couple<String>> moves;
|
||||
Collection<String> 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<Couple<String>> moves;
|
||||
Collection<String> 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<Integer, List<ChangeData>> result, int parent,
|
||||
@NotNull String afterPath, @Nullable String beforePath) throws IOException {
|
||||
int afterId = myPathsEnumerator.enumerate(afterPath);
|
||||
List<ChangeData> 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<ChangeData> beforeData = fillDataWithNulls(result, parent, beforeId);
|
||||
beforeData.add(new ChangeData(ChangeKind.RENAMED_FROM, afterId));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<ChangeData> fillDataWithNulls(@NotNull Map<Integer, List<ChangeData>> result,
|
||||
int parent, int pathId) {
|
||||
List<ChangeData> 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<String> getParentPaths(@NotNull Collection<String> paths) {
|
||||
Set<String> result = ContainerUtil.newHashSet();
|
||||
@@ -289,24 +334,95 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex<Integer> {
|
||||
}
|
||||
}
|
||||
|
||||
private static class NullableIntKeyDescriptor implements DataExternalizer<Integer> {
|
||||
private static class ChangeDataListKeyDescriptor implements DataExternalizer<List<ChangeData>> {
|
||||
@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<ChangeData> 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<ChangeData> read(@NotNull DataInput in) throws IOException {
|
||||
List<ChangeData> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user