2 * Copyright 2000-2016 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.vcs.log.data.index;
18 import com.intellij.openapi.Disposable;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.util.Couple;
21 import com.intellij.openapi.util.SystemInfo;
22 import com.intellij.openapi.util.io.FileUtil;
23 import com.intellij.openapi.vcs.FilePath;
24 import com.intellij.openapi.vcs.changes.Change;
25 import com.intellij.openapi.vfs.VirtualFile;
26 import com.intellij.util.Consumer;
27 import com.intellij.util.PathUtil;
28 import com.intellij.util.containers.ContainerUtil;
29 import com.intellij.util.indexing.DataIndexer;
30 import com.intellij.util.indexing.StorageException;
31 import com.intellij.util.io.*;
32 import com.intellij.util.text.CaseInsensitiveStringHashingStrategy;
33 import com.intellij.vcs.log.VcsFullCommitDetails;
34 import com.intellij.vcs.log.impl.FatalErrorHandler;
35 import com.intellij.vcs.log.impl.VcsChangesLazilyParsedDetails;
36 import com.intellij.vcs.log.util.PersistentUtil;
37 import gnu.trove.THashMap;
38 import gnu.trove.TIntHashSet;
39 import org.jetbrains.annotations.NotNull;
41 import java.io.DataInput;
42 import java.io.DataOutput;
44 import java.io.IOException;
45 import java.util.Arrays;
46 import java.util.Collection;
50 import static com.intellij.util.containers.ContainerUtil.newTroveSet;
51 import static com.intellij.vcs.log.data.index.VcsLogPersistentIndex.getVersion;
53 public class VcsLogPathsIndex extends VcsLogFullDetailsIndex<Integer> {
54 private static final Logger LOG = Logger.getInstance(VcsLogPathsIndex.class);
55 public static final String PATHS = "paths";
56 public static final String INDEX_PATHS_IDS = "paths-ids";
58 @NotNull private final PathsIndexer myPathsIndexer;
60 public VcsLogPathsIndex(@NotNull String logId,
61 @NotNull Set<VirtualFile> roots,
62 @NotNull FatalErrorHandler fatalErrorHandler,
63 @NotNull Disposable disposableParent) throws IOException {
64 super(logId, PATHS, getVersion(), new PathsIndexer(createPathsEnumerator(logId), roots),
65 new NullableIntKeyDescriptor(), fatalErrorHandler, disposableParent);
67 myPathsIndexer = (PathsIndexer)myIndexer;
68 myPathsIndexer.setFatalErrorConsumer(e -> fatalErrorHandler.consume(this, e));
72 private static PersistentEnumeratorBase<String> createPathsEnumerator(@NotNull String logId) throws IOException {
73 File storageFile = PersistentUtil.getStorageFile(INDEX, INDEX_PATHS_IDS, logId, getVersion(), true);
74 return new PersistentBTreeEnumerator<>(storageFile, SystemInfo.isFileSystemCaseSensitive ? EnumeratorStringDescriptor.INSTANCE
75 : new ToLowerCaseStringDescriptor(),
76 Page.PAGE_SIZE, null, getVersion());
80 public void flush() throws StorageException {
82 myPathsIndexer.getPathsEnumerator().force();
85 public TIntHashSet getCommitsForPaths(@NotNull Collection<FilePath> paths) throws IOException, StorageException {
86 Set<Integer> allPathIds = ContainerUtil.newHashSet();
87 for (FilePath path : paths) {
88 allPathIds.add(myPathsIndexer.myPathsEnumerator.enumerate(path.getPath()));
91 TIntHashSet result = new TIntHashSet();
92 Set<Integer> renames = allPathIds;
93 while (!renames.isEmpty()) {
94 renames = addCommitsAndGetRenames(renames, allPathIds, result);
95 allPathIds.addAll(renames);
102 public Set<Integer> addCommitsAndGetRenames(@NotNull Set<Integer> newPathIds,
103 @NotNull Set<Integer> allPathIds,
104 @NotNull TIntHashSet commits)
105 throws StorageException {
106 Set<Integer> renames = ContainerUtil.newHashSet();
107 for (Integer key : newPathIds) {
108 iterateCommitIdsAndValues(key, (value, commit) -> {
110 if (value != null && !allPathIds.contains(value)) {
119 public void dispose() {
122 myPathsIndexer.getPathsEnumerator().close();
124 catch (IOException e) {
129 private static class PathsIndexer implements DataIndexer<Integer, Integer, VcsFullCommitDetails> {
130 @NotNull private final PersistentEnumeratorBase<String> myPathsEnumerator;
131 @NotNull private final Set<String> myRoots;
132 @NotNull private Consumer<Exception> myFatalErrorConsumer = LOG::error;
134 private PathsIndexer(@NotNull PersistentEnumeratorBase<String> enumerator, @NotNull Set<VirtualFile> roots) {
135 myPathsEnumerator = enumerator;
136 myRoots = newTroveSet(FileUtil.PATH_HASHING_STRATEGY);
137 for (VirtualFile root : roots) {
138 myRoots.add(root.getPath());
142 public void setFatalErrorConsumer(@NotNull Consumer<Exception> fatalErrorConsumer) {
143 myFatalErrorConsumer = fatalErrorConsumer;
148 public Map<Integer, Integer> map(@NotNull VcsFullCommitDetails inputData) {
149 Map<Integer, Integer> result = new THashMap<>();
152 Collection<Couple<String>> moves;
153 Collection<String> changedPaths;
154 if (inputData instanceof VcsChangesLazilyParsedDetails) {
155 changedPaths = ((VcsChangesLazilyParsedDetails)inputData).getModifiedPaths();
156 moves = ((VcsChangesLazilyParsedDetails)inputData).getRenamedPaths();
159 moves = ContainerUtil.newHashSet();
160 changedPaths = ContainerUtil.newHashSet();
161 for (Change change : inputData.getChanges()) {
162 if (change.getAfterRevision() != null) changedPaths.add(change.getAfterRevision().getFile().getPath());
163 if (change.getBeforeRevision() != null) changedPaths.add(change.getBeforeRevision().getFile().getPath());
164 if (change.getType().equals(Change.Type.MOVED)) {
165 moves.add(Couple.of(change.getBeforeRevision().getFile().getPath(), change.getAfterRevision().getFile().getPath()));
170 getParentPaths(changedPaths).forEach(changedPath -> {
172 result.put(myPathsEnumerator.enumerate(changedPath), null);
174 catch (IOException e) {
175 myFatalErrorConsumer.consume(e);
178 moves.forEach(renamedPaths -> {
180 int beforeId = myPathsEnumerator.enumerate(renamedPaths.first);
181 int afterId = myPathsEnumerator.enumerate(renamedPaths.second);
183 result.put(beforeId, afterId);
184 result.put(afterId, beforeId);
186 catch (IOException e) {
187 myFatalErrorConsumer.consume(e);
195 private Collection<String> getParentPaths(@NotNull Collection<String> paths) {
196 Set<String> result = ContainerUtil.newHashSet();
197 for (String path : paths) {
198 while (!path.isEmpty() && !result.contains(path)) {
200 if (myRoots.contains(path)) break;
202 path = PathUtil.getParentPath(path);
209 public PersistentEnumeratorBase<String> getPathsEnumerator() {
210 return myPathsEnumerator;
214 private static class NullableIntKeyDescriptor implements DataExternalizer<Integer> {
216 public void save(@NotNull DataOutput out, Integer value) throws IOException {
218 out.writeBoolean(false);
221 out.writeBoolean(true);
227 public Integer read(@NotNull DataInput in) throws IOException {
228 if (in.readBoolean()) {
235 private static class ToLowerCaseStringDescriptor implements KeyDescriptor<String> {
237 public int getHashCode(String value) {
238 return CaseInsensitiveStringHashingStrategy.INSTANCE.computeHashCode(value);
242 public boolean isEqual(String val1, String val2) {
243 return CaseInsensitiveStringHashingStrategy.INSTANCE.equals(val1, val2);
247 public void save(@NotNull DataOutput out, String value) throws IOException {
248 IOUtil.writeUTF(out, value.toLowerCase());
252 public String read(@NotNull DataInput in) throws IOException {
253 return IOUtil.readUTF(in);