diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/cherrypick/VcsCherryPickAction.java b/platform/dvcs-impl/src/com/intellij/dvcs/cherrypick/VcsCherryPickAction.java index bd648865da74..cc7ed702028c 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/cherrypick/VcsCherryPickAction.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/cherrypick/VcsCherryPickAction.java @@ -21,6 +21,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; @@ -32,9 +33,9 @@ import com.intellij.openapi.vcs.VcsKey; import com.intellij.openapi.vcs.VcsNotifier; import com.intellij.openapi.vcs.changes.ChangeListManager; import com.intellij.openapi.vcs.changes.ChangeListManagerEx; +import com.intellij.util.Consumer; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.hash.HashMap; import com.intellij.vcs.log.Hash; import com.intellij.vcs.log.VcsFullCommitDetails; import com.intellij.vcs.log.VcsLog; @@ -57,39 +58,17 @@ public class VcsCherryPickAction extends DumbAwareAction { @Override public void actionPerformed(AnActionEvent e) { + FileDocumentManager.getInstance().saveAllDocuments(); + final Project project = e.getRequiredData(CommonDataKeys.PROJECT); VcsLog log = e.getRequiredData(VcsLogDataKeys.VCS_LOG); - final List commits = VcsLogUtil.collectLoadedSelectedDetails(log, false); - for (VcsFullCommitDetails commit : commits) { - myIdsInProgress.add(commit.getId()); - } - - FileDocumentManager.getInstance().saveAllDocuments(); - final ChangeListManagerEx changeListManagerEx = (ChangeListManagerEx)ChangeListManager.getInstance(project); - changeListManagerEx.blockModalNotifications(); - - new Task.Backgroundable(project, "Cherry-picking", false) { - public void run(@NotNull ProgressIndicator indicator) { - try { - List sortedCommits = sortCommits(commits); - Map> groupedCommits = groupByVcs(project, sortedCommits); - for (Map.Entry> entry : groupedCommits.entrySet()) { - entry.getKey().cherryPick(entry.getValue()); - } - } - finally { - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - changeListManagerEx.unblockModalNotifications(); - for (VcsFullCommitDetails commit : commits) { - myIdsInProgress.remove(commit.getId()); - } - } - }); - } + log.requestSelectedDetails(new Consumer>() { + @Override + public void consume(Set details) { + ProgressManager.getInstance().run(new CherryPickingTask(project, details)); } - }.queue(); + }, null); } /** @@ -101,27 +80,6 @@ public class VcsCherryPickAction extends DumbAwareAction { return commits; } - - private static Map> groupByVcs(@NotNull Project project, - @NotNull List commits) { - final ProjectLevelVcsManager projectLevelVcsManager = ProjectLevelVcsManager.getInstance(project); - Map> resultMap = new HashMap>(); - for (VcsFullCommitDetails commit : commits) { - VcsCherryPicker cherryPicker = getCherryPickerForCommit(project, projectLevelVcsManager, commit); - if (cherryPicker == null) { - VcsNotifier.getInstance(project).notifyWeakError( - "Cherry pick is not supported for commit " + commit.getId().toShortString() + " from root " + commit.getRoot().getName()); - return Collections.emptyMap(); - } - List list = resultMap.get(cherryPicker); - if (list == null) { - resultMap.put(cherryPicker, list = new ArrayList()); // ordered set!! - } - list.add(commit); - } - return resultMap; - } - @Nullable private static VcsCherryPicker getCherryPickerFor(@NotNull Project project, @NotNull final VcsKey key) { return ContainerUtil.find(Extensions.getExtensions(VcsCherryPicker.EXTENSION_POINT_NAME, project), new Condition() { @@ -191,4 +149,67 @@ public class VcsCherryPickAction extends DumbAwareAction { VcsKey key = vcs.getKeyInstanceMethod(); return getCherryPickerFor(project, key); } + + private class CherryPickingTask extends Task.Backgroundable { + private final Project myProject; + private final ProjectLevelVcsManager myProjectLevelVcsManager; + private final Map> myGroupedCommits = ContainerUtil.newHashMap(); + private final Collection myAllCommits; + private final ChangeListManagerEx myChangeListManagerEx; + + public CherryPickingTask(@NotNull Project project, @NotNull Set details) { + super(project, "Cherry-Picking"); + myProject = project; + myProjectLevelVcsManager = ProjectLevelVcsManager.getInstance(myProject); + myAllCommits = details; + myChangeListManagerEx = (ChangeListManagerEx)ChangeListManager.getInstance(myProject); + myChangeListManagerEx.blockModalNotifications(); + } + + public boolean processDetails(@NotNull VcsFullCommitDetails details) { + myIdsInProgress.add(details.getId()); + + VcsCherryPicker cherryPicker = getCherryPickerForCommit(myProject, myProjectLevelVcsManager, details); + if (cherryPicker == null) { + VcsNotifier.getInstance(myProject).notifyWeakError( + "Cherry pick is not supported for commit " + details.getId().toShortString() + " from root " + details.getRoot().getName()); + return false; + } + List list = myGroupedCommits.get(cherryPicker); + if (list == null) { + myGroupedCommits.put(cherryPicker, list = new ArrayList()); // ordered set!! + } + list.add(details); + return true; + } + + @Override + public void run(@NotNull ProgressIndicator indicator) { + try { + boolean isOk = true; + for (VcsFullCommitDetails details : myAllCommits) { + if (!processDetails(details)) { + isOk = false; + break; + } + } + + if (isOk) { + for (Map.Entry> entry : myGroupedCommits.entrySet()) { + entry.getKey().cherryPick(sortCommits(entry.getValue())); + } + } + } + finally { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + myChangeListManagerEx.unblockModalNotifications(); + for (VcsFullCommitDetails commit : myAllCommits) { + myIdsInProgress.remove(commit.getId()); + } + } + }); + } + } + } } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/ui/VcsLogAction.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/VcsLogAction.java index 936eda2291a7..51adc76500ca 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/ui/VcsLogAction.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/ui/VcsLogAction.java @@ -25,6 +25,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.vcs.VcsNotifier; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import com.intellij.vcs.log.VcsFullCommitDetails; @@ -34,17 +35,24 @@ import com.intellij.vcs.log.impl.VcsLogUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.List; +import java.util.Collection; +import java.util.Set; public abstract class VcsLogAction extends DumbAwareAction { @Override public void actionPerformed(@NotNull AnActionEvent e) { - Project project = e.getRequiredData(CommonDataKeys.PROJECT); + final Project project = e.getRequiredData(CommonDataKeys.PROJECT); VcsLog log = e.getRequiredData(VcsLogDataKeys.VCS_LOG); - MultiMap grouped = getGroupedCommits(project, log, false); - if (grouped == null) return; - actionPerformed(project, grouped); + + log.requestSelectedDetails(new Consumer>() { + @Override + public void consume(Set details) { + MultiMap grouped = groupCommits(project, details, false); + if (grouped == null) return; + actionPerformed(project, grouped); + } + }, null); } @Override @@ -88,7 +96,13 @@ public abstract class VcsLogAction extends DumbAwareAct @Nullable private MultiMap getGroupedCommits(@NotNull Project project, @NotNull VcsLog log, boolean fromUpdate) { - List commits = VcsLogUtil.collectLoadedSelectedDetails(log, fromUpdate); + return groupCommits(project, VcsLogUtil.collectLoadedSelectedDetails(log, fromUpdate), fromUpdate); + } + + @Nullable + private MultiMap groupCommits(@NotNull Project project, + Collection commits, + boolean fromUpdate) { MultiMap map = MultiMap.create(); for (VcsFullCommitDetails commit : commits) { Repo root = getRepositoryForRoot(project, commit.getRoot()); diff --git a/platform/vcs-impl/src/com/intellij/vcsUtil/VcsFileUtil.java b/platform/vcs-impl/src/com/intellij/vcsUtil/VcsFileUtil.java index f98646c30aab..9c8161ab40ca 100644 --- a/platform/vcs-impl/src/com/intellij/vcsUtil/VcsFileUtil.java +++ b/platform/vcs-impl/src/com/intellij/vcsUtil/VcsFileUtil.java @@ -15,6 +15,7 @@ */ package com.intellij.vcsUtil; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.SystemInfo; @@ -61,10 +62,8 @@ public class VcsFileUtil { List> chunks = chunkArguments(arguments); for (List chunk : chunks) { - if (ProgressManager.getInstance().getProgressIndicator() != null && - ProgressManager.getInstance().getProgressIndicator().isCanceled()) { - return result; - } + ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator != null) indicator.checkCanceled(); result.addAll(processor.fun(chunk)); } diff --git a/platform/vcs-log/api/src/com/intellij/vcs/log/VcsLog.java b/platform/vcs-log/api/src/com/intellij/vcs/log/VcsLog.java index 297c8d6c9ca4..2813efbf647d 100644 --- a/platform/vcs-log/api/src/com/intellij/vcs/log/VcsLog.java +++ b/platform/vcs-log/api/src/com/intellij/vcs/log/VcsLog.java @@ -15,12 +15,15 @@ */ package com.intellij.vcs.log; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.util.Consumer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.Collection; import java.util.List; +import java.util.Set; import java.util.concurrent.Future; /** @@ -41,6 +44,16 @@ public interface VcsLog { @NotNull List getSelectedDetails(); + /** + * Sends a request to load details that are currently selected. + * Details are loaded in background. If a progress indicator is specified it is used during loading process. + * After all details are loaded they are provided to the consumer in the EDT. + * + * @param consumer called in EDT after all details are loaded. + * @param indicator progress indicator to use in loading process, can be null. + */ + void requestSelectedDetails(@NotNull Consumer> consumer, @Nullable ProgressIndicator indicator); + /** * Returns names of branches which contain the given commit, or null if this information is unavailable. */ diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/AbstractDataGetter.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/AbstractDataGetter.java index 874fec2aba06..2e7c59c23441 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/AbstractDataGetter.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/AbstractDataGetter.java @@ -1,10 +1,16 @@ package com.intellij.vcs.log.data; import com.intellij.openapi.Disposable; +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.util.Computable; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; import com.intellij.util.Function; import com.intellij.util.ThrowableConsumer; import com.intellij.util.containers.ContainerUtil; @@ -20,23 +26,22 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.awt.*; -import java.util.ArrayList; -import java.util.Collection; +import java.util.*; import java.util.List; -import java.util.Map; /** * The DataGetter realizes the following pattern of getting some data (parametrized by {@code T}) from the VCS: *
    - *
  • it tries to get it from the cache;
  • - *
  • if it fails, it tries to get it from the VCS, and additionally loads several commits around the requested one, - * to avoid querying the VCS if user investigates details of nearby commits.
  • - *
  • The loading happens asynchronously: a fake {@link LoadingDetails} object is returned
  • + *
  • it tries to get it from the cache;
  • + *
  • if it fails, it tries to get it from the VCS, and additionally loads several commits around the requested one, + * to avoid querying the VCS if user investigates details of nearby commits.
  • + *
  • The loading happens asynchronously: a fake {@link LoadingDetails} object is returned
  • *
* * @author Kirill Likhodedov */ abstract class AbstractDataGetter implements Disposable, DataGetter { + private static final Logger LOG = Logger.getInstance(AbstractDataGetter.class); private static final int UP_PRELOAD_COUNT = 20; private static final int DOWN_PRELOAD_COUNT = 40; @@ -62,19 +67,23 @@ abstract class AbstractDataGetter implements Di myLogProviders = logProviders; myCache = cache; Disposer.register(parentDisposable, this); - myLoader = new SequentialLimitedLifoExecutor(this, MAX_LOADING_TASKS, - new ThrowableConsumer() { + myLoader = + new SequentialLimitedLifoExecutor(this, MAX_LOADING_TASKS, new ThrowableConsumer() { + @Override + public void consume(final TaskDescriptor task) throws VcsException { + preLoadCommitData(task.myCommits); + notifyLoaded(); + } + }); + } + + private void notifyLoaded() { + UIUtil.invokeAndWaitIfNeeded(new Runnable() { @Override - public void consume(TaskDescriptor task) throws VcsException { - preLoadCommitData(task.myCommits); - UIUtil.invokeAndWaitIfNeeded(new Runnable() { - @Override - public void run() { - for (Runnable loadingFinishedListener : myLoadingFinishedListeners) { - loadingFinishedListener.run(); - } - } - }); + public void run() { + for (Runnable loadingFinishedListener : myLoadingFinishedListeners) { + loadingFinishedListener.run(); + } } }); } @@ -85,7 +94,7 @@ abstract class AbstractDataGetter implements Di } @Override - @Nullable + @NotNull public T getCommitData(int row, @NotNull GraphTableModel tableModel) { assert EventQueue.isDispatchThread(); Integer hash = tableModel.getIdAtRow(row); @@ -93,8 +102,104 @@ abstract class AbstractDataGetter implements Di if (details != null) { return details; } - runLoadAroundCommitData(row, tableModel); - return myCache.get(hash); // now it is in the cache as "Loading Details". + + runLoadCommitsData(tableModel, createRowsIterable(row, UP_PRELOAD_COUNT, DOWN_PRELOAD_COUNT, tableModel.getRowCount())); + + T result = myCache.get(hash); + assert result != null; // now it is in the cache as "Loading Details" (runLoadCommitsData puts it there) + return result; + } + + private static Iterable createRowsIterable(final int row, final int above, final int below, final int maxRows) { + return new Iterable() { + @NotNull + @Override + public Iterator iterator() { + return new Iterator() { + private int myIndex = Math.max(0, row - above); + + @Override + public boolean hasNext() { + return myIndex < row + below && myIndex < maxRows; + } + + @Override + public Integer next() { + int next = myIndex; + myIndex++; + return next; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Removing elements is not supported."); + } + }; + } + }; + } + + @Override + public void loadCommitsData(@NotNull List rows, + @NotNull GraphTableModel tableModel, + @NotNull Consumer> consumer, + @Nullable ProgressIndicator indicator) { + assert EventQueue.isDispatchThread(); + loadCommitsData(getCommitsForRows(rows, tableModel), consumer, indicator); + } + + private void loadCommitsData(@NotNull final MultiMap commits, + @NotNull final Consumer> consumer, + @Nullable ProgressIndicator indicator) { + final Set result = ContainerUtil.newHashSet(); + final MultiMap toLoad = MultiMap.create(); + + long taskNumber = myCurrentTaskIndex++; + + for (VirtualFile root : commits.keySet()) { + Collection hashesForRoot = commits.get(root); + for (final Integer commitId : hashesForRoot) { + T details = getFromCache(commitId); + if (details == null || details instanceof LoadingDetails) { + toLoad.putValue(root, commitId); + cacheCommit(commitId, root, taskNumber); + } + else { + result.add(details); + } + } + } + + if (toLoad.isEmpty()) { + consumer.consume(result); + } + else { + Task.Backgroundable task = + new Task.Backgroundable(null, "Loading Selected Details", true, PerformInBackgroundOption.ALWAYS_BACKGROUND) { + @Override + public void run(@NotNull final ProgressIndicator indicator) { + indicator.checkCanceled(); + try { + result.addAll(preLoadCommitData(toLoad)); + notifyLoaded(); + } + catch (VcsException e) { + LOG.error(e); + } + } + + @Override + public void onSuccess() { + consumer.consume(result); + } + }; + if (indicator != null) { + ProgressManager.getInstance().runProcessWithProgressAsynchronously(task, indicator); + } + else { + ProgressManager.getInstance().run(task); + } + } } @Override @@ -125,47 +230,48 @@ abstract class AbstractDataGetter implements Di @Nullable protected abstract T getFromAdditionalCache(int commitId); - private void runLoadAroundCommitData(int row, @NotNull GraphTableModel tableModel) { + private void runLoadCommitsData(@NotNull GraphTableModel tableModel, @NotNull Iterable rows) { long taskNumber = myCurrentTaskIndex++; - MultiMap commits = getCommitsAround(row, tableModel, UP_PRELOAD_COUNT, DOWN_PRELOAD_COUNT); + MultiMap commits = getCommitsForRows(rows, tableModel); for (Map.Entry> hashesByRoots : commits.entrySet()) { VirtualFile root = hashesByRoots.getKey(); Collection hashes = hashesByRoots.getValue(); - // fill the cache with temporary "Loading" values to avoid producing queries for each commit that has not been cached yet, - // even if it will be loaded within a previous query for (final int commitId : hashes) { - if (!myCache.isKeyCached(commitId)) { - myCache.put(commitId, (T)new LoadingDetails(new Computable(){ - - @Override - public Hash compute() { - return myHashMap.getCommitId(commitId).getHash(); - } - }, taskNumber, root)); - } + cacheCommit(commitId, root, taskNumber); } } - TaskDescriptor task = new TaskDescriptor(commits); - myLoader.queue(task); + myLoader.queue(new TaskDescriptor(commits)); + } + + private void cacheCommit(final int commitId, VirtualFile root, long taskNumber) { + // fill the cache with temporary "Loading" values to avoid producing queries for each commit that has not been cached yet, + // even if it will be loaded within a previous query + if (!myCache.isKeyCached(commitId)) { + myCache.put(commitId, (T)new LoadingDetails(new Computable() { + + @Override + public Hash compute() { + return myHashMap.getCommitId(commitId).getHash(); + } + }, taskNumber, root)); + } } @NotNull - private static MultiMap getCommitsAround(int selectedRow, - @NotNull GraphTableModel model, - int above, - int below) { + private static MultiMap getCommitsForRows(@NotNull Iterable rows, @NotNull GraphTableModel model) { MultiMap commits = MultiMap.create(); - for (int row = Math.max(0, selectedRow - above); row < selectedRow + below && row < model.getRowCount(); row++) { - Integer hash = model.getIdAtRow(row); + for (int row : rows) { + Integer commitId = model.getIdAtRow(row); VirtualFile root = model.getRoot(row); - commits.putValue(root, hash); + commits.putValue(root, commitId); } return commits; } - private void preLoadCommitData(@NotNull MultiMap commits) throws VcsException { + private Set preLoadCommitData(@NotNull MultiMap commits) throws VcsException { + Set result = ContainerUtil.newHashSet(); for (Map.Entry> entry : commits.entrySet()) { List hashStrings = ContainerUtil.map(entry.getValue(), new Function() { @Override @@ -174,8 +280,10 @@ abstract class AbstractDataGetter implements Di } }); List details = readDetails(myLogProviders.get(entry.getKey()), entry.getKey(), hashStrings); + result.addAll(details); saveInCache(details); } + return result; } public void saveInCache(final List details) { @@ -202,11 +310,10 @@ abstract class AbstractDataGetter implements Di } private static class TaskDescriptor { - private final MultiMap myCommits; + @NotNull private final MultiMap myCommits; - private TaskDescriptor(MultiMap commits) { + private TaskDescriptor(@NotNull MultiMap commits) { myCommits = commits; } } - } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/DataGetter.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/DataGetter.java index 4da4b90a5ed7..1e4b485c102c 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/DataGetter.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/DataGetter.java @@ -15,15 +15,25 @@ */ package com.intellij.vcs.log.data; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.util.Consumer; import com.intellij.vcs.log.VcsShortCommitDetails; import com.intellij.vcs.log.ui.tables.GraphTableModel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; +import java.util.Set; + public interface DataGetter { @Nullable T getCommitData(int row, @NotNull GraphTableModel tableModel); + void loadCommitsData(@NotNull List rows, + @NotNull GraphTableModel tableModel, + @NotNull Consumer> consumer, + @Nullable ProgressIndicator indicator); + @Nullable T getCommitDataIfAvailable(int hash); } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogImpl.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogImpl.java index d62eb930726c..a3be80855d75 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogImpl.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogImpl.java @@ -15,7 +15,11 @@ */ package com.intellij.vcs.log.impl; +import com.google.common.primitives.Ints; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.util.Condition; +import com.intellij.ui.table.JBTable; +import com.intellij.util.Consumer; import com.intellij.util.containers.ContainerUtil; import com.intellij.vcs.log.*; import com.intellij.vcs.log.data.VcsLogDataHolder; @@ -25,9 +29,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.awt.*; -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.Collection; +import java.util.*; import java.util.List; import java.util.concurrent.Future; @@ -35,6 +37,7 @@ import java.util.concurrent.Future; * */ public class VcsLogImpl implements VcsLog { + private static final int SLICE_SIZE = 50; @NotNull private final VcsLogDataHolder myDataHolder; @NotNull private final VcsLogUiImpl myUi; @@ -79,6 +82,12 @@ public class VcsLogImpl implements VcsLog { }; } + @Override + public void requestSelectedDetails(@NotNull Consumer> consumer, @Nullable ProgressIndicator indicator) { + List rowsList = Ints.asList(myUi.getTable().getSelectedRows()); + myDataHolder.getCommitDetailsGetter().loadCommitsData(rowsList, (GraphTableModel)myUi.getTable().getModel(), consumer, indicator); + } + @Nullable @Override public Collection getContainingBranches(@NotNull Hash commitHash) { diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/DetailsPanel.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/DetailsPanel.java index 7a2784a3ddf6..392a4be00560 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/DetailsPanel.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/DetailsPanel.java @@ -168,10 +168,6 @@ class DetailsPanel extends JPanel implements ListSelectionListener { int row = rows[0]; GraphTableModel tableModel = (GraphTableModel)myGraphTable.getModel(); VcsFullCommitDetails commitData = myLogDataHolder.getCommitDetailsGetter().getCommitData(row, tableModel); - if (commitData == null) { - showMessage("No commits selected"); - return; - } if (commitData instanceof LoadingDetails) { myLoadingPanel.startLoading(); myCommitDetailsPanel.setData(null); diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java index 66b2fd0bee33..de2c00977bd5 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java @@ -4,6 +4,8 @@ import com.intellij.icons.AllIcons; import com.intellij.ide.actions.RefreshAction; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; @@ -12,6 +14,7 @@ import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vcs.VcsDataKeys; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.TextRevisionNumber; +import com.intellij.openapi.vcs.changes.committed.CommittedChangesTreeBrowser; import com.intellij.openapi.vcs.changes.committed.RepositoryChangesBrowser; import com.intellij.openapi.vcs.changes.ui.ChangesBrowser; import com.intellij.openapi.vcs.history.VcsRevisionNumber; @@ -19,6 +22,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.ScrollPaneFactory; import com.intellij.ui.components.JBLoadingPanel; import com.intellij.util.ArrayUtil; +import com.intellij.util.Consumer; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; @@ -44,6 +48,7 @@ import javax.swing.event.ListSelectionListener; import java.awt.*; import java.util.*; import java.util.List; +import java.util.Set; import static com.intellij.util.ObjectUtils.assertNotNull; import static com.intellij.util.containers.ContainerUtil.getFirstItem; @@ -154,7 +159,6 @@ public class MainFrame extends JPanel implements TypeSafeDataProvider { myLogDataHolder.getCommitDetailsGetter().addDetailsLoadedListener(new Runnable() { @Override public void run() { - selectionChangeListener.valueChanged(null); myDetailsPanel.valueChanged(null); } }); @@ -291,11 +295,6 @@ public class MainFrame extends JPanel implements TypeSafeDataProvider { myBranchesPanel.setVisible(visible); } - @Nullable - public List getSelectedChanges() { - return myGraphTable.getSelectedChanges(); - } - @Override public void calcData(DataKey key, DataSink sink) { if (VcsLogDataKeys.VCS_LOG == key) { @@ -308,7 +307,7 @@ public class MainFrame extends JPanel implements TypeSafeDataProvider { sink.put(key, myLogDataHolder); } else if (VcsDataKeys.CHANGES == key || VcsDataKeys.SELECTED_CHANGES == key) { - List selectedChanges = getSelectedChanges(); + List selectedChanges = myGraphTable.getSelectedChanges(); if (selectedChanges != null) { sink.put(key, ArrayUtil.toObjectArray(selectedChanges, Change.class)); } @@ -363,6 +362,7 @@ public class MainFrame extends JPanel implements TypeSafeDataProvider { private class CommitSelectionListener implements ListSelectionListener { private final ChangesBrowser myChangesBrowser; + private ProgressIndicator myLastRequest; public CommitSelectionListener(ChangesBrowser changesBrowser) { myChangesBrowser = changesBrowser; @@ -371,23 +371,38 @@ public class MainFrame extends JPanel implements TypeSafeDataProvider { @Override public void valueChanged(@Nullable ListSelectionEvent event) { if (event != null && event.getValueIsAdjusting()) return; + + if (myLastRequest != null) myLastRequest.cancel(); + myLastRequest = null; + int rows = getGraphTable().getSelectedRowCount(); - if (rows < 1 || rows > MAX_SELECTED_COMMITS) { + if (rows < 1) { myChangesLoadingPane.stopLoading(); - myChangesBrowser.getViewer().setEmptyText(rows < 1 ? "" : "Too many commits selected."); + myChangesBrowser.getViewer().setEmptyText(""); myChangesBrowser.setChangesToDisplay(Collections.emptyList()); } else { - List selectedChanges = getSelectedChanges(); - if (selectedChanges != null) { - myChangesLoadingPane.stopLoading(); - myChangesBrowser.setChangesToDisplay(selectedChanges); - } - else { - myChangesBrowser.setChangesToDisplay(Collections.emptyList()); - setDefaultEmptyText(myChangesBrowser); - myChangesLoadingPane.startLoading(); - } + myChangesBrowser.setChangesToDisplay(Collections.emptyList()); + setDefaultEmptyText(myChangesBrowser); + myChangesLoadingPane.startLoading(); + + final EmptyProgressIndicator indicator = new EmptyProgressIndicator(); + myLastRequest = indicator; + myLog.requestSelectedDetails(new Consumer>() { + @Override + public void consume(Set detailsList) { + if (myLastRequest == indicator && !(indicator.isCanceled())) { + myLastRequest = null; + List changes = ContainerUtil.newArrayList(); + for (VcsFullCommitDetails details : detailsList) { + changes.addAll(details.getChanges()); + } + changes = CommittedChangesTreeBrowser.zipChanges(changes); + myChangesLoadingPane.stopLoading(); + myChangesBrowser.setChangesToDisplay(changes); + } + } + }, indicator); } } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/tables/GraphTableModel.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/tables/GraphTableModel.java index 4c88002ae2fa..f8a958ff7e99 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/tables/GraphTableModel.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/tables/GraphTableModel.java @@ -152,16 +152,6 @@ public class GraphTableModel extends AbstractTableModel { return COLUMN_COUNT; } - @Nullable - private VcsShortCommitDetails getShortDetails(int rowIndex) { - return myLogDataHolder.getMiniDetailsGetter().getCommitData(rowIndex, this); - } - - @Nullable - public VcsFullCommitDetails getFullCommitDetails(int rowIndex) { - return myLogDataHolder.getCommitDetailsGetter().getCommitData(rowIndex, this); - } - /** * Requests the proper data provider to load more data from the log & recreate the model. * @@ -180,7 +170,7 @@ public class GraphTableModel extends AbstractTableModel { requestToLoadMore(EmptyRunnable.INSTANCE); } - VcsShortCommitDetails data = getShortDetails(rowIndex); + VcsShortCommitDetails data = myLogDataHolder.getMiniDetailsGetter().getCommitData(rowIndex, this); switch (columnIndex) { case ROOT_COLUMN: return getRoot(rowIndex); @@ -225,8 +215,8 @@ public class GraphTableModel extends AbstractTableModel { public List getSelectedChanges(@NotNull List selectedRows) { List changes = new ArrayList(); for (int row : selectedRows) { - VcsFullCommitDetails commitData = getFullCommitDetails(row); - if (commitData == null || commitData instanceof LoadingDetails) { + VcsFullCommitDetails commitData = myLogDataHolder.getCommitDetailsGetter().getCommitData(row, this); + if (commitData instanceof LoadingDetails) { return null; } changes.addAll(commitData.getChanges()); diff --git a/platform/vcs-log/impl/test/com/intellij/vcs/log/data/VisiblePackBuilderTest.kt b/platform/vcs-log/impl/test/com/intellij/vcs/log/data/VisiblePackBuilderTest.kt index 592fca3eb5a9..cae52f9b911f 100644 --- a/platform/vcs-log/impl/test/com/intellij/vcs/log/data/VisiblePackBuilderTest.kt +++ b/platform/vcs-log/impl/test/com/intellij/vcs/log/data/VisiblePackBuilderTest.kt @@ -16,8 +16,10 @@ package com.intellij.vcs.log.data import com.intellij.mock.MockVirtualFile +import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.util.Condition import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.Consumer import com.intellij.util.Function import com.intellij.vcs.log.* import com.intellij.vcs.log.graph.GraphCommit @@ -149,11 +151,14 @@ class VisiblePackBuilderTest { val commitDetailsGetter = object : DataGetter { override fun getCommitData(row: Int, tableModel: GraphTableModel): VcsFullCommitDetails? { - return null; + return null + } + + override fun loadCommitsData(rows: MutableList, tableModel: GraphTableModel, consumer: Consumer>, indicator: ProgressIndicator?) { } override fun getCommitDataIfAvailable(hash: Int): VcsFullCommitDetails? { - return null; + return null } } val builder = VisiblePackBuilder(providers, hashMap, detailsCache, commitDetailsGetter)