mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[log] IDEA-116470 Show "Contained in branches:" information in details
* Improve RefsModel to be able to find references by indices,
not hashes.
* Introduce ContainingBranchesGetter:
- holds a background queue to calculate the information asynchronously
- calculates by walking up the graph starting from the given nodes.
- saves the calculated value in the cache.
- if there is already information about some node in the cache,
reuse it and don't walk over this graph branch.
- clear the cache from VcsLogDataHolder on smart refresh,
if references have changed; and at any case during initial refresh.
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2000-2013 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.util.Condition;
|
||||
import com.intellij.util.ThrowableConsumer;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.SLRUMap;
|
||||
import com.intellij.vcs.log.VcsRef;
|
||||
import com.intellij.vcs.log.graph.elements.Edge;
|
||||
import com.intellij.vcs.log.graph.elements.Node;
|
||||
import com.intellij.vcs.log.util.SequentialLimitedLifoExecutor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Provides capabilities to asynchronously calculate "contained in branches" information.
|
||||
*/
|
||||
public class ContainingBranchesGetter {
|
||||
|
||||
@NotNull private final SequentialLimitedLifoExecutor<Task> myTaskExecutor;
|
||||
@NotNull private final Collection<Runnable> myLoadingFinishedListeners = new ArrayList<Runnable>();
|
||||
@NotNull private final SLRUMap<Node, List<VcsRef>> myCache = new SLRUMap<Node, List<VcsRef>>(1000, 1000);
|
||||
|
||||
ContainingBranchesGetter(@NotNull Disposable parentDisposable) {
|
||||
myTaskExecutor = new SequentialLimitedLifoExecutor<Task>(parentDisposable, 10, new ThrowableConsumer<Task, Throwable>() {
|
||||
@Override
|
||||
public void consume(Task task) throws Throwable {
|
||||
myCache.put(task.node, getContainingBranches(task.pack, task.node));
|
||||
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Runnable listener : myLoadingFinishedListeners) {
|
||||
listener.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void clearCache() {
|
||||
myCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* This task will be executed each time the calculating process completes.
|
||||
*/
|
||||
public void addTaskCompletedListener(@NotNull Runnable runnable) {
|
||||
myLoadingFinishedListeners.add(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the alphabetically sorted list of branches containing the specified node, if this information is ready;
|
||||
* if it is not available, starts calculating in the background and returns null.
|
||||
*/
|
||||
@Nullable
|
||||
public List<VcsRef> requestContainingBranches(@NotNull DataPack dataPack, @NotNull Node node) {
|
||||
List<VcsRef> refs = myCache.get(node);
|
||||
if (refs == null) {
|
||||
myTaskExecutor.queue(new Task(dataPack, node));
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<VcsRef> getContainingBranches(@NotNull DataPack dataPack, @NotNull Node node) {
|
||||
RefsModel refsModel = dataPack.getRefsModel();
|
||||
Set<VcsRef> containingBranches = ContainerUtil.newHashSet();
|
||||
|
||||
Set<Node> visitedNodes = ContainerUtil.newHashSet();
|
||||
Set<Node> nodesToCheck = ContainerUtil.newHashSet();
|
||||
nodesToCheck.add(node);
|
||||
while (!nodesToCheck.isEmpty()) {
|
||||
Iterator<Node> nodeIterator = nodesToCheck.iterator();
|
||||
Node nextNode = nodeIterator.next();
|
||||
nodeIterator.remove();
|
||||
|
||||
if (!visitedNodes.add(nextNode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Edge edge : nextNode.getUpEdges()) {
|
||||
Node upNode = edge.getUpNode();
|
||||
// optimization: the node is contained in all branches which contain its child => no need to walk over this graph branch
|
||||
List<VcsRef> upRefs = myCache.get(upNode);
|
||||
if (upRefs != null) {
|
||||
containingBranches.addAll(upRefs);
|
||||
}
|
||||
else {
|
||||
nodesToCheck.add(upNode);
|
||||
}
|
||||
}
|
||||
|
||||
containingBranches.addAll(getBranchesPointingToThisNode(refsModel, nextNode));
|
||||
}
|
||||
|
||||
return sortByName(containingBranches);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Collection<VcsRef> getBranchesPointingToThisNode(@NotNull RefsModel refsModel, @NotNull Node node) {
|
||||
return ContainerUtil.filter(refsModel.refsToCommit(node.getCommitIndex()), new Condition<VcsRef>() {
|
||||
@Override
|
||||
public boolean value(VcsRef ref) {
|
||||
return ref.getType().isBranch();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static List<VcsRef> sortByName(Collection<VcsRef> branches) {
|
||||
List<VcsRef> branchesList = new ArrayList<VcsRef>(branches);
|
||||
ContainerUtil.sort(branchesList, new Comparator<VcsRef>() {
|
||||
@Override
|
||||
public int compare(VcsRef o1, VcsRef o2) {
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
}
|
||||
});
|
||||
return branchesList;
|
||||
}
|
||||
|
||||
private static class Task {
|
||||
private final DataPack pack;
|
||||
private final Node node;
|
||||
public Task(DataPack pack, Node node) {
|
||||
this.pack = pack;
|
||||
this.node = node;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -52,7 +52,7 @@ public class DataPack {
|
||||
}
|
||||
});
|
||||
|
||||
final RefsModel refsModel = new RefsModel(allRefs, hashGetter);
|
||||
final RefsModel refsModel = new RefsModel(allRefs, indexGetter);
|
||||
graphModel.getFragmentManager().setUnconcealedNodeFunction(new Function<Node, Boolean>() {
|
||||
@NotNull
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.intellij.vcs.log.data;
|
||||
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.NotNullFunction;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.intellij.vcs.log.Hash;
|
||||
@@ -19,10 +19,11 @@ public class RefsModel {
|
||||
|
||||
@NotNull private final Collection<VcsRef> myBranches;
|
||||
@NotNull private final MultiMap<Hash, VcsRef> myRefsToHashes;
|
||||
private final Function<Integer, Hash> myHashGetter;
|
||||
@NotNull private final MultiMap<Integer, VcsRef> myRefsToIndices;
|
||||
@NotNull private final NotNullFunction<Hash, Integer> myIndexGetter;
|
||||
|
||||
public RefsModel(@NotNull Collection<VcsRef> allRefs, Function<Integer, Hash> hashGetter) {
|
||||
myHashGetter = hashGetter;
|
||||
public RefsModel(@NotNull Collection<VcsRef> allRefs, @NotNull NotNullFunction<Hash, Integer> indexGetter) {
|
||||
myIndexGetter = indexGetter;
|
||||
myBranches = ContainerUtil.filter(allRefs, new Condition<VcsRef>() {
|
||||
@Override
|
||||
public boolean value(VcsRef ref) {
|
||||
@@ -31,6 +32,16 @@ public class RefsModel {
|
||||
});
|
||||
|
||||
myRefsToHashes = prepareRefsMap(allRefs);
|
||||
myRefsToIndices = prepareRefsToIndicesMap(allRefs);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private MultiMap<Integer, VcsRef> prepareRefsToIndicesMap(@NotNull Collection<VcsRef> refs) {
|
||||
MultiMap<Integer, VcsRef> map = MultiMap.create();
|
||||
for (VcsRef ref : refs) {
|
||||
map.putValue(myIndexGetter.fun(ref.getCommitHash()), ref);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -42,8 +53,8 @@ public class RefsModel {
|
||||
return map;
|
||||
}
|
||||
|
||||
public boolean isBranchRef(@NotNull Hash commitHash) {
|
||||
for (VcsRef ref : refsToCommit(commitHash)) {
|
||||
public boolean isBranchRef(int hash) {
|
||||
for (VcsRef ref : refsToCommit(hash)) {
|
||||
if (ref.getType().isBranch()) {
|
||||
return true;
|
||||
}
|
||||
@@ -59,6 +70,11 @@ public class RefsModel {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Collection<VcsRef> refsToCommit(int index) {
|
||||
return myRefsToIndices.containsKey(index) ? myRefsToIndices.get(index) : Collections.<VcsRef>emptyList();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Collection<VcsRef> getBranches() {
|
||||
return myBranches;
|
||||
@@ -69,8 +85,4 @@ public class RefsModel {
|
||||
return new ArrayList<VcsRef>(myRefsToHashes.values());
|
||||
}
|
||||
|
||||
public boolean isBranchRef(int hash) {
|
||||
return isBranchRef(myHashGetter.fun(hash));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.intellij.openapi.progress.BackgroundTaskQueue;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
@@ -145,6 +146,7 @@ public class VcsLogDataHolder implements Disposable {
|
||||
private final VcsLogHashMap myHashMap;
|
||||
private final NotNullFunction<Integer, Hash> myHashGetter;
|
||||
private final NotNullFunction<Hash, Integer> myIndexGetter;
|
||||
private final ContainingBranchesGetter myContainingBranchesGetter;
|
||||
|
||||
public VcsLogDataHolder(@NotNull Project project,
|
||||
@NotNull Map<VirtualFile, VcsLogProvider> logProviders, @NotNull VcsLogSettings settings) {
|
||||
@@ -179,6 +181,7 @@ public class VcsLogDataHolder implements Disposable {
|
||||
return putHash(hash);
|
||||
}
|
||||
};
|
||||
myContainingBranchesGetter = new ContainingBranchesGetter(this);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -373,8 +376,12 @@ public class VcsLogDataHolder implements Disposable {
|
||||
VirtualFile root = entry.getKey();
|
||||
RecentCommitsInfo info = entry.getValue();
|
||||
|
||||
Pair<List<TimedVcsCommit>, Integer> joinResult = myLogJoiner.addCommits(myLogData.getLog(root), myLogData.getRefs(root),
|
||||
Collection<VcsRef> oldRefs = myLogData.getRefs(root);
|
||||
Pair<List<TimedVcsCommit>, Integer> joinResult = myLogJoiner.addCommits(myLogData.getLog(root), oldRefs,
|
||||
info.firstBlockCommits, info.newRefs);
|
||||
if (!Comparing.haveEqualElements(oldRefs, info.newRefs)) {
|
||||
myContainingBranchesGetter.clearCache();
|
||||
}
|
||||
List<TimedVcsCommit> refreshedLog = joinResult.getFirst();
|
||||
int newCommitsCount = joinResult.getSecond();
|
||||
// the value can significantly increase if user keeps IDEA open for a long time, and frequently receives many new commits,
|
||||
@@ -437,6 +444,7 @@ public class VcsLogDataHolder implements Disposable {
|
||||
myLogData = new LogData(logsToBuild, refsByRoot, compoundLog, dataPack, false);
|
||||
}
|
||||
|
||||
myContainingBranchesGetter.clearCache();
|
||||
handleOnSuccessInEdt(onSuccess, dataPack);
|
||||
}
|
||||
|
||||
@@ -550,6 +558,10 @@ public class VcsLogDataHolder implements Disposable {
|
||||
return mySettings;
|
||||
}
|
||||
|
||||
public ContainingBranchesGetter getContainingBranchesGetter() {
|
||||
return myContainingBranchesGetter;
|
||||
}
|
||||
|
||||
private static class RecentCommitsInfo {
|
||||
List<TimedVcsCommit> firstBlockCommits;
|
||||
Collection<VcsRef> newRefs;
|
||||
|
||||
@@ -2,16 +2,19 @@ package com.intellij.vcs.log.ui.frame;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkHtmlRenderer;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.ui.BrowserHyperlinkListener;
|
||||
import com.intellij.ui.ScrollPaneFactory;
|
||||
import com.intellij.ui.components.JBLoadingPanel;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.text.DateFormatUtil;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.intellij.vcs.log.Hash;
|
||||
import com.intellij.vcs.log.VcsFullCommitDetails;
|
||||
import com.intellij.vcs.log.VcsRef;
|
||||
import com.intellij.vcs.log.data.DataPack;
|
||||
import com.intellij.vcs.log.data.LoadingDetails;
|
||||
import com.intellij.vcs.log.data.VcsLogDataHolder;
|
||||
import com.intellij.vcs.log.graph.render.PrintParameters;
|
||||
@@ -82,21 +85,24 @@ class DetailsPanel extends JPanel implements ListSelectionListener {
|
||||
}
|
||||
else {
|
||||
((CardLayout)getLayout()).show(this, STANDARD_LAYER);
|
||||
Hash hash = ((AbstractVcsLogTableModel)myGraphTable.getModel()).getHashAtRow(rows[0]);
|
||||
int row = rows[0];
|
||||
Hash hash = ((AbstractVcsLogTableModel)myGraphTable.getModel()).getHashAtRow(row);
|
||||
if (hash == null) {
|
||||
showMessage("No commits selected");
|
||||
return;
|
||||
}
|
||||
|
||||
VcsFullCommitDetails commitData = myLogDataHolder.getCommitDetailsGetter().getCommitData(hash);
|
||||
if (commitData instanceof LoadingDetails) {
|
||||
DataPack dataPack = myLogDataHolder.getDataPack();
|
||||
List<VcsRef> branches = myLogDataHolder.getContainingBranchesGetter().requestContainingBranches(dataPack, dataPack.getNode(row));
|
||||
if (commitData instanceof LoadingDetails || branches == null) {
|
||||
myLoadingPanel.startLoading();
|
||||
myDataPanel.setData(null);
|
||||
myDataPanel.setData(null, null);
|
||||
myRefsPanel.setRefs(Collections.<VcsRef>emptyList());
|
||||
}
|
||||
else {
|
||||
myLoadingPanel.stopLoading();
|
||||
myDataPanel.setData(commitData);
|
||||
myDataPanel.setData(commitData, branches);
|
||||
myRefsPanel.setRefs(sortRefs(hash, commitData.getRoot()));
|
||||
}
|
||||
}
|
||||
@@ -123,19 +129,30 @@ class DetailsPanel extends JPanel implements ListSelectionListener {
|
||||
setEditable(false);
|
||||
myProject = project;
|
||||
addHyperlinkListener(new BrowserHyperlinkListener());
|
||||
setPreferredSize(new Dimension(150, 100));
|
||||
}
|
||||
|
||||
void setData(@Nullable VcsFullCommitDetails commit) {
|
||||
if (commit == null) {
|
||||
void setData(@Nullable VcsFullCommitDetails commit, @Nullable List<VcsRef> branches) {
|
||||
if (commit == null || branches == null) {
|
||||
setText("");
|
||||
}
|
||||
else {
|
||||
String body = getHashText(commit) + "<br/>" + getAuthorText(commit) + "<p>" + getMessageText(commit) + "</p>";
|
||||
String body = getHashText(commit) + "<br/>" + getAuthorText(commit) + "<p>" + getMessageText(commit) + "</p>" +
|
||||
"<p>" + getContainedBranchesText(branches) + "</p>";
|
||||
setText("<html><head>" + UIUtil.getCssFontDeclaration(UIUtil.getLabelFont()) + "</head><body>" + body + "</body></html>");
|
||||
setCaretPosition(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getContainedBranchesText(List<VcsRef> branches) {
|
||||
return "<i>Contained in branches:</i> " + StringUtil.join(branches, new Function<VcsRef, String>() {
|
||||
@Override
|
||||
public String fun(VcsRef ref) {
|
||||
return ref.getName();
|
||||
}
|
||||
}, ", ");
|
||||
}
|
||||
|
||||
private String getMessageText(VcsFullCommitDetails commit) {
|
||||
String subject = commit.getSubject();
|
||||
String description = subject.length() < commit.getFullMessage().length() ? commit.getFullMessage().substring(subject.length()) : "";
|
||||
|
||||
@@ -123,6 +123,12 @@ public class MainFrame extends JPanel implements TypeSafeDataProvider {
|
||||
myDetailsPanel.valueChanged(null);
|
||||
}
|
||||
});
|
||||
myLogDataHolder.getContainingBranchesGetter().addTaskCompletedListener(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
myDetailsPanel.valueChanged(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setupDetailsSplitter(boolean state) {
|
||||
|
||||
Reference in New Issue
Block a user