[vcs-log] introduce VcsLogIndex with a simple implementation that has a phm from commit id to full message

This commit is contained in:
Julia Beliaeva
2016-09-20 00:15:17 +03:00
committed by Julia Beliaeva
parent 7774154880
commit d401d0dec6
11 changed files with 454 additions and 31 deletions
@@ -9,14 +9,13 @@ import java.io.IOException;
* Date: 8/10/11
*/
public interface PersistentMap<K, V> {
V get(K key) throws IOException;
void put(K key, V value) throws IOException;
boolean processKeys(Processor<K> processor) throws IOException;
boolean isClosed();
boolean isDirty();
@@ -69,7 +69,7 @@ abstract class AbstractDataGetter<T extends VcsShortCommitDetails> implements Di
Disposer.register(parentDisposable, this);
myLoader =
new SequentialLimitedLifoExecutor<>(this, MAX_LOADING_TASKS, task -> {
preLoadCommitData(task.myCommits);
preLoadCommitData(task.myCommits, true);
notifyLoaded();
});
}
@@ -142,7 +142,7 @@ abstract class AbstractDataGetter<T extends VcsShortCommitDetails> implements Di
public void run(@NotNull final ProgressIndicator indicator) {
indicator.checkCanceled();
try {
TIntObjectHashMap<T> map = preLoadCommitData(toLoad);
TIntObjectHashMap<T> map = preLoadCommitData(toLoad, true);
map.forEachValue(value -> {
result.add(value);
return true;
@@ -237,7 +237,8 @@ abstract class AbstractDataGetter<T extends VcsShortCommitDetails> implements Di
return commits;
}
private TIntObjectHashMap<T> preLoadCommitData(@NotNull TIntHashSet commits) throws VcsException {
@NotNull
public TIntObjectHashMap<T> preLoadCommitData(@NotNull TIntHashSet commits, boolean saveInCache) throws VcsException {
TIntObjectHashMap<T> result = new TIntObjectHashMap<>();
final MultiMap<VirtualFile, String> rootsAndHashes = MultiMap.create();
commits.forEach(commit -> {
@@ -256,7 +257,7 @@ abstract class AbstractDataGetter<T extends VcsShortCommitDetails> implements Di
int index = myHashMap.getCommitIndex(data.getId(), data.getRoot());
result.put(index, data);
}
saveInCache(result);
if (saveInCache) saveInCache(result);
}
else {
LOG.error("No log provider for root " + entry.getKey().getPath() + ". All known log providers " + myLogProviders);
@@ -0,0 +1,69 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.vcs.log.data;
import com.intellij.util.Processor;
import com.intellij.util.io.PersistentMap;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class InMemoryMap<K, V> implements PersistentMap<K, V> {
private final Map<K, V> myMap = new HashMap<>();
@Override
public V get(K key) throws IOException {
return myMap.get(key);
}
@Override
public void put(K key, V value) throws IOException {
myMap.put(key, value);
}
@Override
public boolean processKeys(Processor<K> processor) throws IOException {
for (K key : myMap.keySet()) {
if (!processor.process(key)) {
return false;
}
}
return true;
}
@Override
public boolean isClosed() {
return false;
}
@Override
public boolean isDirty() {
return false;
}
@Override
public void force() {
}
@Override
public void close() throws IOException {
}
@Override
public void markDirty() throws IOException {
}
}
@@ -72,6 +72,7 @@ public class VcsLogData implements Disposable, VcsLogDataProvider {
@NotNull private final List<DataPackChangeListener> myDataPackChangeListeners = ContainerUtil.createLockFreeCopyOnWriteList();
@NotNull private final Consumer<Exception> myFatalErrorsConsumer;
@NotNull private final VcsLogIndex myIndex;
public VcsLogData(@NotNull Project project,
@NotNull Map<VirtualFile, VcsLogProvider> logProviders,
@@ -86,10 +87,11 @@ public class VcsLogData implements Disposable, VcsLogDataProvider {
myTopCommitsDetailsCache = new TopCommitsCache(myHashMap);
myMiniDetailsGetter = new MiniDetailsGetter(myHashMap, logProviders, myTopCommitsDetailsCache, this);
myDetailsGetter = new CommitDetailsGetter(myHashMap, logProviders, this);
myIndex = new VcsLogPersistentIndex(myProject, myHashMap, logProviders, myDetailsGetter, myFatalErrorsConsumer, this);
myRefresher =
new VcsLogRefresherImpl(myProject, myHashMap, myLogProviders, myUserRegistry, myTopCommitsDetailsCache,
dataPack -> fireDataPackChangeEvent(dataPack), e -> {
new VcsLogRefresherImpl(myProject, myHashMap, myLogProviders, myUserRegistry, myIndex, myTopCommitsDetailsCache,
this::fireDataPackChangeEvent, e -> {
if (!(e instanceof ProcessCanceledException)) {
LOG.error(e);
}
@@ -134,7 +136,7 @@ public class VcsLogData implements Disposable, VcsLogDataProvider {
@NotNull
public VisiblePackBuilder createVisiblePackBuilder() {
return new VisiblePackBuilder(myLogProviders, myHashMap, myTopCommitsDetailsCache, myDetailsGetter);
return new VisiblePackBuilder(myLogProviders, myHashMap, myTopCommitsDetailsCache, myDetailsGetter, myIndex);
}
@Override
@@ -290,4 +292,9 @@ public class VcsLogData implements Disposable, VcsLogDataProvider {
public TopCommitsCache getTopCommitsCache() {
return myTopCommitsDetailsCache;
}
@NotNull
public VcsLogIndex getIndex() {
return myIndex;
}
}
@@ -0,0 +1,33 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.vcs.log.data;
import com.intellij.vcs.log.VcsLogDetailsFilter;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Set;
public interface VcsLogIndex {
void scheduleIndex();
void markForIndexing(int commit);
boolean canFilter(@NotNull List<VcsLogDetailsFilter> filters);
@NotNull
Set<Integer> filter(@NotNull List<VcsLogDetailsFilter> detailsFilters);
}
@@ -0,0 +1,270 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.vcs.log.data;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.PerformInBackgroundOption;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Consumer;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.EnumeratorStringDescriptor;
import com.intellij.util.io.PersistentHashMap;
import com.intellij.util.io.PersistentMap;
import com.intellij.vcs.log.*;
import com.intellij.vcs.log.util.PersistentUtil;
import gnu.trove.TIntHashSet;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class VcsLogPersistentIndex implements VcsLogIndex, Disposable {
private static final Logger LOG = Logger.getInstance(VcsLogPersistentIndex.class);
private static final int BATCH_SIZE = 1000;
@NotNull private final Project myProject;
@NotNull private final Consumer<Exception> myFatalErrorsConsumer;
@NotNull private final CommitDetailsGetter myDetailsGetter;
@NotNull private final VcsLogStorage myHashMap;
@NotNull private final PersistentMap<Integer, String> myMessagesIndex;
@NotNull private TIntHashSet myCommitsToIndex = new TIntHashSet();
public VcsLogPersistentIndex(@NotNull Project project,
@NotNull VcsLogStorage hashMap,
@NotNull Map<VirtualFile, VcsLogProvider> providers,
@NotNull CommitDetailsGetter detailsGetter,
@NotNull Consumer<Exception> fatalErrorsConsumer,
@NotNull Disposable disposableParent) {
myHashMap = hashMap;
myProject = project;
myDetailsGetter = detailsGetter;
myFatalErrorsConsumer = fatalErrorsConsumer;
myMessagesIndex = createMessagesIndex(providers);
Disposer.register(disposableParent, this);
}
@NotNull
public PersistentMap<Integer, String> createMessagesIndex(@NotNull Map<VirtualFile, VcsLogProvider> providers) {
try {
return PersistentUtil
.createPersistentHashMap(EnumeratorStringDescriptor.INSTANCE, "messages", PersistentUtil.calcLogId(myProject, providers), 0);
}
catch (IOException e) {
myFatalErrorsConsumer.consume(e);
return new InMemoryMap<>();
}
}
@Override
public synchronized void scheduleIndex() {
if (myCommitsToIndex.isEmpty()) return;
TIntHashSet commitsToIndex = myCommitsToIndex;
myCommitsToIndex = new TIntHashSet();
Task.Backgroundable task = new MyIndexingTask(commitsToIndex);
ApplicationManager.getApplication().invokeLater(() -> {
BackgroundableProcessIndicator indicator = new BackgroundableProcessIndicator(task);
ProgressManager.getInstance().runProcessWithProgressAsynchronously(task, indicator);
}
);
}
private void index(@NotNull TIntHashSet commits) {
try {
storeDetails(TroveUtil.streamValues(myDetailsGetter.preLoadCommitData(commits, false)).collect(Collectors.toList()));
}
catch (VcsException e) {
LOG.error(e);
commits.forEach(value -> {
markForIndexing(value);
return true;
});
}
}
private void storeDetails(@NotNull Collection<VcsFullCommitDetails> details) {
for (VcsFullCommitDetails detail : details) {
try {
myMessagesIndex.put(myHashMap.getCommitIndex(detail.getId(), detail.getRoot()), detail.getFullMessage());
}
catch (IOException e) {
myFatalErrorsConsumer.consume(e);
}
}
myMessagesIndex.force();
}
public boolean isIndexed(int commit) {
try {
return myMessagesIndex.get(commit) != null;
}
catch (IOException e) {
myFatalErrorsConsumer.consume(e);
}
return false;
}
@Override
public synchronized void markForIndexing(int index) {
myCommitsToIndex.add(index);
}
@NotNull
private <T> TIntHashSet filter(@NotNull PersistentMap<Integer, T> map, @NotNull Condition<T> condition) {
TIntHashSet result = new TIntHashSet();
try {
Processor<Integer> processor = integer -> {
try {
T value = map.get(integer);
if (value != null) {
if (condition.value(value)) {
result.add(integer);
}
}
}
catch (IOException e) {
myFatalErrorsConsumer.consume(e);
return false;
}
return true;
};
if (myMessagesIndex instanceof PersistentHashMap) {
((PersistentHashMap<Integer, T>)myMessagesIndex).processKeysWithExistingMapping(processor);
}
else {
myMessagesIndex.processKeys(processor);
}
}
catch (IOException e) {
myFatalErrorsConsumer.consume(e);
}
return result;
}
@NotNull
public TIntHashSet filterMessages(@NotNull String text) {
return filter(myMessagesIndex, message -> StringUtil.containsIgnoreCase(message, text));
}
@Override
public boolean canFilter(@NotNull List<VcsLogDetailsFilter> filters) {
if (filters.isEmpty()) return false;
for (VcsLogDetailsFilter filter : filters) {
if (!(filter instanceof VcsLogTextFilter)) {
return false;
}
}
return true;
}
@Override
@NotNull
public Set<Integer> filter(@NotNull List<VcsLogDetailsFilter> detailsFilters) {
Set<Integer> result = ContainerUtil.newHashSet();
VcsLogTextFilter textFilter = ContainerUtil.findInstance(detailsFilters, VcsLogTextFilter.class);
if (textFilter != null) {
filterMessages(textFilter.getText()).forEach(value -> {
result.add(value);
return true;
});
}
return result;
}
@Override
public void dispose() {
try {
myMessagesIndex.close();
}
catch (IOException e) {
LOG.warn(e);
}
}
private class MyIndexingTask extends Task.Backgroundable {
private final TIntHashSet myCommits;
public MyIndexingTask(@NotNull TIntHashSet commits) {
super(VcsLogPersistentIndex.this.myProject, "Indexing Commit Data", true, PerformInBackgroundOption.ALWAYS_BACKGROUND);
myCommits = commits;
}
@Override
public void run(@NotNull ProgressIndicator indicator) {
indicator.setIndeterminate(false);
Ref<Integer> nCommit = new Ref<>(0);
Ref<Integer> indexedCommits = new Ref<>(0);
LOG.info("Indexing " + myCommits.size() + " commits");
long time = System.currentTimeMillis();
Ref<TIntHashSet> commitsBatch = new Ref<>(new TIntHashSet());
myCommits.forEach(commit -> {
if (!isIndexed(commit)) {
indexedCommits.set(indexedCommits.get() + 1);
commitsBatch.get().add(commit);
if (commitsBatch.get().size() >= BATCH_SIZE) {
try {
index(commitsBatch.get());
}
finally {
commitsBatch.set(new TIntHashSet());
}
}
}
nCommit.set(nCommit.get() + 1);
indicator.checkCanceled();
indicator.setFraction(((double)nCommit.get()) / myCommits.size());
return true;
});
if (!commitsBatch.get().isEmpty()) {
index(commitsBatch.get());
indicator.checkCanceled(); // TODO schedule for reindexing?
indicator.setFraction(((double)nCommit.get() * BATCH_SIZE) / myCommits.size());
}
LOG.info((System.currentTimeMillis() - time) / 1000.0 + "sec for indexing " + indexedCommits.get() + " new commits");
}
}
}
@@ -50,6 +50,7 @@ public class VcsLogRefresherImpl implements VcsLogRefresher {
@NotNull private final VcsLogStorage myHashMap;
@NotNull private final Map<VirtualFile, VcsLogProvider> myProviders;
@NotNull private final VcsUserRegistryImpl myUserRegistry;
@NotNull private final VcsLogIndex myIndex;
@NotNull private final TopCommitsCache myTopCommitsDetailsCache;
@NotNull private final Consumer<Exception> myExceptionHandler;
@NotNull private final VcsLogProgress myProgress;
@@ -64,6 +65,7 @@ public class VcsLogRefresherImpl implements VcsLogRefresher {
@NotNull VcsLogStorage hashMap,
@NotNull Map<VirtualFile, VcsLogProvider> providers,
@NotNull VcsUserRegistryImpl userRegistry,
@NotNull VcsLogIndex index,
@NotNull TopCommitsCache topCommitsDetailsCache,
@NotNull Consumer<DataPack> dataPackUpdateHandler,
@NotNull Consumer<Exception> exceptionHandler,
@@ -73,6 +75,7 @@ public class VcsLogRefresherImpl implements VcsLogRefresher {
myHashMap = hashMap;
myProviders = providers;
myUserRegistry = userRegistry;
myIndex = index;
myTopCommitsDetailsCache = topCommitsDetailsCache;
myExceptionHandler = exceptionHandler;
myRecentCommitCount = recentCommitsCount;
@@ -136,6 +139,7 @@ public class VcsLogRefresherImpl implements VcsLogRefresher {
}
}.iterate(getProvidersForRoots(requirements.keySet()));
myUserRegistry.flush();
myIndex.scheduleIndex();
sw.report();
return logInfo;
}
@@ -180,7 +184,9 @@ public class VcsLogRefresherImpl implements VcsLogRefresher {
private GraphCommitImpl<Integer> compactCommit(@NotNull TimedVcsCommit commit, @NotNull final VirtualFile root) {
List<Integer> parents = ContainerUtil.map(commit.getParents(),
(NotNullFunction<Hash, Integer>)hash -> myHashMap.getCommitIndex(hash, root));
return new GraphCommitImpl<>(myHashMap.getCommitIndex(commit.getId(), root), parents, commit.getTimestamp());
int index = myHashMap.getCommitIndex(commit.getId(), root);
myIndex.markForIndexing(index);
return new GraphCommitImpl<>(index, parents, commit.getTimestamp());
}
private void storeUsersAndDetails(@NotNull List<? extends VcsCommitMetadata> metadatas) {
@@ -357,6 +363,7 @@ public class VcsLogRefresherImpl implements VcsLogRefresher {
}
}.iterate(myProviders);
myUserRegistry.flush();
myIndex.scheduleIndex();
sw.report();
return logInfo;
}
@@ -44,15 +44,18 @@ class VisiblePackBuilder {
@NotNull private final TopCommitsCache myTopCommitsDetailsCache;
@NotNull private final DataGetter<VcsFullCommitDetails> myCommitDetailsGetter;
@NotNull private final Map<VirtualFile, VcsLogProvider> myLogProviders;
@NotNull private final VcsLogIndex myIndex;
VisiblePackBuilder(@NotNull Map<VirtualFile, VcsLogProvider> providers,
@NotNull VcsLogStorage hashMap,
@NotNull TopCommitsCache topCommitsDetailsCache,
@NotNull DataGetter<VcsFullCommitDetails> detailsGetter) {
@NotNull DataGetter<VcsFullCommitDetails> detailsGetter,
@NotNull VcsLogIndex index) {
myHashMap = hashMap;
myTopCommitsDetailsCache = topCommitsDetailsCache;
myCommitDetailsGetter = detailsGetter;
myLogProviders = providers;
myIndex = index;
}
@NotNull
@@ -67,29 +70,35 @@ class VisiblePackBuilder {
Set<Integer> matchingHeads = getMatchingHeads(dataPack.getRefsModel(), dataPack.getLogProviders().keySet(), filters);
List<VcsLogDetailsFilter> detailsFilters = filters.getDetailsFilters();
Collection<CommitId> matchingCommits = null;
Set<Integer> matchingCommits = null;
boolean canRequestMore = false;
if (!detailsFilters.isEmpty()) {
if (commitCount == CommitCountStage.INITIAL) {
matchingCommits = filterInMemory(dataPack.getPermanentGraph(), detailsFilters, matchingHeads);
if (matchingCommits.size() < commitCount.getCount()) {
commitCount = commitCount.next();
matchingCommits = null;
}
if (myIndex.canFilter(detailsFilters)) {
matchingCommits = myIndex.filter(detailsFilters);
canRequestMore = false;
}
else {
if (commitCount == CommitCountStage.INITIAL) {
matchingCommits = getMatchedCommitIndex(filterInMemory(dataPack.getPermanentGraph(), detailsFilters, matchingHeads));
if (matchingCommits.size() < commitCount.getCount()) {
commitCount = commitCount.next();
matchingCommits = null;
}
}
if (matchingCommits == null) {
try {
matchingCommits = getFilteredDetailsFromTheVcs(myLogProviders, filters, commitCount.getCount());
}
catch (VcsException e) {
// TODO show an error balloon or something else for non-ea guys.
matchingCommits = Collections.emptyList();
LOG.error(e);
if (matchingCommits == null) {
try {
matchingCommits = getMatchedCommitIndex(getFilteredDetailsFromTheVcs(myLogProviders, filters, commitCount.getCount()));
}
catch (VcsException e) {
//TODO show an error balloon or something else for non-ea guys.
matchingCommits = Collections.emptySet();
LOG.error(e);
}
}
canRequestMore = matchingCommits.size() >= commitCount.getCount(); // from VCS: only "==", but from memory can be ">"
}
canRequestMore = matchingCommits.size() >= commitCount.getCount(); // from VCS: only "==", but from memory can be ">"
}
VisibleGraph<Integer> visibleGraph;
@@ -97,7 +106,7 @@ class VisiblePackBuilder {
visibleGraph = EmptyVisibleGraph.getInstance();
}
else {
visibleGraph = dataPack.getPermanentGraph().createVisibleGraph(sortType, matchingHeads, getMatchedCommitIndex(matchingCommits));
visibleGraph = dataPack.getPermanentGraph().createVisibleGraph(sortType, matchingHeads, matchingCommits);
}
return Pair.create(new VisiblePack(dataPack, visibleGraph, canRequestMore, filters), commitCount);
}
@@ -69,4 +69,16 @@ public class PersistentUtil {
return IOUtil.openCleanOrResetBroken(() -> new PersistentBTreeEnumerator<>(storageFile, keyDescriptor, Page.PAGE_SIZE, null, version),
storageFile);
}
@NotNull
public static <V> PersistentHashMap<Integer, V> createPersistentHashMap(@NotNull DataExternalizer<V> externalizer,
@NotNull String storageKind,
@NotNull String logId,
int version) throws IOException {
File storageFile = getStorageFile(storageKind, logId, version);
return IOUtil
.openCleanOrResetBroken(() -> new PersistentHashMap<>(storageFile, new IntInlineKeyDescriptor(), externalizer, Page.PAGE_SIZE),
storageFile);
}
}
@@ -201,7 +201,7 @@ public class VcsLogRefresherTest extends VcsPlatformTest {
private VcsLogRefresherImpl createLoader(Consumer<DataPack> dataPackConsumer) {
myLogData = new VcsLogData(myProject, myLogProviders, LOG::error);
Disposer.register(myProject, myLogData);
return new VcsLogRefresherImpl(myProject, myLogData.getHashMap(), myLogProviders, myLogData.getUserRegistry(),
return new VcsLogRefresherImpl(myProject, myLogData.getHashMap(), myLogProviders, myLogData.getUserRegistry(), myLogData.getIndex(),
myLogData.getTopCommitsCache(), dataPackConsumer, FAILING_EXCEPTION_HANDLER, RECENT_COMMITS_COUNT,
myLogData) {
@Override
@@ -156,7 +156,7 @@ class VisiblePackBuilderTest {
return null
}
}
val builder = VisiblePackBuilder(providers, hashMap, detailsCache, commitDetailsGetter)
val builder = VisiblePackBuilder(providers, hashMap, detailsCache, commitDetailsGetter, EmptyIndex())
return builder.build(dataPack, PermanentGraph.SortType.Normal, filters, CommitCountStage.INITIAL).first
}
@@ -245,5 +245,21 @@ class VisiblePackBuilderTest {
override fun flush() {
}
}
class EmptyIndex : VcsLogIndex {
override fun canFilter(filters: MutableList<VcsLogDetailsFilter>): Boolean {
return false
}
override fun scheduleIndex() {
}
override fun markForIndexing(index: Int) {
}
override fun filter(detailsFilters: MutableList<VcsLogDetailsFilter>): MutableSet<Int> {
throw UnsupportedOperationException()
}
}
}